|
Server : Apache System : Linux server.mata-lashes.com 3.10.0-1160.90.1.el7.x86_64 #1 SMP Thu May 4 15:21:22 UTC 2023 x86_64 User : matalashes ( 1004) PHP Version : 8.1.29 Disable Function : NONE Directory : /usr/src/cloud-init/tests/unittests/config/ |
Upload File : |
# This file is part of cloud-init. See LICENSE file for license information.
import copy
import os
import re
import pytest
from cloudinit import log
from cloudinit.config import cc_ubuntu_drivers as drivers
from cloudinit.config.schema import (
SchemaValidationError,
get_schema,
validate_cloudconfig_schema,
)
from cloudinit.subp import ProcessExecutionError
from tests.unittests.helpers import mock, skipUnlessJsonSchema
MPATH = "cloudinit.config.cc_ubuntu_drivers."
M_TMP_PATH = MPATH + "temp_utils.mkdtemp"
OLD_UBUNTU_DRIVERS_ERROR_STDERR = (
"ubuntu-drivers: error: argument <command>: invalid choice: 'install' "
"(choose from 'list', 'autoinstall', 'devices', 'debug')\n"
)
# The tests in this module call helper methods which are decorated with
# mock.patch. pylint doesn't understand that mock.patch passes parameters to
# the decorated function, so it incorrectly reports that we aren't passing
# values for all parameters. Instead of annotating every single call, we
# disable it for the entire module:
# pylint: disable=no-value-for-parameter
@pytest.mark.parametrize(
"cfg_accepted,install_gpgpu",
[
pytest.param(
{"drivers": {"nvidia": {"license-accepted": True}}},
["ubuntu-drivers", "install", "--gpgpu", "nvidia"],
id="without_version",
),
pytest.param(
{
"drivers": {
"nvidia": {"license-accepted": True, "version": "123"}
}
},
["ubuntu-drivers", "install", "--gpgpu", "nvidia:123"],
id="with_version",
),
],
)
@mock.patch(MPATH + "debconf")
@mock.patch(MPATH + "HAS_DEBCONF", True)
class TestUbuntuDrivers:
install_gpgpu = ["ubuntu-drivers", "install", "--gpgpu", "nvidia"]
@pytest.mark.parametrize(
"true_value",
[
True,
"yes",
"true",
"on",
"1",
],
)
@mock.patch(M_TMP_PATH)
@mock.patch(MPATH + "subp.subp", return_value=("", ""))
@mock.patch(MPATH + "subp.which", return_value=False)
def test_happy_path_taken(
self,
m_which,
m_subp,
m_tmp,
m_debconf,
tmpdir,
cfg_accepted,
install_gpgpu,
true_value,
):
"""Positive path test through handle. Package should be installed."""
new_config: dict = copy.deepcopy(cfg_accepted)
new_config["drivers"]["nvidia"]["license-accepted"] = true_value
tdir = tmpdir
debconf_file = tdir.join("nvidia.template")
m_tmp.return_value = tdir
myCloud = mock.MagicMock()
drivers.handle("ubuntu_drivers", new_config, myCloud, None)
assert [
mock.call(drivers.X_LOADTEMPLATEFILE, debconf_file)
] == m_debconf.DebconfCommunicator().__enter__().command.call_args_list
assert [
mock.call(["ubuntu-drivers-common"])
] == myCloud.distro.install_packages.call_args_list
assert [mock.call(install_gpgpu)] == m_subp.call_args_list
@mock.patch(M_TMP_PATH)
@mock.patch(MPATH + "subp.subp")
@mock.patch(MPATH + "subp.which", return_value=False)
def test_handle_raises_error_if_no_drivers_found(
self,
m_which,
m_subp,
m_tmp,
m_debconf,
caplog,
tmpdir,
cfg_accepted,
install_gpgpu,
):
"""If ubuntu-drivers doesn't install any drivers, raise an error."""
tdir = tmpdir
debconf_file = os.path.join(tdir, "nvidia.template")
m_tmp.return_value = tdir
myCloud = mock.MagicMock()
m_subp.side_effect = ProcessExecutionError(
stdout="No drivers found for installation.\n", exit_code=1
)
with pytest.raises(Exception):
drivers.handle("ubuntu_drivers", cfg_accepted, myCloud, None)
assert [
mock.call(drivers.X_LOADTEMPLATEFILE, debconf_file)
] == m_debconf.DebconfCommunicator().__enter__().command.call_args_list
assert [
mock.call(["ubuntu-drivers-common"])
] == myCloud.distro.install_packages.call_args_list
assert [mock.call(install_gpgpu)] == m_subp.call_args_list
assert (
"ubuntu-drivers found no drivers for installation" in caplog.text
)
@pytest.mark.parametrize(
"config",
[
pytest.param(
{"drivers": {"nvidia": {"license-accepted": False}}},
id="license_not_accepted",
),
pytest.param(
{"drivers": {"nvidia": {"license-accepted": "garbage"}}},
id="garbage_in_license_field",
),
pytest.param({"drivers": {"nvidia": {}}}, id="no_license_key"),
pytest.param(
{"drivers": {"acme": {"license-accepted": True}}},
id="no_nvidia_key",
),
# ensure we don't do anything if string refusal given
pytest.param(
{"drivers": {"nvidia": {"license-accepted": "no"}}},
id="string_given_no",
),
pytest.param(
{"drivers": {"nvidia": {"license-accepted": "false"}}},
id="string_given_false",
),
pytest.param(
{"drivers": {"nvidia": {"license-accepted": "off"}}},
id="string_given_off",
),
pytest.param(
{"drivers": {"nvidia": {"license-accepted": "0"}}},
id="string_given_0",
),
# specifying_a_version_doesnt_override_license_acceptance
pytest.param(
{
"drivers": {
"nvidia": {"license-accepted": False, "version": "123"}
}
},
id="with_version",
),
],
)
@mock.patch(MPATH + "subp.subp", return_value=("", ""))
@mock.patch(MPATH + "subp.which", return_value=False)
def test_handle_inert(
self, m_which, m_subp, m_debconf, cfg_accepted, install_gpgpu, config
):
"""Helper to reduce repetition when testing negative cases"""
myCloud = mock.MagicMock()
drivers.handle("ubuntu_drivers", config, myCloud, None)
assert 0 == myCloud.distro.install_packages.call_count
assert 0 == m_subp.call_count
@mock.patch(MPATH + "install_drivers")
@mock.patch(MPATH + "LOG")
def test_handle_no_drivers_does_nothing(
self, m_log, m_install_drivers, m_debconf, cfg_accepted, install_gpgpu
):
"""If no 'drivers' key in the config, nothing should be done."""
myCloud = mock.MagicMock()
drivers.handle("ubuntu_drivers", {"foo": "bzr"}, myCloud, None)
assert "Skipping module named" in m_log.debug.call_args_list[0][0][0]
assert 0 == m_install_drivers.call_count
@mock.patch(M_TMP_PATH)
@mock.patch(MPATH + "subp.subp", return_value=("", ""))
@mock.patch(MPATH + "subp.which", return_value=True)
def test_install_drivers_no_install_if_present(
self,
m_which,
m_subp,
m_tmp,
m_debconf,
tmpdir,
cfg_accepted,
install_gpgpu,
):
"""If 'ubuntu-drivers' is present, no package install should occur."""
tdir = tmpdir
debconf_file = tmpdir.join("nvidia.template")
m_tmp.return_value = tdir
pkg_install = mock.MagicMock()
distro = mock.Mock()
drivers.install_drivers(
cfg_accepted["drivers"],
pkg_install_func=pkg_install,
distro=distro,
)
assert 0 == pkg_install.call_count
assert [mock.call("ubuntu-drivers")] == m_which.call_args_list
assert [
mock.call(drivers.X_LOADTEMPLATEFILE, debconf_file)
] == m_debconf.DebconfCommunicator().__enter__().command.call_args_list
assert [mock.call(install_gpgpu)] == m_subp.call_args_list
def test_install_drivers_rejects_invalid_config(
self, m_debconf, cfg_accepted, install_gpgpu
):
"""install_drivers should raise TypeError if not given a config dict"""
pkg_install = mock.MagicMock()
distro = mock.Mock()
with pytest.raises(TypeError, match=".*expected dict.*"):
drivers.install_drivers(
"mystring", pkg_install_func=pkg_install, distro=distro
)
assert 0 == pkg_install.call_count
@mock.patch(M_TMP_PATH)
@mock.patch(MPATH + "subp.subp")
@mock.patch(MPATH + "subp.which", return_value=False)
def test_install_drivers_handles_old_ubuntu_drivers_gracefully(
self,
m_which,
m_subp,
m_tmp,
m_debconf,
caplog,
tmpdir,
cfg_accepted,
install_gpgpu,
):
"""Older ubuntu-drivers versions should emit message and raise error"""
debconf_file = tmpdir.join("nvidia.template")
m_tmp.return_value = tmpdir
myCloud = mock.MagicMock()
m_subp.side_effect = ProcessExecutionError(
stderr=OLD_UBUNTU_DRIVERS_ERROR_STDERR, exit_code=2
)
with pytest.raises(Exception):
drivers.handle("ubuntu_drivers", cfg_accepted, myCloud, None)
assert [
mock.call(drivers.X_LOADTEMPLATEFILE, debconf_file)
] == m_debconf.DebconfCommunicator().__enter__().command.call_args_list
assert [
mock.call(["ubuntu-drivers-common"])
] == myCloud.distro.install_packages.call_args_list
assert [mock.call(install_gpgpu)] == m_subp.call_args_list
assert (
MPATH[:-1],
log.WARNING,
(
"the available version of ubuntu-drivers is"
" too old to perform requested driver installation"
),
) == caplog.record_tuples[-1]
@mock.patch(M_TMP_PATH)
@mock.patch(MPATH + "subp.subp", return_value=("", ""))
@mock.patch(MPATH + "subp.which", return_value=False)
def test_debconf_not_installed_does_nothing(
self,
m_which,
m_subp,
m_tmp,
m_debconf,
tmpdir,
cfg_accepted,
install_gpgpu,
):
m_debconf.DebconfCommunicator.side_effect = AttributeError
m_tmp.return_value = tmpdir
myCloud = mock.MagicMock()
version_none_cfg = {
"drivers": {"nvidia": {"license-accepted": True, "version": None}}
}
with pytest.raises(AttributeError):
drivers.handle("ubuntu_drivers", version_none_cfg, myCloud, None)
assert (
0 == m_debconf.DebconfCommunicator.__enter__().command.call_count
)
assert 0 == m_subp.call_count
@mock.patch(MPATH + "debconf")
@mock.patch(MPATH + "HAS_DEBCONF", True)
class TestUbuntuDriversWithVersion:
"""With-version specific tests"""
cfg_accepted = {
"drivers": {"nvidia": {"license-accepted": True, "version": "123"}}
}
install_gpgpu = ["ubuntu-drivers", "install", "--gpgpu", "nvidia:123"]
@mock.patch(M_TMP_PATH)
@mock.patch(MPATH + "subp.subp", return_value=("", ""))
@mock.patch(MPATH + "subp.which", return_value=False)
def test_version_none_uses_latest(
self, m_which, m_subp, m_tmp, m_debconf, tmpdir
):
debconf_file = tmpdir.join("nvidia.template")
m_tmp.return_value = tmpdir
myCloud = mock.MagicMock()
version_none_cfg = {
"drivers": {"nvidia": {"license-accepted": True, "version": None}}
}
drivers.handle("ubuntu_drivers", version_none_cfg, myCloud, None)
assert [
mock.call(drivers.X_LOADTEMPLATEFILE, debconf_file)
] == m_debconf.DebconfCommunicator().__enter__().command.call_args_list
assert [
mock.call(["ubuntu-drivers", "install", "--gpgpu", "nvidia"]),
] == m_subp.call_args_list
@mock.patch(MPATH + "debconf")
class TestUbuntuDriversNotRun:
@mock.patch(MPATH + "HAS_DEBCONF", True)
@mock.patch(M_TMP_PATH)
@mock.patch(MPATH + "install_drivers")
@mock.patch(MPATH + "LOG")
def test_no_cfg_drivers_does_nothing(
self,
m_log,
m_install_drivers,
m_tmp,
m_debconf,
tmpdir,
):
m_tmp.return_value = tmpdir
myCloud = mock.MagicMock()
version_none_cfg = {}
drivers.handle("ubuntu_drivers", version_none_cfg, myCloud, None)
assert 0 == m_install_drivers.call_count
assert (
mock.call(
"Skipping module named %s, no 'drivers' key in config",
"ubuntu_drivers",
)
== m_log.debug.call_args_list[-1]
)
@mock.patch(MPATH + "HAS_DEBCONF", False)
@mock.patch(M_TMP_PATH)
@mock.patch(MPATH + "install_drivers")
@mock.patch(MPATH + "LOG")
def test_has_not_debconf_does_nothing(
self,
m_log,
m_install_drivers,
m_tmp,
m_debconf,
tmpdir,
):
m_tmp.return_value = tmpdir
myCloud = mock.MagicMock()
version_none_cfg = {"drivers": {"nvidia": {"license-accepted": True}}}
drivers.handle("ubuntu_drivers", version_none_cfg, myCloud, None)
assert 0 == m_install_drivers.call_count
assert (
mock.call(
"Skipping module named %s, 'python3-debconf' is not installed",
"ubuntu_drivers",
)
== m_log.warning.call_args_list[-1]
)
class TestUbuntuAdvantageSchema:
@pytest.mark.parametrize(
"config, error_msg",
[
# Strict boolean license-accepted
(
{"drivers": {"nvidia": {"license-accepted": "TRUE"}}},
"drivers.nvidia.license-accepted: 'TRUE' is not of type"
" 'boolean'",
),
# Additional properties disallowed
(
{"drivers": {"bogus": {"license-accepted": True}}},
re.escape(
"drivers: Additional properties are not allowed ('bogus'"
),
),
(
{"drivers": {"nvidia": {"bogus": True}}},
re.escape(
"drivers.nvidia: Additional properties are not allowed"
" ('bogus' "
),
),
],
)
@skipUnlessJsonSchema()
def test_schema_validation(self, config, error_msg):
if error_msg is None:
validate_cloudconfig_schema(config, get_schema(), strict=True)
else:
with pytest.raises(SchemaValidationError, match=error_msg):
validate_cloudconfig_schema(config, get_schema(), strict=True)
# vi: ts=4 expandtab