1
# Copyright 2014 Canonical Ltd. This software is licensed under the
2
# GNU Affero General Public License version 3 (see the file LICENSE).
6
from __future__ import (
25
from errno import ENOENT
28
from provisioningserver.boot.tftppath import compose_image_path
29
from provisioningserver.kernel_opts import compose_kernel_command_line
30
from provisioningserver.utils import locate_config
31
from provisioningserver.utils.registry import Registry
35
class BootMethodError(Exception):
36
"""Exception raised for errors from a BootMethod."""
39
class BootMethodInstallError(BootMethodError):
40
"""Exception raised for errors from a BootMethod performing
45
def get_parameters(match):
46
"""Helper that gets the matched parameters from the
51
for key, value in match.groupdict().items()
56
def gen_template_filenames(purpose, arch, subarch):
57
"""List possible template filenames.
59
:param purpose: The boot purpose, e.g. "local".
60
:param arch: Main machine architecture.
61
:param subarch: Sub-architecture, or "generic" if there is none.
63
Returns a list of possible PXE template filenames using the following
66
config.{purpose}.{arch}.{subarch}.template
67
config.{purpose}.{arch}.template
68
config.{purpose}.template
72
elements = [purpose, arch, subarch]
73
while len(elements) >= 1:
74
yield "config.%s.template" % ".".join(elements)
76
yield "config.template"
80
"""Skeleton for a boot method."""
82
__metaclass__ = ABCMeta
86
"""Name of the boot method."""
89
def template_subdir(self):
90
"""Name of template sub-directory."""
93
def bootloader_path(self):
94
"""Relative path from tftproot to boot loader."""
98
"""Architecture type that supports this method. Used for the
99
dhcpd.conf file that is generated. Must be in the format XX:XX.
103
def match_config_path(self, path):
104
"""Checks path for the configuration file that needs to be
107
:param path: requested path
108
:returns: dict of match params from path, None if no match
112
def render_config(self, kernel_params, **extra):
113
"""Render a configuration file as a unicode string.
115
:param kernel_params: An instance of `KernelParameters`.
116
:param extra: Allow for other arguments. This is a safety valve;
117
parameters generated in another component (for example, see
118
`TFTPBackend.get_config_reader`) won't cause this to break.
122
def install_bootloader(self, destination):
123
"""Installs the required files for this boot method into the
126
:param destination: path to install bootloader
129
def get_template_dir(self):
130
"""Gets the template directory for the boot method."""
131
return locate_config("templates/%s" % self.template_subdir)
133
def get_template(self, purpose, arch, subarch):
134
"""Gets the best avaliable template for the boot method.
136
Templates are loaded each time here so that they can be changed on
137
the fly without restarting the provisioning server.
139
:param purpose: The boot purpose, e.g. "local".
140
:param arch: Main machine architecture.
141
:param subarch: Sub-architecture, or "generic" if there is none.
142
:returns: `tempita.Template`
144
pxe_templates_dir = self.get_template_dir()
145
for filename in gen_template_filenames(purpose, arch, subarch):
146
template_name = path.join(pxe_templates_dir, filename)
148
return tempita.Template.from_filename(
149
template_name, encoding="UTF-8")
150
except IOError as error:
151
if error.errno != ENOENT:
155
"No PXE template found in %r for:\n"
156
" Purpose: %r, Arch: %r, Subarch: %r\n"
157
"This can happen if you manually power up a node when its "
158
"state is not one that allows it. Is the node in the "
159
"'Declared' or 'Ready' states? It needs to be Enlisting, "
160
"Commissioning or Allocated." % (
161
pxe_templates_dir, purpose, arch, subarch))
163
raise AssertionError(error)
165
def compose_template_namespace(self, kernel_params):
166
"""Composes the namespace variables that are used by a boot
169
def image_dir(params):
170
return compose_image_path(
171
params.arch, params.subarch,
172
params.release, params.label)
174
def initrd_path(params):
175
if params.purpose == "install":
176
return "%s/di-initrd" % image_dir(params)
178
return "%s/boot-initrd" % image_dir(params)
180
def kernel_path(params):
181
if params.purpose == "install":
182
return "%s/di-kernel" % image_dir(params)
184
return "%s/boot-kernel" % image_dir(params)
186
def kernel_command(params):
187
return compose_kernel_command_line(params)
190
"initrd_path": initrd_path,
191
"kernel_command": kernel_command,
192
"kernel_params": kernel_params,
193
"kernel_path": kernel_path,
198
class BootMethodRegistry(Registry):
199
"""Registry for boot method classes."""
202
# Import the supported boot methods after defining BootMethod.
203
from provisioningserver.boot.pxe import PXEBootMethod
204
from provisioningserver.boot.uefi import UEFIBootMethod
207
builtin_boot_methods = [
211
for method in builtin_boot_methods:
212
BootMethodRegistry.register_item(method.name, method)