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  }