From 79bbda4e180afb0fda132052e3cb208e70b21a54 Mon Sep 17 00:00:00 2001 From: Felix Kunde Date: Thu, 28 Jan 2021 09:22:10 +0100 Subject: [PATCH 1/4] configurable container capabilities --- .../crds/operatorconfigurations.yaml | 4 ++ charts/postgres-operator/values-crd.yaml | 4 ++ charts/postgres-operator/values.yaml | 3 + docs/reference/operator_parameters.md | 6 ++ e2e/tests/k8s_api.py | 4 ++ e2e/tests/test_e2e.py | 18 +++++ manifests/configmap.yaml | 1 + manifests/operatorconfiguration.crd.yaml | 4 ++ ...gresql-operator-default-configuration.yaml | 2 + pkg/apis/acid.zalan.do/v1/crds.go | 8 +++ .../v1/operator_configuration_type.go | 1 + .../acid.zalan.do/v1/zz_generated.deepcopy.go | 5 ++ pkg/cluster/k8sres.go | 17 +++++ pkg/cluster/k8sres_test.go | 43 ++++++++++-- pkg/controller/operator_config.go | 1 + pkg/util/config/config.go | 65 ++++++++++--------- 16 files changed, 150 insertions(+), 36 deletions(-) diff --git a/charts/postgres-operator/crds/operatorconfigurations.yaml b/charts/postgres-operator/crds/operatorconfigurations.yaml index a360da0c6..ef9b2c84d 100644 --- a/charts/postgres-operator/crds/operatorconfigurations.yaml +++ b/charts/postgres-operator/crds/operatorconfigurations.yaml @@ -130,6 +130,10 @@ spec: kubernetes: type: object properties: + additional_pod_capabilities: + type: array + items: + type: string cluster_domain: type: string default: "cluster.local" diff --git a/charts/postgres-operator/values-crd.yaml b/charts/postgres-operator/values-crd.yaml index f3115dc8e..42af903cd 100644 --- a/charts/postgres-operator/values-crd.yaml +++ b/charts/postgres-operator/values-crd.yaml @@ -59,6 +59,10 @@ configUsers: super_username: postgres configKubernetes: + # list of additional capabilities for postgres container + # additional_pod_capabilities: + # - "SYS_NICE" + # default DNS domain of K8s cluster where operator is running cluster_domain: cluster.local # additional labels assigned to the cluster objects diff --git a/charts/postgres-operator/values.yaml b/charts/postgres-operator/values.yaml index e8a330d4b..c46e21e1f 100644 --- a/charts/postgres-operator/values.yaml +++ b/charts/postgres-operator/values.yaml @@ -61,6 +61,9 @@ configUsers: super_username: postgres configKubernetes: + # list of additional capabilities for postgres container + # additional_pod_capabilities: "SYS_NICE" + # default DNS domain of K8s cluster where operator is running cluster_domain: cluster.local # additional labels assigned to the cluster objects diff --git a/docs/reference/operator_parameters.md b/docs/reference/operator_parameters.md index 5c5850505..54d13ffc2 100644 --- a/docs/reference/operator_parameters.md +++ b/docs/reference/operator_parameters.md @@ -351,6 +351,12 @@ configuration they are grouped under the `kubernetes` key. used for AWS volume resizing and not required if you don't need that capability. The default is `false`. +* **additional_pod_capabilities** + list of additional capabilities to be added to the postgres container's + SecurityContext (e.g. SYS_NICE etc.). Please, make sure first that the + PodSecruityPolicy allows the capabilities listed here. Otherwise, the + container will not start. The default is empty. + * **master_pod_move_timeout** The period of time to wait for the success of migration of master pods from an unschedulable node. The migration includes Patroni switchovers to diff --git a/e2e/tests/k8s_api.py b/e2e/tests/k8s_api.py index 95e1dc9ad..4fe744469 100644 --- a/e2e/tests/k8s_api.py +++ b/e2e/tests/k8s_api.py @@ -433,6 +433,10 @@ def count_running_pods(self, labels='application=spilo,cluster-name=acid-minimal pods = self.api.core_v1.list_namespaced_pod(namespace, label_selector=labels).items return len(list(filter(lambda x: x.status.phase == 'Running', pods))) + def count_pods_with_container_capabilities(self, capabilities, namespace='default'): + pods = self.api.core_v1.list_namespaced_pod(namespace, label_selector='application=spilo,cluster-name=acid-minimal-cluster').items + return len(list(filter(lambda x: x.spec.containers[0].securityContext.capabilities.add == capabilities, pods))) + def wait_for_pod_failover(self, failover_targets, labels, namespace='default'): pod_phase = 'Failing over' new_pod_node = '' diff --git a/e2e/tests/test_e2e.py b/e2e/tests/test_e2e.py index ecc0b2327..bd71ce982 100644 --- a/e2e/tests/test_e2e.py +++ b/e2e/tests/test_e2e.py @@ -155,6 +155,24 @@ def setUpClass(cls): print('Operator log: {}'.format(k8s.get_operator_log())) raise + @timeout_decorator.timeout(TEST_TIMEOUT_SEC) + def test_additional_pod_capabilities(self): + ''' + Extend postgres container capabilities + ''' + capabilities = ["SYS_NICE","CHOWN"] + patch_capabilities = { + "data": { + "additional_pod_capabilities": ','.join(capabilities), + }, + } + self.k8s.update_config(patch_capabilities) + self.eventuallyEqual(lambda: self.k8s.get_operator_state(), {"0": "idle"}, + "Operator does not get in sync") + + self.eventuallyEqual(lambda: self.k8s.count_pods_with_container_capabilities(capabilities), + 2, "Container capabilities not updated") + @timeout_decorator.timeout(TEST_TIMEOUT_SEC) def test_overwrite_pooler_deployment(self): self.k8s.create_with_kubectl("manifests/minimal-fake-pooler-deployment.yaml") diff --git a/manifests/configmap.yaml b/manifests/configmap.yaml index 3788d8b32..f1bde6811 100644 --- a/manifests/configmap.yaml +++ b/manifests/configmap.yaml @@ -3,6 +3,7 @@ kind: ConfigMap metadata: name: postgres-operator data: + # additional_pod_capabilities: "SYS_NICE" # additional_secret_mount: "some-secret-name" # additional_secret_mount_path: "/some/dir" api_port: "8080" diff --git a/manifests/operatorconfiguration.crd.yaml b/manifests/operatorconfiguration.crd.yaml index 7add1b8c6..7388a765b 100644 --- a/manifests/operatorconfiguration.crd.yaml +++ b/manifests/operatorconfiguration.crd.yaml @@ -126,6 +126,10 @@ spec: kubernetes: type: object properties: + additional_pod_capabilities: + type: array + items: + type: string cluster_domain: type: string default: "cluster.local" diff --git a/manifests/postgresql-operator-default-configuration.yaml b/manifests/postgresql-operator-default-configuration.yaml index 96394976d..18680fbb0 100644 --- a/manifests/postgresql-operator-default-configuration.yaml +++ b/manifests/postgresql-operator-default-configuration.yaml @@ -26,6 +26,8 @@ configuration: replication_username: standby super_username: postgres kubernetes: + # additional_pod_capabilities: + # - "SYS_NICE" cluster_domain: cluster.local cluster_labels: application: spilo diff --git a/pkg/apis/acid.zalan.do/v1/crds.go b/pkg/apis/acid.zalan.do/v1/crds.go index 02d40342f..3c4bc315a 100644 --- a/pkg/apis/acid.zalan.do/v1/crds.go +++ b/pkg/apis/acid.zalan.do/v1/crds.go @@ -968,6 +968,14 @@ var OperatorConfigCRDResourceValidation = apiextv1.CustomResourceValidation{ "kubernetes": { Type: "object", Properties: map[string]apiextv1.JSONSchemaProps{ + "additional_pod_capabilities": { + Type: "array", + Items: &apiextv1.JSONSchemaPropsOrArray{ + Schema: &apiextv1.JSONSchemaProps{ + Type: "string", + }, + }, + }, "cluster_domain": { Type: "string", }, diff --git a/pkg/apis/acid.zalan.do/v1/operator_configuration_type.go b/pkg/apis/acid.zalan.do/v1/operator_configuration_type.go index b55bfa492..cddaa9dd4 100644 --- a/pkg/apis/acid.zalan.do/v1/operator_configuration_type.go +++ b/pkg/apis/acid.zalan.do/v1/operator_configuration_type.go @@ -52,6 +52,7 @@ type KubernetesMetaConfiguration struct { SpiloRunAsUser *int64 `json:"spilo_runasuser,omitempty"` SpiloRunAsGroup *int64 `json:"spilo_runasgroup,omitempty"` SpiloFSGroup *int64 `json:"spilo_fsgroup,omitempty"` + AdditionalPodCapabilities []string `json:"additional_pod_capabilities,omitempty"` WatchedNamespace string `json:"watched_namespace,omitempty"` PDBNameFormat config.StringTemplate `json:"pdb_name_format,omitempty"` EnablePodDisruptionBudget *bool `json:"enable_pod_disruption_budget,omitempty"` diff --git a/pkg/apis/acid.zalan.do/v1/zz_generated.deepcopy.go b/pkg/apis/acid.zalan.do/v1/zz_generated.deepcopy.go index 4bcbd2f5e..81f8a76b5 100644 --- a/pkg/apis/acid.zalan.do/v1/zz_generated.deepcopy.go +++ b/pkg/apis/acid.zalan.do/v1/zz_generated.deepcopy.go @@ -162,6 +162,11 @@ func (in *KubernetesMetaConfiguration) DeepCopyInto(out *KubernetesMetaConfigura *out = new(int64) **out = **in } + if in.AdditionalPodCapabilities != nil { + in, out := &in.AdditionalPodCapabilities, &out.AdditionalPodCapabilities + *out = make([]string, len(*in)) + copy(*out, *in) + } if in.EnablePodDisruptionBudget != nil { in, out := &in.EnablePodDisruptionBudget, &out.EnablePodDisruptionBudget *out = new(bool) diff --git a/pkg/cluster/k8sres.go b/pkg/cluster/k8sres.go index 83098b8a9..56500bb29 100644 --- a/pkg/cluster/k8sres.go +++ b/pkg/cluster/k8sres.go @@ -320,6 +320,19 @@ func getLocalAndBoostrapPostgreSQLParameters(parameters map[string]string) (loca return } +func generateCapabilities(capabilities []string) v1.Capabilities { + if len(capabilities) > 1 { + additionalCapabilities := []v1.Capability{} + for _, capability := range capabilities { + additionalCapabilities = append(additionalCapabilities, v1.Capability(strings.ToUpper(capability))) + } + return v1.Capabilities{ + Add: additionalCapabilities, + } + } + return v1.Capabilities{} +} + func nodeAffinity(nodeReadinessLabel map[string]string, nodeAffinity *v1.NodeAffinity) *v1.Affinity { if len(nodeReadinessLabel) == 0 && nodeAffinity == nil { return nil @@ -430,6 +443,7 @@ func generateContainer( envVars []v1.EnvVar, volumeMounts []v1.VolumeMount, privilegedMode bool, + additionalPodCapabilities v1.Capabilities, ) *v1.Container { return &v1.Container{ Name: name, @@ -456,6 +470,7 @@ func generateContainer( AllowPrivilegeEscalation: &privilegedMode, Privileged: &privilegedMode, ReadOnlyRootFilesystem: util.False(), + Capabilities: &additionalPodCapabilities, }, } } @@ -1148,6 +1163,7 @@ func (c *Cluster) generateStatefulSet(spec *acidv1.PostgresSpec) (*appsv1.Statef deduplicateEnvVars(spiloEnvVars, c.containerName(), c.logger), volumeMounts, c.OpConfig.Resources.SpiloPrivileged, + generateCapabilities(c.OpConfig.AdditionalPodCapabilities), ) // generate container specs for sidecars specified in the cluster manifest @@ -1901,6 +1917,7 @@ func (c *Cluster) generateLogicalBackupJob() (*batchv1beta1.CronJob, error) { envVars, []v1.VolumeMount{}, c.OpConfig.SpiloPrivileged, // use same value as for normal DB pods + v1.Capabilities{}, ) labels := map[string]string{ diff --git a/pkg/cluster/k8sres_test.go b/pkg/cluster/k8sres_test.go index e880fcc3b..12a3c6a9f 100644 --- a/pkg/cluster/k8sres_test.go +++ b/pkg/cluster/k8sres_test.go @@ -973,10 +973,6 @@ func TestTLS(t *testing.T) { makeSpec := func(tls acidv1.TLSDescription) acidv1.PostgresSpec { return acidv1.PostgresSpec{ TeamID: "myapp", NumberOfInstances: 1, - Resources: acidv1.Resources{ - ResourceRequests: acidv1.ResourceDescription{CPU: "1", Memory: "10"}, - ResourceLimits: acidv1.ResourceDescription{CPU: "1", Memory: "10"}, - }, Volume: acidv1.Volume{ Size: "1G", }, @@ -1489,3 +1485,42 @@ func TestGenerateService(t *testing.T) { assert.Equal(t, v1.ServiceExternalTrafficPolicyTypeLocal, service.Spec.ExternalTrafficPolicy) } + +func TestGenerateCapabilities(t *testing.T) { + + testName := "TestGenerateCapabilities" + tests := []struct { + subTest string + configured []string + capabilities v1.Capabilities + err error + }{ + { + subTest: "no capabilities", + configured: nil, + capabilities: v1.Capabilities{}, + err: fmt.Errorf("could not parse capabilities configuration of nil"), + }, + { + subTest: "empty capabilities", + configured: []string{}, + capabilities: v1.Capabilities{}, + err: fmt.Errorf("could not parse empty capabilities configuration"), + }, + { + subTest: "configured capabilities", + configured: []string{"SYS_NICE", "CHOWN"}, + capabilities: v1.Capabilities{ + Add: []v1.Capability{"SYS_NICE", "CHOWN"}, + }, + err: fmt.Errorf("could not parse empty capabilities configuration"), + }, + } + for _, tt := range tests { + caps := generateCapabilities(tt.configured) + if !reflect.DeepEqual(caps, tt.capabilities) { + t.Errorf("%s %s: expected `%v` but got `%v`", + testName, tt.subTest, tt.capabilities, caps) + } + } +} diff --git a/pkg/controller/operator_config.go b/pkg/controller/operator_config.go index 16fb05004..6ef7a2f42 100644 --- a/pkg/controller/operator_config.go +++ b/pkg/controller/operator_config.go @@ -66,6 +66,7 @@ func (c *Controller) importConfigurationFromCRD(fromCRD *acidv1.OperatorConfigur result.SpiloRunAsUser = fromCRD.Kubernetes.SpiloRunAsUser result.SpiloRunAsGroup = fromCRD.Kubernetes.SpiloRunAsGroup result.SpiloFSGroup = fromCRD.Kubernetes.SpiloFSGroup + result.AdditionalPodCapabilities = fromCRD.Kubernetes.AdditionalPodCapabilities result.ClusterDomain = util.Coalesce(fromCRD.Kubernetes.ClusterDomain, "cluster.local") result.WatchedNamespace = fromCRD.Kubernetes.WatchedNamespace result.PDBNameFormat = fromCRD.Kubernetes.PDBNameFormat diff --git a/pkg/util/config/config.go b/pkg/util/config/config.go index 1d8e37bd2..6bc5a6a28 100644 --- a/pkg/util/config/config.go +++ b/pkg/util/config/config.go @@ -23,38 +23,39 @@ type CRD struct { // Resources describes kubernetes resource specific configuration parameters type Resources struct { - ResourceCheckInterval time.Duration `name:"resource_check_interval" default:"3s"` - ResourceCheckTimeout time.Duration `name:"resource_check_timeout" default:"10m"` - PodLabelWaitTimeout time.Duration `name:"pod_label_wait_timeout" default:"10m"` - PodDeletionWaitTimeout time.Duration `name:"pod_deletion_wait_timeout" default:"10m"` - PodTerminateGracePeriod time.Duration `name:"pod_terminate_grace_period" default:"5m"` - SpiloRunAsUser *int64 `json:"spilo_runasuser,omitempty"` - SpiloRunAsGroup *int64 `json:"spilo_runasgroup,omitempty"` - SpiloFSGroup *int64 `name:"spilo_fsgroup"` - PodPriorityClassName string `name:"pod_priority_class_name"` - ClusterDomain string `name:"cluster_domain" default:"cluster.local"` - SpiloPrivileged bool `name:"spilo_privileged" default:"false"` - ClusterLabels map[string]string `name:"cluster_labels" default:"application:spilo"` - InheritedLabels []string `name:"inherited_labels" default:""` - InheritedAnnotations []string `name:"inherited_annotations" default:""` - DownscalerAnnotations []string `name:"downscaler_annotations"` - ClusterNameLabel string `name:"cluster_name_label" default:"cluster-name"` - DeleteAnnotationDateKey string `name:"delete_annotation_date_key"` - DeleteAnnotationNameKey string `name:"delete_annotation_name_key"` - PodRoleLabel string `name:"pod_role_label" default:"spilo-role"` - PodToleration map[string]string `name:"toleration" default:""` - DefaultCPURequest string `name:"default_cpu_request" default:"100m"` - DefaultMemoryRequest string `name:"default_memory_request" default:"100Mi"` - DefaultCPULimit string `name:"default_cpu_limit" default:"1"` - DefaultMemoryLimit string `name:"default_memory_limit" default:"500Mi"` - MinCPULimit string `name:"min_cpu_limit" default:"250m"` - MinMemoryLimit string `name:"min_memory_limit" default:"250Mi"` - PodEnvironmentConfigMap spec.NamespacedName `name:"pod_environment_configmap"` - PodEnvironmentSecret string `name:"pod_environment_secret"` - NodeReadinessLabel map[string]string `name:"node_readiness_label" default:""` - MaxInstances int32 `name:"max_instances" default:"-1"` - MinInstances int32 `name:"min_instances" default:"-1"` - ShmVolume *bool `name:"enable_shm_volume" default:"true"` + ResourceCheckInterval time.Duration `name:"resource_check_interval" default:"3s"` + ResourceCheckTimeout time.Duration `name:"resource_check_timeout" default:"10m"` + PodLabelWaitTimeout time.Duration `name:"pod_label_wait_timeout" default:"10m"` + PodDeletionWaitTimeout time.Duration `name:"pod_deletion_wait_timeout" default:"10m"` + PodTerminateGracePeriod time.Duration `name:"pod_terminate_grace_period" default:"5m"` + SpiloRunAsUser *int64 `json:"spilo_runasuser,omitempty"` + SpiloRunAsGroup *int64 `json:"spilo_runasgroup,omitempty"` + SpiloFSGroup *int64 `name:"spilo_fsgroup"` + PodPriorityClassName string `name:"pod_priority_class_name"` + ClusterDomain string `name:"cluster_domain" default:"cluster.local"` + SpiloPrivileged bool `name:"spilo_privileged" default:"false"` + AdditionalPodCapabilities []string `name:"additional_pod_capabilities" default:""` + ClusterLabels map[string]string `name:"cluster_labels" default:"application:spilo"` + InheritedLabels []string `name:"inherited_labels" default:""` + InheritedAnnotations []string `name:"inherited_annotations" default:""` + DownscalerAnnotations []string `name:"downscaler_annotations"` + ClusterNameLabel string `name:"cluster_name_label" default:"cluster-name"` + DeleteAnnotationDateKey string `name:"delete_annotation_date_key"` + DeleteAnnotationNameKey string `name:"delete_annotation_name_key"` + PodRoleLabel string `name:"pod_role_label" default:"spilo-role"` + PodToleration map[string]string `name:"toleration" default:""` + DefaultCPURequest string `name:"default_cpu_request" default:"100m"` + DefaultMemoryRequest string `name:"default_memory_request" default:"100Mi"` + DefaultCPULimit string `name:"default_cpu_limit" default:"1"` + DefaultMemoryLimit string `name:"default_memory_limit" default:"500Mi"` + MinCPULimit string `name:"min_cpu_limit" default:"250m"` + MinMemoryLimit string `name:"min_memory_limit" default:"250Mi"` + PodEnvironmentConfigMap spec.NamespacedName `name:"pod_environment_configmap"` + PodEnvironmentSecret string `name:"pod_environment_secret"` + NodeReadinessLabel map[string]string `name:"node_readiness_label" default:""` + MaxInstances int32 `name:"max_instances" default:"-1"` + MinInstances int32 `name:"min_instances" default:"-1"` + ShmVolume *bool `name:"enable_shm_volume" default:"true"` } type InfrastructureRole struct { From 0b0ac6717dd8318c98a6a507ca9062479235a9f5 Mon Sep 17 00:00:00 2001 From: Felix Kunde Date: Thu, 28 Jan 2021 13:31:11 +0100 Subject: [PATCH 2/4] revert change on TestTLS --- pkg/cluster/k8sres_test.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pkg/cluster/k8sres_test.go b/pkg/cluster/k8sres_test.go index 12a3c6a9f..6034db214 100644 --- a/pkg/cluster/k8sres_test.go +++ b/pkg/cluster/k8sres_test.go @@ -973,6 +973,10 @@ func TestTLS(t *testing.T) { makeSpec := func(tls acidv1.TLSDescription) acidv1.PostgresSpec { return acidv1.PostgresSpec{ TeamID: "myapp", NumberOfInstances: 1, + Resources: acidv1.Resources{ + ResourceRequests: acidv1.ResourceDescription{CPU: "1", Memory: "10"}, + ResourceLimits: acidv1.ResourceDescription{CPU: "1", Memory: "10"}, + }, Volume: acidv1.Volume{ Size: "1G", }, From f55e48c849342a65ed13f113172332034db98f04 Mon Sep 17 00:00:00 2001 From: Felix Kunde Date: Fri, 29 Jan 2021 08:28:34 +0100 Subject: [PATCH 3/4] fix e2e test --- e2e/tests/k8s_api.py | 8 ++++++-- e2e/tests/test_e2e.py | 3 ++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/e2e/tests/k8s_api.py b/e2e/tests/k8s_api.py index 4fe744469..3fee8058e 100644 --- a/e2e/tests/k8s_api.py +++ b/e2e/tests/k8s_api.py @@ -182,6 +182,10 @@ def count_running_pods(self, labels='application=spilo,cluster-name=acid-minimal pods = self.api.core_v1.list_namespaced_pod(namespace, label_selector=labels).items return len(list(filter(lambda x: x.status.phase == 'Running', pods))) + def count_pods_with_container_capabilities(self, capabilities, labels, namespace='default'): + pods = self.api.core_v1.list_namespaced_pod(namespace, label_selector=labels).items + return len(list(filter(lambda x: x.spec.containers[0].security_context.capabilities.add == capabilities, pods))) + def wait_for_pod_failover(self, failover_targets, labels, namespace='default'): pod_phase = 'Failing over' new_pod_node = '' @@ -433,8 +437,8 @@ def count_running_pods(self, labels='application=spilo,cluster-name=acid-minimal pods = self.api.core_v1.list_namespaced_pod(namespace, label_selector=labels).items return len(list(filter(lambda x: x.status.phase == 'Running', pods))) - def count_pods_with_container_capabilities(self, capabilities, namespace='default'): - pods = self.api.core_v1.list_namespaced_pod(namespace, label_selector='application=spilo,cluster-name=acid-minimal-cluster').items + def count_pods_with_container_capabilities(self, capabilities, labels, namespace='default'): + pods = self.api.core_v1.list_namespaced_pod(namespace, label_selector=labels).items return len(list(filter(lambda x: x.spec.containers[0].securityContext.capabilities.add == capabilities, pods))) def wait_for_pod_failover(self, failover_targets, labels, namespace='default'): diff --git a/e2e/tests/test_e2e.py b/e2e/tests/test_e2e.py index bd71ce982..87bed3baa 100644 --- a/e2e/tests/test_e2e.py +++ b/e2e/tests/test_e2e.py @@ -160,6 +160,7 @@ def test_additional_pod_capabilities(self): ''' Extend postgres container capabilities ''' + cluster_label = 'application=spilo,cluster-name=acid-minimal-cluster' capabilities = ["SYS_NICE","CHOWN"] patch_capabilities = { "data": { @@ -170,7 +171,7 @@ def test_additional_pod_capabilities(self): self.eventuallyEqual(lambda: self.k8s.get_operator_state(), {"0": "idle"}, "Operator does not get in sync") - self.eventuallyEqual(lambda: self.k8s.count_pods_with_container_capabilities(capabilities), + self.eventuallyEqual(lambda: self.k8s.count_pods_with_container_capabilities(capabilities, cluster_label), 2, "Container capabilities not updated") @timeout_decorator.timeout(TEST_TIMEOUT_SEC) From 25ff1b1ddb576df78706a266b0c981a29f518b04 Mon Sep 17 00:00:00 2001 From: Felix Kunde Date: Fri, 29 Jan 2021 12:15:58 +0100 Subject: [PATCH 4/4] minor fix --- e2e/tests/k8s_api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/e2e/tests/k8s_api.py b/e2e/tests/k8s_api.py index 3fee8058e..21e2a16e9 100644 --- a/e2e/tests/k8s_api.py +++ b/e2e/tests/k8s_api.py @@ -439,7 +439,7 @@ def count_running_pods(self, labels='application=spilo,cluster-name=acid-minimal def count_pods_with_container_capabilities(self, capabilities, labels, namespace='default'): pods = self.api.core_v1.list_namespaced_pod(namespace, label_selector=labels).items - return len(list(filter(lambda x: x.spec.containers[0].securityContext.capabilities.add == capabilities, pods))) + return len(list(filter(lambda x: x.spec.containers[0].security_context.capabilities.add == capabilities, pods))) def wait_for_pod_failover(self, failover_targets, labels, namespace='default'): pod_phase = 'Failing over'