Skip to content

Commit 34020db

Browse files
Add tests for deletion policy (#91)
Description of changes: Adds a new set of e2e tests for the deletion policy. `@pytest.mark.parametrize` sets up a matrix for all test combinations of namespace and resource annotations. The new test fixture creates a new namespace (with optional deletion policy annotation) and then creates a bucket (also with optional deletion policy namespace) into it. By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.
1 parent f7ae8f1 commit 34020db

File tree

4 files changed

+166
-8
lines changed

4 files changed

+166
-8
lines changed

test/e2e/requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
acktest @ git+https://github.com/aws-controllers-k8s/test-infra.git@fe6fadc3e1437f2785155c21e27dac3fd8c74f9b
1+
acktest @ git+https://github.com/aws-controllers-k8s/test-infra.git@a8ab6122dbaa6b1f5c75276799c7eec82a67a1ea
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
apiVersion: s3.services.k8s.aws/v1alpha1
2+
kind: Bucket
3+
metadata:
4+
name: $BUCKET_NAME
5+
annotations:
6+
services.k8s.aws/deletion-policy: $DELETION_POLICY
7+
spec:
8+
name: $BUCKET_NAME

test/e2e/tests/test_bucket.py

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
import logging
2020
import re
2121
import boto3
22-
from typing import Generator
22+
from typing import Generator
2323
from dataclasses import dataclass
2424

2525
from acktest.resources import random_suffix_name
@@ -29,7 +29,6 @@
2929
from acktest import tags as tags
3030
from e2e import service_marker, CRD_GROUP, CRD_VERSION, load_s3_resource
3131
from e2e.replacement_values import REPLACEMENT_VALUES
32-
from e2e.bootstrap_resources import BootstrapResources, get_bootstrap_resources
3332

3433
RESOURCE_KIND = "Bucket"
3534
RESOURCE_PLURAL = "buckets"
@@ -61,8 +60,8 @@ def bucket_exists(s3_client, bucket: Bucket) -> bool:
6160

6261
return False
6362

64-
def load_bucket_resource(resource_file_name: str, resource_name: str):
65-
replacements = REPLACEMENT_VALUES.copy()
63+
def load_bucket_resource(resource_file_name: str, resource_name: str, additional_replacements: dict = {}):
64+
replacements = {**REPLACEMENT_VALUES.copy(), **additional_replacements}
6665
replacements["BUCKET_NAME"] = resource_name
6766

6867
resource_data = load_s3_resource(
@@ -72,15 +71,15 @@ def load_bucket_resource(resource_file_name: str, resource_name: str):
7271
logging.debug(resource_data)
7372
return resource_data
7473

75-
def create_bucket(resource_file_name: str) -> Bucket:
74+
def create_bucket(resource_file_name: str, namespace: str = "default", additional_replacements: dict = {}) -> Bucket:
7675
resource_name = random_suffix_name("s3-bucket", 24)
77-
resource_data = load_bucket_resource(resource_file_name, resource_name)
76+
resource_data = load_bucket_resource(resource_file_name, resource_name, additional_replacements)
7877

7978
logging.info(f"Creating bucket {resource_name}")
8079
# Create k8s resource
8180
ref = k8s.CustomResourceReference(
8281
CRD_GROUP, CRD_VERSION, RESOURCE_PLURAL,
83-
resource_name, namespace="default",
82+
resource_name, namespace=namespace,
8483
)
8584
resource_data = k8s.create_custom_resource(ref, resource_data)
8685
k8s.wait_resource_consumed_by_controller(ref)
@@ -100,6 +99,9 @@ def replace_bucket_spec(bucket: Bucket, resource_file_name: str):
10099
time.sleep(MODIFY_WAIT_AFTER_SECONDS)
101100

102101
def delete_bucket(bucket: Bucket):
102+
if not k8s.get_resource_exists(bucket.ref):
103+
return
104+
103105
# Delete k8s resource
104106
_, deleted = k8s.delete_custom_resource(bucket.ref)
105107
assert deleted is True
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License"). You may
4+
# not use this file except in compliance with the License. A copy of the
5+
# License is located at
6+
#
7+
# http://aws.amazon.com/apache2.0/
8+
#
9+
# or in the "license" file accompanying this file. This file is distributed
10+
# on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
11+
# express or implied. See the License for the specific language governing
12+
# permissions and limitations under the License.
13+
14+
"""Integration tests for the deletion policy annotation on Bucket.
15+
"""
16+
17+
from enum import Enum
18+
import pytest
19+
import logging
20+
import itertools
21+
from typing import TYPE_CHECKING, Generator, List, NamedTuple, Tuple
22+
23+
from acktest.resources import random_suffix_name
24+
from acktest.k8s import resource as k8s
25+
from acktest import adoption as adoption
26+
from acktest import tags as tags
27+
from e2e import SERVICE_NAME
28+
from e2e.tests.test_bucket import Bucket, bucket_exists, create_bucket, delete_bucket
29+
30+
DELETION_POLICY_RESOURCE_ANNOTATION_KEY = "services.k8s.aws/deletion-policy"
31+
DELETION_POLICY_NAMESPACE_ANNOTATION_KEY = (
32+
f"{SERVICE_NAME}.services.k8s.aws/deletion-policy"
33+
)
34+
35+
class DeletionPolicy(str, Enum):
36+
NONE = ""
37+
DELETE = "delete"
38+
RETAIN = "retain"
39+
40+
41+
# DeletionPolicyAnnotationTuple represents a tuple of namespace and resource
42+
# deletion policy annotations. These are used when testing all combinations of
43+
# each annotation.
44+
class DeletionPolicyAnnotationTuple(NamedTuple):
45+
namespace: DeletionPolicy
46+
resource: DeletionPolicy
47+
48+
49+
# Create a matrix of combinations for deletion policy annotations that can be
50+
# used as a parameter for tests
51+
DELETION_POLICY_ANNOTATION_COMBINATIONS: List[DeletionPolicyAnnotationTuple] = [
52+
DeletionPolicyAnnotationTuple(r[0], r[1])
53+
for r in itertools.product([p for p in DeletionPolicy], [p for p in DeletionPolicy])
54+
]
55+
56+
# Parameter types are not support by pytest. This adds support for type checking
57+
# the param type.
58+
if TYPE_CHECKING:
59+
60+
class DeletionPolicyFixtureRequest:
61+
param: DeletionPolicyAnnotationTuple
62+
63+
else:
64+
from typing import Any
65+
66+
DeletionPolicyFixtureRequest = Any
67+
68+
69+
def create_deletion_policy_namespace(deletion_policy: DeletionPolicy) -> str:
70+
namespace_name = random_suffix_name("s3-deletion-policy", 24)
71+
annotations = {}
72+
if deletion_policy != DeletionPolicy.NONE:
73+
annotations[DELETION_POLICY_NAMESPACE_ANNOTATION_KEY] = deletion_policy.value
74+
75+
logging.info(f"Creating namespace {namespace_name}")
76+
try:
77+
k8s.create_k8s_namespace(namespace_name, annotations)
78+
except Exception as ex:
79+
return pytest.fail("Failed to create namespace")
80+
81+
return namespace_name
82+
83+
84+
@pytest.fixture(scope="function")
85+
def deletion_policy_namespace_bucket(
86+
request: DeletionPolicyFixtureRequest, s3_client
87+
) -> Generator[Tuple[Bucket, DeletionPolicyAnnotationTuple], None, None]:
88+
bucket_namespace = create_deletion_policy_namespace(request.param.namespace)
89+
90+
bucket = None
91+
try:
92+
if request.param.resource == DeletionPolicy.NONE:
93+
bucket = create_bucket("bucket", namespace=bucket_namespace)
94+
else:
95+
bucket = create_bucket(
96+
"bucket_deletion_policy",
97+
namespace=bucket_namespace,
98+
additional_replacements={
99+
"DELETION_POLICY": request.param.resource.value
100+
},
101+
)
102+
103+
assert k8s.get_resource_exists(bucket.ref)
104+
105+
exists = bucket_exists(s3_client, bucket)
106+
assert exists
107+
except:
108+
if bucket is not None:
109+
delete_bucket(bucket)
110+
return pytest.fail("Bucket failed to create")
111+
112+
yield (bucket, request.param)
113+
114+
delete_bucket(bucket)
115+
116+
exists = bucket_exists(s3_client, bucket)
117+
if exists:
118+
s3_client.delete_bucket(Bucket=bucket.resource_name)
119+
120+
k8s.delete_k8s_namespace(bucket_namespace)
121+
122+
123+
class TestDeletionPolicyBucket:
124+
@pytest.mark.parametrize(
125+
"deletion_policy_namespace_bucket",
126+
DELETION_POLICY_ANNOTATION_COMBINATIONS,
127+
indirect=True,
128+
)
129+
def test_deletion_policy(
130+
self, s3_client, deletion_policy_namespace_bucket
131+
):
132+
(bucket, deletion_policy_annotations) = deletion_policy_namespace_bucket
133+
134+
delete_bucket(bucket)
135+
136+
exists = bucket_exists(s3_client, bucket)
137+
138+
# Assert in order of precedence (resource > namespace)
139+
if deletion_policy_annotations.resource == DeletionPolicy.DELETE:
140+
assert not exists
141+
elif deletion_policy_annotations.resource == DeletionPolicy.RETAIN:
142+
assert exists
143+
elif deletion_policy_annotations.namespace == DeletionPolicy.DELETE:
144+
assert not exists
145+
elif deletion_policy_annotations.namespace == DeletionPolicy.RETAIN:
146+
assert exists
147+
else: # Neither has an annotation
148+
assert not exists

0 commit comments

Comments
 (0)