1
0
Fork 0
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:
Adam Janikowski 2024-03-01 16:03:05 +01:00 committed by GitHub
parent 9459237da6
commit e7ae432b24
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
32 changed files with 3116 additions and 218 deletions

View file

@ -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

View file

@ -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/)

View file

@ -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())
})
}

View file

@ -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),
)
}

View file

@ -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)
})
})
}

View file

@ -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])
})
})
}

View file

@ -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()),
)
}

View 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)
})
})
}

View file

@ -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
}

View file

@ -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)
})
})
}

View file

@ -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"`

View 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)
})
})
}

View file

@ -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 {

View file

@ -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])
})
})
}

View file

@ -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
}

View file

@ -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)
})
})
}

View file

@ -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)

View 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
}

View file

@ -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

View 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)
})
})
}

View file

@ -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.

View 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)
})
})
}

View file

@ -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 {
}
}
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 = s.Tolerations.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)

View 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)
})
})
}

View file

@ -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 {

View 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)
})
})
}

View 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)),
)
}

View 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)),
)
}

View file

@ -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
}

View file

@ -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...)
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
}
if len(newTerm.MatchFields) != 0 {
term.MatchFields = append(term.MatchFields, newTerm.MatchFields...)
terms = append(terms, sa)
}
}
current.NodeSelectorTerms[id] = term
if len(terms) == 0 {
return nil
}
return current
return &core.NodeSelector{NodeSelectorTerms: terms}
}
func OptionalNodeAffinity(a *core.NodeAffinity) *core.NodeAffinity {

View file

@ -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 {