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

Change: additional arguments/assertions for UC24 and OEM 24.04 via Zapper KVM #324

Merged
merged 6 commits into from
Aug 9, 2024
Merged
Show file tree
Hide file tree
Changes from 4 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
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ Zapper-driven provisioning method that makes use of KVM assertions and actions.

Support for vanilla Ubuntu is provided by [autoinstall](https://canonical-subiquity.readthedocs-hosted.com/en/latest/intro-to-autoinstall.html). Supported Ubuntu versions are:

- Core24 (experimental)
- Desktop >= 23.04
- Server >= 20.04
- UC24 (experimental)

Unless specified via _autoinstall_ storage filter, the tool will select the largest storage device on the DUT. See [supported layouts](https://canonical-subiquity.readthedocs-hosted.com/en/latest/reference/autoinstall-reference.html#supported-layouts) for more information.

Expand All @@ -18,14 +18,14 @@ Unless specified via _autoinstall_ storage filter, the tool will select the larg
- __url__: URL to the image to install
- __username__: username to configure
- __password__: password to configure
- **storage_layout**: can be either `lvm`, `direct`, `zfs` or `hybrid` (Core, Desktop 23.10+)
- **robot_tasks**: list of Zapper Robot tasks to run after a hard reset in order to follow the `autoinstall` installation
- **cmdline_append** (optional): kernel parameters to append at the end of GRUB entry cmdline
- **base_user_data** (optional): a custom base user-data file, it should be validated against [this schema](https://canonical-subiquity.readthedocs-hosted.com/en/latest/reference/autoinstall-schema.html)
- __storage_layout__: can be either `lvm`, `direct`, `zfs` or `hybrid` (Desktop 23.10+, UC24)
- __robot_tasks__: list of Zapper Robot tasks to run after a hard reset in order to follow the `autoinstall` installation
- __cmdline_append__ (optional): kernel parameters to append at the end of GRUB entry cmdline
- __base_user_data__ (optional): a string containing base64 encoded autoinstall user-data to use as base for provisioning, it should be validated against [this schema](https://canonical-subiquity.readthedocs-hosted.com/en/latest/reference/autoinstall-schema.html)
- __autoinstall_oem__: (optional): set to "true" to install OEM meta-packages and the reset partition (Desktop 24.04+)
- __ubuntu_sso_email__: (optional): a valid Ubuntu SSO email to which the DUT provisioned with a non-dangerous grade UC image will be linked

## Ubuntu Desktop OEM

### Jammy
## Ubuntu Desktop 22.04 OEM

Ubuntu OEM 22.04 is a two step process:

Expand All @@ -38,16 +38,12 @@ The tool will select the storage device with the following priority:
2. NVME
3. SATA

#### Job parameters
### Job parameters

- __alloem_url__: URL to the `alloem` image
- __url__ (optional): URL to the image to test, will be installed via the OEM script
- __password__: password to configure
- **robot_tasks**: list of Zapper Robot tasks to run after a hard reset in order to follow the `alloem` installation

### Noble

Ubuntu OEM 24.04 uses `autoinstall`. The procedure and the arguments are the same as _vanilla_ Ubuntu.
- __robot_tasks__: list of Zapper Robot tasks to run after a hard reset in order to follow the `alloem` installation

## Live ISO

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,14 @@

"""Zapper Connector for KVM provisioning."""

import base64
import binascii
import logging
import os
import subprocess
from typing import Any, Dict, Optional, Tuple

import yaml
from testflinger_device_connectors.devices import ProvisioningError
from testflinger_device_connectors.devices.zapper import ZapperConnector
from testflinger_device_connectors.devices.oemscript import OemScript
Expand All @@ -36,15 +39,36 @@ class DeviceConnector(ZapperConnector):

PROVISION_METHOD = "ProvisioningKVM"

def _validate_user_data(self, encoded_user_data: str):
"""
Assert `base_user_data` argument is a valid base64 encoded YAML.
"""
try:
user_data = base64.b64decode(encoded_user_data.encode()).decode()
yaml.safe_load(user_data)
except (binascii.Error, ValueError) as exc:
raise ProvisioningError(
"Provided `base_user_data` is not base64 encoded."
) from exc
except yaml.YAMLError as exc:
raise ProvisioningError(
"Provided `base_user_data` is not a valid YAML."
) from exc

def _get_autoinstall_conf(self) -> Optional[Dict[str, Any]]:
"""Prepare autoinstall-related configuration."""
provision = self.job_data["provision_data"]

if "storage_layout" not in provision:
return None
plars marked this conversation as resolved.
Show resolved Hide resolved

autoinstall_conf = {"storage_layout": provision["storage_layout"]}
autoinstall_conf = {
"storage_layout": provision["storage_layout"],
"oem": provision.get("autoinstall_oem", False),
}

if "base_user_data" in provision:
self._validate_user_data(provision["base_user_data"])
autoinstall_conf["base_user_data"] = provision["base_user_data"]

with open(os.path.expanduser("~/.ssh/id_rsa.pub")) as pub:
Expand Down Expand Up @@ -77,6 +101,17 @@ def _validate_configuration(
)
retries = self.job_data["provision_data"].get("robot_retries", 1)

# If a SSO email is specified, e.g. UC, username must match
# the local-part because that would be the only user available
# on the DUT after provisioning.
plars marked this conversation as resolved.
Show resolved Hide resolved
if "ubuntu_sso_email" in self.job_data["provision_data"]:
email = self.job_data["provision_data"]["ubuntu_sso_email"]
if username != email.split("@")[0]:
raise ProvisioningError(
"Test username doesn't match the provided "
"ubuntu_sso_email."
)

provisioning_data = {
"url": url,
"username": username,
Expand All @@ -95,6 +130,7 @@ def _validate_configuration(
"skip_download",
"wait_until_ssh",
"live_image",
"ubuntu_sso_email",
]
provisioning_data.update(
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,10 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""Unit tests for Zapper KVM device connector."""

import base64
import subprocess
import unittest
import yaml
from unittest.mock import Mock, patch, mock_open
from testflinger_device_connectors.devices import ProvisioningError
from testflinger_device_connectors.devices.zapper_kvm import DeviceConnector
Expand Down Expand Up @@ -90,6 +92,7 @@ def test_validate_configuration_w_opt(self):
"skip_download": True,
"wait_until_ssh": True,
"live_image": False,
"ubuntu_sso_email": "username@domain.com",
},
"test_data": {
"test_username": "username",
Expand All @@ -113,10 +116,49 @@ def test_validate_configuration_w_opt(self):
"skip_download": True,
"wait_until_ssh": True,
"live_image": False,
"ubuntu_sso_email": "username@domain.com",
}
self.assertEqual(args, ())
self.assertDictEqual(kwargs, expected)

def test_validate_configuration_raises_sso(self):
"""
Test whether the validate_configuration raises an exception
if the provided test_username doesn't match with the SSO email.
"""

connector = DeviceConnector()
connector.config = {
"device_ip": "1.1.1.1",
"control_host": "1.1.1.2",
"reboot_script": ["cmd1", "cmd2"],
}
connector.job_data = {
"job_queue": "queue",
"provision_data": {
"url": "http://example.com/image.iso",
"robot_tasks": [
"job.robot",
"another.robot",
],
"storage_layout": "lvm",
"robot_retries": 3,
"cmdline_append": "more arguments",
"skip_download": True,
"wait_until_ssh": True,
"live_image": False,
"ubuntu_sso_email": "realuser@domain.com",
},
"test_data": {
"test_username": "username",
"test_password": "password",
},
}

connector._get_autoinstall_conf = Mock()
with self.assertRaises(ProvisioningError):
connector._validate_configuration()

def test_validate_configuration_alloem(self):
"""
Test whether the validate_configuration function returns
Expand Down Expand Up @@ -222,6 +264,7 @@ def test_get_autoinstall_conf(self):
expected = {
"storage_layout": "lvm",
"authorized_keys": ["mykey"],
"oem": False,
}
self.assertDictEqual(conf, expected)

Expand All @@ -242,24 +285,58 @@ def test_get_autoinstall_conf_full(self):
"another.robot",
],
"storage_layout": "lvm",
"base_user_data": "base data content",
"base_user_data": "content",
"autoinstall_oem": True,
},
"test_data": {
"test_username": "username",
"test_password": "password",
},
}

connector._validate_user_data = Mock()
with patch("builtins.open", mock_open(read_data="mykey")):
conf = connector._get_autoinstall_conf()

connector._validate_user_data.assert_called_once_with("content")
expected = {
"storage_layout": "lvm",
"base_user_data": "base data content",
"base_user_data": "content",
"authorized_keys": ["mykey"],
"oem": True,
}
self.assertDictEqual(conf, expected)

def test_validate_user_data(self):
"""
Test whether the function returns without errors in case of a
sane base64 encoded YAML.
"""
connector = DeviceConnector()
user_data_base = yaml.safe_dump({"key1": 1, "key2": [1, 2, 3]})
encoded = base64.b64encode(user_data_base.encode()).decode()
connector._validate_user_data(encoded)

def test_validate_user_data_raises_decode(self):
"""
Test whether the function raises an exception if the input
is not correctly encoded.
"""
connector = DeviceConnector()
with self.assertRaises(ProvisioningError):
connector._validate_user_data("notbase64")

def test_validate_user_data_raises_load(self):
"""
Test whether the function raises an exception if the input
is not a valid YAML.
"""
connector = DeviceConnector()
user_data_base = "not: a: correctly: formatted: yaml"
encoded = base64.b64encode(user_data_base.encode()).decode()
with self.assertRaises(ProvisioningError):
connector._validate_user_data(encoded)

def test_run_oem_no_url(self):
"""
Test the function returns without further action when URL
Expand Down
2 changes: 2 additions & 0 deletions docs/.wordlist.txt
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ SecureBoot
SKU
SQA
SSID
SSO
subdirectories
subfolders
subtree
Expand All @@ -97,6 +98,7 @@ txt
Ubuntu
ubuntu
url
UC
UI
URI
USB
Expand Down
12 changes: 8 additions & 4 deletions docs/reference/device-connector-types.rst
Original file line number Diff line number Diff line change
Expand Up @@ -284,16 +284,20 @@ The ``zapper_kvm`` device connector, depending on the target image, supports the
path from the ``robot/snippets`` path in the Zapper repository.
* - ``storage_layout``
- When provisioning an image supporting *autoinstall*, the storage_layout can
be either ``lvm`` (default), ``direct``, ``zfs`` or ``hybrid`` (Core, Desktop 23.10+)
be either ``lvm`` (default), ``direct``, ``zfs`` or ``hybrid`` (Desktop 23.10+, UC24)
* - ``cmdline_append``
- When provisioning an image supporting *autoinstall*, the cmdline_append can
- (Optional) When provisioning an image supporting *autoinstall*, the cmdline_append can
be used to append Kernel parameters to the standard GRUB entry.
* - ``base_user_data``
- When provisioning an image supporting *autoinstall*, the base_user_data can
e used to provide a base user_data file instead of the basic one hosted by Zapper.
- (Optional) A string containing base64 encoded user-data to use as base for autoinstall-driven provisioning.
For more information, see
`Autoinstall Reference <https://canonical-subiquity.readthedocs-hosted.com/en/latest/reference/autoinstall-reference.html>`_.
on this topic
* - ``autoinstall_oem``:
- (Optional) Set to "true" to install OEM meta-packages and the reset partition (Desktop 24.04+).
* - ``ubuntu_sso_email``:
- (Optional) A valid Ubuntu SSO email to which the DUT provisioned with a non-dangerous grade UC image will be linked (UC24).
.

.. list-table:: Supported ``provision_data`` keys for ``zapper_kvm`` with target Ubuntu OEM 22.04
:header-rows: 1
Expand Down