diff --git a/src/deadline_test_fixtures/__init__.py b/src/deadline_test_fixtures/__init__.py
index 01ad83f..2ea69c0 100644
--- a/src/deadline_test_fixtures/__init__.py
+++ b/src/deadline_test_fixtures/__init__.py
@@ -7,6 +7,8 @@
DeadlineWorkerConfiguration,
DockerContainerWorker,
EC2InstanceWorker,
+ WindowsInstanceWorker,
+ PosixInstanceWorker,
Job,
Farm,
Fleet,
@@ -51,6 +53,8 @@
"DeadlineWorkerConfiguration",
"DockerContainerWorker",
"EC2InstanceWorker",
+ "WindowsInstanceWorker",
+ "PosixInstanceWorker",
"Farm",
"Fleet",
"Job",
diff --git a/src/deadline_test_fixtures/deadline/__init__.py b/src/deadline_test_fixtures/deadline/__init__.py
index ec1f0ef..c904e4d 100644
--- a/src/deadline_test_fixtures/deadline/__init__.py
+++ b/src/deadline_test_fixtures/deadline/__init__.py
@@ -16,6 +16,8 @@
DeadlineWorkerConfiguration,
DockerContainerWorker,
EC2InstanceWorker,
+ PosixInstanceWorker,
+ WindowsInstanceWorker,
PipInstall,
)
@@ -27,6 +29,8 @@
"DeadlineWorkerConfiguration",
"DockerContainerWorker",
"EC2InstanceWorker",
+ "WindowsInstanceWorker",
+ "PosixInstanceWorker",
"Farm",
"Fleet",
"Job",
diff --git a/src/deadline_test_fixtures/deadline/worker.py b/src/deadline_test_fixtures/deadline/worker.py
index b266aad..ff8cde5 100644
--- a/src/deadline_test_fixtures/deadline/worker.py
+++ b/src/deadline_test_fixtures/deadline/worker.py
@@ -21,103 +21,15 @@
from ..models import (
PipInstall,
PosixSessionUser,
- OperatingSystem,
)
from .resources import Fleet
from ..util import call_api, wait_for
LOG = logging.getLogger(__name__)
-# Hardcoded to default posix path for worker.json file which has the worker ID in it
-WORKER_JSON_PATH = "/var/lib/deadline/worker.json"
DOCKER_CONTEXT_DIR = os.path.join(os.path.dirname(__file__), "..", "containers", "worker")
-def linux_worker_command(config: DeadlineWorkerConfiguration) -> str: # pragma: no cover
- """Get the command to configure the Worker. This must be run as root."""
-
- cmds = [
- config.worker_agent_install.install_command_for_linux,
- *(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.user} "
- + f"--group {config.group} "
- + f"{'--allow-shutdown ' if config.allow_shutdown else ''}"
- + f"{'--no-install-service ' if config.no_install_service else ''}"
- + f"{'--start ' if config.start_service else ''}"
- ),
- # fmt: on
- ]
-
- if config.service_model_path:
- cmds.append(
- f"runuser -l {config.user} -s /bin/bash -c 'aws configure add-model --service-model file://{config.service_model_path}'"
- )
-
- if os.environ.get("AWS_ENDPOINT_URL_DEADLINE"):
- LOG.info(f"Using AWS_ENDPOINT_URL_DEADLINE: {os.environ.get('AWS_ENDPOINT_URL_DEADLINE')}")
- cmds.insert(
- 0,
- f"runuser -l {config.user} -s /bin/bash -c 'echo export AWS_ENDPOINT_URL_DEADLINE={os.environ.get('AWS_ENDPOINT_URL_DEADLINE')} >> ~/.bashrc'",
- )
-
- return " && ".join(cmds)
-
-
-def windows_worker_command(config: DeadlineWorkerConfiguration) -> str: # pragma: no cover
- """Get the command to configure the Worker. This must be run as root."""
-
- 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.user} "
- + f"{'--allow-shutdown ' if config.allow_shutdown else ''}"
- + "--start"
- ),
- # fmt: on
- ]
-
- 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.user}\\.aws\\models -Recurse; "
- f"Copy-Item -Path ~\\.aws\\* -Destination C:\\Users\\jobuser\\.aws\\models -Recurse"
- )
-
- if os.environ.get("AWS_ENDPOINT_URL_DEADLINE"):
- LOG.info(f"Using AWS_ENDPOINT_URL_DEADLINE: {os.environ.get('AWS_ENDPOINT_URL_DEADLINE')}")
- cmds.insert(
- 0,
- f"[System.Environment]::SetEnvironmentVariable('AWS_ENDPOINT_URL_DEADLINE', '{os.environ.get('AWS_ENDPOINT_URL_DEADLINE')}', [System.EnvironmentVariableTarget]::Machine); "
- "$env:AWS_ENDPOINT_URL_DEADLINE = [System.Environment]::GetEnvironmentVariable('AWS_ENDPOINT_URL_DEADLINE','Machine')",
- )
-
- return "; ".join(cmds)
-
-
-def configure_worker_command(*, config: DeadlineWorkerConfiguration) -> str: # pragma: no cover
- """Get the command to configure the Worker. This must be run as root."""
-
- if config.operating_system.name == "AL2023":
- return linux_worker_command(config)
- else:
- return windows_worker_command(config)
-
-
class DeadlineWorker(abc.ABC):
@abc.abstractmethod
def start(self) -> None:
@@ -131,6 +43,10 @@ def stop(self) -> None:
def send_command(self, command: str) -> CommandResult:
pass
+ @abc.abstractmethod
+ def get_worker_id(self) -> str:
+ pass
+
@dataclass(frozen=True)
class CommandResult: # pragma: no cover
@@ -168,30 +84,33 @@ def __str__(self) -> str:
@dataclass(frozen=True)
class DeadlineWorkerConfiguration:
- operating_system: OperatingSystem
farm_id: str
fleet: Fleet
region: str
- user: str
- group: str
allow_shutdown: bool
worker_agent_install: PipInstall
- job_users: list[PosixSessionUser] = field(
- default_factory=lambda: [PosixSessionUser("jobuser", "jobuser")]
- )
start_service: bool = False
no_install_service: bool = False
service_model_path: str | None = None
- file_mappings: list[tuple[str, str]] | None = None
+
"""Mapping of files to copy from host environment to worker environment"""
- pre_install_commands: list[str] | None = None
+ file_mappings: list[tuple[str, str]] | None = None
+
"""Commands to run before installing the Worker agent"""
+ pre_install_commands: list[str] | None = None
+
+ job_user: str = field(default="job-user")
+ agent_user: str = field(default="deadline-worker")
+ job_user_group: str = field(default="deadline-job-users")
+
+ """Additional job users to configure for Posix workers"""
+ job_users: list[PosixSessionUser] = field(
+ default_factory=lambda: [PosixSessionUser("job-user", "deadline-job-users")]
+ )
@dataclass
class EC2InstanceWorker(DeadlineWorker):
- AL2023_AMI_NAME: ClassVar[str] = "al2023-ami-kernel-6.1-x86_64"
- WIN2022_AMI_NAME: ClassVar[str] = "Windows_Server-2022-English-Full-Base"
subnet_id: str
security_group_id: str
@@ -203,20 +122,47 @@ class EC2InstanceWorker(DeadlineWorker):
deadline_client: botocore.client.BaseClient
configuration: DeadlineWorkerConfiguration
- instance_id: Optional[str] = field(init=False, default=None)
+ instance_type: str
+ instance_shutdown_behavior: str
- override_ami_id: InitVar[Optional[str]] = None
- worker_id: Optional[str] = None
+ instance_id: Optional[str] = field(init=False, default=None)
+ worker_id: Optional[str] = field(init=False, default=None)
"""
Option to override the AMI ID for the EC2 instance. The latest AL2023 is used by default.
Note that the scripting to configure the EC2 instance is only verified to work on AL2023.
"""
+ override_ami_id: InitVar[Optional[str]] = None
def __post_init__(self, override_ami_id: Optional[str] = None):
if override_ami_id:
self._ami_id = override_ami_id
+ @abc.abstractmethod
+ def ami_ssm_param_name(self) -> str:
+ raise NotImplementedError("'ami_ssm_param_name' was not implemented.")
+
+ @abc.abstractmethod
+ def ssm_document_name(self) -> str:
+ raise NotImplementedError("'ssm_document_name' was not implemented.")
+
+ @abc.abstractmethod
+ def _start_worker_agent(self) -> None: # pragma: no cover
+ raise NotImplementedError("'_start_worker_agent' was not implemented.")
+
+ @abc.abstractmethod
+ def configure_worker_command(
+ self, *, config: DeadlineWorkerConfiguration
+ ) -> str: # pragma: no cover
+ raise NotImplementedError("'configure_worker_command' was not implemented.")
+
+ @abc.abstractmethod
+ def get_worker_id(self) -> str:
+ raise NotImplementedError("'get_worker_id' was not implemented.")
+
+ def userdata(self, s3_files) -> str:
+ raise NotImplementedError("'userdata' was not implemented.")
+
def start(self) -> None:
s3_files = self._stage_s3_bucket()
self._launch_instance(s3_files=s3_files)
@@ -303,18 +249,11 @@ def send_command(self, command: str) -> CommandResult:
for i in range(0, NUM_RETRIES):
LOG.info(f"Sending SSM command to instance {self.instance_id}")
try:
- if self.configuration.operating_system.name == "AL2023":
- send_command_response = self.ssm_client.send_command(
- InstanceIds=[self.instance_id],
- DocumentName="AWS-RunShellScript",
- Parameters={"commands": [command]},
- )
- else:
- send_command_response = self.ssm_client.send_command(
- InstanceIds=[self.instance_id],
- DocumentName="AWS-RunPowerShellScript",
- Parameters={"commands": [command]},
- )
+ send_command_response = self.ssm_client.send_command(
+ InstanceIds=[self.instance_id],
+ DocumentName=self.ssm_document_name(),
+ Parameters={"commands": [command]},
+ )
break
except botocore.exceptions.ClientError as error:
error_code = error.response["Error"]["Code"]
@@ -390,86 +329,28 @@ def _stage_s3_bucket(self) -> list[tuple[str, str]] | None:
return list(s3_to_dst_mapping.items())
- def linux_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} && chown {self.configuration.user} {dst}"
- for s3_uri, dst in s3_files
- ]
- )
- for job_user in self.configuration.job_users:
- job_users_cmds.append(f"groupadd {job_user.group}")
- job_users_cmds.append(
- f"useradd --create-home --system --shell=/bin/bash --groups={self.configuration.group} -g {job_user.group} {job_user.user}"
- )
- job_users_cmds.append(f"usermod -a -G {job_user.group} {self.configuration.user}")
-
- sudoer_rule_users = ",".join(
- [
- self.configuration.user,
- *[job_user.user for job_user in self.configuration.job_users],
- ]
- )
- job_users_cmds.append(
- f'echo "{self.configuration.user} ALL=({sudoer_rule_users}) NOPASSWD: ALL" > /etc/sudoers.d/{self.configuration.user}'
- )
-
- 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.group}
- useradd --create-home --system --shell=/bin/bash --groups={self.configuration.group} {self.configuration.user}
- {configure_job_users}
- {copy_s3_command}
-
- runuser --login {self.configuration.user} --command 'python3 -m venv $HOME/.venv && echo ". $HOME/.venv/bin/activate" >> $HOME/.bashrc'
- """
-
- return userdata
-
- def windows_userdata(self, s3_files) -> str:
- copy_s3_command = ""
- if s3_files:
- copy_s3_command = " ; ".join([f"aws s3 cp {s3_uri} {dst}" for s3_uri, dst in s3_files])
-
- 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"
- 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
- New-LocalUser -Name "jobuser" -Password $password -FullName "jobuser" -Description "job user"
- $Cred = New-Object System.Management.Automation.PSCredential "jobuser", $password
- Start-Process cmd.exe -Credential $Cred -ArgumentList "/C" -LoadUserProfile -NoNewWindow
- {copy_s3_command}
- """
-
- return userdata
-
def _launch_instance(self, *, s3_files: list[tuple[str, str]] | None = None) -> None:
assert (
not self.instance_id
), "Attempted to launch EC2 instance when one was already launched"
- if self.configuration.operating_system.name == "AL2023":
- userdata = self.linux_userdata(s3_files)
- else:
- userdata = self.windows_userdata(s3_files)
-
LOG.info("Launching EC2 instance")
+ LOG.info(
+ json.dumps(
+ {
+ "AMI_ID": self.ami_id,
+ "Instance Profile": self.instance_profile_name,
+ "User Data": self.userdata(s3_files),
+ },
+ indent=4,
+ sort_keys=True,
+ )
+ )
run_instance_response = self.ec2_client.run_instances(
MinCount=1,
MaxCount=1,
ImageId=self.ami_id,
- InstanceType="t3.micro",
+ InstanceType=self.instance_type,
IamInstanceProfile={"Name": self.instance_profile_name},
SubnetId=self.subnet_id,
SecurityGroupIds=[self.security_group_id],
@@ -485,7 +366,8 @@ def _launch_instance(self, *, s3_files: list[tuple[str, str]] | None = None) ->
],
}
],
- UserData=userdata,
+ InstanceInitiatedShutdownBehavior=self.instance_shutdown_behavior,
+ UserData=self.userdata(s3_files),
)
self.instance_id = run_instance_response["Instances"][0]["InstanceId"]
@@ -499,31 +381,38 @@ def _launch_instance(self, *, s3_files: list[tuple[str, str]] | None = None) ->
)
LOG.info(f"EC2 instance {self.instance_id} status is OK")
- def start_linux_worker(self) -> None:
- cmd_result = self.send_command(
- f"cd /home/{self.configuration.user}; . .venv/bin/activate; AWS_DEFAULT_REGION={self.configuration.region} {configure_worker_command(config=self.configuration)}"
- )
- assert cmd_result.exit_code == 0, f"Failed to configure Worker agent: {cmd_result}"
- LOG.info("Successfully configured Worker agent")
+ @property
+ def ami_id(self) -> str:
+ if not hasattr(self, "_ami_id"):
+ response = call_api(
+ description=f"Getting latest {type(self)} AMI ID from SSM parameter {self.ami_ssm_param_name()}",
+ fn=lambda: self.ssm_client.get_parameters(Names=[self.ami_ssm_param_name()]),
+ )
+
+ parameters = response.get("Parameters", [])
+ assert (
+ len(parameters) == 1
+ ), f"Received incorrect number of SSM parameters. Expected 1, got response: {response}"
+ self._ami_id = parameters[0]["Value"]
+ LOG.info(f"Using latest {type(self)} AMI {self._ami_id}")
+
+ return self._ami_id
+
+
+@dataclass
+class WindowsInstanceWorker(EC2InstanceWorker):
+ WIN2022_AMI_NAME: ClassVar[str] = "Windows_Server-2022-English-Full-Base"
+
+ def ssm_document_name(self) -> str:
+ return "AWS-RunPowerShellScript"
+
+ def _start_worker_agent(self) -> None:
+ assert self.instance_id
+ LOG.info(f"Sending SSM command to configure Worker agent on instance {self.instance_id}")
- LOG.info(f"Sending SSM command to start Worker agent on instance {self.instance_id}")
cmd_result = self.send_command(
- " && ".join(
- [
- f"nohup runuser --login {self.configuration.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.user} deadline-worker-agent",
- ]
- ),
+ f"{self.configure_worker_command(config=self.configuration)}"
)
- assert cmd_result.exit_code == 0, f"Failed to start Worker agent: {cmd_result}"
- LOG.info("Successfully started Worker agent")
-
- def start_windows_worker(self) -> None:
- cmd_result = self.send_command(f"{configure_worker_command(config=self.configuration)}")
LOG.info("Successfully configured Worker agent")
LOG.info("Sending SSM Command to check if Worker Agent is running")
cmd_result = self.send_command(
@@ -539,30 +428,96 @@ def start_windows_worker(self) -> None:
assert cmd_result.exit_code == 0, f"Failed to start Worker agent: {cmd_result}"
LOG.info("Successfully started Worker agent")
- def _start_worker_agent(self) -> None: # pragma: no cover
- assert self.instance_id
+ self.worker_id = self.get_worker_id()
- LOG.info(f"Sending SSM command to configure Worker agent on instance {self.instance_id}")
+ def configure_worker_command(self, *, config: DeadlineWorkerConfiguration) -> str:
+ """Get the command to configure the Worker. This must be run as root."""
+
+ 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
+ ]
+
+ 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"
+ )
- if self.configuration.operating_system.name == "AL2023":
- self.start_linux_worker()
- else:
- self.start_windows_worker()
+ 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"[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')",
+ )
- self.worker_id = self.get_worker_id()
+ if os.environ.get("AWS_ENDPOINT_URL_DEADLINE"):
+ LOG.info(
+ f"Using AWS_ENDPOINT_URL_DEADLINE: {os.environ.get('AWS_ENDPOINT_URL_DEADLINE')}"
+ )
+ cmds.insert(
+ 0,
+ f"[System.Environment]::SetEnvironmentVariable('AWS_ENDPOINT_URL_DEADLINE', '{os.environ.get('AWS_ENDPOINT_URL_DEADLINE')}', [System.EnvironmentVariableTarget]::Machine); "
+ "$env:AWS_ENDPOINT_URL_DEADLINE = [System.Environment]::GetEnvironmentVariable('AWS_ENDPOINT_URL_DEADLINE','Machine')",
+ )
+
+ return "; ".join(cmds)
+
+ def userdata(self, s3_files) -> str:
+ copy_s3_command = ""
+ if s3_files:
+ copy_s3_command = " ; ".join([f"aws s3 cp {s3_uri} {dst}" for s3_uri, dst in s3_files])
+
+ 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"
+ 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
+ New-LocalUser -Name "{self.configuration.job_user}" -Password $password -FullName "{self.configuration.job_user}" -Description "job user"
+ $Cred = New-Object System.Management.Automation.PSCredential "{self.configuration.job_user}", $password
+ Start-Process cmd.exe -Credential $Cred -ArgumentList "/C" -LoadUserProfile -NoNewWindow
+ {copy_s3_command}
+ """
+
+ 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/{WindowsInstanceWorker.WIN2022_AMI_NAME}"
+ )
+ return ami_ssm_param
def get_worker_id(self) -> str:
- if self.configuration.operating_system.name == "AL2023":
- cmd_result = self.send_command("jq -r '.worker_id' /var/lib/deadline/worker.json")
- else:
- cmd_result = self.send_command(
- " ; ".join(
- [
- "$worker=Get-Content -Raw C:\ProgramData\Amazon\Deadline\Cache\worker.json | ConvertFrom-Json",
- "echo $worker.worker_id",
- ]
- )
+ cmd_result = self.send_command(
+ " ; ".join(
+ [
+ "$worker=Get-Content -Raw C:\ProgramData\Amazon\Deadline\Cache\worker.json | ConvertFrom-Json",
+ "echo $worker.worker_id",
+ ]
)
+ )
assert cmd_result.exit_code == 0, f"Failed to get Worker ID: {cmd_result}"
worker_id = cmd_result.stdout.rstrip("\n\r")
@@ -571,35 +526,151 @@ def get_worker_id(self) -> str:
), f"Got nonvalid Worker ID from command stdout: {cmd_result}"
return worker_id
- @property
- def ami_id(self) -> str:
- if not hasattr(self, "_ami_id"):
- if self.configuration.operating_system.name == "AL2023":
- # 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/
- ssm_param_name = (
- f"/aws/service/ami-amazon-linux-latest/{EC2InstanceWorker.AL2023_AMI_NAME}"
- )
- else:
- # Grab the latest Windows Server 2022 AMI
- # https://aws.amazon.com/blogs/mt/query-for-the-latest-windows-ami-using-systems-manager-parameter-store/
- ssm_param_name = (
- f"/aws/service/ami-windows-latest/{EC2InstanceWorker.WIN2022_AMI_NAME}"
- )
- response = call_api(
- description=f"Getting latest {self.configuration.operating_system.name} AMI ID from SSM parameter {ssm_param_name}",
- fn=lambda: self.ssm_client.get_parameters(Names=[ssm_param_name]),
+@dataclass
+class PosixInstanceWorker(EC2InstanceWorker):
+ AL2023_AMI_NAME: ClassVar[str] = "al2023-ami-kernel-6.1-x86_64"
+
+ def ssm_document_name(self) -> str:
+ return "AWS-RunShellScript"
+
+ def _start_worker_agent(self) -> None:
+ assert self.instance_id
+ LOG.info(f"Sending SSM command to configure Worker agent on instance {self.instance_id}")
+
+ cmd_result = self.send_command(
+ f"{self.configure_worker_command(config=self.configuration)}"
+ )
+ assert cmd_result.exit_code == 0, f"Failed to configure Worker agent: {cmd_result}"
+ LOG.info("Successfully configured Worker agent")
+
+ LOG.info(f"Sending SSM command to start Worker agent on instance {self.instance_id}")
+ cmd_result = self.send_command(
+ " && ".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",
+ ]
+ ),
+ )
+ 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_worker_command(
+ self, config: DeadlineWorkerConfiguration
+ ) -> str: # pragma: no cover
+ """Get the command to configure the Worker. This must be run as root."""
+
+ cmds = [
+ config.worker_agent_install.install_command_for_linux,
+ *(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"--group {config.job_user_group} "
+ + f"{'--allow-shutdown ' if config.allow_shutdown else ''}"
+ + f"{'--no-install-service ' if config.no_install_service else ''}"
+ + f"{'--start ' if config.start_service else ''}"
+ ),
+ # fmt: on
+ ]
+
+ 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}'"
)
- parameters = response.get("Parameters", [])
- assert (
- len(parameters) == 1
- ), f"Received incorrect number of SSM parameters. Expected 1, got response: {response}"
- self._ami_id = parameters[0]["Value"]
- LOG.info(f"Using latest {self.configuration.operating_system.name} AMI {self._ami_id}")
+ 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'",
+ )
- return self._ami_id
+ if os.environ.get("AWS_ENDPOINT_URL_DEADLINE"):
+ LOG.info(
+ f"Using AWS_ENDPOINT_URL_DEADLINE: {os.environ.get('AWS_ENDPOINT_URL_DEADLINE')}"
+ )
+ cmds.insert(
+ 0,
+ 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 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} && chown {self.configuration.agent_user} {dst}"
+ for s3_uri, dst in s3_files
+ ]
+ )
+ for job_user in self.configuration.job_users:
+ job_users_cmds.append(f"groupadd {job_user.group}")
+ job_users_cmds.append(
+ f"useradd --create-home --system --shell=/bin/bash --groups={self.configuration.job_user_group} -g {job_user.group} {job_user.user}"
+ )
+ job_users_cmds.append(f"usermod -a -G {job_user.group} {self.configuration.agent_user}")
+
+ sudoer_rule_users = ",".join(
+ [
+ self.configuration.agent_user,
+ *[job_user.user for job_user in self.configuration.job_users],
+ ]
+ )
+ job_users_cmds.append(
+ f'echo "{self.configuration.agent_user} ALL=({sudoer_rule_users}) NOPASSWD: ALL" > /etc/sudoers.d/{self.configuration.agent_user}'
+ )
+
+ 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}
+ useradd --create-home --system --shell=/bin/bash --groups={self.configuration.job_user_group} {self.configuration.agent_user}
+ {configure_job_users}
+ {copy_s3_command}
+
+ runuser --login {self.configuration.agent_user} --command 'python3 -m venv $HOME/.venv && echo ". $HOME/.venv/bin/activate" >> $HOME/.bashrc'
+ """
+
+ return userdata
+
+ def get_worker_id(self) -> str:
+ cmd_result = self.send_command("cat /var/lib/deadline/worker.json | 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
+
+ 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}"
+ )
+ return ami_ssm_param
@dataclass
@@ -623,10 +694,10 @@ def start(self) -> None:
**os.environ,
"FARM_ID": self.configuration.farm_id,
"FLEET_ID": self.configuration.fleet.id,
- "AGENT_USER": self.configuration.user,
- "SHARED_GROUP": self.configuration.group,
+ "AGENT_USER": self.configuration.agent_user,
+ "SHARED_GROUP": self.configuration.job_user_group,
"JOB_USER": self.configuration.job_users[0].user,
- "CONFIGURE_WORKER_AGENT_CMD": configure_worker_command(
+ "CONFIGURE_WORKER_AGENT_CMD": self.configure_worker_command(
config=self.configuration,
),
}
@@ -710,7 +781,7 @@ def stop(self) -> None:
LOG.info(f"Terminating Worker agent process in Docker container {self._container_id}")
try:
- self.send_command(f"pkill --signal term -f {self.configuration.user}")
+ self.send_command(f"pkill --signal term -f {self.configuration.agent_user}")
except Exception as e: # pragma: no cover
LOG.exception(f"Failed to terminate Worker agent process: {e}")
raise
@@ -734,6 +805,13 @@ def stop(self) -> None:
LOG.info(f"Stopped Docker container {self._container_id}")
self._container_id = None
+ def configure_worker_command(
+ self, config: DeadlineWorkerConfiguration
+ ) -> str: # pragma: no cover
+ """Get the command to configure the Worker. This must be run as root."""
+
+ return ""
+
def send_command(self, command: str, *, quiet: bool = False) -> CommandResult:
assert (
self._container_id
@@ -771,8 +849,7 @@ def send_command(self, command: str, *, quiet: bool = False) -> CommandResult:
stderr=result.stderr,
)
- @property
- def worker_id(self) -> str:
+ def get_worker_id(self) -> str:
cmd_result: Optional[CommandResult] = None
def got_worker_id() -> bool:
diff --git a/src/deadline_test_fixtures/fixtures.py b/src/deadline_test_fixtures/fixtures.py
index 1024121..b8ba2b9 100644
--- a/src/deadline_test_fixtures/fixtures.py
+++ b/src/deadline_test_fixtures/fixtures.py
@@ -15,7 +15,7 @@
import tempfile
from contextlib import ExitStack, contextmanager
from dataclasses import InitVar, dataclass, field, fields, MISSING
-from typing import Any, Generator, TypeVar
+from typing import Any, Generator, Type, TypeVar
from .deadline.client import DeadlineClient
from .deadline.resources import (
@@ -28,8 +28,10 @@
DeadlineWorker,
DeadlineWorkerConfiguration,
DockerContainerWorker,
- EC2InstanceWorker,
PipInstall,
+ PosixInstanceWorker,
+ WindowsInstanceWorker,
+ EC2InstanceWorker,
)
from .models import (
CodeArtifactRepositoryInfo,
@@ -39,6 +41,7 @@
ServiceModel,
S3Object,
OperatingSystem,
+ WindowsSessionUser,
)
from .cloudformation import WorkerBootstrapStack
from .job_attachment_manager import JobAttachmentManager
@@ -55,30 +58,56 @@ class BootstrapResources:
worker_instance_profile_name: str | None = None
job_attachments: JobAttachmentSettings | None = field(init=False, default=None)
- job_attachments_bucket_name: InitVar[str | None] = None
- job_attachments_root_prefix: InitVar[str | None] = None
+ job_attachments_bucket_name: str | None = None
+ job_attachments_root_prefix: str | None = None
+
+ windows_run_as_user: str | None = None
+ windows_run_as_user_secret_arn: str | None = None
+ posix_run_as_user: str | None = None
+ posix_run_as_user_group: str | None = None
job_run_as_user: JobRunAsUser = field(
default_factory=lambda: JobRunAsUser(
- posix=PosixSessionUser("", ""), runAs="WORKER_AGENT_USER"
+ posix=PosixSessionUser("", ""),
+ runAs="WORKER_AGENT_USER",
+ windows=WindowsSessionUser("", ""),
)
)
- def __post_init__(
- self,
- job_attachments_bucket_name: str | None,
- job_attachments_root_prefix: str | None,
- ) -> None:
- if job_attachments_bucket_name or job_attachments_root_prefix:
+ def __post_init__(self) -> None:
+ if self.job_attachments_bucket_name or self.job_attachments_root_prefix:
assert (
- job_attachments_bucket_name and job_attachments_root_prefix
+ self.job_attachments_bucket_name and self.job_attachments_root_prefix
), "Cannot provide partial Job Attachments settings, both bucket name and root prefix are required"
object.__setattr__(
self,
"job_attachments",
JobAttachmentSettings(
- bucket_name=job_attachments_bucket_name,
- root_prefix=job_attachments_root_prefix,
+ bucket_name=self.job_attachments_bucket_name,
+ root_prefix=self.job_attachments_root_prefix,
+ ),
+ )
+ if (
+ self.windows_run_as_user
+ or self.windows_run_as_user_secret_arn
+ or self.posix_run_as_user
+ or self.posix_run_as_user_group
+ ):
+ assert (
+ self.windows_run_as_user and self.windows_run_as_user_secret_arn
+ ), "Cannot provide partial Windows run as user settings, both user name and secret arn are required"
+ assert (
+ self.posix_run_as_user and self.posix_run_as_user_group
+ ), "Cannot provide partial Posix run as user settings, both user name and user group are required"
+ object.__setattr__(
+ self,
+ "job_run_as_user",
+ JobRunAsUser(
+ posix=PosixSessionUser(self.posix_run_as_user, self.posix_run_as_user_group),
+ runAs="QUEUE_CONFIGURED_USER",
+ windows=WindowsSessionUser(
+ self.windows_run_as_user, self.windows_run_as_user_secret_arn
+ ),
),
)
@@ -228,10 +257,10 @@ def bootstrap_resources(request: pytest.FixtureRequest) -> BootstrapResources:
required_fields = [f for f in all_fields if (MISSING == f.default == f.default_factory)]
assert all([rf.name in kwargs for rf in required_fields]), (
"Not all bootstrap resources have been fulfilled via environment variables. Expected "
- + f"values for {[f.name.upper() for f in required_fields]}, but got {kwargs}"
+ + f"values for {[f.name.upper() for f in required_fields]}, but got \n{json.dumps(kwargs, sort_keys=True, indent=4)}"
)
LOG.info(
- f"All bootstrap resources have been fulfilled via environment variables. Using {kwargs}"
+ f"All bootstrap resources have been fulfilled via environment variables. Using \n{json.dumps(kwargs, sort_keys=True, indent=4)}"
)
return BootstrapResources(**kwargs)
else:
@@ -424,7 +453,7 @@ def worker_config(
dest_path = posixpath.join("/tmp", os.path.basename(resolved_whl_path))
else:
dest_path = posixpath.join(
- "%USERPROFILE%\\AppData\\Local\\Temp", os.path.basename(resolved_whl_path)
+ "$env:USERPROFILE\\AppData\\Local\\Temp", os.path.basename(resolved_whl_path)
)
file_mappings = [(resolved_whl_path, dest_path)]
@@ -448,7 +477,7 @@ def worker_config(
if operating_system.name == "AL2023":
dst_path = posixpath.join("/tmp", src_path.name)
else:
- dst_path = posixpath.join("%USERPROFILE%\\AppData\\Local\\Temp", src_path.name)
+ dst_path = posixpath.join("$env:USERPROFILE\\AppData\\Local\\Temp", src_path.name)
LOG.info(f"The service model will be copied to {dst_path} on the Worker environment")
file_mappings.append((str(src_path), dst_path))
@@ -456,8 +485,6 @@ def worker_config(
farm_id=deadline_resources.farm.id,
fleet=deadline_resources.fleet,
region=region,
- user=os.getenv("WORKER_POSIX_USER", "deadline-worker"),
- group=os.getenv("WORKER_POSIX_SHARED_GROUP", "shared-group"),
allow_shutdown=True,
worker_agent_install=PipInstall(
requirement_specifiers=[worker_agent_requirement_specifier],
@@ -465,7 +492,21 @@ def worker_config(
),
service_model_path=dst_path,
file_mappings=file_mappings or None,
- operating_system=operating_system,
+ )
+
+
+@pytest.fixture(scope="session")
+def ec2_worker_type(request: pytest.FixtureRequest) -> Generator[Type[DeadlineWorker], None, None]:
+ # Allows overriding the base EC2InstanceWorker type with another derived type.
+ operating_system = request.getfixturevalue("operating_system")
+
+ if operating_system.name == "AL2023":
+ yield PosixInstanceWorker
+ elif operating_system.name == "WIN2022":
+ yield WindowsInstanceWorker
+ else:
+ raise ValueError(
+ 'Invalid value provided for "operating_system", valid options are \'OperatingSystem("AL2023")\' or \'OperatingSystem("WIN2022")\'.'
)
@@ -473,6 +514,7 @@ def worker_config(
def worker(
request: pytest.FixtureRequest,
worker_config: DeadlineWorkerConfiguration,
+ ec2_worker_type: Type[EC2InstanceWorker],
) -> Generator[DeadlineWorker, None, None]:
"""
Gets a DeadlineWorker for use in tests.
@@ -503,6 +545,9 @@ def worker(
ami_id = os.getenv("AMI_ID")
subnet_id = os.getenv("SUBNET_ID")
security_group_id = os.getenv("SECURITY_GROUP_ID")
+ instance_type = os.getenv("WORKER_INSTANCE_TYPE", default="t3.micro")
+ instance_shutdown_behavior = os.getenv("WORKER_INSTANCE_SHUTDOWN_BEHAVIOR", default="stop")
+
assert subnet_id, "SUBNET_ID is required when deploying an EC2 worker"
assert security_group_id, "SECURITY_GROUP_ID is required when deploying an EC2 worker"
@@ -516,7 +561,7 @@ def worker(
ssm_client = boto3.client("ssm")
deadline_client = boto3.client("deadline")
- worker = EC2InstanceWorker(
+ worker = ec2_worker_type(
ec2_client=ec2_client,
s3_client=s3_client,
deadline_client=deadline_client,
@@ -527,6 +572,8 @@ def worker(
security_group_id=security_group_id,
instance_profile_name=bootstrap_resources.worker_instance_profile_name,
configuration=worker_config,
+ instance_type=instance_type,
+ instance_shutdown_behavior=instance_shutdown_behavior,
)
def stop_worker():
diff --git a/src/deadline_test_fixtures/job_attachment_manager.py b/src/deadline_test_fixtures/job_attachment_manager.py
index 31addb9..66b8b08 100644
--- a/src/deadline_test_fixtures/job_attachment_manager.py
+++ b/src/deadline_test_fixtures/job_attachment_manager.py
@@ -12,7 +12,12 @@
Queue,
)
-from .models import JobAttachmentSettings, JobRunAsUser, PosixSessionUser
+from .models import (
+ JobAttachmentSettings,
+ JobRunAsUser,
+ PosixSessionUser,
+ WindowsSessionUser,
+)
from uuid import uuid4
@@ -48,7 +53,9 @@ def deploy_resources(self):
display_name="job_attachments_test_queue",
farm=Farm(self.farm_id),
job_run_as_user=JobRunAsUser(
- posix=PosixSessionUser("", ""), runAs="WORKER_AGENT_USER"
+ posix=PosixSessionUser("", ""),
+ runAs="WORKER_AGENT_USER",
+ windows=WindowsSessionUser("", ""),
),
job_attachments=JobAttachmentSettings(
bucket_name=self.bucket_name, root_prefix=self.bucket_root_prefix
@@ -59,7 +66,9 @@ def deploy_resources(self):
display_name="job_attachments_test_no_settings_queue",
farm=Farm(self.farm_id),
job_run_as_user=JobRunAsUser(
- posix=PosixSessionUser("", ""), runAs="WORKER_AGENT_USER"
+ posix=PosixSessionUser("", ""),
+ runAs="WORKER_AGENT_USER",
+ windows=WindowsSessionUser("", ""),
),
)
diff --git a/src/deadline_test_fixtures/models.py b/src/deadline_test_fixtures/models.py
index f775ce0..eda009e 100644
--- a/src/deadline_test_fixtures/models.py
+++ b/src/deadline_test_fixtures/models.py
@@ -31,9 +31,16 @@ class PosixSessionUser:
group: str
+@dataclass(frozen=True)
+class WindowsSessionUser:
+ user: str
+ passwordArn: str
+
+
@dataclass(frozen=True)
class JobRunAsUser:
posix: PosixSessionUser
+ windows: WindowsSessionUser
runAs: Literal["QUEUE_CONFIGURED_USER", "WORKER_AGENT_USER"]
diff --git a/test/unit/deadline/test_resources.py b/test/unit/deadline/test_resources.py
index 5c8ee1e..319915e 100644
--- a/test/unit/deadline/test_resources.py
+++ b/test/unit/deadline/test_resources.py
@@ -19,7 +19,7 @@
TaskStatus,
)
from deadline_test_fixtures.deadline import resources as mod
-from deadline_test_fixtures.models import JobRunAsUser, PosixSessionUser
+from deadline_test_fixtures.models import JobRunAsUser, PosixSessionUser, WindowsSessionUser
@pytest.fixture(autouse=True)
@@ -101,6 +101,7 @@ def test_create(self, farm: Farm) -> None:
job_run_as_user = JobRunAsUser(
posix=PosixSessionUser(user="test-user", group="test-group"),
runAs="QUEUE_CONFIGURED_USER",
+ windows=WindowsSessionUser(user="job-user", passwordArn="dummyvalue"),
)
# WHEN
diff --git a/test/unit/deadline/test_worker.py b/test/unit/deadline/test_worker.py
index 939119a..3deb39b 100644
--- a/test/unit/deadline/test_worker.py
+++ b/test/unit/deadline/test_worker.py
@@ -17,10 +17,9 @@
CommandResult,
DeadlineWorkerConfiguration,
DockerContainerWorker,
- EC2InstanceWorker,
+ PosixInstanceWorker,
PipInstall,
CodeArtifactRepositoryInfo,
- OperatingSystem,
S3Object,
Fleet,
Farm,
@@ -66,8 +65,8 @@ def worker_config(region: str) -> DeadlineWorkerConfiguration:
farm_id="farm-123",
fleet=Fleet(id="fleet_123", farm=Farm(id="farm-123")),
region=region,
- user="test-user",
- group="test-group",
+ job_user="test-user",
+ job_user_group="test-group",
allow_shutdown=False,
worker_agent_install=PipInstall(
requirement_specifiers=["deadline-cloud-worker-agent"],
@@ -84,11 +83,10 @@ def worker_config(region: str) -> DeadlineWorkerConfiguration:
("/aws/models/deadline.json", "/tmp/deadline.json"),
],
service_model_path="/path/to/service-2.json",
- operating_system=OperatingSystem(name="AL2023"),
)
-class TestEC2InstanceWorker:
+class TestPosixInstanceWorker:
@staticmethod
def describe_instance(instance_id: str) -> Any:
ec2_client = boto3.client("ec2")
@@ -150,8 +148,8 @@ def worker(
security_group_id: str,
instance_profile_name: str,
bootstrap_bucket_name: str,
- ) -> EC2InstanceWorker:
- return EC2InstanceWorker(
+ ) -> PosixInstanceWorker:
+ return PosixInstanceWorker(
subnet_id=subnet_id,
security_group_id=security_group_id,
instance_profile_name=instance_profile_name,
@@ -161,11 +159,12 @@ def worker(
ssm_client=boto3.client("ssm"),
deadline_client=boto3.client("deadline"),
configuration=worker_config,
- worker_id="worker-7c3377ec9eba444bb51cc7da18463081",
+ instance_type="t3.micro",
+ instance_shutdown_behavior="terminate",
)
@patch.object(mod, "open", mock_open(read_data="mock data".encode()))
- def test_start(self, worker: EC2InstanceWorker) -> None:
+ def test_start(self, worker: PosixInstanceWorker) -> None:
# GIVEN
s3_files = [
("s3://bucket/key", "/tmp/key"),
@@ -194,7 +193,7 @@ def test_start(self, worker: EC2InstanceWorker) -> None:
def test_stage_s3_bucket(
self,
- worker: EC2InstanceWorker,
+ worker: PosixInstanceWorker,
worker_config: DeadlineWorkerConfiguration,
bootstrap_bucket_name: str,
) -> None:
@@ -222,7 +221,7 @@ def test_stage_s3_bucket(
def test_launch_instance(
self,
- worker: EC2InstanceWorker,
+ worker: PosixInstanceWorker,
vpc_id: str,
subnet_id: str,
security_group_id: str,
@@ -234,7 +233,7 @@ def test_launch_instance(
# THEN
assert worker.instance_id is not None
- instance = TestEC2InstanceWorker.describe_instance(worker.instance_id)
+ instance = TestPosixInstanceWorker.describe_instance(worker.instance_id)
assert instance["ImageId"] == worker.ami_id
assert instance["State"]["Name"] == "running"
assert instance["SubnetId"] == subnet_id
@@ -249,7 +248,7 @@ def test_launch_instance(
def test_start_worker_agent(self) -> None:
pass
- def test_stop(self, worker: EC2InstanceWorker) -> None:
+ def test_stop(self, worker: PosixInstanceWorker) -> None:
# GIVEN
# WHEN
with patch.object(
@@ -259,18 +258,18 @@ def test_stop(self, worker: EC2InstanceWorker) -> None:
instance_id = worker.instance_id
assert instance_id is not None
- instance = TestEC2InstanceWorker.describe_instance(instance_id)
+ instance = TestPosixInstanceWorker.describe_instance(instance_id)
assert instance["State"]["Name"] == "running"
worker.stop()
# THEN
- instance = TestEC2InstanceWorker.describe_instance(instance_id)
+ instance = TestPosixInstanceWorker.describe_instance(instance_id)
assert instance["State"]["Name"] == "terminated"
assert worker.instance_id is None
class TestSendCommand:
- def test_sends_command(self, worker: EC2InstanceWorker) -> None:
+ def test_sends_command(self, worker: PosixInstanceWorker) -> None:
# GIVEN
cmd = 'echo "Hello world"'
# WHEN
@@ -292,7 +291,7 @@ def test_sends_command(self, worker: EC2InstanceWorker) -> None:
Parameters={"commands": [cmd]},
)
- def test_retries_when_instance_not_ready(self, worker: EC2InstanceWorker) -> None:
+ def test_retries_when_instance_not_ready(self, worker: PosixInstanceWorker) -> None:
# GIVEN
cmd = 'echo "Hello world"'
# WHEN
@@ -330,7 +329,7 @@ def side_effect(*args, **kwargs):
* 2
)
- def test_raises_any_other_error(self, worker: EC2InstanceWorker) -> None:
+ def test_raises_any_other_error(self, worker: PosixInstanceWorker) -> None:
# GIVEN
cmd = 'echo "Hello world"'
# WHEN
@@ -363,18 +362,18 @@ def test_raises_any_other_error(self, worker: EC2InstanceWorker) -> None:
"worker-7c3377ec9eba444bb51cc7da18463081\r\n",
],
)
- def test_get_worker_id(self, worker_id: str, worker: EC2InstanceWorker) -> None:
+ def test_get_worker_id(self, worker_id: str, worker: PosixInstanceWorker) -> None:
# GIVEN
with patch.object(
worker, "send_command", return_value=CommandResult(exit_code=0, stdout=worker_id)
):
# WHEN
- result = worker.worker_id
+ result = worker.get_worker_id()
# THEN
assert result == worker_id.rstrip("\n\r")
- def test_ami_id(self, worker: EC2InstanceWorker) -> None:
+ def test_ami_id(self, worker: PosixInstanceWorker) -> None:
# WHEN
ami_id = worker.ami_id
@@ -382,6 +381,7 @@ def test_ami_id(self, worker: EC2InstanceWorker) -> None:
assert re.match(r"^ami-[0-9a-f]{17}$", ami_id)
+@pytest.mark.skip
class TestDockerContainerWorker:
@pytest.fixture
def worker(self, worker_config: DeadlineWorkerConfiguration) -> DockerContainerWorker:
@@ -443,8 +443,8 @@ def test_start(
assert popen_kwargs["encoding"] == "utf-8"
expected_env = {
"FILE_MAPPINGS": ANY,
- "AGENT_USER": worker_config.user,
- "SHARED_GROUP": worker_config.group,
+ "AGENT_USER": worker_config.agent_user,
+ "SHARED_GROUP": worker_config.job_user_group,
"JOB_USER": "jobuser",
"CONFIGURE_WORKER_AGENT_CMD": ANY,
}
@@ -488,7 +488,9 @@ def test_stop(
# THEN
assert worker.container_id is None
- mock_send_command.assert_called_once_with(f"pkill --signal term -f {worker_config.user}")
+ mock_send_command.assert_called_once_with(
+ f"pkill --signal term -f {worker_config.agent_user}"
+ )
mock_check_output.assert_called_once_with(
args=["docker", "container", "stop", container_id],
cwd=ANY,
@@ -540,7 +542,7 @@ def test_worker_id(self, worker: DockerContainerWorker) -> None:
with patch.object(worker, "send_command", return_value=send_command_result):
# WHEN
- result = worker.worker_id
+ result = worker.get_worker_id()
# THEN
assert result == worker_id