diff --git a/go-chaos/internal/deployment.go b/go-chaos/internal/deployment.go new file mode 100644 index 000000000..a64da78c5 --- /dev/null +++ b/go-chaos/internal/deployment.go @@ -0,0 +1,51 @@ +// Copyright 2022 Camunda Services GmbH +// +// 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. + +package internal + +import ( + "context" + "errors" + "fmt" + + v12 "k8s.io/api/apps/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func (c K8Client) getGatewayDeployment() (*v12.Deployment, error) { + listOptions := metav1.ListOptions{ + LabelSelector: getSelfManagedGatewayLabels(), + } + deploymentList, err := c.Clientset.AppsV1().Deployments(c.GetCurrentNamespace()).List(context.TODO(), listOptions) + if err != nil { + return nil, err + } + + if deploymentList == nil || len(deploymentList.Items) <= 0 { + // lets check for SaaS setup + listOptions.LabelSelector = getSaasGatewayLabels() + deploymentList, err = c.Clientset.AppsV1().Deployments(c.GetCurrentNamespace()).List(context.TODO(), listOptions) + if err != nil { + return nil, err + } + + // here it is currently hard to distingush between not existing and embedded gateway; + // since we don't use embedded gateway in our current chaos setup I would not support it right now here + if deploymentList == nil || len(deploymentList.Items) <= 0 { + return nil, errors.New(fmt.Sprintf("Expected to find standalone gateway deployment in namespace %s, but none found!", c.GetCurrentNamespace())) + } + } + + return &deploymentList.Items[0], err +} diff --git a/go-chaos/internal/deployment_test.go b/go-chaos/internal/deployment_test.go new file mode 100644 index 000000000..d5c557323 --- /dev/null +++ b/go-chaos/internal/deployment_test.go @@ -0,0 +1,96 @@ +// Copyright 2022 Camunda Services GmbH +// +// 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. + +package internal + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func Test_ShouldReturnTrueForRunningGatewayDeployment(t *testing.T) { + // given + k8Client := CreateFakeClient() + selector, err := metav1.ParseToLabelSelector(getSelfManagedGatewayLabels()) + require.NoError(t, err) + k8Client.CreateDeploymentWithLabelsAndName(t, selector, "gateway") + + // when + running, err := k8Client.checkIfGatewaysAreRunning() + + // then + require.NoError(t, err) + assert.Equal(t, true, running) +} + +func Test_ShouldReturnTrueForRunningSaaSGatewayDeployment(t *testing.T) { + // given + k8Client := CreateFakeClient() + selector, err := metav1.ParseToLabelSelector(getSaasGatewayLabels()) + require.NoError(t, err) + k8Client.CreateDeploymentWithLabelsAndName(t, selector, "gateway") + + // when + running, err := k8Client.checkIfGatewaysAreRunning() + + // then + require.NoError(t, err) + assert.Equal(t, true, running) +} + +func Test_ShouldReturnErrorForNonExistingDeployment(t *testing.T) { + // given + k8Client := CreateFakeClient() + + // when + running, err := k8Client.checkIfGatewaysAreRunning() + + // then + require.Error(t, err) + require.Contains(t, err.Error(), "Expected to find standalone gateway deployment") + assert.Equal(t, false, running) +} + +func Test_ShouldReturnGatewayDeployment(t *testing.T) { + // given + k8Client := CreateFakeClient() + selector, err := metav1.ParseToLabelSelector(getSelfManagedGatewayLabels()) + require.NoError(t, err) + k8Client.CreateDeploymentWithLabelsAndName(t, selector, "gateway") + + // when + deployment, err := k8Client.getGatewayDeployment() + + // then + require.NoError(t, err) + assert.Equal(t, "gateway", deployment.Name) +} + +func Test_ShouldReturnSaaSGatewayDeployment(t *testing.T) { + // given + k8Client := CreateFakeClient() + selector, err := metav1.ParseToLabelSelector(getSaasGatewayLabels()) + require.NoError(t, err) + k8Client.CreateDeploymentWithLabelsAndName(t, selector, "gateway") + + // when + deployment, err := k8Client.getGatewayDeployment() + + // then + require.NoError(t, err) + assert.Equal(t, "gateway", deployment.Name) +} diff --git a/go-chaos/internal/helper_test.go b/go-chaos/internal/helper_test.go index 0ae0d0c4c..0d0f5bf16 100644 --- a/go-chaos/internal/helper_test.go +++ b/go-chaos/internal/helper_test.go @@ -68,12 +68,22 @@ func (c K8Client) CreatePodWithLabelsAndName(t *testing.T, selector *metav1.Labe require.NoError(t, err) } -func (c K8Client) CreateDeploymentWithLabelsAndName(t *testing.T, selector *metav1.LabelSelector, podName string) { +func (c K8Client) CreateDeploymentWithLabelsAndName(t *testing.T, selector *metav1.LabelSelector, name string) { _, err := c.Clientset.AppsV1().Deployments(c.GetCurrentNamespace()).Create(context.TODO(), &v12.Deployment{ - ObjectMeta: metav1.ObjectMeta{Labels: selector.MatchLabels, Name: podName}, + ObjectMeta: metav1.ObjectMeta{Labels: selector.MatchLabels, Name: name}, Spec: v12.DeploymentSpec{}, Status: v12.DeploymentStatus{}, }, metav1.CreateOptions{}) require.NoError(t, err) } + +func (c K8Client) CreateStatefulSetWithLabelsAndName(t *testing.T, selector *metav1.LabelSelector, name string) { + _, err := c.Clientset.AppsV1().StatefulSets(c.GetCurrentNamespace()).Create(context.TODO(), &v12.StatefulSet{ + ObjectMeta: metav1.ObjectMeta{Labels: selector.MatchLabels, Name: name}, + Spec: v12.StatefulSetSpec{}, + Status: v12.StatefulSetStatus{}, + }, metav1.CreateOptions{}) + + require.NoError(t, err) +} diff --git a/go-chaos/internal/network.go b/go-chaos/internal/network.go index 6aeb8cdba..1bc46218d 100644 --- a/go-chaos/internal/network.go +++ b/go-chaos/internal/network.go @@ -27,21 +27,39 @@ import ( func (c K8Client) ApplyNetworkPatch() error { - // todo support cloud - listOptions := metav1.ListOptions{ - LabelSelector: "app=camunda-platform", - } - - statefulSetList, err := c.Clientset.AppsV1().StatefulSets(c.GetCurrentNamespace()).List(context.TODO(), listOptions) + statefulSet, err := c.GetZeebeStatefulSet() if err != nil { return err } - if len(statefulSetList.Items) <= 0 { - return errors.New(fmt.Sprintf("Expected to find the Zeebe statefulset but nothing was found in namespace %s", c.GetCurrentNamespace())) - } + patch := []byte(`{ + "spec":{ + "template":{ + "spec":{ + "containers":[ + { + "name": "zeebe", + "securityContext":{ + "capabilities":{ + "add":["NET_ADMIN"] + } + } + }] + } + } + } + }`) + + _, err = c.Clientset.AppsV1().StatefulSets(c.GetCurrentNamespace()).Patch(context.TODO(), statefulSet.Name, types.StrategicMergePatchType, patch, metav1.PatchOptions{}) + return err +} - statefulSet := statefulSetList.Items[0] +func (c K8Client) ApplyNetworkPatchOnGateway() error { + + deployment, err := c.getGatewayDeployment() + if err != nil { + return err + } patch := []byte(`{ "spec":{ @@ -61,7 +79,7 @@ func (c K8Client) ApplyNetworkPatch() error { } }`) - _, err = c.Clientset.AppsV1().StatefulSets(c.GetCurrentNamespace()).Patch(context.TODO(), statefulSet.Name, types.StrategicMergePatchType, patch, metav1.PatchOptions{}) + _, err = c.Clientset.AppsV1().Deployments(c.GetCurrentNamespace()).Patch(context.TODO(), deployment.Name, types.StrategicMergePatchType, patch, metav1.PatchOptions{}) return err } diff --git a/go-chaos/internal/network_test.go b/go-chaos/internal/network_test.go new file mode 100644 index 000000000..8a9d0db27 --- /dev/null +++ b/go-chaos/internal/network_test.go @@ -0,0 +1,62 @@ +// Copyright 2022 Camunda Services GmbH +// +// 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. + +package internal + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func Test_ShouldApplyNetworkPatchOnStatefulSet(t *testing.T) { + // given + k8Client := CreateFakeClient() + k8Client.CreateStatefulSetWithLabelsAndName(t, &metav1.LabelSelector{}, "zeebe") + + // when + err := k8Client.ApplyNetworkPatch() + + // then + require.NoError(t, err) + + statefulSet, err := k8Client.GetZeebeStatefulSet() + require.NoError(t, err) + + require.NotNil(t, statefulSet) + assert.Equal(t, v1.Capability("NET_ADMIN"), statefulSet.Spec.Template.Spec.Containers[0].SecurityContext.Capabilities.Add[0], "Expected to add capability to statefulset") +} + +func Test_ShouldApplyNetworkPatchOnDeployment(t *testing.T) { + // given + k8Client := CreateFakeClient() + selector, err := metav1.ParseToLabelSelector(getSaasGatewayLabels()) + require.NoError(t, err) + k8Client.CreateDeploymentWithLabelsAndName(t, selector, "gateway") + + // when + err = k8Client.ApplyNetworkPatchOnGateway() + + // then + require.NoError(t, err) + + deployment, err := k8Client.getGatewayDeployment() + require.NoError(t, err) + + require.NotNil(t, deployment) + assert.Equal(t, v1.Capability("NET_ADMIN"), deployment.Spec.Template.Spec.Containers[0].SecurityContext.Capabilities.Add[0], "Expected to add capability to deployment") +} diff --git a/go-chaos/internal/pods.go b/go-chaos/internal/pods.go index 3198be1bb..2425e9ede 100644 --- a/go-chaos/internal/pods.go +++ b/go-chaos/internal/pods.go @@ -161,31 +161,11 @@ func (c K8Client) checkIfBrokersAreRunning() (bool, error) { } func (c K8Client) checkIfGatewaysAreRunning() (bool, error) { - listOptions := metav1.ListOptions{ - LabelSelector: getSelfManagedGatewayLabels(), - } - deploymentList, err := c.Clientset.AppsV1().Deployments(c.GetCurrentNamespace()).List(context.TODO(), listOptions) + deployment, err := c.getGatewayDeployment() if err != nil { return false, err } - if deploymentList == nil || len(deploymentList.Items) <= 0 { - // lets check for SaaS setup - listOptions.LabelSelector = getSaasGatewayLabels() - deploymentList, err = c.Clientset.AppsV1().Deployments(c.GetCurrentNamespace()).List(context.TODO(), listOptions) - if err != nil { - return false, err - } - - // here it is currently hard to distingush between not existing and embedded gateway; - // since we don't use embedded gateway in our current chaos setup I would not support it right now here - if deploymentList == nil || len(deploymentList.Items) <= 0 { - return false, errors.New(fmt.Sprintf("Expected to find standalone gateway deployment in namespace %s, but none found!", c.GetCurrentNamespace())) - } - } - - deployment := deploymentList.Items[0] - if deployment.Status.UnavailableReplicas > 0 { if Verbosity { fmt.Printf("Gateway deployment not fully available. [Available replicas: %d/%d]\n", deployment.Status.AvailableReplicas, deployment.Status.Replicas) diff --git a/go-chaos/internal/pods_test.go b/go-chaos/internal/pods_test.go index 3002cec44..b3860b08c 100644 --- a/go-chaos/internal/pods_test.go +++ b/go-chaos/internal/pods_test.go @@ -201,46 +201,3 @@ func Test_GetEmbeddedGateway(t *testing.T) { require.NotEmpty(t, names) assert.Equal(t, "broker", names[0], "Expected to retrieve broker") } - -func Test_ShouldReturnTrueForRunningGatewayDeployment(t *testing.T) { - // given - k8Client := CreateFakeClient() - selector, err := metav1.ParseToLabelSelector(getSelfManagedGatewayLabels()) - require.NoError(t, err) - k8Client.CreateDeploymentWithLabelsAndName(t, selector, "gateway") - - // when - running, err := k8Client.checkIfGatewaysAreRunning() - - // then - require.NoError(t, err) - assert.Equal(t, true, running) -} - -func Test_ShouldReturnTrueForRunningSaaSGatewayDeployment(t *testing.T) { - // given - k8Client := CreateFakeClient() - selector, err := metav1.ParseToLabelSelector(getSaasGatewayLabels()) - require.NoError(t, err) - k8Client.CreateDeploymentWithLabelsAndName(t, selector, "gateway") - - // when - running, err := k8Client.checkIfGatewaysAreRunning() - - // then - require.NoError(t, err) - assert.Equal(t, true, running) -} - -func Test_ShouldReturnErrorForNonExistingDeployment(t *testing.T) { - // given - k8Client := CreateFakeClient() - - // when - running, err := k8Client.checkIfGatewaysAreRunning() - - // then - require.Error(t, err) - require.Contains(t, err.Error(), "Expected to find standalone gateway deployment") - assert.Equal(t, false, running) -} diff --git a/go-chaos/internal/saas.go b/go-chaos/internal/saas.go new file mode 100644 index 000000000..932842ee1 --- /dev/null +++ b/go-chaos/internal/saas.go @@ -0,0 +1,57 @@ +// Copyright 2022 Camunda Services GmbH +// +// 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. + +package internal + +import ( + "context" + "fmt" + "strings" + + k8sErrors "k8s.io/apimachinery/pkg/api/errors" + meta "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/util/retry" +) + +func (c K8Client) PauseReconciliation() error { + return c.setPauseFlag(true) +} + +func (c K8Client) ResumeReconciliation() error { + return c.setPauseFlag(false) +} + +// Sets the pause reconciliation flag in SaaS environment +// this is necessary to make changes at the deployed resources +// otherwise it gets overwritten on the next reconcilation loop by the controller +// Based on https://github.com/camunda-cloud/zeebe-controller-k8s#turning-the-controller-off +func (c K8Client) setPauseFlag(pauseEnabled bool) error { + ctx := context.TODO() + namespace := c.GetCurrentNamespace() + clusterId := strings.TrimSuffix(namespace, "-zeebe") + zeebeCrd := schema.GroupVersionResource{Group: "cloud.camunda.io", Version: "v1alpha1", Resource: "zeebeclusters"} + payload := fmt.Sprintf(`{"metadata": {"labels": {"cloud.camunda.io/pauseReconciliation": "%t"}}}`, pauseEnabled) + err := retry.RetryOnConflict(retry.DefaultBackoff, func() error { + _, err := c.DynamicClient.Resource(zeebeCrd).Patch(ctx, clusterId, types.MergePatchType, []byte(payload), meta.PatchOptions{}) + return err + }) + if k8sErrors.IsNotFound(err) { + // No zb resource found so probably not Saas. Ignore for now. + fmt.Printf("Did not find zeebe cluster to pause reconciliation, ignoring. %s\n", err) + return nil + } + return err +} diff --git a/go-chaos/internal/cluster.go b/go-chaos/internal/statefulset.go similarity index 70% rename from go-chaos/internal/cluster.go rename to go-chaos/internal/statefulset.go index 820b8c2c5..674413a56 100644 --- a/go-chaos/internal/cluster.go +++ b/go-chaos/internal/statefulset.go @@ -18,15 +18,12 @@ import ( "context" "errors" "fmt" + "time" + v1 "k8s.io/api/apps/v1" - k8sErrors "k8s.io/apimachinery/pkg/api/errors" meta "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/wait" "k8s.io/client-go/util/retry" - "strings" - "time" ) func (c K8Client) GetZeebeStatefulSet() (*v1.StatefulSet, error) { @@ -87,29 +84,3 @@ func (c K8Client) ScaleZeebeCluster(replicas int) (int, error) { return initialReplicas, err } - -func (c K8Client) PauseReconciliation() error { - return c.setPauseFlag(true) -} - -func (c K8Client) ResumeReconciliation() error { - return c.setPauseFlag(false) -} - -func (c K8Client) setPauseFlag(pauseEnabled bool) error { - ctx := context.TODO() - namespace := c.GetCurrentNamespace() - clusterId := strings.TrimSuffix(namespace, "-zeebe") - zeebeCrd := schema.GroupVersionResource{Group: "cloud.camunda.io", Version: "v1alpha1", Resource: "zeebeclusters"} - payload := fmt.Sprintf(`{"metadata": {"labels": {"cloud.camunda.io/pauseReconciliation": "%t"}}}`, pauseEnabled) - err := retry.RetryOnConflict(retry.DefaultBackoff, func() error { - _, err := c.DynamicClient.Resource(zeebeCrd).Patch(ctx, clusterId, types.MergePatchType, []byte(payload), meta.PatchOptions{}) - return err - }) - if k8sErrors.IsNotFound(err) { - // No zb resource found so probably not Saas. Ignore for now. - fmt.Printf("Did not find zeebe cluster to pause reconciliation, ignoring. %s\n", err) - return nil - } - return err -} diff --git a/go-chaos/internal/statefulset_test.go b/go-chaos/internal/statefulset_test.go new file mode 100644 index 000000000..02b9de048 --- /dev/null +++ b/go-chaos/internal/statefulset_test.go @@ -0,0 +1,66 @@ +// Copyright 2022 Camunda Services GmbH +// +// 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. + +package internal + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func Test_ShouldReturnSelfManagedStatefulSet(t *testing.T) { + // given + k8Client := CreateFakeClient() + selector, err := metav1.ParseToLabelSelector(getSelfManagedZeebeStatefulSetLabels()) + require.NoError(t, err) + k8Client.CreateStatefulSetWithLabelsAndName(t, selector, "zeebe") + + // when + statefulset, err := k8Client.GetZeebeStatefulSet() + + // then + require.NoError(t, err) + assert.NotNil(t, statefulset) + assert.Equal(t, "zeebe", statefulset.Name) +} + +func Test_ShouldReturnSaaSStatefulSet(t *testing.T) { + // given + k8Client := CreateFakeClient() + k8Client.CreateStatefulSetWithLabelsAndName(t, &metav1.LabelSelector{}, "zeebe") + + // when + statefulset, err := k8Client.GetZeebeStatefulSet() + + // then + require.NoError(t, err) + assert.NotNil(t, statefulset) + assert.Equal(t, "zeebe", statefulset.Name) +} + +func Test_ShouldReturnErrorForNonExistingStatefulSet(t *testing.T) { + // given + k8Client := CreateFakeClient() + + // when + statefulset, err := k8Client.GetZeebeStatefulSet() + + // then + require.Error(t, err) + require.Contains(t, err.Error(), "statefulsets.apps \"zeebe\" not found") + assert.Nil(t, statefulset) +}