github.com/percona/percona-xtradb-cluster-operator@v1.14.0/pkg/controller/pxc/status_test.go (about) 1 package pxc 2 3 import ( 4 "fmt" 5 "testing" 6 7 api "github.com/percona/percona-xtradb-cluster-operator/pkg/apis/pxc/v1" 8 "github.com/percona/percona-xtradb-cluster-operator/pkg/pxc/app/statefulset" 9 "github.com/percona/percona-xtradb-cluster-operator/version" 10 "github.com/pkg/errors" 11 12 corev1 "k8s.io/api/core/v1" 13 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 14 "k8s.io/apimachinery/pkg/runtime" 15 "k8s.io/client-go/kubernetes/scheme" 16 "sigs.k8s.io/controller-runtime/pkg/client/fake" // nolint 17 ) 18 19 var podStatusReady = corev1.PodStatus{ 20 ContainerStatuses: []corev1.ContainerStatus{ 21 {Ready: true}, 22 }, 23 Conditions: []corev1.PodCondition{ 24 { 25 Type: corev1.ContainersReady, 26 Status: corev1.ConditionTrue, 27 }, 28 }, 29 } 30 31 func newCR(name, namespace string) *api.PerconaXtraDBCluster { 32 return &api.PerconaXtraDBCluster{ 33 ObjectMeta: metav1.ObjectMeta{ 34 Name: name, 35 Namespace: namespace, 36 }, 37 Spec: api.PerconaXtraDBClusterSpec{ 38 Platform: version.PlatformKubernetes, 39 CRVersion: "1.6.0", 40 PXC: &api.PXCSpec{ 41 PodSpec: &api.PodSpec{ 42 Enabled: true, 43 Size: 3, 44 }, 45 Expose: api.ServiceExpose{ 46 Enabled: false, 47 Type: corev1.ServiceTypeClusterIP, 48 }, 49 }, 50 HAProxy: &api.HAProxySpec{ 51 PodSpec: api.PodSpec{ 52 Enabled: true, 53 Size: 3, 54 }, 55 ExposePrimary: api.ServiceExpose{ 56 Enabled: false, 57 Type: corev1.ServiceTypeClusterIP, 58 }, 59 ExposeReplicas: &api.ServiceExpose{ 60 Enabled: false, 61 Type: corev1.ServiceTypeClusterIP, 62 }, 63 }, 64 ProxySQL: &api.ProxySQLSpec{ 65 PodSpec: api.PodSpec{ 66 Enabled: false, 67 }, 68 Expose: api.ServiceExpose{ 69 Enabled: false, 70 Type: corev1.ServiceTypeClusterIP, 71 }, 72 }, 73 }, 74 Status: api.PerconaXtraDBClusterStatus{}, 75 } 76 } 77 78 func newMockPod(name, namespace string, labels map[string]string, status corev1.PodStatus) *corev1.Pod { 79 return &corev1.Pod{ 80 ObjectMeta: metav1.ObjectMeta{ 81 Name: name, 82 Namespace: namespace, 83 Labels: labels, 84 }, 85 Spec: corev1.PodSpec{}, 86 Status: status, 87 } 88 } 89 90 // creates a fake client to mock API calls with the mock objects 91 func buildFakeClient(objs []runtime.Object) *ReconcilePerconaXtraDBCluster { 92 s := scheme.Scheme 93 94 s.AddKnownTypes(api.SchemeGroupVersion, &api.PerconaXtraDBCluster{}) 95 96 cl := fake.NewClientBuilder(). 97 WithScheme(s). 98 WithRuntimeObjects(objs...). 99 WithStatusSubresource(&api.PerconaXtraDBCluster{}). 100 Build() 101 102 return &ReconcilePerconaXtraDBCluster{client: cl, scheme: s} 103 } 104 105 func TestAppStatusInit(t *testing.T) { 106 cr := newCR("cr-mock", "pxc") 107 108 pxc := statefulset.NewNode(cr) 109 pxcSfs := pxc.StatefulSet() 110 111 r := buildFakeClient([]runtime.Object{cr, pxcSfs}) 112 113 status, err := r.appStatus(pxc, cr.Namespace, cr.Spec.PXC.PodSpec, cr.CompareVersionWith("1.7.0") == -1, false) 114 if err != nil { 115 t.Error(err) 116 } 117 118 if status.Status != api.AppStateInit { 119 t.Errorf("AppStatus.Status got %#v, want %#v", status.Status, api.AppStateInit) 120 } 121 } 122 123 func TestPXCAppStatusReady(t *testing.T) { 124 cr := newCR("cr-mock", "pxc") 125 126 pxc := statefulset.NewNode(cr) 127 pxcSfs := pxc.StatefulSet() 128 129 objs := []runtime.Object{cr, pxcSfs} 130 131 for i := 0; i < int(cr.Spec.PXC.Size); i++ { 132 objs = append(objs, newMockPod(fmt.Sprintf("pxc-mock-%d", i), cr.Namespace, pxc.Labels(), podStatusReady)) 133 } 134 135 r := buildFakeClient(objs) 136 137 status, err := r.appStatus(pxc, cr.Namespace, cr.Spec.PXC.PodSpec, cr.CompareVersionWith("1.7.0") == -1, false) 138 if err != nil { 139 t.Error(err) 140 } 141 142 if status.Status != api.AppStateReady { 143 t.Errorf("AppStatus.Status got %#v, want %#v", status.Status, api.AppStateReady) 144 } 145 146 if status.Ready != cr.Spec.PXC.Size { 147 t.Errorf("AppStatus.Ready got %#v, want %#v", status.Ready, cr.Spec.PXC.Size) 148 } 149 } 150 151 func TestHAProxyAppStatusReady(t *testing.T) { 152 cr := newCR("cr-mock", "pxc") 153 154 haproxy := statefulset.NewHAProxy(cr) 155 haproxySfs := haproxy.StatefulSet() 156 157 objs := []runtime.Object{cr, haproxySfs} 158 159 for i := 0; i < int(cr.Spec.HAProxy.Size); i++ { 160 objs = append(objs, newMockPod(fmt.Sprintf("haproxy-mock-%d", i), cr.Namespace, haproxy.Labels(), podStatusReady)) 161 } 162 163 r := buildFakeClient(objs) 164 165 status, err := r.appStatus(haproxy, cr.Namespace, cr.Spec.PXC.PodSpec, cr.CompareVersionWith("1.7.0") == -1, false) 166 if err != nil { 167 t.Error(err) 168 } 169 170 if status.Status != api.AppStateReady { 171 t.Errorf("AppStatus.Status got %#v, want %#v", status.Status, api.AppStateReady) 172 } 173 174 if status.Ready != cr.Spec.HAProxy.Size { 175 t.Errorf("AppStatus.Ready got %#v, want %#v", status.Ready, cr.Spec.HAProxy.Size) 176 } 177 } 178 179 func TestUpdateStatusReady(t *testing.T) { 180 cr := newCR("cr-mock", "pxc") 181 182 pxc := statefulset.NewNode(cr) 183 pxcSfs := pxc.StatefulSet() 184 haproxy := statefulset.NewHAProxy(cr) 185 haproxySfs := haproxy.StatefulSet() 186 187 objs := []runtime.Object{cr, pxcSfs, haproxySfs} 188 189 for i := 0; i < int(cr.Spec.PXC.Size); i++ { 190 objs = append(objs, newMockPod(fmt.Sprintf("pxc-mock-%d", i), cr.Namespace, pxc.Labels(), podStatusReady)) 191 } 192 193 for i := 0; i < int(cr.Spec.HAProxy.Size); i++ { 194 objs = append(objs, newMockPod(fmt.Sprintf("haproxy-mock-%d", i), cr.Namespace, haproxy.Labels(), podStatusReady)) 195 } 196 197 r := buildFakeClient(objs) 198 199 if err := r.updateStatus(cr, false, nil); err != nil { 200 t.Error(err) 201 } 202 203 if cr.Status.Status != api.AppStateReady { 204 t.Errorf("cr.Status.Status got %#v, want %#v", cr.Status.Status, api.AppStateReady) 205 } 206 } 207 208 func TestUpdateStatusError(t *testing.T) { 209 cr := newCR("cr-mock", "pxc") 210 211 pxc := statefulset.NewNode(cr) 212 pxcSfs := pxc.StatefulSet() 213 214 haproxy := statefulset.NewHAProxy(cr) 215 haproxySfs := haproxy.StatefulSet() 216 217 r := buildFakeClient([]runtime.Object{cr, pxcSfs, haproxySfs}) 218 219 if err := r.updateStatus(cr, false, errors.New("mock error")); err != nil { 220 t.Error(err) 221 } 222 223 if cr.Status.Status != api.AppStateError { 224 t.Errorf("cr.Status.Status got %#v, want %#v", cr.Status.Status, api.AppStateError) 225 } 226 } 227 228 func TestAppHostNoLoadBalancer(t *testing.T) { 229 cr := newCR("cr-mock", "pxc") 230 231 pxc := statefulset.NewNode(cr) 232 pxcSfs := pxc.StatefulSet() 233 234 haproxy := statefulset.NewHAProxy(cr) 235 haproxySfs := haproxy.StatefulSet() 236 237 r := buildFakeClient([]runtime.Object{cr, pxcSfs, haproxySfs}) 238 239 host, err := r.appHost(cr, haproxy, &cr.Spec.HAProxy.PodSpec, &cr.Spec.HAProxy.ExposePrimary) 240 if err != nil { 241 t.Error(err) 242 } 243 244 want := haproxy.Service() + "." + cr.Namespace 245 if host != want { 246 t.Errorf("host got %#v, want %#v", host, want) 247 } 248 } 249 250 func TestAppHostLoadBalancerNoSvc(t *testing.T) { 251 cr := newCR("cr-mock", "pxc") 252 cr.Spec.CRVersion = "1.14.0" 253 254 pxc := statefulset.NewNode(cr) 255 pxcSfs := pxc.StatefulSet() 256 257 haproxy := statefulset.NewHAProxy(cr) 258 haproxySfs := haproxy.StatefulSet() 259 cr.Spec.HAProxy.ExposePrimary.Type = corev1.ServiceTypeLoadBalancer 260 261 r := buildFakeClient([]runtime.Object{cr, pxcSfs, haproxySfs}) 262 263 _, err := r.appHost(cr, haproxy, &cr.Spec.HAProxy.PodSpec, &cr.Spec.HAProxy.ExposePrimary) 264 if err == nil { 265 t.Error("want err, got nil") 266 } 267 } 268 269 func TestAppHostLoadBalancerOnlyIP(t *testing.T) { 270 cr := newCR("cr-mock", "pxc") 271 cr.Spec.CRVersion = "1.14.0" 272 273 pxc := statefulset.NewNode(cr) 274 pxcSfs := pxc.StatefulSet() 275 276 haproxy := statefulset.NewHAProxy(cr) 277 haproxySfs := haproxy.StatefulSet() 278 cr.Spec.HAProxy.ExposePrimary.Type = corev1.ServiceTypeLoadBalancer 279 ip := "99.99.99.99" 280 haproxySvc := &corev1.Service{ 281 ObjectMeta: metav1.ObjectMeta{ 282 Name: haproxy.Service(), 283 Namespace: cr.Namespace, 284 }, 285 Status: corev1.ServiceStatus{ 286 LoadBalancer: corev1.LoadBalancerStatus{ 287 Ingress: []corev1.LoadBalancerIngress{{IP: ip}}, 288 }, 289 }, 290 } 291 292 r := buildFakeClient([]runtime.Object{cr, pxcSfs, haproxySfs, haproxySvc}) 293 294 host, err := r.appHost(cr, haproxy, &cr.Spec.HAProxy.PodSpec, &cr.Spec.HAProxy.ExposePrimary) 295 if err != nil { 296 t.Error(err) 297 } 298 299 if host != ip { 300 t.Errorf("host got %#v, want %#v", host, ip) 301 } 302 } 303 304 func TestAppHostLoadBalancerWithHostname(t *testing.T) { 305 cr := newCR("cr-mock", "pxc") 306 cr.Spec.CRVersion = "1.14.0" 307 308 pxc := statefulset.NewNode(cr) 309 pxcSfs := pxc.StatefulSet() 310 311 haproxy := statefulset.NewHAProxy(cr) 312 haproxySfs := haproxy.StatefulSet() 313 cr.Spec.HAProxy.ExposePrimary.Type = corev1.ServiceTypeLoadBalancer 314 wantHost := "cr-mock.haproxy.test" 315 haproxySvc := &corev1.Service{ 316 ObjectMeta: metav1.ObjectMeta{ 317 Name: haproxy.Service(), 318 Namespace: cr.Namespace, 319 }, 320 Status: corev1.ServiceStatus{ 321 LoadBalancer: corev1.LoadBalancerStatus{ 322 Ingress: []corev1.LoadBalancerIngress{{IP: "99.99.99.99", Hostname: wantHost}}, 323 }, 324 }, 325 } 326 327 r := buildFakeClient([]runtime.Object{cr, pxcSfs, haproxySfs, haproxySvc}) 328 329 gotHost, err := r.appHost(cr, haproxy, &cr.Spec.HAProxy.PodSpec, &cr.Spec.HAProxy.ExposePrimary) 330 if err != nil { 331 t.Error(err) 332 } 333 334 if gotHost != wantHost { 335 t.Errorf("host got %#v, want %#v", gotHost, wantHost) 336 } 337 } 338 339 func TestClusterStatus(t *testing.T) { 340 tests := map[string]struct { 341 status api.PerconaXtraDBClusterStatus 342 want api.AppState 343 }{ 344 "Unknown": { 345 status: api.PerconaXtraDBClusterStatus{}, 346 want: api.AppStateInit, 347 }, 348 "PXC init": { 349 status: api.PerconaXtraDBClusterStatus{PXC: api.AppStatus{ComponentStatus: api.ComponentStatus{Status: api.AppStateInit}}}, 350 want: api.AppStateInit, 351 }, 352 "PXC ready without host": { 353 status: api.PerconaXtraDBClusterStatus{PXC: api.AppStatus{ComponentStatus: api.ComponentStatus{Status: api.AppStateReady}}}, 354 want: api.AppStateInit, 355 }, 356 "PXC ready with host": { 357 status: api.PerconaXtraDBClusterStatus{ 358 PXC: api.AppStatus{ 359 ComponentStatus: api.ComponentStatus{ 360 Status: api.AppStateReady, 361 }, 362 }, 363 Host: "localhost", 364 }, 365 want: api.AppStateReady, 366 }, 367 "PXC stopping": { 368 status: api.PerconaXtraDBClusterStatus{PXC: api.AppStatus{ComponentStatus: api.ComponentStatus{Status: api.AppStateStopping}}}, 369 want: api.AppStateStopping, 370 }, 371 "PXC paused, HAProxy stopping": { 372 status: api.PerconaXtraDBClusterStatus{ 373 PXC: api.AppStatus{ComponentStatus: api.ComponentStatus{Status: api.AppStatePaused}}, 374 HAProxy: api.AppStatus{ComponentStatus: api.ComponentStatus{Status: api.AppStateStopping}}, 375 }, 376 want: api.AppStateStopping, 377 }, 378 "PXC paused, ProxySQL stopping": { 379 status: api.PerconaXtraDBClusterStatus{ 380 PXC: api.AppStatus{ComponentStatus: api.ComponentStatus{Status: api.AppStatePaused}}, 381 ProxySQL: api.AppStatus{ComponentStatus: api.ComponentStatus{Status: api.AppStateStopping}}, 382 }, 383 want: api.AppStateStopping, 384 }, 385 "PXC paused, HAProxy paused": { 386 status: api.PerconaXtraDBClusterStatus{ 387 PXC: api.AppStatus{ComponentStatus: api.ComponentStatus{Status: api.AppStatePaused}}, 388 HAProxy: api.AppStatus{ComponentStatus: api.ComponentStatus{Status: api.AppStatePaused}}, 389 }, 390 want: api.AppStatePaused, 391 }, 392 "HAProxy init": { 393 status: api.PerconaXtraDBClusterStatus{HAProxy: api.AppStatus{ComponentStatus: api.ComponentStatus{Status: api.AppStateInit}}}, 394 want: api.AppStateInit, 395 }, 396 "HAProxy ready without host": { 397 status: api.PerconaXtraDBClusterStatus{ 398 PXC: api.AppStatus{ComponentStatus: api.ComponentStatus{Status: api.AppStateReady}}, 399 HAProxy: api.AppStatus{ComponentStatus: api.ComponentStatus{Status: api.AppStateReady}}, 400 }, 401 want: api.AppStateInit, 402 }, 403 "HAProxy ready with host": { 404 status: api.PerconaXtraDBClusterStatus{ 405 PXC: api.AppStatus{ComponentStatus: api.ComponentStatus{Status: api.AppStateReady}}, 406 HAProxy: api.AppStatus{ComponentStatus: api.ComponentStatus{Status: api.AppStateReady}}, 407 Host: "localhost", 408 }, 409 want: api.AppStateReady, 410 }, 411 "ProxySQL init": { 412 status: api.PerconaXtraDBClusterStatus{ProxySQL: api.AppStatus{ComponentStatus: api.ComponentStatus{Status: api.AppStateInit}}}, 413 want: api.AppStateInit, 414 }, 415 "ProxySQL ready without host": { 416 status: api.PerconaXtraDBClusterStatus{ 417 PXC: api.AppStatus{ComponentStatus: api.ComponentStatus{Status: api.AppStateReady}}, 418 ProxySQL: api.AppStatus{ComponentStatus: api.ComponentStatus{Status: api.AppStateReady}}, 419 }, 420 want: api.AppStateInit, 421 }, 422 "ProxySQL ready with host": { 423 status: api.PerconaXtraDBClusterStatus{ 424 PXC: api.AppStatus{ComponentStatus: api.ComponentStatus{Status: api.AppStateReady}}, 425 ProxySQL: api.AppStatus{ComponentStatus: api.ComponentStatus{Status: api.AppStateReady}}, 426 Host: "localhost", 427 }, 428 want: api.AppStateReady, 429 }, 430 } 431 432 for name, test := range tests { 433 t.Run(name, func(tt *testing.T) { 434 got := test.status.ClusterStatus(false, false) 435 436 if got != test.want { 437 tt.Errorf("AppState got %#v, want %#v", got, test.want) 438 } 439 }) 440 } 441 }