Skip to content

Commit

Permalink
Support different job owner when creating Beaker jobs (#3179)
Browse files Browse the repository at this point in the history
Adds new key, `beaker-job-owner`; if specified, Beaker jobs would have
`user` attribute set, asking Beaker to use the given user as job owner
instead of the submitter.

Related to #2345.
  • Loading branch information
happz authored Sep 2, 2024
1 parent f054be9 commit 613a7cd
Show file tree
Hide file tree
Showing 3 changed files with 77 additions and 28 deletions.
5 changes: 5 additions & 0 deletions docs/releases.rst
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@ tmt-1.36
We have started to use ``warnings.deprecated`` to advertise upcoming
API deprecations.

The :ref:`/plugins/provision/beaker` provision plugin gains
support for submitting jobs on behalf of other users, through
``beaker-job-owner`` key. The current user must be a submission delegate
for the given job owner.


tmt-1.35
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Expand Down
3 changes: 3 additions & 0 deletions tmt/schemas/provision/beaker.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -43,5 +43,8 @@ properties:
whiteboard:
type: string

beaker-job-owner:
type: string

required:
- how
97 changes: 69 additions & 28 deletions tmt/steps/provision/mrack.py
Original file line number Diff line number Diff line change
Expand Up @@ -673,15 +673,22 @@ def _translate_tmt_hw(self, hw: tmt.hardware.Hardware) -> dict[str, Any]:
'hostRequires': transformed.to_mrack()
}

def create_host_requirement(self, host: dict[str, Any]) -> dict[str, Any]:
def create_host_requirement(self, host: CreateJobParameters) -> dict[str, Any]:
""" Create single input for Beaker provisioner """
hardware = cast(Optional[tmt.hardware.Hardware], host.get('hardware'))
if hardware and hardware.constraint:
host.update({"beaker": self._translate_tmt_hw(hardware)})
req: dict[str, Any] = super().create_host_requirement(host)
whiteboard = host.get("whiteboard", host.get("tmt_name", req.get("whiteboard")))
req.update({"whiteboard": whiteboard})
logger.info('whiteboard', whiteboard, 'green')

req: dict[str, Any] = super().create_host_requirement(dataclasses.asdict(host))

if host.hardware and host.hardware.constraint:
req['beaker'] = self._translate_tmt_hw(host.hardware)

if host.beaker_job_owner:
req['job_owner'] = host.beaker_job_owner

# Whiteboard must be added *after* request preparation, to overwrite the default one.
req['whiteboard'] = host.whiteboard

logger.info('whiteboard', host.whiteboard, 'green')

return req

_MRACK_IMPORTED = True
Expand Down Expand Up @@ -755,6 +762,15 @@ class BeakerGuestData(tmt.steps.provision.GuestSshData):
""",
normalize=tmt.utils.normalize_int)

beaker_job_owner: Optional[str] = field(
default=None,
option='--beaker-job-owner',
metavar='USERNAME',
help="""
If set, Beaker jobs will be submitted on behalf of ``USERNAME``.
Submitting user must be a submission delegate for the ``USERNAME``.
""")


@dataclasses.dataclass
class ProvisionBeakerData(BeakerGuestData, tmt.steps.provision.ProvisionStepData):
Expand All @@ -778,6 +794,20 @@ class ProvisionBeakerData(BeakerGuestData, tmt.steps.provision.ProvisionStepData
}


@dataclasses.dataclass
class CreateJobParameters:
""" Collect all parameters for a future Beaker job """

tmt_name: str
name: str
os: str
arch: str
hardware: Optional[tmt.hardware.Hardware]
whiteboard: Optional[str]
beaker_job_owner: Optional[str]
group: str = 'linux'


class BeakerAPI:
# req is a requirement passed to Beaker mrack provisioner
mrack_requirement: dict[str, Any] = {}
Expand Down Expand Up @@ -840,14 +870,13 @@ async def __init__(self, guest: 'GuestBeaker') -> None: # type: ignore[misc]
@async_run
async def create(
self,
data: dict[str, Any],
) -> Any:
data: CreateJobParameters) -> Any:
"""
Create - or request creation of - a resource using mrack up.
:param data: optional key/value data to send with the request.
:param data: describes the provisioning request.
"""

mrack_requirement = self._mrack_transformer.create_host_requirement(data)
log_msg_start = f"{self.dsp_name} [{self.mrack_requirement.get('name')}]"
self._bkr_job_id, self._req = await self._mrack_provider.create_server(mrack_requirement)
Expand Down Expand Up @@ -880,6 +909,8 @@ class GuestBeaker(tmt.steps.provision.GuestSsh):
image: str = "fedora-latest"
hardware: Optional[tmt.hardware.Hardware] = None

beaker_job_owner: Optional[str] = None

# Provided in Beaker response
job_id: Optional[str]

Expand Down Expand Up @@ -946,25 +977,35 @@ def is_ready(self) -> bool:
def _create(self, tmt_name: str) -> None:
""" Create beaker job xml request and submit it to Beaker hub """

data: dict[str, Any] = {
'tmt_name': tmt_name,
'hardware': self.hardware,
'name': f'{self.image}-{self.arch}',
'os': self.image,
'group': 'linux',
}

if self.whiteboard is not None:
data["whiteboard"] = self.whiteboard

if self.arch is not None:
data["arch"] = self.arch
data = CreateJobParameters(
tmt_name=tmt_name,
hardware=self.hardware,
arch=self.arch,
os=self.image,
name=f'{self.image}-{self.arch}',
whiteboard=self.whiteboard or tmt_name,
beaker_job_owner=self.beaker_job_owner)

try:
response = self.api.create(data)
except ProvisioningError as mrack_provisioning_err:
raise ProvisionError(
f"Failed to create, response:\n{mrack_provisioning_err}")

except ProvisioningError as exc:
import xmlrpc.client

cause = exc.__cause__

if isinstance(cause, xmlrpc.client.Fault):
if 'is not a valid user name' in cause.faultString:
raise ProvisionError(
f"Failed to create Beaker job, job owner '{self.beaker_job_owner}' "
"was refused as unknown.") from exc

if 'is not a valid submission delegate' in cause.faultString:
raise ProvisionError(
f"Failed to create Beaker job, job owner '{self.beaker_job_owner}' "
"is not a valid submission delegate.") from exc

raise ProvisionError('Failed to create Beaker job') from exc

if response:
self.info('guest', 'has been requested', 'green')
Expand Down

0 comments on commit 613a7cd

Please sign in to comment.