From a7bd9cb54e8c8909deb0501ff9e8d1e187ce716b Mon Sep 17 00:00:00 2001 From: Adam Janikowski <12255597+ajanikow@users.noreply.github.com> Date: Tue, 5 Dec 2023 14:20:18 +0100 Subject: [PATCH] [Feature] [ML] Init Job (#1514) --- chart/kube-arangodb/templates/ml/role.yaml | 5 ++ docs/api/ArangoDeployment.V1.md | 48 ++++++------ docs/api/ArangoMLExtension.V1Alpha1.md | 74 ++++++++++++++++++- pkg/apis/deployment/v1/deployment_spec.go | 23 ++---- .../deployment/v2alpha1/deployment_spec.go | 23 ++---- pkg/apis/ml/v1alpha1/extension_conditions.go | 1 + pkg/apis/ml/v1alpha1/extension_spec.go | 41 +++++++++- pkg/apis/ml/v1alpha1/extension_spec_init.go | 45 +++++++++++ .../extension_spec_metadata_service.go | 2 +- pkg/apis/ml/v1alpha1/zz_generated.deepcopy.go | 35 ++++++++- .../crds/ml-extension.schema.generated.yaml | 38 +++++++++- pkg/operatorV2/handle.go | 32 ++++++++ pkg/util/constants/constants.go | 1 + pkg/util/context.go | 11 +++ pkg/util/k8sutil/images.go | 53 +++++++++++++ pkg/util/tests/kubernetes.go | 37 ++++++++++ pkg/util/tests/kubernetes_test.go | 1 + 17 files changed, 403 insertions(+), 67 deletions(-) create mode 100644 pkg/apis/ml/v1alpha1/extension_spec_init.go diff --git a/chart/kube-arangodb/templates/ml/role.yaml b/chart/kube-arangodb/templates/ml/role.yaml index 784fab921..a5dd45353 100644 --- a/chart/kube-arangodb/templates/ml/role.yaml +++ b/chart/kube-arangodb/templates/ml/role.yaml @@ -34,5 +34,10 @@ rules: - "get" - "list" - "watch" + - apiGroups: [""] + resources: + - "secrets" + - "pods" + verbs: ["*"] {{- end }} {{- end }} \ No newline at end of file diff --git a/docs/api/ArangoDeployment.V1.md b/docs/api/ArangoDeployment.V1.md index 6339c8a65..02826a639 100644 --- a/docs/api/ArangoDeployment.V1.md +++ b/docs/api/ArangoDeployment.V1.md @@ -910,7 +910,7 @@ Links: ### .spec.allowUnsafeUpgrade -Type: `boolean` [\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.35/pkg/apis/deployment/v1/deployment_spec.go#L163) +Type: `boolean` [\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.35/pkg/apis/deployment/v1/deployment_spec.go#L152) AllowUnsafeUpgrade determines if upgrade on missing member or with not in sync shards is allowed @@ -918,7 +918,7 @@ AllowUnsafeUpgrade determines if upgrade on missing member or with not in sync s ### .spec.annotations -Type: `object` [\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.35/pkg/apis/deployment/v1/deployment_spec.go#L126) +Type: `object` [\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.35/pkg/apis/deployment/v1/deployment_spec.go#L115) Annotations specifies the annotations added to all ArangoDeployment owned resources (pods, services, PVC’s, PDB’s). @@ -926,7 +926,7 @@ Annotations specifies the annotations added to all ArangoDeployment owned resour ### .spec.annotationsIgnoreList -Type: `array` [\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.35/pkg/apis/deployment/v1/deployment_spec.go#L129) +Type: `array` [\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.35/pkg/apis/deployment/v1/deployment_spec.go#L118) AnnotationsIgnoreList list regexp or plain definitions which annotations should be ignored @@ -934,7 +934,7 @@ AnnotationsIgnoreList list regexp or plain definitions which annotations should ### .spec.annotationsMode -Type: `string` [\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.35/pkg/apis/deployment/v1/deployment_spec.go#L135) +Type: `string` [\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.35/pkg/apis/deployment/v1/deployment_spec.go#L124) AnnotationsMode defines annotations mode which should be use while overriding annotations. @@ -947,7 +947,7 @@ Possible Values: ### .spec.architecture -Type: `[]string` [\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.35/pkg/apis/deployment/v1/deployment_spec.go#L265) +Type: `[]string` [\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.35/pkg/apis/deployment/v1/deployment_spec.go#L254) Architecture defines the list of supported architectures. First element on the list is marked as default architecture. @@ -1026,7 +1026,7 @@ KillPodProbability is the chance of a pod being killed during an event ### .spec.ClusterDomain -Type: `string` [\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.35/pkg/apis/deployment/v1/deployment_spec.go#L237) +Type: `string` [\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.35/pkg/apis/deployment/v1/deployment_spec.go#L226) ClusterDomain define domain used in the kubernetes cluster. Required only of domain is not set to default (cluster.local) @@ -1037,7 +1037,7 @@ Default Value: `cluster.local` ### .spec.communicationMethod -Type: `string` [\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.35/pkg/apis/deployment/v1/deployment_spec.go#L245) +Type: `string` [\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.35/pkg/apis/deployment/v1/deployment_spec.go#L234) CommunicationMethod define communication method used in deployment @@ -2872,7 +2872,7 @@ Links: ### .spec.disableIPv6 -Type: `boolean` [\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.35/pkg/apis/deployment/v1/deployment_spec.go#L109) +Type: `boolean` [\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.35/pkg/apis/deployment/v1/deployment_spec.go#L98) DisableIPv6 setting prevents the use of IPv6 addresses by ArangoDB servers. This setting cannot be changed after the deployment has been created. @@ -2883,7 +2883,7 @@ Default Value: `false` ### .spec.downtimeAllowed -Type: `boolean` [\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.35/pkg/apis/deployment/v1/deployment_spec.go#L104) +Type: `boolean` [\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.35/pkg/apis/deployment/v1/deployment_spec.go#L93) DowntimeAllowed setting is used to allow automatic reconciliation actions that yield some downtime of the ArangoDB deployment. When this setting is set to false, no automatic action that may result in downtime is allowed. @@ -2899,7 +2899,7 @@ Default Value: `false` ### .spec.environment -Type: `string` [\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.35/pkg/apis/deployment/v1/deployment_spec.go#L65) +Type: `string` [\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.35/pkg/apis/deployment/v1/deployment_spec.go#L54) Environment setting specifies the type of environment in which the deployment is created. @@ -3194,7 +3194,7 @@ Links: ### .spec.image -Type: `string` [\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.35/pkg/apis/deployment/v1/deployment_spec.go#L78) +Type: `string` [\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.35/pkg/apis/deployment/v1/deployment_spec.go#L67) Image specifies the docker image to use for all ArangoDB servers. In a development environment this setting defaults to arangodb/arangodb:latest. @@ -3205,7 +3205,7 @@ It is highly recommend to use explicit version (not latest) for production envir ### .spec.imageDiscoveryMode -Type: `string` [\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.35/pkg/apis/deployment/v1/deployment_spec.go#L94) +Type: `string` [\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.35/pkg/apis/deployment/v1/deployment_spec.go#L83) ImageDiscoveryMode specifies the image discovery mode. @@ -3217,7 +3217,7 @@ Possible Values: ### .spec.imagePullPolicy -Type: `core.PullPolicy` [\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.35/pkg/apis/deployment/v1/deployment_spec.go#L86) +Type: `core.PullPolicy` [\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.35/pkg/apis/deployment/v1/deployment_spec.go#L75) ImagePullPolicy specifies the pull policy for the docker image to use for all ArangoDB servers. @@ -3233,7 +3233,7 @@ Possible Values: ### .spec.imagePullSecrets -Type: `array` [\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.35/pkg/apis/deployment/v1/deployment_spec.go#L89) +Type: `array` [\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.35/pkg/apis/deployment/v1/deployment_spec.go#L78) ImagePullSecrets specifies the list of image pull secrets for the docker image to use for all ArangoDB servers. @@ -3241,7 +3241,7 @@ ImagePullSecrets specifies the list of image pull secrets for the docker image t ### .spec.labels -Type: `object` [\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.35/pkg/apis/deployment/v1/deployment_spec.go#L138) +Type: `object` [\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.35/pkg/apis/deployment/v1/deployment_spec.go#L127) Labels specifies the labels added to Pods in this group. @@ -3249,7 +3249,7 @@ Labels specifies the labels added to Pods in this group. ### .spec.labelsIgnoreList -Type: `array` [\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.35/pkg/apis/deployment/v1/deployment_spec.go#L141) +Type: `array` [\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.35/pkg/apis/deployment/v1/deployment_spec.go#L130) LabelsIgnoreList list regexp or plain definitions which labels should be ignored @@ -3257,7 +3257,7 @@ LabelsIgnoreList list regexp or plain definitions which labels should be ignored ### .spec.labelsMode -Type: `string` [\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.35/pkg/apis/deployment/v1/deployment_spec.go#L147) +Type: `string` [\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.35/pkg/apis/deployment/v1/deployment_spec.go#L136) LabelsMode Define labels mode which should be use while overriding labels @@ -3291,7 +3291,7 @@ Links: ### .spec.memberPropagationMode -Type: `string` [\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.35/pkg/apis/deployment/v1/deployment_spec.go#L220) +Type: `string` [\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.35/pkg/apis/deployment/v1/deployment_spec.go#L209) MemberPropagationMode defines how changes to pod spec should be propogated. Changes to a pod’s configuration require a restart of that pod in almost all cases. @@ -3386,7 +3386,7 @@ Default Value: `true` ### .spec.mode -Type: `string` [\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.35/pkg/apis/deployment/v1/deployment_spec.go#L60) +Type: `string` [\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.35/pkg/apis/deployment/v1/deployment_spec.go#L49) Mode specifies the type of ArangoDB deployment to create. @@ -3401,7 +3401,7 @@ This field is **immutable**: Change of the ArangoDeployment Mode is not possible ### .spec.networkAttachedVolumes -Type: `boolean` [\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.35/pkg/apis/deployment/v1/deployment_spec.go#L123) +Type: `boolean` [\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.35/pkg/apis/deployment/v1/deployment_spec.go#L112) NetworkAttachedVolumes If set to `true`, a ResignLeadership operation will be triggered when a DB-Server pod is evicted (rather than a CleanOutServer operation). @@ -3448,7 +3448,7 @@ Type: `boolean` [\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1. ### .spec.restoreEncryptionSecret -Type: `string` [\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.35/pkg/apis/deployment/v1/deployment_spec.go#L160) +Type: `string` [\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.35/pkg/apis/deployment/v1/deployment_spec.go#L149) RestoreEncryptionSecret specifies optional name of secret which contains encryption key used for restore @@ -3456,7 +3456,7 @@ RestoreEncryptionSecret specifies optional name of secret which contains encrypt ### .spec.restoreFrom -Type: `string` [\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.35/pkg/apis/deployment/v1/deployment_spec.go#L157) +Type: `string` [\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.35/pkg/apis/deployment/v1/deployment_spec.go#L146) RestoreFrom setting specifies a `ArangoBackup` resource name the cluster should be restored from. After a restore or failure to do so, the status of the deployment contains information about the restore operation in the restore key. @@ -4390,7 +4390,7 @@ Links: ### .spec.storageEngine -Type: `string` [\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.35/pkg/apis/deployment/v1/deployment_spec.go#L72) +Type: `string` [\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.35/pkg/apis/deployment/v1/deployment_spec.go#L61) StorageEngine specifies the type of storage engine used for all servers in the cluster. @@ -6437,7 +6437,7 @@ MaintenanceGracePeriod action timeout ### .spec.timezone -Type: `string` [\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.35/pkg/apis/deployment/v1/deployment_spec.go#L269) +Type: `string` [\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.35/pkg/apis/deployment/v1/deployment_spec.go#L258) Timezone if specified, will set a timezone for deployment. Must be in format accepted by "tzdata", e.g. `America/New_York` or `Europe/London` diff --git a/docs/api/ArangoMLExtension.V1Alpha1.md b/docs/api/ArangoMLExtension.V1Alpha1.md index 58f8160af..adf256690 100644 --- a/docs/api/ArangoMLExtension.V1Alpha1.md +++ b/docs/api/ArangoMLExtension.V1Alpha1.md @@ -2,6 +2,40 @@ ## Spec +### .spec.image + +Type: `string` [\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.35/pkg/apis/shared/v1/image.go#L31) + +Image define image details + +*** + +### .spec.init.image + +Type: `string` [\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.35/pkg/apis/shared/v1/image.go#L31) + +Image define image details + +*** + +### .spec.init.pullPolicy + +Type: `string` [\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.35/pkg/apis/shared/v1/image.go#L35) + +PullPolicy define Image pull policy + +Default Value: `IfNotPresent` + +*** + +### .spec.init.pullSecrets + +Type: `array` [\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.35/pkg/apis/shared/v1/image.go#L38) + +PullSecrets define Secrets used to pull Image from registry + +*** + ### .spec.metadataService.local.arangoMLFeatureStore Type: `string` [\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.35/pkg/apis/ml/v1alpha1/extension_spec_metadata_service.go#L65) @@ -22,11 +56,45 @@ Default Value: `arangopipe` *** -### .spec.storage +### .spec.pullPolicy -Type: `string` [\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.35/pkg/apis/ml/v1alpha1/extension_spec.go#L31) +Type: `string` [\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.35/pkg/apis/shared/v1/image.go#L35) -Storage specify the ArangoMLStorage used within Extension +PullPolicy define Image pull policy + +Default Value: `IfNotPresent` + +*** + +### .spec.pullSecrets + +Type: `array` [\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.35/pkg/apis/shared/v1/image.go#L38) + +PullSecrets define Secrets used to pull Image from registry + +*** + +### .spec.storage.name + +Type: `string` [\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.35/pkg/apis/shared/v1/object.go#L32) + +Name of the object + +*** + +### .spec.storage.namespace + +Type: `string` [\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.35/pkg/apis/shared/v1/object.go#L35) + +Namespace of the object. Should default to the namespace of the parent object + +*** + +### .spec.storage.uid + +Type: `string` [\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.35/pkg/apis/shared/v1/object.go#L38) + +UID keeps the information about object UID ## Status diff --git a/pkg/apis/deployment/v1/deployment_spec.go b/pkg/apis/deployment/v1/deployment_spec.go index c770f17b4..6c6ad87e0 100644 --- a/pkg/apis/deployment/v1/deployment_spec.go +++ b/pkg/apis/deployment/v1/deployment_spec.go @@ -38,17 +38,6 @@ var ( DefaultImage = "arangodb/arangodb:latest" ) -// validatePullPolicy the image pull policy. -// Return errors when validation fails, nil on success. -func validatePullPolicy(v core.PullPolicy) error { - switch v { - case "", core.PullAlways, core.PullNever, core.PullIfNotPresent: - return nil - default: - return errors.WithStack(errors.Wrapf(ValidationError, "Unknown pull policy: '%s'", string(v))) - } -} - // DeploymentSpec contains the spec part of a ArangoDeployment resource. type DeploymentSpec struct { @@ -509,11 +498,13 @@ func (s *DeploymentSpec) Validate() error { if err := s.GetStorageEngine().Validate(); err != nil { return errors.WithStack(errors.Wrap(err, "spec.storageEngine")) } - if err := validatePullPolicy(s.GetImagePullPolicy()); err != nil { - return errors.WithStack(errors.Wrap(err, "spec.imagePullPolicy")) - } - if s.GetImage() == "" { - return errors.WithStack(errors.Wrapf(ValidationError, "spec.image must be set")) + if s != nil { + if err := shared.ValidateOptional(s.ImagePullPolicy, shared.ValidatePullPolicy); err != nil { + return errors.WithStack(errors.Wrap(err, "spec.imagePullPolicy")) + } + if err := shared.ValidateOptional(s.Image, shared.ValidateImage); err != nil { + return errors.WithStack(errors.Wrapf(err, "spec.image must be set")) + } } if err := s.ExternalAccess.Validate(); err != nil { return errors.WithStack(errors.Wrap(err, "spec.externalAccess")) diff --git a/pkg/apis/deployment/v2alpha1/deployment_spec.go b/pkg/apis/deployment/v2alpha1/deployment_spec.go index 480a932f7..4863e524a 100644 --- a/pkg/apis/deployment/v2alpha1/deployment_spec.go +++ b/pkg/apis/deployment/v2alpha1/deployment_spec.go @@ -38,17 +38,6 @@ var ( DefaultImage = "arangodb/arangodb:latest" ) -// validatePullPolicy the image pull policy. -// Return errors when validation fails, nil on success. -func validatePullPolicy(v core.PullPolicy) error { - switch v { - case "", core.PullAlways, core.PullNever, core.PullIfNotPresent: - return nil - default: - return errors.WithStack(errors.Wrapf(ValidationError, "Unknown pull policy: '%s'", string(v))) - } -} - // DeploymentSpec contains the spec part of a ArangoDeployment resource. type DeploymentSpec struct { @@ -509,11 +498,13 @@ func (s *DeploymentSpec) Validate() error { if err := s.GetStorageEngine().Validate(); err != nil { return errors.WithStack(errors.Wrap(err, "spec.storageEngine")) } - if err := validatePullPolicy(s.GetImagePullPolicy()); err != nil { - return errors.WithStack(errors.Wrap(err, "spec.imagePullPolicy")) - } - if s.GetImage() == "" { - return errors.WithStack(errors.Wrapf(ValidationError, "spec.image must be set")) + if s != nil { + if err := shared.ValidateOptional(s.ImagePullPolicy, shared.ValidatePullPolicy); err != nil { + return errors.WithStack(errors.Wrap(err, "spec.imagePullPolicy")) + } + if err := shared.ValidateOptional(s.Image, shared.ValidateImage); err != nil { + return errors.WithStack(errors.Wrapf(err, "spec.image must be set")) + } } if err := s.ExternalAccess.Validate(); err != nil { return errors.WithStack(errors.Wrap(err, "spec.externalAccess")) diff --git a/pkg/apis/ml/v1alpha1/extension_conditions.go b/pkg/apis/ml/v1alpha1/extension_conditions.go index 2920fbba6..a5af8f0ff 100644 --- a/pkg/apis/ml/v1alpha1/extension_conditions.go +++ b/pkg/apis/ml/v1alpha1/extension_conditions.go @@ -25,6 +25,7 @@ import api "github.com/arangodb/kube-arangodb/pkg/apis/deployment/v1" const ( ExtensionStorageFoundCondition api.ConditionType = "StorageFound" ExtensionDeploymentFoundCondition api.ConditionType = "DeploymentFound" + ExtensionBootstrapCompletedCondition api.ConditionType = "BootstrapCompleted" ExtensionMetadataServiceValidCondition api.ConditionType = "MetadataServiceValid" LicenseValidCondition api.ConditionType = "LicenseValid" ) diff --git a/pkg/apis/ml/v1alpha1/extension_spec.go b/pkg/apis/ml/v1alpha1/extension_spec.go index 35386d8c1..1efabae52 100644 --- a/pkg/apis/ml/v1alpha1/extension_spec.go +++ b/pkg/apis/ml/v1alpha1/extension_spec.go @@ -20,7 +20,10 @@ package v1alpha1 -import "github.com/arangodb/kube-arangodb/pkg/apis/shared" +import ( + "github.com/arangodb/kube-arangodb/pkg/apis/shared" + sharedApi "github.com/arangodb/kube-arangodb/pkg/apis/shared/v1" +) type ArangoMLExtensionSpec struct { // MetadataService keeps the MetadataService configuration @@ -28,7 +31,13 @@ type ArangoMLExtensionSpec struct { MetadataService *ArangoMLExtensionSpecMetadataService `json:"metadataService,omitempty"` // Storage specify the ArangoMLStorage used within Extension - Storage *string `json:"storage,omitempty"` + Storage *sharedApi.Object `json:"storage,omitempty"` + + // Image define default image used for the extension + *sharedApi.Image `json:",inline"` + + // ArangoMLExtensionSpecInit define Init job specification + Init *ArangoMLExtensionSpecInit `json:"init,omitempty"` } func (a *ArangoMLExtensionSpec) GetMetadataService() *ArangoMLExtensionSpecMetadataService { @@ -39,7 +48,23 @@ func (a *ArangoMLExtensionSpec) GetMetadataService() *ArangoMLExtensionSpecMetad return a.MetadataService } -func (a *ArangoMLExtensionSpec) GetStorage() *string { +func (a *ArangoMLExtensionSpec) GetImage() *sharedApi.Image { + if a == nil || a.Image == nil { + return nil + } + + return a.Image +} + +func (a *ArangoMLExtensionSpec) GetInit() *ArangoMLExtensionSpecInit { + if a == nil || a.Init == nil { + return nil + } + + return a.Init +} + +func (a *ArangoMLExtensionSpec) GetStorage() *sharedApi.Object { if a == nil || a.Storage == nil { return nil } @@ -48,8 +73,16 @@ func (a *ArangoMLExtensionSpec) GetStorage() *string { } func (a *ArangoMLExtensionSpec) Validate() error { + if a == nil { + a = &ArangoMLExtensionSpec{} + } + return shared.WithErrors(shared.PrefixResourceErrors("spec", shared.PrefixResourceErrors("metadataService", a.GetMetadataService().Validate()), - shared.PrefixResourceErrors("storage", shared.ValidateRequired(a.GetStorage(), shared.ValidateResourceName)), + shared.PrefixResourceErrors("storage", shared.ValidateRequired(a.GetStorage(), func(obj sharedApi.Object) error { return obj.Validate() })), + a.GetImage().Validate(), + shared.PrefixResourceErrors("init", a.GetInit().Validate()), + shared.PrefixResourceErrors("storage", shared.ValidateRequired(a.Storage, func(obj sharedApi.Object) error { return obj.Validate() })), + shared.ValidateAnyNotNil(".image or .init.image needs to be specified", a.GetImage(), a.GetInit().GetImage()), )) } diff --git a/pkg/apis/ml/v1alpha1/extension_spec_init.go b/pkg/apis/ml/v1alpha1/extension_spec_init.go new file mode 100644 index 000000000..6dadeaa69 --- /dev/null +++ b/pkg/apis/ml/v1alpha1/extension_spec_init.go @@ -0,0 +1,45 @@ +// +// DISCLAIMER +// +// Copyright 2023 ArangoDB GmbH, Cologne, Germany +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// Copyright holder is ArangoDB GmbH, Cologne, Germany +// + +package v1alpha1 + +import ( + "github.com/arangodb/kube-arangodb/pkg/apis/shared" + sharedApi "github.com/arangodb/kube-arangodb/pkg/apis/shared/v1" +) + +type ArangoMLExtensionSpecInit struct { + // Image define default image used for the init job + *sharedApi.Image `json:",inline"` +} + +func (a *ArangoMLExtensionSpecInit) GetImage() *sharedApi.Image { + if a == nil || a.Image == nil { + return nil + } + + return a.Image +} + +func (a *ArangoMLExtensionSpecInit) Validate() error { + return shared.WithErrors( + a.GetImage().Validate(), + ) +} diff --git a/pkg/apis/ml/v1alpha1/extension_spec_metadata_service.go b/pkg/apis/ml/v1alpha1/extension_spec_metadata_service.go index af90ab41b..d9137c315 100644 --- a/pkg/apis/ml/v1alpha1/extension_spec_metadata_service.go +++ b/pkg/apis/ml/v1alpha1/extension_spec_metadata_service.go @@ -28,7 +28,7 @@ import ( const ( ArangoMLExtensionSpecMetadataServiceLocalDefaultArangoPipeDatabase = "arangopipe" - ArangoMLExtensionSpecMetadataServiceLocalDefaultArangoMLFeatureStoreDatabase = "arangomlfeaturestore" + ArangoMLExtensionSpecMetadataServiceLocalDefaultArangoMLFeatureStoreDatabase = "arangoml_feature_store" ) type ArangoMLExtensionSpecMetadataService struct { diff --git a/pkg/apis/ml/v1alpha1/zz_generated.deepcopy.go b/pkg/apis/ml/v1alpha1/zz_generated.deepcopy.go index 57101b0d9..46f1bdcb2 100644 --- a/pkg/apis/ml/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/apis/ml/v1alpha1/zz_generated.deepcopy.go @@ -309,8 +309,18 @@ func (in *ArangoMLExtensionSpec) DeepCopyInto(out *ArangoMLExtensionSpec) { } if in.Storage != nil { in, out := &in.Storage, &out.Storage - *out = new(string) - **out = **in + *out = new(sharedv1.Object) + (*in).DeepCopyInto(*out) + } + if in.Image != nil { + in, out := &in.Image, &out.Image + *out = new(sharedv1.Image) + (*in).DeepCopyInto(*out) + } + if in.Init != nil { + in, out := &in.Init, &out.Init + *out = new(ArangoMLExtensionSpecInit) + (*in).DeepCopyInto(*out) } return } @@ -325,6 +335,27 @@ func (in *ArangoMLExtensionSpec) DeepCopy() *ArangoMLExtensionSpec { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ArangoMLExtensionSpecInit) DeepCopyInto(out *ArangoMLExtensionSpecInit) { + *out = *in + if in.Image != nil { + in, out := &in.Image, &out.Image + *out = new(sharedv1.Image) + (*in).DeepCopyInto(*out) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ArangoMLExtensionSpecInit. +func (in *ArangoMLExtensionSpecInit) DeepCopy() *ArangoMLExtensionSpecInit { + if in == nil { + return nil + } + out := new(ArangoMLExtensionSpecInit) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ArangoMLExtensionSpecMetadataService) DeepCopyInto(out *ArangoMLExtensionSpecMetadataService) { *out = *in diff --git a/pkg/crd/crds/ml-extension.schema.generated.yaml b/pkg/crd/crds/ml-extension.schema.generated.yaml index 10edeb823..5090bd3e3 100644 --- a/pkg/crd/crds/ml-extension.schema.generated.yaml +++ b/pkg/crd/crds/ml-extension.schema.generated.yaml @@ -3,6 +3,24 @@ v1alpha1: properties: spec: properties: + image: + description: Image define image details + type: string + init: + description: ArangoMLExtensionSpecInit define Init job specification + properties: + image: + description: Image define image details + type: string + pullPolicy: + description: PullPolicy define Image pull policy + type: string + pullSecrets: + description: PullSecrets define Secrets used to pull Image from registry + items: + type: string + type: array + type: object metadataService: description: MetadataService keeps the MetadataService configuration properties: @@ -17,9 +35,27 @@ v1alpha1: type: string type: object type: object + pullPolicy: + description: PullPolicy define Image pull policy + type: string + pullSecrets: + description: PullSecrets define Secrets used to pull Image from registry + items: + type: string + type: array storage: description: Storage specify the ArangoMLStorage used within Extension - type: string + properties: + name: + description: Name of the object + type: string + namespace: + description: Namespace of the object. Should default to the namespace of the parent object + type: string + uid: + description: UID keeps the information about object UID + type: string + type: object type: object type: object x-kubernetes-preserve-unknown-fields: true diff --git a/pkg/operatorV2/handle.go b/pkg/operatorV2/handle.go index ecff725da..17b27f214 100644 --- a/pkg/operatorV2/handle.go +++ b/pkg/operatorV2/handle.go @@ -59,6 +59,8 @@ type HandleP4Func[P1, P2, P3, P4 interface{}] func(ctx context.Context, p1 P1, p type HandleP5Func[P1, P2, P3, P4, P5 interface{}] func(ctx context.Context, p1 P1, p2 P2, p3 P3, p4 P4, p5 P5) (bool, error) +type HandleP6Func[P1, P2, P3, P4, P5, P6 interface{}] func(ctx context.Context, p1 P1, p2 P2, p3 P3, p4 P4, p5 P5, p6 P6) (bool, error) + type HandleP9Func[P1, P2, P3, P4, P5, P6, P7, P8, P9 interface{}] func(ctx context.Context, p1 P1, p2 P2, p3 P3, p4 P4, p5 P5, p6 P6, p7 P7, p8 P8, p9 P9) (bool, error) func HandleP0(ctx context.Context, handler ...HandleP0Func) (bool, error) { @@ -217,6 +219,36 @@ func HandleP5WithCondition[P1, P2, P3, P4, P5 interface{}](ctx context.Context, return WithCondition(conditions, condition, changed, err) } +func HandleP6[P1, P2, P3, P4, P5, P6 interface{}](ctx context.Context, p1 P1, p2 P2, p3 P3, p4 P4, p5 P5, p6 P6, handler ...HandleP6Func[P1, P2, P3, P4, P5, P6]) (bool, error) { + isChanged := false + for _, h := range handler { + changed, err := h(ctx, p1, p2, p3, p4, p5, p6) + if changed { + isChanged = true + } + + if err != nil { + return isChanged, err + } + } + + return isChanged, nil +} + +func HandleP6WithStop[P1, P2, P3, P4, P5, P6 interface{}](ctx context.Context, p1 P1, p2 P2, p3 P3, p4 P4, p5 P5, p6 P6, handler ...HandleP6Func[P1, P2, P3, P4, P5, P6]) (bool, error) { + changed, err := HandleP6[P1, P2, P3, P4, P5, P6](ctx, p1, p2, p3, p4, p5, p6, handler...) + if IsStop(err) { + return changed, nil + } + + return changed, err +} + +func HandleP6WithCondition[P1, P2, P3, P4, P5, P6 interface{}](ctx context.Context, conditions *api.ConditionList, condition api.ConditionType, p1 P1, p2 P2, p3 P3, p4 P4, p5 P5, p6 P6, handler ...HandleP6Func[P1, P2, P3, P4, P5, P6]) (bool, error) { + changed, err := HandleP6[P1, P2, P3, P4, P5, P6](ctx, p1, p2, p3, p4, p5, p6, handler...) + return WithCondition(conditions, condition, changed, err) +} + func HandleP9[P1, P2, P3, P4, P5, P6, P7, P8, P9 interface{}](ctx context.Context, p1 P1, p2 P2, p3 P3, p4 P4, p5 P5, p6 P6, p7 P7, p8 P8, p9 P9, handler ...HandleP9Func[P1, P2, P3, P4, P5, P6, P7, P8, P9]) (bool, error) { isChanged := false for _, h := range handler { diff --git a/pkg/util/constants/constants.go b/pkg/util/constants/constants.go index 79ec26438..f53d5a0ae 100644 --- a/pkg/util/constants/constants.go +++ b/pkg/util/constants/constants.go @@ -25,6 +25,7 @@ const ( EnvOperatorNodeNameArango = "NODE_NAME" EnvOperatorPodName = "MY_POD_NAME" EnvOperatorPodNamespace = "MY_POD_NAMESPACE" + EnvOperatorCoreContainer = "MY_POD_CORE_CONTAINER" EnvOperatorPodIP = "MY_POD_IP" EnvArangoJobSAName = "ARANGOJOB_SA_NAME" diff --git a/pkg/util/context.go b/pkg/util/context.go index 807de1a93..36ee4f867 100644 --- a/pkg/util/context.go +++ b/pkg/util/context.go @@ -27,6 +27,17 @@ import ( "github.com/arangodb/kube-arangodb/pkg/util/globals" ) +func WithKubernetesContextTimeoutP1A2[P1, A1, A2 interface{}](ctx context.Context, f func(context.Context, A1, A2) P1, a1 A1, a2 A2) P1 { + return WithContextTimeoutP1A2(ctx, globals.GetGlobals().Timeouts().Kubernetes().Get(), f, a1, a2) +} + +func WithContextTimeoutP1A2[P1, A1, A2 interface{}](ctx context.Context, timeout time.Duration, f func(context.Context, A1, A2) P1, a1 A1, a2 A2) P1 { + nCtx, c := context.WithTimeout(ctx, timeout) + defer c() + + return f(nCtx, a1, a2) +} + func WithKubernetesContextTimeoutP2A2[P1, P2, A1, A2 interface{}](ctx context.Context, f func(context.Context, A1, A2) (P1, P2), a1 A1, a2 A2) (P1, P2) { return WithContextTimeoutP2A2(ctx, globals.GetGlobals().Timeouts().Kubernetes().Get(), f, a1, a2) } diff --git a/pkg/util/k8sutil/images.go b/pkg/util/k8sutil/images.go index db7f9468b..957aa629a 100644 --- a/pkg/util/k8sutil/images.go +++ b/pkg/util/k8sutil/images.go @@ -26,6 +26,7 @@ import ( core "k8s.io/api/core/v1" "github.com/arangodb/kube-arangodb/pkg/apis/shared" + sharedApi "github.com/arangodb/kube-arangodb/pkg/apis/shared/v1" "github.com/arangodb/kube-arangodb/pkg/util/errors" ) @@ -61,3 +62,55 @@ func GetArangoDBImageIDFromPod(pod *core.Pod) (string, error) { // If Server container is not found use first container return ConvertImageID2Image(pod.Status.ContainerStatuses[0].ImageID), nil } + +// GetImageDetails Returns latest defined Image details +func GetImageDetails(images ...*sharedApi.Image) *sharedApi.Image { + var out *sharedApi.Image + + for _, image := range images { + if image != nil { + out = image + } + } + + return out +} + +// InjectImageDetails injects image details into the Pod definition +func InjectImageDetails(image *sharedApi.Image, pod *core.PodTemplateSpec, containers ...*core.Container) error { + if image == nil { + return errors.Newf("Image not found") + } else if err := image.Validate(); err != nil { + return errors.Wrapf(err, "Unable to validate image") + } + + for _, secret := range image.PullSecrets { + if HasImagePullSecret(pod.Spec.ImagePullSecrets, secret) { + continue + } + + pod.Spec.ImagePullSecrets = append(pod.Spec.ImagePullSecrets, core.LocalObjectReference{ + Name: secret, + }) + } + + for _, container := range containers { + container.Image = *image.Image + + if ps := image.PullPolicy; ps != nil { + container.ImagePullPolicy = *ps + } + } + + return nil +} + +func HasImagePullSecret(secrets []core.LocalObjectReference, secret string) bool { + for _, sec := range secrets { + if sec.Name == secret { + return true + } + } + + return false +} diff --git a/pkg/util/tests/kubernetes.go b/pkg/util/tests/kubernetes.go index d2694120f..691cf011d 100644 --- a/pkg/util/tests/kubernetes.go +++ b/pkg/util/tests/kubernetes.go @@ -86,6 +86,12 @@ func CreateObjects(t *testing.T, k8s kubernetes.Interface, arango arangoClientSe vl := *v _, err := k8s.CoreV1().Secrets(vl.GetNamespace()).Create(context.Background(), vl, meta.CreateOptions{}) require.NoError(t, err) + case **core.Pod: + require.NotNil(t, v) + + vl := *v + _, err := k8s.CoreV1().Pods(vl.GetNamespace()).Create(context.Background(), vl, meta.CreateOptions{}) + require.NoError(t, err) case **api.ArangoDeployment: require.NotNil(t, v) @@ -135,6 +141,12 @@ func UpdateObjects(t *testing.T, k8s kubernetes.Interface, arango arangoClientSe vl := *v _, err := k8s.BatchV1().Jobs(vl.GetNamespace()).Update(context.Background(), vl, meta.UpdateOptions{}) require.NoError(t, err) + case **core.Pod: + require.NotNil(t, v) + + vl := *v + _, err := k8s.CoreV1().Pods(vl.GetNamespace()).Update(context.Background(), vl, meta.UpdateOptions{}) + require.NoError(t, err) case **core.Secret: require.NotNil(t, v) @@ -199,6 +211,21 @@ func RefreshObjects(t *testing.T, k8s kubernetes.Interface, arango arangoClientS } else { *v = vn } + case **core.Pod: + require.NotNil(t, v) + + vl := *v + + vn, err := k8s.CoreV1().Pods(vl.GetNamespace()).Get(context.Background(), vl.GetName(), meta.GetOptions{}) + if err != nil { + if kerrors.IsNotFound(err) { + *v = nil + } else { + require.NoError(t, err) + } + } else { + *v = vn + } case **core.Secret: require.NotNil(t, v) @@ -305,6 +332,12 @@ func SetMetaBasedOnType(t *testing.T, object meta.Object) { v.SetSelfLink(fmt.Sprintf("/api/batch/v1/jobs/%s/%s", object.GetNamespace(), object.GetName())) + case *core.Pod: + v.Kind = "Pod" + v.APIVersion = "v1" + v.SetSelfLink(fmt.Sprintf("/api/v1/Pods/%s/%s", + object.GetNamespace(), + object.GetName())) case *core.Secret: v.Kind = "Secret" v.APIVersion = "v1" @@ -391,6 +424,10 @@ func NewItem(t *testing.T, o operation.Operation, object meta.Object) operation. item.Group = "batch" item.Version = "v1" item.Kind = "Job" + case *core.Pod: + item.Group = "" + item.Version = "v1" + item.Kind = "Pod" case *core.Secret: item.Group = "" item.Version = "v1" diff --git a/pkg/util/tests/kubernetes_test.go b/pkg/util/tests/kubernetes_test.go index 6aef391a8..3123660a1 100644 --- a/pkg/util/tests/kubernetes_test.go +++ b/pkg/util/tests/kubernetes_test.go @@ -60,6 +60,7 @@ func NewMetaObjectRun[T meta.Object](t *testing.T) { func Test_NewMetaObject(t *testing.T) { NewMetaObjectRun[*batch.Job](t) + NewMetaObjectRun[*core.Pod](t) NewMetaObjectRun[*core.Secret](t) NewMetaObjectRun[*api.ArangoDeployment](t) NewMetaObjectRun[*api.ArangoClusterSynchronization](t)