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 }