diff --git a/pkg/clustermanager/cluster_manager.go b/pkg/clustermanager/cluster_manager.go index 05f88c43a374..8497e8210131 100644 --- a/pkg/clustermanager/cluster_manager.go +++ b/pkg/clustermanager/cluster_manager.go @@ -90,6 +90,7 @@ type ClusterClient interface { GetNamespace(ctx context.Context, kubeconfig string, namespace string) error ValidateControlPlaneNodes(ctx context.Context, cluster *types.Cluster, clusterName string) error ValidateWorkerNodes(ctx context.Context, clusterName string, kubeconfigFile string) error + CountMachineDeploymentReplicasReady(ctx context.Context, clusterName string, kubeconfigFile string) (int, int, error) GetBundles(ctx context.Context, kubeconfigFile, name, namespace string) (*releasev1alpha1.Bundles, error) GetApiServerUrl(ctx context.Context, cluster *types.Cluster) (string, error) GetClusterCATlsCert(ctx context.Context, clusterName string, cluster *types.Cluster, namespace string) ([]byte, error) @@ -707,17 +708,25 @@ func (c *ClusterManager) waitForControlPlaneReplicasReady(ctx context.Context, m } func (c *ClusterManager) waitForMachineDeploymentReplicasReady(ctx context.Context, managementCluster *types.Cluster, clusterSpec *cluster.Spec) error { + ready, total := 0, 0 + policy := func(_ int, _ error) (bool, time.Duration) { + return true, c.machineBackoff * time.Duration(total-ready) + } + var machineDeploymentReplicasCount int for _, workerNodeGroupConfiguration := range clusterSpec.Cluster.Spec.WorkerNodeGroupConfigurations { machineDeploymentReplicasCount += workerNodeGroupConfiguration.Count } - isMdReady := func() error { - return c.clusterClient.ValidateWorkerNodes(ctx, clusterSpec.Cluster.Name, managementCluster.KubeconfigFile) - } - - err := isMdReady() - if err == nil { + areMdReplicasReady := func() error { + var err error + ready, total, err = c.clusterClient.CountMachineDeploymentReplicasReady(ctx, clusterSpec.Cluster.Name, managementCluster.KubeconfigFile) + if err != nil { + return err + } + if ready != total { + return fmt.Errorf("%d machine deployment replicas are not ready", total-ready) + } return nil } @@ -726,8 +735,8 @@ func (c *ClusterManager) waitForMachineDeploymentReplicasReady(ctx context.Conte timeout = c.machinesMinWait } - r := retrier.New(timeout) - if err := r.Retry(isMdReady); err != nil { + r := retrier.New(timeout, retrier.WithRetryPolicy(policy)) + if err := r.Retry(areMdReplicasReady); err != nil { return fmt.Errorf("retries exhausted waiting for machinedeployment replicas to be ready: %v", err) } return nil diff --git a/pkg/clustermanager/cluster_manager_test.go b/pkg/clustermanager/cluster_manager_test.go index 5765137ece41..7cfacbbfde38 100644 --- a/pkg/clustermanager/cluster_manager_test.go +++ b/pkg/clustermanager/cluster_manager_test.go @@ -479,7 +479,7 @@ func TestClusterManagerUpgradeSelfManagedClusterSuccess(t *testing.T) { tt.mocks.provider.EXPECT().MachineDeploymentsToDelete(wCluster, tt.clusterSpec, tt.clusterSpec.DeepCopy()).Return([]string{}) tt.mocks.client.EXPECT().WaitForDeployment(tt.ctx, wCluster, "30m", "Available", gomock.Any(), gomock.Any()).MaxTimes(10) tt.mocks.client.EXPECT().ValidateControlPlaneNodes(tt.ctx, mCluster, wCluster.Name).Return(nil) - tt.mocks.client.EXPECT().ValidateWorkerNodes(tt.ctx, wCluster.Name, mCluster.KubeconfigFile).Return(nil) + tt.mocks.client.EXPECT().CountMachineDeploymentReplicasReady(tt.ctx, wCluster.Name, mCluster.KubeconfigFile).Return(0, 0, nil) tt.mocks.provider.EXPECT().GetDeployments() tt.mocks.writer.EXPECT().Write(clusterName+"-eks-a-cluster.yaml", gomock.Any(), gomock.Not(gomock.Nil())) tt.mocks.client.EXPECT().GetEksaOIDCConfig(tt.ctx, tt.clusterSpec.Cluster.Spec.IdentityProviderRefs[0].Name, tt.cluster.KubeconfigFile, tt.clusterSpec.Cluster.Namespace).Return(nil, nil) @@ -515,7 +515,7 @@ func TestClusterManagerUpgradeWorkloadClusterSuccess(t *testing.T) { tt.mocks.provider.EXPECT().MachineDeploymentsToDelete(mCluster, tt.clusterSpec, tt.clusterSpec.DeepCopy()).Return([]string{}) tt.mocks.client.EXPECT().WaitForDeployment(tt.ctx, mCluster, "30m", "Available", gomock.Any(), gomock.Any()).MaxTimes(10) tt.mocks.client.EXPECT().ValidateControlPlaneNodes(tt.ctx, mCluster, mCluster.Name).Return(nil) - tt.mocks.client.EXPECT().ValidateWorkerNodes(tt.ctx, mCluster.Name, mCluster.KubeconfigFile).Return(nil) + tt.mocks.client.EXPECT().CountMachineDeploymentReplicasReady(tt.ctx, mCluster.Name, mCluster.KubeconfigFile).Return(0, 0, nil) tt.mocks.provider.EXPECT().GetDeployments() tt.mocks.writer.EXPECT().Write(mgmtClusterName+"-eks-a-cluster.yaml", gomock.Any(), gomock.Not(gomock.Nil())) tt.mocks.client.EXPECT().GetEksaOIDCConfig(tt.ctx, tt.clusterSpec.Cluster.Spec.IdentityProviderRefs[0].Name, mCluster.KubeconfigFile, tt.clusterSpec.Cluster.Namespace).Return(nil, nil) @@ -551,7 +551,85 @@ func TestClusterManagerUpgradeCloudStackWorkloadClusterSuccess(t *testing.T) { tt.mocks.provider.EXPECT().MachineDeploymentsToDelete(mCluster, tt.clusterSpec, tt.clusterSpec.DeepCopy()).Return([]string{}) tt.mocks.client.EXPECT().WaitForDeployment(tt.ctx, mCluster, "30m", "Available", gomock.Any(), gomock.Any()).MaxTimes(10) tt.mocks.client.EXPECT().ValidateControlPlaneNodes(tt.ctx, mCluster, mCluster.Name).Return(nil) - tt.mocks.client.EXPECT().ValidateWorkerNodes(tt.ctx, mCluster.Name, mCluster.KubeconfigFile).Return(nil) + tt.mocks.client.EXPECT().CountMachineDeploymentReplicasReady(tt.ctx, mCluster.Name, mCluster.KubeconfigFile).Return(0, 0, nil) + tt.mocks.provider.EXPECT().GetDeployments() + tt.mocks.writer.EXPECT().Write(mgmtClusterName+"-eks-a-cluster.yaml", gomock.Any(), gomock.Not(gomock.Nil())) + tt.mocks.client.EXPECT().GetEksaOIDCConfig(tt.ctx, tt.clusterSpec.Cluster.Spec.IdentityProviderRefs[0].Name, mCluster.KubeconfigFile, tt.clusterSpec.Cluster.Namespace).Return(nil, nil) + tt.mocks.networking.EXPECT().RunPostControlPlaneUpgradeSetup(tt.ctx, wCluster).Return(nil) + + if err := tt.clusterManager.UpgradeCluster(tt.ctx, mCluster, wCluster, tt.clusterSpec, tt.mocks.provider); err != nil { + t.Errorf("ClusterManager.UpgradeCluster() error = %v, wantErr nil", err) + } +} + +func TestClusterManagerUpgradeWorkloadClusterWaitForMDReadyErrorOnce(t *testing.T) { + mgmtClusterName := "cluster-name" + workClusterName := "cluster-name-w" + + mCluster := &types.Cluster{ + Name: mgmtClusterName, + ExistingManagement: true, + } + wCluster := &types.Cluster{ + Name: workClusterName, + } + + tt := newSpecChangedTest(t) + tt.mocks.client.EXPECT().GetEksaCluster(tt.ctx, mCluster, mgmtClusterName).Return(tt.oldClusterConfig, nil) + tt.mocks.client.EXPECT().GetBundles(tt.ctx, mCluster.KubeconfigFile, mCluster.Name, "").Return(test.Bundles(t), nil) + tt.mocks.client.EXPECT().GetEksdRelease(tt.ctx, gomock.Any(), constants.EksaSystemNamespace, gomock.Any()) + tt.mocks.provider.EXPECT().GenerateCAPISpecForUpgrade(tt.ctx, mCluster, mCluster, tt.clusterSpec, tt.clusterSpec.DeepCopy()) + tt.mocks.client.EXPECT().ApplyKubeSpecFromBytesWithNamespace(tt.ctx, mCluster, test.OfType("[]uint8"), constants.EksaSystemNamespace).Times(2) + tt.mocks.provider.EXPECT().RunPostControlPlaneUpgrade(tt.ctx, tt.clusterSpec, tt.clusterSpec, wCluster, mCluster) + tt.mocks.client.EXPECT().WaitForControlPlaneReady(tt.ctx, mCluster, "60m", mgmtClusterName).MaxTimes(2) + tt.mocks.client.EXPECT().WaitForControlPlaneNotReady(tt.ctx, mCluster, "1m", mgmtClusterName) + tt.mocks.client.EXPECT().GetMachines(tt.ctx, mCluster, mCluster.Name).Return([]types.Machine{}, nil).Times(2) + tt.mocks.provider.EXPECT().MachineDeploymentsToDelete(mCluster, tt.clusterSpec, tt.clusterSpec.DeepCopy()).Return([]string{}) + tt.mocks.client.EXPECT().WaitForDeployment(tt.ctx, mCluster, "30m", "Available", gomock.Any(), gomock.Any()).MaxTimes(10) + tt.mocks.client.EXPECT().ValidateControlPlaneNodes(tt.ctx, mCluster, mCluster.Name).Return(nil) + // Fail once + tt.mocks.client.EXPECT().CountMachineDeploymentReplicasReady(tt.ctx, mCluster.Name, mCluster.KubeconfigFile).Times(1).Return(0, 0, errors.New("error counting MD replicas")) + // Return 1 and 1 for ready and total replicas + tt.mocks.client.EXPECT().CountMachineDeploymentReplicasReady(tt.ctx, mCluster.Name, mCluster.KubeconfigFile).Times(1).Return(1, 1, nil) + tt.mocks.provider.EXPECT().GetDeployments() + tt.mocks.writer.EXPECT().Write(mgmtClusterName+"-eks-a-cluster.yaml", gomock.Any(), gomock.Not(gomock.Nil())) + tt.mocks.client.EXPECT().GetEksaOIDCConfig(tt.ctx, tt.clusterSpec.Cluster.Spec.IdentityProviderRefs[0].Name, mCluster.KubeconfigFile, tt.clusterSpec.Cluster.Namespace).Return(nil, nil) + tt.mocks.networking.EXPECT().RunPostControlPlaneUpgradeSetup(tt.ctx, wCluster).Return(nil) + + if err := tt.clusterManager.UpgradeCluster(tt.ctx, mCluster, wCluster, tt.clusterSpec, tt.mocks.provider); err != nil { + t.Errorf("ClusterManager.UpgradeCluster() error = %v, wantErr nil", err) + } +} + +func TestClusterManagerUpgradeWorkloadClusterWaitForMDReadyUnreadyOnce(t *testing.T) { + mgmtClusterName := "cluster-name" + workClusterName := "cluster-name-w" + + mCluster := &types.Cluster{ + Name: mgmtClusterName, + ExistingManagement: true, + } + wCluster := &types.Cluster{ + Name: workClusterName, + } + + tt := newSpecChangedTest(t) + tt.mocks.client.EXPECT().GetEksaCluster(tt.ctx, mCluster, mgmtClusterName).Return(tt.oldClusterConfig, nil) + tt.mocks.client.EXPECT().GetBundles(tt.ctx, mCluster.KubeconfigFile, mCluster.Name, "").Return(test.Bundles(t), nil) + tt.mocks.client.EXPECT().GetEksdRelease(tt.ctx, gomock.Any(), constants.EksaSystemNamespace, gomock.Any()) + tt.mocks.provider.EXPECT().GenerateCAPISpecForUpgrade(tt.ctx, mCluster, mCluster, tt.clusterSpec, tt.clusterSpec.DeepCopy()) + tt.mocks.client.EXPECT().ApplyKubeSpecFromBytesWithNamespace(tt.ctx, mCluster, test.OfType("[]uint8"), constants.EksaSystemNamespace).Times(2) + tt.mocks.provider.EXPECT().RunPostControlPlaneUpgrade(tt.ctx, tt.clusterSpec, tt.clusterSpec, wCluster, mCluster) + tt.mocks.client.EXPECT().WaitForControlPlaneReady(tt.ctx, mCluster, "60m", mgmtClusterName).MaxTimes(2) + tt.mocks.client.EXPECT().WaitForControlPlaneNotReady(tt.ctx, mCluster, "1m", mgmtClusterName) + tt.mocks.client.EXPECT().GetMachines(tt.ctx, mCluster, mCluster.Name).Return([]types.Machine{}, nil).Times(2) + tt.mocks.provider.EXPECT().MachineDeploymentsToDelete(mCluster, tt.clusterSpec, tt.clusterSpec.DeepCopy()).Return([]string{}) + tt.mocks.client.EXPECT().WaitForDeployment(tt.ctx, mCluster, "30m", "Available", gomock.Any(), gomock.Any()).MaxTimes(10) + tt.mocks.client.EXPECT().ValidateControlPlaneNodes(tt.ctx, mCluster, mCluster.Name).Return(nil) + // Return 0 and 1 for ready and total replicas once + tt.mocks.client.EXPECT().CountMachineDeploymentReplicasReady(tt.ctx, mCluster.Name, mCluster.KubeconfigFile).Times(1).Return(0, 1, nil) + // Return 1 and 1 for ready and total replicas + tt.mocks.client.EXPECT().CountMachineDeploymentReplicasReady(tt.ctx, mCluster.Name, mCluster.KubeconfigFile).Times(1).Return(1, 1, nil) tt.mocks.provider.EXPECT().GetDeployments() tt.mocks.writer.EXPECT().Write(mgmtClusterName+"-eks-a-cluster.yaml", gomock.Any(), gomock.Not(gomock.Nil())) tt.mocks.client.EXPECT().GetEksaOIDCConfig(tt.ctx, tt.clusterSpec.Cluster.Spec.IdentityProviderRefs[0].Name, mCluster.KubeconfigFile, tt.clusterSpec.Cluster.Namespace).Return(nil, nil) @@ -660,7 +738,7 @@ func TestClusterManagerUpgradeWorkloadClusterWaitForCAPITimeout(t *testing.T) { tt.mocks.provider.EXPECT().MachineDeploymentsToDelete(wCluster, tt.clusterSpec, tt.clusterSpec.DeepCopy()).Return([]string{}) tt.mocks.client.EXPECT().WaitForDeployment(tt.ctx, wCluster, "30m", "Available", gomock.Any(), gomock.Any()).Return(errors.New("time out")) tt.mocks.client.EXPECT().ValidateControlPlaneNodes(tt.ctx, mCluster, wCluster.Name).Return(nil) - tt.mocks.client.EXPECT().ValidateWorkerNodes(tt.ctx, wCluster.Name, mCluster.KubeconfigFile).Return(nil) + tt.mocks.client.EXPECT().CountMachineDeploymentReplicasReady(tt.ctx, wCluster.Name, mCluster.KubeconfigFile).Return(0, 0, nil) tt.mocks.writer.EXPECT().Write(clusterName+"-eks-a-cluster.yaml", gomock.Any(), gomock.Not(gomock.Nil())) tt.mocks.client.EXPECT().GetEksaOIDCConfig(tt.ctx, tt.clusterSpec.Cluster.Spec.IdentityProviderRefs[0].Name, tt.cluster.KubeconfigFile, tt.clusterSpec.Cluster.Namespace).Return(nil, nil) tt.mocks.networking.EXPECT().RunPostControlPlaneUpgradeSetup(tt.ctx, wCluster).Return(nil) @@ -692,7 +770,7 @@ func TestClusterManagerMoveCAPISuccess(t *testing.T) { m.client.EXPECT().GetClusters(ctx, to).Return(clusters, nil) m.client.EXPECT().WaitForControlPlaneReady(ctx, to, "15m0s", capiClusterName) m.client.EXPECT().ValidateControlPlaneNodes(ctx, to, to.Name) - m.client.EXPECT().ValidateWorkerNodes(ctx, to.Name, to.KubeconfigFile) + m.client.EXPECT().CountMachineDeploymentReplicasReady(ctx, to.Name, to.KubeconfigFile) m.client.EXPECT().GetMachines(ctx, to, to.Name) if err := c.MoveCAPI(ctx, from, to, to.Name, clusterSpec); err != nil { @@ -793,7 +871,7 @@ func TestClusterManagerMoveCAPIErrorGetMachines(t *testing.T) { m.client.EXPECT().MoveManagement(ctx, from, to) m.client.EXPECT().GetClusters(ctx, to) m.client.EXPECT().ValidateControlPlaneNodes(ctx, to, to.Name) - m.client.EXPECT().ValidateWorkerNodes(ctx, to.Name, to.KubeconfigFile) + m.client.EXPECT().CountMachineDeploymentReplicasReady(ctx, to.Name, to.KubeconfigFile) m.client.EXPECT().GetMachines(ctx, to, from.Name).Return(nil, errors.New("error getting machines")).AnyTimes() if err := c.MoveCAPI(ctx, from, to, from.Name, clusterSpec); err == nil { diff --git a/pkg/clustermanager/mocks/client_and_networking.go b/pkg/clustermanager/mocks/client_and_networking.go index ea442833d4c4..297958e420fe 100644 --- a/pkg/clustermanager/mocks/client_and_networking.go +++ b/pkg/clustermanager/mocks/client_and_networking.go @@ -85,6 +85,22 @@ func (mr *MockClusterClientMockRecorder) ApplyKubeSpecFromBytesWithNamespace(arg return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ApplyKubeSpecFromBytesWithNamespace", reflect.TypeOf((*MockClusterClient)(nil).ApplyKubeSpecFromBytesWithNamespace), arg0, arg1, arg2, arg3) } +// CountMachineDeploymentReplicasReady mocks base method. +func (m *MockClusterClient) CountMachineDeploymentReplicasReady(arg0 context.Context, arg1, arg2 string) (int, int, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CountMachineDeploymentReplicasReady", arg0, arg1, arg2) + ret0, _ := ret[0].(int) + ret1, _ := ret[1].(int) + ret2, _ := ret[2].(error) + return ret0, ret1, ret2 +} + +// CountMachineDeploymentReplicasReady indicates an expected call of CountMachineDeploymentReplicasReady. +func (mr *MockClusterClientMockRecorder) CountMachineDeploymentReplicasReady(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CountMachineDeploymentReplicasReady", reflect.TypeOf((*MockClusterClient)(nil).CountMachineDeploymentReplicasReady), arg0, arg1, arg2) +} + // CreateNamespace mocks base method. func (m *MockClusterClient) CreateNamespace(arg0 context.Context, arg1, arg2 string) error { m.ctrl.T.Helper() diff --git a/pkg/executables/kubectl.go b/pkg/executables/kubectl.go index d23bdca1c0ee..a0d69486fdd4 100644 --- a/pkg/executables/kubectl.go +++ b/pkg/executables/kubectl.go @@ -480,24 +480,35 @@ func (k *Kubectl) ValidateControlPlaneNodes(ctx context.Context, cluster *types. func (k *Kubectl) ValidateWorkerNodes(ctx context.Context, clusterName string, kubeconfig string) error { logger.V(6).Info("waiting for nodes", "cluster", clusterName) - deployments, err := k.GetMachineDeployments(ctx, WithKubeconfig(kubeconfig), WithNamespace(constants.EksaSystemNamespace)) + ready, total, err := k.CountMachineDeploymentReplicasReady(ctx, clusterName, kubeconfig) if err != nil { return err } + if ready != total { + return fmt.Errorf("%d machine deployment replicas are not ready", total-ready) + } + return nil +} + +func (k *Kubectl) CountMachineDeploymentReplicasReady(ctx context.Context, clusterName string, kubeconfig string) (ready, total int, err error) { + logger.V(6).Info("counting ready machine deployment replicas", "cluster", clusterName) + deployments, err := k.GetMachineDeployments(ctx, WithKubeconfig(kubeconfig), WithNamespace(constants.EksaSystemNamespace)) + if err != nil { + return 0, 0, err + } for _, machineDeployment := range deployments { if machineDeployment.Status.Phase != "Running" { - return fmt.Errorf("machine deployment is in %s phase", machineDeployment.Status.Phase) + return 0, 0, fmt.Errorf("machine deployment is in %s phase", machineDeployment.Status.Phase) } if machineDeployment.Status.UnavailableReplicas != 0 { - return fmt.Errorf("%v machine deployment replicas are unavailable", machineDeployment.Status.UnavailableReplicas) + return 0, 0, fmt.Errorf("%d machine deployment replicas are unavailable", machineDeployment.Status.UnavailableReplicas) } - if machineDeployment.Status.ReadyReplicas != machineDeployment.Status.Replicas { - return fmt.Errorf("%v machine deployment replicas are not ready", machineDeployment.Status.Replicas-machineDeployment.Status.ReadyReplicas) - } + ready += int(machineDeployment.Status.ReadyReplicas) + total += int(machineDeployment.Status.Replicas) } - return nil + return ready, total, nil } func (k *Kubectl) VsphereWorkerNodesMachineTemplate(ctx context.Context, clusterName string, kubeconfig string, namespace string) (*vspherev1.VSphereMachineTemplate, error) { diff --git a/pkg/executables/kubectl_test.go b/pkg/executables/kubectl_test.go index d4605d31511b..3df9abe6ed45 100644 --- a/pkg/executables/kubectl_test.go +++ b/pkg/executables/kubectl_test.go @@ -1358,6 +1358,132 @@ func TestKubectlGetMachineDeployments(t *testing.T) { } } +func TestKubectlCountMachineDeploymentReplicasReady(t *testing.T) { + tests := []struct { + testName string + jsonResponseFile string + wantError bool + wantTotal int + wantReady int + returnError bool + }{ + { + testName: "no machine deployments", + jsonResponseFile: "testdata/kubectl_no_machine_deployments.json", + wantError: false, + wantReady: 0, + wantTotal: 0, + returnError: false, + }, + { + testName: "multiple machine deployments", + jsonResponseFile: "testdata/kubectl_machine_deployments.json", + wantError: false, + wantReady: 2, + wantTotal: 2, + returnError: false, + }, + { + testName: "multiple machine deployments with unready replicas", + jsonResponseFile: "testdata/kubectl_machine_deployments_unready.json", + wantError: false, + wantReady: 2, + wantTotal: 3, + returnError: false, + }, + { + testName: "non-running machine deployments", + jsonResponseFile: "testdata/kubectl_machine_deployments_provisioned.json", + wantError: true, + wantReady: 0, + wantTotal: 0, + returnError: false, + }, + { + testName: "unavailable replicas", + jsonResponseFile: "testdata/kubectl_machine_deployments_unavailable.json", + wantError: true, + wantReady: 0, + wantTotal: 0, + }, + { + testName: "error response", + jsonResponseFile: "", + wantError: true, + wantReady: 0, + wantTotal: 0, + returnError: true, + }, + } + + for _, tc := range tests { + t.Run(tc.testName, func(t *testing.T) { + k, ctx, cluster, e := newKubectl(t) + tt := newKubectlTest(t) + if tc.returnError { + e.EXPECT().Execute(ctx, []string{"get", "machinedeployments.cluster.x-k8s.io", "-o", "json", "--kubeconfig", cluster.KubeconfigFile, "--namespace", "eksa-system"}).Return(*bytes.NewBufferString(""), errors.New("")) + } else { + fileContent := test.ReadFile(t, tc.jsonResponseFile) + e.EXPECT().Execute(ctx, []string{"get", "machinedeployments.cluster.x-k8s.io", "-o", "json", "--kubeconfig", cluster.KubeconfigFile, "--namespace", "eksa-system"}).Return(*bytes.NewBufferString(fileContent), nil) + } + + ready, total, err := k.CountMachineDeploymentReplicasReady(ctx, cluster.Name, cluster.KubeconfigFile) + if tc.wantError { + tt.Expect(err).NotTo(BeNil()) + } else { + tt.Expect(err).To(BeNil()) + } + tt.Expect(ready).To(Equal(tc.wantReady)) + tt.Expect(total).To(Equal(tc.wantTotal)) + }) + } +} + +func TestKubectlValidateWorkerNodes(t *testing.T) { + tests := []struct { + testName string + jsonResponseFile string + wantError bool + }{ + { + testName: "no machine deployments", + jsonResponseFile: "testdata/kubectl_no_machine_deployments.json", + wantError: false, + }, + { + testName: "multiple machine deployments", + jsonResponseFile: "testdata/kubectl_machine_deployments.json", + wantError: false, + }, + { + testName: "multiple machine deployments with unready replicas", + jsonResponseFile: "testdata/kubectl_machine_deployments_unready.json", + wantError: true, + }, + { + testName: "non-running machine deployments", + jsonResponseFile: "testdata/kubectl_machine_deployments_provisioned.json", + wantError: true, + }, + } + + for _, tc := range tests { + t.Run(tc.testName, func(t *testing.T) { + k, ctx, cluster, e := newKubectl(t) + tt := newKubectlTest(t) + fileContent := test.ReadFile(t, tc.jsonResponseFile) + e.EXPECT().Execute(ctx, []string{"get", "machinedeployments.cluster.x-k8s.io", "-o", "json", "--kubeconfig", cluster.KubeconfigFile, "--namespace", "eksa-system"}).Return(*bytes.NewBufferString(fileContent), nil) + + err := k.ValidateWorkerNodes(ctx, cluster.Name, cluster.KubeconfigFile) + if tc.wantError { + tt.Expect(err).NotTo(BeNil()) + } else { + tt.Expect(err).To(BeNil()) + } + }) + } +} + func TestKubectlGetKubeAdmControlPlanes(t *testing.T) { tests := []struct { testName string diff --git a/pkg/executables/testdata/kubectl_machine_deployments_provisioned.json b/pkg/executables/testdata/kubectl_machine_deployments_provisioned.json new file mode 100644 index 000000000000..10fcd88c2e2b --- /dev/null +++ b/pkg/executables/testdata/kubectl_machine_deployments_provisioned.json @@ -0,0 +1,376 @@ +{ + "apiVersion": "v1", + "items": [ + { + "apiVersion": "cluster.x-k8s.io/v1alpha3", + "kind": "MachineDeployment", + "metadata": { + "annotations": { + "kubectl.kubernetes.io/last-applied-configuration": "{\"apiVersion\":\"cluster.x-k8s.io/v1alpha3\",\"kind\":\"MachineDeployment\",\"metadata\":{\"annotations\":{},\"name\":\"test0-md-0\",\"namespace\":\"default\"},\"spec\":{\"clusterName\":\"test0\",\"replicas\":1,\"selector\":{\"matchLabels\":null},\"template\":{\"spec\":{\"bootstrap\":{\"configRef\":{\"apiVersion\":\"bootstrap.cluster.x-k8s.io/v1alpha3\",\"kind\":\"KubeadmConfigTemplate\",\"name\":\"test0-md-0\",\"namespace\":\"default\"}},\"clusterName\":\"test0\",\"infrastructureRef\":{\"apiVersion\":\"infrastructure.cluster.x-k8s.io/v1alpha3\",\"kind\":\"DockerMachineTemplate\",\"name\":\"test0-md-0\",\"namespace\":\"default\"},\"version\":\"v1.19.8-eks-1-19-4\"}}}}\n", + "machinedeployment.clusters.x-k8s.io/revision": "1" + }, + "creationTimestamp": "2021-07-01T14:50:15Z", + "generation": 1, + "labels": { + "cluster.x-k8s.io/cluster-name": "test0" + }, + "managedFields": [ + { + "apiVersion": "cluster.x-k8s.io/v1alpha3", + "fieldsType": "FieldsV1", + "fieldsV1": { + "f:metadata": { + "f:annotations": { + ".": {}, + "f:kubectl.kubernetes.io/last-applied-configuration": {}, + "f:machinedeployment.clusters.x-k8s.io/revision": {} + }, + "f:labels": { + ".": {}, + "f:cluster.x-k8s.io/cluster-name": {} + }, + "f:ownerReferences": {} + }, + "f:spec": { + ".": {}, + "f:clusterName": {}, + "f:minReadySeconds": {}, + "f:progressDeadlineSeconds": {}, + "f:replicas": {}, + "f:revisionHistoryLimit": {}, + "f:selector": { + ".": {}, + "f:matchLabels": { + ".": {}, + "f:cluster.x-k8s.io/cluster-name": {}, + "f:cluster.x-k8s.io/deployment-name": {} + } + }, + "f:strategy": { + ".": {}, + "f:rollingUpdate": { + ".": {}, + "f:maxSurge": {}, + "f:maxUnavailable": {} + }, + "f:type": {} + }, + "f:template": { + ".": {}, + "f:metadata": { + ".": {}, + "f:labels": { + ".": {}, + "f:cluster.x-k8s.io/cluster-name": {}, + "f:cluster.x-k8s.io/deployment-name": {} + } + }, + "f:spec": { + ".": {}, + "f:bootstrap": { + ".": {}, + "f:configRef": { + ".": {}, + "f:apiVersion": {}, + "f:kind": {}, + "f:name": {}, + "f:namespace": {} + } + }, + "f:clusterName": {}, + "f:infrastructureRef": { + ".": {}, + "f:apiVersion": {}, + "f:kind": {}, + "f:name": {}, + "f:namespace": {} + }, + "f:version": {} + } + } + } + }, + "manager": "clusterctl", + "operation": "Update", + "time": "2021-07-01T14:50:15Z" + }, + { + "apiVersion": "cluster.x-k8s.io/v1alpha3", + "fieldsType": "FieldsV1", + "fieldsV1": { + "f:status": { + ".": {}, + "f:availableReplicas": {}, + "f:observedGeneration": {}, + "f:phase": {}, + "f:readyReplicas": {}, + "f:replicas": {}, + "f:selector": {}, + "f:updatedReplicas": {} + } + }, + "manager": "manager", + "operation": "Update", + "time": "2021-07-01T14:50:17Z" + } + ], + "name": "test0-md-0", + "namespace": "default", + "ownerReferences": [ + { + "apiVersion": "cluster.x-k8s.io/v1alpha3", + "kind": "Cluster", + "name": "test0", + "uid": "9607241e-c3a5-40c7-8f51-268231e615c1" + } + ], + "resourceVersion": "3226", + "selfLink": "/apis/cluster.x-k8s.io/v1alpha3/namespaces/default/machinedeployments/test0-md-0", + "uid": "324c8511-f947-45f8-b586-c015e5711d69" + }, + "spec": { + "clusterName": "test0", + "minReadySeconds": 0, + "progressDeadlineSeconds": 600, + "replicas": 1, + "revisionHistoryLimit": 1, + "selector": { + "matchLabels": { + "cluster.x-k8s.io/cluster-name": "test0", + "cluster.x-k8s.io/deployment-name": "test0-md-0" + } + }, + "strategy": { + "rollingUpdate": { + "maxSurge": 1, + "maxUnavailable": 0 + }, + "type": "RollingUpdate" + }, + "template": { + "metadata": { + "labels": { + "cluster.x-k8s.io/cluster-name": "test0", + "cluster.x-k8s.io/deployment-name": "test0-md-0" + } + }, + "spec": { + "bootstrap": { + "configRef": { + "apiVersion": "bootstrap.cluster.x-k8s.io/v1alpha3", + "kind": "KubeadmConfigTemplate", + "name": "test0-md-0", + "namespace": "default" + } + }, + "clusterName": "test0", + "infrastructureRef": { + "apiVersion": "infrastructure.cluster.x-k8s.io/v1alpha3", + "kind": "DockerMachineTemplate", + "name": "test0-md-0", + "namespace": "default" + }, + "version": "v1.19.8-eks-1-19-4" + } + } + }, + "status": { + "availableReplicas": 1, + "observedGeneration": 1, + "phase": "Running", + "readyReplicas": 1, + "replicas": 2, + "selector": "cluster.x-k8s.io/cluster-name=test0,cluster.x-k8s.io/deployment-name=test0-md-0", + "updatedReplicas": 1 + } + }, + { + "apiVersion": "cluster.x-k8s.io/v1alpha3", + "kind": "MachineDeployment", + "metadata": { + "annotations": { + "kubectl.kubernetes.io/last-applied-configuration": "{\"apiVersion\":\"cluster.x-k8s.io/v1alpha3\",\"kind\":\"MachineDeployment\",\"metadata\":{\"annotations\":{},\"name\":\"test1-md-0\",\"namespace\":\"default\"},\"spec\":{\"clusterName\":\"test1\",\"replicas\":1,\"selector\":{\"matchLabels\":null},\"template\":{\"spec\":{\"bootstrap\":{\"configRef\":{\"apiVersion\":\"bootstrap.cluster.x-k8s.io/v1alpha3\",\"kind\":\"KubeadmConfigTemplate\",\"name\":\"test1-md-0\",\"namespace\":\"default\"}},\"clusterName\":\"test1\",\"infrastructureRef\":{\"apiVersion\":\"infrastructure.cluster.x-k8s.io/v1alpha3\",\"kind\":\"DockerMachineTemplate\",\"name\":\"test1-md-0\",\"namespace\":\"default\"},\"version\":\"v1.19.8-eks-1-19-4\"}}}}\n", + "machinedeployment.clusters.x-k8s.io/revision": "1" + }, + "creationTimestamp": "2021-07-01T14:50:15Z", + "generation": 1, + "labels": { + "cluster.x-k8s.io/cluster-name": "test1" + }, + "managedFields": [ + { + "apiVersion": "cluster.x-k8s.io/v1alpha3", + "fieldsType": "FieldsV1", + "fieldsV1": { + "f:metadata": { + "f:annotations": { + ".": {}, + "f:kubectl.kubernetes.io/last-applied-configuration": {}, + "f:machinedeployment.clusters.x-k8s.io/revision": {} + }, + "f:labels": { + ".": {}, + "f:cluster.x-k8s.io/cluster-name": {} + }, + "f:ownerReferences": {} + }, + "f:spec": { + ".": {}, + "f:clusterName": {}, + "f:minReadySeconds": {}, + "f:progressDeadlineSeconds": {}, + "f:replicas": {}, + "f:revisionHistoryLimit": {}, + "f:selector": { + ".": {}, + "f:matchLabels": { + ".": {}, + "f:cluster.x-k8s.io/cluster-name": {}, + "f:cluster.x-k8s.io/deployment-name": {} + } + }, + "f:strategy": { + ".": {}, + "f:rollingUpdate": { + ".": {}, + "f:maxSurge": {}, + "f:maxUnavailable": {} + }, + "f:type": {} + }, + "f:template": { + ".": {}, + "f:metadata": { + ".": {}, + "f:labels": { + ".": {}, + "f:cluster.x-k8s.io/cluster-name": {}, + "f:cluster.x-k8s.io/deployment-name": {} + } + }, + "f:spec": { + ".": {}, + "f:bootstrap": { + ".": {}, + "f:configRef": { + ".": {}, + "f:apiVersion": {}, + "f:kind": {}, + "f:name": {}, + "f:namespace": {} + } + }, + "f:clusterName": {}, + "f:infrastructureRef": { + ".": {}, + "f:apiVersion": {}, + "f:kind": {}, + "f:name": {}, + "f:namespace": {} + }, + "f:version": {} + } + } + } + }, + "manager": "clusterctl", + "operation": "Update", + "time": "2021-07-01T14:50:15Z" + }, + { + "apiVersion": "cluster.x-k8s.io/v1alpha3", + "fieldsType": "FieldsV1", + "fieldsV1": { + "f:status": { + ".": {}, + "f:availableReplicas": {}, + "f:observedGeneration": {}, + "f:phase": {}, + "f:readyReplicas": {}, + "f:replicas": {}, + "f:selector": {}, + "f:updatedReplicas": {} + } + }, + "manager": "manager", + "operation": "Update", + "time": "2021-07-01T14:50:17Z" + } + ], + "name": "test1-md-0", + "namespace": "default", + "ownerReferences": [ + { + "apiVersion": "cluster.x-k8s.io/v1alpha3", + "kind": "Cluster", + "name": "test1", + "uid": "9607241e-c3a5-40c7-8f51-268231e615c1" + } + ], + "resourceVersion": "3226", + "selfLink": "/apis/cluster.x-k8s.io/v1alpha3/namespaces/default/machinedeployments/test1-md-0", + "uid": "324c8511-f947-45f8-b586-c015e5711d69" + }, + "spec": { + "clusterName": "test1", + "minReadySeconds": 0, + "progressDeadlineSeconds": 600, + "replicas": 1, + "revisionHistoryLimit": 1, + "selector": { + "matchLabels": { + "cluster.x-k8s.io/cluster-name": "test1", + "cluster.x-k8s.io/deployment-name": "test1-md-0" + } + }, + "strategy": { + "rollingUpdate": { + "maxSurge": 1, + "maxUnavailable": 0 + }, + "type": "RollingUpdate" + }, + "template": { + "metadata": { + "labels": { + "cluster.x-k8s.io/cluster-name": "test1", + "cluster.x-k8s.io/deployment-name": "test1-md-0" + } + }, + "spec": { + "bootstrap": { + "configRef": { + "apiVersion": "bootstrap.cluster.x-k8s.io/v1alpha3", + "kind": "KubeadmConfigTemplate", + "name": "test1-md-0", + "namespace": "default" + } + }, + "clusterName": "test1", + "infrastructureRef": { + "apiVersion": "infrastructure.cluster.x-k8s.io/v1alpha3", + "kind": "DockerMachineTemplate", + "name": "test1-md-0", + "namespace": "default" + }, + "version": "v1.19.8-eks-1-19-4" + } + } + }, + "status": { + "availableReplicas": 1, + "observedGeneration": 1, + "phase": "Provisioned", + "readyReplicas": 1, + "replicas": 1, + "selector": "cluster.x-k8s.io/cluster-name=test1,cluster.x-k8s.io/deployment-name=test1-md-0", + "updatedReplicas": 1 + } + } + ], + "kind": "List", + "metadata": { + "resourceVersion": "", + "selfLink": "" + } +} diff --git a/pkg/executables/testdata/kubectl_machine_deployments_unavailable.json b/pkg/executables/testdata/kubectl_machine_deployments_unavailable.json new file mode 100644 index 000000000000..2c247895ab40 --- /dev/null +++ b/pkg/executables/testdata/kubectl_machine_deployments_unavailable.json @@ -0,0 +1,377 @@ +{ + "apiVersion": "v1", + "items": [ + { + "apiVersion": "cluster.x-k8s.io/v1alpha3", + "kind": "MachineDeployment", + "metadata": { + "annotations": { + "kubectl.kubernetes.io/last-applied-configuration": "{\"apiVersion\":\"cluster.x-k8s.io/v1alpha3\",\"kind\":\"MachineDeployment\",\"metadata\":{\"annotations\":{},\"name\":\"test0-md-0\",\"namespace\":\"default\"},\"spec\":{\"clusterName\":\"test0\",\"replicas\":1,\"selector\":{\"matchLabels\":null},\"template\":{\"spec\":{\"bootstrap\":{\"configRef\":{\"apiVersion\":\"bootstrap.cluster.x-k8s.io/v1alpha3\",\"kind\":\"KubeadmConfigTemplate\",\"name\":\"test0-md-0\",\"namespace\":\"default\"}},\"clusterName\":\"test0\",\"infrastructureRef\":{\"apiVersion\":\"infrastructure.cluster.x-k8s.io/v1alpha3\",\"kind\":\"DockerMachineTemplate\",\"name\":\"test0-md-0\",\"namespace\":\"default\"},\"version\":\"v1.19.8-eks-1-19-4\"}}}}\n", + "machinedeployment.clusters.x-k8s.io/revision": "1" + }, + "creationTimestamp": "2021-07-01T14:50:15Z", + "generation": 1, + "labels": { + "cluster.x-k8s.io/cluster-name": "test0" + }, + "managedFields": [ + { + "apiVersion": "cluster.x-k8s.io/v1alpha3", + "fieldsType": "FieldsV1", + "fieldsV1": { + "f:metadata": { + "f:annotations": { + ".": {}, + "f:kubectl.kubernetes.io/last-applied-configuration": {}, + "f:machinedeployment.clusters.x-k8s.io/revision": {} + }, + "f:labels": { + ".": {}, + "f:cluster.x-k8s.io/cluster-name": {} + }, + "f:ownerReferences": {} + }, + "f:spec": { + ".": {}, + "f:clusterName": {}, + "f:minReadySeconds": {}, + "f:progressDeadlineSeconds": {}, + "f:replicas": {}, + "f:revisionHistoryLimit": {}, + "f:selector": { + ".": {}, + "f:matchLabels": { + ".": {}, + "f:cluster.x-k8s.io/cluster-name": {}, + "f:cluster.x-k8s.io/deployment-name": {} + } + }, + "f:strategy": { + ".": {}, + "f:rollingUpdate": { + ".": {}, + "f:maxSurge": {}, + "f:maxUnavailable": {} + }, + "f:type": {} + }, + "f:template": { + ".": {}, + "f:metadata": { + ".": {}, + "f:labels": { + ".": {}, + "f:cluster.x-k8s.io/cluster-name": {}, + "f:cluster.x-k8s.io/deployment-name": {} + } + }, + "f:spec": { + ".": {}, + "f:bootstrap": { + ".": {}, + "f:configRef": { + ".": {}, + "f:apiVersion": {}, + "f:kind": {}, + "f:name": {}, + "f:namespace": {} + } + }, + "f:clusterName": {}, + "f:infrastructureRef": { + ".": {}, + "f:apiVersion": {}, + "f:kind": {}, + "f:name": {}, + "f:namespace": {} + }, + "f:version": {} + } + } + } + }, + "manager": "clusterctl", + "operation": "Update", + "time": "2021-07-01T14:50:15Z" + }, + { + "apiVersion": "cluster.x-k8s.io/v1alpha3", + "fieldsType": "FieldsV1", + "fieldsV1": { + "f:status": { + ".": {}, + "f:availableReplicas": {}, + "f:observedGeneration": {}, + "f:phase": {}, + "f:readyReplicas": {}, + "f:replicas": {}, + "f:selector": {}, + "f:updatedReplicas": {} + } + }, + "manager": "manager", + "operation": "Update", + "time": "2021-07-01T14:50:17Z" + } + ], + "name": "test0-md-0", + "namespace": "default", + "ownerReferences": [ + { + "apiVersion": "cluster.x-k8s.io/v1alpha3", + "kind": "Cluster", + "name": "test0", + "uid": "9607241e-c3a5-40c7-8f51-268231e615c1" + } + ], + "resourceVersion": "3226", + "selfLink": "/apis/cluster.x-k8s.io/v1alpha3/namespaces/default/machinedeployments/test0-md-0", + "uid": "324c8511-f947-45f8-b586-c015e5711d69" + }, + "spec": { + "clusterName": "test0", + "minReadySeconds": 0, + "progressDeadlineSeconds": 600, + "replicas": 1, + "revisionHistoryLimit": 1, + "selector": { + "matchLabels": { + "cluster.x-k8s.io/cluster-name": "test0", + "cluster.x-k8s.io/deployment-name": "test0-md-0" + } + }, + "strategy": { + "rollingUpdate": { + "maxSurge": 1, + "maxUnavailable": 0 + }, + "type": "RollingUpdate" + }, + "template": { + "metadata": { + "labels": { + "cluster.x-k8s.io/cluster-name": "test0", + "cluster.x-k8s.io/deployment-name": "test0-md-0" + } + }, + "spec": { + "bootstrap": { + "configRef": { + "apiVersion": "bootstrap.cluster.x-k8s.io/v1alpha3", + "kind": "KubeadmConfigTemplate", + "name": "test0-md-0", + "namespace": "default" + } + }, + "clusterName": "test0", + "infrastructureRef": { + "apiVersion": "infrastructure.cluster.x-k8s.io/v1alpha3", + "kind": "DockerMachineTemplate", + "name": "test0-md-0", + "namespace": "default" + }, + "version": "v1.19.8-eks-1-19-4" + } + } + }, + "status": { + "availableReplicas": 1, + "observedGeneration": 1, + "phase": "Running", + "readyReplicas": 1, + "replicas": 2, + "selector": "cluster.x-k8s.io/cluster-name=test0,cluster.x-k8s.io/deployment-name=test0-md-0", + "updatedReplicas": 1 + } + }, + { + "apiVersion": "cluster.x-k8s.io/v1alpha3", + "kind": "MachineDeployment", + "metadata": { + "annotations": { + "kubectl.kubernetes.io/last-applied-configuration": "{\"apiVersion\":\"cluster.x-k8s.io/v1alpha3\",\"kind\":\"MachineDeployment\",\"metadata\":{\"annotations\":{},\"name\":\"test1-md-0\",\"namespace\":\"default\"},\"spec\":{\"clusterName\":\"test1\",\"replicas\":1,\"selector\":{\"matchLabels\":null},\"template\":{\"spec\":{\"bootstrap\":{\"configRef\":{\"apiVersion\":\"bootstrap.cluster.x-k8s.io/v1alpha3\",\"kind\":\"KubeadmConfigTemplate\",\"name\":\"test1-md-0\",\"namespace\":\"default\"}},\"clusterName\":\"test1\",\"infrastructureRef\":{\"apiVersion\":\"infrastructure.cluster.x-k8s.io/v1alpha3\",\"kind\":\"DockerMachineTemplate\",\"name\":\"test1-md-0\",\"namespace\":\"default\"},\"version\":\"v1.19.8-eks-1-19-4\"}}}}\n", + "machinedeployment.clusters.x-k8s.io/revision": "1" + }, + "creationTimestamp": "2021-07-01T14:50:15Z", + "generation": 1, + "labels": { + "cluster.x-k8s.io/cluster-name": "test1" + }, + "managedFields": [ + { + "apiVersion": "cluster.x-k8s.io/v1alpha3", + "fieldsType": "FieldsV1", + "fieldsV1": { + "f:metadata": { + "f:annotations": { + ".": {}, + "f:kubectl.kubernetes.io/last-applied-configuration": {}, + "f:machinedeployment.clusters.x-k8s.io/revision": {} + }, + "f:labels": { + ".": {}, + "f:cluster.x-k8s.io/cluster-name": {} + }, + "f:ownerReferences": {} + }, + "f:spec": { + ".": {}, + "f:clusterName": {}, + "f:minReadySeconds": {}, + "f:progressDeadlineSeconds": {}, + "f:replicas": {}, + "f:revisionHistoryLimit": {}, + "f:selector": { + ".": {}, + "f:matchLabels": { + ".": {}, + "f:cluster.x-k8s.io/cluster-name": {}, + "f:cluster.x-k8s.io/deployment-name": {} + } + }, + "f:strategy": { + ".": {}, + "f:rollingUpdate": { + ".": {}, + "f:maxSurge": {}, + "f:maxUnavailable": {} + }, + "f:type": {} + }, + "f:template": { + ".": {}, + "f:metadata": { + ".": {}, + "f:labels": { + ".": {}, + "f:cluster.x-k8s.io/cluster-name": {}, + "f:cluster.x-k8s.io/deployment-name": {} + } + }, + "f:spec": { + ".": {}, + "f:bootstrap": { + ".": {}, + "f:configRef": { + ".": {}, + "f:apiVersion": {}, + "f:kind": {}, + "f:name": {}, + "f:namespace": {} + } + }, + "f:clusterName": {}, + "f:infrastructureRef": { + ".": {}, + "f:apiVersion": {}, + "f:kind": {}, + "f:name": {}, + "f:namespace": {} + }, + "f:version": {} + } + } + } + }, + "manager": "clusterctl", + "operation": "Update", + "time": "2021-07-01T14:50:15Z" + }, + { + "apiVersion": "cluster.x-k8s.io/v1alpha3", + "fieldsType": "FieldsV1", + "fieldsV1": { + "f:status": { + ".": {}, + "f:availableReplicas": {}, + "f:observedGeneration": {}, + "f:phase": {}, + "f:readyReplicas": {}, + "f:replicas": {}, + "f:selector": {}, + "f:updatedReplicas": {} + } + }, + "manager": "manager", + "operation": "Update", + "time": "2021-07-01T14:50:17Z" + } + ], + "name": "test1-md-0", + "namespace": "default", + "ownerReferences": [ + { + "apiVersion": "cluster.x-k8s.io/v1alpha3", + "kind": "Cluster", + "name": "test1", + "uid": "9607241e-c3a5-40c7-8f51-268231e615c1" + } + ], + "resourceVersion": "3226", + "selfLink": "/apis/cluster.x-k8s.io/v1alpha3/namespaces/default/machinedeployments/test1-md-0", + "uid": "324c8511-f947-45f8-b586-c015e5711d69" + }, + "spec": { + "clusterName": "test1", + "minReadySeconds": 0, + "progressDeadlineSeconds": 600, + "replicas": 1, + "revisionHistoryLimit": 1, + "selector": { + "matchLabels": { + "cluster.x-k8s.io/cluster-name": "test1", + "cluster.x-k8s.io/deployment-name": "test1-md-0" + } + }, + "strategy": { + "rollingUpdate": { + "maxSurge": 1, + "maxUnavailable": 0 + }, + "type": "RollingUpdate" + }, + "template": { + "metadata": { + "labels": { + "cluster.x-k8s.io/cluster-name": "test1", + "cluster.x-k8s.io/deployment-name": "test1-md-0" + } + }, + "spec": { + "bootstrap": { + "configRef": { + "apiVersion": "bootstrap.cluster.x-k8s.io/v1alpha3", + "kind": "KubeadmConfigTemplate", + "name": "test1-md-0", + "namespace": "default" + } + }, + "clusterName": "test1", + "infrastructureRef": { + "apiVersion": "infrastructure.cluster.x-k8s.io/v1alpha3", + "kind": "DockerMachineTemplate", + "name": "test1-md-0", + "namespace": "default" + }, + "version": "v1.19.8-eks-1-19-4" + } + } + }, + "status": { + "availableReplicas": 1, + "observedGeneration": 1, + "phase": "Running", + "readyReplicas": 0, + "replicas": 1, + "selector": "cluster.x-k8s.io/cluster-name=test1,cluster.x-k8s.io/deployment-name=test1-md-0", + "updatedReplicas": 1, + "UnavailableReplicas": 1 + } + } + ], + "kind": "List", + "metadata": { + "resourceVersion": "", + "selfLink": "" + } +} diff --git a/pkg/executables/testdata/kubectl_machine_deployments_unready.json b/pkg/executables/testdata/kubectl_machine_deployments_unready.json new file mode 100644 index 000000000000..48a9a26d86db --- /dev/null +++ b/pkg/executables/testdata/kubectl_machine_deployments_unready.json @@ -0,0 +1,376 @@ +{ + "apiVersion": "v1", + "items": [ + { + "apiVersion": "cluster.x-k8s.io/v1alpha3", + "kind": "MachineDeployment", + "metadata": { + "annotations": { + "kubectl.kubernetes.io/last-applied-configuration": "{\"apiVersion\":\"cluster.x-k8s.io/v1alpha3\",\"kind\":\"MachineDeployment\",\"metadata\":{\"annotations\":{},\"name\":\"test0-md-0\",\"namespace\":\"default\"},\"spec\":{\"clusterName\":\"test0\",\"replicas\":1,\"selector\":{\"matchLabels\":null},\"template\":{\"spec\":{\"bootstrap\":{\"configRef\":{\"apiVersion\":\"bootstrap.cluster.x-k8s.io/v1alpha3\",\"kind\":\"KubeadmConfigTemplate\",\"name\":\"test0-md-0\",\"namespace\":\"default\"}},\"clusterName\":\"test0\",\"infrastructureRef\":{\"apiVersion\":\"infrastructure.cluster.x-k8s.io/v1alpha3\",\"kind\":\"DockerMachineTemplate\",\"name\":\"test0-md-0\",\"namespace\":\"default\"},\"version\":\"v1.19.8-eks-1-19-4\"}}}}\n", + "machinedeployment.clusters.x-k8s.io/revision": "1" + }, + "creationTimestamp": "2021-07-01T14:50:15Z", + "generation": 1, + "labels": { + "cluster.x-k8s.io/cluster-name": "test0" + }, + "managedFields": [ + { + "apiVersion": "cluster.x-k8s.io/v1alpha3", + "fieldsType": "FieldsV1", + "fieldsV1": { + "f:metadata": { + "f:annotations": { + ".": {}, + "f:kubectl.kubernetes.io/last-applied-configuration": {}, + "f:machinedeployment.clusters.x-k8s.io/revision": {} + }, + "f:labels": { + ".": {}, + "f:cluster.x-k8s.io/cluster-name": {} + }, + "f:ownerReferences": {} + }, + "f:spec": { + ".": {}, + "f:clusterName": {}, + "f:minReadySeconds": {}, + "f:progressDeadlineSeconds": {}, + "f:replicas": {}, + "f:revisionHistoryLimit": {}, + "f:selector": { + ".": {}, + "f:matchLabels": { + ".": {}, + "f:cluster.x-k8s.io/cluster-name": {}, + "f:cluster.x-k8s.io/deployment-name": {} + } + }, + "f:strategy": { + ".": {}, + "f:rollingUpdate": { + ".": {}, + "f:maxSurge": {}, + "f:maxUnavailable": {} + }, + "f:type": {} + }, + "f:template": { + ".": {}, + "f:metadata": { + ".": {}, + "f:labels": { + ".": {}, + "f:cluster.x-k8s.io/cluster-name": {}, + "f:cluster.x-k8s.io/deployment-name": {} + } + }, + "f:spec": { + ".": {}, + "f:bootstrap": { + ".": {}, + "f:configRef": { + ".": {}, + "f:apiVersion": {}, + "f:kind": {}, + "f:name": {}, + "f:namespace": {} + } + }, + "f:clusterName": {}, + "f:infrastructureRef": { + ".": {}, + "f:apiVersion": {}, + "f:kind": {}, + "f:name": {}, + "f:namespace": {} + }, + "f:version": {} + } + } + } + }, + "manager": "clusterctl", + "operation": "Update", + "time": "2021-07-01T14:50:15Z" + }, + { + "apiVersion": "cluster.x-k8s.io/v1alpha3", + "fieldsType": "FieldsV1", + "fieldsV1": { + "f:status": { + ".": {}, + "f:availableReplicas": {}, + "f:observedGeneration": {}, + "f:phase": {}, + "f:readyReplicas": {}, + "f:replicas": {}, + "f:selector": {}, + "f:updatedReplicas": {} + } + }, + "manager": "manager", + "operation": "Update", + "time": "2021-07-01T14:50:17Z" + } + ], + "name": "test0-md-0", + "namespace": "default", + "ownerReferences": [ + { + "apiVersion": "cluster.x-k8s.io/v1alpha3", + "kind": "Cluster", + "name": "test0", + "uid": "9607241e-c3a5-40c7-8f51-268231e615c1" + } + ], + "resourceVersion": "3226", + "selfLink": "/apis/cluster.x-k8s.io/v1alpha3/namespaces/default/machinedeployments/test0-md-0", + "uid": "324c8511-f947-45f8-b586-c015e5711d69" + }, + "spec": { + "clusterName": "test0", + "minReadySeconds": 0, + "progressDeadlineSeconds": 600, + "replicas": 1, + "revisionHistoryLimit": 1, + "selector": { + "matchLabels": { + "cluster.x-k8s.io/cluster-name": "test0", + "cluster.x-k8s.io/deployment-name": "test0-md-0" + } + }, + "strategy": { + "rollingUpdate": { + "maxSurge": 1, + "maxUnavailable": 0 + }, + "type": "RollingUpdate" + }, + "template": { + "metadata": { + "labels": { + "cluster.x-k8s.io/cluster-name": "test0", + "cluster.x-k8s.io/deployment-name": "test0-md-0" + } + }, + "spec": { + "bootstrap": { + "configRef": { + "apiVersion": "bootstrap.cluster.x-k8s.io/v1alpha3", + "kind": "KubeadmConfigTemplate", + "name": "test0-md-0", + "namespace": "default" + } + }, + "clusterName": "test0", + "infrastructureRef": { + "apiVersion": "infrastructure.cluster.x-k8s.io/v1alpha3", + "kind": "DockerMachineTemplate", + "name": "test0-md-0", + "namespace": "default" + }, + "version": "v1.19.8-eks-1-19-4" + } + } + }, + "status": { + "availableReplicas": 1, + "observedGeneration": 1, + "phase": "Running", + "readyReplicas": 1, + "replicas": 2, + "selector": "cluster.x-k8s.io/cluster-name=test0,cluster.x-k8s.io/deployment-name=test0-md-0", + "updatedReplicas": 1 + } + }, + { + "apiVersion": "cluster.x-k8s.io/v1alpha3", + "kind": "MachineDeployment", + "metadata": { + "annotations": { + "kubectl.kubernetes.io/last-applied-configuration": "{\"apiVersion\":\"cluster.x-k8s.io/v1alpha3\",\"kind\":\"MachineDeployment\",\"metadata\":{\"annotations\":{},\"name\":\"test1-md-0\",\"namespace\":\"default\"},\"spec\":{\"clusterName\":\"test1\",\"replicas\":1,\"selector\":{\"matchLabels\":null},\"template\":{\"spec\":{\"bootstrap\":{\"configRef\":{\"apiVersion\":\"bootstrap.cluster.x-k8s.io/v1alpha3\",\"kind\":\"KubeadmConfigTemplate\",\"name\":\"test1-md-0\",\"namespace\":\"default\"}},\"clusterName\":\"test1\",\"infrastructureRef\":{\"apiVersion\":\"infrastructure.cluster.x-k8s.io/v1alpha3\",\"kind\":\"DockerMachineTemplate\",\"name\":\"test1-md-0\",\"namespace\":\"default\"},\"version\":\"v1.19.8-eks-1-19-4\"}}}}\n", + "machinedeployment.clusters.x-k8s.io/revision": "1" + }, + "creationTimestamp": "2021-07-01T14:50:15Z", + "generation": 1, + "labels": { + "cluster.x-k8s.io/cluster-name": "test1" + }, + "managedFields": [ + { + "apiVersion": "cluster.x-k8s.io/v1alpha3", + "fieldsType": "FieldsV1", + "fieldsV1": { + "f:metadata": { + "f:annotations": { + ".": {}, + "f:kubectl.kubernetes.io/last-applied-configuration": {}, + "f:machinedeployment.clusters.x-k8s.io/revision": {} + }, + "f:labels": { + ".": {}, + "f:cluster.x-k8s.io/cluster-name": {} + }, + "f:ownerReferences": {} + }, + "f:spec": { + ".": {}, + "f:clusterName": {}, + "f:minReadySeconds": {}, + "f:progressDeadlineSeconds": {}, + "f:replicas": {}, + "f:revisionHistoryLimit": {}, + "f:selector": { + ".": {}, + "f:matchLabels": { + ".": {}, + "f:cluster.x-k8s.io/cluster-name": {}, + "f:cluster.x-k8s.io/deployment-name": {} + } + }, + "f:strategy": { + ".": {}, + "f:rollingUpdate": { + ".": {}, + "f:maxSurge": {}, + "f:maxUnavailable": {} + }, + "f:type": {} + }, + "f:template": { + ".": {}, + "f:metadata": { + ".": {}, + "f:labels": { + ".": {}, + "f:cluster.x-k8s.io/cluster-name": {}, + "f:cluster.x-k8s.io/deployment-name": {} + } + }, + "f:spec": { + ".": {}, + "f:bootstrap": { + ".": {}, + "f:configRef": { + ".": {}, + "f:apiVersion": {}, + "f:kind": {}, + "f:name": {}, + "f:namespace": {} + } + }, + "f:clusterName": {}, + "f:infrastructureRef": { + ".": {}, + "f:apiVersion": {}, + "f:kind": {}, + "f:name": {}, + "f:namespace": {} + }, + "f:version": {} + } + } + } + }, + "manager": "clusterctl", + "operation": "Update", + "time": "2021-07-01T14:50:15Z" + }, + { + "apiVersion": "cluster.x-k8s.io/v1alpha3", + "fieldsType": "FieldsV1", + "fieldsV1": { + "f:status": { + ".": {}, + "f:availableReplicas": {}, + "f:observedGeneration": {}, + "f:phase": {}, + "f:readyReplicas": {}, + "f:replicas": {}, + "f:selector": {}, + "f:updatedReplicas": {} + } + }, + "manager": "manager", + "operation": "Update", + "time": "2021-07-01T14:50:17Z" + } + ], + "name": "test1-md-0", + "namespace": "default", + "ownerReferences": [ + { + "apiVersion": "cluster.x-k8s.io/v1alpha3", + "kind": "Cluster", + "name": "test1", + "uid": "9607241e-c3a5-40c7-8f51-268231e615c1" + } + ], + "resourceVersion": "3226", + "selfLink": "/apis/cluster.x-k8s.io/v1alpha3/namespaces/default/machinedeployments/test1-md-0", + "uid": "324c8511-f947-45f8-b586-c015e5711d69" + }, + "spec": { + "clusterName": "test1", + "minReadySeconds": 0, + "progressDeadlineSeconds": 600, + "replicas": 1, + "revisionHistoryLimit": 1, + "selector": { + "matchLabels": { + "cluster.x-k8s.io/cluster-name": "test1", + "cluster.x-k8s.io/deployment-name": "test1-md-0" + } + }, + "strategy": { + "rollingUpdate": { + "maxSurge": 1, + "maxUnavailable": 0 + }, + "type": "RollingUpdate" + }, + "template": { + "metadata": { + "labels": { + "cluster.x-k8s.io/cluster-name": "test1", + "cluster.x-k8s.io/deployment-name": "test1-md-0" + } + }, + "spec": { + "bootstrap": { + "configRef": { + "apiVersion": "bootstrap.cluster.x-k8s.io/v1alpha3", + "kind": "KubeadmConfigTemplate", + "name": "test1-md-0", + "namespace": "default" + } + }, + "clusterName": "test1", + "infrastructureRef": { + "apiVersion": "infrastructure.cluster.x-k8s.io/v1alpha3", + "kind": "DockerMachineTemplate", + "name": "test1-md-0", + "namespace": "default" + }, + "version": "v1.19.8-eks-1-19-4" + } + } + }, + "status": { + "availableReplicas": 1, + "observedGeneration": 1, + "phase": "Running", + "readyReplicas": 1, + "replicas": 1, + "selector": "cluster.x-k8s.io/cluster-name=test1,cluster.x-k8s.io/deployment-name=test1-md-0", + "updatedReplicas": 1 + } + } + ], + "kind": "List", + "metadata": { + "resourceVersion": "", + "selfLink": "" + } +}