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  }