mirror of
https://github.com/arangodb/kube-arangodb.git
synced 2024-12-14 11:57:37 +00:00
[Feature] JobScheduler Coverage (#1606)
This commit is contained in:
parent
9459237da6
commit
e7ae432b24
32 changed files with 3116 additions and 218 deletions
|
@ -6,6 +6,7 @@
|
|||
- (Bugfix) Fix Resources Copy mechanism to prevent invalid pod creation
|
||||
- (Bugfix) Wait for ImageStatus in ImageDiscover
|
||||
- (Bugfix) Fix Image Error Propagation
|
||||
- (Feature) JobScheduler Coverage
|
||||
|
||||
## [1.2.38](https://github.com/arangodb/kube-arangodb/tree/1.2.38) (2024-02-22)
|
||||
- (Feature) Extract GRPC Server
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -116,7 +116,7 @@ Default Value: `9202`
|
|||
|
||||
### .spec.mode.sidecar.env
|
||||
|
||||
Type: `core.EnvVar` <sup>[\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.38/pkg/apis/scheduler/v1alpha1/container/resources/environments.go#L33)</sup>
|
||||
Type: `core.EnvVar` <sup>[\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.38/pkg/apis/scheduler/v1alpha1/container/resources/environments.go#L36)</sup>
|
||||
|
||||
Env keeps the information about environment variables provided to the container
|
||||
|
||||
|
@ -127,7 +127,7 @@ Links:
|
|||
|
||||
### .spec.mode.sidecar.envFrom
|
||||
|
||||
Type: `core.EnvFromSource` <sup>[\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.38/pkg/apis/scheduler/v1alpha1/container/resources/environments.go#L38)</sup>
|
||||
Type: `core.EnvFromSource` <sup>[\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.38/pkg/apis/scheduler/v1alpha1/container/resources/environments.go#L41)</sup>
|
||||
|
||||
EnvFrom keeps the information about environment variable sources provided to the container
|
||||
|
||||
|
@ -138,7 +138,7 @@ Links:
|
|||
|
||||
### .spec.mode.sidecar.image
|
||||
|
||||
Type: `string` <sup>[\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.38/pkg/apis/scheduler/v1alpha1/container/resources/image.go#L34)</sup>
|
||||
Type: `string` <sup>[\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.38/pkg/apis/scheduler/v1alpha1/container/resources/image.go#L37)</sup>
|
||||
|
||||
Image define image details
|
||||
|
||||
|
@ -146,7 +146,7 @@ Image define image details
|
|||
|
||||
### .spec.mode.sidecar.imagePullPolicy
|
||||
|
||||
Type: `string` <sup>[\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.38/pkg/apis/scheduler/v1alpha1/container/resources/image.go#L38)</sup>
|
||||
Type: `string` <sup>[\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.38/pkg/apis/scheduler/v1alpha1/container/resources/image.go#L41)</sup>
|
||||
|
||||
ImagePullPolicy define Image pull policy
|
||||
|
||||
|
@ -156,7 +156,7 @@ Default Value: `IfNotPresent`
|
|||
|
||||
### .spec.mode.sidecar.imagePullSecrets
|
||||
|
||||
Type: `array` <sup>[\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.38/pkg/apis/scheduler/v1alpha1/container/resources/image.go#L41)</sup>
|
||||
Type: `array` <sup>[\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.38/pkg/apis/scheduler/v1alpha1/container/resources/image.go#L44)</sup>
|
||||
|
||||
ImagePullSecrets define Secrets used to pull Image from registry
|
||||
|
||||
|
@ -174,7 +174,7 @@ Default Value: `9201`
|
|||
|
||||
### .spec.mode.sidecar.resources
|
||||
|
||||
Type: `core.ResourceRequirements` <sup>[\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.38/pkg/apis/scheduler/v1alpha1/container/resources/resources.go#L34)</sup>
|
||||
Type: `core.ResourceRequirements` <sup>[\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.38/pkg/apis/scheduler/v1alpha1/container/resources/resources.go#L37)</sup>
|
||||
|
||||
Resources holds resource requests & limits for container
|
||||
|
||||
|
@ -185,9 +185,9 @@ Links:
|
|||
|
||||
### .spec.mode.sidecar.securityContext
|
||||
|
||||
Type: `core.SecurityContext` <sup>[\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.38/pkg/apis/scheduler/v1alpha1/container/resources/security.go#L31)</sup>
|
||||
Type: `core.SecurityContext` <sup>[\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.38/pkg/apis/scheduler/v1alpha1/container/resources/security.go#L35)</sup>
|
||||
|
||||
PodSecurityContext holds pod-level security attributes and common container settings.
|
||||
SecurityContext holds container-level security attributes and common container settings.
|
||||
|
||||
Links:
|
||||
* [Kubernetes docs](https://kubernetes.io/docs/tasks/configure-pod-container/security-context/)
|
||||
|
|
|
@ -74,21 +74,22 @@ func Test_ArangoMLStorageSpec(t *testing.T) {
|
|||
expectedRequirements := core.ResourceRequirements{
|
||||
Requests: assignedRequirements.Requests,
|
||||
Limits: core.ResourceList{
|
||||
core.ResourceCPU: resource.MustParse("100m"),
|
||||
core.ResourceMemory: resource.MustParse("128Mi"),
|
||||
core.ResourceCPU: resource.MustParse("200m"),
|
||||
core.ResourceMemory: resource.MustParse("256Mi"),
|
||||
},
|
||||
}
|
||||
|
||||
actualRequirements := s.Mode.Sidecar.GetResources().With(&schedulerContainerResourcesApi.Resources{Resources: &core.ResourceRequirements{
|
||||
Limits: core.ResourceList{
|
||||
core.ResourceCPU: resource.MustParse("100m"),
|
||||
core.ResourceMemory: resource.MustParse("128Mi"),
|
||||
},
|
||||
Requests: core.ResourceList{
|
||||
core.ResourceCPU: resource.MustParse("200m"),
|
||||
core.ResourceMemory: resource.MustParse("256Mi"),
|
||||
},
|
||||
Requests: core.ResourceList{
|
||||
core.ResourceCPU: resource.MustParse("100m"),
|
||||
core.ResourceMemory: resource.MustParse("128Mi"),
|
||||
},
|
||||
}})
|
||||
|
||||
require.Equal(t, expectedRequirements, actualRequirements.GetResources())
|
||||
})
|
||||
}
|
||||
|
|
|
@ -24,8 +24,10 @@ import (
|
|||
core "k8s.io/api/core/v1"
|
||||
|
||||
schedulerContainerResourcesApi "github.com/arangodb/kube-arangodb/pkg/apis/scheduler/v1alpha1/container/resources"
|
||||
"github.com/arangodb/kube-arangodb/pkg/apis/scheduler/v1alpha1/interfaces"
|
||||
shared "github.com/arangodb/kube-arangodb/pkg/apis/shared"
|
||||
"github.com/arangodb/kube-arangodb/pkg/util"
|
||||
"github.com/arangodb/kube-arangodb/pkg/util/errors"
|
||||
"github.com/arangodb/kube-arangodb/pkg/util/k8sutil/container"
|
||||
)
|
||||
|
||||
|
@ -89,6 +91,18 @@ func (c Containers) With(other Containers) Containers {
|
|||
return ret
|
||||
}
|
||||
|
||||
func (c Containers) Validate() error {
|
||||
for name, container := range c {
|
||||
if err := container.Validate(); err != nil {
|
||||
return errors.Wrapf(err, "Container %s failed", name)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
var _ interfaces.Container[Container] = &Container{}
|
||||
|
||||
type Container struct {
|
||||
// Security keeps the security settings for Container
|
||||
*schedulerContainerResourcesApi.Security `json:",inline"`
|
||||
|
@ -109,10 +123,10 @@ func (c *Container) Apply(template *core.PodTemplateSpec, container *core.Contai
|
|||
}
|
||||
|
||||
return shared.WithErrors(
|
||||
c.Security.Apply(container),
|
||||
c.Environments.Apply(container),
|
||||
c.Security.Apply(template, container),
|
||||
c.Environments.Apply(template, container),
|
||||
c.Image.Apply(template, container),
|
||||
c.Resources.Apply(container),
|
||||
c.Resources.Apply(template, container),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,236 @@
|
|||
//
|
||||
// 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 container
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
core "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/resource"
|
||||
"k8s.io/apimachinery/pkg/util/yaml"
|
||||
|
||||
schedulerContainerResourcesApi "github.com/arangodb/kube-arangodb/pkg/apis/scheduler/v1alpha1/container/resources"
|
||||
"github.com/arangodb/kube-arangodb/pkg/util"
|
||||
)
|
||||
|
||||
func applyContainer(t *testing.T, template *core.PodTemplateSpec, container *core.Container, ns ...*Container) func(in func(t *testing.T, pod *core.PodTemplateSpec, container *core.Container, spec *Container)) {
|
||||
var i *Container
|
||||
|
||||
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{}
|
||||
}
|
||||
|
||||
container = container.DeepCopy()
|
||||
if container == nil {
|
||||
container = &core.Container{}
|
||||
}
|
||||
|
||||
template.Spec.Containers = append(template.Spec.Containers, *container)
|
||||
|
||||
container = &template.Spec.Containers[0]
|
||||
|
||||
require.NoError(t, i.Apply(template, container))
|
||||
|
||||
return func(in func(t *testing.T, pod *core.PodTemplateSpec, container *core.Container, spec *Container)) {
|
||||
t.Run("Validate", func(t *testing.T) {
|
||||
if i != nil {
|
||||
in(t, template, container, i)
|
||||
} else {
|
||||
in(t, template, container, &Container{})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func applyContainerYAML(t *testing.T, template *core.PodTemplateSpec, container *core.Container, ns ...string) func(in func(t *testing.T, pod *core.PodTemplateSpec, container *core.Container, spec *Container)) {
|
||||
elements := make([]*Container, len(ns))
|
||||
|
||||
for id := range ns {
|
||||
var p Container
|
||||
require.NoError(t, yaml.Unmarshal([]byte(ns[id]), &p))
|
||||
elements[id] = p.DeepCopy()
|
||||
}
|
||||
|
||||
return applyContainer(t, template, container, elements...)
|
||||
}
|
||||
|
||||
func Test_Container(t *testing.T) {
|
||||
t.Run("Nil", func(t *testing.T) {
|
||||
applyContainer(t, nil, nil)(func(t *testing.T, pod *core.PodTemplateSpec, container *core.Container, spec *Container) {
|
||||
require.Nil(t, spec.Resources)
|
||||
require.Nil(t, spec.Image)
|
||||
require.Nil(t, spec.Security)
|
||||
require.Nil(t, spec.Environments)
|
||||
|
||||
require.Len(t, container.Env, 0)
|
||||
})
|
||||
})
|
||||
t.Run("Empty template", func(t *testing.T) {
|
||||
applyContainer(t, &core.PodTemplateSpec{}, &core.Container{})(func(t *testing.T, pod *core.PodTemplateSpec, container *core.Container, spec *Container) {
|
||||
require.Nil(t, spec.Resources)
|
||||
require.Nil(t, spec.Image)
|
||||
require.Nil(t, spec.Security)
|
||||
require.Nil(t, spec.Environments)
|
||||
|
||||
require.Len(t, container.Env, 0)
|
||||
})
|
||||
})
|
||||
t.Run("With fields", func(t *testing.T) {
|
||||
applyContainer(t, &core.PodTemplateSpec{}, &core.Container{}, &Container{
|
||||
Security: &schedulerContainerResourcesApi.Security{
|
||||
SecurityContext: &core.SecurityContext{
|
||||
RunAsUser: util.NewType[int64](50),
|
||||
},
|
||||
},
|
||||
Environments: &schedulerContainerResourcesApi.Environments{
|
||||
Env: []core.EnvVar{
|
||||
{
|
||||
Name: "key1",
|
||||
Value: "value1",
|
||||
},
|
||||
},
|
||||
},
|
||||
Image: &schedulerContainerResourcesApi.Image{
|
||||
Image: util.NewType("test"),
|
||||
},
|
||||
Resources: &schedulerContainerResourcesApi.Resources{
|
||||
Resources: &core.ResourceRequirements{
|
||||
Limits: map[core.ResourceName]resource.Quantity{
|
||||
core.ResourceCPU: resource.MustParse("1"),
|
||||
},
|
||||
},
|
||||
},
|
||||
})(func(t *testing.T, pod *core.PodTemplateSpec, container *core.Container, spec *Container) {
|
||||
// Spec
|
||||
require.NotNil(t, spec.Resources)
|
||||
require.NotNil(t, spec.Resources.Resources)
|
||||
require.Contains(t, spec.Resources.Resources.Limits, core.ResourceCPU)
|
||||
require.EqualValues(t, resource.MustParse("1"), spec.Resources.Resources.Limits[core.ResourceCPU])
|
||||
|
||||
require.NotNil(t, spec.Image)
|
||||
require.NotNil(t, spec.Image.Image)
|
||||
require.EqualValues(t, "test", *spec.Image.Image)
|
||||
|
||||
require.NotNil(t, spec.Security)
|
||||
require.NotNil(t, spec.Security.SecurityContext)
|
||||
require.NotNil(t, spec.Security.SecurityContext.RunAsUser)
|
||||
require.EqualValues(t, 50, *spec.Security.SecurityContext.RunAsUser)
|
||||
|
||||
require.NotNil(t, spec.Environments)
|
||||
require.Len(t, spec.Environments.Env, 1)
|
||||
require.EqualValues(t, "key1", spec.Environments.Env[0].Name)
|
||||
require.EqualValues(t, "value1", spec.Environments.Env[0].Value)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Container_YAML(t *testing.T) {
|
||||
t.Run("With Override", func(t *testing.T) {
|
||||
applyContainerYAML(t, &core.PodTemplateSpec{}, &core.Container{}, `
|
||||
---
|
||||
securityContext:
|
||||
runAsUser: 50
|
||||
|
||||
env:
|
||||
- name: key1
|
||||
value: value1
|
||||
|
||||
image: test
|
||||
|
||||
resources:
|
||||
limits:
|
||||
cpu: 1
|
||||
`, `
|
||||
---
|
||||
|
||||
securityContext:
|
||||
runAsUser: 10
|
||||
`)(func(t *testing.T, pod *core.PodTemplateSpec, container *core.Container, spec *Container) {
|
||||
// Spec
|
||||
require.NotNil(t, spec.Resources)
|
||||
require.NotNil(t, spec.Resources.Resources)
|
||||
require.Contains(t, spec.Resources.Resources.Limits, core.ResourceCPU)
|
||||
require.EqualValues(t, resource.MustParse("1"), spec.Resources.Resources.Limits[core.ResourceCPU])
|
||||
|
||||
require.NotNil(t, spec.Image)
|
||||
require.NotNil(t, spec.Image.Image)
|
||||
require.EqualValues(t, "test", *spec.Image.Image)
|
||||
|
||||
require.NotNil(t, spec.Security)
|
||||
require.NotNil(t, spec.Security.SecurityContext)
|
||||
require.NotNil(t, spec.Security.SecurityContext.RunAsUser)
|
||||
require.EqualValues(t, 10, *spec.Security.SecurityContext.RunAsUser)
|
||||
|
||||
require.NotNil(t, spec.Environments)
|
||||
require.Len(t, spec.Environments.Env, 1)
|
||||
require.EqualValues(t, "key1", spec.Environments.Env[0].Name)
|
||||
require.EqualValues(t, "value1", spec.Environments.Env[0].Value)
|
||||
})
|
||||
})
|
||||
t.Run("With fields", func(t *testing.T) {
|
||||
applyContainerYAML(t, &core.PodTemplateSpec{}, &core.Container{}, `
|
||||
---
|
||||
securityContext:
|
||||
runAsUser: 50
|
||||
|
||||
env:
|
||||
- name: key1
|
||||
value: value1
|
||||
|
||||
image: test
|
||||
|
||||
resources:
|
||||
limits:
|
||||
cpu: 1
|
||||
`)(func(t *testing.T, pod *core.PodTemplateSpec, container *core.Container, spec *Container) {
|
||||
// Spec
|
||||
require.NotNil(t, spec.Resources)
|
||||
require.NotNil(t, spec.Resources.Resources)
|
||||
require.Contains(t, spec.Resources.Resources.Limits, core.ResourceCPU)
|
||||
require.EqualValues(t, resource.MustParse("1"), spec.Resources.Resources.Limits[core.ResourceCPU])
|
||||
|
||||
require.NotNil(t, spec.Image)
|
||||
require.NotNil(t, spec.Image.Image)
|
||||
require.EqualValues(t, "test", *spec.Image.Image)
|
||||
|
||||
require.NotNil(t, spec.Security)
|
||||
require.NotNil(t, spec.Security.SecurityContext)
|
||||
require.NotNil(t, spec.Security.SecurityContext.RunAsUser)
|
||||
require.EqualValues(t, 50, *spec.Security.SecurityContext.RunAsUser)
|
||||
|
||||
require.NotNil(t, spec.Environments)
|
||||
require.Len(t, spec.Environments.Env, 1)
|
||||
require.EqualValues(t, "key1", spec.Environments.Env[0].Name)
|
||||
require.EqualValues(t, "value1", spec.Environments.Env[0].Value)
|
||||
})
|
||||
})
|
||||
}
|
|
@ -0,0 +1,205 @@
|
|||
//
|
||||
// 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 container
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
core "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/resource"
|
||||
"k8s.io/apimachinery/pkg/util/yaml"
|
||||
|
||||
schedulerContainerResourcesApi "github.com/arangodb/kube-arangodb/pkg/apis/scheduler/v1alpha1/container/resources"
|
||||
"github.com/arangodb/kube-arangodb/pkg/util"
|
||||
)
|
||||
|
||||
func applyContainers(t *testing.T, template *core.PodTemplateSpec, ns ...Containers) func(in func(t *testing.T, pod *core.PodTemplateSpec, spec Containers)) {
|
||||
var i Containers
|
||||
|
||||
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, pod *core.PodTemplateSpec, spec Containers)) {
|
||||
t.Run("Validate", func(t *testing.T) {
|
||||
if i != nil {
|
||||
in(t, template, i)
|
||||
} else {
|
||||
in(t, template, Containers{})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func applyContainersYAML(t *testing.T, template *core.PodTemplateSpec, ns ...string) func(in func(t *testing.T, pod *core.PodTemplateSpec, spec Containers)) {
|
||||
elements := make([]Containers, len(ns))
|
||||
|
||||
for id := range ns {
|
||||
var p Containers
|
||||
require.NoError(t, yaml.Unmarshal([]byte(ns[id]), &p))
|
||||
elements[id] = p.DeepCopy()
|
||||
}
|
||||
|
||||
return applyContainers(t, template, elements...)
|
||||
}
|
||||
|
||||
func Test_Containers(t *testing.T) {
|
||||
t.Run("Nil", func(t *testing.T) {
|
||||
applyContainers(t, nil)(func(t *testing.T, pod *core.PodTemplateSpec, spec Containers) {
|
||||
require.Len(t, spec, 0)
|
||||
require.Len(t, pod.Spec.Containers, 0)
|
||||
})
|
||||
})
|
||||
t.Run("Empty template", func(t *testing.T) {
|
||||
applyContainers(t, &core.PodTemplateSpec{})(func(t *testing.T, pod *core.PodTemplateSpec, spec Containers) {
|
||||
require.Len(t, spec, 0)
|
||||
require.Len(t, pod.Spec.Containers, 0)
|
||||
})
|
||||
})
|
||||
t.Run("Add container", func(t *testing.T) {
|
||||
applyContainers(t, &core.PodTemplateSpec{}, Containers{
|
||||
"test": {
|
||||
Image: &schedulerContainerResourcesApi.Image{
|
||||
Image: util.NewType("test"),
|
||||
},
|
||||
Resources: &schedulerContainerResourcesApi.Resources{
|
||||
Resources: &core.ResourceRequirements{
|
||||
Limits: map[core.ResourceName]resource.Quantity{
|
||||
core.ResourceCPU: resource.MustParse("1"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})(func(t *testing.T, pod *core.PodTemplateSpec, spec Containers) {
|
||||
require.Len(t, spec, 1)
|
||||
require.Len(t, pod.Spec.Containers, 1)
|
||||
require.EqualValues(t, "test", pod.Spec.Containers[0].Name)
|
||||
require.EqualValues(t, "test", pod.Spec.Containers[0].Image)
|
||||
require.Len(t, pod.Spec.Containers[0].Resources.Limits, 1)
|
||||
require.Contains(t, pod.Spec.Containers[0].Resources.Limits, core.ResourceCPU)
|
||||
require.EqualValues(t, resource.MustParse("1"), pod.Spec.Containers[0].Resources.Limits[core.ResourceCPU])
|
||||
})
|
||||
})
|
||||
t.Run("Append container", func(t *testing.T) {
|
||||
applyContainers(t, &core.PodTemplateSpec{
|
||||
Spec: core.PodSpec{
|
||||
Containers: []core.Container{
|
||||
{
|
||||
Name: "example",
|
||||
},
|
||||
},
|
||||
},
|
||||
}, Containers{
|
||||
"test": {
|
||||
Image: &schedulerContainerResourcesApi.Image{
|
||||
Image: util.NewType("test"),
|
||||
},
|
||||
Resources: &schedulerContainerResourcesApi.Resources{
|
||||
Resources: &core.ResourceRequirements{
|
||||
Limits: map[core.ResourceName]resource.Quantity{
|
||||
core.ResourceCPU: resource.MustParse("1"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})(func(t *testing.T, pod *core.PodTemplateSpec, spec Containers) {
|
||||
require.Len(t, spec, 1)
|
||||
require.Len(t, pod.Spec.Containers, 2)
|
||||
require.EqualValues(t, "test", pod.Spec.Containers[1].Name)
|
||||
require.EqualValues(t, "test", pod.Spec.Containers[1].Image)
|
||||
require.Len(t, pod.Spec.Containers[1].Resources.Limits, 1)
|
||||
require.Contains(t, pod.Spec.Containers[1].Resources.Limits, core.ResourceCPU)
|
||||
require.EqualValues(t, resource.MustParse("1"), pod.Spec.Containers[1].Resources.Limits[core.ResourceCPU])
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Containers_YAML(t *testing.T) {
|
||||
t.Run("Nil", func(t *testing.T) {
|
||||
applyContainersYAML(t, nil)(func(t *testing.T, pod *core.PodTemplateSpec, spec Containers) {
|
||||
require.Len(t, spec, 0)
|
||||
require.Len(t, pod.Spec.Containers, 0)
|
||||
})
|
||||
})
|
||||
t.Run("Empty template", func(t *testing.T) {
|
||||
applyContainersYAML(t, &core.PodTemplateSpec{})(func(t *testing.T, pod *core.PodTemplateSpec, spec Containers) {
|
||||
require.Len(t, spec, 0)
|
||||
require.Len(t, pod.Spec.Containers, 0)
|
||||
})
|
||||
})
|
||||
t.Run("Add container", func(t *testing.T) {
|
||||
applyContainersYAML(t, &core.PodTemplateSpec{}, `
|
||||
---
|
||||
test:
|
||||
image: test
|
||||
resources:
|
||||
limits:
|
||||
cpu: 1
|
||||
`)(func(t *testing.T, pod *core.PodTemplateSpec, spec Containers) {
|
||||
require.Len(t, spec, 1)
|
||||
require.Len(t, pod.Spec.Containers, 1)
|
||||
require.EqualValues(t, "test", pod.Spec.Containers[0].Name)
|
||||
require.EqualValues(t, "test", pod.Spec.Containers[0].Image)
|
||||
require.Len(t, pod.Spec.Containers[0].Resources.Limits, 1)
|
||||
require.Contains(t, pod.Spec.Containers[0].Resources.Limits, core.ResourceCPU)
|
||||
require.EqualValues(t, resource.MustParse("1"), pod.Spec.Containers[0].Resources.Limits[core.ResourceCPU])
|
||||
})
|
||||
})
|
||||
t.Run("Append container", func(t *testing.T) {
|
||||
applyContainersYAML(t, &core.PodTemplateSpec{
|
||||
Spec: core.PodSpec{
|
||||
Containers: []core.Container{
|
||||
{
|
||||
Name: "example",
|
||||
},
|
||||
},
|
||||
},
|
||||
}, `
|
||||
---
|
||||
test:
|
||||
image: test
|
||||
resources:
|
||||
limits:
|
||||
cpu: 1
|
||||
`)(func(t *testing.T, pod *core.PodTemplateSpec, spec Containers) {
|
||||
require.Len(t, spec, 1)
|
||||
require.Len(t, pod.Spec.Containers, 2)
|
||||
require.EqualValues(t, "test", pod.Spec.Containers[1].Name)
|
||||
require.EqualValues(t, "test", pod.Spec.Containers[1].Image)
|
||||
require.Len(t, pod.Spec.Containers[1].Resources.Limits, 1)
|
||||
require.Contains(t, pod.Spec.Containers[1].Resources.Limits, core.ResourceCPU)
|
||||
require.EqualValues(t, resource.MustParse("1"), pod.Spec.Containers[1].Resources.Limits[core.ResourceCPU])
|
||||
})
|
||||
})
|
||||
}
|
|
@ -24,13 +24,13 @@ import (
|
|||
core "k8s.io/api/core/v1"
|
||||
|
||||
schedulerContainerResourcesApi "github.com/arangodb/kube-arangodb/pkg/apis/scheduler/v1alpha1/container/resources"
|
||||
"github.com/arangodb/kube-arangodb/pkg/apis/scheduler/v1alpha1/interfaces"
|
||||
shared "github.com/arangodb/kube-arangodb/pkg/apis/shared"
|
||||
)
|
||||
|
||||
type Generic struct {
|
||||
// Security keeps the security settings for Container
|
||||
*schedulerContainerResourcesApi.Security `json:",inline"`
|
||||
var _ interfaces.Pod[Generic] = &Generic{}
|
||||
|
||||
type Generic struct {
|
||||
// Environments keeps the environment variables for Container
|
||||
*schedulerContainerResourcesApi.Environments `json:",inline"`
|
||||
}
|
||||
|
@ -42,8 +42,7 @@ func (c *Generic) Apply(template *core.PodTemplateSpec) error {
|
|||
|
||||
for id := range template.Spec.Containers {
|
||||
if err := shared.WithErrors(
|
||||
c.Security.Apply(&template.Spec.Containers[id]),
|
||||
c.Environments.Apply(&template.Spec.Containers[id]),
|
||||
c.Environments.Apply(template, &template.Spec.Containers[id]),
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -52,14 +51,6 @@ func (c *Generic) Apply(template *core.PodTemplateSpec) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (c *Generic) GetSecurity() *schedulerContainerResourcesApi.Security {
|
||||
if c == nil || c.Security == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return c.Security
|
||||
}
|
||||
|
||||
func (c *Generic) GetEnvironments() *schedulerContainerResourcesApi.Environments {
|
||||
if c == nil || c.Environments == nil {
|
||||
return nil
|
||||
|
@ -82,7 +73,6 @@ func (c *Generic) With(other *Generic) *Generic {
|
|||
}
|
||||
|
||||
return &Generic{
|
||||
Security: c.Security.With(other.Security),
|
||||
Environments: c.Environments.With(other.Environments),
|
||||
}
|
||||
}
|
||||
|
@ -93,7 +83,6 @@ func (c *Generic) Validate() error {
|
|||
}
|
||||
|
||||
return shared.WithErrors(
|
||||
shared.PrefixResourceErrors("containerSecurity", c.Security.Validate()),
|
||||
shared.PrefixResourceErrors("containerEnvironments", c.Environments.Validate()),
|
||||
)
|
||||
}
|
||||
|
|
176
pkg/apis/scheduler/v1alpha1/container/generic_test.go
Normal file
176
pkg/apis/scheduler/v1alpha1/container/generic_test.go
Normal file
|
@ -0,0 +1,176 @@
|
|||
//
|
||||
// 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 container
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
core "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/util/yaml"
|
||||
|
||||
schedulerContainerResourcesApi "github.com/arangodb/kube-arangodb/pkg/apis/scheduler/v1alpha1/container/resources"
|
||||
)
|
||||
|
||||
func applyGeneric(t *testing.T, template *core.PodTemplateSpec, ns ...*Generic) func(in func(t *testing.T, pod *core.PodTemplateSpec, spec *Generic)) {
|
||||
var i *Generic
|
||||
|
||||
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, pod *core.PodTemplateSpec, spec *Generic)) {
|
||||
t.Run("Validate", func(t *testing.T) {
|
||||
if i != nil {
|
||||
in(t, template, i)
|
||||
} else {
|
||||
in(t, template, &Generic{})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func applyGenericYAML(t *testing.T, template *core.PodTemplateSpec, ns ...string) func(in func(t *testing.T, pod *core.PodTemplateSpec, spec *Generic)) {
|
||||
elements := make([]*Generic, len(ns))
|
||||
|
||||
for id := range ns {
|
||||
var p Generic
|
||||
require.NoError(t, yaml.Unmarshal([]byte(ns[id]), &p))
|
||||
elements[id] = p.DeepCopy()
|
||||
}
|
||||
|
||||
return applyGeneric(t, template, elements...)
|
||||
}
|
||||
|
||||
func Test_Generic(t *testing.T) {
|
||||
t.Run("Nil", func(t *testing.T) {
|
||||
applyGeneric(t, nil, nil)(func(t *testing.T, pod *core.PodTemplateSpec, spec *Generic) {
|
||||
require.Nil(t, spec.Environments)
|
||||
})
|
||||
})
|
||||
t.Run("Empty template", func(t *testing.T) {
|
||||
applyGeneric(t, &core.PodTemplateSpec{})(func(t *testing.T, pod *core.PodTemplateSpec, spec *Generic) {
|
||||
require.Nil(t, spec.Environments)
|
||||
})
|
||||
})
|
||||
t.Run("With fields", func(t *testing.T) {
|
||||
applyGeneric(t, &core.PodTemplateSpec{
|
||||
Spec: core.PodSpec{
|
||||
Containers: []core.Container{
|
||||
{},
|
||||
},
|
||||
},
|
||||
}, &Generic{
|
||||
Environments: &schedulerContainerResourcesApi.Environments{
|
||||
Env: []core.EnvVar{
|
||||
{
|
||||
Name: "key1",
|
||||
Value: "value1",
|
||||
},
|
||||
},
|
||||
},
|
||||
})(func(t *testing.T, pod *core.PodTemplateSpec, spec *Generic) {
|
||||
// Spec
|
||||
require.NotNil(t, spec.Environments)
|
||||
require.Len(t, spec.Environments.Env, 1)
|
||||
require.EqualValues(t, "key1", spec.Environments.Env[0].Name)
|
||||
require.EqualValues(t, "value1", spec.Environments.Env[0].Value)
|
||||
|
||||
// Check
|
||||
require.Len(t, pod.Spec.Containers, 1)
|
||||
require.Len(t, pod.Spec.Containers[0].Env, 1)
|
||||
require.EqualValues(t, "key1", pod.Spec.Containers[0].Env[0].Name)
|
||||
require.EqualValues(t, "value1", pod.Spec.Containers[0].Env[0].Value)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Generic_YAML(t *testing.T) {
|
||||
t.Run("With Override", func(t *testing.T) {
|
||||
applyGenericYAML(t, &core.PodTemplateSpec{
|
||||
Spec: core.PodSpec{
|
||||
Containers: []core.Container{
|
||||
{},
|
||||
},
|
||||
},
|
||||
}, `
|
||||
---
|
||||
env:
|
||||
- name: key1
|
||||
value: value1
|
||||
`, `
|
||||
---
|
||||
env:
|
||||
- name: key1
|
||||
value: value2
|
||||
`)(func(t *testing.T, pod *core.PodTemplateSpec, spec *Generic) {
|
||||
// Spec
|
||||
require.NotNil(t, spec.Environments)
|
||||
require.Len(t, spec.Environments.Env, 1)
|
||||
require.EqualValues(t, "key1", spec.Environments.Env[0].Name)
|
||||
require.EqualValues(t, "value2", spec.Environments.Env[0].Value)
|
||||
|
||||
// Check
|
||||
require.Len(t, pod.Spec.Containers, 1)
|
||||
require.Len(t, pod.Spec.Containers[0].Env, 1)
|
||||
require.EqualValues(t, "key1", pod.Spec.Containers[0].Env[0].Name)
|
||||
require.EqualValues(t, "value2", pod.Spec.Containers[0].Env[0].Value)
|
||||
})
|
||||
})
|
||||
t.Run("With fields", func(t *testing.T) {
|
||||
applyGenericYAML(t, &core.PodTemplateSpec{
|
||||
Spec: core.PodSpec{
|
||||
Containers: []core.Container{
|
||||
{},
|
||||
},
|
||||
},
|
||||
}, `
|
||||
---
|
||||
env:
|
||||
- name: key1
|
||||
value: value1
|
||||
`)(func(t *testing.T, pod *core.PodTemplateSpec, spec *Generic) {
|
||||
// Spec
|
||||
require.NotNil(t, spec.Environments)
|
||||
require.Len(t, spec.Environments.Env, 1)
|
||||
require.EqualValues(t, "key1", spec.Environments.Env[0].Name)
|
||||
require.EqualValues(t, "value1", spec.Environments.Env[0].Value)
|
||||
|
||||
// Check
|
||||
require.Len(t, pod.Spec.Containers, 1)
|
||||
require.Len(t, pod.Spec.Containers[0].Env, 1)
|
||||
require.EqualValues(t, "key1", pod.Spec.Containers[0].Env[0].Name)
|
||||
require.EqualValues(t, "value1", pod.Spec.Containers[0].Env[0].Value)
|
||||
})
|
||||
})
|
||||
}
|
|
@ -23,9 +23,12 @@ package resources
|
|||
import (
|
||||
core "k8s.io/api/core/v1"
|
||||
|
||||
"github.com/arangodb/kube-arangodb/pkg/apis/scheduler/v1alpha1/interfaces"
|
||||
"github.com/arangodb/kube-arangodb/pkg/util/k8sutil/envs"
|
||||
)
|
||||
|
||||
var _ interfaces.Container[Environments] = &Environments{}
|
||||
|
||||
type Environments struct {
|
||||
// Env keeps the information about environment variables provided to the container
|
||||
// +doc/type: core.EnvVar
|
||||
|
@ -38,7 +41,7 @@ type Environments struct {
|
|||
EnvFrom []core.EnvFromSource `json:"envFrom,omitempty"`
|
||||
}
|
||||
|
||||
func (e *Environments) Apply(container *core.Container) error {
|
||||
func (e *Environments) Apply(_ *core.PodTemplateSpec, container *core.Container) error {
|
||||
if e == nil {
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -0,0 +1,221 @@
|
|||
//
|
||||
// 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"
|
||||
"k8s.io/apimachinery/pkg/api/resource"
|
||||
)
|
||||
|
||||
func applyEnvironments(t *testing.T, template *core.PodTemplateSpec, container *core.Container, ns ...*Environments) func(in func(t *testing.T, pod *core.PodTemplateSpec, container *core.Container)) {
|
||||
var i *Environments
|
||||
|
||||
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{}
|
||||
}
|
||||
|
||||
container = container.DeepCopy()
|
||||
if container == nil {
|
||||
container = &core.Container{}
|
||||
}
|
||||
|
||||
template.Spec.Containers = append(template.Spec.Containers, *container)
|
||||
|
||||
container = &template.Spec.Containers[0]
|
||||
|
||||
require.NoError(t, i.Apply(template, container))
|
||||
|
||||
return func(in func(t *testing.T, spec *core.PodTemplateSpec, container *core.Container)) {
|
||||
t.Run("Validate", func(t *testing.T) {
|
||||
in(t, template, container)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_Environments(t *testing.T) {
|
||||
t.Run("With Nil", func(t *testing.T) {
|
||||
applyEnvironments(t, nil, nil)(func(t *testing.T, _ *core.PodTemplateSpec, container *core.Container) {
|
||||
require.Len(t, container.Env, 0)
|
||||
require.Len(t, container.EnvFrom, 0)
|
||||
})
|
||||
})
|
||||
t.Run("With Empty", func(t *testing.T) {
|
||||
applyEnvironments(t, &core.PodTemplateSpec{}, &core.Container{})(func(t *testing.T, _ *core.PodTemplateSpec, container *core.Container) {
|
||||
require.Len(t, container.Env, 0)
|
||||
require.Len(t, container.EnvFrom, 0)
|
||||
})
|
||||
})
|
||||
t.Run("With Env", func(t *testing.T) {
|
||||
applyEnvironments(t, &core.PodTemplateSpec{}, &core.Container{}, &Environments{
|
||||
Env: []core.EnvVar{
|
||||
{
|
||||
Name: "var1",
|
||||
Value: "value1",
|
||||
},
|
||||
},
|
||||
EnvFrom: []core.EnvFromSource{
|
||||
{
|
||||
Prefix: "DATA_",
|
||||
SecretRef: &core.SecretEnvSource{
|
||||
LocalObjectReference: core.LocalObjectReference{
|
||||
Name: "secret1",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})(func(t *testing.T, _ *core.PodTemplateSpec, container *core.Container) {
|
||||
require.Len(t, container.Env, 1)
|
||||
require.EqualValues(t, "var1", container.Env[0].Name)
|
||||
require.EqualValues(t, "value1", container.Env[0].Value)
|
||||
require.Nil(t, container.Env[0].ValueFrom)
|
||||
|
||||
require.Len(t, container.EnvFrom, 1)
|
||||
require.EqualValues(t, "DATA_", container.EnvFrom[0].Prefix)
|
||||
require.NotNil(t, container.EnvFrom[0].SecretRef)
|
||||
require.Nil(t, container.EnvFrom[0].SecretRef.Optional)
|
||||
require.EqualValues(t, "secret1", container.EnvFrom[0].SecretRef.Name)
|
||||
require.Nil(t, container.EnvFrom[0].ConfigMapRef)
|
||||
})
|
||||
})
|
||||
t.Run("With Env Merge", func(t *testing.T) {
|
||||
applyEnvironments(t, &core.PodTemplateSpec{}, &core.Container{}, &Environments{
|
||||
Env: []core.EnvVar{
|
||||
{
|
||||
Name: "var1",
|
||||
Value: "value1",
|
||||
},
|
||||
},
|
||||
EnvFrom: []core.EnvFromSource{
|
||||
{
|
||||
Prefix: "DATA_",
|
||||
SecretRef: &core.SecretEnvSource{
|
||||
LocalObjectReference: core.LocalObjectReference{
|
||||
Name: "secret1",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}, &Environments{
|
||||
Env: []core.EnvVar{
|
||||
{
|
||||
Name: "var2",
|
||||
Value: "value2",
|
||||
},
|
||||
},
|
||||
EnvFrom: []core.EnvFromSource{
|
||||
{
|
||||
Prefix: "DATA2_",
|
||||
ConfigMapRef: &core.ConfigMapEnvSource{
|
||||
LocalObjectReference: core.LocalObjectReference{
|
||||
Name: "cm1",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})(func(t *testing.T, _ *core.PodTemplateSpec, container *core.Container) {
|
||||
require.Len(t, container.Env, 2)
|
||||
require.EqualValues(t, "var1", container.Env[0].Name)
|
||||
require.EqualValues(t, "value1", container.Env[0].Value)
|
||||
require.Nil(t, container.Env[0].ValueFrom)
|
||||
require.EqualValues(t, "var2", container.Env[1].Name)
|
||||
require.EqualValues(t, "value2", container.Env[1].Value)
|
||||
require.Nil(t, container.Env[1].ValueFrom)
|
||||
|
||||
require.Len(t, container.EnvFrom, 2)
|
||||
require.EqualValues(t, "DATA_", container.EnvFrom[0].Prefix)
|
||||
require.NotNil(t, container.EnvFrom[0].SecretRef)
|
||||
require.Nil(t, container.EnvFrom[0].SecretRef.Optional)
|
||||
require.EqualValues(t, "secret1", container.EnvFrom[0].SecretRef.Name)
|
||||
require.Nil(t, container.EnvFrom[0].ConfigMapRef)
|
||||
|
||||
require.EqualValues(t, "DATA2_", container.EnvFrom[1].Prefix)
|
||||
require.NotNil(t, container.EnvFrom[1].ConfigMapRef)
|
||||
require.Nil(t, container.EnvFrom[1].ConfigMapRef.Optional)
|
||||
require.EqualValues(t, "cm1", container.EnvFrom[1].ConfigMapRef.Name)
|
||||
require.Nil(t, container.EnvFrom[1].SecretRef)
|
||||
})
|
||||
})
|
||||
t.Run("With Env Replace", func(t *testing.T) {
|
||||
applyEnvironments(t, &core.PodTemplateSpec{}, &core.Container{}, &Environments{
|
||||
Env: []core.EnvVar{
|
||||
{
|
||||
Name: "var1",
|
||||
Value: "value1",
|
||||
},
|
||||
},
|
||||
}, &Environments{
|
||||
Env: []core.EnvVar{
|
||||
{
|
||||
Name: "var1",
|
||||
Value: "value2",
|
||||
},
|
||||
},
|
||||
})(func(t *testing.T, _ *core.PodTemplateSpec, container *core.Container) {
|
||||
require.Len(t, container.EnvFrom, 0)
|
||||
require.Len(t, container.Env, 1)
|
||||
require.EqualValues(t, "var1", container.Env[0].Name)
|
||||
require.EqualValues(t, "value2", container.Env[0].Value)
|
||||
require.Nil(t, container.Env[0].ValueFrom)
|
||||
})
|
||||
})
|
||||
t.Run("With Env Replace Type", func(t *testing.T) {
|
||||
applyEnvironments(t, &core.PodTemplateSpec{}, &core.Container{}, &Environments{
|
||||
Env: []core.EnvVar{
|
||||
{
|
||||
Name: "var1",
|
||||
Value: "value1",
|
||||
},
|
||||
},
|
||||
}, &Environments{
|
||||
Env: []core.EnvVar{
|
||||
{
|
||||
Name: "var1",
|
||||
ValueFrom: &core.EnvVarSource{
|
||||
ResourceFieldRef: &core.ResourceFieldSelector{
|
||||
ContainerName: "a",
|
||||
Resource: "b",
|
||||
Divisor: resource.Quantity{},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})(func(t *testing.T, _ *core.PodTemplateSpec, container *core.Container) {
|
||||
require.Len(t, container.EnvFrom, 0)
|
||||
require.Len(t, container.Env, 1)
|
||||
require.EqualValues(t, "var1", container.Env[0].Name)
|
||||
require.Empty(t, container.Env[0].Value)
|
||||
require.NotNil(t, container.Env[0].ValueFrom)
|
||||
})
|
||||
})
|
||||
}
|
|
@ -23,12 +23,15 @@ 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"
|
||||
"github.com/arangodb/kube-arangodb/pkg/util"
|
||||
)
|
||||
|
||||
type ImagePullSecrets []string
|
||||
|
||||
var _ interfaces.Container[Image] = &Image{}
|
||||
|
||||
type Image struct {
|
||||
// Image define image details
|
||||
Image *string `json:"image,omitempty"`
|
||||
|
|
120
pkg/apis/scheduler/v1alpha1/container/resources/image_test.go
Normal file
120
pkg/apis/scheduler/v1alpha1/container/resources/image_test.go
Normal file
|
@ -0,0 +1,120 @@
|
|||
//
|
||||
// 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"
|
||||
|
||||
"github.com/arangodb/kube-arangodb/pkg/util"
|
||||
)
|
||||
|
||||
func applyImage(t *testing.T, template *core.PodTemplateSpec, container *core.Container, ns ...*Image) func(in func(t *testing.T, pod *core.PodTemplateSpec, container *core.Container)) {
|
||||
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{}
|
||||
}
|
||||
|
||||
container = container.DeepCopy()
|
||||
if container == nil {
|
||||
container = &core.Container{}
|
||||
}
|
||||
|
||||
template.Spec.Containers = append(template.Spec.Containers, *container)
|
||||
|
||||
container = &template.Spec.Containers[0]
|
||||
|
||||
require.NoError(t, i.Apply(template, container))
|
||||
|
||||
return func(in func(t *testing.T, spec *core.PodTemplateSpec, container *core.Container)) {
|
||||
t.Run("Validate", func(t *testing.T) {
|
||||
in(t, template, container)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
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, container *core.Container) {
|
||||
require.Empty(t, container.Image)
|
||||
require.Empty(t, container.ImagePullPolicy)
|
||||
require.Len(t, pod.Spec.ImagePullSecrets, 0)
|
||||
})
|
||||
})
|
||||
t.Run("With Empty", func(t *testing.T) {
|
||||
applyImage(t, &core.PodTemplateSpec{}, &core.Container{})(func(t *testing.T, pod *core.PodTemplateSpec, container *core.Container) {
|
||||
require.Empty(t, container.Image)
|
||||
require.Empty(t, container.ImagePullPolicy)
|
||||
require.Len(t, pod.Spec.ImagePullSecrets, 0)
|
||||
})
|
||||
})
|
||||
t.Run("With Image", func(t *testing.T) {
|
||||
applyImage(t, &core.PodTemplateSpec{}, &core.Container{}, &Image{
|
||||
Image: util.NewType("image"),
|
||||
})(func(t *testing.T, pod *core.PodTemplateSpec, container *core.Container) {
|
||||
require.EqualValues(t, "image", container.Image)
|
||||
require.Empty(t, container.ImagePullPolicy)
|
||||
require.Len(t, pod.Spec.ImagePullSecrets, 0)
|
||||
})
|
||||
})
|
||||
t.Run("With PS", func(t *testing.T) {
|
||||
applyImage(t, &core.PodTemplateSpec{}, &core.Container{}, &Image{
|
||||
Image: util.NewType("image"),
|
||||
ImagePullPolicy: util.NewType(core.PullAlways),
|
||||
ImagePullSecrets: []string{
|
||||
"secret",
|
||||
},
|
||||
})(func(t *testing.T, pod *core.PodTemplateSpec, container *core.Container) {
|
||||
require.EqualValues(t, "image", container.Image)
|
||||
require.EqualValues(t, core.PullAlways, container.ImagePullPolicy)
|
||||
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{}, &core.Container{}, &Image{
|
||||
Image: util.NewType("image"),
|
||||
ImagePullPolicy: util.NewType(core.PullAlways),
|
||||
ImagePullSecrets: []string{
|
||||
"secret",
|
||||
"secret",
|
||||
},
|
||||
})(func(t *testing.T, pod *core.PodTemplateSpec, container *core.Container) {
|
||||
require.EqualValues(t, "image", container.Image)
|
||||
require.EqualValues(t, core.PullAlways, container.ImagePullPolicy)
|
||||
require.Len(t, pod.Spec.ImagePullSecrets, 1)
|
||||
require.Equal(t, "secret", pod.Spec.ImagePullSecrets[0].Name)
|
||||
})
|
||||
})
|
||||
}
|
|
@ -23,10 +23,13 @@ package resources
|
|||
import (
|
||||
core "k8s.io/api/core/v1"
|
||||
|
||||
"github.com/arangodb/kube-arangodb/pkg/apis/scheduler/v1alpha1/interfaces"
|
||||
"github.com/arangodb/kube-arangodb/pkg/util"
|
||||
"github.com/arangodb/kube-arangodb/pkg/util/k8sutil/resources"
|
||||
)
|
||||
|
||||
var _ interfaces.Container[Resources] = &Resources{}
|
||||
|
||||
type Resources struct {
|
||||
// Resources holds resource requests & limits for container
|
||||
// +doc/type: core.ResourceRequirements
|
||||
|
@ -34,12 +37,12 @@ type Resources struct {
|
|||
Resources *core.ResourceRequirements `json:"resources,omitempty"`
|
||||
}
|
||||
|
||||
func (r *Resources) Apply(template *core.Container) error {
|
||||
func (r *Resources) Apply(_ *core.PodTemplateSpec, template *core.Container) error {
|
||||
if r == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
template.Resources = util.WithDefault(r.Resources.DeepCopy())
|
||||
template.Resources = r.GetResources()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -57,7 +60,7 @@ func (r *Resources) With(newResources *Resources) *Resources {
|
|||
return r.DeepCopy()
|
||||
}
|
||||
|
||||
return &Resources{Resources: util.NewType(resources.ApplyContainerResource(r.GetResources(), newResources.GetResources()))}
|
||||
return &Resources{Resources: util.NewType(resources.MergeContainerResource(r.GetResources(), newResources.GetResources()))}
|
||||
}
|
||||
|
||||
func (r *Resources) GetResources() core.ResourceRequirements {
|
||||
|
@ -65,7 +68,11 @@ func (r *Resources) GetResources() core.ResourceRequirements {
|
|||
return core.ResourceRequirements{}
|
||||
}
|
||||
|
||||
return *r.Resources
|
||||
local := r.Resources.DeepCopy()
|
||||
|
||||
local.Limits = resources.UpscaleOptionalContainerResourceList(local.Limits, local.Requests)
|
||||
|
||||
return *local
|
||||
}
|
||||
|
||||
func (r *Resources) Validate() error {
|
||||
|
|
|
@ -0,0 +1,210 @@
|
|||
//
|
||||
// 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"
|
||||
"k8s.io/apimachinery/pkg/api/resource"
|
||||
)
|
||||
|
||||
func applyResources(t *testing.T, template *core.PodTemplateSpec, container *core.Container, ns ...*Resources) func(in func(t *testing.T, pod *core.PodTemplateSpec, container *core.Container)) {
|
||||
var i *Resources
|
||||
|
||||
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{}
|
||||
}
|
||||
|
||||
container = container.DeepCopy()
|
||||
if container == nil {
|
||||
container = &core.Container{}
|
||||
}
|
||||
|
||||
template.Spec.Containers = append(template.Spec.Containers, *container)
|
||||
|
||||
container = &template.Spec.Containers[0]
|
||||
|
||||
require.NoError(t, i.Apply(template, container))
|
||||
|
||||
return func(in func(t *testing.T, spec *core.PodTemplateSpec, container *core.Container)) {
|
||||
t.Run("Validate", func(t *testing.T) {
|
||||
in(t, template, container)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_Resources(t *testing.T) {
|
||||
v1Mi := resource.MustParse("1Mi")
|
||||
v8Mi := resource.MustParse("8Mi")
|
||||
v0 := resource.MustParse("0")
|
||||
|
||||
t.Run("With Nil", func(t *testing.T) {
|
||||
applyResources(t, nil, nil)(func(t *testing.T, pod *core.PodTemplateSpec, container *core.Container) {
|
||||
require.Empty(t, container.Resources)
|
||||
})
|
||||
})
|
||||
t.Run("With Empty", func(t *testing.T) {
|
||||
applyResources(t, &core.PodTemplateSpec{}, &core.Container{})(func(t *testing.T, pod *core.PodTemplateSpec, container *core.Container) {
|
||||
require.Empty(t, container.Resources)
|
||||
})
|
||||
})
|
||||
t.Run("Add", func(t *testing.T) {
|
||||
applyResources(t, &core.PodTemplateSpec{}, &core.Container{}, &Resources{
|
||||
Resources: &core.ResourceRequirements{
|
||||
Limits: core.ResourceList{
|
||||
core.ResourceCPU: v1Mi,
|
||||
},
|
||||
Requests: core.ResourceList{
|
||||
core.ResourceCPU: v1Mi,
|
||||
},
|
||||
},
|
||||
})(func(t *testing.T, pod *core.PodTemplateSpec, container *core.Container) {
|
||||
require.Len(t, container.Resources.Limits, 1)
|
||||
require.Contains(t, container.Resources.Limits, core.ResourceCPU)
|
||||
require.EqualValues(t, v1Mi, container.Resources.Limits[core.ResourceCPU])
|
||||
|
||||
require.Len(t, container.Resources.Requests, 1)
|
||||
require.Contains(t, container.Resources.Requests, core.ResourceCPU)
|
||||
require.EqualValues(t, v1Mi, container.Resources.Requests[core.ResourceCPU])
|
||||
})
|
||||
})
|
||||
t.Run("Add New One", func(t *testing.T) {
|
||||
applyResources(t, &core.PodTemplateSpec{}, &core.Container{}, &Resources{
|
||||
Resources: &core.ResourceRequirements{
|
||||
Limits: core.ResourceList{
|
||||
core.ResourceCPU: v1Mi,
|
||||
},
|
||||
Requests: core.ResourceList{
|
||||
core.ResourceCPU: v1Mi,
|
||||
},
|
||||
},
|
||||
}, &Resources{
|
||||
Resources: &core.ResourceRequirements{
|
||||
Limits: core.ResourceList{
|
||||
core.ResourceMemory: v1Mi,
|
||||
},
|
||||
Requests: core.ResourceList{
|
||||
core.ResourceMemory: v1Mi,
|
||||
},
|
||||
},
|
||||
})(func(t *testing.T, pod *core.PodTemplateSpec, container *core.Container) {
|
||||
require.Len(t, container.Resources.Limits, 2)
|
||||
require.Contains(t, container.Resources.Limits, core.ResourceCPU)
|
||||
require.EqualValues(t, v1Mi, container.Resources.Limits[core.ResourceCPU])
|
||||
require.Contains(t, container.Resources.Limits, core.ResourceMemory)
|
||||
require.EqualValues(t, v1Mi, container.Resources.Limits[core.ResourceMemory])
|
||||
|
||||
require.Len(t, container.Resources.Requests, 2)
|
||||
require.Contains(t, container.Resources.Requests, core.ResourceCPU)
|
||||
require.EqualValues(t, v1Mi, container.Resources.Requests[core.ResourceCPU])
|
||||
require.Contains(t, container.Resources.Requests, core.ResourceMemory)
|
||||
require.EqualValues(t, v1Mi, container.Resources.Requests[core.ResourceMemory])
|
||||
})
|
||||
})
|
||||
t.Run("Update", func(t *testing.T) {
|
||||
applyResources(t, &core.PodTemplateSpec{}, &core.Container{}, &Resources{
|
||||
Resources: &core.ResourceRequirements{
|
||||
Limits: core.ResourceList{
|
||||
core.ResourceCPU: v1Mi,
|
||||
},
|
||||
Requests: core.ResourceList{
|
||||
core.ResourceCPU: v1Mi,
|
||||
},
|
||||
},
|
||||
}, &Resources{
|
||||
Resources: &core.ResourceRequirements{
|
||||
Limits: core.ResourceList{
|
||||
core.ResourceCPU: v8Mi,
|
||||
},
|
||||
},
|
||||
})(func(t *testing.T, pod *core.PodTemplateSpec, container *core.Container) {
|
||||
require.Len(t, container.Resources.Limits, 1)
|
||||
require.Contains(t, container.Resources.Limits, core.ResourceCPU)
|
||||
require.EqualValues(t, v8Mi, container.Resources.Limits[core.ResourceCPU])
|
||||
|
||||
require.Len(t, container.Resources.Requests, 1)
|
||||
require.Contains(t, container.Resources.Requests, core.ResourceCPU)
|
||||
require.EqualValues(t, v1Mi, container.Resources.Requests[core.ResourceCPU])
|
||||
})
|
||||
})
|
||||
t.Run("Remove", func(t *testing.T) {
|
||||
applyResources(t, &core.PodTemplateSpec{}, &core.Container{}, &Resources{
|
||||
Resources: &core.ResourceRequirements{
|
||||
Limits: core.ResourceList{
|
||||
core.ResourceCPU: v1Mi,
|
||||
},
|
||||
Requests: core.ResourceList{
|
||||
core.ResourceCPU: v1Mi,
|
||||
},
|
||||
},
|
||||
}, &Resources{
|
||||
Resources: &core.ResourceRequirements{
|
||||
Limits: core.ResourceList{
|
||||
core.ResourceCPU: v0,
|
||||
},
|
||||
},
|
||||
})(func(t *testing.T, pod *core.PodTemplateSpec, container *core.Container) {
|
||||
require.Len(t, container.Resources.Limits, 0)
|
||||
|
||||
require.Len(t, container.Resources.Requests, 1)
|
||||
require.Contains(t, container.Resources.Requests, core.ResourceCPU)
|
||||
require.EqualValues(t, v1Mi, container.Resources.Requests[core.ResourceCPU])
|
||||
})
|
||||
})
|
||||
t.Run("Upscale", func(t *testing.T) {
|
||||
applyResources(t, &core.PodTemplateSpec{}, &core.Container{}, &Resources{
|
||||
Resources: &core.ResourceRequirements{
|
||||
Limits: core.ResourceList{
|
||||
core.ResourceCPU: v1Mi,
|
||||
},
|
||||
Requests: core.ResourceList{
|
||||
core.ResourceCPU: v1Mi,
|
||||
},
|
||||
},
|
||||
}, &Resources{
|
||||
Resources: &core.ResourceRequirements{
|
||||
Requests: core.ResourceList{
|
||||
core.ResourceCPU: v8Mi,
|
||||
},
|
||||
},
|
||||
})(func(t *testing.T, pod *core.PodTemplateSpec, container *core.Container) {
|
||||
require.Len(t, container.Resources.Limits, 1)
|
||||
require.Contains(t, container.Resources.Limits, core.ResourceCPU)
|
||||
require.EqualValues(t, v8Mi, container.Resources.Limits[core.ResourceCPU])
|
||||
|
||||
require.Len(t, container.Resources.Requests, 1)
|
||||
require.Contains(t, container.Resources.Requests, core.ResourceCPU)
|
||||
require.EqualValues(t, v8Mi, container.Resources.Requests[core.ResourceCPU])
|
||||
})
|
||||
})
|
||||
}
|
|
@ -22,16 +22,20 @@ package resources
|
|||
|
||||
import (
|
||||
core "k8s.io/api/core/v1"
|
||||
|
||||
"github.com/arangodb/kube-arangodb/pkg/apis/scheduler/v1alpha1/interfaces"
|
||||
)
|
||||
|
||||
var _ interfaces.Container[Security] = &Security{}
|
||||
|
||||
type Security struct {
|
||||
// PodSecurityContext holds pod-level security attributes and common container settings.
|
||||
// SecurityContext holds container-level security attributes and common container settings.
|
||||
// +doc/type: core.SecurityContext
|
||||
// +doc/link: Kubernetes docs|https://kubernetes.io/docs/tasks/configure-pod-container/security-context/
|
||||
SecurityContext *core.SecurityContext `json:"securityContext,omitempty"`
|
||||
}
|
||||
|
||||
func (s *Security) Apply(template *core.Container) error {
|
||||
func (s *Security) Apply(_ *core.PodTemplateSpec, template *core.Container) error {
|
||||
if s == nil {
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -0,0 +1,96 @@
|
|||
//
|
||||
// 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"
|
||||
|
||||
"github.com/arangodb/kube-arangodb/pkg/util"
|
||||
)
|
||||
|
||||
func applySecurity(t *testing.T, template *core.PodTemplateSpec, container *core.Container, ns ...*Security) func(in func(t *testing.T, pod *core.PodTemplateSpec, container *core.Container)) {
|
||||
var i *Security
|
||||
|
||||
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{}
|
||||
}
|
||||
|
||||
container = container.DeepCopy()
|
||||
if container == nil {
|
||||
container = &core.Container{}
|
||||
}
|
||||
|
||||
template.Spec.Containers = append(template.Spec.Containers, *container)
|
||||
|
||||
container = &template.Spec.Containers[0]
|
||||
|
||||
require.NoError(t, i.Apply(template, container))
|
||||
|
||||
return func(in func(t *testing.T, spec *core.PodTemplateSpec, container *core.Container)) {
|
||||
t.Run("Validate", func(t *testing.T) {
|
||||
in(t, template, container)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_Security(t *testing.T) {
|
||||
t.Run("With Nil", func(t *testing.T) {
|
||||
applySecurity(t, nil, nil)(func(t *testing.T, pod *core.PodTemplateSpec, container *core.Container) {
|
||||
require.Nil(t, container.SecurityContext)
|
||||
})
|
||||
})
|
||||
t.Run("With Empty", func(t *testing.T) {
|
||||
applySecurity(t, &core.PodTemplateSpec{}, &core.Container{})(func(t *testing.T, pod *core.PodTemplateSpec, container *core.Container) {
|
||||
require.Nil(t, container.SecurityContext)
|
||||
})
|
||||
})
|
||||
t.Run("Pick Always Latest Not Nil", func(t *testing.T) {
|
||||
applySecurity(t, &core.PodTemplateSpec{}, &core.Container{}, &Security{
|
||||
SecurityContext: &core.SecurityContext{
|
||||
RunAsUser: util.NewType[int64](20),
|
||||
RunAsGroup: util.NewType[int64](30),
|
||||
},
|
||||
}, &Security{
|
||||
SecurityContext: &core.SecurityContext{
|
||||
RunAsGroup: util.NewType[int64](60),
|
||||
},
|
||||
}, nil)(func(t *testing.T, _ *core.PodTemplateSpec, container *core.Container) {
|
||||
require.NotNil(t, container.SecurityContext)
|
||||
|
||||
require.Nil(t, container.SecurityContext.RunAsUser)
|
||||
require.NotNil(t, container.SecurityContext.RunAsGroup)
|
||||
require.EqualValues(t, 60, *container.SecurityContext.RunAsGroup)
|
||||
})
|
||||
})
|
||||
}
|
|
@ -90,11 +90,6 @@ func (in Containers) DeepCopy() Containers {
|
|||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *Generic) DeepCopyInto(out *Generic) {
|
||||
*out = *in
|
||||
if in.Security != nil {
|
||||
in, out := &in.Security, &out.Security
|
||||
*out = new(resources.Security)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.Environments != nil {
|
||||
in, out := &in.Environments, &out.Environments
|
||||
*out = new(resources.Environments)
|
||||
|
|
38
pkg/apis/scheduler/v1alpha1/interfaces/interfaces.go
Normal file
38
pkg/apis/scheduler/v1alpha1/interfaces/interfaces.go
Normal file
|
@ -0,0 +1,38 @@
|
|||
//
|
||||
// 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 interfaces
|
||||
|
||||
import core "k8s.io/api/core/v1"
|
||||
|
||||
type Core[T any] interface {
|
||||
With(T) T
|
||||
Validate() error
|
||||
}
|
||||
|
||||
type Container[T any] interface {
|
||||
Core[*T]
|
||||
Apply(template *core.PodTemplateSpec, container *core.Container) error
|
||||
}
|
||||
|
||||
type Pod[T any] interface {
|
||||
Core[*T]
|
||||
Apply(template *core.PodTemplateSpec) error
|
||||
}
|
|
@ -23,10 +23,13 @@ package pod
|
|||
import (
|
||||
core "k8s.io/api/core/v1"
|
||||
|
||||
"github.com/arangodb/kube-arangodb/pkg/apis/scheduler/v1alpha1/interfaces"
|
||||
schedulerPodResourcesApi "github.com/arangodb/kube-arangodb/pkg/apis/scheduler/v1alpha1/pod/resources"
|
||||
shared "github.com/arangodb/kube-arangodb/pkg/apis/shared"
|
||||
)
|
||||
|
||||
var _ interfaces.Pod[Pod] = &Pod{}
|
||||
|
||||
type Pod struct {
|
||||
// Scheduling keeps the scheduling information
|
||||
*schedulerPodResourcesApi.Scheduling `json:",inline"`
|
||||
|
@ -38,6 +41,26 @@ type Pod struct {
|
|||
*schedulerPodResourcesApi.Security `json:",inline"`
|
||||
}
|
||||
|
||||
func (a *Pod) With(other *Pod) *Pod {
|
||||
if a == nil && other == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if a == nil {
|
||||
return other.DeepCopy()
|
||||
}
|
||||
|
||||
if other == nil {
|
||||
return a.DeepCopy()
|
||||
}
|
||||
|
||||
return &Pod{
|
||||
Scheduling: a.Scheduling.With(other.Scheduling),
|
||||
Namespace: a.Namespace.With(other.Namespace),
|
||||
Security: a.Security.With(other.Security),
|
||||
}
|
||||
}
|
||||
|
||||
func (a *Pod) Apply(template *core.PodTemplateSpec) error {
|
||||
if a == nil {
|
||||
return nil
|
||||
|
|
212
pkg/apis/scheduler/v1alpha1/pod/definition_test.go
Normal file
212
pkg/apis/scheduler/v1alpha1/pod/definition_test.go
Normal file
|
@ -0,0 +1,212 @@
|
|||
//
|
||||
// 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 pod
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
core "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/util/yaml"
|
||||
|
||||
schedulerPodResourcesApi "github.com/arangodb/kube-arangodb/pkg/apis/scheduler/v1alpha1/pod/resources"
|
||||
"github.com/arangodb/kube-arangodb/pkg/util"
|
||||
)
|
||||
|
||||
func applyPod(t *testing.T, template *core.PodTemplateSpec, ns ...*Pod) func(in func(t *testing.T, pod *core.PodTemplateSpec, podData *Pod)) {
|
||||
var i *Pod
|
||||
|
||||
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, podData *Pod)) {
|
||||
t.Run("Validate", func(t *testing.T) {
|
||||
if i != nil {
|
||||
in(t, template, i)
|
||||
} else {
|
||||
in(t, template, &Pod{})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func applyPodYAML(t *testing.T, template *core.PodTemplateSpec, ns ...string) func(in func(t *testing.T, pod *core.PodTemplateSpec, podData *Pod)) {
|
||||
elements := make([]*Pod, len(ns))
|
||||
|
||||
for id := range ns {
|
||||
var p Pod
|
||||
require.NoError(t, yaml.Unmarshal([]byte(ns[id]), &p))
|
||||
elements[id] = p.DeepCopy()
|
||||
}
|
||||
|
||||
return applyPod(t, template, elements...)
|
||||
}
|
||||
|
||||
func Test_Pod(t *testing.T) {
|
||||
t.Run("Nil", func(t *testing.T) {
|
||||
applyPod(t, nil)(func(t *testing.T, pod *core.PodTemplateSpec, _ *Pod) {
|
||||
require.Nil(t, pod.Spec.SecurityContext)
|
||||
require.Nil(t, pod.Spec.Affinity)
|
||||
})
|
||||
})
|
||||
t.Run("Empty template", func(t *testing.T) {
|
||||
applyPod(t, &core.PodTemplateSpec{})(func(t *testing.T, pod *core.PodTemplateSpec, _ *Pod) {
|
||||
require.Nil(t, pod.Spec.SecurityContext)
|
||||
require.Nil(t, pod.Spec.Affinity)
|
||||
})
|
||||
})
|
||||
t.Run("Add scheduling", func(t *testing.T) {
|
||||
applyPod(t, &core.PodTemplateSpec{}, &Pod{
|
||||
Security: &schedulerPodResourcesApi.Security{
|
||||
PodSecurityContext: &core.PodSecurityContext{
|
||||
RunAsGroup: util.NewType[int64](50),
|
||||
},
|
||||
},
|
||||
}, &Pod{
|
||||
Scheduling: &schedulerPodResourcesApi.Scheduling{
|
||||
NodeSelector: map[string]string{
|
||||
"A": "B",
|
||||
},
|
||||
},
|
||||
}, &Pod{
|
||||
Scheduling: &schedulerPodResourcesApi.Scheduling{
|
||||
NodeSelector: map[string]string{
|
||||
"A1": "B1",
|
||||
},
|
||||
},
|
||||
})(func(t *testing.T, pod *core.PodTemplateSpec, _ *Pod) {
|
||||
require.NotNil(t, pod.Spec.SecurityContext)
|
||||
require.NotNil(t, pod.Spec.SecurityContext.RunAsGroup)
|
||||
require.EqualValues(t, 50, *pod.Spec.SecurityContext.RunAsGroup)
|
||||
|
||||
require.NotNil(t, pod.Spec.NodeSelector)
|
||||
require.Len(t, pod.Spec.NodeSelector, 2)
|
||||
|
||||
require.Contains(t, pod.Spec.NodeSelector, "A")
|
||||
require.Equal(t, "B", pod.Spec.NodeSelector["A"])
|
||||
|
||||
require.Contains(t, pod.Spec.NodeSelector, "A1")
|
||||
require.Equal(t, "B1", pod.Spec.NodeSelector["A1"])
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Pod_YAML(t *testing.T) {
|
||||
t.Run("Nil", func(t *testing.T) {
|
||||
applyPodYAML(t, nil)(func(t *testing.T, _ *core.PodTemplateSpec, spec *Pod) {
|
||||
require.Nil(t, spec.Security)
|
||||
require.Nil(t, spec.Scheduling)
|
||||
require.Nil(t, spec.Namespace)
|
||||
})
|
||||
})
|
||||
t.Run("Empty template", func(t *testing.T) {
|
||||
applyPodYAML(t, &core.PodTemplateSpec{})(func(t *testing.T, pod *core.PodTemplateSpec, _ *Pod) {
|
||||
require.Nil(t, pod.Spec.SecurityContext)
|
||||
require.Nil(t, pod.Spec.Affinity)
|
||||
})
|
||||
})
|
||||
t.Run("Add nodeSelector", func(t *testing.T) {
|
||||
applyPodYAML(t, nil, `
|
||||
---
|
||||
|
||||
nodeSelector:
|
||||
A: B
|
||||
`)(func(t *testing.T, _ *core.PodTemplateSpec, spec *Pod) {
|
||||
require.Nil(t, spec.Security)
|
||||
require.NotNil(t, spec.Scheduling)
|
||||
require.Len(t, spec.Scheduling.NodeSelector, 1)
|
||||
require.Contains(t, spec.Scheduling.NodeSelector, "A")
|
||||
require.Equal(t, "B", spec.Scheduling.NodeSelector["A"])
|
||||
require.Nil(t, spec.Namespace)
|
||||
})
|
||||
})
|
||||
t.Run("Merge nodeSelector", func(t *testing.T) {
|
||||
applyPodYAML(t, nil, `
|
||||
---
|
||||
|
||||
nodeSelector:
|
||||
A: B
|
||||
`, `
|
||||
---
|
||||
|
||||
nodeSelector:
|
||||
C: D
|
||||
`)(func(t *testing.T, _ *core.PodTemplateSpec, spec *Pod) {
|
||||
require.Nil(t, spec.Security)
|
||||
require.NotNil(t, spec.Scheduling)
|
||||
require.Len(t, spec.Scheduling.NodeSelector, 2)
|
||||
require.Contains(t, spec.Scheduling.NodeSelector, "A")
|
||||
require.Equal(t, "B", spec.Scheduling.NodeSelector["A"])
|
||||
require.Contains(t, spec.Scheduling.NodeSelector, "C")
|
||||
require.Equal(t, "D", spec.Scheduling.NodeSelector["C"])
|
||||
require.Nil(t, spec.Namespace)
|
||||
})
|
||||
})
|
||||
t.Run("Add multiple values", func(t *testing.T) {
|
||||
applyPodYAML(t, nil, `
|
||||
---
|
||||
|
||||
nodeSelector:
|
||||
A: B
|
||||
podSecurityContext:
|
||||
runAsUser: 10
|
||||
hostPID: true
|
||||
`)(func(t *testing.T, pod *core.PodTemplateSpec, spec *Pod) {
|
||||
// Spec
|
||||
require.NotNil(t, spec.Security)
|
||||
require.NotNil(t, spec.Security.PodSecurityContext)
|
||||
require.NotNil(t, spec.Security.PodSecurityContext.RunAsUser)
|
||||
require.EqualValues(t, 10, *spec.Security.PodSecurityContext.RunAsUser)
|
||||
require.NotNil(t, spec.Scheduling)
|
||||
require.Len(t, spec.Scheduling.NodeSelector, 1)
|
||||
require.Contains(t, spec.Scheduling.NodeSelector, "A")
|
||||
require.Equal(t, "B", spec.Scheduling.NodeSelector["A"])
|
||||
require.NotNil(t, spec.Namespace)
|
||||
require.NotNil(t, spec.Namespace.HostPID)
|
||||
require.True(t, *spec.Namespace.HostPID)
|
||||
|
||||
// Pod
|
||||
require.NotNil(t, pod.Spec.SecurityContext)
|
||||
require.NotNil(t, pod.Spec.SecurityContext.RunAsUser)
|
||||
require.EqualValues(t, 10, *pod.Spec.SecurityContext.RunAsUser)
|
||||
require.NotNil(t, pod.Spec.NodeSelector)
|
||||
require.Len(t, pod.Spec.NodeSelector, 1)
|
||||
require.Contains(t, pod.Spec.NodeSelector, "A")
|
||||
require.Equal(t, "B", pod.Spec.NodeSelector["A"])
|
||||
require.Nil(t, pod.Spec.Affinity)
|
||||
require.NotNil(t, pod.Spec.HostPID)
|
||||
require.True(t, pod.Spec.HostPID)
|
||||
})
|
||||
})
|
||||
}
|
|
@ -23,9 +23,12 @@ package resources
|
|||
import (
|
||||
core "k8s.io/api/core/v1"
|
||||
|
||||
"github.com/arangodb/kube-arangodb/pkg/apis/scheduler/v1alpha1/interfaces"
|
||||
"github.com/arangodb/kube-arangodb/pkg/util"
|
||||
)
|
||||
|
||||
var _ interfaces.Pod[Namespace] = &Namespace{}
|
||||
|
||||
type Namespace struct {
|
||||
// HostNetwork requests Host network for this pod. Use the host's network namespace.
|
||||
// If this option is set, the ports that will be used must be specified.
|
||||
|
|
136
pkg/apis/scheduler/v1alpha1/pod/resources/namespace_test.go
Normal file
136
pkg/apis/scheduler/v1alpha1/pod/resources/namespace_test.go
Normal file
|
@ -0,0 +1,136 @@
|
|||
//
|
||||
// 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/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
core "k8s.io/api/core/v1"
|
||||
|
||||
"github.com/arangodb/kube-arangodb/pkg/util"
|
||||
)
|
||||
|
||||
func applyNamespace(t *testing.T, template *core.PodTemplateSpec, ns ...*Namespace) func(in func(t *testing.T, pod *core.PodTemplateSpec)) {
|
||||
var i *Namespace
|
||||
|
||||
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_Namespace(t *testing.T) {
|
||||
t.Run("Apply", func(t *testing.T) {
|
||||
applyNamespace(t, &core.PodTemplateSpec{})(func(t *testing.T, pod *core.PodTemplateSpec) {
|
||||
assert.False(t, pod.Spec.HostNetwork)
|
||||
assert.False(t, pod.Spec.HostPID)
|
||||
assert.False(t, pod.Spec.HostIPC)
|
||||
assert.Nil(t, pod.Spec.ShareProcessNamespace)
|
||||
})
|
||||
})
|
||||
t.Run("Apply nil", func(t *testing.T) {
|
||||
applyNamespace(t, nil)(func(t *testing.T, pod *core.PodTemplateSpec) {
|
||||
assert.False(t, pod.Spec.HostNetwork)
|
||||
assert.False(t, pod.Spec.HostPID)
|
||||
assert.False(t, pod.Spec.HostIPC)
|
||||
assert.Nil(t, pod.Spec.ShareProcessNamespace)
|
||||
})
|
||||
})
|
||||
t.Run("Apply with nils", func(t *testing.T) {
|
||||
applyNamespace(t, nil)(func(t *testing.T, pod *core.PodTemplateSpec) {
|
||||
assert.False(t, pod.Spec.HostNetwork)
|
||||
assert.False(t, pod.Spec.HostPID)
|
||||
assert.False(t, pod.Spec.HostIPC)
|
||||
assert.Nil(t, pod.Spec.ShareProcessNamespace)
|
||||
})
|
||||
})
|
||||
t.Run("Apply with template", func(t *testing.T) {
|
||||
applyNamespace(t, nil, &Namespace{
|
||||
HostPID: util.NewType(true),
|
||||
})(func(t *testing.T, pod *core.PodTemplateSpec) {
|
||||
assert.False(t, pod.Spec.HostNetwork)
|
||||
assert.True(t, pod.Spec.HostPID)
|
||||
assert.False(t, pod.Spec.HostIPC)
|
||||
assert.Nil(t, pod.Spec.ShareProcessNamespace)
|
||||
})
|
||||
})
|
||||
t.Run("Apply with template overrides", func(t *testing.T) {
|
||||
applyNamespace(t, nil, &Namespace{
|
||||
HostPID: util.NewType(true),
|
||||
}, &Namespace{
|
||||
HostNetwork: util.NewType(true),
|
||||
}, &Namespace{
|
||||
HostPID: util.NewType(false),
|
||||
})(func(t *testing.T, pod *core.PodTemplateSpec) {
|
||||
assert.True(t, pod.Spec.HostNetwork)
|
||||
assert.False(t, pod.Spec.HostPID)
|
||||
assert.False(t, pod.Spec.HostIPC)
|
||||
assert.Nil(t, pod.Spec.ShareProcessNamespace)
|
||||
})
|
||||
})
|
||||
t.Run("Apply with all", func(t *testing.T) {
|
||||
applyNamespace(t, nil, &Namespace{
|
||||
HostNetwork: util.NewType(true),
|
||||
HostPID: util.NewType(true),
|
||||
HostIPC: util.NewType(true),
|
||||
ShareProcessNamespace: util.NewType(true),
|
||||
})(func(t *testing.T, pod *core.PodTemplateSpec) {
|
||||
assert.True(t, pod.Spec.HostNetwork)
|
||||
assert.True(t, pod.Spec.HostPID)
|
||||
assert.True(t, pod.Spec.HostIPC)
|
||||
assert.NotNil(t, pod.Spec.ShareProcessNamespace)
|
||||
assert.True(t, *pod.Spec.ShareProcessNamespace)
|
||||
})
|
||||
})
|
||||
t.Run("Apply false", func(t *testing.T) {
|
||||
applyNamespace(t, nil, &Namespace{
|
||||
HostNetwork: util.NewType(true),
|
||||
HostPID: util.NewType(true),
|
||||
HostIPC: util.NewType(true),
|
||||
ShareProcessNamespace: util.NewType(false),
|
||||
})(func(t *testing.T, pod *core.PodTemplateSpec) {
|
||||
assert.True(t, pod.Spec.HostNetwork)
|
||||
assert.True(t, pod.Spec.HostPID)
|
||||
assert.True(t, pod.Spec.HostIPC)
|
||||
assert.NotNil(t, pod.Spec.ShareProcessNamespace)
|
||||
assert.False(t, *pod.Spec.ShareProcessNamespace)
|
||||
})
|
||||
})
|
||||
}
|
|
@ -23,6 +23,7 @@ package resources
|
|||
import (
|
||||
core "k8s.io/api/core/v1"
|
||||
|
||||
"github.com/arangodb/kube-arangodb/pkg/apis/scheduler/v1alpha1/interfaces"
|
||||
"github.com/arangodb/kube-arangodb/pkg/util"
|
||||
"github.com/arangodb/kube-arangodb/pkg/util/affinity"
|
||||
"github.com/arangodb/kube-arangodb/pkg/util/k8sutil/tolerations"
|
||||
|
@ -30,6 +31,8 @@ import (
|
|||
|
||||
type Tolerations []core.Toleration
|
||||
|
||||
var _ interfaces.Pod[Scheduling] = &Scheduling{}
|
||||
|
||||
type Scheduling struct {
|
||||
// NodeSelector is a selector that must be true for the workload to fit on a node.
|
||||
// +doc/link: Kubernetes docs|https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#nodeselector
|
||||
|
@ -65,8 +68,13 @@ func (s *Scheduling) Apply(template *core.PodTemplateSpec) error {
|
|||
}
|
||||
}
|
||||
|
||||
template.Spec.Affinity = s.Affinity.DeepCopy()
|
||||
template.Spec.Tolerations = s.Tolerations.DeepCopy()
|
||||
if s.Affinity != nil {
|
||||
if s.Affinity.NodeAffinity != nil || s.Affinity.PodAffinity != nil || s.Affinity.PodAntiAffinity != nil {
|
||||
template.Spec.Affinity = s.Affinity.DeepCopy()
|
||||
}
|
||||
}
|
||||
|
||||
template.Spec.Tolerations = tolerations.AddTolerationsIfNotFound(nil, s.Tolerations.DeepCopy()...)
|
||||
|
||||
template.Spec.SchedulerName = util.WithDefault(s.SchedulerName)
|
||||
|
||||
|
@ -126,17 +134,17 @@ func (s *Scheduling) With(other *Scheduling) *Scheduling {
|
|||
current.NodeSelector = new.NodeSelector
|
||||
} else if len(new.NodeSelector) > 0 {
|
||||
for k, v := range new.NodeSelector {
|
||||
other.NodeSelector[k] = v
|
||||
current.NodeSelector[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
// SchedulerName
|
||||
if new.SchedulerName != nil {
|
||||
other.SchedulerName = new.SchedulerName
|
||||
current.SchedulerName = new.SchedulerName
|
||||
}
|
||||
|
||||
// Tolerations
|
||||
new.Tolerations = tolerations.AddTolerationsIfNotFound(new.Tolerations, other.Tolerations...)
|
||||
current.Tolerations = tolerations.AddTolerationsIfNotFound(new.Tolerations, other.Tolerations...)
|
||||
|
||||
// Affinity
|
||||
current.Affinity = affinity.Merge(current.Affinity, new.Affinity)
|
||||
|
|
844
pkg/apis/scheduler/v1alpha1/pod/resources/scheduling_test.go
Normal file
844
pkg/apis/scheduler/v1alpha1/pod/resources/scheduling_test.go
Normal file
|
@ -0,0 +1,844 @@
|
|||
//
|
||||
// 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"
|
||||
|
||||
"github.com/arangodb/kube-arangodb/pkg/util"
|
||||
)
|
||||
|
||||
func applyScheduling(t *testing.T, template *core.PodTemplateSpec, ns ...*Scheduling) func(in func(t *testing.T, pod *core.PodTemplateSpec)) {
|
||||
var i *Scheduling
|
||||
|
||||
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_Scheduling_Affinity(t *testing.T) {
|
||||
t.Run("Nil", func(t *testing.T) {
|
||||
applyScheduling(t, nil)(func(t *testing.T, pod *core.PodTemplateSpec) {
|
||||
require.Nil(t, pod.Spec.Affinity)
|
||||
})
|
||||
})
|
||||
t.Run("Empty Template", func(t *testing.T) {
|
||||
applyScheduling(t, &core.PodTemplateSpec{}, &Scheduling{
|
||||
Affinity: &core.Affinity{},
|
||||
})(func(t *testing.T, pod *core.PodTemplateSpec) {
|
||||
require.Nil(t, pod.Spec.Affinity)
|
||||
})
|
||||
})
|
||||
t.Run("Empty", func(t *testing.T) {
|
||||
applyScheduling(t, nil, &Scheduling{
|
||||
Affinity: &core.Affinity{},
|
||||
})(func(t *testing.T, pod *core.PodTemplateSpec) {
|
||||
require.Nil(t, pod.Spec.Affinity)
|
||||
})
|
||||
})
|
||||
t.Run("PodAffinity", func(t *testing.T) {
|
||||
t.Run("Empty", func(t *testing.T) {
|
||||
applyScheduling(t, nil, &Scheduling{
|
||||
Affinity: &core.Affinity{
|
||||
PodAffinity: &core.PodAffinity{},
|
||||
},
|
||||
})(func(t *testing.T, pod *core.PodTemplateSpec) {
|
||||
require.NotNil(t, pod.Spec.Affinity)
|
||||
require.NotNil(t, pod.Spec.Affinity.PodAffinity)
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("One", func(t *testing.T) {
|
||||
applyScheduling(t, nil, &Scheduling{
|
||||
Affinity: &core.Affinity{
|
||||
PodAffinity: &core.PodAffinity{
|
||||
RequiredDuringSchedulingIgnoredDuringExecution: []core.PodAffinityTerm{
|
||||
{
|
||||
TopologyKey: "test",
|
||||
},
|
||||
},
|
||||
PreferredDuringSchedulingIgnoredDuringExecution: []core.WeightedPodAffinityTerm{
|
||||
{
|
||||
PodAffinityTerm: core.PodAffinityTerm{
|
||||
TopologyKey: "test",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})(func(t *testing.T, pod *core.PodTemplateSpec) {
|
||||
require.NotNil(t, pod.Spec.Affinity)
|
||||
require.NotNil(t, pod.Spec.Affinity.PodAffinity)
|
||||
|
||||
require.Len(t, pod.Spec.Affinity.PodAffinity.RequiredDuringSchedulingIgnoredDuringExecution, 1)
|
||||
|
||||
require.EqualValues(t, "test", pod.Spec.Affinity.PodAffinity.RequiredDuringSchedulingIgnoredDuringExecution[0].TopologyKey)
|
||||
|
||||
require.Len(t, pod.Spec.Affinity.PodAffinity.PreferredDuringSchedulingIgnoredDuringExecution, 1)
|
||||
|
||||
require.EqualValues(t, "test", pod.Spec.Affinity.PodAffinity.PreferredDuringSchedulingIgnoredDuringExecution[0].PodAffinityTerm.TopologyKey)
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("Merge - with Empty", func(t *testing.T) {
|
||||
applyScheduling(t, nil, &Scheduling{
|
||||
Affinity: &core.Affinity{
|
||||
PodAffinity: &core.PodAffinity{
|
||||
RequiredDuringSchedulingIgnoredDuringExecution: []core.PodAffinityTerm{
|
||||
{
|
||||
TopologyKey: "test",
|
||||
},
|
||||
},
|
||||
PreferredDuringSchedulingIgnoredDuringExecution: []core.WeightedPodAffinityTerm{
|
||||
{
|
||||
PodAffinityTerm: core.PodAffinityTerm{
|
||||
TopologyKey: "test",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}, &Scheduling{
|
||||
Affinity: &core.Affinity{
|
||||
PodAffinity: &core.PodAffinity{},
|
||||
},
|
||||
})(func(t *testing.T, pod *core.PodTemplateSpec) {
|
||||
require.NotNil(t, pod.Spec.Affinity)
|
||||
require.NotNil(t, pod.Spec.Affinity.PodAffinity)
|
||||
|
||||
require.Len(t, pod.Spec.Affinity.PodAffinity.RequiredDuringSchedulingIgnoredDuringExecution, 1)
|
||||
|
||||
require.EqualValues(t, "test", pod.Spec.Affinity.PodAffinity.RequiredDuringSchedulingIgnoredDuringExecution[0].TopologyKey)
|
||||
|
||||
require.Len(t, pod.Spec.Affinity.PodAffinity.PreferredDuringSchedulingIgnoredDuringExecution, 1)
|
||||
|
||||
require.EqualValues(t, "test", pod.Spec.Affinity.PodAffinity.PreferredDuringSchedulingIgnoredDuringExecution[0].PodAffinityTerm.TopologyKey)
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("Merge", func(t *testing.T) {
|
||||
applyScheduling(t, nil, &Scheduling{
|
||||
Affinity: &core.Affinity{
|
||||
PodAffinity: &core.PodAffinity{
|
||||
RequiredDuringSchedulingIgnoredDuringExecution: []core.PodAffinityTerm{
|
||||
{
|
||||
TopologyKey: "test",
|
||||
},
|
||||
},
|
||||
PreferredDuringSchedulingIgnoredDuringExecution: []core.WeightedPodAffinityTerm{
|
||||
{
|
||||
PodAffinityTerm: core.PodAffinityTerm{
|
||||
TopologyKey: "test",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}, &Scheduling{
|
||||
Affinity: &core.Affinity{
|
||||
PodAffinity: &core.PodAffinity{
|
||||
RequiredDuringSchedulingIgnoredDuringExecution: []core.PodAffinityTerm{
|
||||
{
|
||||
TopologyKey: "test2",
|
||||
},
|
||||
},
|
||||
PreferredDuringSchedulingIgnoredDuringExecution: []core.WeightedPodAffinityTerm{
|
||||
{
|
||||
PodAffinityTerm: core.PodAffinityTerm{
|
||||
TopologyKey: "test2",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})(func(t *testing.T, pod *core.PodTemplateSpec) {
|
||||
require.NotNil(t, pod.Spec.Affinity)
|
||||
require.NotNil(t, pod.Spec.Affinity.PodAffinity)
|
||||
|
||||
require.Len(t, pod.Spec.Affinity.PodAffinity.RequiredDuringSchedulingIgnoredDuringExecution, 2)
|
||||
|
||||
require.EqualValues(t, "test", pod.Spec.Affinity.PodAffinity.RequiredDuringSchedulingIgnoredDuringExecution[0].TopologyKey)
|
||||
require.EqualValues(t, "test2", pod.Spec.Affinity.PodAffinity.RequiredDuringSchedulingIgnoredDuringExecution[1].TopologyKey)
|
||||
|
||||
require.Len(t, pod.Spec.Affinity.PodAffinity.PreferredDuringSchedulingIgnoredDuringExecution, 2)
|
||||
|
||||
require.EqualValues(t, "test", pod.Spec.Affinity.PodAffinity.PreferredDuringSchedulingIgnoredDuringExecution[0].PodAffinityTerm.TopologyKey)
|
||||
require.EqualValues(t, "test2", pod.Spec.Affinity.PodAffinity.PreferredDuringSchedulingIgnoredDuringExecution[1].PodAffinityTerm.TopologyKey)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("PodAntiAffinity", func(t *testing.T) {
|
||||
t.Run("Empty", func(t *testing.T) {
|
||||
applyScheduling(t, nil, &Scheduling{
|
||||
Affinity: &core.Affinity{
|
||||
PodAntiAffinity: &core.PodAntiAffinity{},
|
||||
},
|
||||
})(func(t *testing.T, pod *core.PodTemplateSpec) {
|
||||
require.NotNil(t, pod.Spec.Affinity)
|
||||
require.NotNil(t, pod.Spec.Affinity.PodAntiAffinity)
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("One", func(t *testing.T) {
|
||||
applyScheduling(t, nil, &Scheduling{
|
||||
Affinity: &core.Affinity{
|
||||
PodAntiAffinity: &core.PodAntiAffinity{
|
||||
RequiredDuringSchedulingIgnoredDuringExecution: []core.PodAffinityTerm{
|
||||
{
|
||||
TopologyKey: "test",
|
||||
},
|
||||
},
|
||||
PreferredDuringSchedulingIgnoredDuringExecution: []core.WeightedPodAffinityTerm{
|
||||
{
|
||||
PodAffinityTerm: core.PodAffinityTerm{
|
||||
TopologyKey: "test",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})(func(t *testing.T, pod *core.PodTemplateSpec) {
|
||||
require.NotNil(t, pod.Spec.Affinity)
|
||||
require.NotNil(t, pod.Spec.Affinity.PodAntiAffinity)
|
||||
|
||||
require.Len(t, pod.Spec.Affinity.PodAntiAffinity.RequiredDuringSchedulingIgnoredDuringExecution, 1)
|
||||
|
||||
require.EqualValues(t, "test", pod.Spec.Affinity.PodAntiAffinity.RequiredDuringSchedulingIgnoredDuringExecution[0].TopologyKey)
|
||||
|
||||
require.Len(t, pod.Spec.Affinity.PodAntiAffinity.PreferredDuringSchedulingIgnoredDuringExecution, 1)
|
||||
|
||||
require.EqualValues(t, "test", pod.Spec.Affinity.PodAntiAffinity.PreferredDuringSchedulingIgnoredDuringExecution[0].PodAffinityTerm.TopologyKey)
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("Merge - with Empty", func(t *testing.T) {
|
||||
applyScheduling(t, nil, &Scheduling{
|
||||
Affinity: &core.Affinity{
|
||||
PodAntiAffinity: &core.PodAntiAffinity{
|
||||
RequiredDuringSchedulingIgnoredDuringExecution: []core.PodAffinityTerm{
|
||||
{
|
||||
TopologyKey: "test",
|
||||
},
|
||||
},
|
||||
PreferredDuringSchedulingIgnoredDuringExecution: []core.WeightedPodAffinityTerm{
|
||||
{
|
||||
PodAffinityTerm: core.PodAffinityTerm{
|
||||
TopologyKey: "test",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}, &Scheduling{
|
||||
Affinity: &core.Affinity{
|
||||
PodAntiAffinity: &core.PodAntiAffinity{},
|
||||
},
|
||||
})(func(t *testing.T, pod *core.PodTemplateSpec) {
|
||||
require.NotNil(t, pod.Spec.Affinity)
|
||||
require.NotNil(t, pod.Spec.Affinity.PodAntiAffinity)
|
||||
|
||||
require.Len(t, pod.Spec.Affinity.PodAntiAffinity.RequiredDuringSchedulingIgnoredDuringExecution, 1)
|
||||
|
||||
require.EqualValues(t, "test", pod.Spec.Affinity.PodAntiAffinity.RequiredDuringSchedulingIgnoredDuringExecution[0].TopologyKey)
|
||||
|
||||
require.Len(t, pod.Spec.Affinity.PodAntiAffinity.PreferredDuringSchedulingIgnoredDuringExecution, 1)
|
||||
|
||||
require.EqualValues(t, "test", pod.Spec.Affinity.PodAntiAffinity.PreferredDuringSchedulingIgnoredDuringExecution[0].PodAffinityTerm.TopologyKey)
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("Merge", func(t *testing.T) {
|
||||
applyScheduling(t, nil, &Scheduling{
|
||||
Affinity: &core.Affinity{
|
||||
PodAntiAffinity: &core.PodAntiAffinity{
|
||||
RequiredDuringSchedulingIgnoredDuringExecution: []core.PodAffinityTerm{
|
||||
{
|
||||
TopologyKey: "test",
|
||||
},
|
||||
},
|
||||
PreferredDuringSchedulingIgnoredDuringExecution: []core.WeightedPodAffinityTerm{
|
||||
{
|
||||
PodAffinityTerm: core.PodAffinityTerm{
|
||||
TopologyKey: "test",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}, &Scheduling{
|
||||
Affinity: &core.Affinity{
|
||||
PodAntiAffinity: &core.PodAntiAffinity{
|
||||
RequiredDuringSchedulingIgnoredDuringExecution: []core.PodAffinityTerm{
|
||||
{
|
||||
TopologyKey: "test2",
|
||||
},
|
||||
},
|
||||
PreferredDuringSchedulingIgnoredDuringExecution: []core.WeightedPodAffinityTerm{
|
||||
{
|
||||
PodAffinityTerm: core.PodAffinityTerm{
|
||||
TopologyKey: "test2",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})(func(t *testing.T, pod *core.PodTemplateSpec) {
|
||||
require.NotNil(t, pod.Spec.Affinity)
|
||||
require.NotNil(t, pod.Spec.Affinity.PodAntiAffinity)
|
||||
|
||||
require.Len(t, pod.Spec.Affinity.PodAntiAffinity.RequiredDuringSchedulingIgnoredDuringExecution, 2)
|
||||
|
||||
require.EqualValues(t, "test", pod.Spec.Affinity.PodAntiAffinity.RequiredDuringSchedulingIgnoredDuringExecution[0].TopologyKey)
|
||||
require.EqualValues(t, "test2", pod.Spec.Affinity.PodAntiAffinity.RequiredDuringSchedulingIgnoredDuringExecution[1].TopologyKey)
|
||||
|
||||
require.Len(t, pod.Spec.Affinity.PodAntiAffinity.PreferredDuringSchedulingIgnoredDuringExecution, 2)
|
||||
|
||||
require.EqualValues(t, "test", pod.Spec.Affinity.PodAntiAffinity.PreferredDuringSchedulingIgnoredDuringExecution[0].PodAffinityTerm.TopologyKey)
|
||||
require.EqualValues(t, "test2", pod.Spec.Affinity.PodAntiAffinity.PreferredDuringSchedulingIgnoredDuringExecution[1].PodAffinityTerm.TopologyKey)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("NodeAffinity", func(t *testing.T) {
|
||||
t.Run("Empty", func(t *testing.T) {
|
||||
applyScheduling(t, nil, &Scheduling{
|
||||
Affinity: &core.Affinity{
|
||||
NodeAffinity: &core.NodeAffinity{},
|
||||
},
|
||||
})(func(t *testing.T, pod *core.PodTemplateSpec) {
|
||||
require.NotNil(t, pod.Spec.Affinity)
|
||||
require.NotNil(t, pod.Spec.Affinity.NodeAffinity)
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("One", func(t *testing.T) {
|
||||
applyScheduling(t, nil, &Scheduling{
|
||||
Affinity: &core.Affinity{
|
||||
NodeAffinity: &core.NodeAffinity{
|
||||
RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{
|
||||
NodeSelectorTerms: []core.NodeSelectorTerm{
|
||||
{
|
||||
MatchExpressions: []core.NodeSelectorRequirement{
|
||||
{
|
||||
Key: "term1",
|
||||
},
|
||||
},
|
||||
MatchFields: []core.NodeSelectorRequirement{
|
||||
{
|
||||
Key: "term1",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
MatchExpressions: []core.NodeSelectorRequirement{
|
||||
{
|
||||
Key: "term2",
|
||||
},
|
||||
},
|
||||
MatchFields: []core.NodeSelectorRequirement{
|
||||
{
|
||||
Key: "term2",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
PreferredDuringSchedulingIgnoredDuringExecution: []core.PreferredSchedulingTerm{
|
||||
{
|
||||
Preference: core.NodeSelectorTerm{
|
||||
MatchExpressions: []core.NodeSelectorRequirement{
|
||||
{
|
||||
Key: "term1",
|
||||
},
|
||||
},
|
||||
MatchFields: []core.NodeSelectorRequirement{
|
||||
{
|
||||
Key: "term1",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Preference: core.NodeSelectorTerm{
|
||||
MatchExpressions: []core.NodeSelectorRequirement{
|
||||
{
|
||||
Key: "term2",
|
||||
},
|
||||
},
|
||||
MatchFields: []core.NodeSelectorRequirement{
|
||||
{
|
||||
Key: "term2",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})(func(t *testing.T, pod *core.PodTemplateSpec) {
|
||||
require.NotNil(t, pod.Spec.Affinity)
|
||||
require.NotNil(t, pod.Spec.Affinity.NodeAffinity)
|
||||
|
||||
require.Len(t, pod.Spec.Affinity.NodeAffinity.PreferredDuringSchedulingIgnoredDuringExecution, 2)
|
||||
|
||||
require.Len(t, pod.Spec.Affinity.NodeAffinity.PreferredDuringSchedulingIgnoredDuringExecution[0].Preference.MatchFields, 1)
|
||||
require.EqualValues(t, "term1", pod.Spec.Affinity.NodeAffinity.PreferredDuringSchedulingIgnoredDuringExecution[0].Preference.MatchFields[0].Key)
|
||||
require.Len(t, pod.Spec.Affinity.NodeAffinity.PreferredDuringSchedulingIgnoredDuringExecution[0].Preference.MatchExpressions, 1)
|
||||
require.EqualValues(t, "term1", pod.Spec.Affinity.NodeAffinity.PreferredDuringSchedulingIgnoredDuringExecution[0].Preference.MatchExpressions[0].Key)
|
||||
|
||||
require.Len(t, pod.Spec.Affinity.NodeAffinity.PreferredDuringSchedulingIgnoredDuringExecution[1].Preference.MatchFields, 1)
|
||||
require.EqualValues(t, "term2", pod.Spec.Affinity.NodeAffinity.PreferredDuringSchedulingIgnoredDuringExecution[1].Preference.MatchFields[0].Key)
|
||||
require.Len(t, pod.Spec.Affinity.NodeAffinity.PreferredDuringSchedulingIgnoredDuringExecution[1].Preference.MatchExpressions, 1)
|
||||
require.EqualValues(t, "term2", pod.Spec.Affinity.NodeAffinity.PreferredDuringSchedulingIgnoredDuringExecution[1].Preference.MatchExpressions[0].Key)
|
||||
|
||||
require.NotNil(t, pod.Spec.Affinity.NodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution)
|
||||
require.Len(t, pod.Spec.Affinity.NodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution.NodeSelectorTerms, 2)
|
||||
|
||||
require.Len(t, pod.Spec.Affinity.NodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution.NodeSelectorTerms[0].MatchFields, 1)
|
||||
require.EqualValues(t, "term1", pod.Spec.Affinity.NodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution.NodeSelectorTerms[0].MatchFields[0].Key)
|
||||
require.Len(t, pod.Spec.Affinity.NodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution.NodeSelectorTerms[0].MatchExpressions, 1)
|
||||
require.EqualValues(t, "term1", pod.Spec.Affinity.NodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution.NodeSelectorTerms[0].MatchExpressions[0].Key)
|
||||
|
||||
require.Len(t, pod.Spec.Affinity.NodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution.NodeSelectorTerms[1].MatchFields, 1)
|
||||
require.EqualValues(t, "term2", pod.Spec.Affinity.NodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution.NodeSelectorTerms[1].MatchFields[0].Key)
|
||||
require.Len(t, pod.Spec.Affinity.NodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution.NodeSelectorTerms[1].MatchExpressions, 1)
|
||||
require.EqualValues(t, "term2", pod.Spec.Affinity.NodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution.NodeSelectorTerms[1].MatchExpressions[0].Key)
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("Merge", func(t *testing.T) {
|
||||
applyScheduling(t, nil, &Scheduling{
|
||||
Affinity: &core.Affinity{
|
||||
NodeAffinity: &core.NodeAffinity{
|
||||
RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{
|
||||
NodeSelectorTerms: []core.NodeSelectorTerm{
|
||||
{
|
||||
MatchExpressions: []core.NodeSelectorRequirement{
|
||||
{
|
||||
Key: "term1",
|
||||
},
|
||||
},
|
||||
MatchFields: []core.NodeSelectorRequirement{
|
||||
{
|
||||
Key: "term1",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
MatchExpressions: []core.NodeSelectorRequirement{
|
||||
{
|
||||
Key: "term2",
|
||||
},
|
||||
},
|
||||
MatchFields: []core.NodeSelectorRequirement{
|
||||
{
|
||||
Key: "term2",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
PreferredDuringSchedulingIgnoredDuringExecution: []core.PreferredSchedulingTerm{
|
||||
{
|
||||
Preference: core.NodeSelectorTerm{
|
||||
MatchExpressions: []core.NodeSelectorRequirement{
|
||||
{
|
||||
Key: "term1",
|
||||
},
|
||||
},
|
||||
MatchFields: []core.NodeSelectorRequirement{
|
||||
{
|
||||
Key: "term1",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Preference: core.NodeSelectorTerm{
|
||||
MatchExpressions: []core.NodeSelectorRequirement{
|
||||
{
|
||||
Key: "term2",
|
||||
},
|
||||
},
|
||||
MatchFields: []core.NodeSelectorRequirement{
|
||||
{
|
||||
Key: "term2",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}, &Scheduling{
|
||||
Affinity: &core.Affinity{
|
||||
NodeAffinity: &core.NodeAffinity{
|
||||
RequiredDuringSchedulingIgnoredDuringExecution: &core.NodeSelector{
|
||||
NodeSelectorTerms: []core.NodeSelectorTerm{
|
||||
{
|
||||
MatchExpressions: []core.NodeSelectorRequirement{
|
||||
{
|
||||
Key: "term3",
|
||||
},
|
||||
},
|
||||
MatchFields: []core.NodeSelectorRequirement{
|
||||
{
|
||||
Key: "term3",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
MatchExpressions: []core.NodeSelectorRequirement{
|
||||
{
|
||||
Key: "term4",
|
||||
},
|
||||
},
|
||||
MatchFields: []core.NodeSelectorRequirement{
|
||||
{
|
||||
Key: "term4",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
PreferredDuringSchedulingIgnoredDuringExecution: []core.PreferredSchedulingTerm{
|
||||
{
|
||||
Preference: core.NodeSelectorTerm{
|
||||
MatchExpressions: []core.NodeSelectorRequirement{
|
||||
{
|
||||
Key: "term3",
|
||||
},
|
||||
},
|
||||
MatchFields: []core.NodeSelectorRequirement{
|
||||
{
|
||||
Key: "term3",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Preference: core.NodeSelectorTerm{
|
||||
MatchExpressions: []core.NodeSelectorRequirement{
|
||||
{
|
||||
Key: "term4",
|
||||
},
|
||||
},
|
||||
MatchFields: []core.NodeSelectorRequirement{
|
||||
{
|
||||
Key: "term4",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})(func(t *testing.T, pod *core.PodTemplateSpec) {
|
||||
require.NotNil(t, pod.Spec.Affinity)
|
||||
require.NotNil(t, pod.Spec.Affinity.NodeAffinity)
|
||||
|
||||
require.Len(t, pod.Spec.Affinity.NodeAffinity.PreferredDuringSchedulingIgnoredDuringExecution, 4)
|
||||
|
||||
require.Len(t, pod.Spec.Affinity.NodeAffinity.PreferredDuringSchedulingIgnoredDuringExecution[0].Preference.MatchFields, 1)
|
||||
require.EqualValues(t, "term1", pod.Spec.Affinity.NodeAffinity.PreferredDuringSchedulingIgnoredDuringExecution[0].Preference.MatchFields[0].Key)
|
||||
require.Len(t, pod.Spec.Affinity.NodeAffinity.PreferredDuringSchedulingIgnoredDuringExecution[0].Preference.MatchExpressions, 1)
|
||||
require.EqualValues(t, "term1", pod.Spec.Affinity.NodeAffinity.PreferredDuringSchedulingIgnoredDuringExecution[0].Preference.MatchExpressions[0].Key)
|
||||
|
||||
require.Len(t, pod.Spec.Affinity.NodeAffinity.PreferredDuringSchedulingIgnoredDuringExecution[1].Preference.MatchFields, 1)
|
||||
require.EqualValues(t, "term2", pod.Spec.Affinity.NodeAffinity.PreferredDuringSchedulingIgnoredDuringExecution[1].Preference.MatchFields[0].Key)
|
||||
require.Len(t, pod.Spec.Affinity.NodeAffinity.PreferredDuringSchedulingIgnoredDuringExecution[1].Preference.MatchExpressions, 1)
|
||||
require.EqualValues(t, "term2", pod.Spec.Affinity.NodeAffinity.PreferredDuringSchedulingIgnoredDuringExecution[1].Preference.MatchExpressions[0].Key)
|
||||
|
||||
require.NotNil(t, pod.Spec.Affinity.NodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution)
|
||||
require.Len(t, pod.Spec.Affinity.NodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution.NodeSelectorTerms, 4)
|
||||
|
||||
require.Len(t, pod.Spec.Affinity.NodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution.NodeSelectorTerms[0].MatchFields, 2)
|
||||
require.EqualValues(t, "term1", pod.Spec.Affinity.NodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution.NodeSelectorTerms[0].MatchFields[0].Key)
|
||||
require.EqualValues(t, "term3", pod.Spec.Affinity.NodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution.NodeSelectorTerms[0].MatchFields[1].Key)
|
||||
require.Len(t, pod.Spec.Affinity.NodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution.NodeSelectorTerms[0].MatchExpressions, 2)
|
||||
require.EqualValues(t, "term1", pod.Spec.Affinity.NodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution.NodeSelectorTerms[0].MatchExpressions[0].Key)
|
||||
require.EqualValues(t, "term3", pod.Spec.Affinity.NodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution.NodeSelectorTerms[0].MatchExpressions[1].Key)
|
||||
|
||||
require.Len(t, pod.Spec.Affinity.NodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution.NodeSelectorTerms[1].MatchFields, 2)
|
||||
require.EqualValues(t, "term1", pod.Spec.Affinity.NodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution.NodeSelectorTerms[1].MatchFields[0].Key)
|
||||
require.EqualValues(t, "term4", pod.Spec.Affinity.NodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution.NodeSelectorTerms[1].MatchFields[1].Key)
|
||||
require.Len(t, pod.Spec.Affinity.NodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution.NodeSelectorTerms[1].MatchExpressions, 2)
|
||||
require.EqualValues(t, "term1", pod.Spec.Affinity.NodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution.NodeSelectorTerms[1].MatchExpressions[0].Key)
|
||||
require.EqualValues(t, "term4", pod.Spec.Affinity.NodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution.NodeSelectorTerms[1].MatchExpressions[1].Key)
|
||||
|
||||
require.Len(t, pod.Spec.Affinity.NodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution.NodeSelectorTerms[2].MatchFields, 2)
|
||||
require.EqualValues(t, "term2", pod.Spec.Affinity.NodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution.NodeSelectorTerms[2].MatchFields[0].Key)
|
||||
require.EqualValues(t, "term3", pod.Spec.Affinity.NodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution.NodeSelectorTerms[2].MatchFields[1].Key)
|
||||
require.Len(t, pod.Spec.Affinity.NodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution.NodeSelectorTerms[2].MatchExpressions, 2)
|
||||
require.EqualValues(t, "term2", pod.Spec.Affinity.NodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution.NodeSelectorTerms[2].MatchExpressions[0].Key)
|
||||
require.EqualValues(t, "term3", pod.Spec.Affinity.NodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution.NodeSelectorTerms[2].MatchExpressions[1].Key)
|
||||
|
||||
require.Len(t, pod.Spec.Affinity.NodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution.NodeSelectorTerms[3].MatchFields, 2)
|
||||
require.EqualValues(t, "term2", pod.Spec.Affinity.NodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution.NodeSelectorTerms[3].MatchFields[0].Key)
|
||||
require.EqualValues(t, "term4", pod.Spec.Affinity.NodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution.NodeSelectorTerms[3].MatchFields[1].Key)
|
||||
require.Len(t, pod.Spec.Affinity.NodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution.NodeSelectorTerms[3].MatchExpressions, 2)
|
||||
require.EqualValues(t, "term2", pod.Spec.Affinity.NodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution.NodeSelectorTerms[3].MatchExpressions[0].Key)
|
||||
require.EqualValues(t, "term4", pod.Spec.Affinity.NodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution.NodeSelectorTerms[3].MatchExpressions[1].Key)
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Scheduling_Tolerations(t *testing.T) {
|
||||
t.Run("Nil", func(t *testing.T) {
|
||||
applyScheduling(t, nil)(func(t *testing.T, pod *core.PodTemplateSpec) {
|
||||
require.Len(t, pod.Spec.Tolerations, 0)
|
||||
})
|
||||
})
|
||||
t.Run("Empty", func(t *testing.T) {
|
||||
applyScheduling(t, nil, &Scheduling{
|
||||
Tolerations: Tolerations{},
|
||||
})(func(t *testing.T, pod *core.PodTemplateSpec) {
|
||||
require.Len(t, pod.Spec.Tolerations, 0)
|
||||
})
|
||||
})
|
||||
t.Run("Single", func(t *testing.T) {
|
||||
applyScheduling(t, nil, &Scheduling{
|
||||
Tolerations: Tolerations{
|
||||
{
|
||||
Operator: core.TolerationOpExists,
|
||||
},
|
||||
},
|
||||
})(func(t *testing.T, pod *core.PodTemplateSpec) {
|
||||
require.Len(t, pod.Spec.Tolerations, 1)
|
||||
|
||||
require.EqualValues(t, core.TolerationOpExists, pod.Spec.Tolerations[0].Operator)
|
||||
require.EqualValues(t, "", pod.Spec.Tolerations[0].Key)
|
||||
require.EqualValues(t, "", pod.Spec.Tolerations[0].Value)
|
||||
require.EqualValues(t, "", pod.Spec.Tolerations[0].Effect)
|
||||
require.Nil(t, pod.Spec.Tolerations[0].TolerationSeconds)
|
||||
})
|
||||
})
|
||||
t.Run("Single - With Grace", func(t *testing.T) {
|
||||
applyScheduling(t, nil, &Scheduling{
|
||||
Tolerations: Tolerations{
|
||||
{
|
||||
Operator: core.TolerationOpExists,
|
||||
TolerationSeconds: util.NewType[int64](5),
|
||||
},
|
||||
},
|
||||
})(func(t *testing.T, pod *core.PodTemplateSpec) {
|
||||
require.Len(t, pod.Spec.Tolerations, 1)
|
||||
|
||||
require.EqualValues(t, core.TolerationOpExists, pod.Spec.Tolerations[0].Operator)
|
||||
require.EqualValues(t, "", pod.Spec.Tolerations[0].Key)
|
||||
require.EqualValues(t, "", pod.Spec.Tolerations[0].Value)
|
||||
require.EqualValues(t, "", pod.Spec.Tolerations[0].Effect)
|
||||
require.NotNil(t, pod.Spec.Tolerations[0].TolerationSeconds)
|
||||
require.EqualValues(t, 5, *pod.Spec.Tolerations[0].TolerationSeconds)
|
||||
})
|
||||
})
|
||||
t.Run("One - merge within spec", func(t *testing.T) {
|
||||
applyScheduling(t, nil, &Scheduling{
|
||||
Tolerations: Tolerations{
|
||||
{
|
||||
Operator: core.TolerationOpExists,
|
||||
},
|
||||
{
|
||||
Operator: core.TolerationOpExists,
|
||||
TolerationSeconds: util.NewType[int64](5),
|
||||
},
|
||||
},
|
||||
})(func(t *testing.T, pod *core.PodTemplateSpec) {
|
||||
require.Len(t, pod.Spec.Tolerations, 1)
|
||||
|
||||
require.EqualValues(t, core.TolerationOpExists, pod.Spec.Tolerations[0].Operator)
|
||||
require.EqualValues(t, "", pod.Spec.Tolerations[0].Key)
|
||||
require.EqualValues(t, "", pod.Spec.Tolerations[0].Value)
|
||||
require.EqualValues(t, "", pod.Spec.Tolerations[0].Effect)
|
||||
require.NotNil(t, pod.Spec.Tolerations[0].TolerationSeconds)
|
||||
require.EqualValues(t, 5, *pod.Spec.Tolerations[0].TolerationSeconds)
|
||||
})
|
||||
})
|
||||
t.Run("Multi - Update", func(t *testing.T) {
|
||||
applyScheduling(t, nil, &Scheduling{
|
||||
Tolerations: Tolerations{
|
||||
{
|
||||
Operator: core.TolerationOpExists,
|
||||
},
|
||||
},
|
||||
}, &Scheduling{
|
||||
Tolerations: Tolerations{
|
||||
{
|
||||
Operator: core.TolerationOpExists,
|
||||
TolerationSeconds: util.NewType[int64](5),
|
||||
},
|
||||
},
|
||||
})(func(t *testing.T, pod *core.PodTemplateSpec) {
|
||||
require.Len(t, pod.Spec.Tolerations, 1)
|
||||
|
||||
require.EqualValues(t, core.TolerationOpExists, pod.Spec.Tolerations[0].Operator)
|
||||
require.EqualValues(t, "", pod.Spec.Tolerations[0].Key)
|
||||
require.EqualValues(t, "", pod.Spec.Tolerations[0].Value)
|
||||
require.EqualValues(t, "", pod.Spec.Tolerations[0].Effect)
|
||||
require.NotNil(t, pod.Spec.Tolerations[0].TolerationSeconds)
|
||||
require.EqualValues(t, 5, *pod.Spec.Tolerations[0].TolerationSeconds)
|
||||
})
|
||||
})
|
||||
t.Run("Multi - Update", func(t *testing.T) {
|
||||
applyScheduling(t, nil, &Scheduling{
|
||||
Tolerations: Tolerations{
|
||||
{
|
||||
Operator: core.TolerationOpExists,
|
||||
TolerationSeconds: util.NewType[int64](5),
|
||||
},
|
||||
},
|
||||
}, &Scheduling{
|
||||
Tolerations: Tolerations{
|
||||
{
|
||||
Operator: core.TolerationOpExists,
|
||||
},
|
||||
},
|
||||
})(func(t *testing.T, pod *core.PodTemplateSpec) {
|
||||
require.Len(t, pod.Spec.Tolerations, 1)
|
||||
|
||||
require.EqualValues(t, core.TolerationOpExists, pod.Spec.Tolerations[0].Operator)
|
||||
require.EqualValues(t, "", pod.Spec.Tolerations[0].Key)
|
||||
require.EqualValues(t, "", pod.Spec.Tolerations[0].Value)
|
||||
require.EqualValues(t, "", pod.Spec.Tolerations[0].Effect)
|
||||
require.Nil(t, pod.Spec.Tolerations[0].TolerationSeconds)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Scheduling_NodeSelector(t *testing.T) {
|
||||
t.Run("Nil", func(t *testing.T) {
|
||||
applyScheduling(t, nil)(func(t *testing.T, pod *core.PodTemplateSpec) {
|
||||
require.Len(t, pod.Spec.NodeSelector, 0)
|
||||
})
|
||||
})
|
||||
t.Run("Empty", func(t *testing.T) {
|
||||
applyScheduling(t, nil, &Scheduling{
|
||||
NodeSelector: map[string]string{},
|
||||
})(func(t *testing.T, pod *core.PodTemplateSpec) {
|
||||
require.Len(t, pod.Spec.NodeSelector, 0)
|
||||
})
|
||||
})
|
||||
t.Run("Selector", func(t *testing.T) {
|
||||
applyScheduling(t, nil, &Scheduling{
|
||||
NodeSelector: map[string]string{
|
||||
"1": "1",
|
||||
},
|
||||
})(func(t *testing.T, pod *core.PodTemplateSpec) {
|
||||
require.Len(t, pod.Spec.NodeSelector, 1)
|
||||
|
||||
require.Contains(t, pod.Spec.NodeSelector, "1")
|
||||
require.EqualValues(t, pod.Spec.NodeSelector["1"], "1")
|
||||
})
|
||||
})
|
||||
t.Run("Append", func(t *testing.T) {
|
||||
applyScheduling(t, nil, &Scheduling{
|
||||
NodeSelector: map[string]string{
|
||||
"1": "1",
|
||||
},
|
||||
}, &Scheduling{
|
||||
NodeSelector: map[string]string{
|
||||
"2": "1",
|
||||
},
|
||||
})(func(t *testing.T, pod *core.PodTemplateSpec) {
|
||||
require.Len(t, pod.Spec.NodeSelector, 2)
|
||||
|
||||
require.Contains(t, pod.Spec.NodeSelector, "1")
|
||||
require.EqualValues(t, pod.Spec.NodeSelector["1"], "1")
|
||||
|
||||
require.Contains(t, pod.Spec.NodeSelector, "2")
|
||||
require.EqualValues(t, pod.Spec.NodeSelector["2"], "1")
|
||||
})
|
||||
})
|
||||
t.Run("Override", func(t *testing.T) {
|
||||
applyScheduling(t, nil, &Scheduling{
|
||||
NodeSelector: map[string]string{
|
||||
"1": "1",
|
||||
},
|
||||
}, &Scheduling{
|
||||
NodeSelector: map[string]string{
|
||||
"2": "1",
|
||||
"1": "2",
|
||||
},
|
||||
})(func(t *testing.T, pod *core.PodTemplateSpec) {
|
||||
require.Len(t, pod.Spec.NodeSelector, 2)
|
||||
|
||||
require.Contains(t, pod.Spec.NodeSelector, "1")
|
||||
require.EqualValues(t, pod.Spec.NodeSelector["1"], "2")
|
||||
|
||||
require.Contains(t, pod.Spec.NodeSelector, "2")
|
||||
require.EqualValues(t, pod.Spec.NodeSelector["2"], "1")
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Scheduling_SchedulerName(t *testing.T) {
|
||||
t.Run("Nil", func(t *testing.T) {
|
||||
applyScheduling(t, nil)(func(t *testing.T, pod *core.PodTemplateSpec) {
|
||||
require.EqualValues(t, "", pod.Spec.SchedulerName)
|
||||
})
|
||||
})
|
||||
t.Run("With Scheduler", func(t *testing.T) {
|
||||
applyScheduling(t, nil, &Scheduling{
|
||||
SchedulerName: util.NewType("example"),
|
||||
})(func(t *testing.T, pod *core.PodTemplateSpec) {
|
||||
require.EqualValues(t, "example", pod.Spec.SchedulerName)
|
||||
})
|
||||
})
|
||||
t.Run("With override", func(t *testing.T) {
|
||||
applyScheduling(t, nil, &Scheduling{
|
||||
SchedulerName: util.NewType("example"),
|
||||
}, &Scheduling{
|
||||
SchedulerName: util.NewType("example2"),
|
||||
})(func(t *testing.T, pod *core.PodTemplateSpec) {
|
||||
require.EqualValues(t, "example2", pod.Spec.SchedulerName)
|
||||
})
|
||||
})
|
||||
t.Run("With skip", func(t *testing.T) {
|
||||
applyScheduling(t, nil, &Scheduling{
|
||||
SchedulerName: util.NewType("example"),
|
||||
}, &Scheduling{})(func(t *testing.T, pod *core.PodTemplateSpec) {
|
||||
require.EqualValues(t, "example", pod.Spec.SchedulerName)
|
||||
})
|
||||
})
|
||||
}
|
|
@ -22,8 +22,12 @@ package resources
|
|||
|
||||
import (
|
||||
core "k8s.io/api/core/v1"
|
||||
|
||||
"github.com/arangodb/kube-arangodb/pkg/apis/scheduler/v1alpha1/interfaces"
|
||||
)
|
||||
|
||||
var _ interfaces.Pod[Security] = &Security{}
|
||||
|
||||
type Security struct {
|
||||
// PodSecurityContext holds pod-level security attributes and common container settings.
|
||||
// +doc/type: core.PodSecurityContext
|
||||
|
@ -62,7 +66,7 @@ func (s *Security) With(newResources *Security) *Security {
|
|||
return s.DeepCopy()
|
||||
}
|
||||
|
||||
return nil
|
||||
return newResources.DeepCopy()
|
||||
}
|
||||
|
||||
func (s *Security) Validate() error {
|
||||
|
|
87
pkg/apis/scheduler/v1alpha1/pod/resources/security_test.go
Normal file
87
pkg/apis/scheduler/v1alpha1/pod/resources/security_test.go
Normal file
|
@ -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 (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
core "k8s.io/api/core/v1"
|
||||
|
||||
"github.com/arangodb/kube-arangodb/pkg/util"
|
||||
)
|
||||
|
||||
func applySecurity(t *testing.T, template *core.PodTemplateSpec, ns ...*Security) func(in func(t *testing.T, pod *core.PodTemplateSpec)) {
|
||||
var i *Security
|
||||
|
||||
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_Security(t *testing.T) {
|
||||
t.Run("Nil", func(t *testing.T) {
|
||||
applySecurity(t, nil)(func(t *testing.T, pod *core.PodTemplateSpec) {
|
||||
require.Nil(t, pod.Spec.SecurityContext)
|
||||
})
|
||||
})
|
||||
t.Run("Empty", func(t *testing.T) {
|
||||
applySecurity(t, &core.PodTemplateSpec{})(func(t *testing.T, pod *core.PodTemplateSpec) {
|
||||
require.Nil(t, pod.Spec.SecurityContext)
|
||||
})
|
||||
})
|
||||
t.Run("Pick Always Latest Not Nil", func(t *testing.T) {
|
||||
applySecurity(t, &core.PodTemplateSpec{}, &Security{
|
||||
PodSecurityContext: &core.PodSecurityContext{
|
||||
FSGroup: util.NewType[int64](50),
|
||||
RunAsGroup: util.NewType[int64](128),
|
||||
},
|
||||
}, &Security{
|
||||
PodSecurityContext: &core.PodSecurityContext{
|
||||
FSGroup: util.NewType[int64](60),
|
||||
},
|
||||
}, nil)(func(t *testing.T, pod *core.PodTemplateSpec) {
|
||||
require.NotNil(t, pod.Spec.SecurityContext)
|
||||
|
||||
require.Nil(t, pod.Spec.SecurityContext.RunAsGroup)
|
||||
require.NotNil(t, pod.Spec.SecurityContext.FSGroup)
|
||||
require.EqualValues(t, 60, *pod.Spec.SecurityContext.FSGroup)
|
||||
})
|
||||
})
|
||||
}
|
78
pkg/apis/scheduler/v1alpha1/profile_container_template.go
Normal file
78
pkg/apis/scheduler/v1alpha1/profile_container_template.go
Normal file
|
@ -0,0 +1,78 @@
|
|||
//
|
||||
// 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 (
|
||||
core "k8s.io/api/core/v1"
|
||||
|
||||
schedulerContainerApi "github.com/arangodb/kube-arangodb/pkg/apis/scheduler/v1alpha1/container"
|
||||
"github.com/arangodb/kube-arangodb/pkg/apis/scheduler/v1alpha1/interfaces"
|
||||
shared "github.com/arangodb/kube-arangodb/pkg/apis/shared"
|
||||
)
|
||||
|
||||
var _ interfaces.Pod[ProfileContainerTemplate] = &ProfileContainerTemplate{}
|
||||
|
||||
type ProfileContainerTemplate struct {
|
||||
Containers schedulerContainerApi.Containers `json:"containers,omitempty"`
|
||||
|
||||
All *schedulerContainerApi.Generic `json:"all,omitempty"`
|
||||
}
|
||||
|
||||
func (p *ProfileContainerTemplate) With(other *ProfileContainerTemplate) *ProfileContainerTemplate {
|
||||
if p == nil && other == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if p == nil {
|
||||
return other.DeepCopy()
|
||||
}
|
||||
|
||||
if other == nil {
|
||||
return p.DeepCopy()
|
||||
}
|
||||
|
||||
return &ProfileContainerTemplate{
|
||||
Containers: p.Containers.With(other.Containers),
|
||||
All: p.All.With(other.All),
|
||||
}
|
||||
}
|
||||
|
||||
func (p *ProfileContainerTemplate) Validate() error {
|
||||
if p == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return shared.WithErrors(
|
||||
shared.PrefixResourceErrors("containers", p.Containers.Validate()),
|
||||
shared.PrefixResourceErrors("all", p.All.Validate()),
|
||||
)
|
||||
}
|
||||
|
||||
func (p *ProfileContainerTemplate) Apply(template *core.PodTemplateSpec) error {
|
||||
if p == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return shared.WithErrors(
|
||||
shared.PrefixResourceErrors("containers", p.Containers.Apply(template)),
|
||||
shared.PrefixResourceErrors("all", p.All.Apply(template)),
|
||||
)
|
||||
}
|
82
pkg/apis/scheduler/v1alpha1/profile_template.go
Normal file
82
pkg/apis/scheduler/v1alpha1/profile_template.go
Normal file
|
@ -0,0 +1,82 @@
|
|||
//
|
||||
// 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 (
|
||||
core "k8s.io/api/core/v1"
|
||||
|
||||
"github.com/arangodb/kube-arangodb/pkg/apis/scheduler/v1alpha1/interfaces"
|
||||
schedulerPodApi "github.com/arangodb/kube-arangodb/pkg/apis/scheduler/v1alpha1/pod"
|
||||
shared "github.com/arangodb/kube-arangodb/pkg/apis/shared"
|
||||
"github.com/arangodb/kube-arangodb/pkg/util"
|
||||
)
|
||||
|
||||
var _ interfaces.Pod[ProfileTemplate] = &ProfileTemplate{}
|
||||
|
||||
type ProfileTemplate struct {
|
||||
Priority *int `json:"priority,omitempty"`
|
||||
|
||||
Pod *schedulerPodApi.Pod `json:"pod,omitempty"`
|
||||
|
||||
Container *ProfileContainerTemplate `json:"container,omitempty"`
|
||||
}
|
||||
|
||||
func (p *ProfileTemplate) With(other *ProfileTemplate) *ProfileTemplate {
|
||||
if p == nil && other == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if p == nil {
|
||||
return other.DeepCopy()
|
||||
}
|
||||
|
||||
if other == nil {
|
||||
return p.DeepCopy()
|
||||
}
|
||||
|
||||
return &ProfileTemplate{
|
||||
Priority: util.First(other.Priority, p.Priority),
|
||||
Pod: p.Pod.With(other.Pod),
|
||||
Container: p.Container.With(other.Container),
|
||||
}
|
||||
}
|
||||
|
||||
func (p *ProfileTemplate) Validate() error {
|
||||
if p == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return shared.WithErrors(
|
||||
shared.PrefixResourceErrors("pod", p.Pod.Validate()),
|
||||
shared.PrefixResourceErrors("container", p.Container.Validate()),
|
||||
)
|
||||
}
|
||||
|
||||
func (p *ProfileTemplate) Apply(template *core.PodTemplateSpec) error {
|
||||
if p == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return shared.WithErrors(
|
||||
shared.PrefixResourceErrors("pod", p.Pod.Apply(template)),
|
||||
shared.PrefixResourceErrors("container", p.Container.Apply(template)),
|
||||
)
|
||||
}
|
64
pkg/apis/scheduler/v1alpha1/zz_generated.deepcopy.go
generated
64
pkg/apis/scheduler/v1alpha1/zz_generated.deepcopy.go
generated
|
@ -24,3 +24,67 @@
|
|||
// Code generated by deepcopy-gen. DO NOT EDIT.
|
||||
|
||||
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"
|
||||
)
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ProfileContainerTemplate) DeepCopyInto(out *ProfileContainerTemplate) {
|
||||
*out = *in
|
||||
if in.Containers != nil {
|
||||
in, out := &in.Containers, &out.Containers
|
||||
*out = make(container.Containers, len(*in))
|
||||
for key, val := range *in {
|
||||
(*out)[key] = *val.DeepCopy()
|
||||
}
|
||||
}
|
||||
if in.All != nil {
|
||||
in, out := &in.All, &out.All
|
||||
*out = new(container.Generic)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ProfileContainerTemplate.
|
||||
func (in *ProfileContainerTemplate) DeepCopy() *ProfileContainerTemplate {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(ProfileContainerTemplate)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ProfileTemplate) DeepCopyInto(out *ProfileTemplate) {
|
||||
*out = *in
|
||||
if in.Priority != nil {
|
||||
in, out := &in.Priority, &out.Priority
|
||||
*out = new(int)
|
||||
**out = **in
|
||||
}
|
||||
if in.Pod != nil {
|
||||
in, out := &in.Pod, &out.Pod
|
||||
*out = new(pod.Pod)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.Container != nil {
|
||||
in, out := &in.Container, &out.Container
|
||||
*out = new(ProfileContainerTemplate)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ProfileTemplate.
|
||||
func (in *ProfileTemplate) DeepCopy() *ProfileTemplate {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(ProfileTemplate)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
|
|
@ -170,24 +170,31 @@ func MergeNodeSelector(a, b *core.NodeSelector) *core.NodeSelector {
|
|||
return a.DeepCopy()
|
||||
}
|
||||
|
||||
current := a.DeepCopy()
|
||||
new := b.DeepCopy()
|
||||
var terms []core.NodeSelectorTerm
|
||||
|
||||
for id := range current.NodeSelectorTerms {
|
||||
term := current.NodeSelectorTerms[id]
|
||||
for _, newTerm := range new.NodeSelectorTerms {
|
||||
if len(newTerm.MatchExpressions) != 0 {
|
||||
term.MatchExpressions = append(term.MatchExpressions, newTerm.MatchExpressions...)
|
||||
}
|
||||
if len(newTerm.MatchFields) != 0 {
|
||||
term.MatchFields = append(term.MatchFields, newTerm.MatchFields...)
|
||||
for _, ida := range a.NodeSelectorTerms {
|
||||
for _, idb := range b.NodeSelectorTerms {
|
||||
var sa, sb core.NodeSelectorTerm
|
||||
|
||||
ida.DeepCopyInto(&sa)
|
||||
idb.DeepCopyInto(&sb)
|
||||
|
||||
sa.MatchExpressions = append(sa.MatchExpressions, sb.MatchExpressions...)
|
||||
sa.MatchFields = append(sa.MatchFields, sb.MatchFields...)
|
||||
|
||||
if len(sa.MatchExpressions) == 0 || len(sa.MatchFields) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
terms = append(terms, sa)
|
||||
}
|
||||
|
||||
current.NodeSelectorTerms[id] = term
|
||||
}
|
||||
|
||||
return current
|
||||
if len(terms) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &core.NodeSelector{NodeSelectorTerms: terms}
|
||||
}
|
||||
|
||||
func OptionalNodeAffinity(a *core.NodeAffinity) *core.NodeAffinity {
|
||||
|
|
|
@ -33,6 +33,16 @@ func ApplyContainerResourceRequirements(container *core.Container, resources cor
|
|||
container.Resources.Requests = ApplyContainerResourceList(container.Resources.Requests, resources.Requests)
|
||||
}
|
||||
|
||||
// MergeContainerResource updates resources from `from` to `to` ResourceList
|
||||
func MergeContainerResource(to core.ResourceRequirements, from core.ResourceRequirements) core.ResourceRequirements {
|
||||
var r core.ResourceRequirements
|
||||
|
||||
r.Limits = MergeContainerResourceList(to.Limits, from.Limits)
|
||||
r.Requests = MergeContainerResourceList(to.Requests, from.Requests)
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
// ApplyContainerResource adds non-existing resources from `from` to `to` ResourceList
|
||||
func ApplyContainerResource(to core.ResourceRequirements, from core.ResourceRequirements) core.ResourceRequirements {
|
||||
var r core.ResourceRequirements
|
||||
|
@ -43,6 +53,27 @@ func ApplyContainerResource(to core.ResourceRequirements, from core.ResourceRequ
|
|||
return r
|
||||
}
|
||||
|
||||
// MergeContainerResourceList updates resources from `from` to `to` ResourceList
|
||||
func MergeContainerResourceList(to core.ResourceList, from core.ResourceList) core.ResourceList {
|
||||
if len(from) == 0 {
|
||||
return to
|
||||
}
|
||||
|
||||
if to == nil {
|
||||
to = core.ResourceList{}
|
||||
}
|
||||
|
||||
for k, v := range from {
|
||||
if v.IsZero() {
|
||||
delete(to, k)
|
||||
} else {
|
||||
to[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
return to
|
||||
}
|
||||
|
||||
// ApplyContainerResourceList adds non-existing resources from `from` to `to` ResourceList
|
||||
func ApplyContainerResourceList(to core.ResourceList, from core.ResourceList) core.ResourceList {
|
||||
if len(from) == 0 {
|
||||
|
|
Loading…
Reference in a new issue