Skip to content

Commit

Permalink
refactor: Refactor JA integ test resource creation so that we use som…
Browse files Browse the repository at this point in the history
…e resources from the environment (#119)

Signed-off-by: Yutong Li <ryanliyt@amazon.com>
Co-authored-by: Yutong Li <ryanliyt@amazon.com>
  • Loading branch information
YutongLi291 and YutongLi291 authored Jun 26, 2024
1 parent 1cd44c3 commit 50b36f1
Show file tree
Hide file tree
Showing 5 changed files with 48 additions and 123 deletions.
2 changes: 0 additions & 2 deletions src/deadline_test_fixtures/cloudformation/__init__.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.

from .job_attachments_bootstrap_stack import JobAttachmentsBootstrapStack
from .worker_bootstrap_stack import WorkerBootstrapStack

__all__ = [
"JobAttachmentsBootstrapStack",
"WorkerBootstrapStack",
]

This file was deleted.

3 changes: 2 additions & 1 deletion src/deadline_test_fixtures/fixtures.py
Original file line number Diff line number Diff line change
Expand Up @@ -570,10 +570,11 @@ def deploy_job_attachment_resources() -> Generator[JobAttachmentManager, None, N
"""
manager = JobAttachmentManager(
s3_client=boto3.client("s3"),
cfn_client=boto3.client("cloudformation"),
deadline_client=DeadlineClient(boto3.client("deadline")),
account_id=os.environ["SERVICE_ACCOUNT_ID"],
stage=os.getenv("STAGE", "dev"),
bucket_name=os.environ["JOB_ATTACHMENTS_BUCKET"],
farm_id=os.environ["FARM_ID"],
)
manager.deploy_resources()
yield manager
Expand Down
53 changes: 22 additions & 31 deletions src/deadline_test_fixtures/job_attachment_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,18 @@
from __future__ import annotations

from dataclasses import InitVar, dataclass, field

import os
from botocore.client import BaseClient
from botocore.exceptions import ClientError, WaiterError

from .cloudformation import JobAttachmentsBootstrapStack
from .deadline.client import DeadlineClient
from .deadline import (
Farm,
Queue,
)

from .models import JobRunAsUser, PosixSessionUser
from .models import JobAttachmentSettings, JobRunAsUser, PosixSessionUser
from uuid import uuid4


@dataclass
Expand All @@ -23,66 +23,60 @@ class JobAttachmentManager:
"""

s3_client: BaseClient
cfn_client: BaseClient
deadline_client: DeadlineClient

stage: InitVar[str]
account_id: InitVar[str]

stack: JobAttachmentsBootstrapStack = field(init=False)
farm: Farm | None = field(init=False, default=None)
bucket_name: str
farm_id: str

queue: Queue | None = field(init=False, default=None)
queue_with_no_settings: Queue | None = field(init=False, default=None)

def __post_init__(
self,
stage: str,
account_id: str,
):
self.bucket_name = f"job-attachment-integ-test-{stage.lower()}-{account_id}"
self.stack = JobAttachmentsBootstrapStack(
name="JobAttachmentIntegTest",
bucket_name=self.bucket_name,
)
bucket_root_prefix: str = os.environ.get("JA_TEST_ROOT_PREFIX", "") + str(
uuid4()
) # Set the bucket root prefix for this test run to an UUID to avoid async test execution race conditions

def deploy_resources(self):
"""
Deploy all of the resources needed for job attachment integration tests.
"""
try:
self.farm = Farm.create(
client=self.deadline_client,
display_name="job_attachments_test_farm",
)

self.queue = Queue.create(
client=self.deadline_client,
display_name="job_attachments_test_queue",
farm=self.farm,
farm=Farm(self.farm_id),
job_run_as_user=JobRunAsUser(
posix=PosixSessionUser("", ""), runAs="WORKER_AGENT_USER"
),
job_attachments=JobAttachmentSettings(
bucket_name=self.bucket_name, root_prefix=self.bucket_root_prefix
),
)
self.queue_with_no_settings = Queue.create(
client=self.deadline_client,
display_name="job_attachments_test_no_settings_queue",
farm=self.farm,
farm=Farm(self.farm_id),
job_run_as_user=JobRunAsUser(
posix=PosixSessionUser("", ""), runAs="WORKER_AGENT_USER"
),
)
self.stack.deploy(cfn_client=self.cfn_client)

except (ClientError, WaiterError):
# If anything goes wrong, rollback
self.cleanup_resources()
raise

def empty_bucket(self):
def empty_bucket_under_root_prefix(self):
"""
Empty the bucket between session runs
"""
try:
# List up all objects and their versions in the bucket
version_list = self.s3_client.list_object_versions(Bucket=self.bucket_name)
version_list = self.s3_client.list_object_versions(
Bucket=self.bucket_name, Prefix=self.bucket_root_prefix
)
object_list = version_list.get("Versions", []) + version_list.get("DeleteMarkers", [])
# Delete all objects and versions
for obj in object_list:
Expand All @@ -96,13 +90,10 @@ def empty_bucket(self):

def cleanup_resources(self):
"""
Cleanup all of the resources that the test used, except for the stack.
Cleanup all of the resources that the test used
"""
self.empty_bucket()
self.stack.destroy(cfn_client=self.cfn_client)
self.empty_bucket_under_root_prefix()
if self.queue:
self.queue.delete(client=self.deadline_client)
if self.queue_with_no_settings:
self.queue_with_no_settings.delete(client=self.deadline_client)
if self.farm:
self.farm.delete(client=self.deadline_client)
56 changes: 24 additions & 32 deletions test/unit/test_job_attachment_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,41 +17,30 @@ class TestJobAttachmentManager:
Test suite for the job attachment manager
"""

@pytest.fixture(autouse=True)
def mock_farm_cls(self) -> Generator[MagicMock, None, None]:
with patch.object(jam_module, "Farm") as mock:
yield mock

@pytest.fixture(autouse=True)
def mock_queue_cls(self) -> Generator[MagicMock, None, None]:
with patch.object(jam_module, "Queue") as mock:
yield mock

@pytest.fixture(autouse=True)
def mock_stack(self) -> Generator[MagicMock, None, None]:
with patch.object(jam_module, "JobAttachmentsBootstrapStack") as mock:
yield mock.return_value

@pytest.fixture
def job_attachment_manager(
self,
) -> Generator[JobAttachmentManager, None, None]:
with mock_s3():
yield JobAttachmentManager(
s3_client=boto3.client("s3"),
cfn_client=MagicMock(),
deadline_client=DeadlineClient(MagicMock()),
stage="test",
account_id="123456789101",
farm_id="farm-123450981092384",
bucket_name="job-attachment-bucket-name",
)

class TestDeployResources:
def test_deploys_all_resources(
self,
job_attachment_manager: JobAttachmentManager,
mock_farm_cls: MagicMock,
mock_queue_cls: MagicMock,
mock_stack: MagicMock,
):
"""
Tests that all resources are created when deploy_resources is called
Expand All @@ -60,9 +49,7 @@ def test_deploys_all_resources(
job_attachment_manager.deploy_resources()

# THEN
mock_farm_cls.create.assert_called_once()
mock_queue_cls.create.call_count == 2
mock_stack.deploy.assert_called_once()

@pytest.mark.parametrize(
"error",
Expand All @@ -75,18 +62,14 @@ def test_cleans_up_when_error_is_raised(
self,
error: Exception,
job_attachment_manager: JobAttachmentManager,
mock_farm_cls: MagicMock,
mock_queue_cls: MagicMock,
mock_stack: MagicMock,
):
"""
Test that if there's an issue deploying resources, the rest get cleaned up.
"""
# GIVEN
possible_failures: list[MagicMock] = [
mock_farm_cls.create,
mock_queue_cls.create,
mock_stack.deploy,
]
for possible_failure in possible_failures:
possible_failure.side_effect = error
Expand All @@ -107,19 +90,30 @@ def test_cleans_up_when_error_is_raised(
spy_cleanup_resources.assert_called_once()

class TestEmptyBucket:
def test_deletes_all_objects(self, job_attachment_manager: JobAttachmentManager):
def test_deletes_all_objects_under_prefix(
self, job_attachment_manager: JobAttachmentManager
):
# GIVEN
bucket = boto3.resource("s3").Bucket(job_attachment_manager.bucket_name)
bucket.create()
bucket.put_object(Key="test-object", Body="Hello world".encode())
bucket.put_object(Key="test-object-2", Body="Hello world 2".encode())
assert len(list(bucket.objects.all())) == 2
bucket.put_object(
Key=job_attachment_manager.bucket_root_prefix + "/" + "test-object",
Body="Hello world".encode(),
)
bucket.put_object(
Key=job_attachment_manager.bucket_root_prefix + "/" + "test-object-2",
Body="Hello world 2".encode(),
)
bucket.put_object(
Key="differen-prefix" + "/" + "test-object-2", Body="Hello world 2".encode()
)
assert len(list(bucket.objects.all())) == 3

# WHEN
job_attachment_manager.empty_bucket()
job_attachment_manager.empty_bucket_under_root_prefix()

# THEN
assert len(list(bucket.objects.all())) == 0
assert len(list(bucket.objects.all())) == 1

def test_swallows_bucket_doesnt_exist_error(
self, job_attachment_manager: JobAttachmentManager
Expand All @@ -132,7 +126,7 @@ def test_swallows_bucket_doesnt_exist_error(

try:
# WHEN
job_attachment_manager.empty_bucket()
job_attachment_manager.empty_bucket_under_root_prefix()
except ClientError as e:
pytest.fail(
f"JobAttachmentManager.empty_bucket raised an error when it shouldn't have: {e}"
Expand All @@ -158,7 +152,7 @@ def test_raises_any_other_error(
mock_s3_client.list_object_versions.side_effect = exc

# WHEN
job_attachment_manager.empty_bucket()
job_attachment_manager.empty_bucket_under_root_prefix()

# THEN
assert raised_exc.value is exc
Expand All @@ -167,9 +161,7 @@ def test_raises_any_other_error(
def test_cleanup_resources(
self,
job_attachment_manager: JobAttachmentManager,
mock_farm_cls: MagicMock,
mock_queue_cls: MagicMock,
mock_stack: MagicMock,
):
"""
Test that all resources get cleaned up when they exist.
Expand All @@ -178,13 +170,13 @@ def test_cleanup_resources(
job_attachment_manager.deploy_resources()

with patch.object(
job_attachment_manager, "empty_bucket", wraps=job_attachment_manager.empty_bucket
job_attachment_manager,
"empty_bucket_under_root_prefix",
wraps=job_attachment_manager.empty_bucket_under_root_prefix,
) as spy_empty_bucket:
# WHEN
job_attachment_manager.cleanup_resources()

# THEN
spy_empty_bucket.assert_called_once()
mock_stack.destroy.assert_called_once()
mock_queue_cls.create.return_value.delete.call_count == 2
mock_farm_cls.create.return_value.delete.assert_called_once()

0 comments on commit 50b36f1

Please sign in to comment.