From fe5419c404065792a7cb56b8aaee1aa5b80e4b94 Mon Sep 17 00:00:00 2001
From: Adam Janikowski <12255597+ajanikow@users.noreply.github.com>
Date: Fri, 22 Mar 2024 22:25:33 +0100
Subject: [PATCH] [Feature] ArangoProfile Selectors (#1627)
---
CHANGELOG.md | 1 +
docs/api/ArangoMLExtension.V1Alpha1.md | 8 +
docs/api/ArangoProfile.V1Alpha1.md | 16 ++
.../v1alpha1/container/resources/resources.go | 5 +-
.../container/resources/volume_mounts.go | 2 +-
pkg/apis/scheduler/v1alpha1/pod/definition.go | 14 +
.../scheduler/v1alpha1/pod/resources/image.go | 87 ++++++
.../v1alpha1/pod/resources/image_test.go | 88 +++++++
.../v1alpha1/pod/resources/service_account.go | 4 +-
.../v1alpha1/pod/resources/volumes.go | 2 +-
.../pod/resources/zz_generated.deepcopy.go | 41 +++
.../v1alpha1/pod/zz_generated.deepcopy.go | 5 +
.../scheduler/v1alpha1/profile_selectors.go | 49 ++++
pkg/apis/scheduler/v1alpha1/profile_spec.go | 18 ++
.../scheduler/v1alpha1/profile_templates.go | 6 +-
.../v1alpha1/zz_generated.deepcopy.go | 27 ++
.../crds/ml-extension.schema.generated.yaml | 4 +
.../scheduler-profile.schema.generated.yaml | 29 ++
pkg/util/k8sutil/resources/selectors.go | 76 ++++++
pkg/util/k8sutil/resources/selectors_test.go | 249 ++++++++++++++++++
pkg/util/tests/kubernetes.go | 45 ++++
pkg/util/tests/kubernetes_test.go | 4 +-
pkg/util/tests/resources.go | 36 +++
23 files changed, 808 insertions(+), 8 deletions(-)
create mode 100644 pkg/apis/scheduler/v1alpha1/pod/resources/image.go
create mode 100644 pkg/apis/scheduler/v1alpha1/pod/resources/image_test.go
create mode 100644 pkg/apis/scheduler/v1alpha1/profile_selectors.go
create mode 100644 pkg/util/k8sutil/resources/selectors.go
create mode 100644 pkg/util/k8sutil/resources/selectors_test.go
create mode 100644 pkg/util/tests/resources.go
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0666ed2d3..15cbcddce 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -9,6 +9,7 @@
- (Feature) Discover Namespace in DebugPackage from K8S
- (Feature) Expose Force CRD Install option
- (Maintenance) Move Container utils functions
+- (Feature) ArangoProfile Selectors
## [1.2.39](https://github.com/arangodb/kube-arangodb/tree/1.2.39) (2024-03-11)
- (Feature) Extract Scheduler API
diff --git a/docs/api/ArangoMLExtension.V1Alpha1.md b/docs/api/ArangoMLExtension.V1Alpha1.md
index 7e122cd82..6dda8b824 100644
--- a/docs/api/ArangoMLExtension.V1Alpha1.md
+++ b/docs/api/ArangoMLExtension.V1Alpha1.md
@@ -71,6 +71,14 @@ Default Value: `false`
***
+### .spec.deployment.imagePullSecrets
+
+Type: `array` [\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.39/pkg/apis/scheduler/v1alpha1/pod/resources/image.go#L36)
+
+ImagePullSecrets define Secrets used to pull Image from registry
+
+***
+
### .spec.deployment.labels
Type: `object` [\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.39/pkg/apis/scheduler/v1alpha1/pod/resources/metadata.go#L39)
diff --git a/docs/api/ArangoProfile.V1Alpha1.md b/docs/api/ArangoProfile.V1Alpha1.md
index 40746264c..3fa5b2541 100644
--- a/docs/api/ArangoProfile.V1Alpha1.md
+++ b/docs/api/ArangoProfile.V1Alpha1.md
@@ -8,6 +8,14 @@ title: ArangoProfile V1Alpha1
## Spec
+### .spec.selectors.label
+
+Type: `meta.LabelSelector` [\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.39/pkg/apis/scheduler/v1alpha1/profile_selectors.go#L32)
+
+Label keeps information about label selector
+
+***
+
### .spec.template.container.all.env
Type: `core.EnvVar` [\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.39/pkg/apis/scheduler/v1alpha1/container/resources/environments.go#L36)
@@ -281,6 +289,14 @@ Default Value: `false`
***
+### .spec.template.pod.imagePullSecrets
+
+Type: `array` [\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.39/pkg/apis/scheduler/v1alpha1/pod/resources/image.go#L36)
+
+ImagePullSecrets define Secrets used to pull Image from registry
+
+***
+
### .spec.template.pod.labels
Type: `object` [\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.39/pkg/apis/scheduler/v1alpha1/pod/resources/metadata.go#L39)
diff --git a/pkg/apis/scheduler/v1alpha1/container/resources/resources.go b/pkg/apis/scheduler/v1alpha1/container/resources/resources.go
index bbe91f2a5..32beffc12 100644
--- a/pkg/apis/scheduler/v1alpha1/container/resources/resources.go
+++ b/pkg/apis/scheduler/v1alpha1/container/resources/resources.go
@@ -42,7 +42,10 @@ func (r *Resources) Apply(_ *core.PodTemplateSpec, template *core.Container) err
return nil
}
- template.Resources = r.GetResources()
+ res := r.GetResources()
+
+ template.Resources.Limits = kresources.UpscaleContainerResourceList(template.Resources.Limits, res.Limits)
+ template.Resources.Requests = kresources.UpscaleContainerResourceList(template.Resources.Requests, res.Requests)
return nil
}
diff --git a/pkg/apis/scheduler/v1alpha1/container/resources/volume_mounts.go b/pkg/apis/scheduler/v1alpha1/container/resources/volume_mounts.go
index fdbf5cd83..d3a5acb78 100644
--- a/pkg/apis/scheduler/v1alpha1/container/resources/volume_mounts.go
+++ b/pkg/apis/scheduler/v1alpha1/container/resources/volume_mounts.go
@@ -42,7 +42,7 @@ func (v *VolumeMounts) Apply(_ *core.PodTemplateSpec, container *core.Container)
obj := v.DeepCopy()
- container.VolumeMounts = obj.VolumeMounts
+ container.VolumeMounts = kresources.MergeVolumeMounts(container.VolumeMounts, obj.VolumeMounts...)
return nil
}
diff --git a/pkg/apis/scheduler/v1alpha1/pod/definition.go b/pkg/apis/scheduler/v1alpha1/pod/definition.go
index faf1448da..9648d2cfb 100644
--- a/pkg/apis/scheduler/v1alpha1/pod/definition.go
+++ b/pkg/apis/scheduler/v1alpha1/pod/definition.go
@@ -34,6 +34,9 @@ type Pod struct {
// Metadata keeps the metadata settings for Pod
*schedulerPodResourcesApi.Metadata `json:",inline"`
+ // Image keeps the image information
+ *schedulerPodResourcesApi.Image `json:",inline"`
+
// Scheduling keeps the scheduling information
*schedulerPodResourcesApi.Scheduling `json:",inline"`
@@ -65,6 +68,7 @@ func (a *Pod) With(other *Pod) *Pod {
return &Pod{
Scheduling: a.Scheduling.With(other.Scheduling),
+ Image: a.Image.With(other.Image),
Namespace: a.Namespace.With(other.Namespace),
Security: a.Security.With(other.Security),
Volumes: a.Volumes.With(other.Volumes),
@@ -80,6 +84,7 @@ func (a *Pod) Apply(template *core.PodTemplateSpec) error {
return shared.WithErrors(
a.Scheduling.Apply(template),
+ a.Image.Apply(template),
a.Namespace.Apply(template),
a.Security.Apply(template),
a.Volumes.Apply(template),
@@ -96,6 +101,14 @@ func (a *Pod) GetSecurity() *schedulerPodResourcesApi.Security {
return a.Security
}
+func (a *Pod) GetImage() *schedulerPodResourcesApi.Image {
+ if a == nil {
+ return nil
+ }
+
+ return a.Image
+}
+
func (a *Pod) GetScheduling() *schedulerPodResourcesApi.Scheduling {
if a == nil {
return nil
@@ -142,6 +155,7 @@ func (a *Pod) Validate() error {
}
return shared.WithErrors(
a.Scheduling.Validate(),
+ a.Image.Validate(),
a.Namespace.Validate(),
a.Security.Validate(),
a.Volumes.Validate(),
diff --git a/pkg/apis/scheduler/v1alpha1/pod/resources/image.go b/pkg/apis/scheduler/v1alpha1/pod/resources/image.go
new file mode 100644
index 000000000..495fe3e30
--- /dev/null
+++ b/pkg/apis/scheduler/v1alpha1/pod/resources/image.go
@@ -0,0 +1,87 @@
+//
+// DISCLAIMER
+//
+// Copyright 2024 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 resources
+
+import (
+ core "k8s.io/api/core/v1"
+
+ "github.com/arangodb/kube-arangodb/pkg/apis/scheduler/v1alpha1/interfaces"
+ shared "github.com/arangodb/kube-arangodb/pkg/apis/shared"
+)
+
+type ImagePullSecrets []string
+
+var _ interfaces.Pod[Image] = &Image{}
+
+type Image struct {
+ // ImagePullSecrets define Secrets used to pull Image from registry
+ ImagePullSecrets ImagePullSecrets `json:"imagePullSecrets,omitempty"`
+}
+
+func (i *Image) Apply(pod *core.PodTemplateSpec) error {
+ if i == nil {
+ return nil
+ }
+
+ for _, secret := range i.ImagePullSecrets {
+ if hasImagePullSecret(pod.Spec.ImagePullSecrets, secret) {
+ continue
+ }
+
+ pod.Spec.ImagePullSecrets = append(pod.Spec.ImagePullSecrets, core.LocalObjectReference{
+ Name: secret,
+ })
+ }
+
+ return nil
+}
+
+func (i *Image) With(other *Image) *Image {
+ if i == nil && other == nil {
+ return nil
+ }
+
+ if other == nil {
+ return i.DeepCopy()
+ }
+
+ return other.DeepCopy()
+}
+
+func (i *Image) Validate() error {
+ if i == nil {
+ return nil
+ }
+
+ return shared.WithErrors(
+ shared.PrefixResourceErrors("pullSecrets", shared.ValidateList(i.ImagePullSecrets, shared.ValidateResourceName)),
+ )
+}
+
+func hasImagePullSecret(secrets []core.LocalObjectReference, secret string) bool {
+ for _, sec := range secrets {
+ if sec.Name == secret {
+ return true
+ }
+ }
+
+ return false
+}
diff --git a/pkg/apis/scheduler/v1alpha1/pod/resources/image_test.go b/pkg/apis/scheduler/v1alpha1/pod/resources/image_test.go
new file mode 100644
index 000000000..0c44770b2
--- /dev/null
+++ b/pkg/apis/scheduler/v1alpha1/pod/resources/image_test.go
@@ -0,0 +1,88 @@
+//
+// DISCLAIMER
+//
+// Copyright 2024 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 resources
+
+import (
+ "testing"
+
+ "github.com/stretchr/testify/require"
+ core "k8s.io/api/core/v1"
+)
+
+func applyImage(t *testing.T, template *core.PodTemplateSpec, ns ...*Image) func(in func(t *testing.T, pod *core.PodTemplateSpec)) {
+ var i *Image
+
+ for _, n := range ns {
+ require.NoError(t, n.Validate())
+
+ i = i.With(n)
+
+ require.NoError(t, i.Validate())
+ }
+
+ template = template.DeepCopy()
+
+ if template == nil {
+ template = &core.PodTemplateSpec{}
+ }
+
+ require.NoError(t, i.Apply(template))
+
+ return func(in func(t *testing.T, spec *core.PodTemplateSpec)) {
+ t.Run("Validate", func(t *testing.T) {
+ in(t, template)
+ })
+ }
+}
+
+func Test_Image(t *testing.T) {
+ t.Run("With Nil", func(t *testing.T) {
+ applyImage(t, nil, nil)(func(t *testing.T, pod *core.PodTemplateSpec) {
+ require.Len(t, pod.Spec.ImagePullSecrets, 0)
+ })
+ })
+ t.Run("With Empty", func(t *testing.T) {
+ applyImage(t, &core.PodTemplateSpec{})(func(t *testing.T, pod *core.PodTemplateSpec) {
+ require.Len(t, pod.Spec.ImagePullSecrets, 0)
+ })
+ })
+ t.Run("With PS", func(t *testing.T) {
+ applyImage(t, &core.PodTemplateSpec{}, &Image{
+ ImagePullSecrets: []string{
+ "secret",
+ },
+ })(func(t *testing.T, pod *core.PodTemplateSpec) {
+ require.Len(t, pod.Spec.ImagePullSecrets, 1)
+ require.Equal(t, "secret", pod.Spec.ImagePullSecrets[0].Name)
+ })
+ })
+ t.Run("With PS2", func(t *testing.T) {
+ applyImage(t, &core.PodTemplateSpec{}, &Image{
+ ImagePullSecrets: []string{
+ "secret",
+ "secret",
+ },
+ })(func(t *testing.T, pod *core.PodTemplateSpec) {
+ require.Len(t, pod.Spec.ImagePullSecrets, 1)
+ require.Equal(t, "secret", pod.Spec.ImagePullSecrets[0].Name)
+ })
+ })
+}
diff --git a/pkg/apis/scheduler/v1alpha1/pod/resources/service_account.go b/pkg/apis/scheduler/v1alpha1/pod/resources/service_account.go
index c8cea0b22..68099a796 100644
--- a/pkg/apis/scheduler/v1alpha1/pod/resources/service_account.go
+++ b/pkg/apis/scheduler/v1alpha1/pod/resources/service_account.go
@@ -46,7 +46,9 @@ func (s *ServiceAccount) Apply(template *core.PodTemplateSpec) error {
c := s.DeepCopy()
template.Spec.ServiceAccountName = c.ServiceAccountName
- template.Spec.AutomountServiceAccountToken = c.AutomountServiceAccountToken
+ if c.AutomountServiceAccountToken != nil {
+ template.Spec.AutomountServiceAccountToken = c.AutomountServiceAccountToken
+ }
return nil
}
diff --git a/pkg/apis/scheduler/v1alpha1/pod/resources/volumes.go b/pkg/apis/scheduler/v1alpha1/pod/resources/volumes.go
index 3e561cdab..9e2bd6efd 100644
--- a/pkg/apis/scheduler/v1alpha1/pod/resources/volumes.go
+++ b/pkg/apis/scheduler/v1alpha1/pod/resources/volumes.go
@@ -43,7 +43,7 @@ func (v *Volumes) Apply(template *core.PodTemplateSpec) error {
obj := v.DeepCopy()
- template.Spec.Volumes = obj.Volumes
+ template.Spec.Volumes = kresources.MergeVolumes(template.Spec.Volumes, obj.Volumes...)
return nil
}
diff --git a/pkg/apis/scheduler/v1alpha1/pod/resources/zz_generated.deepcopy.go b/pkg/apis/scheduler/v1alpha1/pod/resources/zz_generated.deepcopy.go
index 7e833e994..b35c27c24 100644
--- a/pkg/apis/scheduler/v1alpha1/pod/resources/zz_generated.deepcopy.go
+++ b/pkg/apis/scheduler/v1alpha1/pod/resources/zz_generated.deepcopy.go
@@ -30,6 +30,47 @@ import (
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *Image) DeepCopyInto(out *Image) {
+ *out = *in
+ if in.ImagePullSecrets != nil {
+ in, out := &in.ImagePullSecrets, &out.ImagePullSecrets
+ *out = make(ImagePullSecrets, len(*in))
+ copy(*out, *in)
+ }
+ return
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Image.
+func (in *Image) DeepCopy() *Image {
+ if in == nil {
+ return nil
+ }
+ out := new(Image)
+ in.DeepCopyInto(out)
+ return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in ImagePullSecrets) DeepCopyInto(out *ImagePullSecrets) {
+ {
+ in := &in
+ *out = make(ImagePullSecrets, len(*in))
+ copy(*out, *in)
+ return
+ }
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ImagePullSecrets.
+func (in ImagePullSecrets) DeepCopy() ImagePullSecrets {
+ if in == nil {
+ return nil
+ }
+ out := new(ImagePullSecrets)
+ in.DeepCopyInto(out)
+ return *out
+}
+
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Metadata) DeepCopyInto(out *Metadata) {
*out = *in
diff --git a/pkg/apis/scheduler/v1alpha1/pod/zz_generated.deepcopy.go b/pkg/apis/scheduler/v1alpha1/pod/zz_generated.deepcopy.go
index 49c6f6a90..5274a706e 100644
--- a/pkg/apis/scheduler/v1alpha1/pod/zz_generated.deepcopy.go
+++ b/pkg/apis/scheduler/v1alpha1/pod/zz_generated.deepcopy.go
@@ -37,6 +37,11 @@ func (in *Pod) DeepCopyInto(out *Pod) {
*out = new(resources.Metadata)
(*in).DeepCopyInto(*out)
}
+ if in.Image != nil {
+ in, out := &in.Image, &out.Image
+ *out = new(resources.Image)
+ (*in).DeepCopyInto(*out)
+ }
if in.Scheduling != nil {
in, out := &in.Scheduling, &out.Scheduling
*out = new(resources.Scheduling)
diff --git a/pkg/apis/scheduler/v1alpha1/profile_selectors.go b/pkg/apis/scheduler/v1alpha1/profile_selectors.go
new file mode 100644
index 000000000..8e2f33470
--- /dev/null
+++ b/pkg/apis/scheduler/v1alpha1/profile_selectors.go
@@ -0,0 +1,49 @@
+//
+// DISCLAIMER
+//
+// Copyright 2024 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 (
+ meta "k8s.io/apimachinery/pkg/apis/meta/v1"
+
+ kresources "github.com/arangodb/kube-arangodb/pkg/util/k8sutil/resources"
+)
+
+type ProfileSelectors struct {
+ // Label keeps information about label selector
+ // +doc/type: meta.LabelSelector
+ Label *meta.LabelSelector `json:"label,omitempty"`
+}
+
+func (p *ProfileSelectors) Validate() error {
+ if p == nil {
+ return nil
+ }
+
+ return nil
+}
+
+func (p *ProfileSelectors) Select(labels map[string]string) bool {
+ if p == nil || p.Label == nil {
+ return false
+ }
+
+ return kresources.SelectLabels(p.Label, labels)
+}
diff --git a/pkg/apis/scheduler/v1alpha1/profile_spec.go b/pkg/apis/scheduler/v1alpha1/profile_spec.go
index e1aa0f517..30bef5c4b 100644
--- a/pkg/apis/scheduler/v1alpha1/profile_spec.go
+++ b/pkg/apis/scheduler/v1alpha1/profile_spec.go
@@ -20,7 +20,25 @@
package v1alpha1
+import (
+ shared "github.com/arangodb/kube-arangodb/pkg/apis/shared"
+)
+
type ProfileSpec struct {
+ // Selectors keeps information about ProfileSelectors
+ Selectors *ProfileSelectors `json:"selectors,omitempty"`
+
// Template keeps the Profile Template
Template *ProfileTemplate `json:"template,omitempty"`
}
+
+func (p *ProfileSpec) Validate() error {
+ if p == nil {
+ return nil
+ }
+
+ return shared.WithErrors(
+ shared.PrefixResourceErrors("selectors", p.Selectors.Validate()),
+ shared.PrefixResourceErrors("template", p.Template.Validate()),
+ )
+}
diff --git a/pkg/apis/scheduler/v1alpha1/profile_templates.go b/pkg/apis/scheduler/v1alpha1/profile_templates.go
index 34f984c71..f3e87557e 100644
--- a/pkg/apis/scheduler/v1alpha1/profile_templates.go
+++ b/pkg/apis/scheduler/v1alpha1/profile_templates.go
@@ -33,7 +33,7 @@ type ProfileTemplates []*ProfileTemplate
func (p ProfileTemplates) Sort() ProfileTemplates {
sort.Slice(p, func(i, j int) bool {
if a, b := p[i].GetPriority(), p[j].GetPriority(); a != b {
- return a < b
+ return a > b
}
return false
@@ -45,8 +45,8 @@ func (p ProfileTemplates) Sort() ProfileTemplates {
func (p ProfileTemplates) Merge() *ProfileTemplate {
var z *ProfileTemplate
- for _, q := range p {
- z = z.With(q)
+ for id := len(p) - 1; id >= 0; id-- {
+ z = z.With(p[id])
}
return z
diff --git a/pkg/apis/scheduler/v1alpha1/zz_generated.deepcopy.go b/pkg/apis/scheduler/v1alpha1/zz_generated.deepcopy.go
index 0ebd19fd9..19ef99b6f 100644
--- a/pkg/apis/scheduler/v1alpha1/zz_generated.deepcopy.go
+++ b/pkg/apis/scheduler/v1alpha1/zz_generated.deepcopy.go
@@ -28,6 +28,7 @@ package v1alpha1
import (
container "github.com/arangodb/kube-arangodb/pkg/apis/scheduler/v1alpha1/container"
pod "github.com/arangodb/kube-arangodb/pkg/apis/scheduler/v1alpha1/pod"
+ v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
runtime "k8s.io/apimachinery/pkg/runtime"
)
@@ -120,9 +121,35 @@ func (in *ProfileContainerTemplate) DeepCopy() *ProfileContainerTemplate {
return out
}
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *ProfileSelectors) DeepCopyInto(out *ProfileSelectors) {
+ *out = *in
+ if in.Label != nil {
+ in, out := &in.Label, &out.Label
+ *out = new(v1.LabelSelector)
+ (*in).DeepCopyInto(*out)
+ }
+ return
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ProfileSelectors.
+func (in *ProfileSelectors) DeepCopy() *ProfileSelectors {
+ if in == nil {
+ return nil
+ }
+ out := new(ProfileSelectors)
+ in.DeepCopyInto(out)
+ return out
+}
+
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ProfileSpec) DeepCopyInto(out *ProfileSpec) {
*out = *in
+ if in.Selectors != nil {
+ in, out := &in.Selectors, &out.Selectors
+ *out = new(ProfileSelectors)
+ (*in).DeepCopyInto(*out)
+ }
if in.Template != nil {
in, out := &in.Template, &out.Template
*out = new(ProfileTemplate)
diff --git a/pkg/crd/crds/ml-extension.schema.generated.yaml b/pkg/crd/crds/ml-extension.schema.generated.yaml
index 2dbfcb115..ca6fbae51 100644
--- a/pkg/crd/crds/ml-extension.schema.generated.yaml
+++ b/pkg/crd/crds/ml-extension.schema.generated.yaml
@@ -349,6 +349,10 @@ v1alpha1:
type: boolean
hostPID:
type: boolean
+ imagePullSecrets:
+ items:
+ type: string
+ type: array
labels:
additionalProperties:
type: string
diff --git a/pkg/crd/crds/scheduler-profile.schema.generated.yaml b/pkg/crd/crds/scheduler-profile.schema.generated.yaml
index a104c3b1c..0b5b01b49 100644
--- a/pkg/crd/crds/scheduler-profile.schema.generated.yaml
+++ b/pkg/crd/crds/scheduler-profile.schema.generated.yaml
@@ -3,6 +3,31 @@ v1alpha1:
properties:
spec:
properties:
+ selectors:
+ description: Selectors keeps information about ProfileSelectors
+ properties:
+ label:
+ description: Label keeps information about label selector
+ properties:
+ matchExpressions:
+ items:
+ properties:
+ key:
+ type: string
+ operator:
+ type: string
+ values:
+ items:
+ type: string
+ type: array
+ type: object
+ type: array
+ matchLabels:
+ additionalProperties:
+ type: string
+ type: object
+ type: object
+ type: object
template:
description: Template keeps the Profile Template
properties:
@@ -930,6 +955,10 @@ v1alpha1:
type: boolean
hostPID:
type: boolean
+ imagePullSecrets:
+ items:
+ type: string
+ type: array
labels:
additionalProperties:
type: string
diff --git a/pkg/util/k8sutil/resources/selectors.go b/pkg/util/k8sutil/resources/selectors.go
new file mode 100644
index 000000000..8f2532fe0
--- /dev/null
+++ b/pkg/util/k8sutil/resources/selectors.go
@@ -0,0 +1,76 @@
+//
+// DISCLAIMER
+//
+// Copyright 2024 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 resources
+
+import (
+ meta "k8s.io/apimachinery/pkg/apis/meta/v1"
+
+ "github.com/arangodb/kube-arangodb/pkg/util/strings"
+)
+
+func SelectLabels(selector *meta.LabelSelector, labels map[string]string) bool {
+ if selector == nil {
+ return false
+ }
+
+ for k, v := range selector.MatchLabels {
+ if v2, ok := labels[k]; !ok || v2 != v {
+ return false
+ }
+ }
+
+ for _, req := range selector.MatchExpressions {
+ switch req.Operator {
+ case meta.LabelSelectorOpIn:
+ if len(req.Values) == 0 {
+ return false
+ }
+
+ if v, ok := labels[req.Key]; !ok {
+ return false
+ } else if !strings.ListContains(req.Values, v) {
+ return false
+ }
+ case meta.LabelSelectorOpNotIn:
+ if len(req.Values) == 0 {
+ return false
+ }
+
+ if v, ok := labels[req.Key]; ok {
+ if strings.ListContains(req.Values, v) {
+ return false
+ }
+ }
+ case meta.LabelSelectorOpExists:
+ if _, ok := labels[req.Key]; !ok {
+ return false
+ }
+ case meta.LabelSelectorOpDoesNotExist:
+ if _, ok := labels[req.Key]; ok {
+ return false
+ }
+ default:
+ return false
+ }
+ }
+
+ return true
+}
diff --git a/pkg/util/k8sutil/resources/selectors_test.go b/pkg/util/k8sutil/resources/selectors_test.go
new file mode 100644
index 000000000..3505d6006
--- /dev/null
+++ b/pkg/util/k8sutil/resources/selectors_test.go
@@ -0,0 +1,249 @@
+//
+// DISCLAIMER
+//
+// Copyright 2024 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 resources
+
+import (
+ "testing"
+
+ "github.com/stretchr/testify/require"
+ meta "k8s.io/apimachinery/pkg/apis/meta/v1"
+)
+
+func Test_Selectors_Labels(t *testing.T) {
+ labels := map[string]string{
+ "A": "B",
+ "C": "D",
+ "E": "F",
+ }
+
+ t.Run("Match", func(t *testing.T) {
+ t.Run("Do not match with nil", func(t *testing.T) {
+ require.False(t, SelectLabels(nil, nil))
+ })
+ t.Run("Match with any", func(t *testing.T) {
+ require.True(t, SelectLabels(&meta.LabelSelector{}, nil))
+ })
+ t.Run("Match with dedicated labels select", func(t *testing.T) {
+ require.True(t, SelectLabels(&meta.LabelSelector{
+ MatchLabels: map[string]string{
+ "A": "B",
+ },
+ }, labels))
+ })
+ t.Run("Match with multiple dedicated labels select", func(t *testing.T) {
+ require.True(t, SelectLabels(&meta.LabelSelector{
+ MatchLabels: map[string]string{
+ "A": "B",
+ "E": "F",
+ },
+ }, labels))
+ })
+ t.Run("Match with mismatch dedicated labels select", func(t *testing.T) {
+ require.False(t, SelectLabels(&meta.LabelSelector{
+ MatchLabels: map[string]string{
+ "A": "B",
+ "E": "G",
+ },
+ }, labels))
+ })
+ })
+
+ t.Run("Match Expression", func(t *testing.T) {
+ t.Run("Exists", func(t *testing.T) {
+ t.Run("Present", func(t *testing.T) {
+ require.True(t, SelectLabels(&meta.LabelSelector{
+ MatchExpressions: []meta.LabelSelectorRequirement{
+ {
+ Key: "A",
+ Operator: meta.LabelSelectorOpExists,
+ },
+ },
+ }, labels))
+ })
+ t.Run("Missing", func(t *testing.T) {
+ require.False(t, SelectLabels(&meta.LabelSelector{
+ MatchExpressions: []meta.LabelSelectorRequirement{
+ {
+ Key: "B",
+ Operator: meta.LabelSelectorOpExists,
+ },
+ },
+ }, labels))
+ })
+ })
+
+ t.Run("Exists", func(t *testing.T) {
+ t.Run("Present", func(t *testing.T) {
+ require.False(t, SelectLabels(&meta.LabelSelector{
+ MatchExpressions: []meta.LabelSelectorRequirement{
+ {
+ Key: "A",
+ Operator: meta.LabelSelectorOpDoesNotExist,
+ },
+ },
+ }, labels))
+ })
+ t.Run("Missing", func(t *testing.T) {
+ require.True(t, SelectLabels(&meta.LabelSelector{
+ MatchExpressions: []meta.LabelSelectorRequirement{
+ {
+ Key: "B",
+ Operator: meta.LabelSelectorOpDoesNotExist,
+ },
+ },
+ }, labels))
+ })
+ })
+
+ t.Run("In", func(t *testing.T) {
+ t.Run("Empty", func(t *testing.T) {
+ require.False(t, SelectLabels(&meta.LabelSelector{
+ MatchExpressions: []meta.LabelSelectorRequirement{
+ {
+ Key: "A",
+ Operator: meta.LabelSelectorOpIn,
+ },
+ },
+ }, labels))
+ })
+ t.Run("Present", func(t *testing.T) {
+ require.True(t, SelectLabels(&meta.LabelSelector{
+ MatchExpressions: []meta.LabelSelectorRequirement{
+ {
+ Key: "A",
+ Operator: meta.LabelSelectorOpIn,
+ Values: []string{
+ "B",
+ },
+ },
+ },
+ }, labels))
+ })
+ t.Run("Present Multiple", func(t *testing.T) {
+ require.True(t, SelectLabels(&meta.LabelSelector{
+ MatchExpressions: []meta.LabelSelectorRequirement{
+ {
+ Key: "A",
+ Operator: meta.LabelSelectorOpIn,
+ Values: []string{
+ "E",
+ "Z",
+ "B",
+ },
+ },
+ },
+ }, labels))
+ })
+ t.Run("Missing", func(t *testing.T) {
+ require.False(t, SelectLabels(&meta.LabelSelector{
+ MatchExpressions: []meta.LabelSelectorRequirement{
+ {
+ Key: "B",
+ Operator: meta.LabelSelectorOpIn,
+ Values: []string{
+ "B",
+ },
+ },
+ },
+ }, labels))
+ })
+ })
+
+ t.Run("NotIn", func(t *testing.T) {
+ t.Run("Not Existing", func(t *testing.T) {
+ require.False(t, SelectLabels(&meta.LabelSelector{
+ MatchExpressions: []meta.LabelSelectorRequirement{
+ {
+ Key: "Z",
+ Operator: meta.LabelSelectorOpNotIn,
+ },
+ },
+ }, labels))
+ })
+ t.Run("Empty", func(t *testing.T) {
+ require.False(t, SelectLabels(&meta.LabelSelector{
+ MatchExpressions: []meta.LabelSelectorRequirement{
+ {
+ Key: "A",
+ Operator: meta.LabelSelectorOpNotIn,
+ },
+ },
+ }, labels))
+ })
+ t.Run("Present", func(t *testing.T) {
+ require.False(t, SelectLabels(&meta.LabelSelector{
+ MatchExpressions: []meta.LabelSelectorRequirement{
+ {
+ Key: "A",
+ Operator: meta.LabelSelectorOpNotIn,
+ Values: []string{
+ "B",
+ },
+ },
+ },
+ }, labels))
+ })
+ t.Run("Present Multiple", func(t *testing.T) {
+ require.False(t, SelectLabels(&meta.LabelSelector{
+ MatchExpressions: []meta.LabelSelectorRequirement{
+ {
+ Key: "A",
+ Operator: meta.LabelSelectorOpNotIn,
+ Values: []string{
+ "E",
+ "Z",
+ "B",
+ },
+ },
+ },
+ }, labels))
+ })
+ t.Run("Missing", func(t *testing.T) {
+ require.True(t, SelectLabels(&meta.LabelSelector{
+ MatchExpressions: []meta.LabelSelectorRequirement{
+ {
+ Key: "B",
+ Operator: meta.LabelSelectorOpNotIn,
+ Values: []string{
+ "B",
+ },
+ },
+ },
+ }, labels))
+ })
+ t.Run("Missing Value", func(t *testing.T) {
+ require.True(t, SelectLabels(&meta.LabelSelector{
+ MatchExpressions: []meta.LabelSelectorRequirement{
+ {
+ Key: "A",
+ Operator: meta.LabelSelectorOpNotIn,
+ Values: []string{
+ "R",
+ "Z",
+ "D",
+ },
+ },
+ },
+ }, labels))
+ })
+ })
+ })
+}
diff --git a/pkg/util/tests/kubernetes.go b/pkg/util/tests/kubernetes.go
index 7e0dc23e4..eea972e4e 100644
--- a/pkg/util/tests/kubernetes.go
+++ b/pkg/util/tests/kubernetes.go
@@ -41,6 +41,8 @@ import (
api "github.com/arangodb/kube-arangodb/pkg/apis/deployment/v1"
"github.com/arangodb/kube-arangodb/pkg/apis/ml"
mlApi "github.com/arangodb/kube-arangodb/pkg/apis/ml/v1alpha1"
+ "github.com/arangodb/kube-arangodb/pkg/apis/scheduler"
+ schedulerApi "github.com/arangodb/kube-arangodb/pkg/apis/scheduler/v1alpha1"
arangoClientSet "github.com/arangodb/kube-arangodb/pkg/generated/clientset/versioned"
operator "github.com/arangodb/kube-arangodb/pkg/operatorV2"
"github.com/arangodb/kube-arangodb/pkg/operatorV2/operation"
@@ -205,6 +207,12 @@ func CreateObjects(t *testing.T, k8s kubernetes.Interface, arango arangoClientSe
vl := *v
_, err := arango.MlV1alpha1().ArangoMLCronJobs(vl.GetNamespace()).Create(context.Background(), vl, meta.CreateOptions{})
require.NoError(t, err)
+ case **schedulerApi.ArangoProfile:
+ require.NotNil(t, v)
+
+ vl := *v
+ _, err := arango.SchedulerV1alpha1().ArangoProfiles(vl.GetNamespace()).Create(context.Background(), vl, meta.CreateOptions{})
+ require.NoError(t, err)
default:
require.Fail(t, fmt.Sprintf("Unable to create object: %s", reflect.TypeOf(v).String()))
}
@@ -325,6 +333,12 @@ func UpdateObjects(t *testing.T, k8s kubernetes.Interface, arango arangoClientSe
vl := *v
_, err := k8s.RbacV1().RoleBindings(vl.GetNamespace()).Update(context.Background(), vl, meta.UpdateOptions{})
require.NoError(t, err)
+ case **schedulerApi.ArangoProfile:
+ require.NotNil(t, v)
+
+ vl := *v
+ _, err := arango.SchedulerV1alpha1().ArangoProfiles(vl.GetNamespace()).Update(context.Background(), vl, meta.UpdateOptions{})
+ require.NoError(t, err)
default:
require.Fail(t, fmt.Sprintf("Unable to create object: %s", reflect.TypeOf(v).String()))
}
@@ -700,6 +714,21 @@ func RefreshObjects(t *testing.T, k8s kubernetes.Interface, arango arangoClientS
} else {
*v = vn
}
+ case **schedulerApi.ArangoProfile:
+ require.NotNil(t, v)
+
+ vl := *v
+
+ vn, err := arango.SchedulerV1alpha1().ArangoProfiles(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
+ }
default:
require.Fail(t, fmt.Sprintf("Unable to get object: %s", reflect.TypeOf(v).String()))
}
@@ -832,11 +861,23 @@ func SetMetaBasedOnType(t *testing.T, object meta.Object) {
ml.ArangoMLCronJobResourcePlural,
object.GetNamespace(),
object.GetName()))
+ case *schedulerApi.ArangoProfile:
+ v.Kind = scheduler.ArangoProfileResourceKind
+ v.APIVersion = schedulerApi.SchemeGroupVersion.String()
+ v.SetSelfLink(fmt.Sprintf("/api/%s/%s/%s/%s",
+ schedulerApi.SchemeGroupVersion.String(),
+ scheduler.ArangoProfileResourcePlural,
+ object.GetNamespace(),
+ object.GetName()))
default:
require.Fail(t, fmt.Sprintf("Unable to create object: %s", reflect.TypeOf(v).String()))
}
}
+func NewMetaObjectInDefaultNamespace[T meta.Object](t *testing.T, name string, mods ...MetaObjectMod[T]) T {
+ return NewMetaObject[T](t, FakeNamespace, name, mods...)
+}
+
func NewMetaObject[T meta.Object](t *testing.T, namespace, name string, mods ...MetaObjectMod[T]) T {
var obj T
@@ -951,6 +992,10 @@ func NewItem(t *testing.T, o operation.Operation, object meta.Object) operation.
item.Group = ml.ArangoMLGroupName
item.Version = mlApi.ArangoMLVersion
item.Kind = ml.ArangoMLCronJobResourceKind
+ case *schedulerApi.ArangoProfile:
+ item.Group = scheduler.ArangoSchedulerGroupName
+ item.Version = schedulerApi.ArangoSchedulerVersion
+ item.Kind = scheduler.ArangoProfileResourceKind
default:
require.Fail(t, fmt.Sprintf("Unable to create object: %s", reflect.TypeOf(v).String()))
}
diff --git a/pkg/util/tests/kubernetes_test.go b/pkg/util/tests/kubernetes_test.go
index 00af39dba..421373982 100644
--- a/pkg/util/tests/kubernetes_test.go
+++ b/pkg/util/tests/kubernetes_test.go
@@ -1,7 +1,7 @@
//
// DISCLAIMER
//
-// Copyright 2023 ArangoDB GmbH, Cologne, Germany
+// Copyright 2023-2024 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.
@@ -34,6 +34,7 @@ import (
backupApi "github.com/arangodb/kube-arangodb/pkg/apis/backup/v1"
api "github.com/arangodb/kube-arangodb/pkg/apis/deployment/v1"
mlApi "github.com/arangodb/kube-arangodb/pkg/apis/ml/v1alpha1"
+ schedulerApi "github.com/arangodb/kube-arangodb/pkg/apis/scheduler/v1alpha1"
"github.com/arangodb/kube-arangodb/pkg/operatorV2/operation"
"github.com/arangodb/kube-arangodb/pkg/util/kclient"
)
@@ -76,4 +77,5 @@ func Test_NewMetaObject(t *testing.T) {
NewMetaObjectRun[*backupApi.ArangoBackup](t)
NewMetaObjectRun[*mlApi.ArangoMLExtension](t)
NewMetaObjectRun[*mlApi.ArangoMLStorage](t)
+ NewMetaObjectRun[*schedulerApi.ArangoProfile](t)
}
diff --git a/pkg/util/tests/resources.go b/pkg/util/tests/resources.go
new file mode 100644
index 000000000..5241e4971
--- /dev/null
+++ b/pkg/util/tests/resources.go
@@ -0,0 +1,36 @@
+//
+// DISCLAIMER
+//
+// Copyright 2024 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 tests
+
+import (
+ "testing"
+
+ "github.com/stretchr/testify/require"
+ core "k8s.io/api/core/v1"
+
+ kresources "github.com/arangodb/kube-arangodb/pkg/util/k8sutil/resources"
+)
+
+func GetContainerByNameT(t *testing.T, containers []core.Container, name string) core.Container {
+ id := kresources.GetContainerIDByName(containers, name)
+ require.NotEqualValues(t, -1, id)
+ return containers[id]
+}