diff --git a/Makefile b/Makefile index 38fadcec4..3743c4543 100644 --- a/Makefile +++ b/Makefile @@ -159,7 +159,7 @@ undeploy: manifests ## Undeploy operator CONTROLLER_GEN = $(shell pwd)/bin/controller-gen controller-gen: ## Download controller-gen locally if necessary. - $(call go-get-tool,$(CONTROLLER_GEN),sigs.k8s.io/controller-tools/cmd/controller-gen@v0.16.3) + $(call go-get-tool,$(CONTROLLER_GEN),sigs.k8s.io/controller-tools/cmd/controller-gen@v0.18.0) KUSTOMIZE = $(shell pwd)/bin/kustomize kustomize: ## Download kustomize locally if necessary. diff --git a/api/v1alpha1/perconaservermysql_types.go b/api/v1alpha1/perconaservermysql_types.go index f2067eaec..31167be8c 100644 --- a/api/v1alpha1/perconaservermysql_types.go +++ b/api/v1alpha1/perconaservermysql_types.go @@ -189,8 +189,9 @@ type PodSpec struct { SchedulerName string `json:"schedulerName,omitempty"` RuntimeClassName *string `json:"runtimeClassName,omitempty"` - PodSecurityContext *corev1.PodSecurityContext `json:"podSecurityContext,omitempty"` - ServiceAccountName string `json:"serviceAccountName,omitempty"` + PodSecurityContext *corev1.PodSecurityContext `json:"podSecurityContext,omitempty"` + ServiceAccountName string `json:"serviceAccountName,omitempty"` + PodDisruptionBudget *PodDisruptionBudgetSpec `json:"podDisruptionBudget,omitempty"` Configuration string `json:"configuration,omitempty"` diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index 06c7984bd..b55706030 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -899,6 +899,11 @@ func (in *PodSpec) DeepCopyInto(out *PodSpec) { *out = new(corev1.PodSecurityContext) (*in).DeepCopyInto(*out) } + if in.PodDisruptionBudget != nil { + in, out := &in.PodDisruptionBudget, &out.PodDisruptionBudget + *out = new(PodDisruptionBudgetSpec) + (*in).DeepCopyInto(*out) + } in.ContainerSpec.DeepCopyInto(&out.ContainerSpec) } @@ -961,7 +966,7 @@ func (in *ServiceExpose) DeepCopyInto(out *ServiceExpose) { } if in.InternalTrafficPolicy != nil { in, out := &in.InternalTrafficPolicy, &out.InternalTrafficPolicy - *out = new(corev1.ServiceInternalTrafficPolicy) + *out = new(corev1.ServiceInternalTrafficPolicyType) **out = **in } } diff --git a/config/crd/bases/ps.percona.com_perconaservermysqlbackups.yaml b/config/crd/bases/ps.percona.com_perconaservermysqlbackups.yaml index 2c49132d5..c69d56bea 100644 --- a/config/crd/bases/ps.percona.com_perconaservermysqlbackups.yaml +++ b/config/crd/bases/ps.percona.com_perconaservermysqlbackups.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.16.3 + controller-gen.kubebuilder.io/version: v0.18.0 name: perconaservermysqlbackups.ps.percona.com spec: group: ps.percona.com diff --git a/config/crd/bases/ps.percona.com_perconaservermysqlrestores.yaml b/config/crd/bases/ps.percona.com_perconaservermysqlrestores.yaml index 78fba4602..637c006f8 100644 --- a/config/crd/bases/ps.percona.com_perconaservermysqlrestores.yaml +++ b/config/crd/bases/ps.percona.com_perconaservermysqlrestores.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.16.3 + controller-gen.kubebuilder.io/version: v0.18.0 name: perconaservermysqlrestores.ps.percona.com spec: group: ps.percona.com diff --git a/config/crd/bases/ps.percona.com_perconaservermysqls.yaml b/config/crd/bases/ps.percona.com_perconaservermysqls.yaml index 38a71a851..cb43540cd 100644 --- a/config/crd/bases/ps.percona.com_perconaservermysqls.yaml +++ b/config/crd/bases/ps.percona.com_perconaservermysqls.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.16.3 + controller-gen.kubebuilder.io/version: v0.18.0 name: perconaservermysqls.ps.percona.com spec: group: ps.percona.com @@ -877,6 +877,19 @@ spec: additionalProperties: type: string type: object + podDisruptionBudget: + properties: + maxUnavailable: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + minAvailable: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + type: object podSecurityContext: properties: appArmorProfile: @@ -3110,6 +3123,19 @@ spec: additionalProperties: type: string type: object + podDisruptionBudget: + properties: + maxUnavailable: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + minAvailable: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + type: object podSecurityContext: properties: appArmorProfile: @@ -5888,6 +5914,19 @@ spec: additionalProperties: type: string type: object + podDisruptionBudget: + properties: + maxUnavailable: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + minAvailable: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + type: object podSecurityContext: properties: appArmorProfile: @@ -7227,6 +7266,19 @@ spec: additionalProperties: type: string type: object + podDisruptionBudget: + properties: + maxUnavailable: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + minAvailable: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + type: object podSecurityContext: properties: appArmorProfile: @@ -8442,6 +8494,19 @@ spec: additionalProperties: type: string type: object + podDisruptionBudget: + properties: + maxUnavailable: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + minAvailable: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + type: object podSecurityContext: properties: appArmorProfile: diff --git a/config/rbac/cluster/role.yaml b/config/rbac/cluster/role.yaml index 75bede646..2ddbd9afc 100644 --- a/config/rbac/cluster/role.yaml +++ b/config/rbac/cluster/role.yaml @@ -94,6 +94,16 @@ rules: - list - patch - watch +- apiGroups: + - policy + resources: + - poddisruptionbudgets + verbs: + - create + - get + - list + - patch + - update - apiGroups: - ps.percona.com resources: diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index e701af7fc..7bb504ed6 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -94,6 +94,16 @@ rules: - list - patch - watch +- apiGroups: + - policy + resources: + - poddisruptionbudgets + verbs: + - create + - get + - list + - patch + - update - apiGroups: - ps.percona.com resources: diff --git a/deploy/bundle.yaml b/deploy/bundle.yaml index 817a5cd65..96cf491c6 100644 --- a/deploy/bundle.yaml +++ b/deploy/bundle.yaml @@ -2,7 +2,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.16.3 + controller-gen.kubebuilder.io/version: v0.18.0 labels: app.kubernetes.io/component: crd app.kubernetes.io/name: percona-server-crd @@ -962,7 +962,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.16.3 + controller-gen.kubebuilder.io/version: v0.18.0 labels: app.kubernetes.io/component: crd app.kubernetes.io/name: percona-server-crd @@ -1921,7 +1921,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.16.3 + controller-gen.kubebuilder.io/version: v0.18.0 labels: app.kubernetes.io/component: crd app.kubernetes.io/name: percona-server-crd @@ -2800,6 +2800,19 @@ spec: additionalProperties: type: string type: object + podDisruptionBudget: + properties: + maxUnavailable: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + minAvailable: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + type: object podSecurityContext: properties: appArmorProfile: @@ -5033,6 +5046,19 @@ spec: additionalProperties: type: string type: object + podDisruptionBudget: + properties: + maxUnavailable: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + minAvailable: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + type: object podSecurityContext: properties: appArmorProfile: @@ -7811,6 +7837,19 @@ spec: additionalProperties: type: string type: object + podDisruptionBudget: + properties: + maxUnavailable: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + minAvailable: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + type: object podSecurityContext: properties: appArmorProfile: @@ -9150,6 +9189,19 @@ spec: additionalProperties: type: string type: object + podDisruptionBudget: + properties: + maxUnavailable: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + minAvailable: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + type: object podSecurityContext: properties: appArmorProfile: @@ -10365,6 +10417,19 @@ spec: additionalProperties: type: string type: object + podDisruptionBudget: + properties: + maxUnavailable: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + minAvailable: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + type: object podSecurityContext: properties: appArmorProfile: @@ -11641,6 +11706,16 @@ rules: - list - patch - watch +- apiGroups: + - policy + resources: + - poddisruptionbudgets + verbs: + - create + - get + - list + - patch + - update - apiGroups: - ps.percona.com resources: diff --git a/deploy/cr.yaml b/deploy/cr.yaml index 78af79a94..c45a8ef4c 100644 --- a/deploy/cr.yaml +++ b/deploy/cr.yaml @@ -58,6 +58,9 @@ spec: # env: # - name: BOOTSTRAP_READ_TIMEOUT # value: "600" + podDisruptionBudget: + maxUnavailable: 1 +# minAvailable: 0 resources: requests: memory: 2G @@ -190,6 +193,9 @@ spec: # - name: "my-secret-1" # - name: "my-secret-2" + podDisruptionBudget: + maxUnavailable: 1 +# minAvailable: 0 resources: requests: memory: 1G @@ -339,6 +345,9 @@ spec: size: 3 + podDisruptionBudget: + maxUnavailable: 1 +# minAvailable: 0 resources: requests: memory: 256M @@ -436,6 +445,9 @@ spec: # loadBalancerSourceRanges: # - 10.0.0.0/8 + podDisruptionBudget: + maxUnavailable: 1 +# minAvailable: 0 gracePeriod: 30 resources: diff --git a/deploy/crd.yaml b/deploy/crd.yaml index 11599fa9c..6da943cc1 100644 --- a/deploy/crd.yaml +++ b/deploy/crd.yaml @@ -2,7 +2,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.16.3 + controller-gen.kubebuilder.io/version: v0.18.0 labels: app.kubernetes.io/component: crd app.kubernetes.io/name: percona-server-crd @@ -962,7 +962,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.16.3 + controller-gen.kubebuilder.io/version: v0.18.0 labels: app.kubernetes.io/component: crd app.kubernetes.io/name: percona-server-crd @@ -1921,7 +1921,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.16.3 + controller-gen.kubebuilder.io/version: v0.18.0 labels: app.kubernetes.io/component: crd app.kubernetes.io/name: percona-server-crd @@ -2800,6 +2800,19 @@ spec: additionalProperties: type: string type: object + podDisruptionBudget: + properties: + maxUnavailable: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + minAvailable: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + type: object podSecurityContext: properties: appArmorProfile: @@ -5033,6 +5046,19 @@ spec: additionalProperties: type: string type: object + podDisruptionBudget: + properties: + maxUnavailable: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + minAvailable: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + type: object podSecurityContext: properties: appArmorProfile: @@ -7811,6 +7837,19 @@ spec: additionalProperties: type: string type: object + podDisruptionBudget: + properties: + maxUnavailable: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + minAvailable: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + type: object podSecurityContext: properties: appArmorProfile: @@ -9150,6 +9189,19 @@ spec: additionalProperties: type: string type: object + podDisruptionBudget: + properties: + maxUnavailable: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + minAvailable: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + type: object podSecurityContext: properties: appArmorProfile: @@ -10365,6 +10417,19 @@ spec: additionalProperties: type: string type: object + podDisruptionBudget: + properties: + maxUnavailable: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + minAvailable: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + type: object podSecurityContext: properties: appArmorProfile: diff --git a/deploy/cw-bundle.yaml b/deploy/cw-bundle.yaml index e8659ef47..e10853e35 100644 --- a/deploy/cw-bundle.yaml +++ b/deploy/cw-bundle.yaml @@ -2,7 +2,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.16.3 + controller-gen.kubebuilder.io/version: v0.18.0 labels: app.kubernetes.io/component: crd app.kubernetes.io/name: percona-server-crd @@ -962,7 +962,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.16.3 + controller-gen.kubebuilder.io/version: v0.18.0 labels: app.kubernetes.io/component: crd app.kubernetes.io/name: percona-server-crd @@ -1921,7 +1921,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.16.3 + controller-gen.kubebuilder.io/version: v0.18.0 labels: app.kubernetes.io/component: crd app.kubernetes.io/name: percona-server-crd @@ -2800,6 +2800,19 @@ spec: additionalProperties: type: string type: object + podDisruptionBudget: + properties: + maxUnavailable: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + minAvailable: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + type: object podSecurityContext: properties: appArmorProfile: @@ -5033,6 +5046,19 @@ spec: additionalProperties: type: string type: object + podDisruptionBudget: + properties: + maxUnavailable: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + minAvailable: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + type: object podSecurityContext: properties: appArmorProfile: @@ -7811,6 +7837,19 @@ spec: additionalProperties: type: string type: object + podDisruptionBudget: + properties: + maxUnavailable: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + minAvailable: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + type: object podSecurityContext: properties: appArmorProfile: @@ -9150,6 +9189,19 @@ spec: additionalProperties: type: string type: object + podDisruptionBudget: + properties: + maxUnavailable: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + minAvailable: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + type: object podSecurityContext: properties: appArmorProfile: @@ -10365,6 +10417,19 @@ spec: additionalProperties: type: string type: object + podDisruptionBudget: + properties: + maxUnavailable: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + minAvailable: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + type: object podSecurityContext: properties: appArmorProfile: @@ -11641,6 +11706,16 @@ rules: - list - patch - watch +- apiGroups: + - policy + resources: + - poddisruptionbudgets + verbs: + - create + - get + - list + - patch + - update - apiGroups: - ps.percona.com resources: diff --git a/deploy/cw-rbac.yaml b/deploy/cw-rbac.yaml index 1322f95f6..77fc4015f 100644 --- a/deploy/cw-rbac.yaml +++ b/deploy/cw-rbac.yaml @@ -135,6 +135,16 @@ rules: - list - patch - watch +- apiGroups: + - policy + resources: + - poddisruptionbudgets + verbs: + - create + - get + - list + - patch + - update - apiGroups: - ps.percona.com resources: diff --git a/deploy/rbac.yaml b/deploy/rbac.yaml index 1972d0537..05ceed848 100644 --- a/deploy/rbac.yaml +++ b/deploy/rbac.yaml @@ -135,6 +135,16 @@ rules: - list - patch - watch +- apiGroups: + - policy + resources: + - poddisruptionbudgets + verbs: + - create + - get + - list + - patch + - update - apiGroups: - ps.percona.com resources: diff --git a/pkg/controller/ps/controller.go b/pkg/controller/ps/controller.go index 665a67e88..965bca633 100644 --- a/pkg/controller/ps/controller.go +++ b/pkg/controller/ps/controller.go @@ -78,6 +78,7 @@ type PerconaServerMySQLReconciler struct { //+kubebuilder:rbac:groups="",resources=configmaps;services;secrets,verbs=get;list;watch;create;update;patch;delete //+kubebuilder:rbac:groups="",resources=pods;pods/exec,verbs=get;list;watch;create;update;patch;delete;deletecollection //+kubebuilder:rbac:groups="",resources=events,verbs=create;patch +//+kubebuilder:rbac:groups=policy,resources=poddisruptionbudgets,verbs=create;get;list;patch;update //+kubebuilder:rbac:groups=apps,resources=statefulsets;deployments,verbs=get;list;watch;create;update;patch;delete //+kubebuilder:rbac:groups=certmanager.k8s.io;cert-manager.io,resources=issuers;certificates,verbs=get;list;watch;create;update;patch;delete;deletecollection //+kubebuilder:rbac:groups="",resources=serviceaccounts,verbs=get;list;watch;create;patch @@ -535,31 +536,21 @@ func validateClusterType(ctx context.Context, cl client.Client, cr *apiv1alpha1. func (r *PerconaServerMySQLReconciler) reconcileDatabase(ctx context.Context, cr *apiv1alpha1.PerconaServerMySQL) error { log := logf.FromContext(ctx).WithName("reconcileDatabase") - configurable := mysql.Configurable(*cr) - configHash, err := r.reconcileCustomConfiguration(ctx, cr, &configurable) - if err != nil { - return errors.Wrap(err, "reconcile MySQL config") - } - - tlsHash, err := getTLSHash(ctx, r.Client, cr) - if err != nil { - return errors.Wrap(err, "failed to get tls hash") - } - - if err = r.reconcileMySQLAutoConfig(ctx, cr); err != nil { + if err := r.reconcileMySQLAutoConfig(ctx, cr); err != nil { return errors.Wrap(err, "reconcile MySQL auto-config") } - initImage, err := k8s.InitImage(ctx, r.Client, cr, &cr.Spec.MySQL.PodSpec) - if err != nil { - return errors.Wrap(err, "get init image") + component := mysql.Component(*cr) + if err := k8s.EnsureComponent(ctx, r.Client, &component); err != nil { + return errors.Wrap(err, "ensure component") } internalSecret := new(corev1.Secret) - nn := types.NamespacedName{Name: cr.InternalSecretName(), Namespace: cr.Namespace} - err = r.Client.Get(ctx, nn, internalSecret) - if client.IgnoreNotFound(err) != nil { - return errors.Wrapf(err, "get Secret/%s", nn.Name) + if err := r.Get(ctx, types.NamespacedName{ + Name: cr.InternalSecretName(), + Namespace: cr.Namespace, + }, internalSecret); client.IgnoreNotFound(err) != nil { + return errors.Wrapf(err, "get internal secret") } if cr.PVCResizeInProgress() { @@ -567,17 +558,18 @@ func (r *PerconaServerMySQLReconciler) reconcileDatabase(ctx context.Context, cr return nil } - sts := mysql.StatefulSet(cr, initImage, configHash, tlsHash, internalSecret) - - if err := k8s.EnsureObjectWithHash(ctx, r.Client, cr, sts, r.Scheme); err != nil { - return errors.Wrap(err, "reconcile sts") - } - if pmm := cr.Spec.PMM; pmm != nil && pmm.Enabled && !pmm.HasSecret(internalSecret) { log.Info(fmt.Sprintf(`Can't enable PMM: either "%s" key doesn't exist in the secrets, or secrets and internal secrets are out of sync`, apiv1alpha1.UserPMMServerToken), "secrets", cr.Spec.SecretsName, "internalSecrets", cr.InternalSecretName()) } + sts := new(appsv1.StatefulSet) + if err := r.Get(ctx, types.NamespacedName{ + Name: component.Name(), + Namespace: cr.Namespace, + }, sts); err != nil { + return errors.Wrap(err, "get statefulset") + } if cr.Spec.UpdateStrategy == apiv1alpha1.SmartUpdateStatefulSetStrategyType { log.Info("Performing smart update for StatefulSet") return r.smartUpdate(ctx, sts, cr) @@ -775,18 +767,9 @@ func (r *PerconaServerMySQLReconciler) reconcileOrchestrator(ctx context.Context return errors.Wrap(err, "reconcile ConfigMap") } - initImage, err := k8s.InitImage(ctx, r.Client, cr, &cr.Spec.Orchestrator.PodSpec) - if err != nil { - return errors.Wrap(err, "get init image") - } - - tlsHash, err := getTLSHash(ctx, r.Client, cr) - if err != nil { - return errors.Wrap(err, "failed to get tls hash") - } - - if err := k8s.EnsureObjectWithHash(ctx, r.Client, cr, orchestrator.StatefulSet(cr, initImage, tlsHash), r.Scheme); err != nil { - return errors.Wrap(err, "reconcile StatefulSet") + component := orchestrator.Component(*cr) + if err := k8s.EnsureComponent(ctx, r.Client, &component); err != nil { + return errors.Wrap(err, "ensure component") } raftNodes := orchestrator.RaftNodes(cr) @@ -848,17 +831,6 @@ func (r *PerconaServerMySQLReconciler) reconcileHAProxy(ctx context.Context, cr return nil } - configurable := haproxy.Configurable(*cr) - configHash, err := r.reconcileCustomConfiguration(ctx, cr, &configurable) - if err != nil { - return errors.Wrap(err, "reconcile HAProxy config") - } - - tlsHash, err := getTLSHash(ctx, r.Client, cr) - if err != nil { - return errors.Wrap(err, "failed to get tls hash") - } - nn := types.NamespacedName{Namespace: cr.Namespace, Name: mysql.PodName(cr, 0)} firstMySQLPodReady, err := k8s.IsPodWithNameReady(ctx, r.Client, nn) if err != nil { @@ -870,20 +842,9 @@ func (r *PerconaServerMySQLReconciler) reconcileHAProxy(ctx context.Context, cr return nil } - initImage, err := k8s.InitImage(ctx, r.Client, cr, &cr.Spec.Proxy.HAProxy.PodSpec) - if err != nil { - return errors.Wrap(err, "get init image") - } - - internalSecret := new(corev1.Secret) - nn = types.NamespacedName{Name: cr.InternalSecretName(), Namespace: cr.Namespace} - err = r.Client.Get(ctx, nn, internalSecret) - if client.IgnoreNotFound(err) != nil { - return errors.Wrapf(err, "get Secret/%s", nn.Name) - } - - if err := k8s.EnsureObjectWithHash(ctx, r.Client, cr, haproxy.StatefulSet(cr, initImage, configHash, tlsHash, internalSecret), r.Scheme); err != nil { - return errors.Wrap(err, "reconcile StatefulSet") + component := haproxy.Component(*cr) + if err := k8s.EnsureComponent(ctx, r.Client, &component); err != nil { + return errors.Wrap(err, "ensure component") } return nil @@ -1279,17 +1240,6 @@ func (r *PerconaServerMySQLReconciler) reconcileMySQLRouter(ctx context.Context, return nil } - configurable := router.Configurable(*cr) - configHash, err := r.reconcileCustomConfiguration(ctx, cr, &configurable) - if err != nil { - return errors.Wrap(err, "reconcile Router config") - } - - tlsHash, err := getTLSHash(ctx, r.Client, cr) - if err != nil { - return errors.Wrap(err, "failed to get tls hash") - } - if cr.Spec.Proxy.Router.Size > 0 { if cr.Status.MySQL.Ready != cr.Spec.MySQL.Size { log.V(1).Info("Waiting for MySQL pods to be ready") @@ -1316,13 +1266,9 @@ func (r *PerconaServerMySQLReconciler) reconcileMySQLRouter(ctx context.Context, } } - initImage, err := k8s.InitImage(ctx, r.Client, cr, &cr.Spec.Proxy.Router.PodSpec) - if err != nil { - return errors.Wrap(err, "get init image") - } - - if err := k8s.EnsureObjectWithHash(ctx, r.Client, cr, router.Deployment(cr, initImage, configHash, tlsHash), r.Scheme); err != nil { - return errors.Wrap(err, "reconcile Deployment") + component := router.Component(*cr) + if err := k8s.EnsureComponent(ctx, r.Client, &component); err != nil { + return errors.Wrap(err, "ensure component") } return nil diff --git a/pkg/controller/ps/controller_test.go b/pkg/controller/ps/controller_test.go index dc9549144..9b1c6da71 100644 --- a/pkg/controller/ps/controller_test.go +++ b/pkg/controller/ps/controller_test.go @@ -28,18 +28,23 @@ import ( gs "github.com/onsi/gomega/gstruct" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" + policyv1 "k8s.io/api/policy/v1" storagev1 "k8s.io/api/storage/v1" k8serrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/intstr" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" psv1alpha1 "github.com/percona/percona-server-mysql-operator/api/v1alpha1" + "github.com/percona/percona-server-mysql-operator/pkg/haproxy" + "github.com/percona/percona-server-mysql-operator/pkg/innodbcluster" "github.com/percona/percona-server-mysql-operator/pkg/mysql" "github.com/percona/percona-server-mysql-operator/pkg/naming" + "github.com/percona/percona-server-mysql-operator/pkg/orchestrator" ) var _ = Describe("Sidecars", Ordered, func() { @@ -306,6 +311,162 @@ var _ = Describe("Unsafe configurations", Ordered, func() { }) }) +var _ = Describe("PodDisruptionBudget", Ordered, func() { + ctx := context.Background() + + crName := "pdb" + ns := crName + crNamespacedName := types.NamespacedName{Name: crName, Namespace: ns} + + namespace := &corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: crName, + Namespace: ns, + }, + } + + var r *PerconaServerMySQLReconciler + + BeforeAll(func() { + By("Creating the Namespace to perform the tests") + err := k8sClient.Create(ctx, namespace) + Expect(err).To(Not(HaveOccurred())) + }) + + AfterAll(func() { + By("Deleting the Namespace to perform the tests") + _ = k8sClient.Delete(ctx, namespace) + }) + + Context("Check default cluster", Ordered, func() { + cr, err := readDefaultCR(crName, ns) + It("should prepare reconciler", func() { + r = reconciler() + Expect(err).To(Succeed()) + cliCmd, err := getFakeClient(cr, innodbcluster.ClusterStatusOK, []innodbcluster.MemberState{ + innodbcluster.MemberStateOnline, + innodbcluster.MemberStateOnline, + innodbcluster.MemberStateOnline, + }, false, true) + Expect(err).To(Succeed()) + r.ClientCmd = cliCmd + const operatorPass = "test" + secret := &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: cr.InternalSecretName(), + Namespace: cr.Namespace, + }, + Data: map[string][]byte{ + string(psv1alpha1.UserOperator): []byte(operatorPass), + }, + } + Expect(k8sClient.Create(ctx, secret)).Should(Succeed()) + }) + + It("should create cr.yaml", func() { + cr.Spec.MySQL.ClusterType = psv1alpha1.ClusterTypeAsync + cr.Spec.Unsafe.Orchestrator = true + cr.Spec.Unsafe.Proxy = true + cr.Spec.MySQL.PodDisruptionBudget = &psv1alpha1.PodDisruptionBudgetSpec{ + MaxUnavailable: &intstr.IntOrString{ + Type: intstr.Int, + IntVal: 20, + }, + } + cr.Spec.Proxy.Router.Enabled = false + cr.Spec.Proxy.HAProxy.Enabled = true + cr.Spec.Proxy.HAProxy.PodDisruptionBudget = &psv1alpha1.PodDisruptionBudgetSpec{ + MinAvailable: &intstr.IntOrString{ + Type: intstr.Int, + IntVal: 12, + }, + } + cr.Spec.Orchestrator.Enabled = true + cr.Spec.Orchestrator.PodDisruptionBudget = &psv1alpha1.PodDisruptionBudgetSpec{ + MaxUnavailable: &intstr.IntOrString{ + Type: intstr.Int, + IntVal: 11, + }, + } + Expect(k8sClient.Create(ctx, cr)).Should(Succeed()) + }) + + It("should create MySQL pods", func() { + for _, pod := range makeFakeReadyPods(cr, 3, "mysql") { + status := pod.(*corev1.Pod).Status + Expect(k8sClient.Create(ctx, pod)).Should(Succeed()) + p := new(corev1.Pod) + Expect(k8sClient.Get(ctx, client.ObjectKeyFromObject(pod), p)).Should(Succeed()) + p.Status = status + Expect(k8sClient.Status().Update(ctx, p)).Should(Succeed()) + } + }) + + When("HAProxy is enabled", Ordered, func() { + It("should reconcile", func() { + _, err := r.Reconcile(ctx, ctrl.Request{NamespacedName: crNamespacedName}) + Expect(err).NotTo(HaveOccurred()) + }) + It("should check PodDisruptionBudget for MySQL", func() { + pdb := &policyv1.PodDisruptionBudget{ + ObjectMeta: metav1.ObjectMeta{ + Name: cr.Name + "-mysql", + Namespace: cr.Namespace, + }, + } + + Eventually(func() bool { + err := k8sClient.Get(ctx, client.ObjectKeyFromObject(pdb), pdb) + return err == nil + }, time.Second*15, time.Millisecond*250).Should(BeTrue()) + + Expect(pdb.Labels).To(Equal(mysql.MatchLabels(cr))) + Expect(pdb.Spec.Selector.MatchLabels).To(Equal(mysql.MatchLabels(cr))) + + Expect(pdb.Spec.MaxUnavailable.IntVal).To(Equal(int32(20))) + }) + + It("should check PodDisruptionBudget for HAProxy", func() { + pdb := &policyv1.PodDisruptionBudget{ + ObjectMeta: metav1.ObjectMeta{ + Name: cr.Name + "-haproxy", + Namespace: cr.Namespace, + }, + } + + Eventually(func() bool { + err := k8sClient.Get(ctx, client.ObjectKeyFromObject(pdb), pdb) + return err == nil + }, time.Second*15, time.Millisecond*250).Should(BeTrue()) + + Expect(pdb.Labels).To(Equal(haproxy.MatchLabels(cr))) + Expect(pdb.Spec.Selector.MatchLabels).To(Equal(haproxy.MatchLabels(cr))) + + Expect(pdb.Spec.MinAvailable.IntVal).To(Equal(int32(12))) + }) + + It("should check PodDisruptionBudget for Orchestrator", func() { + pdb := &policyv1.PodDisruptionBudget{ + ObjectMeta: metav1.ObjectMeta{ + Name: cr.Name + "-orchestrator", + Namespace: cr.Namespace, + }, + } + + Eventually(func() bool { + err := k8sClient.Get(ctx, client.ObjectKeyFromObject(pdb), pdb) + return err == nil + }, time.Second*15, time.Millisecond*250).Should(BeTrue()) + + Expect(pdb.Labels).To(Equal(orchestrator.MatchLabels(cr))) + Expect(pdb.Spec.Selector.MatchLabels).To(Equal(orchestrator.MatchLabels(cr))) + + Expect(pdb.Spec.MaxUnavailable.IntVal).To(Equal(int32(11))) + }) + }) + }) +}) + var _ = Describe("Reconcile HAProxy when async cluster type", Ordered, func() { ctx := context.Background() diff --git a/pkg/controller/ps/status_test.go b/pkg/controller/ps/status_test.go index c1127ab0c..1bcfd0e11 100644 --- a/pkg/controller/ps/status_test.go +++ b/pkg/controller/ps/status_test.go @@ -641,7 +641,7 @@ func TestReconcileStatusHAProxyGR(t *testing.T) { t.Run(tt.name, func(t *testing.T) { cr := tt.cr.DeepCopy() cb := fake.NewClientBuilder().WithScheme(scheme).WithObjects(cr).WithStatusSubresource(cr).WithObjects(tt.objects...).WithStatusSubresource(tt.objects...) - cliCmd, err := getFakeClient(cr, tt.innodbClusterState, tt.mysqlMemberStates, tt.noMetadataDB) + cliCmd, err := getFakeClient(cr, tt.innodbClusterState, tt.mysqlMemberStates, tt.noMetadataDB, false) if err != nil { t.Fatal(err) } @@ -910,7 +910,7 @@ func TestReconcileStatusRouterGR(t *testing.T) { t.Run(tt.name, func(t *testing.T) { cr := tt.cr.DeepCopy() cb := fake.NewClientBuilder().WithScheme(scheme).WithObjects(cr).WithStatusSubresource(cr).WithObjects(tt.objects...).WithStatusSubresource(tt.objects...) - cliCmd, err := getFakeClient(cr, tt.innodbClusterState, tt.mysqlMemberStates, tt.noMetadataDB) + cliCmd, err := getFakeClient(cr, tt.innodbClusterState, tt.mysqlMemberStates, tt.noMetadataDB, false) if err != nil { t.Fatal(err) } @@ -1016,8 +1016,9 @@ func TestReconcileErrorStatus(t *testing.T) { } type fakeClient struct { - scripts []fakeClientScript - execCount int + scripts []fakeClientScript + execCount int + disableCheck bool } // Exec increments the internal counter `execCount`. @@ -1029,7 +1030,7 @@ func (c *fakeClient) Exec(_ context.Context, _ *corev1.Pod, _ string, command [] if c.execCount >= len(c.scripts) { return errors.Errorf("unexpected exec call") } - if !reflect.DeepEqual(c.scripts[c.execCount].cmd, command) { + if !reflect.DeepEqual(c.scripts[c.execCount].cmd, command) && !c.disableCheck { return errors.Errorf("expected command: %v; got %v", c.scripts[c.execCount].cmd, command) } var in []byte @@ -1040,7 +1041,7 @@ func (c *fakeClient) Exec(_ context.Context, _ *corev1.Pod, _ string, command [] return err } } - if !reflect.DeepEqual(in, c.scripts[c.execCount].stdin) { + if !reflect.DeepEqual(in, c.scripts[c.execCount].stdin) && !c.disableCheck { return errors.Errorf("expected stdin: %v; got %v", c.scripts[c.execCount].stdin, in) } _, err = stdout.Write(c.scripts[c.execCount].stdout) @@ -1080,7 +1081,7 @@ func getFakeClient( cr *apiv1alpha1.PerconaServerMySQL, innodbClusterStatus innodbcluster.ClusterStatus, mysqlMemberStates []innodbcluster.MemberState, - noMetadataDB bool, + noMetadataDB bool, disableCheck bool, ) (clientcmd.Client, error) { queryScript := func(query string, out any) fakeClientScript { buf := new(bytes.Buffer) @@ -1174,7 +1175,8 @@ func getFakeClient( }) return &fakeClient{ - scripts: scripts, + scripts: scripts, + disableCheck: disableCheck, }, nil } diff --git a/pkg/controller/ps/tls.go b/pkg/controller/ps/tls.go index ee8c71948..3820ab208 100644 --- a/pkg/controller/ps/tls.go +++ b/pkg/controller/ps/tls.go @@ -87,27 +87,6 @@ func (r *PerconaServerMySQLReconciler) ensureManualTLS(ctx context.Context, cr * return nil } -func getTLSHash(ctx context.Context, cl client.Client, cr *apiv1alpha1.PerconaServerMySQL) (string, error) { - secret := new(corev1.Secret) - err := cl.Get(ctx, types.NamespacedName{ - Name: cr.Spec.SSLSecretName, - Namespace: cr.Namespace, - }, secret) - if err != nil { - if k8serrors.IsNotFound(err) { - return "", nil - } - return "", errors.Wrap(err, "get secret") - } - - hash, err := k8s.ObjectHash(secret) - if err != nil { - return "", errors.Wrap(err, "get secret hash") - } - - return hash, nil -} - func (r *PerconaServerMySQLReconciler) checkTLSIssuer(ctx context.Context, cr *apiv1alpha1.PerconaServerMySQL) error { if cr.Spec.TLS == nil || cr.Spec.TLS.IssuerConf == nil { return nil diff --git a/pkg/haproxy/component.go b/pkg/haproxy/component.go new file mode 100644 index 000000000..6da5429f0 --- /dev/null +++ b/pkg/haproxy/component.go @@ -0,0 +1,64 @@ +package haproxy + +import ( + "context" + + "github.com/pkg/errors" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" + + apiv1alpha1 "github.com/percona/percona-server-mysql-operator/api/v1alpha1" + "github.com/percona/percona-server-mysql-operator/pkg/k8s" +) + +type Component apiv1alpha1.PerconaServerMySQL + +func (c *Component) Name() string { + cr := c.PerconaServerMySQL() + return Name(cr) +} + +func (c *Component) PerconaServerMySQL() *apiv1alpha1.PerconaServerMySQL { + cr := apiv1alpha1.PerconaServerMySQL(*c) + return &cr +} + +func (c *Component) Labels() map[string]string { + cr := c.PerconaServerMySQL() + return MatchLabels(cr) +} + +func (c *Component) PodSpec() *apiv1alpha1.PodSpec { + return &c.Spec.Proxy.HAProxy.PodSpec +} + +func (c *Component) Object(ctx context.Context, cl client.Client) (client.Object, error) { + cr := c.PerconaServerMySQL() + + initImage, err := k8s.InitImage(ctx, cl, cr, c.PodSpec()) + if err != nil { + return nil, errors.Wrap(err, "get init image") + } + + internalSecret := new(corev1.Secret) + if err := cl.Get(ctx, types.NamespacedName{ + Name: cr.InternalSecretName(), + Namespace: cr.Namespace, + }, internalSecret); client.IgnoreNotFound(err) != nil { + return nil, errors.Wrapf(err, "get internal secret") + } + + configurable := Configurable(*cr) + configHash, err := k8s.CustomConfigHash(ctx, cl, cr, &configurable) + if err != nil { + return nil, errors.Wrapf(err, "get custom config hash") + } + + tlsHash, err := k8s.GetTLSHash(ctx, cl, cr) + if err != nil { + return nil, errors.Wrapf(err, "get tls hash") + } + + return StatefulSet(cr, initImage, configHash, tlsHash, internalSecret), nil +} diff --git a/pkg/controller/ps/configuration.go b/pkg/k8s/configuration.go similarity index 75% rename from pkg/controller/ps/configuration.go rename to pkg/k8s/configuration.go index 7713e25f4..6249fd0ee 100644 --- a/pkg/controller/ps/configuration.go +++ b/pkg/k8s/configuration.go @@ -1,4 +1,4 @@ -package ps +package k8s import ( "context" @@ -14,11 +14,10 @@ import ( "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" logf "sigs.k8s.io/controller-runtime/pkg/log" apiv1alpha1 "github.com/percona/percona-server-mysql-operator/api/v1alpha1" - "github.com/percona/percona-server-mysql-operator/pkg/k8s" - "github.com/percona/percona-server-mysql-operator/pkg/mysql" ) type Configurable interface { @@ -29,18 +28,18 @@ type Configurable interface { ExecuteConfigurationTemplate(configuration string, memory *resource.Quantity) (string, error) } -func (r *PerconaServerMySQLReconciler) reconcileCustomConfiguration(ctx context.Context, cr *apiv1alpha1.PerconaServerMySQL, configurable Configurable) (string, error) { - log := logf.FromContext(ctx).WithName("reconcileCustomConfiguration") +func CustomConfigHash(ctx context.Context, cl client.Client, cr *apiv1alpha1.PerconaServerMySQL, configurable Configurable) (string, error) { + log := logf.FromContext(ctx).WithName("CustomConfigHash") cmName := configurable.GetConfigMapName() nn := types.NamespacedName{Name: cmName, Namespace: cr.Namespace} currCm := &corev1.ConfigMap{} - if err := r.Client.Get(ctx, nn, currCm); err != nil && !k8serrors.IsNotFound(err) { + if err := cl.Get(ctx, nn, currCm); err != nil && !k8serrors.IsNotFound(err) { return "", errors.Wrapf(err, "get ConfigMap/%s", cmName) } if configurable.GetConfiguration() == "" { - exists, err := k8s.ObjectExists(ctx, r.Client, nn, currCm) + exists, err := ObjectExists(ctx, cl, nn, currCm) if err != nil { return "", errors.Wrapf(err, "check if ConfigMap/%s exists", cmName) } @@ -50,12 +49,12 @@ func (r *PerconaServerMySQLReconciler) reconcileCustomConfiguration(ctx context. } if exists && !metav1.IsControlledBy(currCm, cr) { - //ConfigMap exists and is created by the user, not the operator + // ConfigMap exists and is created by the user, not the operator d := struct{ Data map[string]string }{Data: currCm.Data} data, err := json.Marshal(d) - if cmName == cr.Name+"-mysql" && currCm.Data[mysql.CustomConfigKey] == "" { + if cmName == cr.Name+"-mysql" && currCm.Data["my.cnf"] == "" { return "", errors.New("Failed to update config map. Please use my.cnf as a config name. Only in this case config map will be applied to the cluster") } @@ -66,7 +65,7 @@ func (r *PerconaServerMySQLReconciler) reconcileCustomConfiguration(ctx context. return fmt.Sprintf("%x", md5.Sum(data)), nil } - if err := r.Client.Delete(ctx, currCm); err != nil { + if err := cl.Delete(ctx, currCm); err != nil { return "", errors.Wrapf(err, "delete ConfigMaps/%s", cmName) } @@ -96,9 +95,9 @@ func (r *PerconaServerMySQLReconciler) reconcileCustomConfiguration(ctx context. return "", errors.New("resources.limits[memory] or resources.requests[memory] should be specified for template usage in configuration") } - cm := k8s.ConfigMap(cmName, cr.Namespace, configurable.GetConfigMapKey(), configuration) + cm := ConfigMap(cmName, cr.Namespace, configurable.GetConfigMapKey(), configuration) if !reflect.DeepEqual(currCm.Data, cm.Data) { - if err := k8s.EnsureObject(ctx, r.Client, cr, cm, r.Scheme); err != nil { + if err := EnsureObject(ctx, cl, cr, cm, cl.Scheme()); err != nil { return "", errors.Wrapf(err, "ensure ConfigMap/%s", cmName) } diff --git a/pkg/k8s/pdb.go b/pkg/k8s/pdb.go new file mode 100644 index 000000000..939e27946 --- /dev/null +++ b/pkg/k8s/pdb.go @@ -0,0 +1,26 @@ +package k8s + +import ( + policyv1 "k8s.io/api/policy/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + apiv1alpha1 "github.com/percona/percona-server-mysql-operator/api/v1alpha1" + "github.com/percona/percona-server-mysql-operator/pkg/naming" +) + +func podDisruptionBudget(cr *apiv1alpha1.PerconaServerMySQL, spec *apiv1alpha1.PodDisruptionBudgetSpec, labels map[string]string) *policyv1.PodDisruptionBudget { + return &policyv1.PodDisruptionBudget{ + ObjectMeta: metav1.ObjectMeta{ + Name: cr.Name + "-" + labels[naming.LabelName], + Namespace: cr.Namespace, + Labels: labels, + }, + Spec: policyv1.PodDisruptionBudgetSpec{ + MinAvailable: spec.MinAvailable, + MaxUnavailable: spec.MaxUnavailable, + Selector: &metav1.LabelSelector{ + MatchLabels: labels, + }, + }, + } +} diff --git a/pkg/k8s/utils.go b/pkg/k8s/utils.go index 4f4a1e37e..c512918cd 100644 --- a/pkg/k8s/utils.go +++ b/pkg/k8s/utils.go @@ -254,6 +254,47 @@ func EnsureObjectWithHash( return nil } +type Component interface { + Name() string + PerconaServerMySQL() *apiv1alpha1.PerconaServerMySQL + Labels() map[string]string + PodSpec() *apiv1alpha1.PodSpec + + Object(ctx context.Context, cl client.Client) (client.Object, error) +} + +func EnsureComponent( + ctx context.Context, + cl client.Client, + c Component, +) error { + cr := c.PerconaServerMySQL() + + obj, err := c.Object(ctx, cl) + if err != nil { + return errors.Wrap(err, "statefulset") + } + if err := EnsureObjectWithHash(ctx, cl, cr, obj, cl.Scheme()); err != nil { + return errors.Wrap(err, "failed to ensure statefulset") + } + + podSpec := c.PodSpec() + if podSpec == nil || podSpec.PodDisruptionBudget == nil { + return nil + } + + if err := cl.Get(ctx, client.ObjectKeyFromObject(obj), obj); err != nil { + return errors.Wrap(err, "get statefulset") + } + + pdb := podDisruptionBudget(cr, podSpec.PodDisruptionBudget, c.Labels()) + if err := EnsureObjectWithHash(ctx, cl, obj, pdb, cl.Scheme()); err != nil { + return errors.Wrap(err, "failed to create pdb") + } + + return nil +} + func EnsureService( ctx context.Context, cl client.Client, @@ -462,3 +503,24 @@ func GetImageIDFromPod(pod *corev1.Pod, containerName string) (string, error) { return pod.Status.ContainerStatuses[idx].ImageID, nil } + +func GetTLSHash(ctx context.Context, cl client.Client, cr *apiv1alpha1.PerconaServerMySQL) (string, error) { + secret := new(corev1.Secret) + err := cl.Get(ctx, types.NamespacedName{ + Name: cr.Spec.SSLSecretName, + Namespace: cr.Namespace, + }, secret) + if err != nil { + if k8serrors.IsNotFound(err) { + return "", nil + } + return "", errors.Wrap(err, "get secret") + } + + hash, err := ObjectHash(secret) + if err != nil { + return "", errors.Wrap(err, "get secret hash") + } + + return hash, nil +} diff --git a/pkg/mysql/component.go b/pkg/mysql/component.go new file mode 100644 index 000000000..103ceced1 --- /dev/null +++ b/pkg/mysql/component.go @@ -0,0 +1,64 @@ +package mysql + +import ( + "context" + + "github.com/pkg/errors" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" + + apiv1alpha1 "github.com/percona/percona-server-mysql-operator/api/v1alpha1" + "github.com/percona/percona-server-mysql-operator/pkg/k8s" +) + +type Component apiv1alpha1.PerconaServerMySQL + +func (c *Component) Name() string { + cr := c.PerconaServerMySQL() + return Name(cr) +} + +func (c *Component) PerconaServerMySQL() *apiv1alpha1.PerconaServerMySQL { + cr := apiv1alpha1.PerconaServerMySQL(*c) + return &cr +} + +func (c *Component) Labels() map[string]string { + cr := c.PerconaServerMySQL() + return MatchLabels(cr) +} + +func (c *Component) PodSpec() *apiv1alpha1.PodSpec { + return &c.Spec.MySQL.PodSpec +} + +func (c *Component) Object(ctx context.Context, cl client.Client) (client.Object, error) { + cr := c.PerconaServerMySQL() + + initImage, err := k8s.InitImage(ctx, cl, cr, c.PodSpec()) + if err != nil { + return nil, errors.Wrap(err, "get init image") + } + + internalSecret := new(corev1.Secret) + if err := cl.Get(ctx, types.NamespacedName{ + Name: cr.InternalSecretName(), + Namespace: cr.Namespace, + }, internalSecret); client.IgnoreNotFound(err) != nil { + return nil, errors.Wrapf(err, "get internal secret") + } + + configurable := Configurable(*cr) + configHash, err := k8s.CustomConfigHash(ctx, cl, cr, &configurable) + if err != nil { + return nil, errors.Wrapf(err, "get custom config hash") + } + + tlsHash, err := k8s.GetTLSHash(ctx, cl, cr) + if err != nil { + return nil, errors.Wrapf(err, "get tls hash") + } + + return StatefulSet(cr, initImage, configHash, tlsHash, internalSecret), nil +} diff --git a/pkg/orchestrator/component.go b/pkg/orchestrator/component.go new file mode 100644 index 000000000..8476ca89d --- /dev/null +++ b/pkg/orchestrator/component.go @@ -0,0 +1,48 @@ +package orchestrator + +import ( + "context" + + "github.com/pkg/errors" + "sigs.k8s.io/controller-runtime/pkg/client" + + apiv1alpha1 "github.com/percona/percona-server-mysql-operator/api/v1alpha1" + "github.com/percona/percona-server-mysql-operator/pkg/k8s" +) + +type Component apiv1alpha1.PerconaServerMySQL + +func (c *Component) Name() string { + cr := c.PerconaServerMySQL() + return Name(cr) +} + +func (c *Component) PerconaServerMySQL() *apiv1alpha1.PerconaServerMySQL { + cr := apiv1alpha1.PerconaServerMySQL(*c) + return &cr +} + +func (c *Component) Labels() map[string]string { + cr := c.PerconaServerMySQL() + return MatchLabels(cr) +} + +func (c *Component) PodSpec() *apiv1alpha1.PodSpec { + return &c.Spec.Orchestrator.PodSpec +} + +func (c *Component) Object(ctx context.Context, cl client.Client) (client.Object, error) { + cr := c.PerconaServerMySQL() + + initImage, err := k8s.InitImage(ctx, cl, cr, c.PodSpec()) + if err != nil { + return nil, errors.Wrap(err, "get init image") + } + + tlsHash, err := k8s.GetTLSHash(ctx, cl, cr) + if err != nil { + return nil, errors.Wrapf(err, "get tls hash") + } + + return StatefulSet(cr, initImage, tlsHash), nil +} diff --git a/pkg/router/component.go b/pkg/router/component.go new file mode 100644 index 000000000..65e1f589e --- /dev/null +++ b/pkg/router/component.go @@ -0,0 +1,54 @@ +package router + +import ( + "context" + + "github.com/pkg/errors" + "sigs.k8s.io/controller-runtime/pkg/client" + + apiv1alpha1 "github.com/percona/percona-server-mysql-operator/api/v1alpha1" + "github.com/percona/percona-server-mysql-operator/pkg/k8s" +) + +type Component apiv1alpha1.PerconaServerMySQL + +func (c *Component) Name() string { + cr := c.PerconaServerMySQL() + return Name(cr) +} + +func (c *Component) PerconaServerMySQL() *apiv1alpha1.PerconaServerMySQL { + cr := apiv1alpha1.PerconaServerMySQL(*c) + return &cr +} + +func (c *Component) Labels() map[string]string { + cr := c.PerconaServerMySQL() + return MatchLabels(cr) +} + +func (c *Component) PodSpec() *apiv1alpha1.PodSpec { + return &c.Spec.Proxy.Router.PodSpec +} + +func (c *Component) Object(ctx context.Context, cl client.Client) (client.Object, error) { + cr := c.PerconaServerMySQL() + + initImage, err := k8s.InitImage(ctx, cl, cr, c.PodSpec()) + if err != nil { + return nil, errors.Wrap(err, "get init image") + } + + configurable := Configurable(*cr) + configHash, err := k8s.CustomConfigHash(ctx, cl, cr, &configurable) + if err != nil { + return nil, errors.Wrapf(err, "get custom config hash") + } + + tlsHash, err := k8s.GetTLSHash(ctx, cl, cr) + if err != nil { + return nil, errors.Wrapf(err, "get tls hash") + } + + return Deployment(cr, initImage, configHash, tlsHash), nil +}