github.com/percona/percona-xtradb-cluster-operator@v1.14.0/pkg/controller/pxc/status.go (about)

     1  package pxc
     2  
     3  import (
     4  	"context"
     5  	"time"
     6  
     7  	appsv1 "k8s.io/api/apps/v1"
     8  	corev1 "k8s.io/api/core/v1"
     9  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    10  	"k8s.io/apimachinery/pkg/labels"
    11  	"k8s.io/apimachinery/pkg/types"
    12  	k8sretry "k8s.io/client-go/util/retry"
    13  	"sigs.k8s.io/controller-runtime/pkg/client"
    14  
    15  	api "github.com/percona/percona-xtradb-cluster-operator/pkg/apis/pxc/v1"
    16  	"github.com/percona/percona-xtradb-cluster-operator/pkg/pxc/app/statefulset"
    17  	"github.com/pkg/errors"
    18  )
    19  
    20  func (r *ReconcilePerconaXtraDBCluster) updateStatus(cr *api.PerconaXtraDBCluster, inProgress bool, reconcileErr error) (err error) {
    21  	clusterCondition := api.ClusterCondition{
    22  		Status:             api.ConditionTrue,
    23  		Type:               api.AppStateInit,
    24  		LastTransitionTime: metav1.NewTime(time.Now()),
    25  	}
    26  
    27  	if reconcileErr != nil {
    28  		if cr.Status.Status != api.AppStateError {
    29  			clusterCondition := api.ClusterCondition{
    30  				Status:             api.ConditionTrue,
    31  				Type:               api.AppStateError,
    32  				Message:            reconcileErr.Error(),
    33  				Reason:             "ErrorReconcile",
    34  				LastTransitionTime: metav1.NewTime(time.Now()),
    35  			}
    36  			cr.Status.AddCondition(clusterCondition)
    37  
    38  			cr.Status.Messages = append(cr.Status.Messages, "Error: "+reconcileErr.Error())
    39  			cr.Status.Status = api.AppStateError
    40  		}
    41  
    42  		return r.writeStatus(cr)
    43  	}
    44  
    45  	if cr.PVCResizeInProgress() {
    46  		cr.Status.Status = api.AppStateInit
    47  		return r.writeStatus(cr)
    48  	}
    49  
    50  	cr.Status.Messages = cr.Status.Messages[:0]
    51  
    52  	type sfsstatus struct {
    53  		app    api.StatefulApp
    54  		status *api.AppStatus
    55  		spec   *api.PodSpec
    56  		expose *api.ServiceExpose
    57  	}
    58  
    59  	// Maintaining the order of this slice is important!
    60  	// PXC has to be the first object in the slice for cr.Status.Host to be correct.
    61  	// HAProxy and ProxySQL are mutually exclusive and their order shouldn't be important.
    62  	apps := []sfsstatus{
    63  		{
    64  			app:    statefulset.NewNode(cr),
    65  			status: &cr.Status.PXC,
    66  			spec:   cr.Spec.PXC.PodSpec,
    67  			expose: &cr.Spec.PXC.Expose,
    68  		},
    69  	}
    70  
    71  	cr.Status.HAProxy = api.AppStatus{
    72  		ComponentStatus: api.ComponentStatus{
    73  			Version: cr.Status.HAProxy.Version,
    74  		},
    75  	}
    76  	if cr.HAProxyEnabled() {
    77  		apps = append(apps, sfsstatus{
    78  			app:    statefulset.NewHAProxy(cr),
    79  			status: &cr.Status.HAProxy,
    80  			spec:   &cr.Spec.HAProxy.PodSpec,
    81  			expose: &cr.Spec.HAProxy.ExposePrimary,
    82  		})
    83  	}
    84  
    85  	cr.Status.ProxySQL = api.AppStatus{
    86  		ComponentStatus: api.ComponentStatus{
    87  			Version: cr.Status.ProxySQL.Version,
    88  		},
    89  	}
    90  	if cr.ProxySQLEnabled() {
    91  		apps = append(apps, sfsstatus{
    92  			app:    statefulset.NewProxy(cr),
    93  			status: &cr.Status.ProxySQL,
    94  			spec:   &cr.Spec.ProxySQL.PodSpec,
    95  			expose: &cr.Spec.ProxySQL.Expose,
    96  		})
    97  	}
    98  
    99  	cr.Status.Size = 0
   100  	cr.Status.Ready = 0
   101  	for _, a := range apps {
   102  		status, err := r.appStatus(a.app, cr.Namespace, a.spec, cr.CompareVersionWith("1.7.0") == -1, cr.Spec.Pause)
   103  		if err != nil {
   104  			return errors.Wrapf(err, "get %s status", a.app.Name())
   105  		}
   106  		status.Version = a.status.Version
   107  		status.Image = a.status.Image
   108  		// Ready count can be greater than total size in case of downscale
   109  		if status.Ready > status.Size {
   110  			status.Ready = status.Size
   111  		}
   112  		*a.status = status
   113  
   114  		host, err := r.appHost(cr, a.app, a.spec, a.expose)
   115  		if err != nil {
   116  			return errors.Wrapf(err, "get %s host", a.app.Name())
   117  		}
   118  		cr.Status.Host = host
   119  
   120  		if a.status.Message != "" {
   121  			cr.Status.Messages = append(cr.Status.Messages, a.app.Name()+": "+a.status.Message)
   122  		}
   123  
   124  		cr.Status.Size += status.Size
   125  		cr.Status.Ready += status.Ready
   126  
   127  		if !inProgress {
   128  			inProgress, err = r.upgradeInProgress(cr, a.app.Name())
   129  			if err != nil {
   130  				return errors.Wrapf(err, "check %s upgrade progress", a.app.Name())
   131  			}
   132  		}
   133  	}
   134  
   135  	cr.Status.Status = cr.Status.ClusterStatus(inProgress, cr.ObjectMeta.DeletionTimestamp != nil)
   136  	clusterCondition.Type = cr.Status.Status
   137  	cr.Status.AddCondition(clusterCondition)
   138  	cr.Status.ObservedGeneration = cr.ObjectMeta.Generation
   139  
   140  	return r.writeStatus(cr)
   141  }
   142  
   143  func (r *ReconcilePerconaXtraDBCluster) writeStatus(cr *api.PerconaXtraDBCluster) error {
   144  	err := k8sretry.RetryOnConflict(k8sretry.DefaultRetry, func() error {
   145  		c := &api.PerconaXtraDBCluster{}
   146  
   147  		err := r.client.Get(context.TODO(), types.NamespacedName{Name: cr.Name, Namespace: cr.Namespace}, c)
   148  		if err != nil {
   149  			return err
   150  		}
   151  
   152  		c.Status = cr.Status
   153  
   154  		return r.client.Status().Update(context.TODO(), c)
   155  	})
   156  
   157  	return errors.Wrap(err, "write status")
   158  }
   159  
   160  func (r *ReconcilePerconaXtraDBCluster) upgradeInProgress(cr *api.PerconaXtraDBCluster, appName string) (bool, error) {
   161  	sfsObj := &appsv1.StatefulSet{}
   162  	err := r.client.Get(context.TODO(), types.NamespacedName{Name: cr.Name + "-" + appName, Namespace: cr.Namespace}, sfsObj)
   163  	if err != nil {
   164  		return false, err
   165  	}
   166  	return sfsObj.Status.Replicas > sfsObj.Status.UpdatedReplicas, nil
   167  }
   168  
   169  // appStatus counts the ready pods in statefulset (PXC, HAProxy, ProxySQL).
   170  // If ready pods are equal to the size of the statefulset, we consider them ready.
   171  // If a pod is in the unschedulable state for more than 1 min, we consider the statefulset in an error state.
   172  // Otherwise, we consider the statefulset is initializing.
   173  func (r *ReconcilePerconaXtraDBCluster) appStatus(app api.StatefulApp, namespace string, podSpec *api.PodSpec, crLt170, paused bool) (api.AppStatus, error) {
   174  	list := corev1.PodList{}
   175  	err := r.client.List(context.TODO(),
   176  		&list,
   177  		&client.ListOptions{
   178  			Namespace:     namespace,
   179  			LabelSelector: labels.SelectorFromSet(app.Labels()),
   180  		},
   181  	)
   182  	if err != nil {
   183  		return api.AppStatus{}, errors.Wrap(err, "get pod list")
   184  	}
   185  	sfs := app.StatefulSet()
   186  	err = r.client.Get(context.TODO(), types.NamespacedName{Name: sfs.Name, Namespace: sfs.Namespace}, sfs)
   187  	if err != nil {
   188  		return api.AppStatus{}, errors.Wrap(err, "get statefulset")
   189  	}
   190  
   191  	status := api.AppStatus{
   192  		Size: podSpec.Size,
   193  		ComponentStatus: api.ComponentStatus{
   194  			Status:            api.AppStateInit,
   195  			LabelSelectorPath: labels.SelectorFromSet(app.Labels()).String(),
   196  		},
   197  	}
   198  
   199  	for _, pod := range list.Items {
   200  		for _, cntr := range pod.Status.ContainerStatuses {
   201  			if cntr.State.Waiting != nil && cntr.State.Waiting.Message != "" {
   202  				status.Message += cntr.Name + ": " + cntr.State.Waiting.Message + "; "
   203  			}
   204  		}
   205  
   206  		for _, cond := range pod.Status.Conditions {
   207  			switch cond.Type {
   208  			case corev1.ContainersReady:
   209  				if cond.Status != corev1.ConditionTrue {
   210  					continue
   211  				}
   212  
   213  				if !isPXC(app) || crLt170 {
   214  					status.Ready++
   215  					continue
   216  				}
   217  
   218  				isPodWaitingForRecovery, _, err := r.isPodWaitingForRecovery(namespace, pod.Name)
   219  				if err != nil {
   220  					return api.AppStatus{}, errors.Wrapf(err, "parse %s pod logs", pod.Name)
   221  				}
   222  
   223  				if !isPodWaitingForRecovery && pod.ObjectMeta.Labels["controller-revision-hash"] == sfs.Status.UpdateRevision {
   224  					status.Ready++
   225  				}
   226  			case corev1.PodScheduled:
   227  				if cond.Reason == corev1.PodReasonUnschedulable &&
   228  					cond.LastTransitionTime.Time.Before(time.Now().Add(-1*time.Minute)) {
   229  					status.Message = cond.Message
   230  				}
   231  			}
   232  		}
   233  	}
   234  
   235  	switch {
   236  	case paused && status.Ready > 0:
   237  		status.Status = api.AppStateStopping
   238  	case paused:
   239  		status.Status = api.AppStatePaused
   240  	case status.Size == status.Ready:
   241  		status.Status = api.AppStateReady
   242  	}
   243  
   244  	return status, nil
   245  }
   246  
   247  func (r *ReconcilePerconaXtraDBCluster) appHost(cr *api.PerconaXtraDBCluster, app api.StatefulApp,
   248  	podSpec *api.PodSpec, expose *api.ServiceExpose) (string, error) {
   249  	svcName := app.Service()
   250  	if app.Name() == "proxysql" {
   251  		svcName = cr.Name + "-proxysql"
   252  	}
   253  
   254  	svcType := expose.Type
   255  	if cr.CompareVersionWith("1.14.0") < 0 {
   256  		svcType = podSpec.ServiceType
   257  	}
   258  
   259  	if svcType != corev1.ServiceTypeLoadBalancer {
   260  		return svcName + "." + cr.Namespace, nil
   261  	}
   262  
   263  	svc := &corev1.Service{}
   264  	err := r.client.Get(context.TODO(), types.NamespacedName{Namespace: cr.Namespace, Name: svcName}, svc)
   265  	if err != nil {
   266  		return "", errors.Wrapf(err, "get %s service", app.Name())
   267  	}
   268  
   269  	var host string
   270  
   271  	for _, i := range svc.Status.LoadBalancer.Ingress {
   272  		host = i.IP
   273  		if len(i.Hostname) > 0 {
   274  			host = i.Hostname
   275  		}
   276  	}
   277  
   278  	return host, nil
   279  }