From a61e7b0abfe488aba8e123e043ef58f90ad8b64d Mon Sep 17 00:00:00 2001 From: lamai93 Date: Wed, 31 Jul 2019 11:12:26 +0200 Subject: [PATCH 1/3] Added test for resource rotation. --- .../deployment/v1alpha/deployment_spec.go | 19 +++ pkg/deployment/reconcile/plan_builder.go | 2 +- tests/predicates.go | 28 +++++ tests/resources_test.go | 112 ++++++++++++++++++ 4 files changed, 160 insertions(+), 1 deletion(-) create mode 100644 tests/resources_test.go diff --git a/pkg/apis/deployment/v1alpha/deployment_spec.go b/pkg/apis/deployment/v1alpha/deployment_spec.go index 270146b63..e0f1114db 100644 --- a/pkg/apis/deployment/v1alpha/deployment_spec.go +++ b/pkg/apis/deployment/v1alpha/deployment_spec.go @@ -162,6 +162,25 @@ func (s DeploymentSpec) GetServerGroupSpec(group ServerGroup) ServerGroupSpec { } } +// UpdateServerGroupSpec returns the server group spec (from this +// deployment spec) for the given group. +func (s *DeploymentSpec) UpdateServerGroupSpec(group ServerGroup, gspec ServerGroupSpec) { + switch group { + case ServerGroupSingle: + s.Single = gspec + case ServerGroupAgents: + s.Agents = gspec + case ServerGroupDBServers: + s.DBServers = gspec + case ServerGroupCoordinators: + s.Coordinators = gspec + case ServerGroupSyncMasters: + s.SyncMasters = gspec + case ServerGroupSyncWorkers: + s.SyncWorkers = gspec + } +} + // SetDefaults fills in default values when a field is not specified. func (s *DeploymentSpec) SetDefaults(deploymentName string) { if s.GetMode() == "" { diff --git a/pkg/deployment/reconcile/plan_builder.go b/pkg/deployment/reconcile/plan_builder.go index fab2bde24..dce48c315 100644 --- a/pkg/deployment/reconcile/plan_builder.go +++ b/pkg/deployment/reconcile/plan_builder.go @@ -29,11 +29,11 @@ import ( upgraderules "github.com/arangodb/go-upgrade-rules" "github.com/rs/zerolog" "github.com/rs/zerolog/log" - "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" api "github.com/arangodb/kube-arangodb/pkg/apis/deployment/v1alpha" "github.com/arangodb/kube-arangodb/pkg/util/k8sutil" + v1 "k8s.io/api/core/v1" ) // upgradeDecision is the result of an upgrade check. diff --git a/tests/predicates.go b/tests/predicates.go index ccc6ce679..7dbce4381 100644 --- a/tests/predicates.go +++ b/tests/predicates.go @@ -26,6 +26,9 @@ import ( "fmt" api "github.com/arangodb/kube-arangodb/pkg/apis/deployment/v1alpha" + "github.com/arangodb/kube-arangodb/pkg/util/k8sutil" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes" ) // deploymentIsReady creates a predicate that returns nil when the deployment is in @@ -41,3 +44,28 @@ func deploymentIsReady() func(*api.ArangoDeployment) error { return fmt.Errorf("Expected Ready condition to be set, it is not") } } + +func resourcesAsRequested(kubecli kubernetes.Interface, ns string) func(obj *api.ArangoDeployment) error { + return func(obj *api.ArangoDeployment) error { + return obj.ForeachServerGroup(func(group api.ServerGroup, spec api.ServerGroupSpec, status *api.MemberStatusList) error { + + for _, m := range *status { + pod, err := kubecli.CoreV1().Pods(ns).Get(m.PodName, metav1.GetOptions{}) + if err != nil { + return err + } + + c, found := k8sutil.GetContainerByName(pod, k8sutil.ServerContainerName) + if !found { + return fmt.Errorf("Container not found: %s", m.PodName) + } + + if resourcesRequireRotation(spec.Resources, c.Resources) { + return fmt.Errorf("Container of Pod %s need rotation", m.PodName) + } + } + + return nil + }, nil) + } +} diff --git a/tests/resources_test.go b/tests/resources_test.go new file mode 100644 index 000000000..53ef482b9 --- /dev/null +++ b/tests/resources_test.go @@ -0,0 +1,112 @@ +// +// DISCLAIMER +// +// Copyright 2018 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 +// +// Author Lars Maier +// + +package tests + +import ( + "fmt" + "testing" + + api "github.com/arangodb/kube-arangodb/pkg/apis/deployment/v1alpha" + "github.com/arangodb/kube-arangodb/pkg/client" + kubeArangoClient "github.com/arangodb/kube-arangodb/pkg/client" + "github.com/arangodb/kube-arangodb/pkg/util" + "github.com/dchest/uniuri" + "github.com/stretchr/testify/assert" + v1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/resource" +) + +func resourcesRequireRotation(wanted, given v1.ResourceRequirements) bool { + checkList := func(wanted, given v1.ResourceList) bool { + for k, v := range wanted { + if gv, ok := given[k]; !ok { + return true + } else if v.Cmp(gv) != 0 { + return true + } + } + + return false + } + + return checkList(wanted.Limits, given.Limits) || checkList(wanted.Requests, given.Requests) +} + +func TestResourcesChangeLimitsCluster(t *testing.T) { + longOrSkip(t) + c := client.MustNewInCluster() + kubecli := mustNewKubeClient(t) + deploymentClient := kubeArangoClient.MustNewInCluster() + ns := getNamespace(t) + + size500mCPU, _ := resource.ParseQuantity("50m") + size1CPU, _ := resource.ParseQuantity("1") + + // Prepare deployment config + depl := newDeployment("test-chng-limits-" + uniuri.NewLen(4)) + depl.Spec.Mode = api.NewMode(api.DeploymentModeCluster) + depl.Spec.DBServers.Count = util.NewInt(2) + depl.Spec.Coordinators.Count = util.NewInt(2) + depl.Spec.SetDefaults(depl.GetName()) // this must be last + defer deferedCleanupDeployment(c, depl.GetName(), ns) + + // Create deployment + _, err := deploymentClient.DatabaseV1alpha().ArangoDeployments(ns).Create(depl) + defer removeDeployment(deploymentClient, depl.GetName(), ns) + assert.NoError(t, err, "failed to create deplyment: %s", err) + + testGroups := []api.ServerGroup{api.ServerGroupCoordinators, api.ServerGroupAgents, api.ServerGroupDBServers} + + for _, testgroup := range testGroups { + t.Run(testgroup.AsRole(), func(t *testing.T) { + + _, err = waitUntilDeployment(deploymentClient, depl.GetName(), ns, deploymentIsReady()) + assert.NoError(t, err, fmt.Sprintf("Deployment not running in time: %s", err)) + + depl, err = updateDeployment(c, depl.GetName(), ns, func(spec *api.DeploymentSpec) { + gspec := spec.GetServerGroupSpec(testgroup) + gspec.Resources.Limits = v1.ResourceList{ + v1.ResourceCPU: size1CPU, + } + spec.UpdateServerGroupSpec(testgroup, gspec) + }) + assert.NoError(t, err, fmt.Sprintf("Failed to update deployment: %s", err)) + + _, err = waitUntilDeployment(deploymentClient, depl.GetName(), ns, resourcesAsRequested(kubecli, ns)) + assert.NoError(t, err, fmt.Sprintf("Deployment not rotated in time: %s", err)) + + depl, err = updateDeployment(c, depl.GetName(), ns, func(spec *api.DeploymentSpec) { + gspec := spec.GetServerGroupSpec(testgroup) + gspec.Resources.Requests = v1.ResourceList{ + v1.ResourceCPU: size500mCPU, + } + spec.UpdateServerGroupSpec(testgroup, gspec) + }) + assert.NoError(t, err, fmt.Sprintf("Failed to update deployment: %s", err)) + + _, err = waitUntilDeployment(deploymentClient, depl.GetName(), ns, resourcesAsRequested(kubecli, ns)) + assert.NoError(t, err, fmt.Sprintf("Deployment not rotated in time: %s", err)) + }) + } + +} From 33bacfd7f89182d12ddfd682197d54358cabcced Mon Sep 17 00:00:00 2001 From: lamai93 Date: Wed, 31 Jul 2019 11:15:24 +0200 Subject: [PATCH 2/3] Moved function to place where it is used. --- tests/predicates.go | 17 +++++++++++++++++ tests/resources_test.go | 16 ---------------- 2 files changed, 17 insertions(+), 16 deletions(-) diff --git a/tests/predicates.go b/tests/predicates.go index 7dbce4381..c73c40d2a 100644 --- a/tests/predicates.go +++ b/tests/predicates.go @@ -25,6 +25,7 @@ package tests import ( "fmt" + v1 "k8s.io/api/core/v1" api "github.com/arangodb/kube-arangodb/pkg/apis/deployment/v1alpha" "github.com/arangodb/kube-arangodb/pkg/util/k8sutil" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -45,6 +46,22 @@ func deploymentIsReady() func(*api.ArangoDeployment) error { } } +func resourcesRequireRotation(wanted, given v1.ResourceRequirements) bool { + checkList := func(wanted, given v1.ResourceList) bool { + for k, v := range wanted { + if gv, ok := given[k]; !ok { + return true + } else if v.Cmp(gv) != 0 { + return true + } + } + + return false + } + + return checkList(wanted.Limits, given.Limits) || checkList(wanted.Requests, given.Requests) +} + func resourcesAsRequested(kubecli kubernetes.Interface, ns string) func(obj *api.ArangoDeployment) error { return func(obj *api.ArangoDeployment) error { return obj.ForeachServerGroup(func(group api.ServerGroup, spec api.ServerGroupSpec, status *api.MemberStatusList) error { diff --git a/tests/resources_test.go b/tests/resources_test.go index 53ef482b9..9fb6589f5 100644 --- a/tests/resources_test.go +++ b/tests/resources_test.go @@ -36,22 +36,6 @@ import ( "k8s.io/apimachinery/pkg/api/resource" ) -func resourcesRequireRotation(wanted, given v1.ResourceRequirements) bool { - checkList := func(wanted, given v1.ResourceList) bool { - for k, v := range wanted { - if gv, ok := given[k]; !ok { - return true - } else if v.Cmp(gv) != 0 { - return true - } - } - - return false - } - - return checkList(wanted.Limits, given.Limits) || checkList(wanted.Requests, given.Requests) -} - func TestResourcesChangeLimitsCluster(t *testing.T) { longOrSkip(t) c := client.MustNewInCluster() From 634d03484b6aa571699c20795f99252faeea8067 Mon Sep 17 00:00:00 2001 From: lamai93 Date: Wed, 31 Jul 2019 12:50:03 +0200 Subject: [PATCH 3/3] Added more test cases. --- tests/resources_test.go | 72 +++++++++++++++++++++++++++-------------- 1 file changed, 48 insertions(+), 24 deletions(-) diff --git a/tests/resources_test.go b/tests/resources_test.go index 9fb6589f5..ba180db39 100644 --- a/tests/resources_test.go +++ b/tests/resources_test.go @@ -43,8 +43,11 @@ func TestResourcesChangeLimitsCluster(t *testing.T) { deploymentClient := kubeArangoClient.MustNewInCluster() ns := getNamespace(t) - size500mCPU, _ := resource.ParseQuantity("50m") - size1CPU, _ := resource.ParseQuantity("1") + size500m, _ := resource.ParseQuantity("50m") + size1, _ := resource.ParseQuantity("1") + size100Gi, _ := resource.ParseQuantity("100Gi") + size1Gi, _ := resource.ParseQuantity("1Gi") + size2Gi, _ := resource.ParseQuantity("2Gi") // Prepare deployment config depl := newDeployment("test-chng-limits-" + uniuri.NewLen(4)) @@ -61,35 +64,56 @@ func TestResourcesChangeLimitsCluster(t *testing.T) { testGroups := []api.ServerGroup{api.ServerGroupCoordinators, api.ServerGroupAgents, api.ServerGroupDBServers} + testCases := []v1.ResourceRequirements{ + { + Limits: v1.ResourceList{ + v1.ResourceCPU: size1, + }, + }, + { + Requests: v1.ResourceList{ + v1.ResourceCPU: size500m, + }, + }, + { + Requests: v1.ResourceList{ + v1.ResourceCPU: size500m, + v1.ResourceMemory: size1Gi, + }, + }, + { + Requests: v1.ResourceList{ + v1.ResourceCPU: size500m, + v1.ResourceMemory: size2Gi, + }, + }, + { + Limits: v1.ResourceList{ + v1.ResourceCPU: size1, + v1.ResourceMemory: size100Gi, + }, + }, + } + for _, testgroup := range testGroups { t.Run(testgroup.AsRole(), func(t *testing.T) { _, err = waitUntilDeployment(deploymentClient, depl.GetName(), ns, deploymentIsReady()) assert.NoError(t, err, fmt.Sprintf("Deployment not running in time: %s", err)) - depl, err = updateDeployment(c, depl.GetName(), ns, func(spec *api.DeploymentSpec) { - gspec := spec.GetServerGroupSpec(testgroup) - gspec.Resources.Limits = v1.ResourceList{ - v1.ResourceCPU: size1CPU, - } - spec.UpdateServerGroupSpec(testgroup, gspec) - }) - assert.NoError(t, err, fmt.Sprintf("Failed to update deployment: %s", err)) - - _, err = waitUntilDeployment(deploymentClient, depl.GetName(), ns, resourcesAsRequested(kubecli, ns)) - assert.NoError(t, err, fmt.Sprintf("Deployment not rotated in time: %s", err)) - - depl, err = updateDeployment(c, depl.GetName(), ns, func(spec *api.DeploymentSpec) { - gspec := spec.GetServerGroupSpec(testgroup) - gspec.Resources.Requests = v1.ResourceList{ - v1.ResourceCPU: size500mCPU, - } - spec.UpdateServerGroupSpec(testgroup, gspec) - }) - assert.NoError(t, err, fmt.Sprintf("Failed to update deployment: %s", err)) + for i, testCase := range testCases { + t.Run(fmt.Sprintf("case-%d", i+1), func(t *testing.T) { + depl, err = updateDeployment(c, depl.GetName(), ns, func(spec *api.DeploymentSpec) { + gspec := spec.GetServerGroupSpec(testgroup) + gspec.Resources = testCase + spec.UpdateServerGroupSpec(testgroup, gspec) + }) + assert.NoError(t, err, fmt.Sprintf("Failed to update deployment: %s", err)) - _, err = waitUntilDeployment(deploymentClient, depl.GetName(), ns, resourcesAsRequested(kubecli, ns)) - assert.NoError(t, err, fmt.Sprintf("Deployment not rotated in time: %s", err)) + _, err = waitUntilDeployment(deploymentClient, depl.GetName(), ns, resourcesAsRequested(kubecli, ns)) + assert.NoError(t, err, fmt.Sprintf("Deployment not rotated in time: %s", err)) + }) + } }) }