From 91448982b1e53a8d3febf2f367d3313b9913898b Mon Sep 17 00:00:00 2001 From: Richard87 Date: Tue, 27 Aug 2024 10:50:33 +0200 Subject: [PATCH 01/33] Use Replica Override to manually scale a component --- api/deployments/models/component_builder.go | 3 + .../models/component_deployment.go | 6 ++ api/environments/component_handler.go | 82 ++++++------------- api/environments/deployment_updater.go | 10 +-- api/models/component.go | 11 ++- go.mod | 2 +- go.sum | 4 +- 7 files changed, 51 insertions(+), 67 deletions(-) diff --git a/api/deployments/models/component_builder.go b/api/deployments/models/component_builder.go index 61f2aef6..deb1a02a 100644 --- a/api/deployments/models/component_builder.go +++ b/api/deployments/models/component_builder.go @@ -54,6 +54,7 @@ type componentBuilder struct { gitTags string resources *radixv1.ResourceRequirements runtime *radixv1.Runtime + replicasOverride *int } func (b *componentBuilder) WithStatus(status ComponentStatus) ComponentBuilder { @@ -99,6 +100,7 @@ func (b *componentBuilder) WithComponent(component radixv1.RadixCommonDeployComp b.commitID = component.GetEnvironmentVariables()[defaults.RadixCommitHashEnvironmentVariable] b.gitTags = component.GetEnvironmentVariables()[defaults.RadixGitTagsEnvironmentVariable] b.runtime = component.GetRuntime() + b.replicasOverride = component.GetReplicasOverride() ports := []Port{} if component.GetPorts() != nil { @@ -252,6 +254,7 @@ func (b *componentBuilder) BuildComponent() (*Component, error) { Variables: variables, Replicas: b.podNames, ReplicaList: b.replicaSummaryList, + ReplicasOverride: b.replicasOverride, SchedulerPort: b.schedulerPort, ScheduledJobPayloadPath: b.scheduledJobPayloadPath, AuxiliaryResource: b.auxResource, diff --git a/api/deployments/models/component_deployment.go b/api/deployments/models/component_deployment.go index a3467f39..fb4ebbb3 100644 --- a/api/deployments/models/component_deployment.go +++ b/api/deployments/models/component_deployment.go @@ -85,6 +85,12 @@ type Component struct { // required: false ReplicaList []ReplicaSummary `json:"replicaList"` + // Set if manuall control of replicas is in place. null means automatic controll, 0 means stopped and >= 1 is manually scaled. + // + // required: false + // example: nil + ReplicasOverride *int `json:"replicasOverride,omitempty"` + // HorizontalScaling defines horizontal scaling summary for this component // // required: false diff --git a/api/environments/component_handler.go b/api/environments/component_handler.go index 2f659548..b1a7c649 100644 --- a/api/environments/component_handler.go +++ b/api/environments/component_handler.go @@ -9,6 +9,7 @@ import ( environmentModels "github.com/equinor/radix-api/api/environments/models" "github.com/equinor/radix-api/api/utils/labelselector" radixutils "github.com/equinor/radix-common/utils" + "github.com/equinor/radix-common/utils/pointers" "github.com/equinor/radix-operator/pkg/apis/defaults" v1 "github.com/equinor/radix-operator/pkg/apis/radix/v1" operatorUtils "github.com/equinor/radix-operator/pkg/apis/utils" @@ -43,7 +44,7 @@ func (eh EnvironmentHandler) StopComponent(ctx context.Context, appName, envName } return environmentModels.CannotStopComponent(appName, componentName, componentStatus) } - return eh.patchRadixDeploymentWithZeroReplicas(ctx, updater) + return eh.patchRadixDeploymentWithReplicas(ctx, updater, pointers.Ptr(0)) } // StartComponent Starts a component @@ -63,7 +64,7 @@ func (eh EnvironmentHandler) StartComponent(ctx context.Context, appName, envNam } return environmentModels.CannotStartComponent(appName, componentName, componentStatus) } - return eh.patchRadixDeploymentWithReplicasFromConfig(ctx, updater) + return eh.patchRadixDeploymentWithReplicas(ctx, updater, nil) } // RestartComponent Restarts a component @@ -83,6 +84,26 @@ func (eh EnvironmentHandler) RestartComponent(ctx context.Context, appName, envN return eh.patchRadixDeploymentWithTimestampInEnvVar(ctx, updater, defaults.RadixRestartEnvironmentVariable) } +// ScaleComponent Scale a component replicas +func (eh EnvironmentHandler) ScaleComponent(ctx context.Context, appName, envName, componentName string, replicas int) error { + if replicas < 0 { + return environmentModels.CannotScaleComponentToNegativeReplicas(appName, envName, componentName) + } + if replicas > maxScaleReplicas { + return environmentModels.CannotScaleComponentToMoreThanMaxReplicas(appName, envName, componentName, maxScaleReplicas) + } + log.Ctx(ctx).Info().Msgf("Scaling component %s, %s to %d replicas", componentName, appName, replicas) + updater, err := eh.getRadixCommonComponentUpdater(ctx, appName, envName, componentName) + if err != nil { + return err + } + componentStatus := updater.getComponentStatus() + if !radixutils.ContainsString(validaStatusesToScaleComponent, componentStatus) { + return environmentModels.CannotScaleComponent(appName, envName, componentName, componentStatus) + } + return eh.patchRadixDeploymentWithReplicas(ctx, updater, &replicas) +} + // RestartComponentAuxiliaryResource Restarts a component's auxiliary resource func (eh EnvironmentHandler) RestartComponentAuxiliaryResource(ctx context.Context, appName, envName, componentName, auxType string) error { log.Ctx(ctx).Info().Msgf("Restarting auxiliary resource %s for component %s, %s", auxType, componentName, appName) @@ -129,26 +150,6 @@ func (eh EnvironmentHandler) RestartComponentAuxiliaryResource(ctx context.Conte return eh.patchDeploymentForRestart(ctx, &deploymentList.Items[0]) } -// ScaleComponent Scale a component replicas -func (eh EnvironmentHandler) ScaleComponent(ctx context.Context, appName, envName, componentName string, replicas int) error { - if replicas < 0 { - return environmentModels.CannotScaleComponentToNegativeReplicas(appName, envName, componentName) - } - if replicas > maxScaleReplicas { - return environmentModels.CannotScaleComponentToMoreThanMaxReplicas(appName, envName, componentName, maxScaleReplicas) - } - log.Ctx(ctx).Info().Msgf("Scaling component %s, %s to %d replicas", componentName, appName, replicas) - updater, err := eh.getRadixCommonComponentUpdater(ctx, appName, envName, componentName) - if err != nil { - return err - } - componentStatus := updater.getComponentStatus() - if !radixutils.ContainsString(validaStatusesToScaleComponent, componentStatus) { - return environmentModels.CannotScaleComponent(appName, envName, componentName, componentStatus) - } - return eh.patchRadixDeploymentWithReplicas(ctx, updater, replicas) -} - func canDeploymentBeRestarted(deployment *appsv1.Deployment) bool { if deployment == nil { return false @@ -175,14 +176,6 @@ func (eh EnvironmentHandler) patchDeploymentForRestart(ctx context.Context, depl }) } -func getReplicasForComponentInEnvironment(environmentConfig v1.RadixCommonEnvironmentConfig) (*int, error) { - if environmentConfig != nil { - return environmentConfig.GetReplicas(), nil - } - - return nil, nil -} - func (eh EnvironmentHandler) patch(ctx context.Context, namespace, name string, oldJSON, newJSON []byte) error { patchBytes, err := jsonPatch.CreateMergePatch(oldJSON, newJSON) if err != nil { @@ -199,25 +192,9 @@ func (eh EnvironmentHandler) patch(ctx context.Context, namespace, name string, return nil } -func (eh EnvironmentHandler) patchRadixDeploymentWithReplicasFromConfig(ctx context.Context, updater radixDeployCommonComponentUpdater) error { +func (eh EnvironmentHandler) patchRadixDeploymentWithReplicas(ctx context.Context, updater radixDeployCommonComponentUpdater, replicas *int) error { return eh.commit(ctx, updater, func(updater radixDeployCommonComponentUpdater) error { - newReplica := 1 - replicas, err := getReplicasForComponentInEnvironment(updater.getEnvironmentConfig()) - if err != nil { - return err - } - if replicas != nil { - newReplica = *replicas - } - updater.setReplicasToComponent(&newReplica) - updater.setUserMutationTimestampAnnotation(radixutils.FormatTimestamp(time.Now())) - return nil - }) -} - -func (eh EnvironmentHandler) patchRadixDeploymentWithReplicas(ctx context.Context, updater radixDeployCommonComponentUpdater, replicas int) error { - return eh.commit(ctx, updater, func(updater radixDeployCommonComponentUpdater) error { - updater.setReplicasToComponent(&replicas) + updater.setReplicasOverrideToComponent(replicas) updater.setUserMutationTimestampAnnotation(radixutils.FormatTimestamp(time.Now())) return nil }) @@ -235,12 +212,3 @@ func (eh EnvironmentHandler) patchRadixDeploymentWithTimestampInEnvVar(ctx conte return nil }) } - -func (eh EnvironmentHandler) patchRadixDeploymentWithZeroReplicas(ctx context.Context, updater radixDeployCommonComponentUpdater) error { - return eh.commit(ctx, updater, func(updater radixDeployCommonComponentUpdater) error { - newReplica := 0 - updater.setReplicasToComponent(&newReplica) - updater.setUserMutationTimestampAnnotation(radixutils.FormatTimestamp(time.Now())) - return nil - }) -} diff --git a/api/environments/deployment_updater.go b/api/environments/deployment_updater.go index bc9106a8..aac423da 100644 --- a/api/environments/deployment_updater.go +++ b/api/environments/deployment_updater.go @@ -13,7 +13,7 @@ type radixDeployCommonComponentUpdater interface { getComponentStatus() string getRadixDeployment() *v1.RadixDeployment getEnvironmentConfig() v1.RadixCommonEnvironmentConfig - setReplicasToComponent(replicas *int) + setReplicasOverrideToComponent(replicas *int) setUserMutationTimestampAnnotation(timestamp string) } @@ -44,8 +44,8 @@ func (updater *radixDeployComponentUpdater) setEnvironmentVariablesToComponent(e updater.base.radixDeployment.Spec.Components[updater.base.componentIndex].SetEnvironmentVariables(envVars) } -func (updater *radixDeployComponentUpdater) setReplicasToComponent(replicas *int) { - updater.base.radixDeployment.Spec.Components[updater.base.componentIndex].Replicas = replicas +func (updater *radixDeployComponentUpdater) setReplicasOverrideToComponent(replicas *int) { + updater.base.radixDeployment.Spec.Components[updater.base.componentIndex].ReplicasOverride = replicas } func (updater *radixDeployComponentUpdater) setUserMutationTimestampAnnotation(timestamp string) { @@ -76,8 +76,8 @@ func (updater *radixDeployJobComponentUpdater) setUserMutationTimestampAnnotatio updater.base.radixDeployment.Annotations[lastUserMutationAnnotation] = timestamp } -func (updater *radixDeployJobComponentUpdater) setReplicasToComponent(replicas *int) { - //job component has always 1 replica +func (updater *radixDeployJobComponentUpdater) setReplicasOverrideToComponent(replicas *int) { + // job component has always 1 replica } func (updater *radixDeployJobComponentUpdater) getComponentStatus() string { diff --git a/api/models/component.go b/api/models/component.go index 6f2ef9dd..88f477d1 100644 --- a/api/models/component.go +++ b/api/models/component.go @@ -238,11 +238,13 @@ func certificateRequestConditionReady(condition cmv1.CertificateRequestCondition func getComponentStatus(component radixv1.RadixCommonDeployComponent, ra *radixv1.RadixApplication, rd *radixv1.RadixDeployment, pods []corev1.Pod) deploymentModels.ComponentStatus { environmentConfig := utils.GetComponentEnvironmentConfig(ra, rd.Spec.Environment, component.GetName()) if component.GetType() == radixv1.RadixComponentTypeComponent { - if runningReplicaDiffersFromConfig(environmentConfig, pods) && + // Manually stopped or KEDA scaled down to 0 + if runningReplicaDiffersFromConfig(component, environmentConfig, pods) && !runningReplicaDiffersFromSpec(component, pods) && len(pods) == 0 { return deploymentModels.StoppedComponent } + if runningReplicaDiffersFromSpec(component, pods) { return deploymentModels.ComponentReconciling } @@ -272,8 +274,13 @@ func getComponentStatus(component radixv1.RadixCommonDeployComponent, ra *radixv return deploymentModels.ConsistentComponent } -func runningReplicaDiffersFromConfig(environmentConfig radixv1.RadixCommonEnvironmentConfig, actualPods []corev1.Pod) bool { +func runningReplicaDiffersFromConfig(component radixv1.RadixCommonDeployComponent, environmentConfig radixv1.RadixCommonEnvironmentConfig, actualPods []corev1.Pod) bool { actualPodsLength := len(actualPods) + + if component.GetReplicasOverride() != nil { + return actualPodsLength != *component.GetReplicasOverride() + } + if commonutils.IsNil(environmentConfig) { return actualPodsLength != operatordeployment.DefaultReplicas } diff --git a/go.mod b/go.mod index 866fc0e6..1f023f49 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,7 @@ require ( github.com/cert-manager/cert-manager v1.15.0 github.com/equinor/radix-common v1.9.3 github.com/equinor/radix-job-scheduler v1.10.2 - github.com/equinor/radix-operator v1.57.18 + github.com/equinor/radix-operator v1.57.19-0.20240826111516-e94c0a610d2f github.com/evanphx/json-patch/v5 v5.9.0 github.com/felixge/httpsnoop v1.0.4 github.com/golang-jwt/jwt/v5 v5.2.1 diff --git a/go.sum b/go.sum index e30dcc9f..dec098bc 100644 --- a/go.sum +++ b/go.sum @@ -87,8 +87,8 @@ github.com/equinor/radix-common v1.9.3 h1:dLKFzYy8/XyEG9Zygi0rMWIYGCddai/ILwUqjB github.com/equinor/radix-common v1.9.3/go.mod h1:+g0Wj0D40zz29DjNkYKVmCVeYy4OsFWKI7Qi9rA6kpY= github.com/equinor/radix-job-scheduler v1.10.2 h1:Ea/gmSQjVdomC3XzkqJdR1TGQ9Bvq8ZJOKUfwYY3Ask= github.com/equinor/radix-job-scheduler v1.10.2/go.mod h1:cnVXZ09D0rAPTZrcgWynL/txMQnpYmPSPyzfKfTYlec= -github.com/equinor/radix-operator v1.57.18 h1:+zPaeZKfErjXE9O2+EjtUenrKw2nabq3e0y5VBjlwHA= -github.com/equinor/radix-operator v1.57.18/go.mod h1:zCdAiP/wxyvlUO4qGoJuLW3O+ZSt9kTyHMnjmsR3fCU= +github.com/equinor/radix-operator v1.57.19-0.20240826111516-e94c0a610d2f h1:Fe1dguEAN9wUWN8Hqhw3Ej4MGsG39hc45zDA7lHFCZ8= +github.com/equinor/radix-operator v1.57.19-0.20240826111516-e94c0a610d2f/go.mod h1:zCdAiP/wxyvlUO4qGoJuLW3O+ZSt9kTyHMnjmsR3fCU= github.com/evanphx/json-patch v5.9.0+incompatible h1:fBXyNpNMuTTDdquAq/uisOr2lShz4oaXpDTX2bLe7ls= github.com/evanphx/json-patch v5.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch/v5 v5.9.0 h1:kcBlZQbplgElYIlo/n1hJbls2z/1awpXxpRi0/FOJfg= From 3de4fc8288ad475bd35ebfeed96743c5dba8b587 Mon Sep 17 00:00:00 2001 From: Richard87 Date: Tue, 27 Aug 2024 15:27:09 +0200 Subject: [PATCH 02/33] parse component status --- api/models/component.go | 119 ++++++++++++------------------ api/utils/predicate/kubernetes.go | 7 ++ go.mod | 2 +- go.sum | 2 + 4 files changed, 56 insertions(+), 74 deletions(-) diff --git a/api/models/component.go b/api/models/component.go index 88f477d1..a5c74f18 100644 --- a/api/models/component.go +++ b/api/models/component.go @@ -9,14 +9,13 @@ import ( cmv1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" cmmetav1 "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" deploymentModels "github.com/equinor/radix-api/api/deployments/models" - "github.com/equinor/radix-api/api/utils" "github.com/equinor/radix-api/api/utils/event" "github.com/equinor/radix-api/api/utils/predicate" "github.com/equinor/radix-api/api/utils/tlsvalidation" commonutils "github.com/equinor/radix-common/utils" + "github.com/equinor/radix-common/utils/pointers" "github.com/equinor/radix-common/utils/slice" operatordefaults "github.com/equinor/radix-operator/pkg/apis/defaults" - operatordeployment "github.com/equinor/radix-operator/pkg/apis/deployment" radixv1 "github.com/equinor/radix-operator/pkg/apis/radix/v1" operatorutils "github.com/equinor/radix-operator/pkg/apis/utils" radixlabels "github.com/equinor/radix-operator/pkg/apis/utils/labels" @@ -65,11 +64,16 @@ func buildComponent( WithExternalDNS(getComponentExternalDNS(ra.Name, radixComponent, secretList, certs, certRequests, tlsValidator)) componentPods := slice.FindAll(podList, predicate.IsPodForComponent(ra.Name, radixComponent.GetName())) + var kd *appsv1.Deployment + if depl, ok := slice.FindFirst(deploymentList, predicate.IsDeploymentForComponent(ra.Name, radixComponent.GetName())); ok { + kd = &depl + } + if rd.Status.ActiveTo.IsZero() { builder.WithPodNames(slice.Map(componentPods, func(pod corev1.Pod) string { return pod.Name })) builder.WithRadixEnvironmentVariables(getRadixEnvironmentVariables(componentPods)) builder.WithReplicaSummaryList(BuildReplicaSummaryList(componentPods, lastEventWarnings)) - builder.WithStatus(getComponentStatus(radixComponent, ra, rd, componentPods)) + builder.WithStatus(getComponentStatus(radixComponent, kd, rd, podList)) builder.WithAuxiliaryResource(getAuxiliaryResources(ra.Name, radixComponent, deploymentList, podList, lastEventWarnings)) } @@ -235,93 +239,62 @@ func certificateRequestConditionReady(condition cmv1.CertificateRequestCondition return condition.Type == cmv1.CertificateRequestConditionReady } -func getComponentStatus(component radixv1.RadixCommonDeployComponent, ra *radixv1.RadixApplication, rd *radixv1.RadixDeployment, pods []corev1.Pod) deploymentModels.ComponentStatus { - environmentConfig := utils.GetComponentEnvironmentConfig(ra, rd.Spec.Environment, component.GetName()) - if component.GetType() == radixv1.RadixComponentTypeComponent { - // Manually stopped or KEDA scaled down to 0 - if runningReplicaDiffersFromConfig(component, environmentConfig, pods) && - !runningReplicaDiffersFromSpec(component, pods) && - len(pods) == 0 { - return deploymentModels.StoppedComponent - } +func getComponentStatus(component radixv1.RadixCommonDeployComponent, kd *appsv1.Deployment, rd *radixv1.RadixDeployment, pods []corev1.Pod) deploymentModels.ComponentStatus { + var replicasUnavailable, replicasReady, replicas int32 + if kd != nil { + replicasUnavailable = kd.Status.UnavailableReplicas + replicasReady = kd.Status.ReadyReplicas + replicas = pointers.Val(kd.Spec.Replicas) + } - if runningReplicaDiffersFromSpec(component, pods) { - return deploymentModels.ComponentReconciling - } - } else if component.GetType() == radixv1.RadixComponentTypeJob { - if len(pods) == 0 { - return deploymentModels.StoppedComponent - } + if replicas == 0 && component.GetType() == radixv1.RadixComponentTypeJob { + return deploymentModels.StoppedComponent } - if runningReplicaIsOutdated(component, pods) { - return deploymentModels.ComponentOutdated + + if isComponentManuallyStopped(component) && replicas == 0 { + return deploymentModels.StoppedComponent } - restarted := component.GetEnvironmentVariables()[operatordefaults.RadixRestartEnvironmentVariable] - if strings.EqualFold(restarted, "") { - return deploymentModels.ConsistentComponent + + if isCopmonentRestarting(component, rd) { + return deploymentModels.ComponentRestarting } - restartedTime, err := commonutils.ParseTimestamp(restarted) - if err != nil { - // TODO: How should we handle invalid value for restarted time? - log.Logger.Warn().Err(err).Msgf("unable to parse restarted time %v", restarted) - return deploymentModels.ConsistentComponent + // Check if component is scaling up or down + if replicasUnavailable > 0 || replicas < replicasReady { + return deploymentModels.ComponentReconciling } - reconciledTime := rd.Status.Reconciled - if reconciledTime.IsZero() || restartedTime.After(reconciledTime.Time) { - return deploymentModels.ComponentRestarting + + if runningReplicaIsOutdated(component, pods) { + return deploymentModels.ComponentOutdated } + return deploymentModels.ConsistentComponent } -func runningReplicaDiffersFromConfig(component radixv1.RadixCommonDeployComponent, environmentConfig radixv1.RadixCommonEnvironmentConfig, actualPods []corev1.Pod) bool { - actualPodsLength := len(actualPods) - - if component.GetReplicasOverride() != nil { - return actualPodsLength != *component.GetReplicasOverride() +func isComponentManuallyStopped(component radixv1.RadixCommonDeployComponent) bool { + override := component.GetReplicasOverride() + if override == nil { + return false } - if commonutils.IsNil(environmentConfig) { - return actualPodsLength != operatordeployment.DefaultReplicas - } - // No HPA config - if environmentConfig.GetHorizontalScaling() == nil { - if environmentConfig.GetReplicas() != nil { - return actualPodsLength != *environmentConfig.GetReplicas() - } - return actualPodsLength != operatordeployment.DefaultReplicas - } - // With HPA config - if environmentConfig.GetReplicas() != nil && *environmentConfig.GetReplicas() == 0 { - return actualPodsLength != *environmentConfig.GetReplicas() - } - if environmentConfig.GetHorizontalScaling().MinReplicas != nil { - return actualPodsLength < int(*environmentConfig.GetHorizontalScaling().MinReplicas) || - actualPodsLength > int(environmentConfig.GetHorizontalScaling().MaxReplicas) - } - return actualPodsLength < operatordeployment.DefaultReplicas || - actualPodsLength > int(environmentConfig.GetHorizontalScaling().MaxReplicas) + return *override == 0 } -func runningReplicaDiffersFromSpec(component radixv1.RadixCommonDeployComponent, actualPods []corev1.Pod) bool { - actualPodsLength := len(actualPods) - // No HPA config - if component.GetHorizontalScaling() == nil { - if component.GetReplicas() != nil { - return actualPodsLength != *component.GetReplicas() - } - return actualPodsLength != operatordeployment.DefaultReplicas +func isCopmonentRestarting(component radixv1.RadixCommonDeployComponent, rd *radixv1.RadixDeployment) bool { + restarted := component.GetEnvironmentVariables()[operatordefaults.RadixRestartEnvironmentVariable] + if strings.EqualFold(restarted, "") { + return false } - // With HPA config - if component.GetReplicas() != nil && *component.GetReplicas() == 0 { - return actualPodsLength != *component.GetReplicas() + restartedTime, err := commonutils.ParseTimestamp(restarted) + if err != nil { + log.Logger.Warn().Err(err).Msgf("unable to parse restarted time %v", restarted) + return false } - if component.GetHorizontalScaling().MinReplicas != nil { - return actualPodsLength < int(*component.GetHorizontalScaling().MinReplicas) || - actualPodsLength > int(component.GetHorizontalScaling().MaxReplicas) + reconciledTime := rd.Status.Reconciled + if reconciledTime.IsZero() || restartedTime.After(reconciledTime.Time) { + return true } - return actualPodsLength < operatordeployment.DefaultReplicas || - actualPodsLength > int(component.GetHorizontalScaling().MaxReplicas) + return false } func runningReplicaIsOutdated(component radixv1.RadixCommonDeployComponent, actualPods []corev1.Pod) bool { diff --git a/api/utils/predicate/kubernetes.go b/api/utils/predicate/kubernetes.go index cc3bbb3c..766900b9 100644 --- a/api/utils/predicate/kubernetes.go +++ b/api/utils/predicate/kubernetes.go @@ -26,6 +26,13 @@ func IsPodForComponent(appName, componentName string) func(corev1.Pod) bool { } } +func IsDeploymentForComponent(appName, componentName string) func(appsv1.Deployment) bool { + selector := labelselector.ForComponent(appName, componentName).AsSelector() + return func(deployment appsv1.Deployment) bool { + return selector.Matches(labels.Set(deployment.Labels)) + } +} + func IsPodForAuxComponent(appName, componentName, auxType string) func(corev1.Pod) bool { selector := labelselector.ForAuxiliaryResource(appName, componentName, auxType).AsSelector() return func(pod corev1.Pod) bool { diff --git a/go.mod b/go.mod index 1f023f49..04130638 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ toolchain go1.22.5 require ( github.com/cert-manager/cert-manager v1.15.0 - github.com/equinor/radix-common v1.9.3 + github.com/equinor/radix-common v1.9.4 github.com/equinor/radix-job-scheduler v1.10.2 github.com/equinor/radix-operator v1.57.19-0.20240826111516-e94c0a610d2f github.com/evanphx/json-patch/v5 v5.9.0 diff --git a/go.sum b/go.sum index dec098bc..e0681859 100644 --- a/go.sum +++ b/go.sum @@ -85,6 +85,8 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/equinor/radix-common v1.9.3 h1:dLKFzYy8/XyEG9Zygi0rMWIYGCddai/ILwUqjBiGYxQ= github.com/equinor/radix-common v1.9.3/go.mod h1:+g0Wj0D40zz29DjNkYKVmCVeYy4OsFWKI7Qi9rA6kpY= +github.com/equinor/radix-common v1.9.4 h1:ErSnB2tqlRwaQuQdaA0qzsReDtHDgubcvqRO098ncEw= +github.com/equinor/radix-common v1.9.4/go.mod h1:+g0Wj0D40zz29DjNkYKVmCVeYy4OsFWKI7Qi9rA6kpY= github.com/equinor/radix-job-scheduler v1.10.2 h1:Ea/gmSQjVdomC3XzkqJdR1TGQ9Bvq8ZJOKUfwYY3Ask= github.com/equinor/radix-job-scheduler v1.10.2/go.mod h1:cnVXZ09D0rAPTZrcgWynL/txMQnpYmPSPyzfKfTYlec= github.com/equinor/radix-operator v1.57.19-0.20240826111516-e94c0a610d2f h1:Fe1dguEAN9wUWN8Hqhw3Ej4MGsG39hc45zDA7lHFCZ8= From c62637eb852f9978f45653ef92dd9dd5d87e94e3 Mon Sep 17 00:00:00 2001 From: Richard87 Date: Tue, 27 Aug 2024 15:27:54 +0200 Subject: [PATCH 03/33] make readable --- api/models/component.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/models/component.go b/api/models/component.go index a5c74f18..33539a18 100644 --- a/api/models/component.go +++ b/api/models/component.go @@ -247,7 +247,7 @@ func getComponentStatus(component radixv1.RadixCommonDeployComponent, kd *appsv1 replicas = pointers.Val(kd.Spec.Replicas) } - if replicas == 0 && component.GetType() == radixv1.RadixComponentTypeJob { + if component.GetType() == radixv1.RadixComponentTypeJob && replicas == 0 { return deploymentModels.StoppedComponent } From c8911ab1bd8dfe169cc836a46ee79fb4edc9798d Mon Sep 17 00:00:00 2001 From: Richard87 Date: Tue, 27 Aug 2024 15:30:47 +0200 Subject: [PATCH 04/33] handle missing k8s deployment --- api/models/component.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/api/models/component.go b/api/models/component.go index 33539a18..ed41dfa0 100644 --- a/api/models/component.go +++ b/api/models/component.go @@ -240,12 +240,12 @@ func certificateRequestConditionReady(condition cmv1.CertificateRequestCondition } func getComponentStatus(component radixv1.RadixCommonDeployComponent, kd *appsv1.Deployment, rd *radixv1.RadixDeployment, pods []corev1.Pod) deploymentModels.ComponentStatus { - var replicasUnavailable, replicasReady, replicas int32 - if kd != nil { - replicasUnavailable = kd.Status.UnavailableReplicas - replicasReady = kd.Status.ReadyReplicas - replicas = pointers.Val(kd.Spec.Replicas) + if kd == nil { + return deploymentModels.ComponentReconciling } + replicasUnavailable := kd.Status.UnavailableReplicas + replicasReady := kd.Status.ReadyReplicas + replicas := pointers.Val(kd.Spec.Replicas) if component.GetType() == radixv1.RadixComponentTypeJob && replicas == 0 { return deploymentModels.StoppedComponent From feabafc761886df8f4c1125f2ad89772e933c769 Mon Sep 17 00:00:00 2001 From: Richard87 Date: Tue, 27 Aug 2024 15:36:38 +0200 Subject: [PATCH 05/33] simplify code --- api/models/component.go | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/api/models/component.go b/api/models/component.go index ed41dfa0..d6d1b121 100644 --- a/api/models/component.go +++ b/api/models/component.go @@ -273,11 +273,8 @@ func getComponentStatus(component radixv1.RadixCommonDeployComponent, kd *appsv1 func isComponentManuallyStopped(component radixv1.RadixCommonDeployComponent) bool { override := component.GetReplicasOverride() - if override == nil { - return false - } - return *override == 0 + return override != nil && *override == 0 } func isCopmonentRestarting(component radixv1.RadixCommonDeployComponent, rd *radixv1.RadixDeployment) bool { From dac015baed7861e9b9e30cabd7874507576b4a78 Mon Sep 17 00:00:00 2001 From: Richard87 Date: Wed, 28 Aug 2024 09:45:26 +0200 Subject: [PATCH 06/33] use component pods instead of all pods --- api/models/component.go | 32 ++++++++++++++++++++++++-------- go.mod | 2 +- go.sum | 4 ++++ 3 files changed, 29 insertions(+), 9 deletions(-) diff --git a/api/models/component.go b/api/models/component.go index d6d1b121..178f5733 100644 --- a/api/models/component.go +++ b/api/models/component.go @@ -16,6 +16,7 @@ import ( "github.com/equinor/radix-common/utils/pointers" "github.com/equinor/radix-common/utils/slice" operatordefaults "github.com/equinor/radix-operator/pkg/apis/defaults" + "github.com/equinor/radix-operator/pkg/apis/kube" radixv1 "github.com/equinor/radix-operator/pkg/apis/radix/v1" operatorutils "github.com/equinor/radix-operator/pkg/apis/utils" radixlabels "github.com/equinor/radix-operator/pkg/apis/utils/labels" @@ -73,7 +74,7 @@ func buildComponent( builder.WithPodNames(slice.Map(componentPods, func(pod corev1.Pod) string { return pod.Name })) builder.WithRadixEnvironmentVariables(getRadixEnvironmentVariables(componentPods)) builder.WithReplicaSummaryList(BuildReplicaSummaryList(componentPods, lastEventWarnings)) - builder.WithStatus(getComponentStatus(radixComponent, kd, rd, podList)) + builder.WithStatus(getComponentStatus(radixComponent, kd, rd, componentPods)) builder.WithAuxiliaryResource(getAuxiliaryResources(ra.Name, radixComponent, deploymentList, podList, lastEventWarnings)) } @@ -264,7 +265,7 @@ func getComponentStatus(component radixv1.RadixCommonDeployComponent, kd *appsv1 return deploymentModels.ComponentReconciling } - if runningReplicaIsOutdated(component, pods) { + if runningReplicaIsOutdated(component, pods, rd, kd) { return deploymentModels.ComponentOutdated } @@ -294,10 +295,10 @@ func isCopmonentRestarting(component radixv1.RadixCommonDeployComponent, rd *rad return false } -func runningReplicaIsOutdated(component radixv1.RadixCommonDeployComponent, actualPods []corev1.Pod) bool { +func runningReplicaIsOutdated(component radixv1.RadixCommonDeployComponent, actualPods []corev1.Pod, rd *radixv1.RadixDeployment, kd *appsv1.Deployment) bool { switch component.GetType() { case radixv1.RadixComponentTypeComponent: - return runningComponentReplicaIsOutdated(component, actualPods) + return runningComponentReplicaIsOutdated(component, actualPods, rd, kd) case radixv1.RadixComponentTypeJob: return false default: @@ -305,9 +306,8 @@ func runningReplicaIsOutdated(component radixv1.RadixCommonDeployComponent, actu } } -func runningComponentReplicaIsOutdated(component radixv1.RadixCommonDeployComponent, actualPods []corev1.Pod) bool { +func runningComponentReplicaIsOutdated(component radixv1.RadixCommonDeployComponent, actualPods []corev1.Pod, rd *radixv1.RadixDeployment, kd *appsv1.Deployment) bool { // Check if running component's image is not the same as active deployment image tag and that active rd image is equal to 'starting' component image tag - componentIsInconsistent := false for _, pod := range actualPods { if pod.DeletionTimestamp != nil { // Pod is in termination phase @@ -316,12 +316,28 @@ func runningComponentReplicaIsOutdated(component radixv1.RadixCommonDeployCompon for _, container := range pod.Spec.Containers { if container.Image != component.GetImage() { // Container is running an outdated image - componentIsInconsistent = true + return true } } } - return componentIsInconsistent + kdOwner, ok := slice.FindFirst(kd.ObjectMeta.OwnerReferences, func(o metav1.OwnerReference) bool { + return o.Controller != nil && *o.Controller + }) + if !ok || kdOwner.Kind != radixv1.KindRadixDeployment || kdOwner.Name != rd.Name { + // Deployments owner is not correct + return true + } + + observedGeneration, ok := kd.Annotations[kube.RadixDeploymentObservedGeneration] + if !ok || observedGeneration != strconv.Itoa(int(rd.ObjectMeta.Generation)) { + // k8s Deployment version doesn't match Radix Deployments generation + return true + } + + // TODO: Annotate and check configmap and secrets + + return false } func getRadixEnvironmentVariables(pods []corev1.Pod) map[string]string { diff --git a/go.mod b/go.mod index 04130638..58c0330d 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,7 @@ require ( github.com/cert-manager/cert-manager v1.15.0 github.com/equinor/radix-common v1.9.4 github.com/equinor/radix-job-scheduler v1.10.2 - github.com/equinor/radix-operator v1.57.19-0.20240826111516-e94c0a610d2f + github.com/equinor/radix-operator v1.57.19-0.20240828060133-10802ffb3c0e github.com/evanphx/json-patch/v5 v5.9.0 github.com/felixge/httpsnoop v1.0.4 github.com/golang-jwt/jwt/v5 v5.2.1 diff --git a/go.sum b/go.sum index e0681859..f6354f1e 100644 --- a/go.sum +++ b/go.sum @@ -91,6 +91,10 @@ github.com/equinor/radix-job-scheduler v1.10.2 h1:Ea/gmSQjVdomC3XzkqJdR1TGQ9Bvq8 github.com/equinor/radix-job-scheduler v1.10.2/go.mod h1:cnVXZ09D0rAPTZrcgWynL/txMQnpYmPSPyzfKfTYlec= github.com/equinor/radix-operator v1.57.19-0.20240826111516-e94c0a610d2f h1:Fe1dguEAN9wUWN8Hqhw3Ej4MGsG39hc45zDA7lHFCZ8= github.com/equinor/radix-operator v1.57.19-0.20240826111516-e94c0a610d2f/go.mod h1:zCdAiP/wxyvlUO4qGoJuLW3O+ZSt9kTyHMnjmsR3fCU= +github.com/equinor/radix-operator v1.57.19-0.20240828060051-b25d571e8fd8 h1:OguwrUSQylvRtIp6NxyoLz3S+sW/y+JiLeXHfn/8gpU= +github.com/equinor/radix-operator v1.57.19-0.20240828060051-b25d571e8fd8/go.mod h1:zCdAiP/wxyvlUO4qGoJuLW3O+ZSt9kTyHMnjmsR3fCU= +github.com/equinor/radix-operator v1.57.19-0.20240828060133-10802ffb3c0e h1:1ZPVPWIKC2xbt5Xh9kQb5RmyTabQlsRXB92QEZQE9yE= +github.com/equinor/radix-operator v1.57.19-0.20240828060133-10802ffb3c0e/go.mod h1:zCdAiP/wxyvlUO4qGoJuLW3O+ZSt9kTyHMnjmsR3fCU= github.com/evanphx/json-patch v5.9.0+incompatible h1:fBXyNpNMuTTDdquAq/uisOr2lShz4oaXpDTX2bLe7ls= github.com/evanphx/json-patch v5.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch/v5 v5.9.0 h1:kcBlZQbplgElYIlo/n1hJbls2z/1awpXxpRi0/FOJfg= From 14b1360dc0f92452416dd069771b801c2a867ead Mon Sep 17 00:00:00 2001 From: Richard87 Date: Wed, 28 Aug 2024 15:38:31 +0200 Subject: [PATCH 07/33] Deprecate and add reset-scale endpoints --- .../models/component_deployment.go | 5 +- api/environments/component_handler.go | 65 ++++--- api/environments/environment_controller.go | 175 +++++++++++++++++- .../environment_controller_test.go | 2 +- api/environments/environment_handler.go | 14 +- api/environments/models/environment_errors.go | 6 +- swaggerui/html/swagger.json | 117 +++++++++++- 7 files changed, 330 insertions(+), 54 deletions(-) diff --git a/api/deployments/models/component_deployment.go b/api/deployments/models/component_deployment.go index fb4ebbb3..2fca84e0 100644 --- a/api/deployments/models/component_deployment.go +++ b/api/deployments/models/component_deployment.go @@ -88,7 +88,10 @@ type Component struct { // Set if manuall control of replicas is in place. null means automatic controll, 0 means stopped and >= 1 is manually scaled. // // required: false - // example: nil + // example: 5 + // required: false + // Extensions: + // x-nullable: true ReplicasOverride *int `json:"replicasOverride,omitempty"` // HorizontalScaling defines horizontal scaling summary for this component diff --git a/api/environments/component_handler.go b/api/environments/component_handler.go index b1a7c649..3ba884a1 100644 --- a/api/environments/component_handler.go +++ b/api/environments/component_handler.go @@ -26,10 +26,29 @@ const ( maxScaleReplicas = 20 ) -// StopComponent Stops a component -func (eh EnvironmentHandler) StopComponent(ctx context.Context, appName, envName, componentName string, ignoreComponentStatusError bool) error { +// ScaleComponent Scale a component replicas +func (eh EnvironmentHandler) ScaleComponent(ctx context.Context, appName, envName, componentName string, replicas int) error { + if replicas < 0 { + return environmentModels.CannotScaleComponentToNegativeReplicas(appName, envName, componentName) + } + if replicas > maxScaleReplicas { + return environmentModels.CannotScaleComponentToMoreThanMaxReplicas(appName, envName, componentName, maxScaleReplicas) + } + log.Ctx(ctx).Info().Msgf("Scaling component %s, %s to %d replicas", componentName, appName, replicas) + updater, err := eh.getRadixCommonComponentUpdater(ctx, appName, envName, componentName) + if err != nil { + return err + } + componentStatus := updater.getComponentStatus() + if !radixutils.ContainsString(validaStatusesToScaleComponent, componentStatus) { + return environmentModels.CannotScaleComponent(appName, envName, componentName, componentStatus) + } + return eh.patchRadixDeploymentWithReplicas(ctx, updater, &replicas) +} - log.Ctx(ctx).Info().Msgf("Stopping component %s, %s", componentName, appName) +// ResetScaledComponent Starts a component +func (eh EnvironmentHandler) ResetScaledComponent(ctx context.Context, appName, envName, componentName string, ignoreComponentStatusError bool) error { + log.Ctx(ctx).Info().Msgf("Resetting manuall scaled component %s, %s", componentName, appName) updater, err := eh.getRadixCommonComponentUpdater(ctx, appName, envName, componentName) if err != nil { return err @@ -37,19 +56,19 @@ func (eh EnvironmentHandler) StopComponent(ctx context.Context, appName, envName if updater.getComponentToPatch().GetType() == v1.RadixComponentTypeJob { return environmentModels.JobComponentCanOnlyBeRestarted() } - componentStatus := updater.getComponentStatus() - if strings.EqualFold(componentStatus, deploymentModels.StoppedComponent.String()) { + if updater.getComponentToPatch().GetReplicasOverride() == nil { if ignoreComponentStatusError { return nil } - return environmentModels.CannotStopComponent(appName, componentName, componentStatus) + return environmentModels.CannotResetScaledComponent(appName, componentName) } - return eh.patchRadixDeploymentWithReplicas(ctx, updater, pointers.Ptr(0)) + return eh.patchRadixDeploymentWithReplicas(ctx, updater, nil) } -// StartComponent Starts a component -func (eh EnvironmentHandler) StartComponent(ctx context.Context, appName, envName, componentName string, ignoreComponentStatusError bool) error { - log.Ctx(ctx).Info().Msgf("Starting component %s, %s", componentName, appName) +// StopComponent Stops a component +func (eh EnvironmentHandler) StopComponent(ctx context.Context, appName, envName, componentName string, ignoreComponentStatusError bool) error { + + log.Ctx(ctx).Info().Msgf("Stopping component %s, %s", componentName, appName) updater, err := eh.getRadixCommonComponentUpdater(ctx, appName, envName, componentName) if err != nil { return err @@ -58,13 +77,13 @@ func (eh EnvironmentHandler) StartComponent(ctx context.Context, appName, envNam return environmentModels.JobComponentCanOnlyBeRestarted() } componentStatus := updater.getComponentStatus() - if !strings.EqualFold(componentStatus, deploymentModels.StoppedComponent.String()) { + if strings.EqualFold(componentStatus, deploymentModels.StoppedComponent.String()) { if ignoreComponentStatusError { return nil } - return environmentModels.CannotStartComponent(appName, componentName, componentStatus) + return environmentModels.CannotStopComponent(appName, componentName, componentStatus) } - return eh.patchRadixDeploymentWithReplicas(ctx, updater, nil) + return eh.patchRadixDeploymentWithReplicas(ctx, updater, pointers.Ptr(0)) } // RestartComponent Restarts a component @@ -84,26 +103,6 @@ func (eh EnvironmentHandler) RestartComponent(ctx context.Context, appName, envN return eh.patchRadixDeploymentWithTimestampInEnvVar(ctx, updater, defaults.RadixRestartEnvironmentVariable) } -// ScaleComponent Scale a component replicas -func (eh EnvironmentHandler) ScaleComponent(ctx context.Context, appName, envName, componentName string, replicas int) error { - if replicas < 0 { - return environmentModels.CannotScaleComponentToNegativeReplicas(appName, envName, componentName) - } - if replicas > maxScaleReplicas { - return environmentModels.CannotScaleComponentToMoreThanMaxReplicas(appName, envName, componentName, maxScaleReplicas) - } - log.Ctx(ctx).Info().Msgf("Scaling component %s, %s to %d replicas", componentName, appName, replicas) - updater, err := eh.getRadixCommonComponentUpdater(ctx, appName, envName, componentName) - if err != nil { - return err - } - componentStatus := updater.getComponentStatus() - if !radixutils.ContainsString(validaStatusesToScaleComponent, componentStatus) { - return environmentModels.CannotScaleComponent(appName, envName, componentName, componentStatus) - } - return eh.patchRadixDeploymentWithReplicas(ctx, updater, &replicas) -} - // RestartComponentAuxiliaryResource Restarts a component's auxiliary resource func (eh EnvironmentHandler) RestartComponentAuxiliaryResource(ctx context.Context, appName, envName, componentName, auxType string) error { log.Ctx(ctx).Info().Msgf("Restarting auxiliary resource %s for component %s, %s", auxType, componentName, appName) diff --git a/api/environments/environment_controller.go b/api/environments/environment_controller.go index a20626a0..6c0abf53 100644 --- a/api/environments/environment_controller.go +++ b/api/environments/environment_controller.go @@ -71,7 +71,12 @@ func (c *environmentController) GetRoutes() models.Routes { models.Route{ Path: rootPath + "/environments/{envName}/components/{componentName}/start", Method: "POST", - HandlerFunc: c.StartComponent, + HandlerFunc: c.ResetScaledComponent, + }, + models.Route{ + Path: rootPath + "/environments/{envName}/components/{componentName}/reset-scale", + Method: "POST", + HandlerFunc: c.ResetScaledComponent, }, models.Route{ Path: rootPath + "/environments/{envName}/components/{componentName}/restart", @@ -91,7 +96,12 @@ func (c *environmentController) GetRoutes() models.Routes { models.Route{ Path: rootPath + "/environments/{envName}/start", Method: "POST", - HandlerFunc: c.StartEnvironment, + HandlerFunc: c.ResetManuallyStoppedComponentsInEnvironment, + }, + models.Route{ + Path: rootPath + "/environments/{envName}/reset-scale", + Method: "POST", + HandlerFunc: c.ResetManuallyStoppedComponentsInEnvironment, }, models.Route{ Path: rootPath + "/environments/{envName}/restart", @@ -106,7 +116,12 @@ func (c *environmentController) GetRoutes() models.Routes { models.Route{ Path: rootPath + "/start", Method: "POST", - HandlerFunc: c.StartApplication, + HandlerFunc: c.ResetManuallyScaledComponentsInApplication, + }, + models.Route{ + Path: rootPath + "/reset-scale", + Method: "POST", + HandlerFunc: c.ResetManuallyScaledComponentsInApplication, }, models.Route{ Path: rootPath + "/restart", @@ -571,11 +586,65 @@ func (c *environmentController) StopComponent(accounts models.Accounts, w http.R c.JSONResponse(w, r, "Success") } +// ResetScaledComponent reset manually scaled component and resumes normal operation +func (c *environmentController) ResetScaledComponent(accounts models.Accounts, w http.ResponseWriter, r *http.Request) { + // swagger:operation POST /applications/{appName}/environments/{envName}/components/{componentName}/reset-scale component resetScaledComponent + // --- + // summary: Reset manually scaled component and resumes normal operation + // parameters: + // - name: appName + // in: path + // description: Name of application + // type: string + // required: true + // - name: envName + // in: path + // description: Name of environment + // type: string + // required: true + // - name: componentName + // in: path + // description: Name of component + // type: string + // required: true + // - name: Impersonate-User + // in: header + // description: Works only with custom setup of cluster. Allow impersonation of test users (Required if Impersonate-Group is set) + // type: string + // required: false + // - name: Impersonate-Group + // in: header + // description: Works only with custom setup of cluster. Allow impersonation of a comma-seperated list of test groups (Required if Impersonate-User is set) + // type: string + // required: false + // responses: + // "200": + // description: "Component started ok" + // "401": + // description: "Unauthorized" + // "404": + // description: "Not found" + appName := mux.Vars(r)["appName"] + envName := mux.Vars(r)["envName"] + componentName := mux.Vars(r)["componentName"] + + environmentHandler := c.environmentHandlerFactory(accounts) + err := environmentHandler.ResetScaledComponent(r.Context(), appName, envName, componentName, false) + + if err != nil { + c.ErrorResponse(w, r, err) + return + } + + c.JSONResponse(w, r, "Success") +} + // StartComponent Starts job func (c *environmentController) StartComponent(accounts models.Accounts, w http.ResponseWriter, r *http.Request) { // swagger:operation POST /applications/{appName}/environments/{envName}/components/{componentName}/start component startComponent // --- - // summary: Start component + // summary: Deprecated Start component. Use reset-scale instead. This does the same thing, but naming is wrong. This endpoint will be removed after 1. september 2025. + // deprecated: true // parameters: // - name: appName // in: path @@ -614,7 +683,7 @@ func (c *environmentController) StartComponent(accounts models.Accounts, w http. componentName := mux.Vars(r)["componentName"] environmentHandler := c.environmentHandlerFactory(accounts) - err := environmentHandler.StartComponent(r.Context(), appName, envName, componentName, false) + err := environmentHandler.ResetScaledComponent(r.Context(), appName, envName, componentName, false) if err != nil { c.ErrorResponse(w, r, err) @@ -728,11 +797,59 @@ func (c *environmentController) StopEnvironment(accounts models.Accounts, w http c.JSONResponse(w, r, "Success") } +// ResetManuallyStoppedComponentsInEnvironment Reset all manually scaled component and resumes normal operation in environment +func (c *environmentController) ResetManuallyStoppedComponentsInEnvironment(accounts models.Accounts, w http.ResponseWriter, r *http.Request) { + // swagger:operation POST /applications/{appName}/environments/{envName}/reset-scale environment resetManuallyScaledComponentsInEnvironment + // --- + // summary: Reset all manually scaled component and resumes normal operation in environment + // parameters: + // - name: appName + // in: path + // description: Name of application + // type: string + // required: true + // - name: envName + // in: path + // description: Name of environment + // type: string + // required: true + // - name: Impersonate-User + // in: header + // description: Works only with custom setup of cluster. Allow impersonation of test users (Required if Impersonate-Group is set) + // type: string + // required: false + // - name: Impersonate-Group + // in: header + // description: Works only with custom setup of cluster. Allow impersonation of a comma-seperated list of test groups (Required if Impersonate-User is set) + // type: string + // required: false + // responses: + // "200": + // description: "Environment started ok" + // "401": + // description: "Unauthorized" + // "404": + // description: "Not found" + appName := mux.Vars(r)["appName"] + envName := mux.Vars(r)["envName"] + + environmentHandler := c.environmentHandlerFactory(accounts) + err := environmentHandler.ResetManuallyStoppedComponentsInEnvironment(r.Context(), appName, envName) + + if err != nil { + c.ErrorResponse(w, r, err) + return + } + + c.JSONResponse(w, r, "Success") +} + // StartEnvironment Starts all components in the environment func (c *environmentController) StartEnvironment(accounts models.Accounts, w http.ResponseWriter, r *http.Request) { // swagger:operation POST /applications/{appName}/environments/{envName}/start environment startEnvironment // --- - // summary: Start all components in the environment + // summary: Deprecated. Use reset-scale instead that does the same thing, but with better naming. This method will be removed after 1. september 2025. + // deprecated: true // parameters: // - name: appName // in: path @@ -765,7 +882,7 @@ func (c *environmentController) StartEnvironment(accounts models.Accounts, w htt envName := mux.Vars(r)["envName"] environmentHandler := c.environmentHandlerFactory(accounts) - err := environmentHandler.StartEnvironment(r.Context(), appName, envName) + err := environmentHandler.ResetManuallyStoppedComponentsInEnvironment(r.Context(), appName, envName) if err != nil { c.ErrorResponse(w, r, err) @@ -869,11 +986,53 @@ func (c *environmentController) StopApplication(accounts models.Accounts, w http c.JSONResponse(w, r, "Success") } +// ResetManuallyScaledComponentsInApplication Resets and resumes normal opperation for all manually stopped components in all environments of the application +func (c *environmentController) ResetManuallyScaledComponentsInApplication(accounts models.Accounts, w http.ResponseWriter, r *http.Request) { + // swagger:operation POST /applications/{appName}/reset-scale application resetManuallyScaledComponentsInApplication + // --- + // summary: Resets and resumes normal opperation for all manually stopped components in all environments of the application + // parameters: + // - name: appName + // in: path + // description: Name of application + // type: string + // required: true + // - name: Impersonate-User + // in: header + // description: Works only with custom setup of cluster. Allow impersonation of test users (Required if Impersonate-Group is set) + // type: string + // required: false + // - name: Impersonate-Group + // in: header + // description: Works only with custom setup of cluster. Allow impersonation of a comma-seperated list of test groups (Required if Impersonate-User is set) + // type: string + // required: false + // responses: + // "200": + // description: "Application started ok" + // "401": + // description: "Unauthorized" + // "404": + // description: "Not found" + appName := mux.Vars(r)["appName"] + + environmentHandler := c.environmentHandlerFactory(accounts) + err := environmentHandler.StartApplication(r.Context(), appName) + + if err != nil { + c.ErrorResponse(w, r, err) + return + } + + c.JSONResponse(w, r, "Success") +} + // StartApplication Starts all components in all environments of the application func (c *environmentController) StartApplication(accounts models.Accounts, w http.ResponseWriter, r *http.Request) { // swagger:operation POST /applications/{appName}/start application startApplication // --- - // summary: Start all components in all environments of the application + // summary: Deprecated. Use reset scale that does the same thing instead. This will be removed after 1. september 2025. + // deprecated: true // parameters: // - name: appName // in: path diff --git a/api/environments/environment_controller_test.go b/api/environments/environment_controller_test.go index 0e97d1b9..d94a0bc6 100644 --- a/api/environments/environment_controller_test.go +++ b/api/environments/environment_controller_test.go @@ -546,7 +546,7 @@ func TestStartComponent_ApplicationWithDeployment_EnvironmentConsistent(t *testi assert.Equal(t, http.StatusBadRequest, response.Code) errorResponse, _ := controllertest.GetErrorResponse(response) - expectedError := environmentModels.CannotStartComponent(anyAppName, stoppedComponent2, deploymentModels.ComponentReconciling.String()) + expectedError := environmentModels.CannotResetScaledComponent(anyAppName, stoppedComponent2) assert.Equal(t, (expectedError.(*radixhttp.Error)).Message, errorResponse.Message) updatedRd, _ := radixclient.RadixV1().RadixDeployments(rd.GetNamespace()).Get(context.Background(), rd.GetName(), metav1.GetOptions{}) diff --git a/api/environments/environment_handler.go b/api/environments/environment_handler.go index 100d8e74..929e7e64 100644 --- a/api/environments/environment_handler.go +++ b/api/environments/environment_handler.go @@ -319,8 +319,8 @@ func (eh EnvironmentHandler) StopEnvironment(ctx context.Context, appName, envNa return nil } -// StartEnvironment Starts all components in the environment -func (eh EnvironmentHandler) StartEnvironment(ctx context.Context, appName, envName string) error { +// ResetManuallyStoppedComponentsInEnvironment Starts all components in the environment +func (eh EnvironmentHandler) ResetManuallyStoppedComponentsInEnvironment(ctx context.Context, appName, envName string) error { _, radixDeployment, err := eh.getRadixDeployment(ctx, appName, envName) if err != nil { return err @@ -328,9 +328,11 @@ func (eh EnvironmentHandler) StartEnvironment(ctx context.Context, appName, envN log.Ctx(ctx).Info().Msgf("Starting components in environment %s, %s", envName, appName) for _, deployComponent := range radixDeployment.Spec.Components { - err := eh.StartComponent(ctx, appName, envName, deployComponent.GetName(), true) - if err != nil { - return err + if overide := deployComponent.GetReplicasOverride(); overide != nil && *overide == 0 { + err := eh.ResetScaledComponent(ctx, appName, envName, deployComponent.GetName(), true) + if err != nil { + return err + } } } return nil @@ -377,7 +379,7 @@ func (eh EnvironmentHandler) StartApplication(ctx context.Context, appName strin } log.Ctx(ctx).Info().Msgf("Starting components in the application %s", appName) for _, environmentName := range environmentNames { - err := eh.StartEnvironment(ctx, appName, environmentName) + err := eh.ResetManuallyStoppedComponentsInEnvironment(ctx, appName, environmentName) if err != nil { return err } diff --git a/api/environments/models/environment_errors.go b/api/environments/models/environment_errors.go index 19bca05d..04bee012 100644 --- a/api/environments/models/environment_errors.go +++ b/api/environments/models/environment_errors.go @@ -32,9 +32,9 @@ func CannotStopComponent(appName, componentName, state string) error { return radixhttp.ValidationError("Radix Application Component", fmt.Sprintf("Component %s for app %s cannot be stopped when in %s state", componentName, appName, strings.ToLower(state))) } -// CannotStartComponent Component cannot be started -func CannotStartComponent(appName, componentName, state string) error { - return radixhttp.ValidationError("Radix Application Component", fmt.Sprintf("Component %s for app %s cannot be started when in %s state", componentName, appName, strings.ToLower(state))) +// CannotResetScaledComponent Component cannot be started +func CannotResetScaledComponent(appName, componentName string) error { + return radixhttp.ValidationError("Radix Application Component", fmt.Sprintf("Component %s for app %s cannot be reset when not manually scaled", componentName, appName)) } // CannotRestartComponent Component cannot be restarted diff --git a/swaggerui/html/swagger.json b/swaggerui/html/swagger.json index 563c8e7a..2c6007e9 100644 --- a/swaggerui/html/swagger.json +++ b/swaggerui/html/swagger.json @@ -2070,6 +2070,61 @@ } } }, + "/applications/{appName}/environments/{envName}/components/{componentName}/reset-scale": { + "post": { + "tags": [ + "component" + ], + "summary": "Reset manually scaled component and resumes normal operation", + "operationId": "resetScaledComponent", + "parameters": [ + { + "type": "string", + "description": "Name of application", + "name": "appName", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "Name of environment", + "name": "envName", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "Name of component", + "name": "componentName", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "Works only with custom setup of cluster. Allow impersonation of test users (Required if Impersonate-Group is set)", + "name": "Impersonate-User", + "in": "header" + }, + { + "type": "string", + "description": "Works only with custom setup of cluster. Allow impersonation of a comma-seperated list of test groups (Required if Impersonate-User is set)", + "name": "Impersonate-Group", + "in": "header" + } + ], + "responses": { + "200": { + "description": "Component started ok" + }, + "401": { + "description": "Unauthorized" + }, + "404": { + "description": "Not found" + } + } + } + }, "/applications/{appName}/environments/{envName}/components/{componentName}/restart": { "post": { "tags": [ @@ -2367,8 +2422,9 @@ "tags": [ "component" ], - "summary": "Start component", + "summary": "Deprecated Start component. Use Reset scaled component instead. This does the same thing, but naming is wrong. This endpoint will be removed after 1. september 2025.", "operationId": "startComponent", + "deprecated": true, "parameters": [ { "type": "string", @@ -3599,6 +3655,54 @@ } } }, + "/applications/{appName}/environments/{envName}/reset-scale": { + "post": { + "tags": [ + "environment" + ], + "summary": "Reset all manually scaled component and resumes normal operation in environment", + "operationId": "resetManuallyScaledComponentsInEnvironment", + "parameters": [ + { + "type": "string", + "description": "Name of application", + "name": "appName", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "Name of environment", + "name": "envName", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "Works only with custom setup of cluster. Allow impersonation of test users (Required if Impersonate-Group is set)", + "name": "Impersonate-User", + "in": "header" + }, + { + "type": "string", + "description": "Works only with custom setup of cluster. Allow impersonation of a comma-seperated list of test groups (Required if Impersonate-User is set)", + "name": "Impersonate-Group", + "in": "header" + } + ], + "responses": { + "200": { + "description": "Environment started ok" + }, + "401": { + "description": "Unauthorized" + }, + "404": { + "description": "Not found" + } + } + } + }, "/applications/{appName}/environments/{envName}/restart": { "post": { "tags": [ @@ -3655,8 +3759,9 @@ "tags": [ "environment" ], - "summary": "Start all components in the environment", + "summary": "Deprecated. Use ResetManuallyStoppedComponentsInEnvironment that does the same thing, but with better naming. This method will be removed after 1. september 2025.", "operationId": "startEnvironment", + "deprecated": true, "parameters": [ { "type": "string", @@ -5634,6 +5739,14 @@ "server-78fc8857c4-asfa2" ] }, + "replicasOverride": { + "description": "Set if manuall control of replicas is in place. null means automatic controll, 0 means stopped and \u003e= 1 is manually scaled.", + "type": "integer", + "format": "int64", + "x-go-name": "ReplicasOverride", + "x-nullable": true, + "example": 5 + }, "resources": { "$ref": "#/definitions/ResourceRequirements" }, From 715228d7bd485d38db1bc22f3e254092b422ac99 Mon Sep 17 00:00:00 2001 From: Richard87 Date: Thu, 29 Aug 2024 15:10:32 +0200 Subject: [PATCH 08/33] refactor component status --- api/deployments/deployment_handler.go | 17 +- .../mock/deployment_handler_mock.go | 16 ++ api/deployments/models/component_status.go | 68 +++++++- api/environments/component_handler.go | 7 +- api/environments/component_spec.go | 151 ++---------------- api/environments/environment_handler.go | 15 +- api/environments/utils.go | 24 --- api/kubequery/deployment.go | 17 ++ api/kubequery/radixdeployment.go | 28 ++++ api/models/component.go | 107 +------------ api/utils/owner/verify_generation.go | 22 +++ 11 files changed, 185 insertions(+), 287 deletions(-) delete mode 100644 api/environments/utils.go create mode 100644 api/utils/owner/verify_generation.go diff --git a/api/deployments/deployment_handler.go b/api/deployments/deployment_handler.go index 9d4a906d..8823a2a2 100644 --- a/api/deployments/deployment_handler.go +++ b/api/deployments/deployment_handler.go @@ -13,7 +13,7 @@ import ( "github.com/equinor/radix-api/models" "github.com/equinor/radix-common/utils/slice" "github.com/equinor/radix-operator/pkg/apis/kube" - v1 "github.com/equinor/radix-operator/pkg/apis/radix/v1" + radixv1 "github.com/equinor/radix-operator/pkg/apis/radix/v1" operatorUtils "github.com/equinor/radix-operator/pkg/apis/utils" radixlabels "github.com/equinor/radix-operator/pkg/apis/utils/labels" "k8s.io/apimachinery/pkg/api/errors" @@ -28,7 +28,6 @@ type DeployHandler interface { GetDeploymentsForApplicationEnvironment(ctx context.Context, appName, environment string, latest bool) ([]*deploymentModels.DeploymentSummary, error) GetComponentsForDeploymentName(ctx context.Context, appName, deploymentID string) ([]*deploymentModels.Component, error) GetComponentsForDeployment(ctx context.Context, appName, deploymentName, envName string) ([]*deploymentModels.Component, error) - GetLatestDeploymentForApplicationEnvironment(ctx context.Context, appName, environment string) (*deploymentModels.DeploymentSummary, error) GetDeploymentsForPipelineJob(context.Context, string, string) ([]*deploymentModels.DeploymentSummary, error) GetJobComponentDeployments(context.Context, string, string, string) ([]*deploymentModels.DeploymentItem, error) } @@ -64,6 +63,7 @@ func (deploy *deployHandler) GetLogs(ctx context.Context, appName, podName strin return log, nil } + return nil, deploymentModels.NonExistingPod(appName, podName) } @@ -198,7 +198,7 @@ func (deploy *deployHandler) GetDeploymentWithName(ctx context.Context, appName, return dep, nil } -func (deploy *deployHandler) getRadixDeploymentRadixJob(ctx context.Context, appName string, rd *v1.RadixDeployment) (*v1.RadixJob, error) { +func (deploy *deployHandler) getRadixDeploymentRadixJob(ctx context.Context, appName string, rd *radixv1.RadixDeployment) (*radixv1.RadixJob, error) { jobName := rd.GetLabels()[kube.RadixJobNameLabel] radixJob, err := kubequery.GetRadixJob(ctx, deploy.accounts.UserAccount.RadixClient, appName, jobName) if err != nil { @@ -211,7 +211,6 @@ func (deploy *deployHandler) getRadixDeploymentRadixJob(ctx context.Context, app } func (deploy *deployHandler) getEnvironmentNames(ctx context.Context, appName string) ([]string, error) { - radixlabels.ForApplicationName(appName).AsSelector() labelSelector := radixlabels.ForApplicationName(appName).AsSelector() reList, err := deploy.accounts.ServiceAccount.RadixClient.RadixV1().RadixEnvironments().List(ctx, metav1.ListOptions{LabelSelector: labelSelector.String()}) @@ -219,7 +218,7 @@ func (deploy *deployHandler) getEnvironmentNames(ctx context.Context, appName st return nil, err } - return slice.Map(reList.Items, func(re v1.RadixEnvironment) string { + return slice.Map(reList.Items, func(re radixv1.RadixEnvironment) string { return re.Spec.EnvName }), nil } @@ -239,7 +238,7 @@ func (deploy *deployHandler) getDeployments(ctx context.Context, appName string, rdLabelSelector = rdLabelSelector.Add(*jobNameLabel) } - var radixDeploymentList []v1.RadixDeployment + var radixDeploymentList []radixv1.RadixDeployment namespaces := slice.Map(environments, func(env string) string { return operatorUtils.GetEnvironmentNamespace(appName, env) }) for _, ns := range namespaces { rdList, err := deploy.accounts.UserAccount.RadixClient.RadixV1().RadixDeployments(ns).List(ctx, metav1.ListOptions{LabelSelector: rdLabelSelector.String()}) @@ -250,7 +249,7 @@ func (deploy *deployHandler) getDeployments(ctx context.Context, appName string, } appNamespace := operatorUtils.GetAppNamespace(appName) - radixJobMap := make(map[string]*v1.RadixJob) + radixJobMap := make(map[string]*radixv1.RadixJob) if jobName != "" { radixJob, err := deploy.accounts.UserAccount.RadixClient.RadixV1().RadixJobs(appNamespace).Get(ctx, jobName, metav1.GetOptions{}) @@ -278,7 +277,7 @@ func (deploy *deployHandler) getDeployments(ctx context.Context, appName string, rds := sortRdsByActiveFromDesc(radixDeploymentList) var deploymentSummaries []*deploymentModels.DeploymentSummary for _, rd := range rds { - if latest && rd.Status.Condition == v1.DeploymentInactive { + if latest && rd.Status.Condition == radixv1.DeploymentInactive { continue } @@ -298,7 +297,7 @@ func (deploy *deployHandler) getDeployments(ctx context.Context, appName string, return deploymentSummaries, nil } -func sortRdsByActiveFromDesc(rds []v1.RadixDeployment) []v1.RadixDeployment { +func sortRdsByActiveFromDesc(rds []radixv1.RadixDeployment) []radixv1.RadixDeployment { sort.Slice(rds, func(i, j int) bool { if rds[j].Status.ActiveFrom.IsZero() { return true diff --git a/api/deployments/mock/deployment_handler_mock.go b/api/deployments/mock/deployment_handler_mock.go index 59e36e79..91e1dc5e 100644 --- a/api/deployments/mock/deployment_handler_mock.go +++ b/api/deployments/mock/deployment_handler_mock.go @@ -11,6 +11,7 @@ import ( time "time" models "github.com/equinor/radix-api/api/deployments/models" + v1 "github.com/equinor/radix-operator/pkg/apis/radix/v1" gomock "github.com/golang/mock/gomock" ) @@ -142,6 +143,21 @@ func (mr *MockDeployHandlerMockRecorder) GetLatestDeploymentForApplicationEnviro return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetLatestDeploymentForApplicationEnvironment", reflect.TypeOf((*MockDeployHandler)(nil).GetLatestDeploymentForApplicationEnvironment), ctx, appName, environment) } +// GetLatestRadixDeployment mocks base method. +func (m *MockDeployHandler) GetLatestRadixDeployment(ctx context.Context, appName, environment string) (*v1.RadixDeployment, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetLatestRadixDeployment", ctx, appName, environment) + ret0, _ := ret[0].(*v1.RadixDeployment) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetLatestRadixDeployment indicates an expected call of GetLatestRadixDeployment. +func (mr *MockDeployHandlerMockRecorder) GetLatestRadixDeployment(ctx, appName, environment interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetLatestRadixDeployment", reflect.TypeOf((*MockDeployHandler)(nil).GetLatestRadixDeployment), ctx, appName, environment) +} + // GetLogs mocks base method. func (m *MockDeployHandler) GetLogs(ctx context.Context, appName, podName string, sinceTime *time.Time, logLines *int64, previousLog bool) (io.ReadCloser, error) { m.ctrl.T.Helper() diff --git a/api/deployments/models/component_status.go b/api/deployments/models/component_status.go index e7838382..8b8e6bcd 100644 --- a/api/deployments/models/component_status.go +++ b/api/deployments/models/component_status.go @@ -1,6 +1,17 @@ package models -import appsv1 "k8s.io/api/apps/v1" +import ( + "strings" + + "github.com/equinor/radix-api/api/utils/owner" + commonutils "github.com/equinor/radix-common/utils" + "github.com/equinor/radix-common/utils/pointers" + operatordefaults "github.com/equinor/radix-operator/pkg/apis/defaults" + "github.com/equinor/radix-operator/pkg/apis/kube" + radixv1 "github.com/equinor/radix-operator/pkg/apis/radix/v1" + "github.com/rs/zerolog/log" + appsv1 "k8s.io/api/apps/v1" +) // ComponentStatus Enumeration of the statuses of component type ComponentStatus int @@ -43,3 +54,58 @@ func ComponentStatusFromDeployment(deployment *appsv1.Deployment) ComponentStatu return status } + +func GetComponentStatus(component radixv1.RadixCommonDeployComponent, kd *appsv1.Deployment, rd *radixv1.RadixDeployment) ComponentStatus { + if kd == nil { + return ComponentReconciling + } + replicasUnavailable := kd.Status.UnavailableReplicas + replicasReady := kd.Status.ReadyReplicas + replicas := pointers.Val(kd.Spec.Replicas) + + if component.GetType() == radixv1.RadixComponentTypeJob && replicas == 0 { + return StoppedComponent + } + + if isComponentManuallyStopped(component) && replicas == 0 { + return StoppedComponent + } + + if isCopmonentRestarting(component, rd) { + return ComponentRestarting + } + + // Check if component is scaling up or down + if replicasUnavailable > 0 || replicas < replicasReady { + return ComponentReconciling + } + + if owner.VerifyCorrectObjectGeneration(rd, kd, kube.RadixDeploymentObservedGeneration) { + return ComponentOutdated + } + + return ConsistentComponent +} + +func isComponentManuallyStopped(component radixv1.RadixCommonDeployComponent) bool { + override := component.GetReplicasOverride() + + return override != nil && *override == 0 +} + +func isCopmonentRestarting(component radixv1.RadixCommonDeployComponent, rd *radixv1.RadixDeployment) bool { + restarted := component.GetEnvironmentVariables()[operatordefaults.RadixRestartEnvironmentVariable] + if strings.EqualFold(restarted, "") { + return false + } + restartedTime, err := commonutils.ParseTimestamp(restarted) + if err != nil { + log.Logger.Warn().Err(err).Msgf("unable to parse restarted time %v, component: %s", restarted, component.GetName()) + return false + } + reconciledTime := rd.Status.Reconciled + if reconciledTime.IsZero() || restartedTime.After(reconciledTime.Time) { + return true + } + return false +} diff --git a/api/environments/component_handler.go b/api/environments/component_handler.go index 3ba884a1..20fe0077 100644 --- a/api/environments/component_handler.go +++ b/api/environments/component_handler.go @@ -7,6 +7,7 @@ import ( deploymentModels "github.com/equinor/radix-api/api/deployments/models" environmentModels "github.com/equinor/radix-api/api/environments/models" + "github.com/equinor/radix-api/api/kubequery" "github.com/equinor/radix-api/api/utils/labelselector" radixutils "github.com/equinor/radix-common/utils" "github.com/equinor/radix-common/utils/pointers" @@ -107,12 +108,12 @@ func (eh EnvironmentHandler) RestartComponent(ctx context.Context, appName, envN func (eh EnvironmentHandler) RestartComponentAuxiliaryResource(ctx context.Context, appName, envName, componentName, auxType string) error { log.Ctx(ctx).Info().Msgf("Restarting auxiliary resource %s for component %s, %s", auxType, componentName, appName) - deploySummary, err := eh.deployHandler.GetLatestDeploymentForApplicationEnvironment(ctx, appName, envName) + radixDeployment, err := kubequery.GetLatestRadixDeployment(ctx, eh.accounts.UserAccount.RadixClient, appName, envName) if err != nil { return err } - componentsDto, err := eh.deployHandler.GetComponentsForDeployment(ctx, appName, deploySummary.Name, envName) + componentsDto, err := eh.deployHandler.GetComponentsForDeployment(ctx, appName, radixDeployment.Name, envName) if err != nil { return err } @@ -154,7 +155,7 @@ func canDeploymentBeRestarted(deployment *appsv1.Deployment) bool { return false } - return deploymentModels.ComponentStatusFromDeployment(deployment) == deploymentModels.ConsistentComponent + return deployment.Spec.Replicas == nil || *deployment.Spec.Replicas != 0 } func (eh EnvironmentHandler) patchDeploymentForRestart(ctx context.Context, deployment *appsv1.Deployment) error { diff --git a/api/environments/component_spec.go b/api/environments/component_spec.go index f10488e5..5b2c0f81 100644 --- a/api/environments/component_spec.go +++ b/api/environments/component_spec.go @@ -2,17 +2,14 @@ package environments import ( "context" - "strings" deploymentModels "github.com/equinor/radix-api/api/deployments/models" "github.com/equinor/radix-api/api/kubequery" "github.com/equinor/radix-api/api/models" "github.com/equinor/radix-api/api/utils/event" "github.com/equinor/radix-api/api/utils/labelselector" - radixutils "github.com/equinor/radix-common/utils" "github.com/equinor/radix-common/utils/slice" "github.com/equinor/radix-operator/pkg/apis/defaults" - "github.com/equinor/radix-operator/pkg/apis/deployment" "github.com/equinor/radix-operator/pkg/apis/kube" v1 "github.com/equinor/radix-operator/pkg/apis/radix/v1" crdUtils "github.com/equinor/radix-operator/pkg/apis/utils" @@ -26,17 +23,7 @@ import ( ) // getComponentStateFromSpec Returns a component with the current state -func getComponentStateFromSpec( - ctx context.Context, - kubeClient kubernetes.Interface, - appName string, - deployment *deploymentModels.DeploymentSummary, - deploymentStatus v1.RadixDeployStatus, - environmentConfig v1.RadixCommonEnvironmentConfig, - component v1.RadixCommonDeployComponent, - hpas []autoscalingv2.HorizontalPodAutoscaler, - scaledObjects []v1alpha1.ScaledObject, -) (*deploymentModels.Component, error) { +func getComponentStateFromSpec(ctx context.Context, kubeClient kubernetes.Interface, rd *v1.RadixDeployment, component v1.RadixCommonDeployComponent, hpas []autoscalingv2.HorizontalPodAutoscaler, scaledObjects []v1alpha1.ScaledObject) (*deploymentModels.Component, error) { var componentPodNames []string var environmentVariables map[string]string @@ -44,33 +31,34 @@ func getComponentStateFromSpec( var auxResource deploymentModels.AuxiliaryResource var horizontalScalingSummary *deploymentModels.HorizontalScalingSummary - envNs := crdUtils.GetEnvironmentNamespace(appName, deployment.Environment) status := deploymentModels.ConsistentComponent - if deployment.ActiveTo == "" { + if rd.Status.ActiveTo.IsZero() { // current active deployment - we get existing pods - componentPods, err := getComponentPodsByNamespace(ctx, kubeClient, envNs, component.GetName()) + componentPods, err := getComponentPodsByNamespace(ctx, kubeClient, rd.GetNamespace(), component.GetName()) if err != nil { return nil, err } + + kd, err := kubequery.GetDeploymentsForComponent(ctx, kubeClient, rd.Spec.AppName, rd.Spec.Environment, component.GetName()) + if err != nil { + return nil, err + } + componentPodNames = getPodNames(componentPods) environmentVariables = getRadixEnvironmentVariables(componentPods) - eventList, err := kubequery.GetEventsForEnvironment(ctx, kubeClient, appName, deployment.Environment) + eventList, err := kubequery.GetEventsForEnvironment(ctx, kubeClient, rd.Spec.AppName, rd.Spec.Environment) if err != nil { return nil, err } lastEventWarnings := event.ConvertToEventWarnings(eventList) replicaSummaryList = getReplicaSummaryList(componentPods, lastEventWarnings) - auxResource, err = getAuxiliaryResources(ctx, kubeClient, appName, component, envNs) + auxResource, err = getAuxiliaryResources(ctx, kubeClient, rd.Spec.AppName, component, rd.GetNamespace()) if err != nil { return nil, err } - status, err = getStatusOfActiveDeployment(component, - deploymentStatus, environmentConfig, componentPods) - if err != nil { - return nil, err - } + status = deploymentModels.GetComponentStatus(component, kd, rd) } componentBuilder := deploymentModels.NewComponentBuilder() @@ -83,7 +71,7 @@ func getComponentStateFromSpec( } if component.GetType() == v1.RadixComponentTypeComponent { - horizontalScalingSummary = models.GetHpaSummary(appName, component.GetName(), hpas, scaledObjects) + horizontalScalingSummary = models.GetHpaSummary(rd.Spec.AppName, component.GetName(), hpas, scaledObjects) } return componentBuilder. @@ -140,51 +128,6 @@ func getLabelSelectorForComponentPods(componentName string) labels.Selector { return labels.NewSelector().Add(*componentNameRequirement, *notJobAuxRequirement) } -func runningReplicaDiffersFromConfig(environmentConfig v1.RadixCommonEnvironmentConfig, actualPods []corev1.Pod) bool { - actualPodsLength := len(actualPods) - if radixutils.IsNil(environmentConfig) { - return actualPodsLength != deployment.DefaultReplicas - } - // No HPA config - if environmentConfig.GetHorizontalScaling() == nil { - if environmentConfig.GetReplicas() != nil { - return actualPodsLength != *environmentConfig.GetReplicas() - } - return actualPodsLength != deployment.DefaultReplicas - } - // With HPA config - if environmentConfig.GetReplicas() != nil && *environmentConfig.GetReplicas() == 0 { - return actualPodsLength != *environmentConfig.GetReplicas() - } - if environmentConfig.GetHorizontalScaling().MinReplicas != nil { - return actualPodsLength < int(*environmentConfig.GetHorizontalScaling().MinReplicas) || - actualPodsLength > int(environmentConfig.GetHorizontalScaling().MaxReplicas) - } - return actualPodsLength < deployment.DefaultReplicas || - actualPodsLength > int(environmentConfig.GetHorizontalScaling().MaxReplicas) -} - -func runningReplicaDiffersFromSpec(component v1.RadixCommonDeployComponent, actualPods []corev1.Pod) bool { - actualPodsLength := len(actualPods) - // No HPA config - if component.GetHorizontalScaling() == nil { - if component.GetReplicas() != nil { - return actualPodsLength != *component.GetReplicas() - } - return actualPodsLength != deployment.DefaultReplicas - } - // With HPA config - if component.GetReplicas() != nil && *component.GetReplicas() == 0 { - return actualPodsLength != *component.GetReplicas() - } - if component.GetHorizontalScaling().MinReplicas != nil { - return actualPodsLength < int(*component.GetHorizontalScaling().MinReplicas) || - actualPodsLength > int(component.GetHorizontalScaling().MaxReplicas) - } - return actualPodsLength < deployment.DefaultReplicas || - actualPodsLength > int(component.GetHorizontalScaling().MaxReplicas) -} - func getRadixEnvironmentVariables(pods []corev1.Pod) map[string]string { radixEnvironmentVariables := make(map[string]string) @@ -253,71 +196,3 @@ func getAuxiliaryResourceDeployment(ctx context.Context, kubeClient kubernetes.I auxResourceDeployment.Status = deploymentModels.ComponentStatusFromDeployment(&deployment).String() return &auxResourceDeployment, nil } - -func runningReplicaIsOutdated(component v1.RadixCommonDeployComponent, actualPods []corev1.Pod) bool { - switch component.GetType() { - case v1.RadixComponentTypeComponent: - return runningComponentReplicaIsOutdated(component, actualPods) - case v1.RadixComponentTypeJob: - return false - default: - return false - } -} - -func runningComponentReplicaIsOutdated(component v1.RadixCommonDeployComponent, actualPods []corev1.Pod) bool { - // Check if running component's image is not the same as active deployment image tag and that active rd image is equal to 'starting' component image tag - componentIsInconsistent := false - for _, pod := range actualPods { - if pod.DeletionTimestamp != nil { - // Pod is in termination phase - continue - } - for _, container := range pod.Spec.Containers { - if container.Image != component.GetImage() { - // Container is running an outdated image - componentIsInconsistent = true - } - } - } - - return componentIsInconsistent -} - -func getStatusOfActiveDeployment( - component v1.RadixCommonDeployComponent, - deploymentStatus v1.RadixDeployStatus, - environmentConfig v1.RadixCommonEnvironmentConfig, - pods []corev1.Pod) (deploymentModels.ComponentStatus, error) { - - if component.GetType() == v1.RadixComponentTypeComponent { - if runningReplicaDiffersFromConfig(environmentConfig, pods) && - !runningReplicaDiffersFromSpec(component, pods) && - len(pods) == 0 { - return deploymentModels.StoppedComponent, nil - } - if runningReplicaDiffersFromSpec(component, pods) { - return deploymentModels.ComponentReconciling, nil - } - } else if component.GetType() == v1.RadixComponentTypeJob { - if len(pods) == 0 { - return deploymentModels.StoppedComponent, nil - } - } - if runningReplicaIsOutdated(component, pods) { - return deploymentModels.ComponentOutdated, nil - } - restarted := component.GetEnvironmentVariables()[defaults.RadixRestartEnvironmentVariable] - if strings.EqualFold(restarted, "") { - return deploymentModels.ConsistentComponent, nil - } - restartedTime, err := radixutils.ParseTimestamp(restarted) - if err != nil { - return deploymentModels.ConsistentComponent, err - } - reconciledTime := deploymentStatus.Reconciled - if reconciledTime.IsZero() || restartedTime.After(reconciledTime.Time) { - return deploymentModels.ComponentRestarting, nil - } - return deploymentModels.ConsistentComponent, nil -} diff --git a/api/environments/environment_handler.go b/api/environments/environment_handler.go index 929e7e64..c4150957 100644 --- a/api/environments/environment_handler.go +++ b/api/environments/environment_handler.go @@ -304,7 +304,7 @@ func (eh EnvironmentHandler) GetAuxiliaryResourcePodLog(ctx context.Context, app // StopEnvironment Stops all components in the environment func (eh EnvironmentHandler) StopEnvironment(ctx context.Context, appName, envName string) error { - _, radixDeployment, err := eh.getRadixDeployment(ctx, appName, envName) + radixDeployment, err := kubequery.GetLatestRadixDeployment(ctx, eh.accounts.UserAccount.RadixClient, appName, envName) if err != nil { return err } @@ -321,7 +321,7 @@ func (eh EnvironmentHandler) StopEnvironment(ctx context.Context, appName, envNa // ResetManuallyStoppedComponentsInEnvironment Starts all components in the environment func (eh EnvironmentHandler) ResetManuallyStoppedComponentsInEnvironment(ctx context.Context, appName, envName string) error { - _, radixDeployment, err := eh.getRadixDeployment(ctx, appName, envName) + radixDeployment, err := kubequery.GetLatestRadixDeployment(ctx, eh.accounts.UserAccount.RadixClient, appName, envName) if err != nil { return err } @@ -340,7 +340,7 @@ func (eh EnvironmentHandler) ResetManuallyStoppedComponentsInEnvironment(ctx con // RestartEnvironment Restarts all components in the environment func (eh EnvironmentHandler) RestartEnvironment(ctx context.Context, appName, envName string) error { - _, radixDeployment, err := eh.getRadixDeployment(ctx, appName, envName) + radixDeployment, err := kubequery.GetLatestRadixDeployment(ctx, eh.accounts.UserAccount.RadixClient, appName, envName) if err != nil { return err } @@ -404,7 +404,7 @@ func (eh EnvironmentHandler) RestartApplication(ctx context.Context, appName str } func (eh EnvironmentHandler) getRadixCommonComponentUpdater(ctx context.Context, appName, envName, componentName string) (radixDeployCommonComponentUpdater, error) { - deploymentSummary, rd, err := eh.getRadixDeployment(ctx, appName, envName) + rd, err := kubequery.GetLatestRadixDeployment(ctx, eh.accounts.UserAccount.RadixClient, appName, envName) if err != nil { return nil, err } @@ -439,9 +439,12 @@ func (eh EnvironmentHandler) getRadixCommonComponentUpdater(ctx context.Context, baseUpdater.componentIndex = componentIndex baseUpdater.componentToPatch = componentToPatch - ra, _ := kubequery.GetRadixApplication(ctx, eh.accounts.UserAccount.RadixClient, appName) + ra, err := kubequery.GetRadixApplication(ctx, eh.accounts.UserAccount.RadixClient, appName) + if err != nil { + return nil, err + } baseUpdater.environmentConfig = utils.GetComponentEnvironmentConfig(ra, envName, componentName) - baseUpdater.componentState, err = getComponentStateFromSpec(ctx, eh.accounts.UserAccount.Client, appName, deploymentSummary, rd.Status, baseUpdater.environmentConfig, componentToPatch, hpas, scalers) + baseUpdater.componentState, err = getComponentStateFromSpec(ctx, eh.accounts.UserAccount.Client, rd, componentToPatch, hpas, scalers) if err != nil { return nil, err } diff --git a/api/environments/utils.go b/api/environments/utils.go deleted file mode 100644 index 7f13f81e..00000000 --- a/api/environments/utils.go +++ /dev/null @@ -1,24 +0,0 @@ -package environments - -import ( - "context" - - deploymentModels "github.com/equinor/radix-api/api/deployments/models" - v1 "github.com/equinor/radix-operator/pkg/apis/radix/v1" - operatorutils "github.com/equinor/radix-operator/pkg/apis/utils" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -func (eh EnvironmentHandler) getRadixDeployment(ctx context.Context, appName, envName string) (*deploymentModels.DeploymentSummary, *v1.RadixDeployment, error) { - envNs := operatorutils.GetEnvironmentNamespace(appName, envName) - deploymentSummary, err := eh.deployHandler.GetLatestDeploymentForApplicationEnvironment(ctx, appName, envName) - if err != nil { - return nil, nil, err - } - - radixDeployment, err := eh.accounts.UserAccount.RadixClient.RadixV1().RadixDeployments(envNs).Get(ctx, deploymentSummary.Name, metav1.GetOptions{}) - if err != nil { - return nil, nil, err - } - return deploymentSummary, radixDeployment, nil -} diff --git a/api/kubequery/deployment.go b/api/kubequery/deployment.go index 552ceeec..91241782 100644 --- a/api/kubequery/deployment.go +++ b/api/kubequery/deployment.go @@ -3,6 +3,7 @@ package kubequery import ( "context" + "github.com/equinor/radix-api/api/utils/labelselector" operatorUtils "github.com/equinor/radix-operator/pkg/apis/utils" "github.com/equinor/radix-operator/pkg/apis/utils/labels" appsv1 "k8s.io/api/apps/v1" @@ -19,3 +20,19 @@ func GetDeploymentsForEnvironment(ctx context.Context, client kubernetes.Interfa } return deployments.Items, nil } + +// GetDeploymentsForComponent returns all the first deployment matching the specified application, environment and component name. +func GetDeploymentsForComponent(ctx context.Context, client kubernetes.Interface, appName, envName, componentName string) (*appsv1.Deployment, error) { + ns := operatorUtils.GetEnvironmentNamespace(appName, envName) + selector := labelselector.ForComponent(appName, componentName).String() + deployments, err := client.AppsV1().Deployments(ns).List(ctx, metav1.ListOptions{LabelSelector: selector}) + if err != nil { + return nil, err + } + + if len(deployments.Items) == 0 { + return nil, nil + } + + return &deployments.Items[0], nil +} diff --git a/api/kubequery/radixdeployment.go b/api/kubequery/radixdeployment.go index b9d97ad6..7795d409 100644 --- a/api/kubequery/radixdeployment.go +++ b/api/kubequery/radixdeployment.go @@ -2,6 +2,7 @@ package kubequery import ( "context" + "sort" "github.com/equinor/radix-common/utils/slice" radixv1 "github.com/equinor/radix-operator/pkg/apis/radix/v1" @@ -73,3 +74,30 @@ func GetRadixDeploymentByName(ctx context.Context, radixClient radixclient.Inter ns := operatorUtils.GetEnvironmentNamespace(appName, envName) return radixClient.RadixV1().RadixDeployments(ns).Get(ctx, deploymentName, metav1.GetOptions{}) } +func GetLatestRadixDeployment(ctx context.Context, radixClient radixclient.Interface, appName, envName string) (*radixv1.RadixDeployment, error) { + ns := operatorUtils.GetEnvironmentNamespace(appName, envName) + rdList, err := radixClient.RadixV1().RadixDeployments(ns).List(ctx, metav1.ListOptions{}) + if err != nil { + return nil, err + } + if len(rdList.Items) == 0 { + return nil, nil + } + + rds := sortRdsByActiveFromDesc(rdList.Items) + return &rds[0], nil +} + +func sortRdsByActiveFromDesc(rds []radixv1.RadixDeployment) []radixv1.RadixDeployment { + sort.Slice(rds, func(i, j int) bool { + if rds[j].Status.ActiveFrom.IsZero() { + return true + } + + if rds[i].Status.ActiveFrom.IsZero() { + return false + } + return rds[j].Status.ActiveFrom.Before(&rds[i].Status.ActiveFrom) + }) + return rds +} diff --git a/api/models/component.go b/api/models/component.go index 178f5733..2a7502e4 100644 --- a/api/models/component.go +++ b/api/models/component.go @@ -12,16 +12,11 @@ import ( "github.com/equinor/radix-api/api/utils/event" "github.com/equinor/radix-api/api/utils/predicate" "github.com/equinor/radix-api/api/utils/tlsvalidation" - commonutils "github.com/equinor/radix-common/utils" - "github.com/equinor/radix-common/utils/pointers" "github.com/equinor/radix-common/utils/slice" - operatordefaults "github.com/equinor/radix-operator/pkg/apis/defaults" - "github.com/equinor/radix-operator/pkg/apis/kube" radixv1 "github.com/equinor/radix-operator/pkg/apis/radix/v1" operatorutils "github.com/equinor/radix-operator/pkg/apis/utils" radixlabels "github.com/equinor/radix-operator/pkg/apis/utils/labels" "github.com/kedacore/keda/v2/apis/keda/v1alpha1" - "github.com/rs/zerolog/log" appsv1 "k8s.io/api/apps/v1" autoscalingv2 "k8s.io/api/autoscaling/v2" corev1 "k8s.io/api/core/v1" @@ -74,7 +69,7 @@ func buildComponent( builder.WithPodNames(slice.Map(componentPods, func(pod corev1.Pod) string { return pod.Name })) builder.WithRadixEnvironmentVariables(getRadixEnvironmentVariables(componentPods)) builder.WithReplicaSummaryList(BuildReplicaSummaryList(componentPods, lastEventWarnings)) - builder.WithStatus(getComponentStatus(radixComponent, kd, rd, componentPods)) + builder.WithStatus(deploymentModels.GetComponentStatus(radixComponent, kd, rd)) builder.WithAuxiliaryResource(getAuxiliaryResources(ra.Name, radixComponent, deploymentList, podList, lastEventWarnings)) } @@ -240,106 +235,6 @@ func certificateRequestConditionReady(condition cmv1.CertificateRequestCondition return condition.Type == cmv1.CertificateRequestConditionReady } -func getComponentStatus(component radixv1.RadixCommonDeployComponent, kd *appsv1.Deployment, rd *radixv1.RadixDeployment, pods []corev1.Pod) deploymentModels.ComponentStatus { - if kd == nil { - return deploymentModels.ComponentReconciling - } - replicasUnavailable := kd.Status.UnavailableReplicas - replicasReady := kd.Status.ReadyReplicas - replicas := pointers.Val(kd.Spec.Replicas) - - if component.GetType() == radixv1.RadixComponentTypeJob && replicas == 0 { - return deploymentModels.StoppedComponent - } - - if isComponentManuallyStopped(component) && replicas == 0 { - return deploymentModels.StoppedComponent - } - - if isCopmonentRestarting(component, rd) { - return deploymentModels.ComponentRestarting - } - - // Check if component is scaling up or down - if replicasUnavailable > 0 || replicas < replicasReady { - return deploymentModels.ComponentReconciling - } - - if runningReplicaIsOutdated(component, pods, rd, kd) { - return deploymentModels.ComponentOutdated - } - - return deploymentModels.ConsistentComponent -} - -func isComponentManuallyStopped(component radixv1.RadixCommonDeployComponent) bool { - override := component.GetReplicasOverride() - - return override != nil && *override == 0 -} - -func isCopmonentRestarting(component radixv1.RadixCommonDeployComponent, rd *radixv1.RadixDeployment) bool { - restarted := component.GetEnvironmentVariables()[operatordefaults.RadixRestartEnvironmentVariable] - if strings.EqualFold(restarted, "") { - return false - } - restartedTime, err := commonutils.ParseTimestamp(restarted) - if err != nil { - log.Logger.Warn().Err(err).Msgf("unable to parse restarted time %v", restarted) - return false - } - reconciledTime := rd.Status.Reconciled - if reconciledTime.IsZero() || restartedTime.After(reconciledTime.Time) { - return true - } - return false -} - -func runningReplicaIsOutdated(component radixv1.RadixCommonDeployComponent, actualPods []corev1.Pod, rd *radixv1.RadixDeployment, kd *appsv1.Deployment) bool { - switch component.GetType() { - case radixv1.RadixComponentTypeComponent: - return runningComponentReplicaIsOutdated(component, actualPods, rd, kd) - case radixv1.RadixComponentTypeJob: - return false - default: - return false - } -} - -func runningComponentReplicaIsOutdated(component radixv1.RadixCommonDeployComponent, actualPods []corev1.Pod, rd *radixv1.RadixDeployment, kd *appsv1.Deployment) bool { - // Check if running component's image is not the same as active deployment image tag and that active rd image is equal to 'starting' component image tag - for _, pod := range actualPods { - if pod.DeletionTimestamp != nil { - // Pod is in termination phase - continue - } - for _, container := range pod.Spec.Containers { - if container.Image != component.GetImage() { - // Container is running an outdated image - return true - } - } - } - - kdOwner, ok := slice.FindFirst(kd.ObjectMeta.OwnerReferences, func(o metav1.OwnerReference) bool { - return o.Controller != nil && *o.Controller - }) - if !ok || kdOwner.Kind != radixv1.KindRadixDeployment || kdOwner.Name != rd.Name { - // Deployments owner is not correct - return true - } - - observedGeneration, ok := kd.Annotations[kube.RadixDeploymentObservedGeneration] - if !ok || observedGeneration != strconv.Itoa(int(rd.ObjectMeta.Generation)) { - // k8s Deployment version doesn't match Radix Deployments generation - return true - } - - // TODO: Annotate and check configmap and secrets - - return false -} - func getRadixEnvironmentVariables(pods []corev1.Pod) map[string]string { radixEnvironmentVariables := make(map[string]string) diff --git a/api/utils/owner/verify_generation.go b/api/utils/owner/verify_generation.go new file mode 100644 index 00000000..a7ef364c --- /dev/null +++ b/api/utils/owner/verify_generation.go @@ -0,0 +1,22 @@ +package owner + +import ( + "strconv" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// VerifyCorrectObjectGeneration Returns true if owner is controller of object and annotation matches owner generation +func VerifyCorrectObjectGeneration(owner metav1.Object, object metav1.Object, annotation string) bool { + controller := metav1.GetControllerOf(object) + if controller == nil { + return false + } + + if controller.UID != owner.GetUID() { + return false + } + + observedGeneration, ok := object.GetAnnotations()[annotation] + return ok && observedGeneration == strconv.Itoa(int(owner.GetGeneration())) +} From b64134de0f099bf3a45eae6abcb9a12e85bd37ef Mon Sep 17 00:00:00 2001 From: Richard87 Date: Fri, 30 Aug 2024 15:42:01 +0200 Subject: [PATCH 09/33] reafctor environment, aux resources, component spec --- .../mock/deployment_handler_mock.go | 31 --------- api/deployments/models/component_status.go | 27 +------- api/environments/component_spec.go | 64 +++++++++---------- api/kubequery/deployment.go | 4 +- api/models/auxiliary_resource.go | 16 ++--- api/models/component.go | 4 +- api/utils/predicate/kubernetes.go | 4 +- swaggerui/html/swagger.json | 48 +++++++++++++- 8 files changed, 92 insertions(+), 106 deletions(-) diff --git a/api/deployments/mock/deployment_handler_mock.go b/api/deployments/mock/deployment_handler_mock.go index 91e1dc5e..5618d231 100644 --- a/api/deployments/mock/deployment_handler_mock.go +++ b/api/deployments/mock/deployment_handler_mock.go @@ -11,7 +11,6 @@ import ( time "time" models "github.com/equinor/radix-api/api/deployments/models" - v1 "github.com/equinor/radix-operator/pkg/apis/radix/v1" gomock "github.com/golang/mock/gomock" ) @@ -128,36 +127,6 @@ func (mr *MockDeployHandlerMockRecorder) GetJobComponentDeployments(arg0, arg1, return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetJobComponentDeployments", reflect.TypeOf((*MockDeployHandler)(nil).GetJobComponentDeployments), arg0, arg1, arg2, arg3) } -// GetLatestDeploymentForApplicationEnvironment mocks base method. -func (m *MockDeployHandler) GetLatestDeploymentForApplicationEnvironment(ctx context.Context, appName, environment string) (*models.DeploymentSummary, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetLatestDeploymentForApplicationEnvironment", ctx, appName, environment) - ret0, _ := ret[0].(*models.DeploymentSummary) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// GetLatestDeploymentForApplicationEnvironment indicates an expected call of GetLatestDeploymentForApplicationEnvironment. -func (mr *MockDeployHandlerMockRecorder) GetLatestDeploymentForApplicationEnvironment(ctx, appName, environment interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetLatestDeploymentForApplicationEnvironment", reflect.TypeOf((*MockDeployHandler)(nil).GetLatestDeploymentForApplicationEnvironment), ctx, appName, environment) -} - -// GetLatestRadixDeployment mocks base method. -func (m *MockDeployHandler) GetLatestRadixDeployment(ctx context.Context, appName, environment string) (*v1.RadixDeployment, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetLatestRadixDeployment", ctx, appName, environment) - ret0, _ := ret[0].(*v1.RadixDeployment) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// GetLatestRadixDeployment indicates an expected call of GetLatestRadixDeployment. -func (mr *MockDeployHandlerMockRecorder) GetLatestRadixDeployment(ctx, appName, environment interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetLatestRadixDeployment", reflect.TypeOf((*MockDeployHandler)(nil).GetLatestRadixDeployment), ctx, appName, environment) -} - // GetLogs mocks base method. func (m *MockDeployHandler) GetLogs(ctx context.Context, appName, podName string, sinceTime *time.Time, logLines *int64, previousLog bool) (io.ReadCloser, error) { m.ctrl.T.Helper() diff --git a/api/deployments/models/component_status.go b/api/deployments/models/component_status.go index 8b8e6bcd..37ea5b0b 100644 --- a/api/deployments/models/component_status.go +++ b/api/deployments/models/component_status.go @@ -42,20 +42,7 @@ func (p ComponentStatus) String() string { return [...]string{"Stopped", "Consistent", "Reconciling", "Restarting", "Outdated"}[p] } -func ComponentStatusFromDeployment(deployment *appsv1.Deployment) ComponentStatus { - status := ConsistentComponent - - switch { - case deployment.Status.Replicas == 0: - status = StoppedComponent - case deployment.Status.UnavailableReplicas > 0: - status = ComponentReconciling - } - - return status -} - -func GetComponentStatus(component radixv1.RadixCommonDeployComponent, kd *appsv1.Deployment, rd *radixv1.RadixDeployment) ComponentStatus { +func ComponentStatusFromDeployment(component radixv1.RadixCommonDeployComponent, kd *appsv1.Deployment, rd *radixv1.RadixDeployment) ComponentStatus { if kd == nil { return ComponentReconciling } @@ -63,11 +50,7 @@ func GetComponentStatus(component radixv1.RadixCommonDeployComponent, kd *appsv1 replicasReady := kd.Status.ReadyReplicas replicas := pointers.Val(kd.Spec.Replicas) - if component.GetType() == radixv1.RadixComponentTypeJob && replicas == 0 { - return StoppedComponent - } - - if isComponentManuallyStopped(component) && replicas == 0 { + if replicas == 0 { return StoppedComponent } @@ -87,12 +70,6 @@ func GetComponentStatus(component radixv1.RadixCommonDeployComponent, kd *appsv1 return ConsistentComponent } -func isComponentManuallyStopped(component radixv1.RadixCommonDeployComponent) bool { - override := component.GetReplicasOverride() - - return override != nil && *override == 0 -} - func isCopmonentRestarting(component radixv1.RadixCommonDeployComponent, rd *radixv1.RadixDeployment) bool { restarted := component.GetEnvironmentVariables()[operatordefaults.RadixRestartEnvironmentVariable] if strings.EqualFold(restarted, "") { diff --git a/api/environments/component_spec.go b/api/environments/component_spec.go index 5b2c0f81..38e44431 100644 --- a/api/environments/component_spec.go +++ b/api/environments/component_spec.go @@ -7,16 +7,16 @@ import ( "github.com/equinor/radix-api/api/kubequery" "github.com/equinor/radix-api/api/models" "github.com/equinor/radix-api/api/utils/event" - "github.com/equinor/radix-api/api/utils/labelselector" + "github.com/equinor/radix-api/api/utils/predicate" "github.com/equinor/radix-common/utils/slice" "github.com/equinor/radix-operator/pkg/apis/defaults" "github.com/equinor/radix-operator/pkg/apis/kube" v1 "github.com/equinor/radix-operator/pkg/apis/radix/v1" crdUtils "github.com/equinor/radix-operator/pkg/apis/utils" "github.com/kedacore/keda/v2/apis/keda/v1alpha1" + appsv1 "k8s.io/api/apps/v1" autoscalingv2 "k8s.io/api/autoscaling/v2" corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/selection" "k8s.io/client-go/kubernetes" @@ -30,20 +30,23 @@ func getComponentStateFromSpec(ctx context.Context, kubeClient kubernetes.Interf var replicaSummaryList []deploymentModels.ReplicaSummary var auxResource deploymentModels.AuxiliaryResource var horizontalScalingSummary *deploymentModels.HorizontalScalingSummary + deployments, err := kubequery.GetDeploymentsForEnvironment(ctx, kubeClient, rd.Spec.AppName, rd.Spec.Environment) + if err != nil { + return nil, err + } + pods, err := kubequery.GetPodsForEnvironmentComponents(ctx, kubeClient, rd.Spec.AppName, rd.Spec.Environment) status := deploymentModels.ConsistentComponent if rd.Status.ActiveTo.IsZero() { // current active deployment - we get existing pods - componentPods, err := getComponentPodsByNamespace(ctx, kubeClient, rd.GetNamespace(), component.GetName()) + componentPods, err := getComponentPodsByNamespace(pods, component.GetName()) if err != nil { return nil, err } - kd, err := kubequery.GetDeploymentsForComponent(ctx, kubeClient, rd.Spec.AppName, rd.Spec.Environment, component.GetName()) - if err != nil { - return nil, err - } + // TODO: Make sure we select the cdeployment, not the aux deplyment with same labels!!! + kd, _ := slice.FindFirst(deployments, predicate.IsDeploymentForComponent(rd.Spec.AppName, component.GetName())) componentPodNames = getPodNames(componentPods) environmentVariables = getRadixEnvironmentVariables(componentPods) @@ -53,12 +56,13 @@ func getComponentStateFromSpec(ctx context.Context, kubeClient kubernetes.Interf } lastEventWarnings := event.ConvertToEventWarnings(eventList) replicaSummaryList = getReplicaSummaryList(componentPods, lastEventWarnings) - auxResource, err = getAuxiliaryResources(ctx, kubeClient, rd.Spec.AppName, component, rd.GetNamespace()) + auxResource, err = getAuxiliaryResources(pods, deployments, rd, component) if err != nil { return nil, err } - status = deploymentModels.GetComponentStatus(component, kd, rd) + kd, _ = slice.FindFirst(deployments, predicate.IsDeploymentForAuxComponent(rd.Spec.AppName, component.GetName(), defaults.OAuthProxyAuxiliaryComponentType)) + status = deploymentModels.ComponentStatusFromDeployment(component, &kd, rd) } componentBuilder := deploymentModels.NewComponentBuilder() @@ -93,16 +97,15 @@ func getPodNames(pods []corev1.Pod) []string { return names } -func getComponentPodsByNamespace(ctx context.Context, client kubernetes.Interface, envNs, componentName string) ([]corev1.Pod, error) { +func getComponentPodsByNamespace(allPods []corev1.Pod, componentName string) ([]corev1.Pod, error) { var componentPods []corev1.Pod - pods, err := client.CoreV1().Pods(envNs).List(ctx, metav1.ListOptions{ - LabelSelector: getLabelSelectorForComponentPods(componentName).String(), + + selector := getLabelSelectorForComponentPods(componentName) + pods := slice.FindAll(allPods, func(pod corev1.Pod) bool { + return selector.Matches(labels.Set(pod.Labels)) }) - if err != nil { - return nil, err - } - for _, pod := range pods.Items { + for _, pod := range pods { pod := pod // A previous version of the job-scheduler added the "radix-job-type" label to job pods. @@ -150,9 +153,9 @@ func getReplicaSummaryList(pods []corev1.Pod, lastEventWarnings event.LastEventW }) } -func getAuxiliaryResources(ctx context.Context, kubeClient kubernetes.Interface, appName string, component v1.RadixCommonDeployComponent, envNamespace string) (auxResource deploymentModels.AuxiliaryResource, err error) { +func getAuxiliaryResources(podList []corev1.Pod, deploymentList []appsv1.Deployment, deployment *v1.RadixDeployment, component v1.RadixCommonDeployComponent) (auxResource deploymentModels.AuxiliaryResource, err error) { if auth := component.GetAuthentication(); component.IsPublic() && auth != nil && auth.OAuth2 != nil { - auxResource.OAuth2, err = getOAuth2AuxiliaryResource(ctx, kubeClient, appName, component.GetName(), envNamespace) + auxResource.OAuth2, err = getOAuth2AuxiliaryResource(podList, deploymentList, deployment, component) if err != nil { return } @@ -161,9 +164,9 @@ func getAuxiliaryResources(ctx context.Context, kubeClient kubernetes.Interface, return } -func getOAuth2AuxiliaryResource(ctx context.Context, kubeClient kubernetes.Interface, appName, componentName, envNamespace string) (*deploymentModels.OAuth2AuxiliaryResource, error) { +func getOAuth2AuxiliaryResource(podList []corev1.Pod, deploymentList []appsv1.Deployment, deployment *v1.RadixDeployment, component v1.RadixCommonDeployComponent) (*deploymentModels.OAuth2AuxiliaryResource, error) { var oauth2Resource deploymentModels.OAuth2AuxiliaryResource - oauthDeployment, err := getAuxiliaryResourceDeployment(ctx, kubeClient, appName, componentName, envNamespace, defaults.OAuthProxyAuxiliaryComponentType) + oauthDeployment, err := getAuxiliaryResourceDeployment(podList, deploymentList, deployment, component, defaults.OAuthProxyAuxiliaryComponentType) if err != nil { return nil, err } @@ -174,25 +177,18 @@ func getOAuth2AuxiliaryResource(ctx context.Context, kubeClient kubernetes.Inter return &oauth2Resource, nil } -func getAuxiliaryResourceDeployment(ctx context.Context, kubeClient kubernetes.Interface, appName, componentName, envNamespace, auxType string) (*deploymentModels.AuxiliaryResourceDeployment, error) { +func getAuxiliaryResourceDeployment(podList []corev1.Pod, deploymentList []appsv1.Deployment, rd *v1.RadixDeployment, component v1.RadixCommonDeployComponent, auxType string) (*deploymentModels.AuxiliaryResourceDeployment, error) { var auxResourceDeployment deploymentModels.AuxiliaryResourceDeployment - selector := labelselector.ForAuxiliaryResource(appName, componentName, auxType).String() - deployments, err := kubeClient.AppsV1().Deployments(envNamespace).List(ctx, metav1.ListOptions{LabelSelector: selector}) - if err != nil { - return nil, err - } - if len(deployments.Items) == 0 { + kd, ok := slice.FindFirst(deploymentList, predicate.IsDeploymentForAuxComponent(rd.Spec.AppName, component.GetName(), auxType)) + if !ok { auxResourceDeployment.Status = deploymentModels.ComponentReconciling.String() return &auxResourceDeployment, nil } - deployment := deployments.Items[0] - pods, err := kubeClient.CoreV1().Pods(envNamespace).List(ctx, metav1.ListOptions{LabelSelector: selector}) - if err != nil { - return nil, err - } - auxResourceDeployment.ReplicaList = getReplicaSummaryList(pods.Items, nil) - auxResourceDeployment.Status = deploymentModels.ComponentStatusFromDeployment(&deployment).String() + pods := slice.FindAll(podList, predicate.IsPodForAuxComponent(rd.Spec.AppName, rd.Spec.Environment, auxType)) + + auxResourceDeployment.ReplicaList = getReplicaSummaryList(pods, nil) + auxResourceDeployment.Status = deploymentModels.ComponentStatusFromDeployment(component, &kd, rd).String() return &auxResourceDeployment, nil } diff --git a/api/kubequery/deployment.go b/api/kubequery/deployment.go index 91241782..1376a210 100644 --- a/api/kubequery/deployment.go +++ b/api/kubequery/deployment.go @@ -21,8 +21,8 @@ func GetDeploymentsForEnvironment(ctx context.Context, client kubernetes.Interfa return deployments.Items, nil } -// GetDeploymentsForComponent returns all the first deployment matching the specified application, environment and component name. -func GetDeploymentsForComponent(ctx context.Context, client kubernetes.Interface, appName, envName, componentName string) (*appsv1.Deployment, error) { +// GetDeploymentForComponent returns all the first deployment matching the specified application, environment and component name. +func GetDeploymentForComponent(ctx context.Context, client kubernetes.Interface, appName, envName, componentName string) (*appsv1.Deployment, error) { ns := operatorUtils.GetEnvironmentNamespace(appName, envName) selector := labelselector.ForComponent(appName, componentName).String() deployments, err := client.AppsV1().Deployments(ns).List(ctx, metav1.ListOptions{LabelSelector: selector}) diff --git a/api/models/auxiliary_resource.go b/api/models/auxiliary_resource.go index 19a3b08b..456412c2 100644 --- a/api/models/auxiliary_resource.go +++ b/api/models/auxiliary_resource.go @@ -10,30 +10,30 @@ import ( corev1 "k8s.io/api/core/v1" ) -func getAuxiliaryResources(appName string, component radixv1.RadixCommonDeployComponent, deploymentList []appsv1.Deployment, podList []corev1.Pod, eventWarnings map[string]string) deploymentModels.AuxiliaryResource { +func getAuxiliaryResources(rd *radixv1.RadixDeployment, component radixv1.RadixCommonDeployComponent, deploymentList []appsv1.Deployment, podList []corev1.Pod, eventWarnings map[string]string) deploymentModels.AuxiliaryResource { var auxResource deploymentModels.AuxiliaryResource if auth := component.GetAuthentication(); component.IsPublic() && auth != nil && auth.OAuth2 != nil { - auxResource.OAuth2 = getOAuth2AuxiliaryResource(appName, component.GetName(), deploymentList, podList, eventWarnings) + auxResource.OAuth2 = getOAuth2AuxiliaryResource(rd, component, deploymentList, podList, eventWarnings) } return auxResource } -func getOAuth2AuxiliaryResource(appName, componentName string, deploymentList []appsv1.Deployment, podList []corev1.Pod, eventWarnings map[string]string) *deploymentModels.OAuth2AuxiliaryResource { +func getOAuth2AuxiliaryResource(rd *radixv1.RadixDeployment, component radixv1.RadixCommonDeployComponent, deploymentList []appsv1.Deployment, podList []corev1.Pod, eventWarnings map[string]string) *deploymentModels.OAuth2AuxiliaryResource { return &deploymentModels.OAuth2AuxiliaryResource{ - Deployment: getAuxiliaryResourceDeployment(appName, componentName, operatordefaults.OAuthProxyAuxiliaryComponentType, deploymentList, podList, eventWarnings), + Deployment: getAuxiliaryResourceDeployment(rd, component, operatordefaults.OAuthProxyAuxiliaryComponentType, deploymentList, podList, eventWarnings), } } -func getAuxiliaryResourceDeployment(appName, componentName, auxType string, deploymentList []appsv1.Deployment, podList []corev1.Pod, eventWarnings map[string]string) deploymentModels.AuxiliaryResourceDeployment { +func getAuxiliaryResourceDeployment(rd *radixv1.RadixDeployment, component radixv1.RadixCommonDeployComponent, auxType string, deploymentList []appsv1.Deployment, podList []corev1.Pod, eventWarnings map[string]string) deploymentModels.AuxiliaryResourceDeployment { var auxResourceDeployment deploymentModels.AuxiliaryResourceDeployment - auxDeployments := slice.FindAll(deploymentList, predicate.IsDeploymentForAuxComponent(appName, componentName, auxType)) + auxDeployments := slice.FindAll(deploymentList, predicate.IsDeploymentForAuxComponent(rd.Spec.AppName, component.GetName(), auxType)) if len(auxDeployments) == 0 { auxResourceDeployment.Status = deploymentModels.ComponentReconciling.String() return auxResourceDeployment } deployment := auxDeployments[0] - auxPods := slice.FindAll(podList, predicate.IsPodForAuxComponent(appName, componentName, auxType)) + auxPods := slice.FindAll(podList, predicate.IsPodForAuxComponent(rd.Spec.AppName, component.GetName(), auxType)) auxResourceDeployment.ReplicaList = BuildReplicaSummaryList(auxPods, eventWarnings) - auxResourceDeployment.Status = deploymentModels.ComponentStatusFromDeployment(&deployment).String() + auxResourceDeployment.Status = deploymentModels.ComponentStatusFromDeployment(component, &deployment, rd).String() return auxResourceDeployment } diff --git a/api/models/component.go b/api/models/component.go index 2a7502e4..23113a6f 100644 --- a/api/models/component.go +++ b/api/models/component.go @@ -69,8 +69,8 @@ func buildComponent( builder.WithPodNames(slice.Map(componentPods, func(pod corev1.Pod) string { return pod.Name })) builder.WithRadixEnvironmentVariables(getRadixEnvironmentVariables(componentPods)) builder.WithReplicaSummaryList(BuildReplicaSummaryList(componentPods, lastEventWarnings)) - builder.WithStatus(deploymentModels.GetComponentStatus(radixComponent, kd, rd)) - builder.WithAuxiliaryResource(getAuxiliaryResources(ra.Name, radixComponent, deploymentList, podList, lastEventWarnings)) + builder.WithStatus(deploymentModels.ComponentStatusFromDeployment(radixComponent, kd, rd)) + builder.WithAuxiliaryResource(getAuxiliaryResources(rd, radixComponent, deploymentList, podList, lastEventWarnings)) } // TODO: Use radixComponent.GetType() instead? diff --git a/api/utils/predicate/kubernetes.go b/api/utils/predicate/kubernetes.go index 766900b9..5aa4dfd2 100644 --- a/api/utils/predicate/kubernetes.go +++ b/api/utils/predicate/kubernetes.go @@ -20,7 +20,9 @@ func IsAppAliasIngress(ingress networkingv1.Ingress) bool { } func IsPodForComponent(appName, componentName string) func(corev1.Pod) bool { - selector := labels.SelectorFromSet(radixlabels.Merge(radixlabels.ForApplicationName(appName), radixlabels.ForComponentName(componentName))) + selector := labels.SelectorFromSet(radixlabels.Merge( + radixlabels.ForApplicationName(appName), + radixlabels.ForComponentName(componentName))) return func(pod corev1.Pod) bool { return selector.Matches(labels.Set(pod.Labels)) } diff --git a/swaggerui/html/swagger.json b/swaggerui/html/swagger.json index 2c6007e9..f3588258 100644 --- a/swaggerui/html/swagger.json +++ b/swaggerui/html/swagger.json @@ -2422,7 +2422,7 @@ "tags": [ "component" ], - "summary": "Deprecated Start component. Use Reset scaled component instead. This does the same thing, but naming is wrong. This endpoint will be removed after 1. september 2025.", + "summary": "Deprecated Start component. Use reset-scale instead. This does the same thing, but naming is wrong. This endpoint will be removed after 1. september 2025.", "operationId": "startComponent", "deprecated": true, "parameters": [ @@ -3759,7 +3759,7 @@ "tags": [ "environment" ], - "summary": "Deprecated. Use ResetManuallyStoppedComponentsInEnvironment that does the same thing, but with better naming. This method will be removed after 1. september 2025.", + "summary": "Deprecated. Use reset-scale instead that does the same thing, but with better naming. This method will be removed after 1. september 2025.", "operationId": "startEnvironment", "deprecated": true, "parameters": [ @@ -4980,6 +4980,47 @@ } } }, + "/applications/{appName}/reset-scale": { + "post": { + "tags": [ + "application" + ], + "summary": "Resets and resumes normal opperation for all manually stopped components in all environments of the application", + "operationId": "resetManuallyScaledComponentsInApplication", + "parameters": [ + { + "type": "string", + "description": "Name of application", + "name": "appName", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "Works only with custom setup of cluster. Allow impersonation of test users (Required if Impersonate-Group is set)", + "name": "Impersonate-User", + "in": "header" + }, + { + "type": "string", + "description": "Works only with custom setup of cluster. Allow impersonation of a comma-seperated list of test groups (Required if Impersonate-User is set)", + "name": "Impersonate-Group", + "in": "header" + } + ], + "responses": { + "200": { + "description": "Application started ok" + }, + "401": { + "description": "Unauthorized" + }, + "404": { + "description": "Not found" + } + } + } + }, "/applications/{appName}/restart": { "post": { "tags": [ @@ -5026,8 +5067,9 @@ "tags": [ "application" ], - "summary": "Start all components in all environments of the application", + "summary": "Deprecated. Use reset scale that does the same thing instead. This will be removed after 1. september 2025.", "operationId": "startApplication", + "deprecated": true, "parameters": [ { "type": "string", From 3e3048eef65a0f661ae10503ac0c636c0fa68a59 Mon Sep 17 00:00:00 2001 From: Richard87 Date: Mon, 2 Sep 2024 13:38:25 +0200 Subject: [PATCH 10/33] bugfixes --- api/deployments/models/component_deployment.go | 3 +-- api/deployments/models/component_status.go | 4 ++-- api/environments/component_spec.go | 7 +++++-- api/environments/environment_handler.go | 3 +++ 4 files changed, 11 insertions(+), 6 deletions(-) diff --git a/api/deployments/models/component_deployment.go b/api/deployments/models/component_deployment.go index 2fca84e0..87ec7dd9 100644 --- a/api/deployments/models/component_deployment.go +++ b/api/deployments/models/component_deployment.go @@ -89,10 +89,9 @@ type Component struct { // // required: false // example: 5 - // required: false // Extensions: // x-nullable: true - ReplicasOverride *int `json:"replicasOverride,omitempty"` + ReplicasOverride *int `json:"replicasOverride"` // HorizontalScaling defines horizontal scaling summary for this component // diff --git a/api/deployments/models/component_status.go b/api/deployments/models/component_status.go index 37ea5b0b..c9ff788d 100644 --- a/api/deployments/models/component_status.go +++ b/api/deployments/models/component_status.go @@ -43,7 +43,7 @@ func (p ComponentStatus) String() string { } func ComponentStatusFromDeployment(component radixv1.RadixCommonDeployComponent, kd *appsv1.Deployment, rd *radixv1.RadixDeployment) ComponentStatus { - if kd == nil { + if kd == nil || kd.GetName() == "" { return ComponentReconciling } replicasUnavailable := kd.Status.UnavailableReplicas @@ -63,7 +63,7 @@ func ComponentStatusFromDeployment(component radixv1.RadixCommonDeployComponent, return ComponentReconciling } - if owner.VerifyCorrectObjectGeneration(rd, kd, kube.RadixDeploymentObservedGeneration) { + if !owner.VerifyCorrectObjectGeneration(rd, kd, kube.RadixDeploymentObservedGeneration) { return ComponentOutdated } diff --git a/api/environments/component_spec.go b/api/environments/component_spec.go index 38e44431..e0879689 100644 --- a/api/environments/component_spec.go +++ b/api/environments/component_spec.go @@ -61,8 +61,11 @@ func getComponentStateFromSpec(ctx context.Context, kubeClient kubernetes.Interf return nil, err } - kd, _ = slice.FindFirst(deployments, predicate.IsDeploymentForAuxComponent(rd.Spec.AppName, component.GetName(), defaults.OAuthProxyAuxiliaryComponentType)) - status = deploymentModels.ComponentStatusFromDeployment(component, &kd, rd) + if kd, ok := slice.FindFirst(deployments, predicate.IsDeploymentForAuxComponent(rd.Spec.AppName, component.GetName(), defaults.OAuthProxyAuxiliaryComponentType)); ok { + status = deploymentModels.ComponentStatusFromDeployment(component, &kd, rd) + } else { + status = deploymentModels.ComponentReconciling + } } componentBuilder := deploymentModels.NewComponentBuilder() diff --git a/api/environments/environment_handler.go b/api/environments/environment_handler.go index c4150957..cdb32706 100644 --- a/api/environments/environment_handler.go +++ b/api/environments/environment_handler.go @@ -18,6 +18,7 @@ import ( "github.com/equinor/radix-api/api/utils/predicate" "github.com/equinor/radix-api/api/utils/tlsvalidation" "github.com/equinor/radix-api/models" + "github.com/equinor/radix-common/net/http" radixutils "github.com/equinor/radix-common/utils" "github.com/equinor/radix-common/utils/slice" deployUtils "github.com/equinor/radix-operator/pkg/apis/deployment" @@ -407,6 +408,8 @@ func (eh EnvironmentHandler) getRadixCommonComponentUpdater(ctx context.Context, rd, err := kubequery.GetLatestRadixDeployment(ctx, eh.accounts.UserAccount.RadixClient, appName, envName) if err != nil { return nil, err + } else if rd == nil { + return nil, http.ValidationError(v1.KindRadixDeployment, "no radix deployments found") } baseUpdater := &baseComponentUpdater{ appName: appName, From 18e416b8389bb5926134c1ac55f734e878174ef4 Mon Sep 17 00:00:00 2001 From: Richard87 Date: Mon, 2 Sep 2024 13:47:02 +0200 Subject: [PATCH 11/33] bugfixesuse status from component, not auxiliar service --- api/environments/component_spec.go | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/api/environments/component_spec.go b/api/environments/component_spec.go index e0879689..d206ca54 100644 --- a/api/environments/component_spec.go +++ b/api/environments/component_spec.go @@ -45,9 +45,6 @@ func getComponentStateFromSpec(ctx context.Context, kubeClient kubernetes.Interf return nil, err } - // TODO: Make sure we select the cdeployment, not the aux deplyment with same labels!!! - kd, _ := slice.FindFirst(deployments, predicate.IsDeploymentForComponent(rd.Spec.AppName, component.GetName())) - componentPodNames = getPodNames(componentPods) environmentVariables = getRadixEnvironmentVariables(componentPods) eventList, err := kubequery.GetEventsForEnvironment(ctx, kubeClient, rd.Spec.AppName, rd.Spec.Environment) @@ -61,11 +58,8 @@ func getComponentStateFromSpec(ctx context.Context, kubeClient kubernetes.Interf return nil, err } - if kd, ok := slice.FindFirst(deployments, predicate.IsDeploymentForAuxComponent(rd.Spec.AppName, component.GetName(), defaults.OAuthProxyAuxiliaryComponentType)); ok { - status = deploymentModels.ComponentStatusFromDeployment(component, &kd, rd) - } else { - status = deploymentModels.ComponentReconciling - } + kd, _ := slice.FindFirst(deployments, predicate.IsDeploymentForComponent(rd.Spec.AppName, component.GetName())) + status = deploymentModels.ComponentStatusFromDeployment(component, &kd, rd) } componentBuilder := deploymentModels.NewComponentBuilder() From f6d099165f0d3efd82ff271540169a7480b16f93 Mon Sep 17 00:00:00 2001 From: Richard87 Date: Tue, 3 Sep 2024 10:37:14 +0200 Subject: [PATCH 12/33] upadte tests --- api/deployments/models/component_status.go | 16 +- .../models/component_status_test.go | 73 ++++++ api/environments/component_spec_test.go | 219 ------------------ 3 files changed, 81 insertions(+), 227 deletions(-) create mode 100644 api/deployments/models/component_status_test.go delete mode 100644 api/environments/component_spec_test.go diff --git a/api/deployments/models/component_status.go b/api/deployments/models/component_status.go index c9ff788d..04626061 100644 --- a/api/deployments/models/component_status.go +++ b/api/deployments/models/component_status.go @@ -50,23 +50,23 @@ func ComponentStatusFromDeployment(component radixv1.RadixCommonDeployComponent, replicasReady := kd.Status.ReadyReplicas replicas := pointers.Val(kd.Spec.Replicas) - if replicas == 0 { - return StoppedComponent - } - if isCopmonentRestarting(component, rd) { return ComponentRestarting } + if !owner.VerifyCorrectObjectGeneration(rd, kd, kube.RadixDeploymentObservedGeneration) { + return ComponentOutdated + } + + if replicas == 0 { + return StoppedComponent + } + // Check if component is scaling up or down if replicasUnavailable > 0 || replicas < replicasReady { return ComponentReconciling } - if !owner.VerifyCorrectObjectGeneration(rd, kd, kube.RadixDeploymentObservedGeneration) { - return ComponentOutdated - } - return ConsistentComponent } diff --git a/api/deployments/models/component_status_test.go b/api/deployments/models/component_status_test.go new file mode 100644 index 00000000..29c592a8 --- /dev/null +++ b/api/deployments/models/component_status_test.go @@ -0,0 +1,73 @@ +package models_test + +import ( + "testing" + "time" + + "github.com/equinor/radix-api/api/deployments/models" + radixutils "github.com/equinor/radix-common/utils" + "github.com/equinor/radix-common/utils/pointers" + operatordefaults "github.com/equinor/radix-operator/pkg/apis/defaults" + "github.com/equinor/radix-operator/pkg/apis/kube" + radixv1 "github.com/equinor/radix-operator/pkg/apis/radix/v1" + "github.com/stretchr/testify/assert" + appsv1 "k8s.io/api/apps/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func TestNoKubeDeployments_IsReconciling(t *testing.T) { + status := models.ComponentStatusFromDeployment(&radixv1.RadixDeployComponent{}, nil, nil) + assert.Equal(t, models.ComponentReconciling, status) +} + +func TestKubeDeploymentsWithRestartLabel_IsRestarting(t *testing.T) { + status := models.ComponentStatusFromDeployment( + &radixv1.RadixDeployComponent{EnvironmentVariables: map[string]string{operatordefaults.RadixRestartEnvironmentVariable: radixutils.FormatTimestamp(time.Now())}}, + createKubeDeployment(0), + &radixv1.RadixDeployment{ + ObjectMeta: metav1.ObjectMeta{Generation: 2}, + Status: radixv1.RadixDeployStatus{Reconciled: metav1.NewTime(time.Now().Add(-10 * time.Minute))}, + }) + + assert.Equal(t, models.ComponentRestarting, status) +} + +func TestKubeDeploymentsWithoutReplicas_IsStopped(t *testing.T) { + status := models.ComponentStatusFromDeployment( + &radixv1.RadixDeployComponent{}, + createKubeDeployment(0), + &radixv1.RadixDeployment{ + ObjectMeta: metav1.ObjectMeta{Generation: 1}, + }) + assert.Equal(t, models.StoppedComponent, status) +} + +func TestKubeDeployment_IsConsistent(t *testing.T) { + status := models.ComponentStatusFromDeployment( + &radixv1.RadixDeployComponent{}, + createKubeDeployment(1), + &radixv1.RadixDeployment{ + ObjectMeta: metav1.ObjectMeta{Generation: 1}, + }) + assert.Equal(t, models.ConsistentComponent, status) +} + +func TestKubeDeployment_IsOutdated(t *testing.T) { + status := models.ComponentStatusFromDeployment( + &radixv1.RadixDeployComponent{}, + createKubeDeployment(1), + &radixv1.RadixDeployment{ + ObjectMeta: metav1.ObjectMeta{Generation: 2}, + }) + assert.Equal(t, models.ComponentOutdated, status) +} + +func createKubeDeployment(replicas int32) *appsv1.Deployment { + return &appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{ + Name: "helloworld", + Annotations: map[string]string{kube.RadixDeploymentObservedGeneration: "1"}, + OwnerReferences: []metav1.OwnerReference{{Controller: pointers.Ptr(true)}}}, + Spec: appsv1.DeploymentSpec{Replicas: pointers.Ptr[int32](replicas)}, + } +} diff --git a/api/environments/component_spec_test.go b/api/environments/component_spec_test.go deleted file mode 100644 index 5d8374c6..00000000 --- a/api/environments/component_spec_test.go +++ /dev/null @@ -1,219 +0,0 @@ -package environments - -import ( - "testing" - "time" - - v1 "github.com/equinor/radix-operator/pkg/apis/radix/v1" - "github.com/stretchr/testify/assert" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -func TestRunningReplicaDiffersFromConfig_NoHPA(t *testing.T) { - // Test replicas 2, pods 3 - replicas := 2 - raEnvironmentConfig := &v1.RadixEnvironmentConfig{ - Replicas: &replicas, - } - actualPods := []corev1.Pod{{}, {}, {}} - isDifferent := runningReplicaDiffersFromConfig(raEnvironmentConfig, actualPods) - assert.True(t, isDifferent) - - // Test replicas 2, pods 2 - actualPods = []corev1.Pod{{}, {}} - isDifferent = runningReplicaDiffersFromConfig(raEnvironmentConfig, actualPods) - assert.False(t, isDifferent) - - // Test replicas 0, pods 2 - replicas = 0 - isDifferent = runningReplicaDiffersFromConfig(raEnvironmentConfig, actualPods) - assert.True(t, isDifferent) - - // Test replicas nil, pods 2 - raEnvironmentConfig.Replicas = nil - isDifferent = runningReplicaDiffersFromConfig(raEnvironmentConfig, actualPods) - assert.True(t, isDifferent) - - // Test RadixEnvironmentConfig nil - raEnvironmentConfig = nil - isDifferent = runningReplicaDiffersFromConfig(raEnvironmentConfig, actualPods) - assert.True(t, isDifferent) -} -func TestRunningReplicaDiffersFromConfig_WithHPA(t *testing.T) { - // Test replicas 0, pods 3, minReplicas 2, maxReplicas 6 - replicas := 0 - minReplicas := int32(2) - raEnvironmentConfig := &v1.RadixEnvironmentConfig{ - Replicas: &replicas, - HorizontalScaling: &v1.RadixHorizontalScaling{ - MinReplicas: &minReplicas, - MaxReplicas: 6, - }, - } - actualPods := []corev1.Pod{{}, {}, {}} - isDifferent := runningReplicaDiffersFromConfig(raEnvironmentConfig, actualPods) - assert.True(t, isDifferent) - - // Test replicas 4, pods 3, minReplicas 2, maxReplicas 6 - replicas = 4 - isDifferent = runningReplicaDiffersFromConfig(raEnvironmentConfig, actualPods) - assert.False(t, isDifferent) - - // Test replicas 4, pods 1, minReplicas 2, maxReplicas 6 - actualPods = []corev1.Pod{{}} - isDifferent = runningReplicaDiffersFromConfig(raEnvironmentConfig, actualPods) - assert.True(t, isDifferent) - - // Test replicas 4, pods 1, minReplicas nil, maxReplicas 6 - raEnvironmentConfig.HorizontalScaling.MinReplicas = nil - isDifferent = runningReplicaDiffersFromConfig(raEnvironmentConfig, actualPods) - assert.False(t, isDifferent) -} - -func TestRunningReplicaDiffersFromSpec_NoHPA(t *testing.T) { - // Test replicas 0, pods 1 - replicas := 0 - rdComponent := v1.RadixDeployComponent{ - Replicas: &replicas, - } - actualPods := []corev1.Pod{{}} - isDifferent := runningReplicaDiffersFromSpec(&rdComponent, actualPods) - assert.True(t, isDifferent) - - // Test replicas 1, pods 1 - replicas = 1 - isDifferent = runningReplicaDiffersFromSpec(&rdComponent, actualPods) - assert.False(t, isDifferent) - - // Test replicas nil, pods 1 - rdComponent.Replicas = nil - isDifferent = runningReplicaDiffersFromSpec(&rdComponent, actualPods) - assert.False(t, isDifferent) -} - -func TestRunningReplicaDiffersFromSpec_WithHPA(t *testing.T) { - // Test replicas 0, pods 1, minReplicas 2, maxReplicas 6 - replicas := 0 - minReplicas := int32(2) - rdComponent := v1.RadixDeployComponent{ - Replicas: &replicas, - HorizontalScaling: &v1.RadixHorizontalScaling{ - MinReplicas: &minReplicas, - MaxReplicas: 6, - }, - } - actualPods := []corev1.Pod{{}} - isDifferent := runningReplicaDiffersFromSpec(&rdComponent, actualPods) - assert.True(t, isDifferent) - - // Test replicas 1, pods 1, minReplicas 2, maxReplicas 6 - replicas = 1 - isDifferent = runningReplicaDiffersFromSpec(&rdComponent, actualPods) - assert.True(t, isDifferent) - - // Test replicas 1, pods 3, minReplicas 2, maxReplicas 6 - actualPods = []corev1.Pod{{}, {}, {}} - isDifferent = runningReplicaDiffersFromSpec(&rdComponent, actualPods) - assert.False(t, isDifferent) - - // Test replicas 1, pods 3, minReplicas nil, maxReplicas 6 - rdComponent.HorizontalScaling.MinReplicas = nil - isDifferent = runningReplicaDiffersFromSpec(&rdComponent, actualPods) - assert.False(t, isDifferent) -} - -func TestRunningReplicaOutdatedImage(t *testing.T) { - // Test replicas 0, pods 1, minReplicas 2, maxReplicas 6 - replicas := 0 - minReplicas := int32(2) - rdComponent := v1.RadixDeployComponent{ - Image: "not-outdated", - Replicas: &replicas, - HorizontalScaling: &v1.RadixHorizontalScaling{ - MinReplicas: &minReplicas, - MaxReplicas: 6, - }, - } - - actualPods := []corev1.Pod{ - { - Spec: corev1.PodSpec{ - Containers: []corev1.Container{ - { - Name: "test", - Image: "outdated", - }, - }, - }, - }, - } - - isOutdated := runningReplicaIsOutdated(&rdComponent, actualPods) - assert.True(t, isOutdated) - -} - -func TestRunningReplicaNotOutdatedImage_(t *testing.T) { - // Test replicas 0, pods 1, minReplicas 2, maxReplicas 6 - replicas := 0 - minReplicas := int32(2) - rdComponent := v1.RadixDeployComponent{ - Image: "not-outdated", - Replicas: &replicas, - HorizontalScaling: &v1.RadixHorizontalScaling{ - MinReplicas: &minReplicas, - MaxReplicas: 6, - }, - } - - actualPods := []corev1.Pod{ - { - Spec: corev1.PodSpec{ - Containers: []corev1.Container{ - { - Name: "test", - Image: "not-outdated", - }, - }, - }, - }, - } - - isOutdated := runningReplicaIsOutdated(&rdComponent, actualPods) - assert.False(t, isOutdated) -} -func TestRunningReplicaNotOutdatedImage_TerminatingPod(t *testing.T) { - // Test replicas 0, pods 1, minReplicas 2, maxReplicas 6 - replicas := 0 - minReplicas := int32(2) - rdComponent := v1.RadixDeployComponent{ - Image: "not-outdated", - Replicas: &replicas, - HorizontalScaling: &v1.RadixHorizontalScaling{ - MinReplicas: &minReplicas, - MaxReplicas: 6, - }, - } - - actualPods := []corev1.Pod{ - { - ObjectMeta: metav1.ObjectMeta{ - DeletionTimestamp: &metav1.Time{ - Time: time.Now(), - }, - }, - Spec: corev1.PodSpec{ - Containers: []corev1.Container{ - { - Name: "test", - Image: "not-outdated", - }, - }, - }, - }, - } - - isOutdated := runningReplicaIsOutdated(&rdComponent, actualPods) - assert.False(t, isOutdated) -} From 0ffbe311f4af50f7dd16164f2a0e2c168e8979d1 Mon Sep 17 00:00:00 2001 From: Richard Hagen Date: Tue, 3 Sep 2024 13:34:21 +0200 Subject: [PATCH 13/33] Update api/deployments/models/component_deployment.go Co-authored-by: Sergey Smolnikov --- api/deployments/models/component_deployment.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/deployments/models/component_deployment.go b/api/deployments/models/component_deployment.go index 87ec7dd9..fc4e73ef 100644 --- a/api/deployments/models/component_deployment.go +++ b/api/deployments/models/component_deployment.go @@ -85,7 +85,7 @@ type Component struct { // required: false ReplicaList []ReplicaSummary `json:"replicaList"` - // Set if manuall control of replicas is in place. null means automatic controll, 0 means stopped and >= 1 is manually scaled. + // Set if manual control of replicas is in place. Not set means automatic control, 0 means stopped and >= 1 is manually scaled. // // required: false // example: 5 From c4306a70104684179af398eb43d78d2fd2bf4bd3 Mon Sep 17 00:00:00 2001 From: Richard Hagen Date: Tue, 3 Sep 2024 13:34:31 +0200 Subject: [PATCH 14/33] Update api/environments/component_handler.go Co-authored-by: Sergey Smolnikov --- api/environments/component_handler.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/environments/component_handler.go b/api/environments/component_handler.go index 20fe0077..c6348c99 100644 --- a/api/environments/component_handler.go +++ b/api/environments/component_handler.go @@ -49,7 +49,7 @@ func (eh EnvironmentHandler) ScaleComponent(ctx context.Context, appName, envNam // ResetScaledComponent Starts a component func (eh EnvironmentHandler) ResetScaledComponent(ctx context.Context, appName, envName, componentName string, ignoreComponentStatusError bool) error { - log.Ctx(ctx).Info().Msgf("Resetting manuall scaled component %s, %s", componentName, appName) + log.Ctx(ctx).Info().Msgf("Resetting manually scaled component %s, %s", componentName, appName) updater, err := eh.getRadixCommonComponentUpdater(ctx, appName, envName, componentName) if err != nil { return err From 3a5f555c606cc154e4a3da8f1411c7ed6cfd40ff Mon Sep 17 00:00:00 2001 From: Richard Hagen Date: Tue, 3 Sep 2024 13:35:21 +0200 Subject: [PATCH 15/33] Update api/deployments/models/component_status.go Co-authored-by: Sergey Smolnikov --- api/deployments/models/component_status.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/deployments/models/component_status.go b/api/deployments/models/component_status.go index 04626061..82bd1830 100644 --- a/api/deployments/models/component_status.go +++ b/api/deployments/models/component_status.go @@ -70,7 +70,7 @@ func ComponentStatusFromDeployment(component radixv1.RadixCommonDeployComponent, return ConsistentComponent } -func isCopmonentRestarting(component radixv1.RadixCommonDeployComponent, rd *radixv1.RadixDeployment) bool { +func isComponentRestarting(component radixv1.RadixCommonDeployComponent, rd *radixv1.RadixDeployment) bool { restarted := component.GetEnvironmentVariables()[operatordefaults.RadixRestartEnvironmentVariable] if strings.EqualFold(restarted, "") { return false From 302f3f7ff8078a39a4c44cfdb74d11454dc111b3 Mon Sep 17 00:00:00 2001 From: Richard Hagen Date: Tue, 3 Sep 2024 13:35:26 +0200 Subject: [PATCH 16/33] Update api/deployments/models/component_status.go Co-authored-by: Sergey Smolnikov --- api/deployments/models/component_status.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/deployments/models/component_status.go b/api/deployments/models/component_status.go index 82bd1830..2b822aff 100644 --- a/api/deployments/models/component_status.go +++ b/api/deployments/models/component_status.go @@ -50,7 +50,7 @@ func ComponentStatusFromDeployment(component radixv1.RadixCommonDeployComponent, replicasReady := kd.Status.ReadyReplicas replicas := pointers.Val(kd.Spec.Replicas) - if isCopmonentRestarting(component, rd) { + if isComponentRestarting(component, rd) { return ComponentRestarting } From f2dd8585010c010eac9d45541b6b316e4917b1c2 Mon Sep 17 00:00:00 2001 From: Richard Hagen Date: Tue, 3 Sep 2024 13:35:35 +0200 Subject: [PATCH 17/33] Update api/deployments/models/component_status.go Co-authored-by: Sergey Smolnikov --- api/deployments/models/component_status.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/deployments/models/component_status.go b/api/deployments/models/component_status.go index 2b822aff..02339089 100644 --- a/api/deployments/models/component_status.go +++ b/api/deployments/models/component_status.go @@ -72,7 +72,7 @@ func ComponentStatusFromDeployment(component radixv1.RadixCommonDeployComponent, func isComponentRestarting(component radixv1.RadixCommonDeployComponent, rd *radixv1.RadixDeployment) bool { restarted := component.GetEnvironmentVariables()[operatordefaults.RadixRestartEnvironmentVariable] - if strings.EqualFold(restarted, "") { + if restarted == "" { return false } restartedTime, err := commonutils.ParseTimestamp(restarted) From 6da2ded1ef89a0f7dd6819298f00b4bbdb3cf705 Mon Sep 17 00:00:00 2001 From: Richard Hagen Date: Tue, 3 Sep 2024 13:35:50 +0200 Subject: [PATCH 18/33] Update api/deployments/models/component_status.go Co-authored-by: Sergey Smolnikov --- api/deployments/models/component_status.go | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/api/deployments/models/component_status.go b/api/deployments/models/component_status.go index 02339089..58636593 100644 --- a/api/deployments/models/component_status.go +++ b/api/deployments/models/component_status.go @@ -81,8 +81,5 @@ func isComponentRestarting(component radixv1.RadixCommonDeployComponent, rd *rad return false } reconciledTime := rd.Status.Reconciled - if reconciledTime.IsZero() || restartedTime.After(reconciledTime.Time) { - return true - } - return false + return reconciledTime.IsZero() || restartedTime.After(reconciledTime.Time) } From e146ad1679d3cbcd50adaa588b3ee4eb87da7c58 Mon Sep 17 00:00:00 2001 From: Richard87 Date: Tue, 3 Sep 2024 13:43:08 +0200 Subject: [PATCH 19/33] fix panic in tests --- .../environment_controller_test.go | 22 ++++++++++--------- api/kubequery/radixapplication_test.go | 1 - api/kubequery/radixjob_test.go | 1 - api/kubequery/radixregistration_test.go | 1 - 4 files changed, 12 insertions(+), 13 deletions(-) diff --git a/api/environments/environment_controller_test.go b/api/environments/environment_controller_test.go index d94a0bc6..fd732a62 100644 --- a/api/environments/environment_controller_test.go +++ b/api/environments/environment_controller_test.go @@ -25,6 +25,7 @@ import ( radixhttp "github.com/equinor/radix-common/net/http" radixutils "github.com/equinor/radix-common/utils" "github.com/equinor/radix-common/utils/numbers" + "github.com/equinor/radix-common/utils/pointers" "github.com/equinor/radix-common/utils/slice" operatordefaults "github.com/equinor/radix-operator/pkg/apis/defaults" "github.com/equinor/radix-operator/pkg/apis/kube" @@ -499,7 +500,7 @@ func TestRestartComponent_ApplicationWithDeployment_EnvironmentConsistent(t *tes response = <-responseChannel // Since pods are not appearing out of nowhere with kubernetes-fake, the component will be in // a reconciling state and cannot be restarted - assert.Equal(t, http.StatusBadRequest, response.Code) + require.Equal(t, http.StatusBadRequest, response.Code) errorResponse, _ := controllertest.GetErrorResponse(response) expectedError := environmentModels.CannotRestartComponent(anyAppName, stoppedComponent, deploymentModels.ComponentReconciling.String()) @@ -520,20 +521,20 @@ func TestStartComponent_ApplicationWithDeployment_EnvironmentConsistent(t *testi t.Run("Component Start Succeeds", func(t *testing.T) { component := findComponentInDeployment(rd, stoppedComponent1) - assert.True(t, *component.Replicas == zeroReplicas) + assert.True(t, *component.ReplicasOverride == zeroReplicas) responseChannel := environmentControllerTestUtils.ExecuteRequest("POST", fmt.Sprintf("/api/v1/applications/%s/environments/%s/components/%s/start", anyAppName, anyEnvironment, stoppedComponent1)) response := <-responseChannel - assert.Equal(t, http.StatusOK, response.Code) + require.Equal(t, http.StatusOK, response.Code) updatedRd, _ := radixclient.RadixV1().RadixDeployments(rd.GetNamespace()).Get(context.Background(), rd.GetName(), metav1.GetOptions{}) component = findComponentInDeployment(updatedRd, stoppedComponent1) - assert.True(t, *component.Replicas > zeroReplicas) + assert.Nil(t, component.ReplicasOverride) }) t.Run("Component Start Fails", func(t *testing.T) { component := findComponentInDeployment(rd, stoppedComponent2) - assert.True(t, *component.Replicas == zeroReplicas) + assert.True(t, *component.ReplicasOverride == zeroReplicas) // Create pod _, err := createComponentPod(client, rd.GetNamespace(), stoppedComponent2) @@ -543,7 +544,7 @@ func TestStartComponent_ApplicationWithDeployment_EnvironmentConsistent(t *testi response := <-responseChannel // Since pods are not appearing out of nowhere with kubernetes-fake, the component will be in // a reconciling state and cannot be started - assert.Equal(t, http.StatusBadRequest, response.Code) + require.Equal(t, http.StatusBadRequest, response.Code) errorResponse, _ := controllertest.GetErrorResponse(response) expectedError := environmentModels.CannotResetScaledComponent(anyAppName, stoppedComponent2) @@ -579,17 +580,17 @@ func TestStopComponent_ApplicationWithDeployment_EnvironmentConsistent(t *testin updatedRd, _ := radixclient.RadixV1().RadixDeployments(rd.GetNamespace()).Get(context.Background(), rd.GetName(), metav1.GetOptions{}) component = findComponentInDeployment(updatedRd, runningComponent) - assert.True(t, *component.Replicas == zeroReplicas) + assert.True(t, *component.GetReplicasOverride() == zeroReplicas) }) t.Run("Stop Component Fails", func(t *testing.T) { component := findComponentInDeployment(rd, stoppedComponent) - assert.True(t, *component.Replicas == zeroReplicas) + assert.True(t, *component.ReplicasOverride == zeroReplicas) responseChannel := environmentControllerTestUtils.ExecuteRequest("POST", fmt.Sprintf("/api/v1/applications/%s/environments/%s/components/%s/stop", anyAppName, anyEnvironment, stoppedComponent)) response := <-responseChannel // The component is in a stopped state since replicas in spec = 0, and therefore cannot be stopped again - assert.Equal(t, http.StatusBadRequest, response.Code) + require.Equal(t, http.StatusBadRequest, response.Code) errorResponse, _ := controllertest.GetErrorResponse(response) expectedError := environmentModels.CannotStopComponent(anyAppName, stoppedComponent, deploymentModels.StoppedComponent.String()) @@ -2908,7 +2909,8 @@ func createRadixDeploymentWithReplicas(tu *commontest.Utils, appName, envName st operatorutils. NewDeployComponentBuilder(). WithName(component.name). - WithReplicas(numbers.IntPtr(component.number)), + WithReplicas(pointers.Ptr(component.number)). + WithReplicasOverride(pointers.Ptr(component.number)), ) } diff --git a/api/kubequery/radixapplication_test.go b/api/kubequery/radixapplication_test.go index 2989ee09..19e6ce2c 100644 --- a/api/kubequery/radixapplication_test.go +++ b/api/kubequery/radixapplication_test.go @@ -25,5 +25,4 @@ func Test_GetRadixApplication(t *testing.T) { // Get non-existing RA (wrong namespace) actual, err = GetRadixApplication(context.Background(), client, "app2") assert.True(t, errors.IsNotFound(err)) - assert.Nil(t, actual) } diff --git a/api/kubequery/radixjob_test.go b/api/kubequery/radixjob_test.go index 62a6d600..87df2756 100644 --- a/api/kubequery/radixjob_test.go +++ b/api/kubequery/radixjob_test.go @@ -39,5 +39,4 @@ func Test_GetRadixJob(t *testing.T) { // Get non-existing RJ actual, err = GetRadixJob(context.Background(), client, "app2", "unmatched") assert.True(t, errors.IsNotFound(err)) - assert.Nil(t, actual) } diff --git a/api/kubequery/radixregistration_test.go b/api/kubequery/radixregistration_test.go index 500e2682..f4ff79c9 100644 --- a/api/kubequery/radixregistration_test.go +++ b/api/kubequery/radixregistration_test.go @@ -24,5 +24,4 @@ func Test_GetRadixRegistration(t *testing.T) { // Get non-existing RR actual, err = GetRadixRegistration(context.Background(), client, "anyapp") assert.True(t, errors.IsNotFound(err)) - assert.Nil(t, actual) } From 70b68a3b9719ef1fd82fab02a53e4acce47d240c Mon Sep 17 00:00:00 2001 From: Richard87 Date: Tue, 3 Sep 2024 14:20:23 +0200 Subject: [PATCH 20/33] fix lint bugs --- api/deployments/models/component_status.go | 2 -- api/kubequery/radixapplication_test.go | 2 +- api/kubequery/radixjob_test.go | 2 +- api/kubequery/radixregistration_test.go | 2 +- 4 files changed, 3 insertions(+), 5 deletions(-) diff --git a/api/deployments/models/component_status.go b/api/deployments/models/component_status.go index 58636593..a1ab761d 100644 --- a/api/deployments/models/component_status.go +++ b/api/deployments/models/component_status.go @@ -1,8 +1,6 @@ package models import ( - "strings" - "github.com/equinor/radix-api/api/utils/owner" commonutils "github.com/equinor/radix-common/utils" "github.com/equinor/radix-common/utils/pointers" diff --git a/api/kubequery/radixapplication_test.go b/api/kubequery/radixapplication_test.go index 19e6ce2c..321aea3a 100644 --- a/api/kubequery/radixapplication_test.go +++ b/api/kubequery/radixapplication_test.go @@ -23,6 +23,6 @@ func Test_GetRadixApplication(t *testing.T) { assert.Equal(t, &matched, actual) // Get non-existing RA (wrong namespace) - actual, err = GetRadixApplication(context.Background(), client, "app2") + _, err = GetRadixApplication(context.Background(), client, "app2") assert.True(t, errors.IsNotFound(err)) } diff --git a/api/kubequery/radixjob_test.go b/api/kubequery/radixjob_test.go index 87df2756..5ac3120e 100644 --- a/api/kubequery/radixjob_test.go +++ b/api/kubequery/radixjob_test.go @@ -37,6 +37,6 @@ func Test_GetRadixJob(t *testing.T) { assert.Equal(t, &matched, actual) // Get non-existing RJ - actual, err = GetRadixJob(context.Background(), client, "app2", "unmatched") + _, err = GetRadixJob(context.Background(), client, "app2", "unmatched") assert.True(t, errors.IsNotFound(err)) } diff --git a/api/kubequery/radixregistration_test.go b/api/kubequery/radixregistration_test.go index f4ff79c9..5e94c4d5 100644 --- a/api/kubequery/radixregistration_test.go +++ b/api/kubequery/radixregistration_test.go @@ -22,6 +22,6 @@ func Test_GetRadixRegistration(t *testing.T) { assert.Equal(t, &matched, actual) // Get non-existing RR - actual, err = GetRadixRegistration(context.Background(), client, "anyapp") + _, err = GetRadixRegistration(context.Background(), client, "anyapp") assert.True(t, errors.IsNotFound(err)) } From 00be7a98d3fef181dc0a0aa4cb9d1a86185a68df Mon Sep 17 00:00:00 2001 From: Richard Hagen Date: Wed, 4 Sep 2024 10:08:49 +0200 Subject: [PATCH 21/33] Update api/environments/environment_handler.go Co-authored-by: Sergey Smolnikov --- api/environments/environment_handler.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/api/environments/environment_handler.go b/api/environments/environment_handler.go index cdb32706..784a290c 100644 --- a/api/environments/environment_handler.go +++ b/api/environments/environment_handler.go @@ -329,9 +329,8 @@ func (eh EnvironmentHandler) ResetManuallyStoppedComponentsInEnvironment(ctx con log.Ctx(ctx).Info().Msgf("Starting components in environment %s, %s", envName, appName) for _, deployComponent := range radixDeployment.Spec.Components { - if overide := deployComponent.GetReplicasOverride(); overide != nil && *overide == 0 { - err := eh.ResetScaledComponent(ctx, appName, envName, deployComponent.GetName(), true) - if err != nil { + if override := deployComponent.GetReplicasOverride(); override != nil && *override == 0 { + if err := eh.ResetScaledComponent(ctx, appName, envName, deployComponent.GetName(), true); err != nil { return err } } From b985ceb89a51537a66e8b3e762ae4585656318db Mon Sep 17 00:00:00 2001 From: Richard Hagen Date: Wed, 4 Sep 2024 10:09:45 +0200 Subject: [PATCH 22/33] Update api/environments/environment_handler.go Co-authored-by: Sergey Smolnikov --- api/environments/environment_handler.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/api/environments/environment_handler.go b/api/environments/environment_handler.go index 784a290c..6e9ce48c 100644 --- a/api/environments/environment_handler.go +++ b/api/environments/environment_handler.go @@ -407,7 +407,8 @@ func (eh EnvironmentHandler) getRadixCommonComponentUpdater(ctx context.Context, rd, err := kubequery.GetLatestRadixDeployment(ctx, eh.accounts.UserAccount.RadixClient, appName, envName) if err != nil { return nil, err - } else if rd == nil { + } + if rd == nil { return nil, http.ValidationError(v1.KindRadixDeployment, "no radix deployments found") } baseUpdater := &baseComponentUpdater{ From b8ecd6520b81070d1f7af0a2bb4e1caae01faeff Mon Sep 17 00:00:00 2001 From: Richard87 Date: Wed, 4 Sep 2024 10:11:18 +0200 Subject: [PATCH 23/33] remove unused function --- api/kubequery/deployment.go | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/api/kubequery/deployment.go b/api/kubequery/deployment.go index 1376a210..552ceeec 100644 --- a/api/kubequery/deployment.go +++ b/api/kubequery/deployment.go @@ -3,7 +3,6 @@ package kubequery import ( "context" - "github.com/equinor/radix-api/api/utils/labelselector" operatorUtils "github.com/equinor/radix-operator/pkg/apis/utils" "github.com/equinor/radix-operator/pkg/apis/utils/labels" appsv1 "k8s.io/api/apps/v1" @@ -20,19 +19,3 @@ func GetDeploymentsForEnvironment(ctx context.Context, client kubernetes.Interfa } return deployments.Items, nil } - -// GetDeploymentForComponent returns all the first deployment matching the specified application, environment and component name. -func GetDeploymentForComponent(ctx context.Context, client kubernetes.Interface, appName, envName, componentName string) (*appsv1.Deployment, error) { - ns := operatorUtils.GetEnvironmentNamespace(appName, envName) - selector := labelselector.ForComponent(appName, componentName).String() - deployments, err := client.AppsV1().Deployments(ns).List(ctx, metav1.ListOptions{LabelSelector: selector}) - if err != nil { - return nil, err - } - - if len(deployments.Items) == 0 { - return nil, nil - } - - return &deployments.Items[0], nil -} From 0fae6c2bf8799bfbb8db649e1b3ffc0891266c1c Mon Sep 17 00:00:00 2001 From: Richard87 Date: Wed, 4 Sep 2024 10:14:14 +0200 Subject: [PATCH 24/33] Add documentation --- api/kubequery/radixdeployment.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/api/kubequery/radixdeployment.go b/api/kubequery/radixdeployment.go index 7795d409..f6b85c61 100644 --- a/api/kubequery/radixdeployment.go +++ b/api/kubequery/radixdeployment.go @@ -74,6 +74,8 @@ func GetRadixDeploymentByName(ctx context.Context, radixClient radixclient.Inter ns := operatorUtils.GetEnvironmentNamespace(appName, envName) return radixClient.RadixV1().RadixDeployments(ns).Get(ctx, deploymentName, metav1.GetOptions{}) } + +// GetLatestRadixDeployment returns the last active Radix Deployment found in environment, will be nil if not found func GetLatestRadixDeployment(ctx context.Context, radixClient radixclient.Interface, appName, envName string) (*radixv1.RadixDeployment, error) { ns := operatorUtils.GetEnvironmentNamespace(appName, envName) rdList, err := radixClient.RadixV1().RadixDeployments(ns).List(ctx, metav1.ListOptions{}) From 23a15e427a48f96f75d254cb83258c6df15e3821 Mon Sep 17 00:00:00 2001 From: Richard87 Date: Wed, 4 Sep 2024 10:22:11 +0200 Subject: [PATCH 25/33] return err if deployment not found --- api/environments/component_handler.go | 4 ++++ api/environments/environment_handler.go | 9 +++++++++ 2 files changed, 13 insertions(+) diff --git a/api/environments/component_handler.go b/api/environments/component_handler.go index c6348c99..a97ef77c 100644 --- a/api/environments/component_handler.go +++ b/api/environments/component_handler.go @@ -9,6 +9,7 @@ import ( environmentModels "github.com/equinor/radix-api/api/environments/models" "github.com/equinor/radix-api/api/kubequery" "github.com/equinor/radix-api/api/utils/labelselector" + "github.com/equinor/radix-common/net/http" radixutils "github.com/equinor/radix-common/utils" "github.com/equinor/radix-common/utils/pointers" "github.com/equinor/radix-operator/pkg/apis/defaults" @@ -112,6 +113,9 @@ func (eh EnvironmentHandler) RestartComponentAuxiliaryResource(ctx context.Conte if err != nil { return err } + if radixDeployment == nil { + return http.ValidationError(v1.KindRadixDeployment, "no radix deployments found") + } componentsDto, err := eh.deployHandler.GetComponentsForDeployment(ctx, appName, radixDeployment.Name, envName) if err != nil { diff --git a/api/environments/environment_handler.go b/api/environments/environment_handler.go index 6e9ce48c..738b4665 100644 --- a/api/environments/environment_handler.go +++ b/api/environments/environment_handler.go @@ -309,6 +309,9 @@ func (eh EnvironmentHandler) StopEnvironment(ctx context.Context, appName, envNa if err != nil { return err } + if radixDeployment == nil { + return http.ValidationError(v1.KindRadixDeployment, "no radix deployments found") + } log.Ctx(ctx).Info().Msgf("Stopping components in environment %s, %s", envName, appName) for _, deployComponent := range radixDeployment.Spec.Components { @@ -326,6 +329,9 @@ func (eh EnvironmentHandler) ResetManuallyStoppedComponentsInEnvironment(ctx con if err != nil { return err } + if radixDeployment == nil { + return http.ValidationError(v1.KindRadixDeployment, "no radix deployments found") + } log.Ctx(ctx).Info().Msgf("Starting components in environment %s, %s", envName, appName) for _, deployComponent := range radixDeployment.Spec.Components { @@ -344,6 +350,9 @@ func (eh EnvironmentHandler) RestartEnvironment(ctx context.Context, appName, en if err != nil { return err } + if radixDeployment == nil { + return http.ValidationError(v1.KindRadixDeployment, "no radix deployments found") + } log.Ctx(ctx).Info().Msgf("Restarting components in environment %s, %s", envName, appName) for _, deployComponent := range radixDeployment.Spec.Components { From 032646e8b2b6ed7eafad721c65e8979d3d0b9863 Mon Sep 17 00:00:00 2001 From: Richard Hagen Date: Wed, 4 Sep 2024 10:24:58 +0200 Subject: [PATCH 26/33] Update api/utils/predicate/kubernetes.go Co-authored-by: Sergey Smolnikov --- api/utils/predicate/kubernetes.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/api/utils/predicate/kubernetes.go b/api/utils/predicate/kubernetes.go index 5aa4dfd2..b0654c9e 100644 --- a/api/utils/predicate/kubernetes.go +++ b/api/utils/predicate/kubernetes.go @@ -20,9 +20,7 @@ func IsAppAliasIngress(ingress networkingv1.Ingress) bool { } func IsPodForComponent(appName, componentName string) func(corev1.Pod) bool { - selector := labels.SelectorFromSet(radixlabels.Merge( - radixlabels.ForApplicationName(appName), - radixlabels.ForComponentName(componentName))) + selector := labelselector.ForComponent(appName, componentName).AsSelector() return func(pod corev1.Pod) bool { return selector.Matches(labels.Set(pod.Labels)) } From 7e387b2b8cd10e56fe5783001312b99970c62727 Mon Sep 17 00:00:00 2001 From: Richard87 Date: Wed, 4 Sep 2024 11:16:39 +0200 Subject: [PATCH 27/33] update swagger --- swaggerui/html/swagger.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/swaggerui/html/swagger.json b/swaggerui/html/swagger.json index 5d084faa..156c8ccf 100644 --- a/swaggerui/html/swagger.json +++ b/swaggerui/html/swagger.json @@ -5782,7 +5782,7 @@ ] }, "replicasOverride": { - "description": "Set if manuall control of replicas is in place. null means automatic controll, 0 means stopped and \u003e= 1 is manually scaled.", + "description": "Set if manual control of replicas is in place. Not set means automatic control, 0 means stopped and \u003e= 1 is manually scaled.", "type": "integer", "format": "int64", "x-go-name": "ReplicasOverride", From 57165b7d6aad28df7723271de06802291be709c2 Mon Sep 17 00:00:00 2001 From: Richard87 Date: Thu, 5 Sep 2024 13:22:27 +0200 Subject: [PATCH 28/33] remove outdated null check --- api/environments/environment_controller_test.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/api/environments/environment_controller_test.go b/api/environments/environment_controller_test.go index fd732a62..371c4240 100644 --- a/api/environments/environment_controller_test.go +++ b/api/environments/environment_controller_test.go @@ -2982,8 +2982,7 @@ func assertBatchDeleted(t *testing.T, rc radixclient.Interface, ns, batchName st require.Nil(t, err) } else { // not deletable - require.Nil(t, updatedBatch) - require.NotNil(t, err) + require.Error(t, err) } } From 3b2ea000f66b5fb4f1e9a677710246e95219ea53 Mon Sep 17 00:00:00 2001 From: Richard87 Date: Thu, 5 Sep 2024 15:04:44 +0200 Subject: [PATCH 29/33] remove outdated test, introduce WithComponentStatuserFunc --- api/deployments/models/component_status.go | 2 + api/environments/component_handler.go | 2 +- api/environments/component_spec.go | 11 +- .../environment_controller_test.go | 110 +----------------- api/environments/environment_handler.go | 21 +++- 5 files changed, 24 insertions(+), 122 deletions(-) diff --git a/api/deployments/models/component_status.go b/api/deployments/models/component_status.go index a1ab761d..a5347606 100644 --- a/api/deployments/models/component_status.go +++ b/api/deployments/models/component_status.go @@ -40,6 +40,8 @@ func (p ComponentStatus) String() string { return [...]string{"Stopped", "Consistent", "Reconciling", "Restarting", "Outdated"}[p] } +type ComponentStatuserFunc func(component radixv1.RadixCommonDeployComponent, kd *appsv1.Deployment, rd *radixv1.RadixDeployment) ComponentStatus + func ComponentStatusFromDeployment(component radixv1.RadixCommonDeployComponent, kd *appsv1.Deployment, rd *radixv1.RadixDeployment) ComponentStatus { if kd == nil || kd.GetName() == "" { return ComponentReconciling diff --git a/api/environments/component_handler.go b/api/environments/component_handler.go index a97ef77c..9a9d8803 100644 --- a/api/environments/component_handler.go +++ b/api/environments/component_handler.go @@ -96,7 +96,7 @@ func (eh EnvironmentHandler) RestartComponent(ctx context.Context, appName, envN return err } componentStatus := updater.getComponentStatus() - if !strings.EqualFold(componentStatus, deploymentModels.ConsistentComponent.String()) { + if strings.EqualFold(componentStatus, deploymentModels.ComponentRestarting.String()) { if ignoreComponentStatusError { return nil } diff --git a/api/environments/component_spec.go b/api/environments/component_spec.go index f98484a4..de701d9c 100644 --- a/api/environments/component_spec.go +++ b/api/environments/component_spec.go @@ -19,22 +19,21 @@ import ( corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/selection" - "k8s.io/client-go/kubernetes" ) // getComponentStateFromSpec Returns a component with the current state -func getComponentStateFromSpec(ctx context.Context, kubeClient kubernetes.Interface, rd *v1.RadixDeployment, component v1.RadixCommonDeployComponent, hpas []autoscalingv2.HorizontalPodAutoscaler, scaledObjects []v1alpha1.ScaledObject) (*deploymentModels.Component, error) { +func (eh EnvironmentHandler) getComponentStateFromSpec(ctx context.Context, rd *v1.RadixDeployment, component v1.RadixCommonDeployComponent, hpas []autoscalingv2.HorizontalPodAutoscaler, scaledObjects []v1alpha1.ScaledObject) (*deploymentModels.Component, error) { var componentPodNames []string var environmentVariables map[string]string var replicaSummaryList []deploymentModels.ReplicaSummary var auxResource deploymentModels.AuxiliaryResource var horizontalScalingSummary *deploymentModels.HorizontalScalingSummary - deployments, err := kubequery.GetDeploymentsForEnvironment(ctx, kubeClient, rd.Spec.AppName, rd.Spec.Environment) + deployments, err := kubequery.GetDeploymentsForEnvironment(ctx, eh.accounts.UserAccount.Client, rd.Spec.AppName, rd.Spec.Environment) if err != nil { return nil, err } - pods, err := kubequery.GetPodsForEnvironmentComponents(ctx, kubeClient, rd.Spec.AppName, rd.Spec.Environment) + pods, err := kubequery.GetPodsForEnvironmentComponents(ctx, eh.accounts.UserAccount.Client, rd.Spec.AppName, rd.Spec.Environment) if err != nil { return nil, err } @@ -50,7 +49,7 @@ func getComponentStateFromSpec(ctx context.Context, kubeClient kubernetes.Interf componentPodNames = getPodNames(componentPods) environmentVariables = getRadixEnvironmentVariables(componentPods) - eventList, err := kubequery.GetEventsForEnvironment(ctx, kubeClient, rd.Spec.AppName, rd.Spec.Environment) + eventList, err := kubequery.GetEventsForEnvironment(ctx, eh.accounts.UserAccount.Client, rd.Spec.AppName, rd.Spec.Environment) if err != nil { return nil, err } @@ -62,7 +61,7 @@ func getComponentStateFromSpec(ctx context.Context, kubeClient kubernetes.Interf } kd, _ := slice.FindFirst(deployments, predicate.IsDeploymentForComponent(rd.Spec.AppName, component.GetName())) - status = deploymentModels.ComponentStatusFromDeployment(component, &kd, rd) + status = eh.ComponentStatuser(component, &kd, rd) } componentBuilder := deploymentModels.NewComponentBuilder() diff --git a/api/environments/environment_controller_test.go b/api/environments/environment_controller_test.go index 371c4240..acb790f7 100644 --- a/api/environments/environment_controller_test.go +++ b/api/environments/environment_controller_test.go @@ -482,7 +482,7 @@ func TestRestartComponent_ApplicationWithDeployment_EnvironmentConsistent(t *tes t.Run("Component Restart Fails", func(t *testing.T) { component := findComponentInDeployment(rd, stoppedComponent) - assert.True(t, *component.Replicas == zeroReplicas) + assert.True(t, *component.ReplicasOverride == zeroReplicas) // Emulate a stopped component err := deleteComponentPod(client, rd.GetNamespace(), stoppedComponent) @@ -657,114 +657,6 @@ func TestRestartEnvrionment_ApplicationWithDeployment_EnvironmentConsistent(t *t }) } -func TestStartEnvrionment_ApplicationWithDeployment_EnvironmentConsistent(t *testing.T) { - zeroReplicas := 0 - - // Setup - commonTestUtils, environmentControllerTestUtils, _, _, radixclient, _, _, _, _ := setupTest(t, nil) - - // Test - t.Run("Start Environment", func(t *testing.T) { - envName := "fullyStoppedEnv" - rd, _ := createRadixDeploymentWithReplicas(commonTestUtils, anyAppName, envName, []ComponentCreatorStruct{ - {name: "stoppedComponent1", number: 0}, - {name: "stoppedComponent2", number: 0}, - }) - for _, comp := range rd.Spec.Components { - assert.True(t, *comp.Replicas == zeroReplicas) - } - - responseChannel := environmentControllerTestUtils.ExecuteRequest("POST", fmt.Sprintf("/api/v1/applications/%s/environments/%s/start", anyAppName, envName)) - response := <-responseChannel - assert.Equal(t, http.StatusOK, response.Code) - - updatedRd, _ := radixclient.RadixV1().RadixDeployments(rd.GetNamespace()).Get(context.Background(), rd.GetName(), metav1.GetOptions{}) - for _, comp := range updatedRd.Spec.Components { - assert.True(t, *comp.Replicas > zeroReplicas) - } - }) - - t.Run("Start Environment with running component", func(t *testing.T) { - envName := "partiallyRunningEnv" - rd, _ := createRadixDeploymentWithReplicas(commonTestUtils, anyAppName, envName, []ComponentCreatorStruct{ - {name: "stoppedComponent", number: 0}, - {name: "runningComponent", number: 7}, - }) - replicaCount := 0 - for _, comp := range rd.Spec.Components { - replicaCount += *comp.Replicas - } - assert.True(t, replicaCount > zeroReplicas) - - responseChannel := environmentControllerTestUtils.ExecuteRequest("POST", fmt.Sprintf("/api/v1/applications/%s/environments/%s/start", anyAppName, envName)) - response := <-responseChannel - assert.Equal(t, http.StatusOK, response.Code) - - errorResponse, _ := controllertest.GetErrorResponse(response) - assert.Nil(t, errorResponse) - - updatedRd, _ := radixclient.RadixV1().RadixDeployments(rd.GetNamespace()).Get(context.Background(), rd.GetName(), metav1.GetOptions{}) - updatedReplicaCount := 0 - for _, comp := range updatedRd.Spec.Components { - updatedReplicaCount += *comp.Replicas - } - assert.True(t, updatedReplicaCount > replicaCount) - }) -} - -func TestStopEnvrionment_ApplicationWithDeployment_EnvironmentConsistent(t *testing.T) { - zeroReplicas := 0 - - // Setup - commonTestUtils, environmentControllerTestUtils, _, _, radixclient, _, _, _, _ := setupTest(t, nil) - - // Test - t.Run("Stop Environment", func(t *testing.T) { - envName := "fullyRunningEnv" - rd, _ := createRadixDeploymentWithReplicas(commonTestUtils, anyAppName, envName, []ComponentCreatorStruct{ - {name: "runningComponent1", number: 3}, - {name: "runningComponent2", number: 7}, - }) - for _, comp := range rd.Spec.Components { - assert.True(t, *comp.Replicas > zeroReplicas) - } - - responseChannel := environmentControllerTestUtils.ExecuteRequest("POST", fmt.Sprintf("/api/v1/applications/%s/environments/%s/stop", anyAppName, envName)) - response := <-responseChannel - assert.Equal(t, http.StatusOK, response.Code) - - updatedRd, _ := radixclient.RadixV1().RadixDeployments(rd.GetNamespace()).Get(context.Background(), rd.GetName(), metav1.GetOptions{}) - for _, comp := range updatedRd.Spec.Components { - assert.True(t, *comp.Replicas == zeroReplicas) - } - }) - - t.Run("Stop Environment with stopped component", func(t *testing.T) { - envName := "partiallyRunningEnv" - rd, _ := createRadixDeploymentWithReplicas(commonTestUtils, anyAppName, envName, []ComponentCreatorStruct{ - {name: "stoppedComponent", number: 0}, - {name: "runningComponent", number: 7}, - }) - replicaCount := 0 - for _, comp := range rd.Spec.Components { - replicaCount += *comp.Replicas - } - assert.True(t, replicaCount > zeroReplicas) - - responseChannel := environmentControllerTestUtils.ExecuteRequest("POST", fmt.Sprintf("/api/v1/applications/%s/environments/%s/stop", anyAppName, envName)) - response := <-responseChannel - assert.Equal(t, http.StatusOK, response.Code) - - errorResponse, _ := controllertest.GetErrorResponse(response) - assert.Nil(t, errorResponse) - - updatedRd, _ := radixclient.RadixV1().RadixDeployments(rd.GetNamespace()).Get(context.Background(), rd.GetName(), metav1.GetOptions{}) - for _, comp := range updatedRd.Spec.Components { - assert.True(t, *comp.Replicas == zeroReplicas) - } - }) -} - func TestCreateEnvironment(t *testing.T) { // Setup commonTestUtils, environmentControllerTestUtils, _, _, _, _, _, _, _ := setupTest(t, nil) diff --git a/api/environments/environment_handler.go b/api/environments/environment_handler.go index 738b4665..0665ed7e 100644 --- a/api/environments/environment_handler.go +++ b/api/environments/environment_handler.go @@ -58,6 +58,12 @@ func WithTLSValidator(validator tlsvalidation.Validator) EnvironmentHandlerOptio } } +func WithComponentStatuserFunc(statuser deploymentModels.ComponentStatuserFunc) EnvironmentHandlerOptions { + return func(eh *EnvironmentHandler) { + eh.ComponentStatuser = statuser + } +} + // EnvironmentHandlerFactory defines a factory function for EnvironmentHandler type EnvironmentHandlerFactory func(accounts models.Accounts) EnvironmentHandler @@ -76,10 +82,11 @@ func NewEnvironmentHandlerFactory(opts ...EnvironmentHandlerOptions) Environment // EnvironmentHandler Instance variables type EnvironmentHandler struct { - deployHandler deployments.DeployHandler - eventHandler events.EventHandler - accounts models.Accounts - tlsValidator tlsvalidation.Validator + deployHandler deployments.DeployHandler + eventHandler events.EventHandler + accounts models.Accounts + tlsValidator tlsvalidation.Validator + ComponentStatuser deploymentModels.ComponentStatuserFunc } var validaStatusesToScaleComponent []string @@ -90,7 +97,9 @@ var validaStatusesToScaleComponent []string func Init(opts ...EnvironmentHandlerOptions) EnvironmentHandler { validaStatusesToScaleComponent = []string{deploymentModels.ConsistentComponent.String(), deploymentModels.StoppedComponent.String()} - eh := EnvironmentHandler{} + eh := EnvironmentHandler{ + ComponentStatuser: deploymentModels.ComponentStatusFromDeployment, + } for _, opt := range opts { opt(&eh) @@ -456,7 +465,7 @@ func (eh EnvironmentHandler) getRadixCommonComponentUpdater(ctx context.Context, return nil, err } baseUpdater.environmentConfig = utils.GetComponentEnvironmentConfig(ra, envName, componentName) - baseUpdater.componentState, err = getComponentStateFromSpec(ctx, eh.accounts.UserAccount.Client, rd, componentToPatch, hpas, scalers) + baseUpdater.componentState, err = eh.getComponentStateFromSpec(ctx, rd, componentToPatch, hpas, scalers) if err != nil { return nil, err } From 83b320df58265a2d3234ab39fba5538ccd5bc6cf Mon Sep 17 00:00:00 2001 From: Richard87 Date: Thu, 5 Sep 2024 16:05:17 +0200 Subject: [PATCH 30/33] Test component actions with status --- api/environments/component_handler.go | 2 +- .../environment_controller_test.go | 183 ++++-------------- go.mod | 6 +- 3 files changed, 39 insertions(+), 152 deletions(-) diff --git a/api/environments/component_handler.go b/api/environments/component_handler.go index 9a9d8803..58e7f9a9 100644 --- a/api/environments/component_handler.go +++ b/api/environments/component_handler.go @@ -96,7 +96,7 @@ func (eh EnvironmentHandler) RestartComponent(ctx context.Context, appName, envN return err } componentStatus := updater.getComponentStatus() - if strings.EqualFold(componentStatus, deploymentModels.ComponentRestarting.String()) { + if strings.EqualFold(componentStatus, deploymentModels.StoppedComponent.String()) { if ignoreComponentStatusError { return nil } diff --git a/api/environments/environment_controller_test.go b/api/environments/environment_controller_test.go index acb790f7..3e62076a 100644 --- a/api/environments/environment_controller_test.go +++ b/api/environments/environment_controller_test.go @@ -451,155 +451,40 @@ func setupGetDeploymentsTest(t *testing.T, commonTestUtils *commontest.Utils, ap require.NoError(t, err) } -func TestRestartComponent_ApplicationWithDeployment_EnvironmentConsistent(t *testing.T) { - zeroReplicas := 0 - stoppedComponent, startedComponent := "stoppedComponent", "startedComponent" - - // Setup - commonTestUtils, environmentControllerTestUtils, _, client, radixclient, _, _, _, _ := setupTest(t, nil) - rd, _ := createRadixDeploymentWithReplicas(commonTestUtils, anyAppName, anyEnvironment, []ComponentCreatorStruct{ - {name: stoppedComponent, number: 0}, - {name: startedComponent, number: 1}, - }) - - t.Run("Component Restart Succeeds", func(t *testing.T) { - component := findComponentInDeployment(rd, startedComponent) - assert.True(t, *component.Replicas > zeroReplicas) - - // Emulate a started component - _, err := createComponentPod(client, rd.GetNamespace(), startedComponent) - require.NoError(t, err) - - responseChannel := environmentControllerTestUtils.ExecuteRequest("POST", fmt.Sprintf("/api/v1/applications/%s/environments/%s/components/%s/restart", anyAppName, anyEnvironment, startedComponent)) - response := <-responseChannel - assert.Equal(t, http.StatusOK, response.Code) - - updatedRd, _ := radixclient.RadixV1().RadixDeployments(rd.GetNamespace()).Get(context.Background(), rd.GetName(), metav1.GetOptions{}) - component = findComponentInDeployment(updatedRd, startedComponent) - assert.True(t, *component.Replicas > zeroReplicas) - assert.NotEmpty(t, component.EnvironmentVariables[operatordefaults.RadixRestartEnvironmentVariable]) - }) - - t.Run("Component Restart Fails", func(t *testing.T) { - component := findComponentInDeployment(rd, stoppedComponent) - assert.True(t, *component.ReplicasOverride == zeroReplicas) - - // Emulate a stopped component - err := deleteComponentPod(client, rd.GetNamespace(), stoppedComponent) - require.NoError(t, err) - - responseChannel := environmentControllerTestUtils.ExecuteRequest("POST", fmt.Sprintf("/api/v1/applications/%s/environments/%s/components/%s/start", anyAppName, anyEnvironment, stoppedComponent)) - response := <-responseChannel - assert.Equal(t, http.StatusOK, response.Code) - - updatedRd, _ := radixclient.RadixV1().RadixDeployments(rd.GetNamespace()).Get(context.Background(), rd.GetName(), metav1.GetOptions{}) - component = findComponentInDeployment(updatedRd, stoppedComponent) - assert.True(t, *component.Replicas > zeroReplicas) - - responseChannel = environmentControllerTestUtils.ExecuteRequest("POST", fmt.Sprintf("/api/v1/applications/%s/environments/%s/components/%s/restart", anyAppName, anyEnvironment, stoppedComponent)) - response = <-responseChannel - // Since pods are not appearing out of nowhere with kubernetes-fake, the component will be in - // a reconciling state and cannot be restarted - require.Equal(t, http.StatusBadRequest, response.Code) - - errorResponse, _ := controllertest.GetErrorResponse(response) - expectedError := environmentModels.CannotRestartComponent(anyAppName, stoppedComponent, deploymentModels.ComponentReconciling.String()) - assert.Equal(t, (expectedError.(*radixhttp.Error)).Message, errorResponse.Message) - }) -} - -func TestStartComponent_ApplicationWithDeployment_EnvironmentConsistent(t *testing.T) { - zeroReplicas := 0 - stoppedComponent1, stoppedComponent2 := "stoppedComponent1", "stoppedComponent2" - - // Setup - commonTestUtils, environmentControllerTestUtils, _, client, radixclient, _, _, _, _ := setupTest(t, nil) - rd, _ := createRadixDeploymentWithReplicas(commonTestUtils, anyAppName, anyEnvironment, []ComponentCreatorStruct{ - {name: stoppedComponent1, number: 0}, - {name: stoppedComponent2, number: 0}, - }) - - t.Run("Component Start Succeeds", func(t *testing.T) { - component := findComponentInDeployment(rd, stoppedComponent1) - assert.True(t, *component.ReplicasOverride == zeroReplicas) - - responseChannel := environmentControllerTestUtils.ExecuteRequest("POST", fmt.Sprintf("/api/v1/applications/%s/environments/%s/components/%s/start", anyAppName, anyEnvironment, stoppedComponent1)) - response := <-responseChannel - require.Equal(t, http.StatusOK, response.Code) - - updatedRd, _ := radixclient.RadixV1().RadixDeployments(rd.GetNamespace()).Get(context.Background(), rd.GetName(), metav1.GetOptions{}) - component = findComponentInDeployment(updatedRd, stoppedComponent1) - assert.Nil(t, component.ReplicasOverride) - }) - - t.Run("Component Start Fails", func(t *testing.T) { - component := findComponentInDeployment(rd, stoppedComponent2) - assert.True(t, *component.ReplicasOverride == zeroReplicas) - - // Create pod - _, err := createComponentPod(client, rd.GetNamespace(), stoppedComponent2) - require.NoError(t, err) - - responseChannel := environmentControllerTestUtils.ExecuteRequest("POST", fmt.Sprintf("/api/v1/applications/%s/environments/%s/components/%s/start", anyAppName, anyEnvironment, stoppedComponent2)) - response := <-responseChannel - // Since pods are not appearing out of nowhere with kubernetes-fake, the component will be in - // a reconciling state and cannot be started - require.Equal(t, http.StatusBadRequest, response.Code) - - errorResponse, _ := controllertest.GetErrorResponse(response) - expectedError := environmentModels.CannotResetScaledComponent(anyAppName, stoppedComponent2) - assert.Equal(t, (expectedError.(*radixhttp.Error)).Message, errorResponse.Message) - - updatedRd, _ := radixclient.RadixV1().RadixDeployments(rd.GetNamespace()).Get(context.Background(), rd.GetName(), metav1.GetOptions{}) - component = findComponentInDeployment(updatedRd, stoppedComponent2) - assert.True(t, *component.Replicas == zeroReplicas) - }) -} +func TestComponentStatusActions(t *testing.T) { + + scenarios := []ComponentCreatorStruct{ + {scenarioName: "Stop unstopped component", number: 1, name: "comp1", action: "stop", status: deploymentModels.ConsistentComponent, expectedStatus: http.StatusOK}, + {scenarioName: "Stop stopped component", number: 1, name: "comp2", action: "stop", status: deploymentModels.StoppedComponent, expectedStatus: http.StatusBadRequest}, + {scenarioName: "Start stopped component", number: 1, name: "comp3", action: "start", status: deploymentModels.StoppedComponent, expectedStatus: http.StatusOK}, + {scenarioName: "Start started component", number: 1, name: "comp4", action: "start", status: deploymentModels.ConsistentComponent, expectedStatus: http.StatusOK}, + {scenarioName: "Restart started component", number: 1, name: "comp5", action: "restart", status: deploymentModels.ConsistentComponent, expectedStatus: http.StatusOK}, + {scenarioName: "Restart stopped component", number: 1, name: "comp6", action: "restart", status: deploymentModels.StoppedComponent, expectedStatus: http.StatusBadRequest}, + {scenarioName: "Reset manually scaled component", number: 1, name: "comp7", action: "reset-scale", status: deploymentModels.StoppedComponent, expectedStatus: http.StatusOK}, + } + + // Mock Status + statuser := func(component v1.RadixCommonDeployComponent, kd *appsv1.Deployment, rd *v1.RadixDeployment) deploymentModels.ComponentStatus { + for _, scenario := range scenarios { + if scenario.name == component.GetName() { + return scenario.status + } + } -func TestStopComponent_ApplicationWithDeployment_EnvironmentConsistent(t *testing.T) { - zeroReplicas := 0 - runningComponent, stoppedComponent := "runningComp", "stoppedComponent" + panic("unknown component! ") + } // Setup - commonTestUtils, environmentControllerTestUtils, _, _, radixclient, _, _, _, _ := setupTest(t, nil) - rd, _ := createRadixDeploymentWithReplicas(commonTestUtils, anyAppName, anyEnvironment, []ComponentCreatorStruct{ - {name: runningComponent, number: 3}, - {name: stoppedComponent, number: 0}, - }) + commonTestUtils, environmentControllerTestUtils, _, _, _, _, _, _, _ := setupTest(t, []EnvironmentHandlerOptions{WithComponentStatuserFunc(statuser)}) + _, _ = createRadixDeploymentWithReplicas(commonTestUtils, anyAppName, anyEnvironment, scenarios) - // Test - t.Run("Stop Component Succeeds", func(t *testing.T) { - component := findComponentInDeployment(rd, runningComponent) - assert.True(t, *component.Replicas > zeroReplicas) - - responseChannel := environmentControllerTestUtils.ExecuteRequest("POST", fmt.Sprintf("/api/v1/applications/%s/environments/%s/components/%s/stop", anyAppName, anyEnvironment, runningComponent)) - response := <-responseChannel - // Since pods are not appearing out of nowhere with kubernetes-fake, the component will be in - // a reconciling state because number of replicas in spec > 0. Therefore it can be stopped - assert.Equal(t, http.StatusOK, response.Code) - - updatedRd, _ := radixclient.RadixV1().RadixDeployments(rd.GetNamespace()).Get(context.Background(), rd.GetName(), metav1.GetOptions{}) - component = findComponentInDeployment(updatedRd, runningComponent) - assert.True(t, *component.GetReplicasOverride() == zeroReplicas) - }) - - t.Run("Stop Component Fails", func(t *testing.T) { - component := findComponentInDeployment(rd, stoppedComponent) - assert.True(t, *component.ReplicasOverride == zeroReplicas) - - responseChannel := environmentControllerTestUtils.ExecuteRequest("POST", fmt.Sprintf("/api/v1/applications/%s/environments/%s/components/%s/stop", anyAppName, anyEnvironment, stoppedComponent)) - response := <-responseChannel - // The component is in a stopped state since replicas in spec = 0, and therefore cannot be stopped again - require.Equal(t, http.StatusBadRequest, response.Code) - - errorResponse, _ := controllertest.GetErrorResponse(response) - expectedError := environmentModels.CannotStopComponent(anyAppName, stoppedComponent, deploymentModels.StoppedComponent.String()) - assert.Equal(t, (expectedError.(*radixhttp.Error)).Message, errorResponse.Message) - - updatedRd, _ := radixclient.RadixV1().RadixDeployments(rd.GetNamespace()).Get(context.Background(), rd.GetName(), metav1.GetOptions{}) - component = findComponentInDeployment(updatedRd, stoppedComponent) - assert.True(t, *component.Replicas == zeroReplicas) - }) + for _, scenario := range scenarios { + t.Run(scenario.scenarioName, func(t *testing.T) { + responseChannel := environmentControllerTestUtils.ExecuteRequest("POST", fmt.Sprintf("/api/v1/applications/%s/environments/%s/components/%s/%s", anyAppName, anyEnvironment, scenario.name, scenario.action)) + response := <-responseChannel + assert.Equal(t, scenario.expectedStatus, response.Code) + }) + } } func TestRestartEnvrionment_ApplicationWithDeployment_EnvironmentConsistent(t *testing.T) { @@ -2789,8 +2674,12 @@ func initHandler(client kubernetes.Interface, } type ComponentCreatorStruct struct { - name string - number int + name string + number int + action string + status deploymentModels.ComponentStatus + expectedStatus int + scenarioName string } func createRadixDeploymentWithReplicas(tu *commontest.Utils, appName, envName string, components []ComponentCreatorStruct) (*v1.RadixDeployment, error) { diff --git a/go.mod b/go.mod index 3c8dd5df..a4fcc40e 100644 --- a/go.mod +++ b/go.mod @@ -1,8 +1,6 @@ module github.com/equinor/radix-api -go 1.22.0 - -toolchain go1.22.5 +go 1.23.0 require ( github.com/cert-manager/cert-manager v1.15.0 @@ -27,6 +25,7 @@ require ( github.com/stretchr/testify v1.9.0 github.com/tektoncd/pipeline v0.55.0 github.com/urfave/negroni/v3 v3.1.0 + golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 golang.org/x/sync v0.8.0 k8s.io/api v0.31.0 k8s.io/apimachinery v0.31.0 @@ -102,7 +101,6 @@ require ( go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.0 // indirect golang.org/x/crypto v0.26.0 // indirect - golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect golang.org/x/net v0.28.0 // indirect golang.org/x/oauth2 v0.22.0 // indirect golang.org/x/sys v0.24.0 // indirect From 274fc64c910728bbb16d62925c1ea0eb5e65f1e6 Mon Sep 17 00:00:00 2001 From: Richard87 Date: Thu, 5 Sep 2024 16:08:44 +0200 Subject: [PATCH 31/33] revert go 1.23 --- go.mod | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/go.mod b/go.mod index a4fcc40e..3c8dd5df 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,8 @@ module github.com/equinor/radix-api -go 1.23.0 +go 1.22.0 + +toolchain go1.22.5 require ( github.com/cert-manager/cert-manager v1.15.0 @@ -25,7 +27,6 @@ require ( github.com/stretchr/testify v1.9.0 github.com/tektoncd/pipeline v0.55.0 github.com/urfave/negroni/v3 v3.1.0 - golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 golang.org/x/sync v0.8.0 k8s.io/api v0.31.0 k8s.io/apimachinery v0.31.0 @@ -101,6 +102,7 @@ require ( go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.0 // indirect golang.org/x/crypto v0.26.0 // indirect + golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect golang.org/x/net v0.28.0 // indirect golang.org/x/oauth2 v0.22.0 // indirect golang.org/x/sys v0.24.0 // indirect From 9e4e9c4bb06d13d3105dd37e5c7cb5b74c6a6afa Mon Sep 17 00:00:00 2001 From: Richard87 Date: Fri, 6 Sep 2024 08:57:13 +0200 Subject: [PATCH 32/33] cleanup unused code --- .../environment_controller_test.go | 39 ------------------- 1 file changed, 39 deletions(-) diff --git a/api/environments/environment_controller_test.go b/api/environments/environment_controller_test.go index 3e62076a..8b978eba 100644 --- a/api/environments/environment_controller_test.go +++ b/api/environments/environment_controller_test.go @@ -44,7 +44,6 @@ import ( "github.com/stretchr/testify/require" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes" kubefake "k8s.io/client-go/kubernetes/fake" @@ -2708,44 +2707,6 @@ func createRadixDeploymentWithReplicas(tu *commontest.Utils, appName, envName st return rd, err } -func createComponentPod(kubeclient kubernetes.Interface, namespace, componentName string) (*corev1.Pod, error) { - podSpec := getPodSpec(componentName) - return kubeclient.CoreV1().Pods(namespace).Create(context.Background(), podSpec, metav1.CreateOptions{}) -} - -func deleteComponentPod(kubeclient kubernetes.Interface, namespace, componentName string) error { - err := kubeclient.CoreV1().Pods(namespace).Delete(context.Background(), getComponentPodName(componentName), metav1.DeleteOptions{}) - if err != nil && !errors.IsNotFound(err) { - return err - } - return nil -} - -func findComponentInDeployment(rd *v1.RadixDeployment, componentName string) *v1.RadixDeployComponent { - for _, comp := range rd.Spec.Components { - if comp.Name == componentName { - return &comp - } - } - - return nil -} - -func getPodSpec(componentName string) *corev1.Pod { - return &corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: getComponentPodName(componentName), - Labels: map[string]string{ - kube.RadixComponentLabel: componentName, - }, - }, - } -} - -func getComponentPodName(componentName string) string { - return fmt.Sprintf("%s-pod", componentName) -} - func contains(secrets []secretModels.Secret, name string) bool { for _, secret := range secrets { if secret.Name == name { From 3aafadb1de50284b20bbef11b6bf743121ab928d Mon Sep 17 00:00:00 2001 From: Richard87 Date: Wed, 11 Sep 2024 07:56:33 +0200 Subject: [PATCH 33/33] update radix-operator --- api/deployments/models/component_deployment.go | 2 +- go.mod | 2 +- go.sum | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/api/deployments/models/component_deployment.go b/api/deployments/models/component_deployment.go index fc4e73ef..ae95f58b 100644 --- a/api/deployments/models/component_deployment.go +++ b/api/deployments/models/component_deployment.go @@ -593,7 +593,7 @@ func getReplicaType(pod corev1.Pod) ReplicaType { switch { case pod.GetLabels()[kube.RadixPodIsJobSchedulerLabel] == "true": return JobManager - case pod.GetLabels()[kube.RadixPodIsJobAuxObjectLabel] == "true": + case pod.GetLabels()[kube.RadixAuxiliaryComponentTypeLabel] == kube.RadixJobTypeManagerAux: return JobManagerAux case pod.GetLabels()[kube.RadixAuxiliaryComponentTypeLabel] == "oauth": return OAuth2 diff --git a/go.mod b/go.mod index 3c8dd5df..563f096e 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,7 @@ require ( github.com/cert-manager/cert-manager v1.15.0 github.com/equinor/radix-common v1.9.4 github.com/equinor/radix-job-scheduler v1.11.0 - github.com/equinor/radix-operator v1.58.2-0.20240903100713-bce7143c1baa + github.com/equinor/radix-operator v1.58.3 github.com/evanphx/json-patch/v5 v5.9.0 github.com/felixge/httpsnoop v1.0.4 github.com/golang-jwt/jwt/v5 v5.2.1 diff --git a/go.sum b/go.sum index ac759407..a34541a0 100644 --- a/go.sum +++ b/go.sum @@ -87,8 +87,8 @@ github.com/equinor/radix-common v1.9.4 h1:ErSnB2tqlRwaQuQdaA0qzsReDtHDgubcvqRO09 github.com/equinor/radix-common v1.9.4/go.mod h1:+g0Wj0D40zz29DjNkYKVmCVeYy4OsFWKI7Qi9rA6kpY= github.com/equinor/radix-job-scheduler v1.11.0 h1:8wCmXOVl/1cto8q2WJQEE06Cw68/QmfoifYVR49vzkY= github.com/equinor/radix-job-scheduler v1.11.0/go.mod h1:yPXn3kDcMY0Z3kBkosjuefsdY1x2g0NlBeybMmHz5hc= -github.com/equinor/radix-operator v1.58.2-0.20240903100713-bce7143c1baa h1:5yDz0yuPxCurESYrPzb5fM6zS8qd0w338MpCPNRjAY0= -github.com/equinor/radix-operator v1.58.2-0.20240903100713-bce7143c1baa/go.mod h1:DTPXOxU3uHPvji7qBGSK1b03iXROpX3l94kYjcOHkPM= +github.com/equinor/radix-operator v1.58.3 h1:F4YhNkQ4uRONP125OTfG8hdy9PiyKlOWVO8/p2NIi70= +github.com/equinor/radix-operator v1.58.3/go.mod h1:DTPXOxU3uHPvji7qBGSK1b03iXROpX3l94kYjcOHkPM= github.com/evanphx/json-patch v5.9.0+incompatible h1:fBXyNpNMuTTDdquAq/uisOr2lShz4oaXpDTX2bLe7ls= github.com/evanphx/json-patch v5.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch/v5 v5.9.0 h1:kcBlZQbplgElYIlo/n1hJbls2z/1awpXxpRi0/FOJfg=