A service for managing and provisioning Bare Metal servers.
Você não pode selecionar mais de 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.

312 linhas
13KB

  1. # Copyright 2013 Hewlett-Packard Development Company, L.P.
  2. #
  3. # Licensed under the Apache License, Version 2.0 (the "License"); you may
  4. # not use this file except in compliance with the License. You may obtain
  5. # a copy of the License at
  6. #
  7. # http://www.apache.org/licenses/LICENSE-2.0
  8. #
  9. # Unless required by applicable law or agreed to in writing, software
  10. # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  11. # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  12. # License for the specific language governing permissions and limitations
  13. # under the License.
  14. """
  15. iPXE Boot Interface
  16. """
  17. from ironic_lib import metrics_utils
  18. from oslo_log import log as logging
  19. from oslo_utils import strutils
  20. from ironic.common import boot_devices
  21. from ironic.common import dhcp_factory
  22. from ironic.common import exception
  23. from ironic.common.glance_service import service_utils
  24. from ironic.common.i18n import _
  25. from ironic.common import pxe_utils
  26. from ironic.common import states
  27. from ironic.conductor import utils as manager_utils
  28. from ironic.conf import CONF
  29. from ironic.drivers import base
  30. from ironic.drivers.modules import boot_mode_utils
  31. from ironic.drivers.modules import deploy_utils
  32. from ironic.drivers.modules import pxe
  33. from ironic.drivers.modules import pxe_base
  34. from ironic.drivers import utils as driver_utils
  35. LOG = logging.getLogger(__name__)
  36. METRICS = metrics_utils.get_metrics_logger(__name__)
  37. COMMON_PROPERTIES = pxe_base.COMMON_PROPERTIES
  38. class iPXEBoot(pxe_base.PXEBaseMixin, base.BootInterface):
  39. ipxe_enabled = True
  40. capabilities = ['iscsi_volume_boot', 'ramdisk_boot', 'ipxe_boot']
  41. def __init__(self):
  42. pxe_utils.create_ipxe_boot_script()
  43. @METRICS.timer('iPXEBoot.validate')
  44. def validate(self, task):
  45. """Validate the PXE-specific info for booting deploy/instance images.
  46. This method validates the PXE-specific info for booting the
  47. ramdisk and instance on the node. If invalid, raises an
  48. exception; otherwise returns None.
  49. :param task: a task from TaskManager.
  50. :returns: None
  51. :raises: InvalidParameterValue, if some parameters are invalid.
  52. :raises: MissingParameterValue, if some required parameters are
  53. missing.
  54. """
  55. node = task.node
  56. if not driver_utils.get_node_mac_addresses(task):
  57. raise exception.MissingParameterValue(
  58. _("Node %s does not have any port associated with it.")
  59. % node.uuid)
  60. if not CONF.deploy.http_url or not CONF.deploy.http_root:
  61. raise exception.MissingParameterValue(_(
  62. "iPXE boot is enabled but no HTTP URL or HTTP "
  63. "root was specified."))
  64. # Check the trusted_boot capabilities value.
  65. deploy_utils.validate_capabilities(node)
  66. if deploy_utils.is_trusted_boot_requested(node):
  67. # Check if 'boot_option' and boot mode is compatible with
  68. # trusted boot.
  69. # NOTE(TheJulia): So in theory (huge theory here, not put to
  70. # practice or tested), that one can define the kernel as tboot
  71. # and define the actual kernel and ramdisk as appended data.
  72. # Similar to how one can iPXE load the XEN hypervisor.
  73. # tboot mailing list seem to indicate pxe/ipxe support, or
  74. # more specifically avoiding breaking the scenarios of use,
  75. # but there is also no definitive documentation on the subject.
  76. LOG.warning('Trusted boot has been requested for %(node)s in '
  77. 'concert with iPXE. This is not a supported '
  78. 'configuration for an ironic deployment.',
  79. {'node': node.uuid})
  80. pxe.validate_boot_parameters_for_trusted_boot(node)
  81. pxe_utils.parse_driver_info(node)
  82. # NOTE(TheJulia): If we're not writing an image, we can skip
  83. # the remainder of this method.
  84. if (not task.driver.storage.should_write_image(task)):
  85. return
  86. d_info = deploy_utils.get_image_instance_info(node)
  87. if (node.driver_internal_info.get('is_whole_disk_image')
  88. or deploy_utils.get_boot_option(node) == 'local'):
  89. props = []
  90. elif service_utils.is_glance_image(d_info['image_source']):
  91. props = ['kernel_id', 'ramdisk_id']
  92. else:
  93. props = ['kernel', 'ramdisk']
  94. deploy_utils.validate_image_properties(task.context, d_info, props)
  95. @METRICS.timer('iPXEBoot.prepare_ramdisk')
  96. def prepare_ramdisk(self, task, ramdisk_params):
  97. """Prepares the boot of Ironic ramdisk using PXE.
  98. This method prepares the boot of the deploy or rescue kernel/ramdisk
  99. after reading relevant information from the node's driver_info and
  100. instance_info.
  101. :param task: a task from TaskManager.
  102. :param ramdisk_params: the parameters to be passed to the ramdisk.
  103. pxe driver passes these parameters as kernel command-line
  104. arguments.
  105. :returns: None
  106. :raises: MissingParameterValue, if some information is missing in
  107. node's driver_info or instance_info.
  108. :raises: InvalidParameterValue, if some information provided is
  109. invalid.
  110. :raises: IronicException, if some power or set boot boot device
  111. operation failed on the node.
  112. """
  113. node = task.node
  114. # Label indicating a deploy or rescue operation being carried out on
  115. # the node, 'deploy' or 'rescue'. Unless the node is in a rescue like
  116. # state, the mode is set to 'deploy', indicating deploy operation is
  117. # being carried out.
  118. mode = deploy_utils.rescue_or_deploy_mode(node)
  119. # NOTE(mjturek): At this point, the ipxe boot script should
  120. # already exist as it is created at startup time. However, we
  121. # call the boot script create method here to assert its
  122. # existence and handle the unlikely case that it wasn't created
  123. # or was deleted.
  124. pxe_utils.create_ipxe_boot_script()
  125. dhcp_opts = pxe_utils.dhcp_options_for_instance(
  126. task, ipxe_enabled=True)
  127. provider = dhcp_factory.DHCPFactory()
  128. provider.update_dhcp(task, dhcp_opts)
  129. pxe_info = pxe_utils.get_image_info(node, mode=mode,
  130. ipxe_enabled=True)
  131. # NODE: Try to validate and fetch instance images only
  132. # if we are in DEPLOYING state.
  133. if node.provision_state == states.DEPLOYING:
  134. pxe_info.update(
  135. pxe_utils.get_instance_image_info(task, ipxe_enabled=True))
  136. boot_mode_utils.sync_boot_mode(task)
  137. pxe_options = pxe_utils.build_pxe_config_options(
  138. task, pxe_info, ipxe_enabled=True, ramdisk_params=ramdisk_params)
  139. # TODO(dtantsur): backwards compability hack, remove in the V release
  140. if ramdisk_params.get("ipa-api-url"):
  141. pxe_options["ipa-api-url"] = ramdisk_params["ipa-api-url"]
  142. pxe_config_template = deploy_utils.get_pxe_config_template(node)
  143. pxe_utils.create_pxe_config(task, pxe_options,
  144. pxe_config_template,
  145. ipxe_enabled=True)
  146. persistent = False
  147. value = node.driver_info.get('force_persistent_boot_device',
  148. 'Default')
  149. if value in {'Always', 'Default', 'Never'}:
  150. if value == 'Always':
  151. persistent = True
  152. else:
  153. persistent = strutils.bool_from_string(value, False)
  154. manager_utils.node_set_boot_device(task, boot_devices.PXE,
  155. persistent=persistent)
  156. if CONF.pxe.ipxe_use_swift:
  157. kernel_label = '%s_kernel' % mode
  158. ramdisk_label = '%s_ramdisk' % mode
  159. pxe_info.pop(kernel_label, None)
  160. pxe_info.pop(ramdisk_label, None)
  161. if pxe_info:
  162. pxe_utils.cache_ramdisk_kernel(task, pxe_info, ipxe_enabled=True)
  163. LOG.debug('Ramdisk iPXE boot for node %(node)s has been prepared '
  164. 'with kernel params %(params)s',
  165. {'node': node.uuid, 'params': pxe_options})
  166. @METRICS.timer('iPXEBoot.prepare_instance')
  167. def prepare_instance(self, task):
  168. """Prepares the boot of instance.
  169. This method prepares the boot of the instance after reading
  170. relevant information from the node's instance_info. In case of netboot,
  171. it updates the dhcp entries and switches the PXE config. In case of
  172. localboot, it cleans up the PXE config.
  173. :param task: a task from TaskManager.
  174. :returns: None
  175. """
  176. boot_mode_utils.sync_boot_mode(task)
  177. node = task.node
  178. boot_option = deploy_utils.get_boot_option(node)
  179. boot_device = None
  180. instance_image_info = {}
  181. if boot_option == "ramdisk":
  182. instance_image_info = pxe_utils.get_instance_image_info(
  183. task, ipxe_enabled=True)
  184. pxe_utils.cache_ramdisk_kernel(task, instance_image_info,
  185. ipxe_enabled=True)
  186. if deploy_utils.is_iscsi_boot(task) or boot_option == "ramdisk":
  187. pxe_utils.prepare_instance_pxe_config(
  188. task, instance_image_info,
  189. iscsi_boot=deploy_utils.is_iscsi_boot(task),
  190. ramdisk_boot=(boot_option == "ramdisk"),
  191. ipxe_enabled=True)
  192. boot_device = boot_devices.PXE
  193. elif boot_option != "local":
  194. if task.driver.storage.should_write_image(task):
  195. # Make sure that the instance kernel/ramdisk is cached.
  196. # This is for the takeover scenario for active nodes.
  197. instance_image_info = pxe_utils.get_instance_image_info(
  198. task, ipxe_enabled=True)
  199. pxe_utils.cache_ramdisk_kernel(task, instance_image_info,
  200. ipxe_enabled=True)
  201. # If it's going to PXE boot we need to update the DHCP server
  202. dhcp_opts = pxe_utils.dhcp_options_for_instance(task,
  203. ipxe_enabled=True)
  204. provider = dhcp_factory.DHCPFactory()
  205. provider.update_dhcp(task, dhcp_opts)
  206. iwdi = task.node.driver_internal_info.get('is_whole_disk_image')
  207. try:
  208. root_uuid_or_disk_id = task.node.driver_internal_info[
  209. 'root_uuid_or_disk_id'
  210. ]
  211. except KeyError:
  212. if not task.driver.storage.should_write_image(task):
  213. pass
  214. elif not iwdi:
  215. LOG.warning("The UUID for the root partition can't be "
  216. "found, unable to switch the pxe config from "
  217. "deployment mode to service (boot) mode for "
  218. "node %(node)s", {"node": task.node.uuid})
  219. else:
  220. LOG.warning("The disk id for the whole disk image can't "
  221. "be found, unable to switch the pxe config "
  222. "from deployment mode to service (boot) mode "
  223. "for node %(node)s. Booting the instance "
  224. "from disk.", {"node": task.node.uuid})
  225. pxe_utils.clean_up_pxe_config(task, ipxe_enabled=True)
  226. boot_device = boot_devices.DISK
  227. else:
  228. pxe_utils.build_service_pxe_config(task, instance_image_info,
  229. root_uuid_or_disk_id,
  230. ipxe_enabled=True)
  231. boot_device = boot_devices.PXE
  232. else:
  233. # If it's going to boot from the local disk, we don't need
  234. # PXE config files. They still need to be generated as part
  235. # of the prepare() because the deployment does PXE boot the
  236. # deploy ramdisk
  237. pxe_utils.clean_up_pxe_config(task, ipxe_enabled=True)
  238. boot_device = boot_devices.DISK
  239. # NOTE(pas-ha) do not re-set boot device on ACTIVE nodes
  240. # during takeover
  241. if boot_device and task.node.provision_state != states.ACTIVE:
  242. persistent = True
  243. if node.driver_info.get('force_persistent_boot_device',
  244. 'Default') == 'Never':
  245. persistent = False
  246. manager_utils.node_set_boot_device(task, boot_device,
  247. persistent=persistent)
  248. @METRICS.timer('iPXEBoot.clean_up_instance')
  249. def clean_up_instance(self, task):
  250. """Cleans up the boot of instance.
  251. This method cleans up the environment that was setup for booting
  252. the instance. It unlinks the instance kernel/ramdisk in node's
  253. directory in tftproot and removes the PXE config.
  254. :param task: a task from TaskManager.
  255. :returns: None
  256. """
  257. node = task.node
  258. try:
  259. images_info = pxe_utils.get_instance_image_info(task,
  260. ipxe_enabled=True)
  261. except exception.MissingParameterValue as e:
  262. LOG.warning('Could not get instance image info '
  263. 'to clean up images for node %(node)s: %(err)s',
  264. {'node': node.uuid, 'err': e})
  265. else:
  266. pxe_utils.clean_up_pxe_env(task, images_info, ipxe_enabled=True)