diff --git a/pkg/api/admin/openshiftcluster.go b/pkg/api/admin/openshiftcluster.go index 31717ebcb23..aa38d74976a 100644 --- a/pkg/api/admin/openshiftcluster.go +++ b/pkg/api/admin/openshiftcluster.go @@ -87,24 +87,37 @@ const ( type MaintenanceState string const ( - MaintenanceStateNone MaintenanceState = "None" - MaintenanceStatePending MaintenanceState = "Pending" - MaintenanceStatePlanned MaintenanceState = "Planned" - MaintenanceStateUnplanned MaintenanceState = "Unplanned" + MaintenanceStateNone MaintenanceState = "None" + MaintenanceStatePending MaintenanceState = "Pending" + MaintenanceStatePlanned MaintenanceState = "Planned" + MaintenanceStateUnplanned MaintenanceState = "Unplanned" + MaintenanceStateCustomerActionNeeded MaintenanceState = "CustomerActionNeeded" ) type MaintenanceTask string const ( + // + // Maintenance tasks that perform work on the cluster + // + MaintenanceTaskEverything MaintenanceTask = "Everything" MaintenanceTaskOperator MaintenanceTask = "OperatorUpdate" MaintenanceTaskRenewCerts MaintenanceTask = "CertificatesRenewal" + // // Maintenance tasks for updating customer maintenance signals + // + + MaintenanceTaskPending MaintenanceTask = "Pending" + // None signal should only be used when (1) admin update fails and (2) SRE fixes the failed admin update without running another admin updates // Admin update success should automatically set the cluster into None state - MaintenanceTaskPending MaintenanceTask = "Pending" - MaintenanceTaskNone MaintenanceTask = "None" + MaintenanceTaskNone MaintenanceTask = "None" + + // Customer action needed signal should only be used when (1) admin update fails and (2) customer needs to take action to resolve the failure + // To remove the signal after customer takes action, use maintenance task None + MaintenanceTaskCustomerActionNeeded MaintenanceTask = "CustomerActionNeeded" ) // Operator feature flags diff --git a/pkg/api/admin/openshiftcluster_validatestatic.go b/pkg/api/admin/openshiftcluster_validatestatic.go index 27db9aa2b47..fe0d7d8d420 100644 --- a/pkg/api/admin/openshiftcluster_validatestatic.go +++ b/pkg/api/admin/openshiftcluster_validatestatic.go @@ -38,7 +38,8 @@ func validateMaintenanceTask(task MaintenanceTask) error { task == MaintenanceTaskOperator || task == MaintenanceTaskRenewCerts || task == MaintenanceTaskPending || - task == MaintenanceTaskNone) { + task == MaintenanceTaskNone || + task == MaintenanceTaskCustomerActionNeeded) { return api.NewCloudError(http.StatusBadRequest, api.CloudErrorCodeInvalidParameter, "properties.maintenanceTask", "Invalid enum parameter.") } diff --git a/pkg/api/admin/openshiftcluster_validatestatic_test.go b/pkg/api/admin/openshiftcluster_validatestatic_test.go index f9351e4c0a9..77a4c0d7062 100644 --- a/pkg/api/admin/openshiftcluster_validatestatic_test.go +++ b/pkg/api/admin/openshiftcluster_validatestatic_test.go @@ -702,6 +702,19 @@ func TestOpenShiftClusterStaticValidateDelta(t *testing.T) { oc.Properties.MaintenanceTask = "" }, }, + { + name: "maintenanceTask change to customer action needed allowed", + oc: func() *OpenShiftCluster { + return &OpenShiftCluster{ + Properties: OpenShiftClusterProperties{ + MaintenanceTask: MaintenanceTaskCustomerActionNeeded, + }, + } + }, + modify: func(oc *OpenShiftCluster) { + oc.Properties.MaintenanceTask = "" + }, + }, { name: "maintenanceTask change to other values is disallowed", oc: func() *OpenShiftCluster { diff --git a/pkg/api/openshiftcluster.go b/pkg/api/openshiftcluster.go index 2dd86c8f9f9..95144a5f127 100644 --- a/pkg/api/openshiftcluster.go +++ b/pkg/api/openshiftcluster.go @@ -185,24 +185,37 @@ const ( type MaintenanceState string const ( - MaintenanceStateNone MaintenanceState = "None" - MaintenanceStatePending MaintenanceState = "Pending" - MaintenanceStatePlanned MaintenanceState = "Planned" - MaintenanceStateUnplanned MaintenanceState = "Unplanned" + MaintenanceStateNone MaintenanceState = "None" + MaintenanceStatePending MaintenanceState = "Pending" + MaintenanceStatePlanned MaintenanceState = "Planned" + MaintenanceStateUnplanned MaintenanceState = "Unplanned" + MaintenanceStateCustomerActionNeeded MaintenanceState = "CustomerActionNeeded" ) type MaintenanceTask string const ( + // + // Maintenance tasks that perform work on the cluster + // + MaintenanceTaskEverything MaintenanceTask = "Everything" MaintenanceTaskOperator MaintenanceTask = "OperatorUpdate" MaintenanceTaskRenewCerts MaintenanceTask = "CertificatesRenewal" + // // Maintenance tasks for updating customer maintenance signals + // + + MaintenanceTaskPending MaintenanceTask = "Pending" + // None signal should only be used when (1) admin update fails and (2) SRE fixes the failed admin update without running another admin updates // Admin update success should automatically set the cluster into None state - MaintenanceTaskPending MaintenanceTask = "Pending" - MaintenanceTaskNone MaintenanceTask = "None" + MaintenanceTaskNone MaintenanceTask = "None" + + // Customer action needed signal should only be used when (1) admin update fails and (2) customer needs to take action to resolve the failure + // To remove the signal after customer takes action, use maintenance task None + MaintenanceTaskCustomerActionNeeded MaintenanceTask = "CustomerActionNeeded" ) // IsMaintenanceOngoingTask returns true if the maintenance task should change state to maintenance ongoing (planned/unplanned) diff --git a/pkg/frontend/openshiftcluster_putorpatch.go b/pkg/frontend/openshiftcluster_putorpatch.go index 5fe58897874..c3b57b99e7e 100644 --- a/pkg/frontend/openshiftcluster_putorpatch.go +++ b/pkg/frontend/openshiftcluster_putorpatch.go @@ -345,6 +345,8 @@ func adminUpdateProvisioningState(doc *api.OpenShiftClusterDocument) { doc.OpenShiftCluster.Properties.MaintenanceState = api.MaintenanceStatePending case api.MaintenanceTaskNone: doc.OpenShiftCluster.Properties.MaintenanceState = api.MaintenanceStateNone + case api.MaintenanceTaskCustomerActionNeeded: + doc.OpenShiftCluster.Properties.MaintenanceState = api.MaintenanceStateCustomerActionNeeded } // This enables future admin update actions with body `{}` to succeed diff --git a/pkg/frontend/openshiftcluster_putorpatch_test.go b/pkg/frontend/openshiftcluster_putorpatch_test.go index 4dbb25fcf2e..c3978cb12f0 100644 --- a/pkg/frontend/openshiftcluster_putorpatch_test.go +++ b/pkg/frontend/openshiftcluster_putorpatch_test.go @@ -1213,6 +1213,507 @@ func TestPutOrPatchOpenShiftClusterAdminAPI(t *testing.T) { }, }, }, + { + name: "patch a failed planned maintenance cluster with customer action needed", + request: func(oc *admin.OpenShiftCluster) { + oc.Properties.MaintenanceTask = admin.MaintenanceTaskCustomerActionNeeded + }, + isPatch: true, + fixture: func(f *testdatabase.Fixture) { + f.AddSubscriptionDocuments(&api.SubscriptionDocument{ + ID: mockSubID, + Subscription: &api.Subscription{ + State: api.SubscriptionStateRegistered, + }, + }) + f.AddOpenShiftClusterDocuments(&api.OpenShiftClusterDocument{ + Key: strings.ToLower(testdatabase.GetResourcePath(mockSubID, "resourceName")), + OpenShiftCluster: &api.OpenShiftCluster{ + ID: testdatabase.GetResourcePath(mockSubID, "resourceName"), + Type: "Microsoft.RedHatOpenShift/openShiftClusters", + Tags: map[string]string{"tag": "will-be-kept"}, + Properties: api.OpenShiftClusterProperties{ + ProvisioningState: api.ProvisioningStateSucceeded, + LastProvisioningState: api.ProvisioningStateSucceeded, + OperatorFlags: api.OperatorFlags{"testFlag": "true"}, + LastAdminUpdateError: "error", + MaintenanceState: api.MaintenanceStatePlanned, + }, + }, + }) + }, + wantSystemDataEnriched: true, + wantEnriched: []string{testdatabase.GetResourcePath(mockSubID, "resourceName")}, + wantDocuments: func(c *testdatabase.Checker) { + c.AddAsyncOperationDocuments(&api.AsyncOperationDocument{ + OpenShiftClusterKey: strings.ToLower(testdatabase.GetResourcePath(mockSubID, "resourceName")), + AsyncOperation: &api.AsyncOperation{ + InitialProvisioningState: api.ProvisioningStateSucceeded, + ProvisioningState: api.ProvisioningStateSucceeded, + }, + }) + c.AddOpenShiftClusterDocuments(&api.OpenShiftClusterDocument{ + Key: strings.ToLower(testdatabase.GetResourcePath(mockSubID, "resourceName")), + OpenShiftCluster: &api.OpenShiftCluster{ + ID: testdatabase.GetResourcePath(mockSubID, "resourceName"), + Type: "Microsoft.RedHatOpenShift/openShiftClusters", + Tags: map[string]string{"tag": "will-be-kept"}, + Properties: api.OpenShiftClusterProperties{ + ProvisioningState: api.ProvisioningStateSucceeded, + LastProvisioningState: api.ProvisioningStateSucceeded, + ClusterProfile: api.ClusterProfile{ + FipsValidatedModules: api.FipsValidatedModulesDisabled, + }, + MaintenanceTask: "", + NetworkProfile: api.NetworkProfile{ + OutboundType: api.OutboundTypeLoadbalancer, + PreconfiguredNSG: api.PreconfiguredNSGDisabled, + LoadBalancerProfile: &api.LoadBalancerProfile{ + ManagedOutboundIPs: &api.ManagedOutboundIPs{ + Count: 1, + }, + }, + }, + MasterProfile: api.MasterProfile{ + EncryptionAtHost: api.EncryptionAtHostDisabled, + }, + OperatorFlags: api.OperatorFlags{"testFlag": "true"}, + MaintenanceState: api.MaintenanceStateCustomerActionNeeded, + LastAdminUpdateError: "error", + }, + }, + }) + }, + wantAsync: true, + wantStatusCode: http.StatusOK, + wantResponse: &admin.OpenShiftCluster{ + ID: testdatabase.GetResourcePath(mockSubID, "resourceName"), + Type: "Microsoft.RedHatOpenShift/openShiftClusters", + Tags: map[string]string{"tag": "will-be-kept"}, + Properties: admin.OpenShiftClusterProperties{ + ProvisioningState: admin.ProvisioningStateSucceeded, + LastProvisioningState: admin.ProvisioningStateSucceeded, + ClusterProfile: admin.ClusterProfile{ + FipsValidatedModules: admin.FipsValidatedModulesDisabled, + }, + NetworkProfile: admin.NetworkProfile{ + OutboundType: admin.OutboundTypeLoadbalancer, + LoadBalancerProfile: &admin.LoadBalancerProfile{ + ManagedOutboundIPs: &admin.ManagedOutboundIPs{ + Count: 1, + }, + }, + }, + MasterProfile: admin.MasterProfile{ + EncryptionAtHost: admin.EncryptionAtHostDisabled, + }, + OperatorFlags: admin.OperatorFlags{"testFlag": "true"}, + MaintenanceState: admin.MaintenanceStateCustomerActionNeeded, + LastAdminUpdateError: "error", + }, + }, + }, + { + name: "patch a failed planned maintenance cluster with customer action needed", + request: func(oc *admin.OpenShiftCluster) { + oc.Properties.MaintenanceTask = admin.MaintenanceTaskCustomerActionNeeded + }, + isPatch: true, + fixture: func(f *testdatabase.Fixture) { + f.AddSubscriptionDocuments(&api.SubscriptionDocument{ + ID: mockSubID, + Subscription: &api.Subscription{ + State: api.SubscriptionStateRegistered, + }, + }) + f.AddOpenShiftClusterDocuments(&api.OpenShiftClusterDocument{ + Key: strings.ToLower(testdatabase.GetResourcePath(mockSubID, "resourceName")), + OpenShiftCluster: &api.OpenShiftCluster{ + ID: testdatabase.GetResourcePath(mockSubID, "resourceName"), + Type: "Microsoft.RedHatOpenShift/openShiftClusters", + Tags: map[string]string{"tag": "will-be-kept"}, + Properties: api.OpenShiftClusterProperties{ + ProvisioningState: api.ProvisioningStateSucceeded, + FailedProvisioningState: api.ProvisioningStateUpdating, + OperatorFlags: api.OperatorFlags{"testFlag": "true"}, + LastAdminUpdateError: "error", + MaintenanceState: api.MaintenanceStatePlanned, + }, + }, + }) + }, + wantSystemDataEnriched: true, + wantEnriched: []string{testdatabase.GetResourcePath(mockSubID, "resourceName")}, + wantDocuments: func(c *testdatabase.Checker) { + c.AddAsyncOperationDocuments(&api.AsyncOperationDocument{ + OpenShiftClusterKey: strings.ToLower(testdatabase.GetResourcePath(mockSubID, "resourceName")), + AsyncOperation: &api.AsyncOperation{ + InitialProvisioningState: api.ProvisioningStateSucceeded, + ProvisioningState: api.ProvisioningStateSucceeded, + }, + }) + c.AddOpenShiftClusterDocuments(&api.OpenShiftClusterDocument{ + Key: strings.ToLower(testdatabase.GetResourcePath(mockSubID, "resourceName")), + OpenShiftCluster: &api.OpenShiftCluster{ + ID: testdatabase.GetResourcePath(mockSubID, "resourceName"), + Type: "Microsoft.RedHatOpenShift/openShiftClusters", + Tags: map[string]string{"tag": "will-be-kept"}, + Properties: api.OpenShiftClusterProperties{ + ProvisioningState: api.ProvisioningStateSucceeded, + FailedProvisioningState: api.ProvisioningStateUpdating, + ClusterProfile: api.ClusterProfile{ + FipsValidatedModules: api.FipsValidatedModulesDisabled, + }, + MaintenanceTask: "", + NetworkProfile: api.NetworkProfile{ + OutboundType: api.OutboundTypeLoadbalancer, + PreconfiguredNSG: api.PreconfiguredNSGDisabled, + LoadBalancerProfile: &api.LoadBalancerProfile{ + ManagedOutboundIPs: &api.ManagedOutboundIPs{ + Count: 1, + }, + }, + }, + MasterProfile: api.MasterProfile{ + EncryptionAtHost: api.EncryptionAtHostDisabled, + }, + OperatorFlags: api.OperatorFlags{"testFlag": "true"}, + MaintenanceState: api.MaintenanceStateCustomerActionNeeded, + LastAdminUpdateError: "error", + }, + }, + }) + }, + wantAsync: true, + wantStatusCode: http.StatusOK, + wantResponse: &admin.OpenShiftCluster{ + ID: testdatabase.GetResourcePath(mockSubID, "resourceName"), + Type: "Microsoft.RedHatOpenShift/openShiftClusters", + Tags: map[string]string{"tag": "will-be-kept"}, + Properties: admin.OpenShiftClusterProperties{ + ProvisioningState: admin.ProvisioningStateSucceeded, + FailedProvisioningState: admin.ProvisioningStateUpdating, + ClusterProfile: admin.ClusterProfile{ + FipsValidatedModules: admin.FipsValidatedModulesDisabled, + }, + NetworkProfile: admin.NetworkProfile{ + OutboundType: admin.OutboundTypeLoadbalancer, + LoadBalancerProfile: &admin.LoadBalancerProfile{ + ManagedOutboundIPs: &admin.ManagedOutboundIPs{ + Count: 1, + }, + }, + }, + MasterProfile: admin.MasterProfile{ + EncryptionAtHost: admin.EncryptionAtHostDisabled, + }, + OperatorFlags: admin.OperatorFlags{"testFlag": "true"}, + MaintenanceState: admin.MaintenanceStateCustomerActionNeeded, + LastAdminUpdateError: "error", + }, + }, + }, + { + name: "patch a failed unplanned maintenance cluster with customer action needed", + request: func(oc *admin.OpenShiftCluster) { + oc.Properties.MaintenanceTask = admin.MaintenanceTaskCustomerActionNeeded + }, + isPatch: true, + fixture: func(f *testdatabase.Fixture) { + f.AddSubscriptionDocuments(&api.SubscriptionDocument{ + ID: mockSubID, + Subscription: &api.Subscription{ + State: api.SubscriptionStateRegistered, + }, + }) + f.AddOpenShiftClusterDocuments(&api.OpenShiftClusterDocument{ + Key: strings.ToLower(testdatabase.GetResourcePath(mockSubID, "resourceName")), + OpenShiftCluster: &api.OpenShiftCluster{ + ID: testdatabase.GetResourcePath(mockSubID, "resourceName"), + Type: "Microsoft.RedHatOpenShift/openShiftClusters", + Tags: map[string]string{"tag": "will-be-kept"}, + Properties: api.OpenShiftClusterProperties{ + ProvisioningState: api.ProvisioningStateSucceeded, + FailedProvisioningState: api.ProvisioningStateUpdating, + OperatorFlags: api.OperatorFlags{"testFlag": "true"}, + LastAdminUpdateError: "error", + MaintenanceState: api.MaintenanceStateUnplanned, + }, + }, + }) + }, + wantSystemDataEnriched: true, + wantEnriched: []string{testdatabase.GetResourcePath(mockSubID, "resourceName")}, + wantDocuments: func(c *testdatabase.Checker) { + c.AddAsyncOperationDocuments(&api.AsyncOperationDocument{ + OpenShiftClusterKey: strings.ToLower(testdatabase.GetResourcePath(mockSubID, "resourceName")), + AsyncOperation: &api.AsyncOperation{ + InitialProvisioningState: api.ProvisioningStateSucceeded, + ProvisioningState: api.ProvisioningStateSucceeded, + }, + }) + c.AddOpenShiftClusterDocuments(&api.OpenShiftClusterDocument{ + Key: strings.ToLower(testdatabase.GetResourcePath(mockSubID, "resourceName")), + OpenShiftCluster: &api.OpenShiftCluster{ + ID: testdatabase.GetResourcePath(mockSubID, "resourceName"), + Type: "Microsoft.RedHatOpenShift/openShiftClusters", + Tags: map[string]string{"tag": "will-be-kept"}, + Properties: api.OpenShiftClusterProperties{ + ProvisioningState: api.ProvisioningStateSucceeded, + FailedProvisioningState: api.ProvisioningStateUpdating, + ClusterProfile: api.ClusterProfile{ + FipsValidatedModules: api.FipsValidatedModulesDisabled, + }, + MaintenanceTask: "", + NetworkProfile: api.NetworkProfile{ + OutboundType: api.OutboundTypeLoadbalancer, + PreconfiguredNSG: api.PreconfiguredNSGDisabled, + LoadBalancerProfile: &api.LoadBalancerProfile{ + ManagedOutboundIPs: &api.ManagedOutboundIPs{ + Count: 1, + }, + }, + }, + MasterProfile: api.MasterProfile{ + EncryptionAtHost: api.EncryptionAtHostDisabled, + }, + OperatorFlags: api.OperatorFlags{"testFlag": "true"}, + MaintenanceState: api.MaintenanceStateCustomerActionNeeded, + LastAdminUpdateError: "error", + }, + }, + }) + }, + wantAsync: true, + wantStatusCode: http.StatusOK, + wantResponse: &admin.OpenShiftCluster{ + ID: testdatabase.GetResourcePath(mockSubID, "resourceName"), + Type: "Microsoft.RedHatOpenShift/openShiftClusters", + Tags: map[string]string{"tag": "will-be-kept"}, + Properties: admin.OpenShiftClusterProperties{ + ProvisioningState: admin.ProvisioningStateSucceeded, + FailedProvisioningState: admin.ProvisioningStateUpdating, + ClusterProfile: admin.ClusterProfile{ + FipsValidatedModules: admin.FipsValidatedModulesDisabled, + }, + NetworkProfile: admin.NetworkProfile{ + OutboundType: admin.OutboundTypeLoadbalancer, + LoadBalancerProfile: &admin.LoadBalancerProfile{ + ManagedOutboundIPs: &admin.ManagedOutboundIPs{ + Count: 1, + }, + }, + }, + MasterProfile: admin.MasterProfile{ + EncryptionAtHost: admin.EncryptionAtHostDisabled, + }, + OperatorFlags: admin.OperatorFlags{"testFlag": "true"}, + MaintenanceState: admin.MaintenanceStateCustomerActionNeeded, + LastAdminUpdateError: "error", + }, + }, + }, + { + name: "patch a customer action needed cluster with maintenance state none", + request: func(oc *admin.OpenShiftCluster) { + oc.Properties.MaintenanceTask = admin.MaintenanceTaskNone + }, + isPatch: true, + fixture: func(f *testdatabase.Fixture) { + f.AddSubscriptionDocuments(&api.SubscriptionDocument{ + ID: mockSubID, + Subscription: &api.Subscription{ + State: api.SubscriptionStateRegistered, + }, + }) + f.AddOpenShiftClusterDocuments(&api.OpenShiftClusterDocument{ + Key: strings.ToLower(testdatabase.GetResourcePath(mockSubID, "resourceName")), + OpenShiftCluster: &api.OpenShiftCluster{ + ID: testdatabase.GetResourcePath(mockSubID, "resourceName"), + Type: "Microsoft.RedHatOpenShift/openShiftClusters", + Tags: map[string]string{"tag": "will-be-kept"}, + Properties: api.OpenShiftClusterProperties{ + ProvisioningState: api.ProvisioningStateSucceeded, + FailedProvisioningState: api.ProvisioningStateUpdating, + OperatorFlags: api.OperatorFlags{"testFlag": "true"}, + LastAdminUpdateError: "error", + MaintenanceState: api.MaintenanceStateCustomerActionNeeded, + }, + }, + }) + }, + wantSystemDataEnriched: true, + wantEnriched: []string{testdatabase.GetResourcePath(mockSubID, "resourceName")}, + wantDocuments: func(c *testdatabase.Checker) { + c.AddAsyncOperationDocuments(&api.AsyncOperationDocument{ + OpenShiftClusterKey: strings.ToLower(testdatabase.GetResourcePath(mockSubID, "resourceName")), + AsyncOperation: &api.AsyncOperation{ + InitialProvisioningState: api.ProvisioningStateSucceeded, + ProvisioningState: api.ProvisioningStateSucceeded, + }, + }) + c.AddOpenShiftClusterDocuments(&api.OpenShiftClusterDocument{ + Key: strings.ToLower(testdatabase.GetResourcePath(mockSubID, "resourceName")), + OpenShiftCluster: &api.OpenShiftCluster{ + ID: testdatabase.GetResourcePath(mockSubID, "resourceName"), + Type: "Microsoft.RedHatOpenShift/openShiftClusters", + Tags: map[string]string{"tag": "will-be-kept"}, + Properties: api.OpenShiftClusterProperties{ + ProvisioningState: api.ProvisioningStateSucceeded, + FailedProvisioningState: api.ProvisioningStateUpdating, + ClusterProfile: api.ClusterProfile{ + FipsValidatedModules: api.FipsValidatedModulesDisabled, + }, + LastAdminUpdateError: "error", + MaintenanceTask: "", + NetworkProfile: api.NetworkProfile{ + OutboundType: api.OutboundTypeLoadbalancer, + PreconfiguredNSG: api.PreconfiguredNSGDisabled, + LoadBalancerProfile: &api.LoadBalancerProfile{ + ManagedOutboundIPs: &api.ManagedOutboundIPs{ + Count: 1, + }, + }, + }, + MasterProfile: api.MasterProfile{ + EncryptionAtHost: api.EncryptionAtHostDisabled, + }, + OperatorFlags: api.OperatorFlags{"testFlag": "true"}, + MaintenanceState: api.MaintenanceStateNone, + }, + }, + }) + }, + wantAsync: true, + wantStatusCode: http.StatusOK, + wantResponse: &admin.OpenShiftCluster{ + ID: testdatabase.GetResourcePath(mockSubID, "resourceName"), + Type: "Microsoft.RedHatOpenShift/openShiftClusters", + Tags: map[string]string{"tag": "will-be-kept"}, + Properties: admin.OpenShiftClusterProperties{ + ProvisioningState: admin.ProvisioningStateSucceeded, + FailedProvisioningState: admin.ProvisioningStateUpdating, + ClusterProfile: admin.ClusterProfile{ + FipsValidatedModules: admin.FipsValidatedModulesDisabled, + }, + NetworkProfile: admin.NetworkProfile{ + OutboundType: admin.OutboundTypeLoadbalancer, + LoadBalancerProfile: &admin.LoadBalancerProfile{ + ManagedOutboundIPs: &admin.ManagedOutboundIPs{ + Count: 1, + }, + }, + }, + MasterProfile: admin.MasterProfile{ + EncryptionAtHost: admin.EncryptionAtHostDisabled, + }, + OperatorFlags: admin.OperatorFlags{"testFlag": "true"}, + MaintenanceState: admin.MaintenanceStateNone, + LastAdminUpdateError: "error", + }, + }, + }, + { + name: "patch a customer action needed cluster with maintenance state unplanned", + request: func(oc *admin.OpenShiftCluster) { + oc.Properties.MaintenanceTask = admin.MaintenanceTaskEverything + }, + isPatch: true, + fixture: func(f *testdatabase.Fixture) { + f.AddSubscriptionDocuments(&api.SubscriptionDocument{ + ID: mockSubID, + Subscription: &api.Subscription{ + State: api.SubscriptionStateRegistered, + }, + }) + f.AddOpenShiftClusterDocuments(&api.OpenShiftClusterDocument{ + Key: strings.ToLower(testdatabase.GetResourcePath(mockSubID, "resourceName")), + OpenShiftCluster: &api.OpenShiftCluster{ + ID: testdatabase.GetResourcePath(mockSubID, "resourceName"), + Type: "Microsoft.RedHatOpenShift/openShiftClusters", + Tags: map[string]string{"tag": "will-be-kept"}, + Properties: api.OpenShiftClusterProperties{ + ProvisioningState: api.ProvisioningStateSucceeded, + FailedProvisioningState: api.ProvisioningStateUpdating, + OperatorFlags: api.OperatorFlags{"testFlag": "true"}, + LastAdminUpdateError: "error", + MaintenanceState: api.MaintenanceStateCustomerActionNeeded, + }, + }, + }) + }, + wantSystemDataEnriched: true, + wantEnriched: []string{testdatabase.GetResourcePath(mockSubID, "resourceName")}, + wantDocuments: func(c *testdatabase.Checker) { + c.AddAsyncOperationDocuments(&api.AsyncOperationDocument{ + OpenShiftClusterKey: strings.ToLower(testdatabase.GetResourcePath(mockSubID, "resourceName")), + AsyncOperation: &api.AsyncOperation{ + InitialProvisioningState: api.ProvisioningStateAdminUpdating, + ProvisioningState: api.ProvisioningStateAdminUpdating, + }, + }) + c.AddOpenShiftClusterDocuments(&api.OpenShiftClusterDocument{ + Key: strings.ToLower(testdatabase.GetResourcePath(mockSubID, "resourceName")), + OpenShiftCluster: &api.OpenShiftCluster{ + ID: testdatabase.GetResourcePath(mockSubID, "resourceName"), + Type: "Microsoft.RedHatOpenShift/openShiftClusters", + Tags: map[string]string{"tag": "will-be-kept"}, + Properties: api.OpenShiftClusterProperties{ + ProvisioningState: api.ProvisioningStateAdminUpdating, + LastProvisioningState: api.ProvisioningStateSucceeded, + FailedProvisioningState: api.ProvisioningStateUpdating, + ClusterProfile: api.ClusterProfile{ + FipsValidatedModules: api.FipsValidatedModulesDisabled, + }, + MaintenanceTask: api.MaintenanceTaskEverything, + NetworkProfile: api.NetworkProfile{ + OutboundType: api.OutboundTypeLoadbalancer, + PreconfiguredNSG: api.PreconfiguredNSGDisabled, + LoadBalancerProfile: &api.LoadBalancerProfile{ + ManagedOutboundIPs: &api.ManagedOutboundIPs{ + Count: 1, + }, + }, + }, + MasterProfile: api.MasterProfile{ + EncryptionAtHost: api.EncryptionAtHostDisabled, + }, + OperatorFlags: api.OperatorFlags{"testFlag": "true"}, + MaintenanceState: api.MaintenanceStateUnplanned, + }, + }, + }) + }, + wantAsync: true, + wantStatusCode: http.StatusOK, + wantResponse: &admin.OpenShiftCluster{ + ID: testdatabase.GetResourcePath(mockSubID, "resourceName"), + Type: "Microsoft.RedHatOpenShift/openShiftClusters", + Tags: map[string]string{"tag": "will-be-kept"}, + Properties: admin.OpenShiftClusterProperties{ + ProvisioningState: admin.ProvisioningStateAdminUpdating, + LastProvisioningState: admin.ProvisioningStateSucceeded, + FailedProvisioningState: admin.ProvisioningStateUpdating, + ClusterProfile: admin.ClusterProfile{ + FipsValidatedModules: admin.FipsValidatedModulesDisabled, + }, + NetworkProfile: admin.NetworkProfile{ + OutboundType: admin.OutboundTypeLoadbalancer, + LoadBalancerProfile: &admin.LoadBalancerProfile{ + ManagedOutboundIPs: &admin.ManagedOutboundIPs{ + Count: 1, + }, + }, + }, + MasterProfile: admin.MasterProfile{ + EncryptionAtHost: admin.EncryptionAtHostDisabled, + }, + OperatorFlags: admin.OperatorFlags{"testFlag": "true"}, + MaintenanceState: admin.MaintenanceStateUnplanned, + MaintenanceTask: admin.MaintenanceTaskEverything, + }, + }, + }, } { t.Run(tt.name, func(t *testing.T) { ti := newTestInfra(t). diff --git a/pkg/monitor/cluster/maintenance.go b/pkg/monitor/cluster/maintenance.go index 332f0d58dc5..ebf56a51eca 100644 --- a/pkg/monitor/cluster/maintenance.go +++ b/pkg/monitor/cluster/maintenance.go @@ -36,10 +36,11 @@ func (m maintenanceState) String() string { } const ( - none maintenanceState = "none" - pending maintenanceState = "pending" - planned maintenanceState = "planned" - unplanned maintenanceState = "unplanned" + none maintenanceState = "none" + pending maintenanceState = "pending" + planned maintenanceState = "planned" + unplanned maintenanceState = "unplanned" + customerActionNeeded maintenanceState = "customerActionNeeded" ) func (mon *Monitor) emitMaintenanceState(ctx context.Context) error { @@ -59,6 +60,8 @@ func getMaintenanceState(clusterProperties api.OpenShiftClusterProperties) maint return planned case api.MaintenanceStateUnplanned: return unplanned + case api.MaintenanceStateCustomerActionNeeded: + return customerActionNeeded case api.MaintenanceStateNone: fallthrough // For new clusters, no maintenance state has been set yet diff --git a/pkg/monitor/cluster/maintenance_test.go b/pkg/monitor/cluster/maintenance_test.go index c942b1c5e8f..69e14c20646 100644 --- a/pkg/monitor/cluster/maintenance_test.go +++ b/pkg/monitor/cluster/maintenance_test.go @@ -50,6 +50,13 @@ func TestEmitMaintenanceState(t *testing.T) { maintenanceState: api.MaintenanceStatePlanned, expectedState: planned, }, + { + name: "state customer action needed", + provisioningState: api.ProvisioningStateSucceeded, + adminUpdateErr: "error", + maintenanceState: api.MaintenanceStateCustomerActionNeeded, + expectedState: customerActionNeeded, + }, } { t.Run(tt.name, func(t *testing.T) { ctx := context.Background()