1
0
Fork 0
mirror of https://github.com/arangodb/kube-arangodb.git synced 2024-12-14 11:57:37 +00:00

[Bugfix] Fix resource propagation to InitContainers (#1418)

This commit is contained in:
Adam Janikowski 2023-09-25 23:13:18 +02:00 committed by GitHub
parent fdc6608321
commit 50a672c4e9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 267 additions and 134 deletions

View file

@ -23,6 +23,7 @@
- (Feature) Add --deployment.feature.init-containers-copy-resources (default enabled)
- (Feature) Add maxBackups option to ArangoBackupPolicy
- (Improvement) Better detection for AllInSync condition for DC2DC sync status
- (Bugfix) Fix resource propagation to InitContainers
## [1.2.32](https://github.com/arangodb/kube-arangodb/tree/1.2.32) (2023-08-07)
- (Feature) Backup lifetime - remove Backup once its lifetime has been reached

View file

@ -358,7 +358,7 @@ func TestEnsurePod_ArangoDB_Core(t *testing.T) {
Image: testImage,
Command: createTestCommandForDBServer(firstDBServerStatus.ID, false, false, false),
Ports: createTestPorts(api.ServerGroupDBServers),
Resources: k8sutil.ExtractPodResourceRequirement(resourcesUnfiltered),
Resources: k8sutil.ExtractPodAcceptedResourceRequirement(resourcesUnfiltered),
VolumeMounts: []core.VolumeMount{
k8sutil.ArangodVolumeMount(),
},
@ -415,7 +415,7 @@ func TestEnsurePod_ArangoDB_Core(t *testing.T) {
Image: testImage,
Command: createTestCommandForDBServer(firstDBServerStatus.ID, false, false, false),
Ports: createTestPorts(api.ServerGroupDBServers),
Resources: k8sutil.ExtractPodResourceRequirement(resourcesUnfiltered),
Resources: k8sutil.ExtractPodAcceptedResourceRequirement(resourcesUnfiltered),
VolumeMounts: []core.VolumeMount{
k8sutil.ArangodVolumeMount(),
},
@ -475,7 +475,7 @@ func TestEnsurePod_ArangoDB_Core(t *testing.T) {
Image: testImage,
Command: createTestCommandForDBServer(firstDBServerStatus.ID, false, false, false),
Ports: createTestPorts(api.ServerGroupDBServers),
Resources: k8sutil.ExtractPodResourceRequirement(resourcesUnfiltered),
Resources: k8sutil.ExtractPodAcceptedResourceRequirement(resourcesUnfiltered),
VolumeMounts: []core.VolumeMount{
k8sutil.ArangodVolumeMount(),
},
@ -1009,7 +1009,7 @@ func TestEnsurePod_ArangoDB_Core(t *testing.T) {
ImagePullPolicy: core.PullIfNotPresent,
SecurityContext: securityContext.NewSecurityContext(),
},
testArangodbInternalExporterContainer(false, true, k8sutil.ExtractPodResourceRequirement(resourcesUnfiltered)),
testArangodbInternalExporterContainer(false, true, k8sutil.ExtractPodAcceptedResourceRequirement(resourcesUnfiltered)),
},
RestartPolicy: core.RestartPolicyNever,
TerminationGracePeriodSeconds: &defaultDBServerTerminationTimeout,
@ -1064,7 +1064,7 @@ func TestEnsurePod_ArangoDB_Core(t *testing.T) {
k8sutil.LifecycleVolume(),
},
InitContainers: []core.Container{
createTestLifecycleContainer(k8sutil.ExtractPodResourceRequirement(resourcesUnfiltered)),
createTestLifecycleContainer(k8sutil.ExtractPodAcceptedResourceRequirement(resourcesUnfiltered)),
},
Containers: []core.Container{
{

View file

@ -134,7 +134,7 @@ func TestEnsurePod_ArangoDB_Resources(t *testing.T) {
Image: testImage,
Command: createTestCommandForDBServer(firstDBServerStatus.ID, false, false, false),
Ports: createTestPorts(api.ServerGroupAgents),
Resources: k8sutil.ExtractPodResourceRequirement(resourcesUnfiltered),
Resources: k8sutil.ExtractPodAcceptedResourceRequirement(resourcesUnfiltered),
VolumeMounts: []core.VolumeMount{
k8sutil.ArangodVolumeMount(),
},
@ -194,7 +194,7 @@ func TestEnsurePod_ArangoDB_Resources(t *testing.T) {
Image: testImage,
Command: createTestCommandForDBServer(firstDBServerStatus.ID, false, false, false),
Ports: createTestPorts(api.ServerGroupAgents),
Resources: k8sutil.ExtractPodResourceRequirement(resourcesUnfiltered),
Resources: k8sutil.ExtractPodAcceptedResourceRequirement(resourcesUnfiltered),
VolumeMounts: []core.VolumeMount{
k8sutil.ArangodVolumeMount(),
},
@ -252,7 +252,7 @@ func TestEnsurePod_ArangoDB_Resources(t *testing.T) {
Image: testImage,
Command: createTestCommandForDBServer(firstDBServerStatus.ID, false, false, false),
Ports: createTestPorts(api.ServerGroupAgents),
Resources: k8sutil.ExtractPodResourceRequirement(resourcesUnfiltered),
Resources: k8sutil.ExtractPodAcceptedResourceRequirement(resourcesUnfiltered),
VolumeMounts: []core.VolumeMount{
k8sutil.ArangodVolumeMount(),
},

View file

@ -1304,7 +1304,7 @@ func TestEnsurePod_Sync_Worker(t *testing.T) {
},
ImagePullPolicy: core.PullIfNotPresent,
Lifecycle: createTestLifecycle(api.ServerGroupSyncMasters),
Resources: k8sutil.ExtractPodResourceRequirement(resourcesUnfiltered),
Resources: k8sutil.ExtractPodAcceptedResourceRequirement(resourcesUnfiltered),
SecurityContext: securityContext.NewSecurityContext(),
VolumeMounts: []core.VolumeMount{
k8sutil.LifecycleVolumeMount(),
@ -1399,7 +1399,7 @@ func TestEnsurePod_Sync_Worker(t *testing.T) {
},
ImagePullPolicy: core.PullIfNotPresent,
Lifecycle: createTestLifecycle(api.ServerGroupSyncMasters),
Resources: k8sutil.ExtractPodResourceRequirement(resourcesUnfiltered),
Resources: k8sutil.ExtractPodAcceptedResourceRequirement(resourcesUnfiltered),
SecurityContext: securityContext.NewSecurityContext(),
VolumeMounts: []core.VolumeMount{
k8sutil.LifecycleVolumeMount(),

View file

@ -54,7 +54,7 @@ func ArangodbInternalExporterContainer(image string, args []string, livenessProb
Protocol: core.ProtocolTCP,
},
},
Resources: k8sutil.ExtractPodResourceRequirement(resources),
Resources: k8sutil.ExtractPodAcceptedResourceRequirement(resources),
SecurityContext: k8sutil.CreateSecurityContext(groupSpec.SecurityContext),
ImagePullPolicy: core.PullIfNotPresent,
VolumeMounts: []core.VolumeMount{k8sutil.LifecycleVolumeMount()},

View file

@ -281,7 +281,7 @@ func (a *ArangoDContainer) GetEnvs() ([]core.EnvVar, []core.EnvFromSource) {
}
func (a *ArangoDContainer) GetResourceRequirements() core.ResourceRequirements {
return k8sutil.ExtractPodResourceRequirement(a.arangoMember.Spec.Overrides.GetResources(&a.groupSpec))
return k8sutil.ExtractPodAcceptedResourceRequirement(a.arangoMember.Spec.Overrides.GetResources(&a.groupSpec))
}
func (a *ArangoDContainer) GetLifecycle() (*core.Lifecycle, error) {
@ -513,7 +513,7 @@ func (m *MemberArangoDPod) GetInitContainers(cachedStatus interfaces.Inspector)
}
}
return applyInitContainersResourceResources(initContainers, &m.groupSpec.Resources), nil
return applyInitContainersResourceResources(initContainers, k8sutil.ExtractPodInitContainerAcceptedResourceRequirement(m.GetContainerCreator().GetResourceRequirements())), nil
}
func (m *MemberArangoDPod) GetFinalizers() []string {

View file

@ -148,7 +148,7 @@ func (a *ArangoSyncContainer) GetProbes() (*core.Probe, *core.Probe, *core.Probe
}
func (a *ArangoSyncContainer) GetResourceRequirements() core.ResourceRequirements {
return k8sutil.ExtractPodResourceRequirement(a.arangoMember.Spec.Overrides.GetResources(&a.groupSpec))
return k8sutil.ExtractPodAcceptedResourceRequirement(a.arangoMember.Spec.Overrides.GetResources(&a.groupSpec))
}
func (a *ArangoSyncContainer) GetLifecycle() (*core.Lifecycle, error) {
@ -300,7 +300,7 @@ func (m *MemberSyncPod) GetInitContainers(cachedStatus interfaces.Inspector) ([]
initContainers = append(initContainers, c)
}
return applyInitContainersResourceResources(initContainers, &m.groupSpec.Resources), nil
return applyInitContainersResourceResources(initContainers, k8sutil.ExtractPodInitContainerAcceptedResourceRequirement(m.GetContainerCreator().GetResourceRequirements())), nil
}
func (m *MemberSyncPod) GetFinalizers() []string {

View file

@ -30,18 +30,17 @@ import (
// applyInitContainersResourceResources updates passed init containers to ensure that all resources are set (if such feature is enabled)
// This is applied only to containers added by operator itself
func applyInitContainersResourceResources(initContainers []core.Container, mainContainerResources *core.ResourceRequirements) []core.Container {
if !features.InitContainerCopyResources().Enabled() || mainContainerResources == nil {
func applyInitContainersResourceResources(initContainers []core.Container, mainContainerResources core.ResourceRequirements) []core.Container {
if !features.InitContainerCopyResources().Enabled() {
return initContainers
}
for i, c := range initContainers {
if !api.IsReservedServerGroupInitContainerName(c.Name) {
for i := range initContainers {
if !api.IsReservedServerGroupInitContainerName(initContainers[i].Name) {
continue
}
k8sutil.EnsureAllResourcesNotEmpty(mainContainerResources.Limits, &initContainers[i].Resources.Limits)
k8sutil.EnsureAllResourcesNotEmpty(mainContainerResources.Requests, &initContainers[i].Resources.Requests)
k8sutil.ApplyContainerResourceRequirements(&initContainers[i], mainContainerResources)
}
return initContainers
}

View file

@ -74,7 +74,7 @@ func InitLifecycleContainer(image string, resources *core.ResourceRequirements,
}
if resources != nil {
c.Resources = ExtractPodResourceRequirement(*resources)
c.Resources = ExtractPodAcceptedResourceRequirement(*resources)
}
return c, nil
}

View file

@ -532,38 +532,6 @@ func operatorInitContainer(name, operatorImage string, command []string, securit
return c
}
// ExtractPodResourceRequirement filters resource requirements for Pods.
func ExtractPodResourceRequirement(resources core.ResourceRequirements) core.ResourceRequirements {
filter := PodResourceRequirementsFilter(core.ResourceCPU, core.ResourceMemory, core.ResourceEphemeralStorage)
return core.ResourceRequirements{
Limits: filter(resources.Limits),
Requests: filter(resources.Requests),
}
}
func PodResourceRequirementsFilter(filters ...core.ResourceName) func(in core.ResourceList) core.ResourceList {
return func(in core.ResourceList) core.ResourceList {
filtered := map[core.ResourceName]bool{}
for _, k := range filters {
filtered[k] = true
}
n := core.ResourceList{}
for k, v := range in {
if _, ok := filtered[k]; !ok {
continue
}
n[k] = v
}
return n
}
}
// NewContainer creates a container for specified creator
func NewContainer(containerCreator interfaces.ContainerCreator) (core.Container, error) {

View file

@ -22,17 +22,14 @@ package k8sutil
import (
"context"
"fmt"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
core "k8s.io/api/core/v1"
resource "k8s.io/apimachinery/pkg/api/resource"
meta "k8s.io/apimachinery/pkg/apis/meta/v1"
"github.com/arangodb/kube-arangodb/pkg/handlers/utils"
"github.com/arangodb/kube-arangodb/pkg/util"
"github.com/arangodb/kube-arangodb/pkg/util/constants"
"github.com/arangodb/kube-arangodb/pkg/util/kclient"
)
@ -419,61 +416,3 @@ func Test_EnsureFinalizer(t *testing.T) {
require.NotContains(t, pod.Finalizers, f)
})
}
func Test_ExtractPodResourceRequirement(t *testing.T) {
v, err := resource.ParseQuantity("1Gi")
require.NoError(t, err)
t.Run("Filter Storage", func(t *testing.T) {
in := core.ResourceRequirements{
Limits: map[core.ResourceName]resource.Quantity{
core.ResourceCPU: v,
core.ResourceStorage: v,
},
}
require.Len(t, in.Limits, 2)
require.Len(t, in.Requests, 0)
out := ExtractPodResourceRequirement(in)
require.Len(t, out.Limits, 1)
require.Contains(t, out.Limits, core.ResourceCPU)
require.NotContains(t, out.Limits, core.ResourceStorage)
require.Len(t, out.Requests, 0)
})
t.Run("Ensure that all required Resources are filtered", func(t *testing.T) {
resources := map[core.ResourceName]bool{
core.ResourceCPU: true,
core.ResourceMemory: true,
core.ResourceStorage: false,
core.ResourceEphemeralStorage: true,
}
in := core.ResourceRequirements{
Limits: core.ResourceList{},
Requests: core.ResourceList{},
}
for k := range resources {
in.Limits[k] = v
in.Requests[k] = v
}
out := ExtractPodResourceRequirement(in)
for k, v := range resources {
t.Run(fmt.Sprintf("Resource %s should be %s", k, util.BoolSwitch(v, "present", "missing")), func(t *testing.T) {
require.Contains(t, in.Requests, k)
require.Contains(t, in.Limits, k)
if v {
require.Contains(t, out.Requests, k)
require.Contains(t, out.Limits, k)
} else {
require.NotContains(t, out.Requests, k)
require.NotContains(t, out.Limits, k)
}
})
}
})
}

View file

@ -60,17 +60,7 @@ func IsPersistentVolumeClaimResizing(pvc *core.PersistentVolumeClaim) bool {
// ExtractStorageResourceRequirement filters resource requirements for Pods.
func ExtractStorageResourceRequirement(resources core.ResourceRequirements) core.ResourceRequirements {
filterStorage := func(list core.ResourceList) core.ResourceList {
newlist := make(core.ResourceList)
for k, v := range list {
if k != core.ResourceStorage && k != "iops" {
continue
}
newlist[k] = v
}
return newlist
}
filterStorage := NewPodResourceListFilter(core.ResourceStorage, "iops")
return core.ResourceRequirements{
Limits: filterStorage(resources.Limits),

View file

@ -24,15 +24,89 @@ import (
core "k8s.io/api/core/v1"
)
// EnsureAllResourcesNotEmpty copies resource specifications from src to dst if such resource is not defined in dst
func EnsureAllResourcesNotEmpty(src core.ResourceList, dst *core.ResourceList) {
if dst == nil {
l := make(core.ResourceList)
dst = &l
func ApplyContainerResourceRequirements(container *core.Container, resources core.ResourceRequirements) {
if container == nil {
return
}
for k, v := range src {
if _, ok := (*dst)[k]; !ok {
(*dst)[k] = v.DeepCopy()
container.Resources.Limits = ApplyContainerResourceList(container.Resources.Limits, resources.Limits)
container.Resources.Requests = ApplyContainerResourceList(container.Resources.Requests, resources.Requests)
}
// ApplyContainerResourceList adds non-existing resources from `from` to `to` ResourceList
func ApplyContainerResourceList(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 _, ok := to[k]; !ok {
to[k] = v
}
}
return to
}
// ExtractPodInitContainerAcceptedResourceRequirement filters resource requirements for InitContainers.
func ExtractPodInitContainerAcceptedResourceRequirement(resources core.ResourceRequirements) core.ResourceRequirements {
return NewPodResourceRequirementsFilter(PodResourceRequirementsInitContainersAcceptedResourceRequirements()...)(resources)
}
// PodResourceRequirementsInitContainersAcceptedResourceRequirements returns struct if accepted Pod resource types
func PodResourceRequirementsInitContainersAcceptedResourceRequirements() []core.ResourceName {
return []core.ResourceName{core.ResourceCPU, core.ResourceMemory, core.ResourceEphemeralStorage}
}
// ExtractPodAcceptedResourceRequirement filters resource requirements for Pods.
func ExtractPodAcceptedResourceRequirement(resources core.ResourceRequirements) core.ResourceRequirements {
return NewPodResourceRequirementsFilter(PodResourceRequirementsPodAcceptedResourceRequirements()...)(resources)
}
// PodResourceRequirementsPodAcceptedResourceRequirements returns struct if accepted Pod resource types
func PodResourceRequirementsPodAcceptedResourceRequirements() []core.ResourceName {
return []core.ResourceName{core.ResourceCPU, core.ResourceMemory, core.ResourceEphemeralStorage}
}
type PodResourceRequirementsFilter func(in core.ResourceRequirements) core.ResourceRequirements
// NewPodResourceRequirementsFilter returns function which filter out not accepted resources from resource requirements
func NewPodResourceRequirementsFilter(filters ...core.ResourceName) PodResourceRequirementsFilter {
return func(in core.ResourceRequirements) core.ResourceRequirements {
filter := NewPodResourceListFilter(filters...)
return core.ResourceRequirements{
Limits: filter(in.Limits),
Requests: filter(in.Requests),
}
}
}
type PodResourceListFilter func(in core.ResourceList) core.ResourceList
// NewPodResourceListFilter returns function which filter out not accepted resources from list
func NewPodResourceListFilter(filters ...core.ResourceName) PodResourceListFilter {
return func(in core.ResourceList) core.ResourceList {
filtered := map[core.ResourceName]bool{}
for _, k := range filters {
filtered[k] = true
}
n := core.ResourceList{}
for k, v := range in {
if _, ok := filtered[k]; !ok {
continue
}
n[k] = v
}
return n
}
}

View file

@ -0,0 +1,162 @@
//
// DISCLAIMER
//
// Copyright 2023 ArangoDB GmbH, Cologne, Germany
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Copyright holder is ArangoDB GmbH, Cologne, Germany
//
package k8sutil
import (
"fmt"
"testing"
"github.com/stretchr/testify/require"
core "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
"github.com/arangodb/kube-arangodb/pkg/util"
)
func Test_ExtractPodAcceptedResourceRequirement(t *testing.T) {
v, err := resource.ParseQuantity("1Gi")
require.NoError(t, err)
t.Run("Filter Storage", func(t *testing.T) {
in := core.ResourceRequirements{
Limits: map[core.ResourceName]resource.Quantity{
core.ResourceCPU: v,
core.ResourceStorage: v,
},
}
require.Len(t, in.Limits, 2)
require.Len(t, in.Requests, 0)
out := ExtractPodAcceptedResourceRequirement(in)
require.Len(t, out.Limits, 1)
require.Contains(t, out.Limits, core.ResourceCPU)
require.NotContains(t, out.Limits, core.ResourceStorage)
require.Len(t, out.Requests, 0)
})
t.Run("Ensure that all required Resources are filtered", func(t *testing.T) {
resources := map[core.ResourceName]bool{
core.ResourceCPU: true,
core.ResourceMemory: true,
core.ResourceStorage: false,
core.ResourceEphemeralStorage: true,
}
in := core.ResourceRequirements{
Limits: core.ResourceList{},
Requests: core.ResourceList{},
}
for k := range resources {
in.Limits[k] = v
in.Requests[k] = v
}
out := ExtractPodAcceptedResourceRequirement(in)
for k, v := range resources {
t.Run(fmt.Sprintf("Resource %s should be %s", k, util.BoolSwitch(v, "present", "missing")), func(t *testing.T) {
require.Contains(t, in.Requests, k)
require.Contains(t, in.Limits, k)
if v {
require.Contains(t, out.Requests, k)
require.Contains(t, out.Limits, k)
} else {
require.NotContains(t, out.Requests, k)
require.NotContains(t, out.Limits, k)
}
})
}
})
}
func Test_ApplyContainerResourceRequirements(t *testing.T) {
v1, err := resource.ParseQuantity("1Gi")
require.NoError(t, err)
v2, err := resource.ParseQuantity("4Gi")
require.NoError(t, err)
var container core.Container
t.Run("Ensure limits are copied", func(t *testing.T) {
ApplyContainerResourceRequirements(&container, core.ResourceRequirements{
Limits: core.ResourceList{
core.ResourceMemory: v1,
},
Requests: core.ResourceList{
core.ResourceMemory: v1,
},
})
require.Len(t, container.Resources.Requests, 1)
require.Contains(t, container.Resources.Requests, core.ResourceMemory)
require.Equal(t, v1, container.Resources.Requests[core.ResourceMemory])
require.Len(t, container.Resources.Limits, 1)
require.Contains(t, container.Resources.Limits, core.ResourceMemory)
require.Equal(t, v1, container.Resources.Limits[core.ResourceMemory])
})
t.Run("Ensure limits are not overridden", func(t *testing.T) {
ApplyContainerResourceRequirements(&container, core.ResourceRequirements{
Limits: core.ResourceList{
core.ResourceMemory: v2,
},
Requests: core.ResourceList{
core.ResourceMemory: v2,
},
})
require.Len(t, container.Resources.Requests, 1)
require.Contains(t, container.Resources.Requests, core.ResourceMemory)
require.Equal(t, v1, container.Resources.Requests[core.ResourceMemory])
require.Len(t, container.Resources.Limits, 1)
require.Contains(t, container.Resources.Limits, core.ResourceMemory)
require.Equal(t, v1, container.Resources.Limits[core.ResourceMemory])
})
t.Run("Ensure limits are appended", func(t *testing.T) {
ApplyContainerResourceRequirements(&container, core.ResourceRequirements{
Limits: core.ResourceList{
core.ResourceCPU: v2,
},
Requests: core.ResourceList{
core.ResourceStorage: v2,
},
})
require.Len(t, container.Resources.Requests, 2)
require.Contains(t, container.Resources.Requests, core.ResourceMemory)
require.Equal(t, v1, container.Resources.Requests[core.ResourceMemory])
require.Contains(t, container.Resources.Requests, core.ResourceStorage)
require.Equal(t, v2, container.Resources.Requests[core.ResourceStorage])
require.Len(t, container.Resources.Limits, 2)
require.Contains(t, container.Resources.Limits, core.ResourceMemory)
require.Equal(t, v1, container.Resources.Limits[core.ResourceMemory])
require.Contains(t, container.Resources.Limits, core.ResourceCPU)
require.Equal(t, v2, container.Resources.Limits[core.ResourceCPU])
})
}