github.com/percona/percona-xtradb-cluster-operator@v1.14.0/pkg/controller/pxc/controller.go (about) 1 package pxc 2 3 import ( 4 "context" 5 "encoding/base64" 6 "encoding/json" 7 "reflect" 8 "strings" 9 "sync" 10 "sync/atomic" 11 "time" 12 13 cm "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" 14 "github.com/pkg/errors" 15 "github.com/robfig/cron/v3" 16 appsv1 "k8s.io/api/apps/v1" 17 corev1 "k8s.io/api/core/v1" 18 k8serrors "k8s.io/apimachinery/pkg/api/errors" 19 "k8s.io/apimachinery/pkg/api/meta" 20 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 21 "k8s.io/apimachinery/pkg/labels" 22 "k8s.io/apimachinery/pkg/runtime" 23 "k8s.io/apimachinery/pkg/types" 24 "sigs.k8s.io/controller-runtime/pkg/builder" 25 "sigs.k8s.io/controller-runtime/pkg/client" 26 "sigs.k8s.io/controller-runtime/pkg/client/apiutil" 27 "sigs.k8s.io/controller-runtime/pkg/handler" 28 logf "sigs.k8s.io/controller-runtime/pkg/log" 29 "sigs.k8s.io/controller-runtime/pkg/manager" 30 "sigs.k8s.io/controller-runtime/pkg/reconcile" 31 32 "github.com/percona/percona-xtradb-cluster-operator/clientcmd" 33 api "github.com/percona/percona-xtradb-cluster-operator/pkg/apis/pxc/v1" 34 "github.com/percona/percona-xtradb-cluster-operator/pkg/k8s" 35 "github.com/percona/percona-xtradb-cluster-operator/pkg/pxc" 36 "github.com/percona/percona-xtradb-cluster-operator/pkg/pxc/app" 37 "github.com/percona/percona-xtradb-cluster-operator/pkg/pxc/app/config" 38 "github.com/percona/percona-xtradb-cluster-operator/pkg/pxc/app/statefulset" 39 "github.com/percona/percona-xtradb-cluster-operator/pkg/pxc/backup" 40 "github.com/percona/percona-xtradb-cluster-operator/version" 41 ) 42 43 // Add creates a new PerconaXtraDBCluster Controller and adds it to the Manager. The Manager will set fields on the Controller 44 // and Start it when the Manager is Started. 45 func Add(mgr manager.Manager) error { 46 r, err := newReconciler(mgr) 47 if err != nil { 48 return err 49 } 50 51 return add(mgr, r) 52 } 53 54 // newReconciler returns a new reconcile.Reconciler 55 func newReconciler(mgr manager.Manager) (reconcile.Reconciler, error) { 56 sv, err := version.Server() 57 if err != nil { 58 return nil, errors.Wrap(err, "get version") 59 } 60 61 cli, err := clientcmd.NewClient() 62 if err != nil { 63 return nil, errors.Wrap(err, "create clientcmd") 64 } 65 66 return &ReconcilePerconaXtraDBCluster{ 67 client: mgr.GetClient(), 68 scheme: mgr.GetScheme(), 69 crons: NewCronRegistry(), 70 serverVersion: sv, 71 clientcmd: cli, 72 lockers: newLockStore(), 73 }, nil 74 } 75 76 // add adds a new Controller to mgr with r as the reconcile.Reconciler 77 func add(mgr manager.Manager, r reconcile.Reconciler) error { 78 return builder.ControllerManagedBy(mgr). 79 Named("pxc-controller"). 80 Watches(&api.PerconaXtraDBCluster{}, &handler.EnqueueRequestForObject{}). 81 Complete(r) 82 } 83 84 var _ reconcile.Reconciler = &ReconcilePerconaXtraDBCluster{} 85 86 // ReconcilePerconaXtraDBCluster reconciles a PerconaXtraDBCluster object 87 type ReconcilePerconaXtraDBCluster struct { 88 // This client, initialized using mgr.Client() above, is a split client 89 // that reads objects from the cache and writes to the apiserver 90 client client.Client 91 scheme *runtime.Scheme 92 crons CronRegistry 93 clientcmd *clientcmd.Client 94 syncUsersState int32 95 serverVersion *version.ServerVersion 96 lockers lockStore 97 } 98 99 type lockStore struct { 100 store *sync.Map 101 } 102 103 func newLockStore() lockStore { 104 return lockStore{ 105 store: new(sync.Map), 106 } 107 } 108 109 func (l lockStore) LoadOrCreate(key string) lock { 110 val, _ := l.store.LoadOrStore(key, lock{ 111 statusMutex: new(sync.Mutex), 112 updateSync: new(int32), 113 }) 114 115 return val.(lock) 116 } 117 118 type lock struct { 119 statusMutex *sync.Mutex 120 updateSync *int32 121 } 122 123 const ( 124 updateDone = 0 125 updateWait = 1 126 ) 127 128 type CronRegistry struct { 129 crons *cron.Cron 130 ensureVersionJobs *sync.Map 131 backupJobs *sync.Map 132 } 133 134 // AddFuncWithSeconds does the same as cron.AddFunc but changes the schedule so that the function will run the exact second that this method is called. 135 func (r *CronRegistry) AddFuncWithSeconds(spec string, cmd func()) (cron.EntryID, error) { 136 schedule, err := cron.ParseStandard(spec) 137 if err != nil { 138 return 0, errors.Wrap(err, "failed to parse cron schedule") 139 } 140 schedule.(*cron.SpecSchedule).Second = uint64(1 << time.Now().Second()) 141 id := r.crons.Schedule(schedule, cron.FuncJob(cmd)) 142 return id, nil 143 } 144 145 const ( 146 stateFree = 0 147 stateLocked = 1 148 ) 149 150 func NewCronRegistry() CronRegistry { 151 c := CronRegistry{ 152 crons: cron.New(), 153 ensureVersionJobs: new(sync.Map), 154 backupJobs: new(sync.Map), 155 } 156 157 c.crons.Start() 158 159 return c 160 } 161 162 // Reconcile reads that state of the cluster for a PerconaXtraDBCluster object and makes changes based on the state read 163 // and what is in the PerconaXtraDBCluster.Spec 164 // Note: 165 // The Controller will requeue the Request to be processed again if the returned error is non-nil or 166 // Result.Requeue is true, otherwise upon completion it will remove the work from the queue. 167 func (r *ReconcilePerconaXtraDBCluster) Reconcile(ctx context.Context, request reconcile.Request) (reconcile.Result, error) { 168 log := logf.FromContext(ctx) 169 170 rr := reconcile.Result{ 171 RequeueAfter: time.Second * 5, 172 } 173 174 // As operator can handle a few clusters 175 // lock should be created per cluster to not lock cron jobs of other clusters 176 l := r.lockers.LoadOrCreate(request.NamespacedName.String()) 177 178 // Fetch the PerconaXtraDBCluster instance 179 // PerconaXtraDBCluster object is also accessed and changed by a version service's cron job (that run concurrently) 180 l.statusMutex.Lock() 181 defer l.statusMutex.Unlock() 182 // we have to be sure the reconcile loop will be run at least once 183 // in-between any version service jobs (hence any two vs jobs shouldn't be run sequentially). 184 // the version service job sets the state to `updateWait` and the next job can be run only 185 // after the state was dropped to`updateDone` again 186 defer atomic.StoreInt32(l.updateSync, updateDone) 187 188 o := &api.PerconaXtraDBCluster{} 189 err := r.client.Get(context.TODO(), request.NamespacedName, o) 190 if err != nil { 191 if k8serrors.IsNotFound(err) { 192 // Request object not found, could have been deleted after reconcile request. 193 // Owned objects are automatically garbage collected. For additional cleanup logic use finalizers. 194 return rr, nil 195 } 196 // Error reading the object - requeue the request. 197 return reconcile.Result{}, err 198 } 199 200 if err := r.setCRVersion(ctx, o); err != nil { 201 return reconcile.Result{}, errors.Wrap(err, "set CR version") 202 } 203 204 err = o.CheckNSetDefaults(r.serverVersion, log) 205 if err != nil { 206 return reconcile.Result{}, errors.Wrap(err, "wrong PXC options") 207 } 208 209 if o.ObjectMeta.DeletionTimestamp != nil { 210 finalizers := []string{} 211 for _, fnlz := range o.GetFinalizers() { 212 var sfs api.StatefulApp 213 switch fnlz { 214 case "delete-ssl": 215 err = r.deleteCerts(o) 216 case "delete-proxysql-pvc": 217 sfs = statefulset.NewProxy(o) 218 // deletePVC is always true on this stage 219 // because we never reach this point without finalizers 220 err = r.deleteStatefulSet(o, sfs, true, false) 221 case "delete-pxc-pvc": 222 sfs = statefulset.NewNode(o) 223 err = r.deleteStatefulSet(o, sfs, true, true) 224 // nil error gonna be returned only when there is no more pods to delete (only 0 left) 225 // until than finalizer won't be deleted 226 case "delete-pxc-pods-in-order": 227 err = r.deletePXCPods(o) 228 } 229 if err != nil { 230 finalizers = append(finalizers, fnlz) 231 } 232 } 233 234 o.SetFinalizers(finalizers) 235 err = r.client.Update(context.TODO(), o) 236 237 // object is being deleted, no need in further actions 238 return rr, err 239 } 240 241 // wait until token issued to run PXC in data encrypted mode. 242 if o.ShouldWaitForTokenIssue() { 243 log.Info("wait for token issuing") 244 return rr, nil 245 } 246 247 defer func() { 248 uerr := r.updateStatus(o, false, err) 249 if uerr != nil { 250 log.Error(uerr, "Update status") 251 } 252 }() 253 254 if o.CompareVersionWith("1.7.0") >= 0 && *o.Spec.PXC.AutoRecovery { 255 err = r.recoverFullClusterCrashIfNeeded(ctx, o) 256 if err != nil { 257 log.Info("Failed to check if cluster needs to recover", "err", err.Error()) 258 } 259 } 260 261 if o.Spec.ProxySQLEnabled() { 262 haproxySts := appsv1.StatefulSet{ 263 ObjectMeta: metav1.ObjectMeta{ 264 Name: o.Name + "-haproxy", 265 Namespace: o.Namespace, 266 }, 267 } 268 err = r.client.Get(ctx, client.ObjectKeyFromObject(&haproxySts), &haproxySts) 269 if err == nil && !strings.HasPrefix(o.Status.PXC.Version, "5.7") { 270 return reconcile.Result{}, errors.Errorf("failed to enable ProxySQL: for mysql version 8.0 you can't switch from HAProxy to ProxySQL") 271 } 272 } 273 274 err = r.reconcileUsersSecret(ctx, o) 275 if err != nil { 276 return reconcile.Result{}, errors.Wrap(err, "reconcile users secret") 277 } 278 279 userReconcileResult := &ReconcileUsersResult{} 280 281 urr, err := r.reconcileUsers(ctx, o) 282 if err != nil { 283 return rr, errors.Wrap(err, "reconcile users") 284 } 285 if urr != nil { 286 userReconcileResult = urr 287 } 288 289 r.resyncPXCUsersWithProxySQL(ctx, o) 290 291 if o.Status.PXC.Version == "" || strings.HasSuffix(o.Status.PXC.Version, "intermediate") { 292 err := r.ensurePXCVersion(ctx, o, VersionServiceClient{OpVersion: o.Version().String()}) 293 if err != nil { 294 log.Info("failed to ensure version, running with default", "error", err) 295 } 296 } 297 err = r.reconcilePersistentVolumes(ctx, o) 298 if err != nil { 299 return reconcile.Result{}, errors.Wrap(err, "reconcile persistent volumes") 300 } 301 302 err = r.deploy(ctx, o) 303 if err != nil { 304 return reconcile.Result{}, err 305 } 306 initImageName, err := getInitImage(ctx, o, r.client) 307 if err != nil { 308 return reconcile.Result{}, errors.Wrap(err, "failed to get initImage") 309 } 310 311 inits := []corev1.Container{} 312 if o.CompareVersionWith("1.5.0") >= 0 { 313 var initResources corev1.ResourceRequirements 314 if o.CompareVersionWith("1.6.0") >= 0 { 315 initResources = o.Spec.PXC.Resources 316 } 317 if o.Spec.InitContainer.Resources != nil { 318 initResources = *o.Spec.InitContainer.Resources 319 } 320 initC := statefulset.EntrypointInitContainer(initImageName, app.DataVolumeName, initResources, o.Spec.PXC.ContainerSecurityContext, o.Spec.PXC.ImagePullPolicy) 321 inits = append(inits, initC) 322 } 323 324 pxcSet := statefulset.NewNode(o) 325 pxc.MergeTemplateAnnotations(pxcSet.StatefulSet(), userReconcileResult.pxcAnnotations) 326 err = r.updatePod(ctx, pxcSet, o.Spec.PXC.PodSpec, o, inits) 327 if err != nil { 328 return reconcile.Result{}, errors.Wrap(err, "pxc upgrade error") 329 } 330 331 saveOldSvcMeta := true 332 if o.CompareVersionWith("1.14.0") >= 0 { 333 saveOldSvcMeta = len(o.Spec.PXC.Expose.Labels) == 0 && len(o.Spec.PXC.Expose.Annotations) == 0 334 } 335 err = r.createOrUpdateService(o, pxc.NewServicePXC(o), saveOldSvcMeta) 336 if err != nil { 337 return reconcile.Result{}, errors.Wrap(err, "PXC service upgrade error") 338 } 339 err = r.createOrUpdateService(o, pxc.NewServicePXCUnready(o), true) 340 if err != nil { 341 return reconcile.Result{}, errors.Wrap(err, "PXC service upgrade error") 342 } 343 344 if o.Spec.PXC.Expose.Enabled { 345 err = r.ensurePxcPodServices(o) 346 if err != nil { 347 return rr, errors.Wrap(err, "create replication services") 348 } 349 } else { 350 err = r.removePxcPodServices(o) 351 if err != nil { 352 return rr, errors.Wrap(err, "remove pxc pod services") 353 } 354 } 355 356 var proxyInits []corev1.Container 357 if o.CompareVersionWith("1.13.0") >= 0 { 358 initResources := o.Spec.PXC.Resources 359 if o.Spec.InitContainer.Resources != nil { 360 initResources = *o.Spec.InitContainer.Resources 361 } 362 proxyInits = []corev1.Container{ 363 statefulset.EntrypointInitContainer(initImageName, app.BinVolumeName, initResources, o.Spec.PXC.ContainerSecurityContext, o.Spec.PXC.ImagePullPolicy), 364 } 365 } 366 367 if err := r.reconcileHAProxy(ctx, o, userReconcileResult.proxyAnnotations, proxyInits); err != nil { 368 return reconcile.Result{}, err 369 } 370 371 proxysqlSet := statefulset.NewProxy(o) 372 pxc.MergeTemplateAnnotations(proxysqlSet.StatefulSet(), userReconcileResult.proxyAnnotations) 373 374 if o.Spec.ProxySQLEnabled() { 375 err = r.updatePod(ctx, proxysqlSet, &o.Spec.ProxySQL.PodSpec, o, proxyInits) 376 if err != nil { 377 return reconcile.Result{}, errors.Wrap(err, "ProxySQL upgrade error") 378 } 379 svc := pxc.NewServiceProxySQL(o) 380 381 if o.CompareVersionWith("1.14.0") >= 0 { 382 err = r.createOrUpdateService(o, svc, len(o.Spec.ProxySQL.Expose.Labels) == 0 && len(o.Spec.ProxySQL.Expose.Annotations) == 0) 383 if err != nil { 384 return reconcile.Result{}, errors.Wrapf(err, "%s upgrade error", svc.Name) 385 } 386 } else { 387 err = r.createOrUpdateService(o, svc, len(o.Spec.ProxySQL.ServiceLabels) == 0 && len(o.Spec.ProxySQL.ServiceAnnotations) == 0) 388 if err != nil { 389 return reconcile.Result{}, errors.Wrapf(err, "%s upgrade error", svc.Name) 390 } 391 } 392 393 svc = pxc.NewServiceProxySQLUnready(o) 394 err = r.createOrUpdateService(o, svc, true) 395 if err != nil { 396 return reconcile.Result{}, errors.Wrapf(err, "%s upgrade error", svc.Name) 397 } 398 } else { 399 // check if there is need to delete pvc 400 deletePVC := false 401 for _, fnlz := range o.GetFinalizers() { 402 if fnlz == "delete-proxysql-pvc" { 403 deletePVC = true 404 break 405 } 406 } 407 408 err = r.deleteStatefulSet(o, proxysqlSet, deletePVC, false) 409 if err != nil { 410 return reconcile.Result{}, err 411 } 412 413 err = r.deleteServices(pxc.NewServiceProxySQL(o), pxc.NewServiceProxySQLUnready(o)) 414 if err != nil { 415 return reconcile.Result{}, err 416 } 417 } 418 419 if o.CompareVersionWith("1.9.0") >= 0 { 420 err = r.reconcileReplication(ctx, o, userReconcileResult.updateReplicationPassword) 421 if err != nil { 422 log.Info("reconcile replication error", "err", err.Error()) 423 } 424 } 425 426 err = r.reconcileBackups(ctx, o) 427 if err != nil { 428 return reconcile.Result{}, err 429 } 430 431 err = backup.CheckPITRErrors(ctx, r.client, r.clientcmd, o) 432 if err != nil { 433 return reconcile.Result{}, err 434 } 435 436 err = backup.UpdatePITRTimeline(ctx, r.client, r.clientcmd, o) 437 if err != nil { 438 return reconcile.Result{}, err 439 } 440 441 if err := r.fetchVersionFromPXC(ctx, o, pxcSet); err != nil { 442 return rr, errors.Wrap(err, "update CR version") 443 } 444 445 err = r.scheduleEnsurePXCVersion(ctx, o, VersionServiceClient{OpVersion: o.Version().String()}) 446 if err != nil { 447 return reconcile.Result{}, errors.Wrap(err, "failed to ensure version") 448 } 449 450 err = r.scheduleTelemetryRequests(ctx, o, VersionServiceClient{OpVersion: o.Version().String()}) 451 if err != nil { 452 return reconcile.Result{}, errors.Wrap(err, "failed to schedule telemetry requests") 453 } 454 455 return rr, nil 456 } 457 458 func (r *ReconcilePerconaXtraDBCluster) reconcileHAProxy(ctx context.Context, cr *api.PerconaXtraDBCluster, annotations map[string]string, initContainers []corev1.Container) error { 459 if !cr.HAProxyEnabled() { 460 if err := r.deleteServices(pxc.NewServiceHAProxyReplicas(cr)); err != nil { 461 return errors.Wrap(err, "delete HAProxy replica service") 462 } 463 464 if err := r.deleteServices(pxc.NewServiceHAProxy(cr)); err != nil { 465 return errors.Wrap(err, "delete HAProxy service") 466 } 467 468 if err := r.deleteStatefulSet(cr, statefulset.NewHAProxy(cr), false, false); err != nil { 469 return errors.Wrap(err, "delete HAProxy stateful set") 470 } 471 472 return nil 473 } 474 envVarsSecret := new(corev1.Secret) 475 if err := r.client.Get(ctx, types.NamespacedName{ 476 Name: cr.Spec.HAProxy.EnvVarsSecretName, 477 Namespace: cr.Namespace, 478 }, envVarsSecret); client.IgnoreNotFound(err) != nil { 479 return errors.Wrap(err, "get haproxy env vars secret") 480 } 481 sts := statefulset.NewHAProxy(cr) 482 pxc.MergeTemplateAnnotations(sts.StatefulSet(), annotations) 483 484 if err := r.updatePod(ctx, sts, &cr.Spec.HAProxy.PodSpec, cr, initContainers); err != nil { 485 return errors.Wrap(err, "HAProxy upgrade error") 486 } 487 svc := pxc.NewServiceHAProxy(cr) 488 podSpec := cr.Spec.HAProxy.PodSpec 489 expose := cr.Spec.HAProxy.ExposePrimary 490 491 if cr.CompareVersionWith("1.14.0") >= 0 { 492 err := r.createOrUpdateService(cr, svc, len(expose.Labels) == 0 && len(expose.Annotations) == 0) 493 if err != nil { 494 return errors.Wrapf(err, "%s upgrade error", svc.Name) 495 } 496 } else { 497 err := r.createOrUpdateService(cr, svc, len(podSpec.ServiceLabels) == 0 && len(podSpec.ServiceAnnotations) == 0) 498 if err != nil { 499 return errors.Wrapf(err, "%s upgrade error", svc.Name) 500 } 501 } 502 503 if cr.HAProxyReplicasServiceEnabled() { 504 svc := pxc.NewServiceHAProxyReplicas(cr) 505 err := setControllerReference(cr, svc, r.scheme) 506 if err != nil { 507 return errors.Wrapf(err, "%s setControllerReference", svc.Name) 508 } 509 510 if cr.CompareVersionWith("1.14.0") >= 0 { 511 e := cr.Spec.HAProxy.ExposeReplicas 512 err = r.createOrUpdateService(cr, svc, len(e.Labels) == 0 && len(e.Annotations) == 0) 513 if err != nil { 514 return errors.Wrapf(err, "%s upgrade error", svc.Name) 515 } 516 } else { 517 err = r.createOrUpdateService(cr, svc, len(podSpec.ReplicasServiceLabels) == 0 && len(podSpec.ReplicasServiceAnnotations) == 0) 518 if err != nil { 519 return errors.Wrapf(err, "%s upgrade error", svc.Name) 520 } 521 } 522 523 } else { 524 if err := r.deleteServices(pxc.NewServiceHAProxyReplicas(cr)); err != nil { 525 return errors.Wrap(err, "delete HAProxy replica service") 526 } 527 } 528 529 return nil 530 } 531 532 func (r *ReconcilePerconaXtraDBCluster) deploy(ctx context.Context, cr *api.PerconaXtraDBCluster) error { 533 log := logf.FromContext(ctx) 534 535 if cr.PVCResizeInProgress() { 536 log.V(1).Info("PVC resize in progress, skipping statefulset") 537 return nil 538 } 539 540 stsApp := statefulset.NewNode(cr) 541 err := r.reconcileConfigMap(cr) 542 if err != nil { 543 return err 544 } 545 546 initImageName, err := getInitImage(ctx, cr, r.client) 547 if err != nil { 548 return errors.Wrap(err, "failed to get initImage") 549 } 550 inits := []corev1.Container{} 551 if cr.CompareVersionWith("1.5.0") >= 0 { 552 var initResources corev1.ResourceRequirements 553 if cr.CompareVersionWith("1.6.0") >= 0 { 554 initResources = cr.Spec.PXC.Resources 555 } 556 if cr.Spec.InitContainer.Resources != nil { 557 initResources = *cr.Spec.InitContainer.Resources 558 } 559 initC := statefulset.EntrypointInitContainer(initImageName, app.DataVolumeName, initResources, cr.Spec.PXC.ContainerSecurityContext, cr.Spec.PXC.ImagePullPolicy) 560 inits = append(inits, initC) 561 } 562 563 secretsName := cr.Spec.SecretsName 564 if cr.CompareVersionWith("1.6.0") >= 0 { 565 secretsName = "internal-" + cr.Name 566 } 567 secrets := new(corev1.Secret) 568 err = r.client.Get(context.TODO(), types.NamespacedName{ 569 Name: secretsName, Namespace: cr.Namespace, 570 }, secrets) 571 if client.IgnoreNotFound(err) != nil { 572 return errors.Wrap(err, "get internal secret") 573 } 574 nodeSet, err := pxc.StatefulSet(ctx, r.client, stsApp, cr.Spec.PXC.PodSpec, cr, secrets, inits, log, r.getConfigVolume) 575 if err != nil { 576 return errors.Wrap(err, "get pxc statefulset") 577 } 578 currentNodeSet := new(appsv1.StatefulSet) 579 err = r.client.Get(context.TODO(), types.NamespacedName{ 580 Namespace: nodeSet.Namespace, 581 Name: nodeSet.Name, 582 }, currentNodeSet) 583 if client.IgnoreNotFound(err) != nil { 584 return errors.Wrap(err, "get current pxc sts") 585 } 586 587 // TODO: code duplication with updatePod function 588 if nodeSet.Spec.Template.Annotations == nil { 589 nodeSet.Spec.Template.Annotations = make(map[string]string) 590 } 591 if v, ok := currentNodeSet.Spec.Template.Annotations["last-applied-secret"]; ok { 592 nodeSet.Spec.Template.Annotations["last-applied-secret"] = v 593 } 594 if cr.CompareVersionWith("1.1.0") >= 0 { 595 hash, err := r.getConfigHash(cr, stsApp) 596 if err != nil { 597 return errors.Wrap(err, "getting node config hash") 598 } 599 nodeSet.Spec.Template.Annotations["percona.com/configuration-hash"] = hash 600 } 601 602 err = r.reconcileSSL(cr) 603 if err != nil { 604 return errors.Wrapf(err, "failed to reconcile SSL.Please create your TLS secret %s and %s manually or setup cert-manager correctly", 605 cr.Spec.PXC.SSLSecretName, cr.Spec.PXC.SSLInternalSecretName) 606 } 607 608 sslHash, err := r.getSecretHash(cr, cr.Spec.PXC.SSLSecretName, cr.Spec.AllowUnsafeConfig) 609 if err != nil { 610 return errors.Wrap(err, "get secret hash") 611 } 612 if sslHash != "" && cr.CompareVersionWith("1.1.0") >= 0 { 613 nodeSet.Spec.Template.Annotations["percona.com/ssl-hash"] = sslHash 614 } 615 616 sslInternalHash, err := r.getSecretHash(cr, cr.Spec.PXC.SSLInternalSecretName, cr.Spec.AllowUnsafeConfig) 617 if err != nil && !k8serrors.IsNotFound(err) { 618 return errors.Wrap(err, "get internal secret hash") 619 } 620 if sslInternalHash != "" && cr.CompareVersionWith("1.1.0") >= 0 { 621 nodeSet.Spec.Template.Annotations["percona.com/ssl-internal-hash"] = sslInternalHash 622 } 623 624 if cr.CompareVersionWith("1.9.0") >= 0 { 625 envVarsHash, err := r.getSecretHash(cr, cr.Spec.PXC.EnvVarsSecretName, true) 626 if err != nil { 627 return errors.Wrap(err, "upgradePod/updateApp error: update secret error") 628 } 629 if envVarsHash != "" { 630 nodeSet.Spec.Template.Annotations["percona.com/env-secret-config-hash"] = envVarsHash 631 } 632 } 633 634 vaultConfigHash, err := r.getSecretHash(cr, cr.Spec.VaultSecretName, true) 635 if err != nil { 636 return errors.Wrap(err, "get vault config hash") 637 } 638 if vaultConfigHash != "" && cr.CompareVersionWith("1.6.0") >= 0 { 639 nodeSet.Spec.Template.Annotations["percona.com/vault-config-hash"] = vaultConfigHash 640 } 641 nodeSet.Spec.Template.Spec.Tolerations = cr.Spec.PXC.Tolerations 642 err = setControllerReference(cr, nodeSet, r.scheme) 643 if err != nil { 644 return err 645 } 646 647 err = r.createOrUpdate(cr, nodeSet) 648 if err != nil { 649 return errors.Wrap(err, "create newStatefulSetNode") 650 } 651 652 // PodDisruptionBudget object for nodes 653 err = r.client.Get(context.TODO(), types.NamespacedName{Name: nodeSet.Name, Namespace: nodeSet.Namespace}, nodeSet) 654 if err == nil { 655 err := r.reconcilePDB(cr, cr.Spec.PXC.PodDisruptionBudget, stsApp, nodeSet) 656 if err != nil { 657 return errors.Wrapf(err, "PodDisruptionBudget for %s", nodeSet.Name) 658 } 659 } else if !k8serrors.IsNotFound(err) { 660 return errors.Wrap(err, "get PXC stateful set") 661 } 662 663 var proxyInits []corev1.Container 664 if cr.CompareVersionWith("1.13.0") >= 0 { 665 initResources := cr.Spec.PXC.Resources 666 if cr.Spec.InitContainer.Resources != nil { 667 initResources = *cr.Spec.InitContainer.Resources 668 } 669 proxyInits = []corev1.Container{ 670 statefulset.EntrypointInitContainer(initImageName, app.BinVolumeName, initResources, cr.Spec.PXC.ContainerSecurityContext, cr.Spec.PXC.ImagePullPolicy), 671 } 672 } 673 674 // HAProxy StatefulSet 675 if cr.HAProxyEnabled() { 676 sfsHAProxy := statefulset.NewHAProxy(cr) 677 haProxySet, err := pxc.StatefulSet(ctx, r.client, sfsHAProxy, &cr.Spec.HAProxy.PodSpec, cr, secrets, proxyInits, log, r.getConfigVolume) 678 if err != nil { 679 return errors.Wrap(err, "create HAProxy StatefulSet") 680 } 681 err = setControllerReference(cr, haProxySet, r.scheme) 682 if err != nil { 683 return err 684 } 685 686 // TODO: code duplication with updatePod function 687 if haProxySet.Spec.Template.Annotations == nil { 688 haProxySet.Spec.Template.Annotations = make(map[string]string) 689 } 690 hash, err := r.getConfigHash(cr, sfsHAProxy) 691 if err != nil { 692 return errors.Wrap(err, "getting HAProxy config hash") 693 } 694 haProxySet.Spec.Template.Annotations["percona.com/configuration-hash"] = hash 695 if cr.CompareVersionWith("1.5.0") == 0 { 696 if sslHash != "" { 697 haProxySet.Spec.Template.Annotations["percona.com/ssl-hash"] = sslHash 698 } 699 if sslInternalHash != "" { 700 haProxySet.Spec.Template.Annotations["percona.com/ssl-internal-hash"] = sslInternalHash 701 } 702 } 703 if cr.CompareVersionWith("1.9.0") >= 0 { 704 envVarsHash, err := r.getSecretHash(cr, cr.Spec.HAProxy.EnvVarsSecretName, true) 705 if err != nil { 706 return errors.Wrap(err, "upgradePod/updateApp error: update secret error") 707 } 708 if envVarsHash != "" { 709 haProxySet.Spec.Template.Annotations["percona.com/env-secret-config-hash"] = envVarsHash 710 } 711 } 712 err = r.client.Create(context.TODO(), haProxySet) 713 if err != nil && !k8serrors.IsAlreadyExists(err) { 714 return errors.Wrap(err, "create newStatefulSetHAProxy") 715 } 716 717 // PodDisruptionBudget object for HAProxy 718 err = r.client.Get(context.TODO(), types.NamespacedName{Name: haProxySet.Name, Namespace: haProxySet.Namespace}, haProxySet) 719 if err == nil { 720 err := r.reconcilePDB(cr, cr.Spec.HAProxy.PodDisruptionBudget, sfsHAProxy, haProxySet) 721 if err != nil { 722 return errors.Wrapf(err, "PodDisruptionBudget for %s", haProxySet.Name) 723 } 724 } else if !k8serrors.IsNotFound(err) { 725 return errors.Wrap(err, "get HAProxy stateful set") 726 } 727 } 728 729 if cr.Spec.ProxySQLEnabled() { 730 sfsProxy := statefulset.NewProxy(cr) 731 proxySet, err := pxc.StatefulSet(ctx, r.client, sfsProxy, &cr.Spec.ProxySQL.PodSpec, cr, secrets, proxyInits, log, r.getConfigVolume) 732 if err != nil { 733 return errors.Wrap(err, "create ProxySQL Service") 734 } 735 err = setControllerReference(cr, proxySet, r.scheme) 736 if err != nil { 737 return err 738 } 739 currentProxySet := new(appsv1.StatefulSet) 740 err = r.client.Get(context.TODO(), types.NamespacedName{ 741 Namespace: nodeSet.Namespace, 742 Name: nodeSet.Name, 743 }, currentProxySet) 744 if client.IgnoreNotFound(err) != nil { 745 return errors.Wrap(err, "get current proxy sts") 746 } 747 748 // TODO: code duplication with updatePod function 749 if proxySet.Spec.Template.Annotations == nil { 750 proxySet.Spec.Template.Annotations = make(map[string]string) 751 } 752 if v, ok := currentProxySet.Spec.Template.Annotations["last-applied-secret"]; ok { 753 proxySet.Spec.Template.Annotations["last-applied-secret"] = v 754 } 755 if cr.CompareVersionWith("1.1.0") >= 0 { 756 hash, err := r.getConfigHash(cr, sfsProxy) 757 if err != nil { 758 return errors.Wrap(err, "getting proxySQL config hash") 759 } 760 proxySet.Spec.Template.Annotations["percona.com/configuration-hash"] = hash 761 if sslHash != "" { 762 proxySet.Spec.Template.Annotations["percona.com/ssl-hash"] = sslHash 763 } 764 if sslInternalHash != "" { 765 proxySet.Spec.Template.Annotations["percona.com/ssl-internal-hash"] = sslInternalHash 766 } 767 } 768 if cr.CompareVersionWith("1.9.0") >= 0 { 769 envVarsHash, err := r.getSecretHash(cr, cr.Spec.ProxySQL.EnvVarsSecretName, true) 770 if err != nil { 771 return errors.Wrap(err, "upgradePod/updateApp error: update secret error") 772 } 773 if envVarsHash != "" { 774 proxySet.Spec.Template.Annotations["percona.com/env-secret-config-hash"] = envVarsHash 775 } 776 } 777 err = r.client.Create(context.TODO(), proxySet) 778 if err != nil && !k8serrors.IsAlreadyExists(err) { 779 return errors.Wrap(err, "create newStatefulSetProxySQL") 780 } 781 782 // PodDisruptionBudget object for ProxySQL 783 err = r.client.Get(context.TODO(), types.NamespacedName{Name: proxySet.Name, Namespace: proxySet.Namespace}, proxySet) 784 if err == nil { 785 err := r.reconcilePDB(cr, cr.Spec.ProxySQL.PodDisruptionBudget, sfsProxy, proxySet) 786 if err != nil { 787 return errors.Wrapf(err, "PodDisruptionBudget for %s", proxySet.Name) 788 } 789 } else if !k8serrors.IsNotFound(err) { 790 return errors.Wrap(err, "get ProxySQL stateful set") 791 } 792 } 793 794 return nil 795 } 796 797 func (r *ReconcilePerconaXtraDBCluster) reconcileConfigMap(cr *api.PerconaXtraDBCluster) error { 798 autotuneCm := config.AutoTuneConfigMapName(cr.Name, "pxc") 799 800 _, ok := cr.Spec.PXC.Resources.Limits[corev1.ResourceMemory] 801 if ok { 802 configMap, err := config.NewAutoTuneConfigMap(cr, cr.Spec.PXC.Resources.Limits.Memory(), autotuneCm) 803 if err != nil { 804 return errors.Wrap(err, "new autotune configmap") 805 } 806 807 err = setControllerReference(cr, configMap, r.scheme) 808 if err != nil { 809 return errors.Wrap(err, "set autotune configmap controller ref") 810 } 811 812 err = createOrUpdateConfigmap(r.client, configMap) 813 if err != nil { 814 return errors.Wrap(err, "create or update autotune configmap") 815 } 816 } else { 817 if err := deleteConfigMapIfExists(r.client, cr, autotuneCm); err != nil { 818 return errors.Wrap(err, "delete autotune configmap") 819 } 820 } 821 822 pxcConfigName := config.CustomConfigMapName(cr.Name, "pxc") 823 if cr.Spec.PXC.Configuration != "" { 824 configMap := config.NewConfigMap(cr, pxcConfigName, "init.cnf", cr.Spec.PXC.Configuration) 825 err := setControllerReference(cr, configMap, r.scheme) 826 if err != nil { 827 return errors.Wrap(err, "set controller ref") 828 } 829 830 err = createOrUpdateConfigmap(r.client, configMap) 831 if err != nil { 832 return errors.Wrap(err, "pxc config map") 833 } 834 } else { 835 if err := deleteConfigMapIfExists(r.client, cr, pxcConfigName); err != nil { 836 return errors.Wrap(err, "delete pxc config map") 837 } 838 } 839 840 if cr.CompareVersionWith("1.11.0") >= 0 { 841 pxcHookScriptName := config.HookScriptConfigMapName(cr.Name, "pxc") 842 if cr.Spec.PXC != nil && cr.Spec.PXC.HookScript != "" { 843 err := r.createHookScriptConfigMap(cr, cr.Spec.PXC.PodSpec.HookScript, pxcHookScriptName) 844 if err != nil { 845 return errors.Wrap(err, "create pxc hookscript config map") 846 } 847 } else { 848 if err := deleteConfigMapIfExists(r.client, cr, pxcHookScriptName); err != nil { 849 return errors.Wrap(err, "delete pxc hookscript config map") 850 } 851 } 852 853 proxysqlHookScriptName := config.HookScriptConfigMapName(cr.Name, "proxysql") 854 if cr.Spec.ProxySQL != nil && cr.Spec.ProxySQL.HookScript != "" { 855 err := r.createHookScriptConfigMap(cr, cr.Spec.ProxySQL.HookScript, proxysqlHookScriptName) 856 if err != nil { 857 return errors.Wrap(err, "create proxysql hookscript config map") 858 } 859 } else { 860 if err := deleteConfigMapIfExists(r.client, cr, proxysqlHookScriptName); err != nil { 861 return errors.Wrap(err, "delete proxysql hookscript config map") 862 } 863 } 864 haproxyHookScriptName := config.HookScriptConfigMapName(cr.Name, "haproxy") 865 if cr.Spec.HAProxy != nil && cr.Spec.HAProxy.HookScript != "" { 866 err := r.createHookScriptConfigMap(cr, cr.Spec.HAProxy.PodSpec.HookScript, haproxyHookScriptName) 867 if err != nil { 868 return errors.Wrap(err, "create haproxy hookscript config map") 869 } 870 } else { 871 if err := deleteConfigMapIfExists(r.client, cr, haproxyHookScriptName); err != nil { 872 return errors.Wrap(err, "delete haproxy config map") 873 } 874 } 875 logCollectorHookScriptName := config.HookScriptConfigMapName(cr.Name, "logcollector") 876 if cr.Spec.LogCollector != nil && cr.Spec.LogCollector.HookScript != "" { 877 err := r.createHookScriptConfigMap(cr, cr.Spec.LogCollector.HookScript, logCollectorHookScriptName) 878 if err != nil { 879 return errors.Wrap(err, "create logcollector hookscript config map") 880 } 881 } else { 882 if err := deleteConfigMapIfExists(r.client, cr, logCollectorHookScriptName); err != nil { 883 return errors.Wrap(err, "delete logcollector config map") 884 } 885 } 886 } 887 888 proxysqlConfigName := config.CustomConfigMapName(cr.Name, "proxysql") 889 if cr.Spec.ProxySQLEnabled() { 890 if cr.Spec.ProxySQL.Configuration != "" { 891 configMap := config.NewConfigMap(cr, proxysqlConfigName, "proxysql.cnf", cr.Spec.ProxySQL.Configuration) 892 err := setControllerReference(cr, configMap, r.scheme) 893 if err != nil { 894 return errors.Wrap(err, "set controller ref ProxySQL") 895 } 896 897 err = createOrUpdateConfigmap(r.client, configMap) 898 if err != nil { 899 return errors.Wrap(err, "proxysql config map") 900 } 901 } 902 } else { 903 if err := deleteConfigMapIfExists(r.client, cr, proxysqlConfigName); err != nil { 904 return errors.Wrap(err, "delete proxySQL config map") 905 } 906 } 907 908 haproxyConfigName := config.CustomConfigMapName(cr.Name, "haproxy") 909 if cr.HAProxyEnabled() && cr.Spec.HAProxy.Configuration != "" { 910 configMap := config.NewConfigMap(cr, haproxyConfigName, "haproxy-global.cfg", cr.Spec.HAProxy.Configuration) 911 err := setControllerReference(cr, configMap, r.scheme) 912 if err != nil { 913 return errors.Wrap(err, "set controller ref HAProxy") 914 } 915 916 err = createOrUpdateConfigmap(r.client, configMap) 917 if err != nil { 918 return errors.Wrap(err, "haproxy config map") 919 } 920 } else { 921 if err := deleteConfigMapIfExists(r.client, cr, haproxyConfigName); err != nil { 922 return errors.Wrap(err, "delete haproxy config map") 923 } 924 } 925 926 logCollectorConfigName := config.CustomConfigMapName(cr.Name, "logcollector") 927 if cr.Spec.LogCollector != nil && cr.Spec.LogCollector.Configuration != "" { 928 configMap := config.NewConfigMap(cr, logCollectorConfigName, "fluentbit_custom.conf", cr.Spec.LogCollector.Configuration) 929 err := setControllerReference(cr, configMap, r.scheme) 930 if err != nil { 931 return errors.Wrap(err, "set controller ref LogCollector") 932 } 933 err = createOrUpdateConfigmap(r.client, configMap) 934 if err != nil { 935 return errors.Wrap(err, "logcollector config map") 936 } 937 } else { 938 if err := deleteConfigMapIfExists(r.client, cr, logCollectorConfigName); err != nil { 939 return errors.Wrap(err, "delete log collector config map") 940 } 941 } 942 943 return nil 944 } 945 946 func (r *ReconcilePerconaXtraDBCluster) createHookScriptConfigMap(cr *api.PerconaXtraDBCluster, hookScript string, configMapName string) error { 947 configMap := config.NewConfigMap(cr, configMapName, "hook.sh", hookScript) 948 err := setControllerReference(cr, configMap, r.scheme) 949 if err != nil { 950 return errors.Wrap(err, "set controller ref") 951 } 952 953 err = createOrUpdateConfigmap(r.client, configMap) 954 if err != nil { 955 return errors.Wrap(err, "create or update configmap") 956 } 957 return nil 958 } 959 960 func (r *ReconcilePerconaXtraDBCluster) reconcilePDB(cr *api.PerconaXtraDBCluster, spec *api.PodDisruptionBudgetSpec, sfs api.StatefulApp, owner runtime.Object) error { 961 if spec == nil { 962 return nil 963 } 964 965 pdb := pxc.PodDisruptionBudget(spec, sfs.Labels(), cr.Namespace) 966 err := setControllerReference(owner, pdb, r.scheme) 967 if err != nil { 968 return errors.Wrap(err, "set owner reference") 969 } 970 971 return errors.Wrap(r.createOrUpdate(cr, pdb), "reconcile pdb") 972 } 973 974 func (r *ReconcilePerconaXtraDBCluster) deletePXCPods(cr *api.PerconaXtraDBCluster) error { 975 sfs := statefulset.NewNode(cr) 976 err := r.deleteStatefulSetPods(cr.Namespace, sfs) 977 if err != nil { 978 return errors.Wrap(err, "delete statefulset pods") 979 } 980 if cr.Spec.Backup != nil && cr.Spec.Backup.PITR.Enabled { 981 return errors.Wrap(r.deletePITR(cr), "delete pitr pod") 982 } 983 984 return nil 985 } 986 987 func (r *ReconcilePerconaXtraDBCluster) deleteStatefulSetPods(namespace string, sfs api.StatefulApp) error { 988 list := corev1.PodList{} 989 990 err := r.client.List(context.TODO(), 991 &list, 992 &client.ListOptions{ 993 Namespace: namespace, 994 LabelSelector: labels.SelectorFromSet(sfs.Labels()), 995 }, 996 ) 997 if err != nil { 998 return errors.Wrap(err, "get pod list") 999 } 1000 1001 // the last pod left - we can leave it for the stateful set 1002 if len(list.Items) <= 1 { 1003 time.Sleep(time.Second * 3) 1004 return nil 1005 } 1006 1007 // after setting the pods for delete we need to downscale statefulset to 1 under, 1008 // otherwise it will be trying to deploy the nodes again to reach the desired replicas count 1009 cSet := sfs.StatefulSet() 1010 err = r.client.Get(context.TODO(), types.NamespacedName{Name: cSet.Name, Namespace: cSet.Namespace}, cSet) 1011 if err != nil { 1012 return errors.Wrap(err, "get StatefulSet") 1013 } 1014 1015 if cSet.Spec.Replicas == nil || *cSet.Spec.Replicas != 1 { 1016 dscaleTo := int32(1) 1017 cSet.Spec.Replicas = &dscaleTo 1018 err = r.client.Update(context.TODO(), cSet) 1019 if err != nil { 1020 return errors.Wrap(err, "downscale StatefulSet") 1021 } 1022 } 1023 return errors.New("waiting for pods to be deleted") 1024 } 1025 1026 func (r *ReconcilePerconaXtraDBCluster) deleteStatefulSet(cr *api.PerconaXtraDBCluster, sfs api.StatefulApp, deletePVC, deleteSecrets bool) error { 1027 sfsWithOwner := appsv1.StatefulSet{} 1028 err := r.client.Get(context.TODO(), types.NamespacedName{ 1029 Name: sfs.StatefulSet().Name, 1030 Namespace: cr.Namespace, 1031 }, &sfsWithOwner) 1032 if err != nil && !k8serrors.IsNotFound(err) { 1033 return errors.Wrapf(err, "get statefulset: %s", sfs.StatefulSet().Name) 1034 } 1035 1036 if k8serrors.IsNotFound(err) { 1037 return nil 1038 } 1039 1040 if !metav1.IsControlledBy(&sfsWithOwner, cr) { 1041 return nil 1042 } 1043 1044 err = r.client.Delete(context.TODO(), &sfsWithOwner, &client.DeleteOptions{Preconditions: &metav1.Preconditions{UID: &sfsWithOwner.UID}}) 1045 if err != nil && !k8serrors.IsNotFound(err) { 1046 return errors.Wrapf(err, "delete statefulset: %s", sfs.StatefulSet().Name) 1047 } 1048 if deletePVC { 1049 err = r.deletePVC(cr.Namespace, sfs.Labels()) 1050 if err != nil { 1051 return errors.Wrapf(err, "delete pvc: %s", sfs.StatefulSet().Name) 1052 } 1053 } 1054 1055 if deleteSecrets { 1056 err = r.deleteSecrets(cr) 1057 if err != nil { 1058 return errors.Wrap(err, "delete secrets") 1059 } 1060 } 1061 1062 return nil 1063 } 1064 1065 func (r *ReconcilePerconaXtraDBCluster) deleteServices(svcs ...*corev1.Service) error { 1066 for _, s := range svcs { 1067 err := r.client.Get(context.TODO(), types.NamespacedName{ 1068 Name: s.Name, 1069 Namespace: s.Namespace, 1070 }, &corev1.Service{}) 1071 if err != nil && !k8serrors.IsNotFound(err) { 1072 return errors.Wrapf(err, "get service: %s", s.Name) 1073 } 1074 1075 if k8serrors.IsNotFound(err) { 1076 continue 1077 } 1078 1079 err = r.client.Delete(context.TODO(), s) 1080 if err != nil { 1081 return errors.Wrapf(err, "delete service: %s", s.Name) 1082 } 1083 } 1084 return nil 1085 } 1086 1087 func (r *ReconcilePerconaXtraDBCluster) deletePVC(namespace string, lbls map[string]string) error { 1088 list := corev1.PersistentVolumeClaimList{} 1089 err := r.client.List(context.TODO(), 1090 &list, 1091 &client.ListOptions{ 1092 Namespace: namespace, 1093 LabelSelector: labels.SelectorFromSet(lbls), 1094 }, 1095 ) 1096 if err != nil { 1097 return errors.Wrap(err, "get PVC list") 1098 } 1099 1100 for _, pvc := range list.Items { 1101 err := r.client.Delete(context.TODO(), &pvc, &client.DeleteOptions{Preconditions: &metav1.Preconditions{UID: &pvc.UID}}) 1102 if err != nil { 1103 return errors.Wrapf(err, "delete PVC %s", pvc.Name) 1104 } 1105 } 1106 1107 return nil 1108 } 1109 1110 func (r *ReconcilePerconaXtraDBCluster) deleteSecrets(cr *api.PerconaXtraDBCluster) error { 1111 secrets := []string{cr.Spec.SecretsName, "internal-" + cr.Name} 1112 1113 for _, secretName := range secrets { 1114 secret := &corev1.Secret{} 1115 err := r.client.Get(context.TODO(), types.NamespacedName{ 1116 Namespace: cr.Namespace, 1117 Name: secretName, 1118 }, secret) 1119 1120 if err != nil && !k8serrors.IsNotFound(err) { 1121 return errors.Wrap(err, "get secret") 1122 } 1123 1124 if k8serrors.IsNotFound(err) { 1125 continue 1126 } 1127 1128 err = r.client.Delete(context.TODO(), secret, &client.DeleteOptions{Preconditions: &metav1.Preconditions{UID: &secret.UID}}) 1129 if err != nil { 1130 return errors.Wrapf(err, "delete secret %s", secretName) 1131 } 1132 } 1133 1134 return nil 1135 } 1136 1137 func (r *ReconcilePerconaXtraDBCluster) deleteCerts(cr *api.PerconaXtraDBCluster) error { 1138 issuers := []string{ 1139 cr.Name + "-pxc-ca-issuer", 1140 cr.Name + "-pxc-issuer", 1141 } 1142 for _, issuerName := range issuers { 1143 issuer := &cm.Issuer{} 1144 err := r.client.Get(context.TODO(), types.NamespacedName{Namespace: cr.Namespace, Name: issuerName}, issuer) 1145 if err != nil { 1146 continue 1147 } 1148 1149 err = r.client.Delete(context.TODO(), issuer, &client.DeleteOptions{Preconditions: &metav1.Preconditions{UID: &issuer.UID}}) 1150 if err != nil { 1151 return errors.Wrapf(err, "delete issuer %s", issuerName) 1152 } 1153 } 1154 1155 certs := []string{ 1156 cr.Name + "-ssl", 1157 cr.Name + "-ssl-internal", 1158 cr.Name + "-ca-cert", 1159 } 1160 for _, certName := range certs { 1161 cert := &cm.Certificate{} 1162 err := r.client.Get(context.TODO(), types.NamespacedName{Namespace: cr.Namespace, Name: certName}, cert) 1163 if err != nil { 1164 continue 1165 } 1166 1167 err = r.client.Delete(context.TODO(), cert, &client.DeleteOptions{Preconditions: &metav1.Preconditions{UID: &cert.UID}}) 1168 if err != nil { 1169 return errors.Wrapf(err, "delete certificate %s", certName) 1170 } 1171 } 1172 1173 secrets := []string{ 1174 cr.Name + "-ca-cert", 1175 } 1176 1177 if len(cr.Spec.SSLSecretName) > 0 { 1178 secrets = append(secrets, cr.Spec.SSLSecretName) 1179 } else { 1180 secrets = append(secrets, cr.Name+"-ssl") 1181 } 1182 1183 if len(cr.Spec.SSLInternalSecretName) > 0 { 1184 secrets = append(secrets, cr.Spec.SSLInternalSecretName) 1185 } else { 1186 secrets = append(secrets, cr.Name+"-ssl-internal") 1187 } 1188 1189 for _, secretName := range secrets { 1190 secret := &corev1.Secret{} 1191 err := r.client.Get(context.TODO(), types.NamespacedName{Namespace: cr.Namespace, Name: secretName}, secret) 1192 if err != nil { 1193 continue 1194 } 1195 1196 err = r.client.Delete(context.TODO(), secret, &client.DeleteOptions{Preconditions: &metav1.Preconditions{UID: &secret.UID}}) 1197 if err != nil { 1198 return errors.Wrapf(err, "delete secret %s", secretName) 1199 } 1200 } 1201 1202 return nil 1203 } 1204 1205 func setControllerReference(ro runtime.Object, obj metav1.Object, scheme *runtime.Scheme) error { 1206 ownerRef, err := OwnerRef(ro, scheme) 1207 if err != nil { 1208 return err 1209 } 1210 obj.SetOwnerReferences(append(obj.GetOwnerReferences(), ownerRef)) 1211 return nil 1212 } 1213 1214 // OwnerRef returns OwnerReference to object 1215 func OwnerRef(ro runtime.Object, scheme *runtime.Scheme) (metav1.OwnerReference, error) { 1216 gvk, err := apiutil.GVKForObject(ro, scheme) 1217 if err != nil { 1218 return metav1.OwnerReference{}, err 1219 } 1220 1221 trueVar := true 1222 1223 ca, err := meta.Accessor(ro) 1224 if err != nil { 1225 return metav1.OwnerReference{}, err 1226 } 1227 1228 return metav1.OwnerReference{ 1229 APIVersion: gvk.GroupVersion().String(), 1230 Kind: gvk.Kind, 1231 Name: ca.GetName(), 1232 UID: ca.GetUID(), 1233 Controller: &trueVar, 1234 }, nil 1235 } 1236 1237 // resyncPXCUsersWithProxySQL calls the method of synchronizing users and makes sure that only one Goroutine works at a time 1238 func (r *ReconcilePerconaXtraDBCluster) resyncPXCUsersWithProxySQL(ctx context.Context, cr *api.PerconaXtraDBCluster) { 1239 if !cr.Spec.ProxySQLEnabled() { 1240 return 1241 } 1242 if cr.Status.Status != api.AppStateReady || !atomic.CompareAndSwapInt32(&r.syncUsersState, stateFree, stateLocked) { 1243 return 1244 } 1245 go func() { 1246 err := r.syncPXCUsersWithProxySQL(ctx, cr) 1247 if err != nil && !k8serrors.IsNotFound(err) { 1248 logf.FromContext(ctx).Error(err, "sync users") 1249 } 1250 atomic.StoreInt32(&r.syncUsersState, stateFree) 1251 }() 1252 } 1253 1254 func createOrUpdateConfigmap(cl client.Client, configMap *corev1.ConfigMap) error { 1255 currMap := &corev1.ConfigMap{} 1256 err := cl.Get(context.TODO(), types.NamespacedName{ 1257 Namespace: configMap.Namespace, 1258 Name: configMap.Name, 1259 }, currMap) 1260 if err != nil && !k8serrors.IsNotFound(err) { 1261 return errors.Wrap(err, "get current configmap") 1262 } 1263 1264 if k8serrors.IsNotFound(err) { 1265 return cl.Create(context.TODO(), configMap) 1266 } 1267 1268 if !reflect.DeepEqual(currMap.Data, configMap.Data) { 1269 return cl.Update(context.TODO(), configMap) 1270 } 1271 1272 return nil 1273 } 1274 1275 func deleteConfigMapIfExists(cl client.Client, cr *api.PerconaXtraDBCluster, cmName string) error { 1276 configMap := &corev1.ConfigMap{} 1277 1278 err := cl.Get(context.TODO(), types.NamespacedName{ 1279 Namespace: cr.Namespace, 1280 Name: cmName, 1281 }, configMap) 1282 if err != nil && !k8serrors.IsNotFound(err) { 1283 return errors.Wrap(err, "get config map") 1284 } 1285 1286 if k8serrors.IsNotFound(err) { 1287 return nil 1288 } 1289 1290 if !metav1.IsControlledBy(configMap, cr) { 1291 return nil 1292 } 1293 1294 return cl.Delete(context.Background(), configMap) 1295 } 1296 1297 func (r *ReconcilePerconaXtraDBCluster) createOrUpdate(cr *api.PerconaXtraDBCluster, obj client.Object) error { 1298 if obj.GetAnnotations() == nil { 1299 obj.SetAnnotations(make(map[string]string)) 1300 } 1301 1302 objAnnotations := obj.GetAnnotations() 1303 delete(objAnnotations, "percona.com/last-config-hash") 1304 obj.SetAnnotations(objAnnotations) 1305 1306 hash, err := getObjectHash(obj) 1307 if err != nil { 1308 return errors.Wrap(err, "calculate object hash") 1309 } 1310 1311 objAnnotations = obj.GetAnnotations() 1312 objAnnotations["percona.com/last-config-hash"] = hash 1313 obj.SetAnnotations(objAnnotations) 1314 1315 val := reflect.ValueOf(obj) 1316 if val.Kind() == reflect.Ptr { 1317 val = reflect.Indirect(val) 1318 } 1319 oldObject := reflect.New(val.Type()).Interface().(client.Object) 1320 1321 err = r.client.Get(context.Background(), types.NamespacedName{ 1322 Name: obj.GetName(), 1323 Namespace: obj.GetNamespace(), 1324 }, oldObject) 1325 1326 if err != nil && !k8serrors.IsNotFound(err) { 1327 return errors.Wrap(err, "get object") 1328 } 1329 1330 if k8serrors.IsNotFound(err) { 1331 return r.client.Create(context.TODO(), obj) 1332 } 1333 1334 if oldObject.GetAnnotations()["percona.com/last-config-hash"] != hash || 1335 !isObjectMetaEqual(obj, oldObject) { 1336 1337 obj.SetResourceVersion(oldObject.GetResourceVersion()) 1338 switch object := obj.(type) { 1339 case *corev1.Service: 1340 object.Spec.ClusterIP = oldObject.(*corev1.Service).Spec.ClusterIP 1341 if object.Spec.Type == corev1.ServiceTypeLoadBalancer { 1342 object.Spec.HealthCheckNodePort = oldObject.(*corev1.Service).Spec.HealthCheckNodePort 1343 } 1344 } 1345 1346 return r.client.Update(context.TODO(), obj) 1347 } 1348 1349 return nil 1350 } 1351 1352 func setIgnoredAnnotationsAndLabels(cr *api.PerconaXtraDBCluster, obj, oldObject client.Object) { 1353 oldAnnotations := oldObject.GetAnnotations() 1354 if oldAnnotations == nil { 1355 oldAnnotations = make(map[string]string) 1356 } 1357 annotations := obj.GetAnnotations() 1358 if annotations == nil { 1359 annotations = make(map[string]string) 1360 } 1361 for _, annotation := range cr.Spec.IgnoreAnnotations { 1362 if v, ok := oldAnnotations[annotation]; ok { 1363 annotations[annotation] = v 1364 } 1365 } 1366 obj.SetAnnotations(annotations) 1367 1368 oldLabels := oldObject.GetLabels() 1369 if oldLabels == nil { 1370 oldLabels = make(map[string]string) 1371 } 1372 labels := obj.GetLabels() 1373 if labels == nil { 1374 labels = make(map[string]string) 1375 } 1376 for _, label := range cr.Spec.IgnoreLabels { 1377 if v, ok := oldLabels[label]; ok { 1378 labels[label] = v 1379 } 1380 } 1381 obj.SetLabels(labels) 1382 } 1383 1384 func mergeMaps(x, y map[string]string) map[string]string { 1385 if x == nil { 1386 x = make(map[string]string) 1387 } 1388 for k, v := range y { 1389 if _, ok := x[k]; !ok { 1390 x[k] = v 1391 } 1392 } 1393 return x 1394 } 1395 1396 func (r *ReconcilePerconaXtraDBCluster) createOrUpdateService(cr *api.PerconaXtraDBCluster, svc *corev1.Service, saveOldMeta bool) error { 1397 err := setControllerReference(cr, svc, r.scheme) 1398 if err != nil { 1399 return errors.Wrap(err, "set controller reference") 1400 } 1401 if !saveOldMeta && len(cr.Spec.IgnoreAnnotations) == 0 && len(cr.Spec.IgnoreLabels) == 0 { 1402 return r.createOrUpdate(cr, svc) 1403 } 1404 oldSvc := new(corev1.Service) 1405 err = r.client.Get(context.TODO(), types.NamespacedName{ 1406 Name: svc.GetName(), 1407 Namespace: svc.GetNamespace(), 1408 }, oldSvc) 1409 if err != nil { 1410 if k8serrors.IsNotFound(err) { 1411 return r.createOrUpdate(cr, svc) 1412 } 1413 return errors.Wrap(err, "get object") 1414 } 1415 1416 if saveOldMeta { 1417 svc.SetAnnotations(mergeMaps(svc.GetAnnotations(), oldSvc.GetAnnotations())) 1418 svc.SetLabels(mergeMaps(svc.GetLabels(), oldSvc.GetLabels())) 1419 } 1420 setIgnoredAnnotationsAndLabels(cr, svc, oldSvc) 1421 1422 return r.createOrUpdate(cr, svc) 1423 } 1424 1425 func getObjectHash(obj runtime.Object) (string, error) { 1426 var dataToMarshall interface{} 1427 switch object := obj.(type) { 1428 case *appsv1.StatefulSet: 1429 dataToMarshall = object.Spec 1430 case *appsv1.Deployment: 1431 dataToMarshall = object.Spec 1432 case *corev1.Service: 1433 dataToMarshall = object.Spec 1434 default: 1435 dataToMarshall = obj 1436 } 1437 data, err := json.Marshal(dataToMarshall) 1438 if err != nil { 1439 return "", err 1440 } 1441 return base64.StdEncoding.EncodeToString(data), nil 1442 } 1443 1444 func isObjectMetaEqual(old, new metav1.Object) bool { 1445 return compareMaps(old.GetAnnotations(), new.GetAnnotations()) && 1446 compareMaps(old.GetLabels(), new.GetLabels()) 1447 } 1448 1449 func compareMaps(x, y map[string]string) bool { 1450 if len(x) != len(y) { 1451 return false 1452 } 1453 1454 for k, v := range x { 1455 yVal, ok := y[k] 1456 if !ok || yVal != v { 1457 return false 1458 } 1459 } 1460 1461 return true 1462 } 1463 1464 func (r *ReconcilePerconaXtraDBCluster) getConfigVolume(nsName, cvName, cmName string, useDefaultVolume bool) (corev1.Volume, error) { 1465 n := types.NamespacedName{ 1466 Namespace: nsName, 1467 Name: cmName, 1468 } 1469 1470 err := r.client.Get(context.TODO(), n, &corev1.Secret{}) 1471 if err == nil { 1472 return app.GetSecretVolumes(cvName, cmName, false), nil 1473 } 1474 if !k8serrors.IsNotFound(err) { 1475 return corev1.Volume{}, err 1476 } 1477 1478 err = r.client.Get(context.TODO(), n, &corev1.ConfigMap{}) 1479 if err == nil { 1480 return app.GetConfigVolumes(cvName, cmName), nil 1481 } 1482 if !k8serrors.IsNotFound(err) { 1483 return corev1.Volume{}, err 1484 } 1485 1486 if useDefaultVolume { 1487 return app.GetConfigVolumes(cvName, cmName), nil 1488 } 1489 1490 return corev1.Volume{}, api.NoCustomVolumeErr 1491 } 1492 1493 func getInitImage(ctx context.Context, cr *api.PerconaXtraDBCluster, cli client.Client) (string, error) { 1494 if len(cr.Spec.InitContainer.Image) > 0 { 1495 return cr.Spec.InitContainer.Image, nil 1496 } 1497 if len(cr.Spec.InitImage) > 0 { 1498 return cr.Spec.InitImage, nil 1499 } 1500 operatorPod, err := k8s.OperatorPod(ctx, cli) 1501 if err != nil { 1502 return "", errors.Wrap(err, "get operator deployment") 1503 } 1504 imageName, err := operatorImageName(&operatorPod) 1505 if err != nil { 1506 return "", err 1507 } 1508 if cr.CompareVersionWith(version.Version) != 0 { 1509 imageName = strings.Split(imageName, ":")[0] + ":" + cr.Spec.CRVersion 1510 } 1511 return imageName, nil 1512 } 1513 1514 func operatorImageName(operatorPod *corev1.Pod) (string, error) { 1515 for _, c := range operatorPod.Spec.Containers { 1516 if c.Name == "percona-xtradb-cluster-operator" { 1517 return c.Image, nil 1518 } 1519 } 1520 return "", errors.New("operator image not found") 1521 }