From afbc5fb88f981976d60635cfb84d409eda65ffb8 Mon Sep 17 00:00:00 2001
From: Daniel Neilson <53624638+ddneilson@users.noreply.github.com>
Date: Thu, 15 Aug 2024 16:13:43 -0500
Subject: [PATCH] refactor!: improve support for ec2 instance workers (#143)
Problem:
The PosixInstanceWorker and WindowsInstanceWorker classes implement a
CMF Worker based on an ec2 host. In both cases they start with an empty
AMI image (AL2023 or Win Server 2022) then set up users, install the
agent, etc. Tests that need to use an AMI that already has users and the
agent installed end up having to hack around a lot of assumptions in
these classes, or reimplement everything for an ec2-based worker from
scratch.
Solution:
Refactor the PosixInstanceWorker and WindowsInstanceWorker to split them
into os-specific base classes that contain all of the generic worker
implementation stuff (starting the worker, getting its id, etc), and a
derived class that implements the user-setup, agent install, and the
like. This makes space for tests that use an AMI with the users & agent
pre-setup to derive from the os-specific base classes to get the generic
ec2-worker test functionality.
BREAKING CHANGE: PosixInstanceWorker has been renamed to
PosixInstanceBuildWorker, and WindowsInstanceWorker has been renamed to
WindowsInstanceBuildWorker.
Signed-off-by: Daniel Neilson <53624638+ddneilson@users.noreply.github.com>
---
src/deadline_test_fixtures/__init__.py | 12 +-
.../deadline/__init__.py | 12 +-
src/deadline_test_fixtures/deadline/worker.py | 363 ++++++++++--------
src/deadline_test_fixtures/fixtures.py | 8 +-
test/unit/deadline/test_worker.py | 32 +-
5 files changed, 248 insertions(+), 179 deletions(-)
diff --git a/src/deadline_test_fixtures/__init__.py b/src/deadline_test_fixtures/__init__.py
index 2ea69c0..9b9db10 100644
--- a/src/deadline_test_fixtures/__init__.py
+++ b/src/deadline_test_fixtures/__init__.py
@@ -7,8 +7,10 @@
DeadlineWorkerConfiguration,
DockerContainerWorker,
EC2InstanceWorker,
- WindowsInstanceWorker,
- PosixInstanceWorker,
+ WindowsInstanceWorkerBase,
+ WindowsInstanceBuildWorker,
+ PosixInstanceWorkerBase,
+ PosixInstanceBuildWorker,
Job,
Farm,
Fleet,
@@ -53,8 +55,10 @@
"DeadlineWorkerConfiguration",
"DockerContainerWorker",
"EC2InstanceWorker",
- "WindowsInstanceWorker",
- "PosixInstanceWorker",
+ "WindowsInstanceWorkerBase",
+ "WindowsInstanceBuildWorker",
+ "PosixInstanceWorkerBase",
+ "PosixInstanceBuildWorker",
"Farm",
"Fleet",
"Job",
diff --git a/src/deadline_test_fixtures/deadline/__init__.py b/src/deadline_test_fixtures/deadline/__init__.py
index c904e4d..b2a5dca 100644
--- a/src/deadline_test_fixtures/deadline/__init__.py
+++ b/src/deadline_test_fixtures/deadline/__init__.py
@@ -16,8 +16,10 @@
DeadlineWorkerConfiguration,
DockerContainerWorker,
EC2InstanceWorker,
- PosixInstanceWorker,
- WindowsInstanceWorker,
+ PosixInstanceWorkerBase,
+ PosixInstanceBuildWorker,
+ WindowsInstanceWorkerBase,
+ WindowsInstanceBuildWorker,
PipInstall,
)
@@ -29,8 +31,10 @@
"DeadlineWorkerConfiguration",
"DockerContainerWorker",
"EC2InstanceWorker",
- "WindowsInstanceWorker",
- "PosixInstanceWorker",
+ "WindowsInstanceWorkerBase",
+ "WindowsInstanceBuildWorker",
+ "PosixInstanceWorkerBase",
+ "PosixInstanceBuildWorker",
"Farm",
"Fleet",
"Job",
diff --git a/src/deadline_test_fixtures/deadline/worker.py b/src/deadline_test_fixtures/deadline/worker.py
index e6117ef..b39c26a 100644
--- a/src/deadline_test_fixtures/deadline/worker.py
+++ b/src/deadline_test_fixtures/deadline/worker.py
@@ -409,13 +409,15 @@ def ami_id(self) -> str:
@dataclass
-class WindowsInstanceWorker(EC2InstanceWorker):
+class WindowsInstanceWorkerBase(EC2InstanceWorker):
+ """Base class from which Windows ec2 test instances are derived.
+
+ The methods in this base class are written with two cases of worker hosts in mind:
+ 1. A host that is based on a stock Windows server AMI, with no Deadline-anything installed, that
+ must install the worker agent and the like during boot-up.
+ 2. A host that already has the worker agent, job/agent users, and the like baked into
+ the host AMI in a location & manner that may differ from case (1).
"""
- This class represents a Windows EC2 Worker Host.
- Any commands must be written in Powershell.
- """
-
- WIN2022_AMI_NAME: ClassVar[str] = "Windows_Server-2022-English-Full-Base"
def ssm_document_name(self) -> str:
return "AWS-RunPowerShellScript"
@@ -434,7 +436,7 @@ def _start_worker_agent(self) -> None:
[
"echo 'Running Get-Process to check if the agent is running'",
'for($i=1; $i -le 30 -and "" -ne $err ; $i++){sleep $i; Get-Process pythonservice -ErrorVariable err}',
- "IF(Get-Process pythonservice){echo 'service is running'}ELSE{exit 1}",
+ "IF(Get-Process pythonservice){echo '+++SERVICE IS RUNNING+++'}ELSE{echo '+++SERVICE NOT RUNNING+++'; Get-Content -Encoding utf8 C:\ProgramData\Amazon\Deadline\Logs\worker-agent-bootstrap.log,C:\ProgramData\Amazon\Deadline\Logs\worker-agent.log; exit 1}",
]
),
)
@@ -443,31 +445,17 @@ def _start_worker_agent(self) -> None:
self.worker_id = self.get_worker_id()
- def configure_worker_command(self, *, config: DeadlineWorkerConfiguration) -> str:
- """Get the command to configure the Worker. This must be run as root."""
+ def configure_worker_common(self, *, config: DeadlineWorkerConfiguration) -> str:
+ """Get the command to configure the Worker. This must be run as Administrator.
+ This cannot assume that the agent user exists.
+ """
- cmds = [
- config.worker_agent_install.install_command_for_windows,
- *(config.pre_install_commands or []),
- # fmt: off
- (
- "install-deadline-worker "
- + "-y "
- + f"--farm-id {config.farm_id} "
- + f"--fleet-id {config.fleet.id} "
- + f"--region {config.region} "
- + f"--user {config.agent_user} "
- + f"{'--allow-shutdown ' if config.allow_shutdown else ''}"
- + "--start"
- ),
- # fmt: on
- ]
+ cmds = []
if config.service_model_path:
cmds.append(
f"aws configure add-model --service-model file://{config.service_model_path} --service-name deadline; "
f"Copy-Item -Path ~\\.aws\\* -Destination C:\\Users\\Administrator\\.aws\\models -Recurse; "
- f"Copy-Item -Path ~\\.aws\\* -Destination C:\\Users\\{config.agent_user}\\.aws\\models -Recurse; "
f"Copy-Item -Path ~\\.aws\\* -Destination C:\\Users\\{config.job_user}\\.aws\\models -Recurse"
)
@@ -475,8 +463,7 @@ def configure_worker_command(self, *, config: DeadlineWorkerConfiguration) -> st
LOG.info(
f"Using DEADLINE_WORKER_ALLOW_INSTANCE_PROFILE: {os.environ.get('DEADLINE_WORKER_ALLOW_INSTANCE_PROFILE')}"
)
- cmds.insert(
- 0,
+ cmds.append(
f"[System.Environment]::SetEnvironmentVariable('DEADLINE_WORKER_ALLOW_INSTANCE_PROFILE', '{os.environ.get('DEADLINE_WORKER_ALLOW_INSTANCE_PROFILE')}', [System.EnvironmentVariableTarget]::Machine); "
"$env:DEADLINE_WORKER_ALLOW_INSTANCE_PROFILE = [System.Environment]::GetEnvironmentVariable('DEADLINE_WORKER_ALLOW_INSTANCE_PROFILE','Machine')",
)
@@ -492,44 +479,6 @@ def configure_worker_command(self, *, config: DeadlineWorkerConfiguration) -> st
return "; ".join(cmds)
- def userdata(self, s3_files) -> str:
- copy_s3_command = ""
- job_users_cmds = []
-
- if s3_files:
- copy_s3_command = " ; ".join([f"aws s3 cp {s3_uri} {dst}" for s3_uri, dst in s3_files])
-
- if self.configuration.windows_job_users:
- for job_user in self.configuration.windows_job_users:
- job_users_cmds.append(
- f"New-LocalUser -Name {job_user} -Password $password -FullName {job_user} -Description {job_user}"
- )
- job_users_cmds.append(
- f"$Cred = New-Object System.Management.Automation.PSCredential {job_user}, $password"
- )
- job_users_cmds.append(
- 'Start-Process cmd.exe -Credential $Cred -ArgumentList "/C" -LoadUserProfile -NoNewWindow'
- )
-
- configure_job_users = "\n".join(job_users_cmds)
-
- userdata = f"""
- Invoke-WebRequest -Uri "https://www.python.org/ftp/python/3.11.9/python-3.11.9-amd64.exe" -OutFile "C:\python-3.11.9-amd64.exe"
- $installerHash=(Get-FileHash "C:\python-3.11.9-amd64.exe" -Algorithm "MD5")
- $expectedHash="e8dcd502e34932eebcaf1be056d5cbcd"
- if ($installerHash.Hash -ne $expectedHash) {{ throw "Could not verify Python installer." }}
- Start-Process -FilePath "C:\python-3.11.9-amd64.exe" -ArgumentList "/quiet InstallAllUsers=1 PrependPath=1 AppendPath=1" -Wait
- Invoke-WebRequest -Uri "https://awscli.amazonaws.com/AWSCLIV2.msi" -Outfile "C:\AWSCLIV2.msi"
- Start-Process msiexec.exe -ArgumentList "/i C:\AWSCLIV2.msi /quiet" -Wait
- $env:Path = [System.Environment]::GetEnvironmentVariable("Path","Machine")
- $secret = aws secretsmanager get-secret-value --secret-id WindowsPasswordSecret --query SecretString --output text | ConvertFrom-Json
- $password = ConvertTo-SecureString -String $($secret.password) -AsPlainText -Force
- {copy_s3_command}
- {configure_job_users}
- """
-
- return userdata
-
def start_worker_service(self):
LOG.info("Sending command to start the Worker Agent service")
@@ -543,14 +492,6 @@ def stop_worker_service(self):
assert cmd_result.exit_code == 0, f"Failed to stop Worker Agent service: : {cmd_result}"
- def ami_ssm_param_name(self) -> str:
- # Grab the latest Windows Server 2022 AMI
- # https://aws.amazon.com/blogs/mt/query-for-the-latest-windows-ami-using-systems-manager-parameter-store/
- ami_ssm_param: str = (
- f"/aws/service/ami-windows-latest/{WindowsInstanceWorker.WIN2022_AMI_NAME}"
- )
- return ami_ssm_param
-
def get_worker_id(self) -> str:
cmd_result = self.send_command(
" ; ".join(
@@ -571,13 +512,99 @@ def get_worker_id(self) -> str:
@dataclass
-class PosixInstanceWorker(EC2InstanceWorker):
+class WindowsInstanceBuildWorker(WindowsInstanceWorkerBase):
"""
- This class represents a Linux EC2 Worker Host.
- Any commands must be written in Bash.
+ This class represents a Windows EC2 Worker Host.
+ Any commands must be written in Powershell.
"""
- AL2023_AMI_NAME: ClassVar[str] = "al2023-ami-kernel-6.1-x86_64"
+ WIN2022_AMI_NAME: ClassVar[str] = "Windows_Server-2022-English-Full-Base"
+
+ def configure_worker_command(self, *, config: DeadlineWorkerConfiguration) -> str:
+ """Get the command to configure the Worker. This must be run as Administrator."""
+ cmds = [
+ "Set-PSDebug -trace 1",
+ self.configure_worker_common(config=config),
+ config.worker_agent_install.install_command_for_windows,
+ *(config.pre_install_commands or []),
+ # fmt: off
+ (
+ "install-deadline-worker "
+ + "-y "
+ + f"--farm-id {config.farm_id} "
+ + f"--fleet-id {config.fleet.id} "
+ + f"--region {config.region} "
+ + f"--user {config.agent_user} "
+ + f"{'--allow-shutdown ' if config.allow_shutdown else ''}"
+ + "--start"
+ ),
+ # fmt: on
+ ]
+
+ if config.service_model_path:
+ cmds.append(
+ f"Copy-Item -Path ~\\.aws\\* -Destination C:\\Users\\{config.agent_user}\\.aws\\models -Recurse; "
+ )
+
+ return "; ".join(cmds)
+
+ def userdata(self, s3_files) -> str:
+ copy_s3_command = ""
+ job_users_cmds = []
+
+ if s3_files:
+ copy_s3_command = " ; ".join([f"aws s3 cp {s3_uri} {dst}" for s3_uri, dst in s3_files])
+
+ if self.configuration.windows_job_users:
+ for job_user in self.configuration.windows_job_users:
+ job_users_cmds.append(
+ f"New-LocalUser -Name {job_user} -Password $password -FullName {job_user} -Description {job_user}"
+ )
+ job_users_cmds.append(
+ f"$Cred = New-Object System.Management.Automation.PSCredential {job_user}, $password"
+ )
+ job_users_cmds.append(
+ 'Start-Process cmd.exe -Credential $Cred -ArgumentList "/C" -LoadUserProfile -NoNewWindow'
+ )
+
+ configure_job_users = "\n".join(job_users_cmds)
+
+ userdata = f"""
+Invoke-WebRequest -Uri "https://www.python.org/ftp/python/3.11.9/python-3.11.9-amd64.exe" -OutFile "C:\python-3.11.9-amd64.exe"
+$installerHash=(Get-FileHash "C:\python-3.11.9-amd64.exe" -Algorithm "MD5")
+$expectedHash="e8dcd502e34932eebcaf1be056d5cbcd"
+if ($installerHash.Hash -ne $expectedHash) {{ throw "Could not verify Python installer." }}
+Start-Process -FilePath "C:\python-3.11.9-amd64.exe" -ArgumentList "/quiet InstallAllUsers=1 PrependPath=1 AppendPath=1" -Wait
+Invoke-WebRequest -Uri "https://awscli.amazonaws.com/AWSCLIV2.msi" -Outfile "C:\AWSCLIV2.msi"
+Start-Process msiexec.exe -ArgumentList "/i C:\AWSCLIV2.msi /quiet" -Wait
+$env:Path = [System.Environment]::GetEnvironmentVariable("Path","Machine")
+$secret = aws secretsmanager get-secret-value --secret-id WindowsPasswordSecret --query SecretString --output text | ConvertFrom-Json
+$password = ConvertTo-SecureString -String $($secret.password) -AsPlainText -Force
+{copy_s3_command}
+{configure_job_users}
+"""
+
+ return userdata
+
+ def ami_ssm_param_name(self) -> str:
+ # Grab the latest Windows Server 2022 AMI
+ # https://aws.amazon.com/blogs/mt/query-for-the-latest-windows-ami-using-systems-manager-parameter-store/
+ ami_ssm_param: str = (
+ f"/aws/service/ami-windows-latest/{WindowsInstanceBuildWorker.WIN2022_AMI_NAME}"
+ )
+ return ami_ssm_param
+
+
+@dataclass
+class PosixInstanceWorkerBase(EC2InstanceWorker):
+ """Base class from which posix (i.e. Linux) ec2 test instances are derived.
+
+ The methods in this base class are written with two cases of worker hosts in mind:
+ 1. A host that is based on a stock linux AMI, with no Deadline-anything installed, that
+ must install the worker agent and the like during boot-up.
+ 2. A host that already has the worker agent, job/agent users, and the like baked into
+ the host AMI in a location & manner that may differ from case (1).
+ """
def ssm_document_name(self) -> str:
return "AWS-RunShellScript"
@@ -587,6 +614,9 @@ def send_command(self, command: str) -> CommandResult:
def _start_worker_agent(self) -> None:
assert self.instance_id
+ LOG.info(
+ f"Starting worker for farm: {self.configuration.farm_id} and fleet: {self.configuration.fleet.id}"
+ )
LOG.info(f"Sending SSM command to configure Worker agent on instance {self.instance_id}")
cmd_result = self.send_command(self.configure_worker_command(config=self.configuration))
@@ -595,28 +625,112 @@ def _start_worker_agent(self) -> None:
LOG.info(f"Sending SSM command to start Worker agent on instance {self.instance_id}")
cmd_result = self.send_command(
- " && ".join(
+ "; ".join(
[
- f"nohup runuser --login {self.configuration.agent_user} -c 'AWS_DEFAULT_REGION={self.configuration.region} deadline-worker-agent > /tmp/worker-agent-stdout.txt 2>&1 &'",
- # Verify Worker is still running
- "echo Waiting 5s for agent to get started",
- "sleep 5",
- "echo 'Running pgrep to see if deadline-worker-agent is running'",
- f"pgrep --count --full -u {self.configuration.agent_user} deadline-worker-agent",
+ " && ".join(
+ [
+ "set -x",
+ f"nohup runuser --login {self.configuration.agent_user} -c 'AWS_DEFAULT_REGION={self.configuration.region} deadline-worker-agent > /tmp/worker-agent-stdout.txt 2>&1 &'",
+ # Verify Worker is still running
+ "echo Waiting 5s for agent to get started",
+ "sleep 5",
+ "echo 'Running pgrep to see if deadline-worker-agent is running'",
+ # Note: pgrep has a non-zero exit code if no matching application can be found.
+ f"pgrep --count --full -u {self.configuration.agent_user} deadline-worker-agent",
+ ],
+ ),
+ # If the worker didn't start, then print out the agent logs that exist to aid in debugging.
+ "if test $? -ne 0; then echo '+++AGENT NOT RUNNING+++'; cat /var/log/amazon/deadline/worker-agent-bootstrap.log /var/log/amazon/deadline/worker-agent.log; exit 1; fi",
]
- ),
+ )
)
assert cmd_result.exit_code == 0, f"Failed to start Worker agent: {cmd_result}"
LOG.info("Successfully started Worker agent")
self.worker_id = self.get_worker_id()
+ def configure_agent_user_environment(
+ self, config: DeadlineWorkerConfiguration
+ ) -> str: # pragma: no cover
+ """Get the command to configure the Worker. This must be run as root.
+ This can assume that the agent user exists.
+ """
+
+ cmds = []
+
+ if config.service_model_path:
+ cmds.append(
+ f"runuser -l {config.agent_user} -s /bin/bash -c 'aws configure add-model --service-model file://{config.service_model_path}'"
+ )
+
+ if os.environ.get("DEADLINE_WORKER_ALLOW_INSTANCE_PROFILE"):
+ LOG.info(
+ f"Using DEADLINE_WORKER_ALLOW_INSTANCE_PROFILE: {os.environ.get('DEADLINE_WORKER_ALLOW_INSTANCE_PROFILE')}"
+ )
+ cmds.append(
+ f"runuser -l {config.agent_user} -s /bin/bash -c 'echo export DEADLINE_WORKER_ALLOW_INSTANCE_PROFILE={os.environ.get('DEADLINE_WORKER_ALLOW_INSTANCE_PROFILE')} >> ~/.bashrc'",
+ )
+
+ if os.environ.get("AWS_ENDPOINT_URL_DEADLINE"):
+ LOG.info(
+ f"Using AWS_ENDPOINT_URL_DEADLINE: {os.environ.get('AWS_ENDPOINT_URL_DEADLINE')}"
+ )
+ cmds.append(
+ f"runuser -l {config.agent_user} -s /bin/bash -c 'echo export AWS_ENDPOINT_URL_DEADLINE={os.environ.get('AWS_ENDPOINT_URL_DEADLINE')} >> ~/.bashrc'",
+ )
+
+ return " && ".join(cmds)
+
+ def start_worker_service(self):
+ LOG.info("Sending command to start the Worker Agent service")
+
+ cmd_result = self.send_command("systemctl start deadline-worker")
+
+ assert cmd_result.exit_code == 0, f"Failed to start Worker Agent service: {cmd_result}"
+
+ def stop_worker_service(self):
+ LOG.info("Sending command to stop the Worker Agent service")
+ cmd_result = self.send_command("systemctl stop deadline-worker")
+
+ assert cmd_result.exit_code == 0, f"Failed to stop Worker Agent service: {cmd_result}"
+
+ def get_worker_id(self) -> str:
+ # There can be a race condition, so we may need to wait a little bit for the status file to be written.
+
+ worker_state_filename = "/var/lib/deadline/worker.json"
+ cmd_result = self.send_command(
+ " && ".join(
+ [
+ f"t=0 && while [ $t -le 10 ] && ! (test -f {worker_state_filename}); do sleep $t; t=$[$t+1]; done",
+ f"cat {worker_state_filename} | jq -r '.worker_id'",
+ ]
+ )
+ )
+ assert cmd_result.exit_code == 0, f"Failed to get Worker ID: {cmd_result}"
+
+ worker_id = cmd_result.stdout.rstrip("\n\r")
+ LOG.info(f"Worker ID: {worker_id}")
+ assert re.match(
+ r"^worker-[0-9a-f]{32}$", worker_id
+ ), f"Got nonvalid Worker ID from command stdout: {cmd_result}"
+ return worker_id
+
+
+@dataclass
+class PosixInstanceBuildWorker(PosixInstanceWorkerBase):
+ """
+ This class represents a Linux EC2 Worker Host.
+ Any commands must be written in Bash.
+ """
+
+ AL2023_AMI_NAME: ClassVar[str] = "al2023-ami-kernel-6.1-x86_64"
+
def configure_worker_command(
self, config: DeadlineWorkerConfiguration
) -> str: # pragma: no cover
"""Get the command to configure the Worker. This must be run as root."""
-
cmds = [
+ "set -x",
"source /opt/deadline/worker/bin/activate",
f"AWS_DEFAULT_REGION={self.configuration.region}",
config.worker_agent_install.install_command_for_linux,
@@ -651,27 +765,7 @@ def configure_worker_command(
f'echo "{self.configuration.agent_user} ALL=({sudoer_rule_users}) NOPASSWD: ALL" > /etc/sudoers.d/{self.configuration.agent_user}'
)
- if config.service_model_path:
- cmds.append(
- f"runuser -l {config.agent_user} -s /bin/bash -c 'aws configure add-model --service-model file://{config.service_model_path}'"
- )
-
- if os.environ.get("DEADLINE_WORKER_ALLOW_INSTANCE_PROFILE"):
- LOG.info(
- f"Using DEADLINE_WORKER_ALLOW_INSTANCE_PROFILE: {os.environ.get('DEADLINE_WORKER_ALLOW_INSTANCE_PROFILE')}"
- )
- cmds.insert(
- 0,
- f"runuser -l {config.agent_user} -s /bin/bash -c 'echo export DEADLINE_WORKER_ALLOW_INSTANCE_PROFILE={os.environ.get('DEADLINE_WORKER_ALLOW_INSTANCE_PROFILE')} >> ~/.bashrc'",
- )
-
- if os.environ.get("AWS_ENDPOINT_URL_DEADLINE"):
- LOG.info(
- f"Using AWS_ENDPOINT_URL_DEADLINE: {os.environ.get('AWS_ENDPOINT_URL_DEADLINE')}"
- )
- cmds.append(
- f"runuser -l {config.agent_user} -s /bin/bash -c 'echo export AWS_ENDPOINT_URL_DEADLINE={os.environ.get('AWS_ENDPOINT_URL_DEADLINE')} >> ~/.bashrc'",
- )
+ cmds.append(self.configure_agent_user_environment(config))
return " && ".join(cmds)
@@ -692,56 +786,23 @@ def userdata(self, s3_files) -> str:
configure_job_users = "\n".join(job_users_cmds)
userdata = f"""#!/bin/bash
- # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- set -x
- groupadd --system {self.configuration.job_user_group}
- {configure_job_users}
- {copy_s3_command}
-
- mkdir /opt/deadline
- python3 -m venv /opt/deadline/worker
- """
-
- return userdata
-
- def start_worker_service(self):
- LOG.info("Sending command to start the Worker Agent service")
-
- cmd_result = self.send_command("systemctl start deadline-worker")
-
- assert cmd_result.exit_code == 0, f"Failed to start Worker Agent service: {cmd_result}"
-
- def stop_worker_service(self):
- LOG.info("Sending command to stop the Worker Agent service")
- cmd_result = self.send_command("systemctl stop deadline-worker")
-
- assert cmd_result.exit_code == 0, f"Failed to start Worker Agent service: {cmd_result}"
+# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+set -x
+groupadd --system {self.configuration.job_user_group}
+{configure_job_users}
+{copy_s3_command}
- def get_worker_id(self) -> str:
- # There can be a race condition, so we may need to wait a little bit for the status file to be written.
+mkdir /opt/deadline
+python3 -m venv /opt/deadline/worker
+"""
- worker_state_filename = "/var/lib/deadline/worker.json"
- cmd_result = self.send_command(
- " && ".join(
- [
- f"t=0 && while [ $t -le 10 ] && ! (test -f {worker_state_filename}); do sleep $t; t=$[$t+1]; done",
- f"cat {worker_state_filename} | jq -r '.worker_id'",
- ]
- )
- )
- assert cmd_result.exit_code == 0, f"Failed to get Worker ID: {cmd_result}"
-
- worker_id = cmd_result.stdout.rstrip("\n\r")
- assert re.match(
- r"^worker-[0-9a-f]{32}$", worker_id
- ), f"Got nonvalid Worker ID from command stdout: {cmd_result}"
- return worker_id
+ return userdata
def ami_ssm_param_name(self) -> str:
# Grab the latest AL2023 AMI
# https://aws.amazon.com/blogs/compute/query-for-the-latest-amazon-linux-ami-ids-using-aws-systems-manager-parameter-store/
ami_ssm_param: str = (
- f"/aws/service/ami-amazon-linux-latest/{PosixInstanceWorker.AL2023_AMI_NAME}"
+ f"/aws/service/ami-amazon-linux-latest/{PosixInstanceBuildWorker.AL2023_AMI_NAME}"
)
return ami_ssm_param
diff --git a/src/deadline_test_fixtures/fixtures.py b/src/deadline_test_fixtures/fixtures.py
index de3ce68..3931c5b 100644
--- a/src/deadline_test_fixtures/fixtures.py
+++ b/src/deadline_test_fixtures/fixtures.py
@@ -29,8 +29,8 @@
DeadlineWorkerConfiguration,
DockerContainerWorker,
PipInstall,
- PosixInstanceWorker,
- WindowsInstanceWorker,
+ PosixInstanceBuildWorker,
+ WindowsInstanceBuildWorker,
EC2InstanceWorker,
)
from .models import (
@@ -496,9 +496,9 @@ def ec2_worker_type(request: pytest.FixtureRequest) -> Generator[Type[DeadlineWo
operating_system = request.getfixturevalue("operating_system")
if operating_system.name == "AL2023":
- yield PosixInstanceWorker
+ yield PosixInstanceBuildWorker
elif operating_system.name == "WIN2022":
- yield WindowsInstanceWorker
+ yield WindowsInstanceBuildWorker
else:
raise ValueError(
'Invalid value provided for "operating_system", valid options are \'OperatingSystem("AL2023")\' or \'OperatingSystem("WIN2022")\'.'
diff --git a/test/unit/deadline/test_worker.py b/test/unit/deadline/test_worker.py
index dd27a71..a0c1072 100644
--- a/test/unit/deadline/test_worker.py
+++ b/test/unit/deadline/test_worker.py
@@ -17,7 +17,7 @@
CommandResult,
DeadlineWorkerConfiguration,
DockerContainerWorker,
- PosixInstanceWorker,
+ PosixInstanceBuildWorker,
PipInstall,
CodeArtifactRepositoryInfo,
S3Object,
@@ -86,7 +86,7 @@ def worker_config(region: str) -> DeadlineWorkerConfiguration:
)
-class TestPosixInstanceWorker:
+class TestPosixInstanceBuildWorker:
@staticmethod
def describe_instance(instance_id: str) -> Any:
ec2_client = boto3.client("ec2")
@@ -148,8 +148,8 @@ def worker(
security_group_id: str,
instance_profile_name: str,
bootstrap_bucket_name: str,
- ) -> PosixInstanceWorker:
- return PosixInstanceWorker(
+ ) -> PosixInstanceBuildWorker:
+ return PosixInstanceBuildWorker(
subnet_id=subnet_id,
security_group_id=security_group_id,
instance_profile_name=instance_profile_name,
@@ -164,7 +164,7 @@ def worker(
)
@patch.object(mod, "open", mock_open(read_data="mock data".encode()))
- def test_start(self, worker: PosixInstanceWorker) -> None:
+ def test_start(self, worker: PosixInstanceBuildWorker) -> None:
# GIVEN
s3_files = [
("s3://bucket/key", "/tmp/key"),
@@ -193,7 +193,7 @@ def test_start(self, worker: PosixInstanceWorker) -> None:
def test_stage_s3_bucket(
self,
- worker: PosixInstanceWorker,
+ worker: PosixInstanceBuildWorker,
worker_config: DeadlineWorkerConfiguration,
bootstrap_bucket_name: str,
) -> None:
@@ -221,7 +221,7 @@ def test_stage_s3_bucket(
def test_launch_instance(
self,
- worker: PosixInstanceWorker,
+ worker: PosixInstanceBuildWorker,
vpc_id: str,
subnet_id: str,
security_group_id: str,
@@ -233,7 +233,7 @@ def test_launch_instance(
# THEN
assert worker.instance_id is not None
- instance = TestPosixInstanceWorker.describe_instance(worker.instance_id)
+ instance = TestPosixInstanceBuildWorker.describe_instance(worker.instance_id)
assert instance["ImageId"] == worker.ami_id
assert instance["State"]["Name"] == "running"
assert instance["SubnetId"] == subnet_id
@@ -248,7 +248,7 @@ def test_launch_instance(
def test_start_worker_agent(self) -> None:
pass
- def test_stop(self, worker: PosixInstanceWorker) -> None:
+ def test_stop(self, worker: PosixInstanceBuildWorker) -> None:
# GIVEN
# WHEN
with patch.object(
@@ -258,18 +258,18 @@ def test_stop(self, worker: PosixInstanceWorker) -> None:
instance_id = worker.instance_id
assert instance_id is not None
- instance = TestPosixInstanceWorker.describe_instance(instance_id)
+ instance = TestPosixInstanceBuildWorker.describe_instance(instance_id)
assert instance["State"]["Name"] == "running"
worker.stop()
# THEN
- instance = TestPosixInstanceWorker.describe_instance(instance_id)
+ instance = TestPosixInstanceBuildWorker.describe_instance(instance_id)
assert instance["State"]["Name"] == "terminated"
assert worker.instance_id is None
class TestSendCommand:
- def test_sends_command(self, worker: PosixInstanceWorker) -> None:
+ def test_sends_command(self, worker: PosixInstanceBuildWorker) -> None:
# GIVEN
cmd = 'echo "Hello world"'
# WHEN
@@ -291,7 +291,7 @@ def test_sends_command(self, worker: PosixInstanceWorker) -> None:
Parameters={"commands": ["set -eou pipefail; " + cmd]},
)
- def test_retries_when_instance_not_ready(self, worker: PosixInstanceWorker) -> None:
+ def test_retries_when_instance_not_ready(self, worker: PosixInstanceBuildWorker) -> None:
# GIVEN
cmd = 'echo "Hello world"'
# WHEN
@@ -329,7 +329,7 @@ def side_effect(*args, **kwargs):
* 2
)
- def test_raises_any_other_error(self, worker: PosixInstanceWorker) -> None:
+ def test_raises_any_other_error(self, worker: PosixInstanceBuildWorker) -> None:
# GIVEN
cmd = 'echo "Hello world"'
# WHEN
@@ -362,7 +362,7 @@ def test_raises_any_other_error(self, worker: PosixInstanceWorker) -> None:
"worker-7c3377ec9eba444bb51cc7da18463081\r\n",
],
)
- def test_get_worker_id(self, worker_id: str, worker: PosixInstanceWorker) -> None:
+ def test_get_worker_id(self, worker_id: str, worker: PosixInstanceBuildWorker) -> None:
# GIVEN
with patch.object(
worker, "send_command", return_value=CommandResult(exit_code=0, stdout=worker_id)
@@ -373,7 +373,7 @@ def test_get_worker_id(self, worker_id: str, worker: PosixInstanceWorker) -> Non
# THEN
assert result == worker_id.rstrip("\n\r")
- def test_ami_id(self, worker: PosixInstanceWorker) -> None:
+ def test_ami_id(self, worker: PosixInstanceBuildWorker) -> None:
# WHEN
ami_id = worker.ami_id