Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for kickstart to mrack plugin #3064

Merged
merged 4 commits into from
Oct 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions docs/releases.rst
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,10 @@ guest with the `Input–output memory management unit` has been
added into the :ref:`/spec/hardware` specification and implemented
in the :ref:`/plugins/provision/beaker` provision plugin.

The :ref:`/plugins/provision/beaker` provision plugin now newly
supports providing a custom :ref:`/spec/plans/provision/kickstart`
configuration.


tmt-1.36.1
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Expand Down
6 changes: 6 additions & 0 deletions spec/plans/provision/kickstart.fmf
Original file line number Diff line number Diff line change
Expand Up @@ -74,3 +74,9 @@ example:
"no-autopart harness=restraint"
kernel-options: "ksdevice=eth1"
kernel-options-post: "quiet"

link:
- implemented-by: /tmt/steps/provision/mrack.py
note: since 1.37
- implemented-by: /tmt/steps/provision/artemis.py
note: since 1.22
3 changes: 3 additions & 0 deletions tmt/schemas/provision/beaker.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ properties:
hardware:
$ref: "/schemas/provision/hardware#/definitions/hardware"

kickstart:
$ref: "/schemas/provision/kickstart#/definitions/kickstart"

role:
$ref: "/schemas/common#/definitions/role"

Expand Down
3 changes: 3 additions & 0 deletions tmt/steps/provision/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -719,6 +719,9 @@ def show(
if isinstance(value, (list, tuple)):
printable_value = fmf.utils.listed(value)

elif isinstance(value, dict):
printable_value = tmt.utils.format_value(value)

elif isinstance(value, tmt.hardware.Hardware):
printable_value = tmt.utils.dict_to_yaml(value.to_spec())

Expand Down
33 changes: 3 additions & 30 deletions tmt/steps/provision/artemis.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
UpdatableMessage,
dict_to_yaml,
field,
normalize_string_dict,
retry_session,
)

Expand Down Expand Up @@ -86,34 +87,6 @@
DEFAULT_RETRY_BACKOFF_FACTOR = 1


def _normalize_user_data(
key_address: str,
raw_value: Any,
logger: tmt.log.Logger) -> dict[str, str]:
if isinstance(raw_value, dict):
return {
str(key).strip(): str(value).strip() for key, value in raw_value.items()
}

if isinstance(raw_value, (list, tuple)):
user_data = {}

for datum in raw_value:
try:
key, value = datum.split('=', 1)

except ValueError as exc:
raise tmt.utils.NormalizationError(
key_address, datum, 'a KEY=VALUE string') from exc

user_data[key.strip()] = value.strip()

return user_data

raise tmt.utils.NormalizationError(
key_address, value, 'a dictionary or a list of KEY=VALUE strings')


def _normalize_log_type(
key_address: str,
raw_value: Any,
Expand Down Expand Up @@ -181,14 +154,14 @@ class ArtemisGuestData(tmt.steps.provision.GuestSshData):
metavar='KEY=VALUE',
help='Optional data to attach to guest.',
multiple=True,
normalize=_normalize_user_data)
normalize=normalize_string_dict)
kickstart: dict[str, str] = field(
default_factory=dict,
option='--kickstart',
metavar='KEY=VALUE',
help='Optional Beaker kickstart to use when provisioning the guest.',
multiple=True,
normalize=_normalize_user_data)
normalize=normalize_string_dict)

log_type: list[str] = field(
default_factory=list,
Expand Down
35 changes: 32 additions & 3 deletions tmt/steps/provision/mrack.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,14 @@
import tmt.steps
import tmt.steps.provision
import tmt.utils
from tmt.utils import Command, Path, ProvisionError, ShellScript, UpdatableMessage, field
from tmt.utils import (
Command,
Path,
ProvisionError,
ShellScript,
UpdatableMessage,
field,
)

mrack: Any
providers: Any
Expand Down Expand Up @@ -691,8 +698,7 @@ def _translate_tmt_hw(self, hw: tmt.hardware.Hardware) -> dict[str, Any]:

def create_host_requirement(self, host: CreateJobParameters) -> dict[str, Any]:
""" Create single input for Beaker provisioner """

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

if host.hardware and host.hardware.constraint:
req.update(self._translate_tmt_hw(host.hardware))
Expand All @@ -703,6 +709,8 @@ def create_host_requirement(self, host: CreateJobParameters) -> dict[str, Any]:
# Whiteboard must be added *after* request preparation, to overwrite the default one.
req['whiteboard'] = host.whiteboard

logger.debug('mrack request', req, level=4)

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

return req
Expand Down Expand Up @@ -779,6 +787,13 @@ class BeakerGuestData(tmt.steps.provision.GuestSshData):
{DEFAULT_API_SESSION_REFRESH} seconds by default.
""",
normalize=tmt.utils.normalize_int)
kickstart: dict[str, str] = field(
default_factory=dict,
option='--kickstart',
metavar='KEY=VALUE',
help='Optional Beaker kickstart to use when provisioning the guest.',
multiple=True,
normalize=tmt.utils.normalize_string_dict)

beaker_job_owner: Optional[str] = field(
default=None,
Expand Down Expand Up @@ -821,10 +836,22 @@ class CreateJobParameters:
os: str
arch: str
hardware: Optional[tmt.hardware.Hardware]
kickstart: dict[str, str]
whiteboard: Optional[str]
beaker_job_owner: Optional[str]
group: str = 'linux'

def to_mrack(self) -> dict[str, Any]:
data = dataclasses.asdict(self)

data['beaker'] = {}

if self.kickstart:
data['beaker']['ks_meta'] = self.kickstart.get('metadata')
data['beaker']['ks_append'] = self.kickstart

return data


class BeakerAPI:
# req is a requirement passed to Beaker mrack provisioner
Expand Down Expand Up @@ -923,6 +950,7 @@ class GuestBeaker(tmt.steps.provision.GuestSsh):
arch: str
image: str = "fedora-latest"
hardware: Optional[tmt.hardware.Hardware] = None
kickstart: dict[str, str]

beaker_job_owner: Optional[str] = None

Expand Down Expand Up @@ -995,6 +1023,7 @@ def _create(self, tmt_name: str) -> None:
data = CreateJobParameters(
tmt_name=tmt_name,
hardware=self.hardware,
kickstart=self.kickstart,
arch=self.arch,
os=self.image,
name=f'{self.image}-{self.arch}',
Expand Down
47 changes: 47 additions & 0 deletions tmt/utils/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5389,6 +5389,53 @@ def normalize_shell_script(
raise NormalizationError(key_address, value, 'a string')


def normalize_string_dict(
key_address: str,
raw_value: Any,
logger: tmt.log.Logger) -> dict[str, str]:
"""
Normalize a key/value dictionary.

The input value could be specified in two ways:

* a dictionary, or
* a list of ``KEY=VALUE`` strings.

For example, the following are acceptable inputs:

.. code-block:: python

{'foo': 'bar', 'qux': 'quux'}

['foo=bar', 'qux=quux']

:param value: input value from key source.
"""

if isinstance(raw_value, dict):
return {
str(key).strip(): str(value).strip() for key, value in raw_value.items()
}

if isinstance(raw_value, (list, tuple)):
normalized = {}

for datum in cast(list[str], raw_value):
try:
key, value = datum.split('=', 1)

except ValueError as exc:
raise NormalizationError(
key_address, datum, 'a KEY=VALUE string') from exc

normalized[key.strip()] = value.strip()

return normalized

raise tmt.utils.NormalizationError(
key_address, value, 'a dictionary or a list of KEY=VALUE strings')


class NormalizeKeysMixin(_CommonBase):
"""
Mixin adding support for loading fmf keys into object attributes.
Expand Down
Loading