diff --git a/api/deployments/models/component_builder.go b/api/deployments/models/component_builder.go index e4785183..61f2aef6 100644 --- a/api/deployments/models/component_builder.go +++ b/api/deployments/models/component_builder.go @@ -9,7 +9,7 @@ import ( "github.com/equinor/radix-operator/pkg/apis/defaults" "github.com/equinor/radix-operator/pkg/apis/deployment" "github.com/equinor/radix-operator/pkg/apis/ingress" - v1 "github.com/equinor/radix-operator/pkg/apis/radix/v1" + radixv1 "github.com/equinor/radix-operator/pkg/apis/radix/v1" "github.com/equinor/radix-operator/pkg/apis/utils" ) @@ -21,11 +21,12 @@ type ComponentBuilder interface { WithSchedulerPort(schedulerPort *int32) ComponentBuilder WithScheduledJobPayloadPath(scheduledJobPayloadPath string) ComponentBuilder WithRadixEnvironmentVariables(map[string]string) ComponentBuilder - WithComponent(v1.RadixCommonDeployComponent) ComponentBuilder + WithComponent(radixv1.RadixCommonDeployComponent) ComponentBuilder WithAuxiliaryResource(AuxiliaryResource) ComponentBuilder - WithNotifications(*v1.Notifications) ComponentBuilder + WithNotifications(*radixv1.Notifications) ComponentBuilder WithHorizontalScalingSummary(*HorizontalScalingSummary) ComponentBuilder WithExternalDNS(externalDNS []ExternalDNS) ComponentBuilder + WithRuntime(*radixv1.Runtime) ComponentBuilder BuildComponentSummary() (*ComponentSummary, error) BuildComponent() (*Component, error) } @@ -51,7 +52,8 @@ type componentBuilder struct { errors []error commitID string gitTags string - resources *v1.ResourceRequirements + resources *radixv1.ResourceRequirements + runtime *radixv1.Runtime } func (b *componentBuilder) WithStatus(status ComponentStatus) ComponentBuilder { @@ -89,13 +91,14 @@ func (b *componentBuilder) WithAuxiliaryResource(auxResource AuxiliaryResource) return b } -func (b *componentBuilder) WithComponent(component v1.RadixCommonDeployComponent) ComponentBuilder { +func (b *componentBuilder) WithComponent(component radixv1.RadixCommonDeployComponent) ComponentBuilder { b.componentName = component.GetName() b.componentType = string(component.GetType()) b.componentImage = component.GetImage() b.resources = component.GetResources() b.commitID = component.GetEnvironmentVariables()[defaults.RadixCommitHashEnvironmentVariable] b.gitTags = component.GetEnvironmentVariables()[defaults.RadixGitTagsEnvironmentVariable] + b.runtime = component.GetRuntime() ports := []Port{} if component.GetPorts() != nil { @@ -113,11 +116,11 @@ func (b *componentBuilder) WithComponent(component v1.RadixCommonDeployComponent for _, volumeMount := range component.GetVolumeMounts() { volumeMountType := deployment.GetCsiAzureVolumeMountType(&volumeMount) switch volumeMountType { - case v1.MountTypeBlob: + case radixv1.MountTypeBlob: secretName := defaults.GetBlobFuseCredsSecretName(component.GetName(), volumeMount.Name) b.secrets = append(b.secrets, secretName+defaults.BlobFuseCredsAccountKeyPartSuffix) b.secrets = append(b.secrets, secretName+defaults.BlobFuseCredsAccountNamePartSuffix) - case v1.MountTypeBlobFuse2FuseCsiAzure, v1.MountTypeBlobFuse2Fuse2CsiAzure, v1.MountTypeBlobFuse2NfsCsiAzure, v1.MountTypeAzureFileCsiAzure: + case radixv1.MountTypeBlobFuse2FuseCsiAzure, radixv1.MountTypeBlobFuse2Fuse2CsiAzure, radixv1.MountTypeBlobFuse2NfsCsiAzure, radixv1.MountTypeAzureFileCsiAzure: secretName := defaults.GetCsiAzureVolumeMountCredsSecretName(component.GetName(), volumeMount.Name) b.secrets = append(b.secrets, secretName+defaults.CsiAzureCredsAccountKeyPartSuffix) b.secrets = append(b.secrets, secretName+defaults.CsiAzureCredsAccountNamePartSuffix) @@ -150,7 +153,7 @@ func (b *componentBuilder) WithComponent(component v1.RadixCommonDeployComponent b.secrets = append(b.secrets, component.GetName()+suffix.OAuth2ClientSecret) b.secrets = append(b.secrets, component.GetName()+suffix.OAuth2CookieSecret) - if oauth2.SessionStoreType == v1.SessionStoreRedis { + if oauth2.SessionStoreType == radixv1.SessionStoreRedis { b.secrets = append(b.secrets, component.GetName()+suffix.OAuth2RedisPassword) } } @@ -174,7 +177,7 @@ func (b *componentBuilder) WithComponent(component v1.RadixCommonDeployComponent return b } -func (b *componentBuilder) WithNotifications(notifications *v1.Notifications) ComponentBuilder { +func (b *componentBuilder) WithNotifications(notifications *radixv1.Notifications) ComponentBuilder { if notifications == nil { b.notifications = nil return b @@ -195,6 +198,11 @@ func (b *componentBuilder) WithExternalDNS(externalDNS []ExternalDNS) ComponentB return b } +func (b *componentBuilder) WithRuntime(runtime *radixv1.Runtime) ComponentBuilder { + b.runtime = runtime + return b +} + func (b *componentBuilder) buildError() error { if len(b.errors) == 0 { return nil @@ -203,6 +211,12 @@ func (b *componentBuilder) buildError() error { return errors.Join(b.errors...) } +func (b *componentBuilder) buildRuntimeModel() *Runtime { + return &Runtime{ + Architecture: utils.GetArchitectureFromRuntime(b.runtime), + } +} + func (b *componentBuilder) BuildComponentSummary() (*ComponentSummary, error) { summary := ComponentSummary{ Name: b.componentName, @@ -210,6 +224,7 @@ func (b *componentBuilder) BuildComponentSummary() (*ComponentSummary, error) { Image: b.componentImage, CommitID: b.commitID, GitTags: b.gitTags, + Runtime: b.buildRuntimeModel(), } if b.resources != nil && (len(b.resources.Limits) > 0 || len(b.resources.Requests) > 0) { summary.Resources = pointers.Ptr(ConvertRadixResourceRequirements(*b.resources)) @@ -218,7 +233,7 @@ func (b *componentBuilder) BuildComponentSummary() (*ComponentSummary, error) { } func (b *componentBuilder) BuildComponent() (*Component, error) { - variables := v1.EnvVarsMap{} + variables := radixv1.EnvVarsMap{} for name, value := range b.environmentVariables { variables[name] = value } @@ -246,6 +261,7 @@ func (b *componentBuilder) BuildComponent() (*Component, error) { HorizontalScalingSummary: b.hpa, CommitID: variables[defaults.RadixCommitHashEnvironmentVariable], GitTags: variables[defaults.RadixGitTagsEnvironmentVariable], + Runtime: b.buildRuntimeModel(), } if b.resources != nil && (len(b.resources.Limits) > 0 || len(b.resources.Requests) > 0) { component.Resources = pointers.Ptr(ConvertRadixResourceRequirements(*b.resources)) diff --git a/api/deployments/models/component_deployment.go b/api/deployments/models/component_deployment.go index d2207133..6d12c8cc 100644 --- a/api/deployments/models/component_deployment.go +++ b/api/deployments/models/component_deployment.go @@ -127,6 +127,9 @@ type Component struct { // // required: false Resources *ResourceRequirements `json:"resources,omitempty"` + + // Runtime requirements for the component or job + Runtime *Runtime `json:"runtime,omitempty"` } // ExternalDNS describes an external DNS entry for a component @@ -275,6 +278,9 @@ type ComponentSummary struct { // // required: false Resources *ResourceRequirements `json:"resources,omitempty"` + + // Runtime requirements for the component or job + Runtime *Runtime `json:"runtime,omitempty"` } // ReplicaType The replica type @@ -502,6 +508,14 @@ type ResourceRequirements struct { Requests Resources `json:"requests,omitempty"` } +// Runtime requirements for the component or job +type Runtime struct { + // CPU architecture + // + // example: amd64 + Architecture string `json:"architecture"` +} + func GetReplicaSummary(pod corev1.Pod, lastEventWarning string) ReplicaSummary { replicaSummary := ReplicaSummary{ Type: getReplicaType(pod).String(), diff --git a/api/deployments/models/deployment_builder_test.go b/api/deployments/models/deployment_builder_test.go index b66d156b..6c0a02f2 100644 --- a/api/deployments/models/deployment_builder_test.go +++ b/api/deployments/models/deployment_builder_test.go @@ -4,11 +4,12 @@ import ( "testing" "time" + "github.com/equinor/radix-operator/pkg/apis/defaults" "github.com/equinor/radix-operator/pkg/apis/utils" radixutils "github.com/equinor/radix-common/utils" "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" "github.com/stretchr/testify/assert" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -22,23 +23,25 @@ func Test_DeploymentBuilder_BuildDeploymentSummary(t *testing.T) { t.Parallel() b := NewDeploymentBuilder().WithRadixDeployment( - &v1.RadixDeployment{ + &radixv1.RadixDeployment{ ObjectMeta: metav1.ObjectMeta{ Name: deploymentName, Labels: map[string]string{kube.RadixJobNameLabel: jobName}, }, - Spec: v1.RadixDeploymentSpec{ + Spec: radixv1.RadixDeploymentSpec{ Environment: envName, - Components: []v1.RadixDeployComponent{ + Components: []radixv1.RadixDeployComponent{ {Name: "comp1", Image: "comp_image1"}, - {Name: "comp2", Image: "comp_image2"}, + {Name: "comp2", Image: "comp_image2", Runtime: &radixv1.Runtime{Architecture: radixv1.RuntimeArchitectureArm64}}, + {Name: "comp3", Image: "comp_image3", Node: radixv1.RadixNode{Gpu: "anygpu", GpuCount: "3"}}, }, - Jobs: []v1.RadixDeployJobComponent{ + Jobs: []radixv1.RadixDeployJobComponent{ {Name: "job1", Image: "job_image1"}, - {Name: "job2", Image: "job_image2"}, + {Name: "job2", Image: "job_image2", Runtime: &radixv1.Runtime{Architecture: radixv1.RuntimeArchitectureArm64}}, + {Name: "job3", Image: "job_image3", Node: radixv1.RadixNode{Gpu: "anygpu", GpuCount: "3"}}, }, }, - Status: v1.RadixDeployStatus{ + Status: radixv1.RadixDeployStatus{ ActiveFrom: metav1.NewTime(activeFrom), ActiveTo: metav1.NewTime(activeTo), }, @@ -56,10 +59,12 @@ func Test_DeploymentBuilder_BuildDeploymentSummary(t *testing.T) { CreatedByJob: jobName, }, Components: []*ComponentSummary{ - {Name: "comp1", Image: "comp_image1", Type: string(v1.RadixComponentTypeComponent)}, - {Name: "comp2", Image: "comp_image2", Type: string(v1.RadixComponentTypeComponent)}, - {Name: "job1", Image: "job_image1", Type: string(v1.RadixComponentTypeJob)}, - {Name: "job2", Image: "job_image2", Type: string(v1.RadixComponentTypeJob)}, + {Name: "comp1", Image: "comp_image1", Type: string(radixv1.RadixComponentTypeComponent), Runtime: &Runtime{Architecture: defaults.DefaultNodeSelectorArchitecture}}, + {Name: "comp2", Image: "comp_image2", Type: string(radixv1.RadixComponentTypeComponent), Runtime: &Runtime{Architecture: string(radixv1.RuntimeArchitectureArm64)}}, + {Name: "comp3", Image: "comp_image3", Type: string(radixv1.RadixComponentTypeComponent), Runtime: &Runtime{Architecture: defaults.DefaultNodeSelectorArchitecture}}, + {Name: "job1", Image: "job_image1", Type: string(radixv1.RadixComponentTypeJob), Runtime: &Runtime{Architecture: defaults.DefaultNodeSelectorArchitecture}}, + {Name: "job2", Image: "job_image2", Type: string(radixv1.RadixComponentTypeJob), Runtime: &Runtime{Architecture: string(radixv1.RuntimeArchitectureArm64)}}, + {Name: "job3", Image: "job_image3", Type: string(radixv1.RadixComponentTypeJob), Runtime: &Runtime{Architecture: defaults.DefaultNodeSelectorArchitecture}}, }, } assert.Equal(t, expected, actual) @@ -68,16 +73,16 @@ func Test_DeploymentBuilder_BuildDeploymentSummary(t *testing.T) { t.Run("build with pipeline job info", func(t *testing.T) { t.Parallel() b := NewDeploymentBuilder().WithPipelineJob( - &v1.RadixJob{ + &radixv1.RadixJob{ ObjectMeta: metav1.ObjectMeta{ Name: jobName, }, - Spec: v1.RadixJobSpec{ - PipeLineType: v1.BuildDeploy, - Build: v1.RadixBuildSpec{ + Spec: radixv1.RadixJobSpec{ + PipeLineType: radixv1.BuildDeploy, + Build: radixv1.RadixBuildSpec{ CommitID: commitID, }, - Promote: v1.RadixPromoteSpec{ + Promote: radixv1.RadixPromoteSpec{ FromEnvironment: promoteFromEnv, }, }, @@ -89,7 +94,7 @@ func Test_DeploymentBuilder_BuildDeploymentSummary(t *testing.T) { DeploymentSummaryPipelineJobInfo: DeploymentSummaryPipelineJobInfo{ CreatedByJob: jobName, CommitID: commitID, - PipelineJobType: string(v1.BuildDeploy), + PipelineJobType: string(radixv1.BuildDeploy), PromotedFromEnvironment: promoteFromEnv, }, } @@ -99,27 +104,27 @@ func Test_DeploymentBuilder_BuildDeploymentSummary(t *testing.T) { t.Run("deploy specific components", func(t *testing.T) { t.Parallel() b := NewDeploymentBuilder().WithPipelineJob( - &v1.RadixJob{ + &radixv1.RadixJob{ ObjectMeta: metav1.ObjectMeta{ Name: jobName, }, - Spec: v1.RadixJobSpec{ - PipeLineType: v1.Deploy, - Deploy: v1.RadixDeploySpec{ + Spec: radixv1.RadixJobSpec{ + PipeLineType: radixv1.Deploy, + Deploy: radixv1.RadixDeploySpec{ ToEnvironment: "dev", CommitID: commitID, ComponentsToDeploy: []string{"comp1", "job1"}, }, }, }, - ).WithRadixDeployment(&v1.RadixDeployment{ + ).WithRadixDeployment(&radixv1.RadixDeployment{ ObjectMeta: metav1.ObjectMeta{Name: "rd1"}, - Spec: v1.RadixDeploymentSpec{ - Components: []v1.RadixDeployComponent{ + Spec: radixv1.RadixDeploymentSpec{ + Components: []radixv1.RadixDeployComponent{ {Name: "comp1"}, {Name: "comp2"}, }, - Jobs: []v1.RadixDeployJobComponent{ + Jobs: []radixv1.RadixDeployJobComponent{ {Name: "job1"}, {Name: "job2"}, }, @@ -157,16 +162,16 @@ func Test_DeploymentBuilder_BuildDeployment(t *testing.T) { t.Parallel() b := NewDeploymentBuilder().WithRadixDeployment( - &v1.RadixDeployment{ + &radixv1.RadixDeployment{ ObjectMeta: metav1.ObjectMeta{ Name: deploymentName, Namespace: deploymentNamespace, Labels: map[string]string{kube.RadixJobNameLabel: jobName}, }, - Spec: v1.RadixDeploymentSpec{ + Spec: radixv1.RadixDeploymentSpec{ Environment: envName, }, - Status: v1.RadixDeployStatus{ + Status: radixv1.RadixDeployStatus{ ActiveFrom: metav1.NewTime(activeFrom), ActiveTo: metav1.NewTime(activeTo), }, @@ -193,13 +198,13 @@ func Test_DeploymentBuilder_BuildDeployment(t *testing.T) { b := NewDeploymentBuilder(). WithRadixRegistration(rr). WithPipelineJob( - &v1.RadixJob{ + &radixv1.RadixJob{ ObjectMeta: metav1.ObjectMeta{ Name: jobName, }, - Spec: v1.RadixJobSpec{ - PipeLineType: v1.Deploy, - Deploy: v1.RadixDeploySpec{ + Spec: radixv1.RadixJobSpec{ + PipeLineType: radixv1.Deploy, + Deploy: radixv1.RadixDeploySpec{ ToEnvironment: "dev", ComponentsToDeploy: []string{"comp1", "job1"}, }, @@ -211,24 +216,24 @@ func Test_DeploymentBuilder_BuildDeployment(t *testing.T) { {Name: "job1"}, {Name: "job2"}, }).WithRadixDeployment( - &v1.RadixDeployment{ + &radixv1.RadixDeployment{ ObjectMeta: metav1.ObjectMeta{ Name: deploymentName, Namespace: deploymentNamespace, Labels: map[string]string{kube.RadixJobNameLabel: jobName}, }, - Spec: v1.RadixDeploymentSpec{ + Spec: radixv1.RadixDeploymentSpec{ Environment: envName, - Components: []v1.RadixDeployComponent{ + Components: []radixv1.RadixDeployComponent{ {Name: "comp1"}, {Name: "comp2"}, }, - Jobs: []v1.RadixDeployJobComponent{ + Jobs: []radixv1.RadixDeployJobComponent{ {Name: "job1"}, {Name: "job2"}, }, }, - Status: v1.RadixDeployStatus{ + Status: radixv1.RadixDeployStatus{ ActiveFrom: metav1.NewTime(activeFrom), ActiveTo: metav1.NewTime(activeTo), }, diff --git a/api/deployments/models/scheduled_batch.go b/api/deployments/models/scheduled_batch.go index 1fd05585..f69a7013 100644 --- a/api/deployments/models/scheduled_batch.go +++ b/api/deployments/models/scheduled_batch.go @@ -79,6 +79,9 @@ type ScheduledJobSummary struct { // required: false Node *Node `json:"node,omitempty"` + // Runtime requirements for the batch job + Runtime *Runtime `json:"runtime,omitempty"` + // DeploymentName name of RadixDeployment for the job // // required: false diff --git a/api/environments/component_handler.go b/api/environments/component_handler.go index 9f89dda2..2f659548 100644 --- a/api/environments/component_handler.go +++ b/api/environments/component_handler.go @@ -113,7 +113,7 @@ func (eh EnvironmentHandler) RestartComponentAuxiliaryResource(ctx context.Conte // Get Kubernetes deployment object for auxiliary resource selector := labelselector.ForAuxiliaryResource(appName, componentName, auxType).String() envNs := operatorUtils.GetEnvironmentNamespace(appName, envName) - deploymentList, err := eh.client.AppsV1().Deployments(envNs).List(ctx, metav1.ListOptions{LabelSelector: selector}) + deploymentList, err := eh.accounts.UserAccount.Client.AppsV1().Deployments(envNs).List(ctx, metav1.ListOptions{LabelSelector: selector}) if err != nil { return err } @@ -158,7 +158,7 @@ func canDeploymentBeRestarted(deployment *appsv1.Deployment) bool { } func (eh EnvironmentHandler) patchDeploymentForRestart(ctx context.Context, deployment *appsv1.Deployment) error { - deployClient := eh.client.AppsV1().Deployments(deployment.GetNamespace()) + deployClient := eh.accounts.UserAccount.Client.AppsV1().Deployments(deployment.GetNamespace()) return retry.RetryOnConflict(retry.DefaultRetry, func() error { deployToPatch, err := deployClient.Get(ctx, deployment.GetName(), metav1.GetOptions{}) @@ -190,7 +190,7 @@ func (eh EnvironmentHandler) patch(ctx context.Context, namespace, name string, } if patchBytes != nil { - _, err := eh.radixclient.RadixV1().RadixDeployments(namespace).Patch(ctx, name, types.MergePatchType, patchBytes, metav1.PatchOptions{}) + _, err := eh.accounts.UserAccount.RadixClient.RadixV1().RadixDeployments(namespace).Patch(ctx, name, types.MergePatchType, patchBytes, metav1.PatchOptions{}) if err != nil { return err } diff --git a/api/environments/environment_controller_test.go b/api/environments/environment_controller_test.go index acfb2654..d0597c91 100644 --- a/api/environments/environment_controller_test.go +++ b/api/environments/environment_controller_test.go @@ -28,7 +28,7 @@ import ( "github.com/equinor/radix-common/utils/numbers" "github.com/equinor/radix-common/utils/slice" jobSchedulerModels "github.com/equinor/radix-job-scheduler/models/common" - "github.com/equinor/radix-operator/pkg/apis/defaults" + operatordefaults "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" commontest "github.com/equinor/radix-operator/pkg/apis/test" @@ -478,7 +478,7 @@ func TestRestartComponent_ApplicationWithDeployment_EnvironmentConsistent(t *tes 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[defaults.RadixRestartEnvironmentVariable]) + assert.NotEmpty(t, component.EnvironmentVariables[operatordefaults.RadixRestartEnvironmentVariable]) }) t.Run("Component Restart Fails", func(t *testing.T) { @@ -950,7 +950,7 @@ func TestUpdateSecret_OAuth2_UpdatedOk(t *testing.T) { assert.Equal(t, http.StatusNotFound, response.Code) // Update client secret when k8s secret exists should set Data - secretName := operatorutils.GetAuxiliaryComponentSecretName(anyComponentName, defaults.OAuthProxyAuxiliaryComponentSuffix) + secretName := operatorutils.GetAuxiliaryComponentSecretName(anyComponentName, operatordefaults.OAuthProxyAuxiliaryComponentSuffix) _, err = client.CoreV1().Secrets(envNs).Create(context.Background(), &corev1.Secret{ObjectMeta: metav1.ObjectMeta{Name: secretName}}, metav1.CreateOptions{}) require.NoError(t, err) @@ -962,7 +962,7 @@ func TestUpdateSecret_OAuth2_UpdatedOk(t *testing.T) { response = <-responseChannel assert.Equal(t, http.StatusOK, response.Code) actualSecret, _ := client.CoreV1().Secrets(envNs).Get(context.Background(), secretName, metav1.GetOptions{}) - assert.Equal(t, actualSecret.Data, map[string][]byte{defaults.OAuthClientSecretKeyName: []byte("clientsecret")}) + assert.Equal(t, actualSecret.Data, map[string][]byte{operatordefaults.OAuthClientSecretKeyName: []byte("clientsecret")}) // Update client secret when k8s secret exists should set Data responseChannel = controllerTestUtils.ExecuteRequestWithParameters( @@ -973,7 +973,7 @@ func TestUpdateSecret_OAuth2_UpdatedOk(t *testing.T) { response = <-responseChannel assert.Equal(t, http.StatusOK, response.Code) actualSecret, _ = client.CoreV1().Secrets(envNs).Get(context.Background(), secretName, metav1.GetOptions{}) - assert.Equal(t, actualSecret.Data, map[string][]byte{defaults.OAuthClientSecretKeyName: []byte("clientsecret"), defaults.OAuthCookieSecretKeyName: []byte("cookiesecret")}) + assert.Equal(t, actualSecret.Data, map[string][]byte{operatordefaults.OAuthClientSecretKeyName: []byte("clientsecret"), operatordefaults.OAuthCookieSecretKeyName: []byte("cookiesecret")}) // Update client secret when k8s secret exists should set Data responseChannel = controllerTestUtils.ExecuteRequestWithParameters( @@ -984,7 +984,7 @@ func TestUpdateSecret_OAuth2_UpdatedOk(t *testing.T) { response = <-responseChannel assert.Equal(t, http.StatusOK, response.Code) actualSecret, _ = client.CoreV1().Secrets(envNs).Get(context.Background(), secretName, metav1.GetOptions{}) - assert.Equal(t, actualSecret.Data, map[string][]byte{defaults.OAuthClientSecretKeyName: []byte("clientsecret"), defaults.OAuthCookieSecretKeyName: []byte("cookiesecret"), defaults.OAuthRedisPasswordKeyName: []byte("redispassword")}) + assert.Equal(t, actualSecret.Data, map[string][]byte{operatordefaults.OAuthClientSecretKeyName: []byte("clientsecret"), operatordefaults.OAuthCookieSecretKeyName: []byte("cookiesecret"), operatordefaults.OAuthRedisPasswordKeyName: []byte("redispassword")}) } func TestGetSecretDeployments_SortedWithFromTo(t *testing.T) { @@ -1623,7 +1623,7 @@ func Test_GetJob(t *testing.T) { for _, scenario := range scenarions { scenario := scenario - t.Run(scenario.JobName, func(t *testing.T) { + t.Run(scenario.Name, func(t *testing.T) { responseChannel := environmentControllerTestUtils.ExecuteRequest("GET", fmt.Sprintf("/api/v1/applications/%s/environments/%s/jobcomponents/%s/jobs/%s", anyAppName, anyEnvironment, anyJobName, scenario.JobName)) response := <-responseChannel assert.Equal(t, scenario.Success, response.Code == http.StatusOK) @@ -1753,6 +1753,9 @@ func Test_GetJob_AllProps(t *testing.T) { Created: radixutils.FormatTimestamp(podCreationTime.Time), Status: deploymentModels.ReplicaStatus{Status: "Succeeded"}, }}, + Runtime: &deploymentModels.Runtime{ + Architecture: operatordefaults.DefaultNodeSelectorArchitecture, + }, }, actual) // Test job2 props - override props from RD jobComponent @@ -1773,6 +1776,9 @@ func Test_GetJob_AllProps(t *testing.T) { }, Node: &deploymentModels.Node{Gpu: "gpu2", GpuCount: "3"}, DeploymentName: anyDeployment, + Runtime: &deploymentModels.Runtime{ + Architecture: operatordefaults.DefaultNodeSelectorArchitecture, + }, }, actual) } diff --git a/api/environments/environment_handler.go b/api/environments/environment_handler.go index 21c5004a..dbdd69ae 100644 --- a/api/environments/environment_handler.go +++ b/api/environments/environment_handler.go @@ -25,13 +25,11 @@ import ( "github.com/equinor/radix-operator/pkg/apis/kube" v1 "github.com/equinor/radix-operator/pkg/apis/radix/v1" k8sObjectUtils "github.com/equinor/radix-operator/pkg/apis/utils" - radixclient "github.com/equinor/radix-operator/pkg/client/clientset/versioned" "github.com/rs/zerolog/log" "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/selection" - "k8s.io/client-go/kubernetes" ) // EnvironmentHandlerOptions defines a configuration function @@ -40,8 +38,6 @@ type EnvironmentHandlerOptions func(*EnvironmentHandler) // WithAccounts configures all EnvironmentHandler fields func WithAccounts(accounts models.Accounts) EnvironmentHandlerOptions { return func(eh *EnvironmentHandler) { - eh.client = accounts.UserAccount.Client - eh.radixclient = accounts.UserAccount.RadixClient eh.deployHandler = deployments.Init(accounts) eh.eventHandler = events.Init(accounts.UserAccount.Client) eh.accounts = accounts @@ -89,8 +85,6 @@ func NewEnvironmentHandlerFactory(opts ...EnvironmentHandlerOptions) Environment // EnvironmentHandler Instance variables type EnvironmentHandler struct { - client kubernetes.Interface - radixclient radixclient.Interface deployHandler deployments.DeployHandler eventHandler events.EventHandler accounts models.Accounts @@ -221,13 +215,13 @@ func (eh EnvironmentHandler) GetEnvironment(ctx context.Context, appName, envNam // CreateEnvironment Handler for CreateEnvironment. Creates an environment if it does not exist func (eh EnvironmentHandler) CreateEnvironment(ctx context.Context, appName, envName string) (*v1.RadixEnvironment, error) { // ensure application exists - rr, err := eh.radixclient.RadixV1().RadixRegistrations().Get(ctx, appName, metav1.GetOptions{}) + rr, err := eh.accounts.UserAccount.RadixClient.RadixV1().RadixRegistrations().Get(ctx, appName, metav1.GetOptions{}) if err != nil { return nil, err } // idempotent creation of RadixEnvironment - re, err := eh.getServiceAccount().RadixClient.RadixV1().RadixEnvironments().Create(ctx, k8sObjectUtils. + re, err := eh.accounts.ServiceAccount.RadixClient.RadixV1().RadixEnvironments().Create(ctx, k8sObjectUtils. NewEnvironmentBuilder(). WithAppLabel(). WithAppName(appName). @@ -256,7 +250,7 @@ func (eh EnvironmentHandler) DeleteEnvironment(ctx context.Context, appName, env } // idempotent removal of RadixEnvironment - err = eh.getServiceAccount().RadixClient.RadixV1().RadixEnvironments().Delete(ctx, re.Name, metav1.DeleteOptions{}) + err = eh.accounts.ServiceAccount.RadixClient.RadixV1().RadixEnvironments().Delete(ctx, re.Name, metav1.DeleteOptions{}) // if an error is anything other than not-found, return it if err != nil && !errors.IsNotFound(err) { return err @@ -301,25 +295,21 @@ func (eh EnvironmentHandler) getNotOrphanedEnvNames(ctx context.Context, appName ), nil } -func (eh EnvironmentHandler) getServiceAccount() models.Account { - return eh.accounts.ServiceAccount -} - // GetLogs handler for GetLogs func (eh EnvironmentHandler) GetLogs(ctx context.Context, appName, envName, podName string, sinceTime *time.Time, logLines *int64, previousLog bool) (io.ReadCloser, error) { - podHandler := pods.Init(eh.client) + podHandler := pods.Init(eh.accounts.UserAccount.Client) return podHandler.HandleGetEnvironmentPodLog(ctx, appName, envName, podName, "", sinceTime, logLines, previousLog) } // GetScheduledJobLogs handler for GetScheduledJobLogs func (eh EnvironmentHandler) GetScheduledJobLogs(ctx context.Context, appName, envName, scheduledJobName, replicaName string, sinceTime *time.Time, logLines *int64) (io.ReadCloser, error) { - handler := pods.Init(eh.client) + handler := pods.Init(eh.accounts.UserAccount.Client) return handler.HandleGetEnvironmentScheduledJobLog(ctx, appName, envName, scheduledJobName, replicaName, "", sinceTime, logLines) } // GetAuxiliaryResourcePodLog handler for GetAuxiliaryResourcePodLog func (eh EnvironmentHandler) GetAuxiliaryResourcePodLog(ctx context.Context, appName, envName, componentName, auxType, podName string, sinceTime *time.Time, logLines *int64) (io.ReadCloser, error) { - podHandler := pods.Init(eh.client) + podHandler := pods.Init(eh.accounts.UserAccount.Client) return podHandler.HandleGetEnvironmentAuxiliaryResourcePodLog(ctx, appName, envName, componentName, auxType, podName, sinceTime, logLines) } @@ -460,7 +450,7 @@ func (eh EnvironmentHandler) getRadixCommonComponentUpdater(ctx context.Context, ra, _ := kubequery.GetRadixApplication(ctx, eh.accounts.UserAccount.RadixClient, appName) baseUpdater.environmentConfig = utils.GetComponentEnvironmentConfig(ra, envName, componentName) - baseUpdater.componentState, err = getComponentStateFromSpec(ctx, eh.client, appName, deploymentSummary, rd.Status, baseUpdater.environmentConfig, componentToPatch, hpas, scalers) + baseUpdater.componentState, err = getComponentStateFromSpec(ctx, eh.accounts.UserAccount.Client, appName, deploymentSummary, rd.Status, baseUpdater.environmentConfig, componentToPatch, hpas, scalers) if err != nil { return nil, err } diff --git a/api/environments/job_handler.go b/api/environments/job_handler.go index 68f0339b..1e9a9116 100644 --- a/api/environments/job_handler.go +++ b/api/environments/job_handler.go @@ -11,30 +11,40 @@ 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" + apimodels "github.com/equinor/radix-api/api/models" "github.com/equinor/radix-api/api/utils" radixhttp "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-common/utils/slice" jobsSchedulerModels "github.com/equinor/radix-job-scheduler/models" - jobSchedulerModels "github.com/equinor/radix-job-scheduler/models/common" - jobSchedulerV1Models "github.com/equinor/radix-job-scheduler/models/v1" "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" - "k8s.io/apimachinery/pkg/api/errors" + kubeerrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" ) // GetJobs Get jobs func (eh EnvironmentHandler) GetJobs(ctx context.Context, appName, envName, jobComponentName string) ([]deploymentModels.ScheduledJobSummary, error) { - jobs, err := eh.getJobs(ctx, appName, envName, jobComponentName) + rdList, err := kubequery.GetRadixDeploymentsForEnvironment(ctx, eh.accounts.UserAccount.RadixClient, appName, envName) + if err != nil { + return nil, err + } + rbList, err := kubequery.GetRadixBatchesForJobComponent(ctx, eh.accounts.UserAccount.RadixClient, appName, envName, jobComponentName, kube.RadixBatchTypeJob) if err != nil { return nil, err } + batches := apimodels.BuildScheduledBatchSummaries(rbList, rdList) + + var jobs []deploymentModels.ScheduledJobSummary + for _, batch := range batches { + jobs = append(jobs, batch.JobList...) + } + sort.SliceStable(jobs, func(i, j int) bool { return utils.IsBefore(&jobs[j], &jobs[i]) }) @@ -42,17 +52,33 @@ func (eh EnvironmentHandler) GetJobs(ctx context.Context, appName, envName, jobC return jobs, nil } -func (eh EnvironmentHandler) getJobs(ctx context.Context, appName, envName, jobComponentName string) ([]deploymentModels.ScheduledJobSummary, error) { - radixBatches, err := eh.getRadixBatches(ctx, appName, envName, jobComponentName, kube.RadixBatchTypeJob) +// GetJob Gets job by name +func (eh EnvironmentHandler) GetJob(ctx context.Context, appName, envName, jobComponentName, jobName string) (*deploymentModels.ScheduledJobSummary, error) { + return eh.getJob(ctx, appName, envName, jobComponentName, jobName) +} + +// GetBatches Get batches +func (eh EnvironmentHandler) GetBatches(ctx context.Context, appName, envName, jobComponentName string) ([]deploymentModels.ScheduledBatchSummary, error) { + rdList, err := kubequery.GetRadixDeploymentsForEnvironment(ctx, eh.accounts.UserAccount.RadixClient, appName, envName) + if err != nil { + return nil, err + } + rbList, err := kubequery.GetRadixBatchesForJobComponent(ctx, eh.accounts.UserAccount.RadixClient, appName, envName, jobComponentName, kube.RadixBatchTypeBatch) if err != nil { return nil, err } - return eh.getScheduledJobSummaryList(radixBatches), nil + + batches := apimodels.BuildScheduledBatchSummaries(rbList, rdList) + sort.SliceStable(batches, func(i, j int) bool { + return utils.IsBefore(&batches[j], &batches[i]) + }) + + return batches, nil } -// GetJob Gets job by name -func (eh EnvironmentHandler) GetJob(ctx context.Context, appName, envName, jobComponentName, jobName string) (*deploymentModels.ScheduledJobSummary, error) { - return eh.getJob(ctx, appName, envName, jobComponentName, jobName) +// GetBatch Gets batch by name +func (eh EnvironmentHandler) GetBatch(ctx context.Context, appName, envName, jobComponentName, batchName string) (*deploymentModels.ScheduledBatchSummary, error) { + return eh.getBatch(ctx, appName, envName, jobComponentName, batchName) } // StopJob Stop job by name @@ -99,11 +125,6 @@ func (eh EnvironmentHandler) RestartBatch(ctx context.Context, appName, envName, return err } -func setRestartJobTimeout(batch *radixv1.RadixBatch, jobIdx int, restartTimestamp string) { - batch.Spec.Jobs[jobIdx].Stop = nil - batch.Spec.Jobs[jobIdx].Restart = restartTimestamp -} - // DeleteJob Delete job by name func (eh EnvironmentHandler) DeleteJob(ctx context.Context, appName, envName, jobComponentName, jobName string) error { batchName, _, ok := parseBatchAndJobNameFromScheduledJobName(jobName) @@ -119,57 +140,6 @@ func (eh EnvironmentHandler) DeleteJob(ctx context.Context, appName, envName, jo return eh.accounts.UserAccount.RadixClient.RadixV1().RadixBatches(batch.GetNamespace()).Delete(ctx, batch.GetName(), metav1.DeleteOptions{}) } -func (eh EnvironmentHandler) getJob(ctx context.Context, appName, envName, jobComponentName, jobName string) (*deploymentModels.ScheduledJobSummary, error) { - batchName, batchJobName, ok := parseBatchAndJobNameFromScheduledJobName(jobName) - if !ok { - return nil, jobNotFoundError(jobName) - } - - batch, err := eh.getRadixBatch(ctx, appName, envName, jobComponentName, batchName, "") - if err != nil { - return nil, err - } - - jobs := slice.FindAll(batch.Spec.Jobs, func(job radixv1.RadixBatchJob) bool { return job.Name == batchJobName }) - if len(jobs) == 0 { - return nil, jobNotFoundError(jobName) - } - - var jobComponent *radixv1.RadixDeployJobComponent - namespace := operatorUtils.GetEnvironmentNamespace(appName, envName) - if rd, err := eh.accounts.UserAccount.RadixClient.RadixV1().RadixDeployments(namespace).Get(ctx, batch.Spec.RadixDeploymentJobRef.Name, metav1.GetOptions{}); err == nil { - if rdJobs := slice.FindAll(rd.Spec.Jobs, func(job radixv1.RadixDeployJobComponent) bool { return job.Name == jobComponentName }); len(rdJobs) > 0 { - jobComponent = &rdJobs[0] - } - } - - jobSummary := eh.getScheduledJobSummary(batch, jobs[0], jobComponent) - return &jobSummary, nil -} - -// GetBatches Get batches -func (eh EnvironmentHandler) GetBatches(ctx context.Context, appName, envName, jobComponentName string) ([]deploymentModels.ScheduledBatchSummary, error) { - summaries, err := eh.getBatches(ctx, appName, envName, jobComponentName) - if err != nil { - return nil, err - } - - sort.SliceStable(summaries, func(i, j int) bool { - return utils.IsBefore(&summaries[j], &summaries[i]) - }) - - return summaries, nil -} - -func (eh EnvironmentHandler) getBatches(ctx context.Context, appName, envName, jobComponentName string) ([]deploymentModels.ScheduledBatchSummary, error) { - radixBatches, err := eh.getRadixBatches(ctx, appName, envName, jobComponentName, kube.RadixBatchTypeBatch) - if err != nil { - return nil, err - } - - return eh.getScheduledBatchSummaryList(radixBatches), nil -} - // StopBatch Stop batch by name func (eh EnvironmentHandler) StopBatch(ctx context.Context, appName, envName, jobComponentName, batchName string) error { batch, err := eh.getRadixBatch(ctx, appName, envName, jobComponentName, batchName, kube.RadixBatchTypeBatch) @@ -216,7 +186,7 @@ func (eh EnvironmentHandler) CopyBatch(ctx context.Context, appName, envName, jo if err != nil { return nil, err } - return eh.getScheduledBatchStatus(batchStatus, deploymentName), nil + return eh.getBatch(ctx, appName, envName, jobComponentName, batchStatus.BatchName) } // CopyJob Copy job by name @@ -227,29 +197,52 @@ func (eh EnvironmentHandler) CopyJob(ctx context.Context, appName, envName, jobC if err != nil { return nil, err } - return eh.getScheduledJobStatus(jobStatus, deploymentName), nil + + return eh.getJob(ctx, appName, envName, jobComponentName, jobStatus.Name) } -// GetBatch Gets batch by name -func (eh EnvironmentHandler) GetBatch(ctx context.Context, appName, envName, jobComponentName, batchName string) (*deploymentModels.ScheduledBatchSummary, error) { - return eh.getBatch(ctx, appName, envName, jobComponentName, batchName) +// GetJobPayload Gets job payload +func (eh EnvironmentHandler) GetJobPayload(ctx context.Context, appName, envName, jobComponentName, jobName string) (io.ReadCloser, error) { + return eh.getJobPayload(ctx, appName, envName, jobComponentName, jobName) } -func (eh EnvironmentHandler) getBatch(ctx context.Context, appName, envName, jobComponentName, batchName string) (*deploymentModels.ScheduledBatchSummary, error) { - batch, err := eh.getRadixBatch(ctx, appName, envName, jobComponentName, batchName, kube.RadixBatchTypeBatch) +func (eh EnvironmentHandler) getJob(ctx context.Context, appName, envName, jobComponentName, jobName string) (*deploymentModels.ScheduledJobSummary, error) { + batchName, _, ok := parseBatchAndJobNameFromScheduledJobName(jobName) + if !ok { + return nil, jobNotFoundError(jobName) + } + + rb, err := eh.getRadixBatch(ctx, appName, envName, jobComponentName, batchName, "") if err != nil { return nil, err } - batchSummary := eh.getScheduledBatchSummary(batch) - batchSummary.JobList = eh.getScheduledJobSummaries(batch) - return &batchSummary, nil + rd, err := kubequery.GetRadixDeploymentByName(ctx, eh.accounts.UserAccount.RadixClient, appName, envName, rb.Spec.RadixDeploymentJobRef.Name) + if err != nil && !kubeerrors.IsNotFound(err) { + return nil, err + } + + batch := apimodels.BuildScheduledBatchSummary(rb, rd) + job, found := slice.FindFirst(batch.JobList, func(j deploymentModels.ScheduledJobSummary) bool { return j.Name == jobName }) + if !found { + return nil, jobNotFoundError(jobName) + } + return &job, nil } -// GetJobPayload Gets job payload -func (eh EnvironmentHandler) GetJobPayload(ctx context.Context, appName, envName, jobComponentName, jobName string) (io.ReadCloser, error) { - return eh.getJobPayload(ctx, appName, envName, jobComponentName, jobName) +func (eh EnvironmentHandler) getBatch(ctx context.Context, appName, envName, jobComponentName, batchName string) (*deploymentModels.ScheduledBatchSummary, error) { + rb, err := eh.getRadixBatch(ctx, appName, envName, jobComponentName, batchName, kube.RadixBatchTypeBatch) + if err != nil { + return nil, err + } + + rd, err := kubequery.GetRadixDeploymentByName(ctx, eh.accounts.UserAccount.RadixClient, appName, envName, rb.Spec.RadixDeploymentJobRef.Name) + if err != nil && !kubeerrors.IsNotFound(err) { + return nil, err + } + + return apimodels.BuildScheduledBatchSummary(rb, rd), nil } func (eh EnvironmentHandler) getJobPayload(ctx context.Context, appName, envName, jobComponentName, jobName string) (io.ReadCloser, error) { @@ -276,7 +269,7 @@ func (eh EnvironmentHandler) getJobPayload(ctx context.Context, appName, envName namespace := operatorUtils.GetEnvironmentNamespace(appName, envName) secret, err := eh.accounts.ServiceAccount.Client.CoreV1().Secrets(namespace).Get(ctx, job.PayloadSecretRef.Name, metav1.GetOptions{}) if err != nil { - if errors.IsNotFound(err) { + if kubeerrors.IsNotFound(err) { return nil, environmentModels.ScheduledJobPayloadNotFoundError(appName, jobName) } return nil, err @@ -290,22 +283,6 @@ func (eh EnvironmentHandler) getJobPayload(ctx context.Context, appName, envName return io.NopCloser(bytes.NewReader(payload)), nil } -func (eh EnvironmentHandler) getRadixBatches(ctx context.Context, appName, envName, jobComponentName string, batchType kube.RadixBatchType) ([]radixv1.RadixBatch, error) { - namespace := operatorUtils.GetEnvironmentNamespace(appName, envName) - selector := radixLabels.Merge( - radixLabels.ForApplicationName(appName), - radixLabels.ForComponentName(jobComponentName), - radixLabels.ForBatchType(batchType), - ) - - batches, err := eh.accounts.UserAccount.RadixClient.RadixV1().RadixBatches(namespace).List(ctx, metav1.ListOptions{LabelSelector: selector.String()}) - if err != nil { - return nil, err - } - - return batches.Items, nil -} - func (eh EnvironmentHandler) getRadixBatch(ctx context.Context, appName, envName, jobComponentName, batchName string, batchType kube.RadixBatchType) (*radixv1.RadixBatch, error) { namespace := operatorUtils.GetEnvironmentNamespace(appName, envName) labelSelector := radixLabels.Merge( @@ -322,7 +299,7 @@ func (eh EnvironmentHandler) getRadixBatch(ctx context.Context, appName, envName batch, err := eh.accounts.UserAccount.RadixClient.RadixV1().RadixBatches(namespace).Get(ctx, batchName, metav1.GetOptions{}) if err != nil { - if errors.IsNotFound(err) { + if kubeerrors.IsNotFound(err) { return nil, batchNotFoundError(batchName) } return nil, err @@ -335,222 +312,6 @@ func (eh EnvironmentHandler) getRadixBatch(ctx context.Context, appName, envName return batch, nil } -func (eh EnvironmentHandler) getScheduledBatchSummaryList(batches []radixv1.RadixBatch) (summaries []deploymentModels.ScheduledBatchSummary) { - for _, batch := range batches { - summaries = append(summaries, eh.getScheduledBatchSummary(&batch)) - } - - return -} - -func (eh EnvironmentHandler) getScheduledBatchSummary(batch *radixv1.RadixBatch) deploymentModels.ScheduledBatchSummary { - return deploymentModels.ScheduledBatchSummary{ - Name: batch.Name, - DeploymentName: batch.Spec.RadixDeploymentJobRef.Name, - Status: getScheduledBatchStatus(batch).String(), - TotalJobCount: len(batch.Spec.Jobs), - JobList: eh.getScheduledJobSummaries(batch), - Created: radixutils.FormatTimestamp(batch.GetCreationTimestamp().Time), - Started: radixutils.FormatTime(batch.Status.Condition.ActiveTime), - Ended: radixutils.FormatTime(batch.Status.Condition.CompletionTime), - } -} - -func (eh EnvironmentHandler) getScheduledBatchStatus(batchStatus *jobSchedulerV1Models.BatchStatus, deploymentName string) *deploymentModels.ScheduledBatchSummary { - return &deploymentModels.ScheduledBatchSummary{ - Name: batchStatus.Name, - DeploymentName: deploymentName, - Status: batchStatus.Status, - TotalJobCount: len(batchStatus.JobStatuses), - Created: batchStatus.Created, - Started: batchStatus.Started, - Ended: batchStatus.Ended, - } -} - -func (eh EnvironmentHandler) getScheduledJobStatus(jobStatus *jobSchedulerV1Models.JobStatus, deploymentName string) *deploymentModels.ScheduledJobSummary { - return &deploymentModels.ScheduledJobSummary{ - Name: fmt.Sprintf("%s-%s", jobStatus.BatchName, jobStatus.Name), - DeploymentName: deploymentName, - BatchName: jobStatus.BatchName, - JobId: jobStatus.JobId, - Status: jobStatus.Status, - } -} - -func (eh EnvironmentHandler) getScheduledJobSummaryList(batches []radixv1.RadixBatch) (summaries []deploymentModels.ScheduledJobSummary) { - for _, batch := range batches { - summaries = append(summaries, eh.getScheduledJobSummaries(&batch)...) - } - - return -} - -func (eh EnvironmentHandler) getScheduledJobSummaries(batch *radixv1.RadixBatch) (summaries []deploymentModels.ScheduledJobSummary) { - for _, job := range batch.Spec.Jobs { - summaries = append(summaries, eh.getScheduledJobSummary(batch, job, nil)) - } - return -} - -func (eh EnvironmentHandler) getScheduledJobSummary(batch *radixv1.RadixBatch, job radixv1.RadixBatchJob, jobComponent *radixv1.RadixDeployJobComponent) deploymentModels.ScheduledJobSummary { - var batchName string - if batch.GetLabels()[kube.RadixBatchTypeLabel] == string(kube.RadixBatchTypeBatch) { - batchName = batch.GetName() - } - - summary := deploymentModels.ScheduledJobSummary{ - Name: fmt.Sprintf("%s-%s", batch.GetName(), job.Name), - DeploymentName: batch.Spec.RadixDeploymentJobRef.Name, - BatchName: batchName, - JobId: job.JobId, - ReplicaList: getReplicaSummariesForJob(batch, job), - Status: jobSchedulerModels.Waiting.String(), - } - - if jobComponent != nil { - summary.TimeLimitSeconds = jobComponent.TimeLimitSeconds - if job.TimeLimitSeconds != nil { - summary.TimeLimitSeconds = job.TimeLimitSeconds - } - - if jobComponent.BackoffLimit != nil { - summary.BackoffLimit = *jobComponent.BackoffLimit - } - if job.BackoffLimit != nil { - summary.BackoffLimit = *job.BackoffLimit - } - - if jobComponent.Node != (radixv1.RadixNode{}) { - summary.Node = (*deploymentModels.Node)(&jobComponent.Node) - } - if job.Node != nil { - summary.Node = (*deploymentModels.Node)(job.Node) - } - - if job.Resources != nil { - summary.Resources = deploymentModels.ConvertRadixResourceRequirements(*job.Resources) - } else if len(jobComponent.Resources.Requests) > 0 || len(jobComponent.Resources.Limits) > 0 { - summary.Resources = deploymentModels.ConvertRadixResourceRequirements(jobComponent.Resources) - } - } - - stopJob := job.Stop != nil && *job.Stop - - if statuses := slice.FindAll(batch.Status.JobStatuses, func(jobStatus radixv1.RadixBatchJobStatus) bool { return jobStatus.Name == job.Name }); len(statuses) == 1 { - status := statuses[0] - summary.Status = getScheduledJobStatus(status, stopJob).String() - summary.Created = radixutils.FormatTime(status.CreationTime) - summary.Started = radixutils.FormatTime(status.StartTime) - summary.Ended = radixutils.FormatTime(status.EndTime) - summary.Message = status.Message - summary.FailedCount = status.Failed - summary.Restart = status.Restart - } else if len(statuses) == 0 && stopJob { - summary.Status = jobSchedulerModels.Stopping.String() - } - return summary -} - -func getScheduledBatchStatus(batch *radixv1.RadixBatch) (status jobSchedulerModels.ProgressStatus) { - switch { - case batch.Status.Condition.Type == radixv1.BatchConditionTypeActive: - if slice.Any(batch.Status.JobStatuses, func(jobStatus radixv1.RadixBatchJobStatus) bool { - return jobStatus.Phase == radixv1.BatchJobPhaseRunning - }) { - return jobSchedulerModels.Running - } - return jobSchedulerModels.Active - case batch.Status.Condition.Type == radixv1.BatchConditionTypeCompleted: - if len(batch.Status.JobStatuses) > 0 && slice.All(batch.Status.JobStatuses, func(jobStatus radixv1.RadixBatchJobStatus) bool { - return jobStatus.Phase == radixv1.BatchJobPhaseFailed - }) { - return jobSchedulerModels.Failed - } - return jobSchedulerModels.Succeeded - } - return jobSchedulerModels.Waiting -} - -func getScheduledJobStatus(jobStatus radixv1.RadixBatchJobStatus, stopJob bool) (status jobSchedulerModels.ProgressStatus) { - status = jobSchedulerModels.Waiting - switch jobStatus.Phase { - case radixv1.BatchJobPhaseActive: - status = jobSchedulerModels.Active - case radixv1.BatchJobPhaseRunning: - status = jobSchedulerModels.Running - case radixv1.BatchJobPhaseSucceeded: - status = jobSchedulerModels.Succeeded - case radixv1.BatchJobPhaseFailed: - status = jobSchedulerModels.Failed - case radixv1.BatchJobPhaseStopped: - status = jobSchedulerModels.Stopped - case radixv1.BatchJobPhaseWaiting: - status = jobSchedulerModels.Waiting - } - if stopJob && (status == jobSchedulerModels.Waiting || status == jobSchedulerModels.Active || status == jobSchedulerModels.Running) { - return jobSchedulerModels.Stopping - } - if len(jobStatus.RadixBatchJobPodStatuses) > 0 && slice.All(jobStatus.RadixBatchJobPodStatuses, func(jobPodStatus radixv1.RadixBatchJobPodStatus) bool { - return jobPodStatus.Phase == radixv1.PodFailed - }) { - return jobSchedulerModels.Failed - } - return status -} - -func getReplicaSummariesForJob(radixBatch *radixv1.RadixBatch, job radixv1.RadixBatchJob) []deploymentModels.ReplicaSummary { - if jobStatus, ok := slice.FindFirst(radixBatch.Status.JobStatuses, func(jobStatus radixv1.RadixBatchJobStatus) bool { - return jobStatus.Name == job.Name - }); ok { - return slice.Reduce(jobStatus.RadixBatchJobPodStatuses, make([]deploymentModels.ReplicaSummary, 0), - func(acc []deploymentModels.ReplicaSummary, status radixv1.RadixBatchJobPodStatus) []deploymentModels.ReplicaSummary { - return append(acc, getReplicaSummaryByJobPodStatus(status, job)) - }) - } - return nil -} - -func getReplicaSummaryByJobPodStatus(status radixv1.RadixBatchJobPodStatus, job radixv1.RadixBatchJob) deploymentModels.ReplicaSummary { - summary := deploymentModels.ReplicaSummary{ - Name: status.Name, - Created: radixutils.FormatTimestamp(status.CreationTime.Time), - RestartCount: status.RestartCount, - Image: status.Image, - ImageId: status.ImageID, - PodIndex: status.PodIndex, - Reason: status.Reason, - StatusMessage: status.Message, - ExitCode: status.ExitCode, - Status: getReplicaStatusByJobPodStatusPhase(status.Phase), - } - if status.StartTime != nil { - summary.StartTime = radixutils.FormatTimestamp(status.StartTime.Time) - } - if status.EndTime != nil { - summary.EndTime = radixutils.FormatTimestamp(status.EndTime.Time) - } - if job.Resources != nil { - summary.Resources = pointers.Ptr(deploymentModels.ConvertRadixResourceRequirements(*job.Resources)) - } - return summary -} - -func getReplicaStatusByJobPodStatusPhase(statusPhase radixv1.RadixBatchJobPodPhase) deploymentModels.ReplicaStatus { - replicaStatus := deploymentModels.ReplicaStatus{} - switch statusPhase { - case radixv1.PodFailed: - replicaStatus.Status = "Failed" - case radixv1.PodRunning: - replicaStatus.Status = "Running" - case radixv1.PodSucceeded: - replicaStatus.Status = "Succeeded" - default: - replicaStatus.Status = "Pending" - } - return replicaStatus -} - // check if batch can be stopped func isBatchStoppable(condition radixv1.RadixBatchCondition) bool { return condition.Type == "" || @@ -611,3 +372,8 @@ func getJobSchedulerEnvFor(appName, envName, jobComponentName, deploymentName st RadixJobSchedulersPerEnvironmentHistoryLimit: 10, } } + +func setRestartJobTimeout(batch *radixv1.RadixBatch, jobIdx int, restartTimestamp string) { + batch.Spec.Jobs[jobIdx].Stop = nil + batch.Spec.Jobs[jobIdx].Restart = restartTimestamp +} diff --git a/api/environments/utils.go b/api/environments/utils.go index 9ba0d2bd..7f13f81e 100644 --- a/api/environments/utils.go +++ b/api/environments/utils.go @@ -16,7 +16,7 @@ func (eh EnvironmentHandler) getRadixDeployment(ctx context.Context, appName, en return nil, nil, err } - radixDeployment, err := eh.radixclient.RadixV1().RadixDeployments(envNs).Get(ctx, deploymentSummary.Name, metav1.GetOptions{}) + radixDeployment, err := eh.accounts.UserAccount.RadixClient.RadixV1().RadixDeployments(envNs).Get(ctx, deploymentSummary.Name, metav1.GetOptions{}) if err != nil { return nil, nil, err } diff --git a/api/kubequery/batch.go b/api/kubequery/batch.go new file mode 100644 index 00000000..9865b65a --- /dev/null +++ b/api/kubequery/batch.go @@ -0,0 +1,28 @@ +package kubequery + +import ( + "context" + + "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" + radixclient "github.com/equinor/radix-operator/pkg/client/clientset/versioned" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func GetRadixBatchesForJobComponent(ctx context.Context, client radixclient.Interface, appName, envName, jobComponentName string, batchType kube.RadixBatchType) ([]radixv1.RadixBatch, error) { + namespace := operatorutils.GetEnvironmentNamespace(appName, envName) + selector := radixlabels.Merge( + radixlabels.ForApplicationName(appName), + radixlabels.ForComponentName(jobComponentName), + radixlabels.ForBatchType(batchType), + ) + + batches, err := client.RadixV1().RadixBatches(namespace).List(ctx, metav1.ListOptions{LabelSelector: selector.String()}) + if err != nil { + return nil, err + } + + return batches.Items, nil +} diff --git a/api/kubequery/batch_test.go b/api/kubequery/batch_test.go new file mode 100644 index 00000000..de1e1d51 --- /dev/null +++ b/api/kubequery/batch_test.go @@ -0,0 +1,88 @@ +package kubequery_test + +import ( + "context" + "fmt" + "testing" + + "github.com/equinor/radix-api/api/kubequery" + "github.com/equinor/radix-operator/pkg/apis/kube" + radixv1 "github.com/equinor/radix-operator/pkg/apis/radix/v1" + radixlabels "github.com/equinor/radix-operator/pkg/apis/utils/labels" + radixfake "github.com/equinor/radix-operator/pkg/client/clientset/versioned/fake" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func Test_GetRadixBatchesForJobComponent(t *testing.T) { + app, env, comp := "app1", "env1", "c1" + + ns := func(app, env string) string { return fmt.Sprintf("%s-%s", app, env) } + matchjob1 := radixv1.RadixBatch{ObjectMeta: metav1.ObjectMeta{ + Name: "matchjob1", + Namespace: ns(app, env), + Labels: radixlabels.Merge(radixlabels.ForApplicationName(app), radixlabels.ForComponentName(comp), radixlabels.ForBatchType(kube.RadixBatchTypeJob)), + }} + matchjob2 := radixv1.RadixBatch{ObjectMeta: metav1.ObjectMeta{ + Name: "matchjob2", + Namespace: ns(app, env), + Labels: radixlabels.Merge(radixlabels.ForApplicationName(app), radixlabels.ForComponentName(comp), radixlabels.ForBatchType(kube.RadixBatchTypeJob)), + }} + matchbatch1 := radixv1.RadixBatch{ObjectMeta: metav1.ObjectMeta{ + Name: "matchbatch1", + Namespace: ns(app, env), + Labels: radixlabels.Merge(radixlabels.ForApplicationName(app), radixlabels.ForComponentName(comp), radixlabels.ForBatchType(kube.RadixBatchTypeBatch)), + }} + unmatched1 := radixv1.RadixBatch{ObjectMeta: metav1.ObjectMeta{ + Name: "unmatched1", + Namespace: ns(app, env), + Labels: radixlabels.Merge(radixlabels.ForApplicationName(app), radixlabels.ForComponentName("othercomp"), radixlabels.ForBatchType(kube.RadixBatchTypeJob)), + }} + unmatched2 := radixv1.RadixBatch{ObjectMeta: metav1.ObjectMeta{ + Name: "unmatched2", + Namespace: ns(app, env), + Labels: radixlabels.Merge(radixlabels.ForComponentName(comp), radixlabels.ForBatchType(kube.RadixBatchTypeJob)), + }} + unmatched3 := radixv1.RadixBatch{ObjectMeta: metav1.ObjectMeta{ + Name: "unmatched3", + Namespace: ns(app, env), + Labels: radixlabels.Merge(radixlabels.ForApplicationName(app), radixlabels.ForBatchType(kube.RadixBatchTypeJob)), + }} + unmatched4 := radixv1.RadixBatch{ObjectMeta: metav1.ObjectMeta{ + Name: "unmatched4", + Namespace: ns(app, "otherenv"), + Labels: radixlabels.Merge(radixlabels.ForApplicationName(app), radixlabels.ForComponentName(comp), radixlabels.ForBatchType(kube.RadixBatchTypeJob)), + }} + unmatched5 := radixv1.RadixBatch{ObjectMeta: metav1.ObjectMeta{ + Name: "unmatched5", + Namespace: ns(app, env), + Labels: radixlabels.Merge(radixlabels.ForApplicationName(app), radixlabels.ForComponentName(comp)), + }} + + client := radixfake.NewSimpleClientset() + applyRb := func(rb *radixv1.RadixBatch) error { + _, err := client.RadixV1().RadixBatches(rb.Namespace).Create(context.Background(), rb, metav1.CreateOptions{}) + return err + } + require.NoError(t, applyRb(&matchjob1)) + require.NoError(t, applyRb(&matchjob2)) + require.NoError(t, applyRb(&matchbatch1)) + require.NoError(t, applyRb(&unmatched1)) + require.NoError(t, applyRb(&unmatched2)) + require.NoError(t, applyRb(&unmatched3)) + require.NoError(t, applyRb(&unmatched4)) + require.NoError(t, applyRb(&unmatched5)) + + // Get batches of type job + actual, err := kubequery.GetRadixBatchesForJobComponent(context.Background(), client, app, env, comp, kube.RadixBatchTypeJob) + require.NoError(t, err) + expected := []radixv1.RadixBatch{matchjob1, matchjob2} + assert.ElementsMatch(t, expected, actual) + + // Get batches of type batch + actual, err = kubequery.GetRadixBatchesForJobComponent(context.Background(), client, app, env, comp, kube.RadixBatchTypeBatch) + require.NoError(t, err) + expected = []radixv1.RadixBatch{matchbatch1} + assert.ElementsMatch(t, expected, actual) +} diff --git a/api/kubequery/deployment.go b/api/kubequery/deployment.go index 801fc57a..552ceeec 100644 --- a/api/kubequery/deployment.go +++ b/api/kubequery/deployment.go @@ -3,10 +3,8 @@ package kubequery import ( "context" - radixv1 "github.com/equinor/radix-operator/pkg/apis/radix/v1" operatorUtils "github.com/equinor/radix-operator/pkg/apis/utils" "github.com/equinor/radix-operator/pkg/apis/utils/labels" - radixclient "github.com/equinor/radix-operator/pkg/client/clientset/versioned" appsv1 "k8s.io/api/apps/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes" @@ -21,9 +19,3 @@ func GetDeploymentsForEnvironment(ctx context.Context, client kubernetes.Interfa } return deployments.Items, nil } - -// GetRadixDeploymentByName returns a RadixDeployment for an application and namespace -func GetRadixDeploymentByName(ctx context.Context, radixClient radixclient.Interface, appName, envName, deploymentName string) (*radixv1.RadixDeployment, error) { - ns := operatorUtils.GetEnvironmentNamespace(appName, envName) - return radixClient.RadixV1().RadixDeployments(ns).Get(ctx, deploymentName, metav1.GetOptions{}) -} diff --git a/api/kubequery/radixdeployment.go b/api/kubequery/radixdeployment.go index 2baa1072..9886148b 100644 --- a/api/kubequery/radixdeployment.go +++ b/api/kubequery/radixdeployment.go @@ -54,3 +54,9 @@ func GetRadixDeploymentsForEnvironment(ctx context.Context, client radixclient.I } return rds.Items, nil } + +// GetRadixDeploymentByName returns a RadixDeployment for an application and namespace +func GetRadixDeploymentByName(ctx context.Context, radixClient radixclient.Interface, appName, envName, deploymentName string) (*radixv1.RadixDeployment, error) { + ns := operatorUtils.GetEnvironmentNamespace(appName, envName) + return radixClient.RadixV1().RadixDeployments(ns).Get(ctx, deploymentName, metav1.GetOptions{}) +} diff --git a/api/models/batch.go b/api/models/batch.go new file mode 100644 index 00000000..f07b04eb --- /dev/null +++ b/api/models/batch.go @@ -0,0 +1,222 @@ +package models + +import ( + "fmt" + + deploymentmodels "github.com/equinor/radix-api/api/deployments/models" + "github.com/equinor/radix-api/api/utils/predicate" + radixutils "github.com/equinor/radix-common/utils" + "github.com/equinor/radix-common/utils/pointers" + "github.com/equinor/radix-common/utils/slice" + jobschedulermodels "github.com/equinor/radix-job-scheduler/models/common" + "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" +) + +func BuildScheduledBatchSummaries(rbList []radixv1.RadixBatch, rdRefs []radixv1.RadixDeployment) []deploymentmodels.ScheduledBatchSummary { + batchSummaries := make([]deploymentmodels.ScheduledBatchSummary, 0, len(rbList)) + + for _, batch := range rbList { + var rdRef *radixv1.RadixDeployment + if rd, found := slice.FindFirst(rdRefs, predicate.IsRadixDeploymentForRadixBatch(&batch)); found { + rdRef = &rd + } + + batchSummaries = append(batchSummaries, *BuildScheduledBatchSummary(&batch, rdRef)) + } + + return batchSummaries +} + +func BuildScheduledBatchSummary(rb *radixv1.RadixBatch, rdRef *radixv1.RadixDeployment) *deploymentmodels.ScheduledBatchSummary { + batchSummary := &deploymentmodels.ScheduledBatchSummary{ + Name: rb.Name, + DeploymentName: rb.Spec.RadixDeploymentJobRef.Name, + Status: getScheduledBatchStatus(rb).String(), + JobList: buildScheduledJobSummaries(rb, rdRef), + TotalJobCount: len(rb.Spec.Jobs), + Created: radixutils.FormatTimestamp(rb.GetCreationTimestamp().Time), + Started: radixutils.FormatTime(rb.Status.Condition.ActiveTime), + Ended: radixutils.FormatTime(rb.Status.Condition.CompletionTime), + } + + return batchSummary +} + +func buildScheduledJobSummaries(rb *radixv1.RadixBatch, rdRef *radixv1.RadixDeployment) []deploymentmodels.ScheduledJobSummary { + jobSummaries := make([]deploymentmodels.ScheduledJobSummary, 0, len(rb.Spec.Jobs)) + + for i := range rb.Spec.Jobs { + jobSummaries = append(jobSummaries, *buildScheduledJobSummary(rb, i, rdRef)) + } + + return jobSummaries +} + +func buildScheduledJobSummary(rb *radixv1.RadixBatch, jobIndex int, rdRef *radixv1.RadixDeployment) *deploymentmodels.ScheduledJobSummary { + var batchName string + if rb.GetLabels()[kube.RadixBatchTypeLabel] == string(kube.RadixBatchTypeBatch) { + batchName = rb.GetName() + } + + job := rb.Spec.Jobs[jobIndex] + + summary := deploymentmodels.ScheduledJobSummary{ + Name: fmt.Sprintf("%s-%s", rb.GetName(), job.Name), + DeploymentName: rb.Spec.RadixDeploymentJobRef.Name, + BatchName: batchName, + JobId: job.JobId, + ReplicaList: getBatchJobReplicaSummaries(rb, job), + Status: jobschedulermodels.Waiting.String(), + } + + var jobComponent *radixv1.RadixDeployJobComponent + if rdRef != nil && predicate.IsRadixDeploymentForRadixBatch(rb)(*rdRef) { + jobComponent = rdRef.GetJobComponentByName(rb.Spec.RadixDeploymentJobRef.Job) + } + + if jobComponent != nil { + summary.Runtime = &deploymentmodels.Runtime{ + Architecture: operatorutils.GetArchitectureFromRuntime(jobComponent.GetRuntime()), + } + + summary.TimeLimitSeconds = jobComponent.TimeLimitSeconds + if job.TimeLimitSeconds != nil { + summary.TimeLimitSeconds = job.TimeLimitSeconds + } + + if jobComponent.BackoffLimit != nil { + summary.BackoffLimit = *jobComponent.BackoffLimit + } + if job.BackoffLimit != nil { + summary.BackoffLimit = *job.BackoffLimit + } + + if jobComponent.Node != (radixv1.RadixNode{}) { + summary.Node = (*deploymentmodels.Node)(&jobComponent.Node) + } + if job.Node != nil { + summary.Node = (*deploymentmodels.Node)(job.Node) + } + + if job.Resources != nil { + summary.Resources = deploymentmodels.ConvertRadixResourceRequirements(*job.Resources) + } else if len(jobComponent.Resources.Requests) > 0 || len(jobComponent.Resources.Limits) > 0 { + summary.Resources = deploymentmodels.ConvertRadixResourceRequirements(jobComponent.Resources) + } + } + + stopJob := job.Stop != nil && *job.Stop + + if status, found := slice.FindFirst(rb.Status.JobStatuses, predicate.IsBatchJobStatusForBatchJob(job)); found { + summary.Status = getBatchJobSummaryStatus(status, stopJob).String() + summary.Created = radixutils.FormatTime(status.CreationTime) + summary.Started = radixutils.FormatTime(status.StartTime) + summary.Ended = radixutils.FormatTime(status.EndTime) + summary.Message = status.Message + summary.FailedCount = status.Failed + summary.Restart = status.Restart + } else if stopJob { + summary.Status = jobschedulermodels.Stopping.String() + } + return &summary +} + +func getScheduledBatchStatus(batch *radixv1.RadixBatch) jobschedulermodels.ProgressStatus { + switch { + case batch.Status.Condition.Type == radixv1.BatchConditionTypeActive: + if slice.Any(batch.Status.JobStatuses, func(jobStatus radixv1.RadixBatchJobStatus) bool { + return jobStatus.Phase == radixv1.BatchJobPhaseRunning + }) { + return jobschedulermodels.Running + } + return jobschedulermodels.Active + case batch.Status.Condition.Type == radixv1.BatchConditionTypeCompleted: + if len(batch.Status.JobStatuses) > 0 && slice.All(batch.Status.JobStatuses, func(jobStatus radixv1.RadixBatchJobStatus) bool { + return jobStatus.Phase == radixv1.BatchJobPhaseFailed + }) { + return jobschedulermodels.Failed + } + return jobschedulermodels.Succeeded + } + return jobschedulermodels.Waiting +} + +func getBatchJobSummaryStatus(jobStatus radixv1.RadixBatchJobStatus, stopJob bool) (status jobschedulermodels.ProgressStatus) { + status = jobschedulermodels.Waiting + switch jobStatus.Phase { + case radixv1.BatchJobPhaseActive: + status = jobschedulermodels.Active + case radixv1.BatchJobPhaseRunning: + status = jobschedulermodels.Running + case radixv1.BatchJobPhaseSucceeded: + status = jobschedulermodels.Succeeded + case radixv1.BatchJobPhaseFailed: + status = jobschedulermodels.Failed + case radixv1.BatchJobPhaseStopped: + status = jobschedulermodels.Stopped + case radixv1.BatchJobPhaseWaiting: + status = jobschedulermodels.Waiting + } + if stopJob && (status == jobschedulermodels.Waiting || status == jobschedulermodels.Active || status == jobschedulermodels.Running) { + return jobschedulermodels.Stopping + } + if len(jobStatus.RadixBatchJobPodStatuses) > 0 && slice.All(jobStatus.RadixBatchJobPodStatuses, func(jobPodStatus radixv1.RadixBatchJobPodStatus) bool { + return jobPodStatus.Phase == radixv1.PodFailed + }) { + return jobschedulermodels.Failed + } + return status +} + +func getBatchJobReplicaSummaries(radixBatch *radixv1.RadixBatch, job radixv1.RadixBatchJob) []deploymentmodels.ReplicaSummary { + if jobStatus, ok := slice.FindFirst(radixBatch.Status.JobStatuses, func(jobStatus radixv1.RadixBatchJobStatus) bool { + return jobStatus.Name == job.Name + }); ok { + return slice.Map(jobStatus.RadixBatchJobPodStatuses, func(status radixv1.RadixBatchJobPodStatus) deploymentmodels.ReplicaSummary { + return getBatchJobReplicaSummary(status, job) + }) + } + return nil +} + +func getBatchJobReplicaSummary(status radixv1.RadixBatchJobPodStatus, job radixv1.RadixBatchJob) deploymentmodels.ReplicaSummary { + summary := deploymentmodels.ReplicaSummary{ + Name: status.Name, + Created: radixutils.FormatTimestamp(status.CreationTime.Time), + RestartCount: status.RestartCount, + Image: status.Image, + ImageId: status.ImageID, + PodIndex: status.PodIndex, + Reason: status.Reason, + StatusMessage: status.Message, + ExitCode: status.ExitCode, + Status: getBatchJobReplicaSummaryStatus(status), + } + if status.StartTime != nil { + summary.StartTime = radixutils.FormatTimestamp(status.StartTime.Time) + } + if status.EndTime != nil { + summary.EndTime = radixutils.FormatTimestamp(status.EndTime.Time) + } + if job.Resources != nil { + summary.Resources = pointers.Ptr(deploymentmodels.ConvertRadixResourceRequirements(*job.Resources)) + } + return summary +} + +func getBatchJobReplicaSummaryStatus(status radixv1.RadixBatchJobPodStatus) deploymentmodels.ReplicaStatus { + replicaStatus := deploymentmodels.ReplicaStatus{} + switch status.Phase { + case radixv1.PodFailed: + replicaStatus.Status = "Failed" + case radixv1.PodRunning: + replicaStatus.Status = "Running" + case radixv1.PodSucceeded: + replicaStatus.Status = "Succeeded" + default: + replicaStatus.Status = "Pending" + } + return replicaStatus +} diff --git a/api/utils/predicate/radix.go b/api/utils/predicate/radix.go index 9309a8af..251dc45c 100644 --- a/api/utils/predicate/radix.go +++ b/api/utils/predicate/radix.go @@ -12,4 +12,25 @@ func IsNotOrphanEnvironment(re radixv1.RadixEnvironment) bool { func IsOrphanEnvironment(re radixv1.RadixEnvironment) bool { return re.Status.Orphaned -} \ No newline at end of file +} + +func IsBatchJobStatusForBatchJob(job radixv1.RadixBatchJob) func(jobStatus radixv1.RadixBatchJobStatus) bool { + return func(jobStatus radixv1.RadixBatchJobStatus) bool { + return jobStatus.Name == job.Name + } +} + +func IsBatchJobWithName(name string) func(job radixv1.RadixBatchJob) bool { + return func(job radixv1.RadixBatchJob) bool { + return job.Name == name + } +} + +func IsRadixDeploymentForRadixBatch(batch *radixv1.RadixBatch) func(rd radixv1.RadixDeployment) bool { + return func(rd radixv1.RadixDeployment) bool { + if batch == nil { + return false + } + return batch.Spec.RadixDeploymentJobRef.Name == rd.Name && batch.Namespace == rd.Namespace + } +} diff --git a/api/utils/predicate/radix_test.go b/api/utils/predicate/radix_test.go index 695db269..de9866a8 100644 --- a/api/utils/predicate/radix_test.go +++ b/api/utils/predicate/radix_test.go @@ -5,6 +5,7 @@ import ( radixv1 "github.com/equinor/radix-operator/pkg/apis/radix/v1" "github.com/stretchr/testify/assert" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) func Test_IsActiveRadixDeployment(t *testing.T) { @@ -24,3 +25,45 @@ func Test_IsOrphanEnvironment(t *testing.T) { assert.False(t, IsOrphanEnvironment(radixv1.RadixEnvironment{})) assert.False(t, IsOrphanEnvironment(radixv1.RadixEnvironment{Status: radixv1.RadixEnvironmentStatus{Orphaned: false}})) } + +func Test_IsBatchJobStatusForBatchJob(t *testing.T) { + sut := IsBatchJobStatusForBatchJob(radixv1.RadixBatchJob{Name: "jobname"}) + assert.True(t, sut(radixv1.RadixBatchJobStatus{Name: "jobname"})) + assert.False(t, sut(radixv1.RadixBatchJobStatus{Name: "otherjobname"})) +} + +func Test_IsBatchJobWithName(t *testing.T) { + sut := IsBatchJobWithName("jobname") + assert.True(t, sut(radixv1.RadixBatchJob{Name: "jobname"})) + assert.False(t, sut(radixv1.RadixBatchJob{Name: "otherjobname"})) +} + +func Test_IsRadixDeploymentForRadixBatch(t *testing.T) { + batch := &radixv1.RadixBatch{ + ObjectMeta: v1.ObjectMeta{Namespace: "batchns"}, + Spec: radixv1.RadixBatchSpec{ + RadixDeploymentJobRef: radixv1.RadixDeploymentJobComponentSelector{ + LocalObjectReference: radixv1.LocalObjectReference{Name: "deployname"}, + }, + }, + } + sut := IsRadixDeploymentForRadixBatch(batch) + assert.True(t, sut(radixv1.RadixDeployment{ObjectMeta: v1.ObjectMeta{ + Name: "deployname", + Namespace: "batchns", + }})) + assert.False(t, sut(radixv1.RadixDeployment{ObjectMeta: v1.ObjectMeta{ + Name: "otherdeployname", + Namespace: "batchns", + }})) + assert.False(t, sut(radixv1.RadixDeployment{ObjectMeta: v1.ObjectMeta{ + Name: "deployname", + Namespace: "otherbatchns", + }})) + + sut = IsRadixDeploymentForRadixBatch(nil) + assert.False(t, sut(radixv1.RadixDeployment{ObjectMeta: v1.ObjectMeta{ + Name: "anydeployname", + Namespace: "anybatchns", + }})) +} diff --git a/swaggerui/html/swagger.json b/swaggerui/html/swagger.json index 151fbcba..5a6e81f7 100644 --- a/swaggerui/html/swagger.json +++ b/swaggerui/html/swagger.json @@ -5637,6 +5637,9 @@ "resources": { "$ref": "#/definitions/ResourceRequirements" }, + "runtime": { + "$ref": "#/definitions/Runtime" + }, "scheduledJobPayloadPath": { "description": "ScheduledJobPayloadPath defines the payload path, where payload for Job Scheduler will be mapped as a file. From radixconfig.yaml", "type": "string", @@ -5738,6 +5741,9 @@ "resources": { "$ref": "#/definitions/ResourceRequirements" }, + "runtime": { + "$ref": "#/definitions/Runtime" + }, "skipDeployment": { "description": "SkipDeployment The component should not be deployed, but used existing", "type": "boolean", @@ -7410,6 +7416,19 @@ }, "x-go-package": "github.com/equinor/radix-api/api/deployments/models" }, + "Runtime": { + "description": "Runtime requirements for the component or job", + "type": "object", + "properties": { + "architecture": { + "description": "CPU architecture", + "type": "string", + "x-go-name": "Architecture", + "example": "amd64" + } + }, + "x-go-package": "github.com/equinor/radix-api/api/deployments/models" + }, "ScheduledBatchRequest": { "description": "ScheduledBatchRequest holds information about a creating scheduled batch request", "type": "object", @@ -7594,6 +7613,9 @@ "resources": { "$ref": "#/definitions/ResourceRequirements" }, + "runtime": { + "$ref": "#/definitions/Runtime" + }, "started": { "description": "Started timestamp", "type": "string",