1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134
|
# This file is part of cloud-init. See LICENSE file for license information.
"""Ubuntu Drivers: Interact with third party drivers in Ubuntu."""
import logging
import os
from cloudinit.cloud import Cloud
from cloudinit.distros import Distro
try:
import debconf
HAS_DEBCONF = True
except ImportError:
debconf = None
HAS_DEBCONF = False
from cloudinit import subp, temp_utils, type_utils, util
from cloudinit.config import Config
from cloudinit.config.schema import MetaSchema
from cloudinit.settings import PER_INSTANCE
LOG = logging.getLogger(__name__)
meta: MetaSchema = {
"id": "cc_ubuntu_drivers",
"distros": ["ubuntu"],
"frequency": PER_INSTANCE,
"activate_by_schema_keys": ["drivers"],
}
OLD_UBUNTU_DRIVERS_STDERR_NEEDLE = (
"ubuntu-drivers: error: argument <command>: invalid choice: 'install'"
)
# Use a debconf template to configure a global debconf variable
# (linux/nvidia/latelink) setting this to "true" allows the
# 'linux-restricted-modules' deb to accept the NVIDIA EULA and the package
# will automatically link the drivers to the running kernel.
NVIDIA_DEBCONF_CONTENT = """\
Template: linux/nvidia/latelink
Type: boolean
Default: true
Description: Late-link NVIDIA kernel modules?
Enable this to link the NVIDIA kernel modules in cloud-init and
make them available for use.
"""
X_LOADTEMPLATEFILE = "X_LOADTEMPLATEFILE"
def install_drivers(cfg, pkg_install_func, distro: Distro):
if not isinstance(cfg, dict):
raise TypeError(
"'drivers' config expected dict, found '%s': %s"
% (type_utils.obj_name(cfg), cfg)
)
cfgpath = "nvidia/license-accepted"
# Call translate_bool to ensure that we treat string values like "yes" as
# acceptance and _don't_ treat string values like "nah" as acceptance
# because they're True-ish
nv_acc = util.translate_bool(util.get_cfg_by_path(cfg, cfgpath))
if not nv_acc:
LOG.debug("Not installing NVIDIA drivers. %s=%s", cfgpath, nv_acc)
return
if not subp.which("ubuntu-drivers"):
LOG.debug(
"'ubuntu-drivers' command not available. "
"Installing ubuntu-drivers-common"
)
pkg_install_func(["ubuntu-drivers-common"])
driver_arg = "nvidia"
version_cfg = util.get_cfg_by_path(cfg, "nvidia/version")
if version_cfg:
driver_arg += ":{}".format(version_cfg)
LOG.debug(
"Installing and activating NVIDIA drivers (%s=%s, version=%s)",
cfgpath,
nv_acc,
version_cfg if version_cfg else "latest",
)
# Register and set debconf selection linux/nvidia/latelink = true
tdir = temp_utils.mkdtemp(dir=distro.get_tmp_exec_path(), needs_exe=True)
debconf_file = os.path.join(tdir, "nvidia.template")
try:
util.write_file(debconf_file, NVIDIA_DEBCONF_CONTENT)
with debconf.DebconfCommunicator("cloud-init") as dc:
dc.command(X_LOADTEMPLATEFILE, debconf_file)
except Exception as e:
util.logexc(
LOG, "Failed to register NVIDIA debconf template: %s", str(e)
)
raise
finally:
if os.path.isdir(tdir):
util.del_dir(tdir)
try:
subp.subp(["ubuntu-drivers", "install", "--gpgpu", driver_arg])
except subp.ProcessExecutionError as exc:
if OLD_UBUNTU_DRIVERS_STDERR_NEEDLE in exc.stderr:
LOG.warning(
"the available version of ubuntu-drivers is"
" too old to perform requested driver installation"
)
elif "No drivers found for installation." in exc.stdout:
LOG.warning("ubuntu-drivers found no drivers for installation")
raise
def handle(name: str, cfg: Config, cloud: Cloud, args: list) -> None:
if "drivers" not in cfg:
LOG.debug("Skipping module named %s, no 'drivers' key in config", name)
return
if not HAS_DEBCONF:
LOG.warning(
"Skipping module named %s, 'python3-debconf' is not installed",
name,
)
return
install_drivers(
cfg["drivers"], cloud.distro.install_packages, cloud.distro
)
|