A service for managing and provisioning Bare Metal servers.
Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.

386 lignes
17KB

  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. PXE Boot Interface
  16. """
  17. from ironic_lib import metrics_utils
  18. from oslo_log import log as logging
  19. from ironic.common import boot_devices
  20. from ironic.common import dhcp_factory
  21. from ironic.common import exception
  22. from ironic.common.glance_service import service_utils
  23. from ironic.common.i18n import _
  24. from ironic.common import pxe_utils
  25. from ironic.common import states
  26. from ironic.conductor import task_manager
  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 agent
  31. from ironic.drivers.modules import boot_mode_utils
  32. from ironic.drivers.modules import deploy_utils
  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. # NOTE(TheJulia): This was previously a public method to the code being
  39. # moved. This mapping should be removed in the T* cycle.
  40. validate_boot_parameters_for_trusted_boot = pxe_utils.validate_boot_parameters_for_trusted_boot # noqa
  41. TFTPImageCache = pxe_utils.TFTPImageCache
  42. # NOTE(TheJulia): End section of mappings for migrated common pxe code.
  43. class PXEBoot(pxe_base.PXEBaseMixin, base.BootInterface):
  44. # TODO(TheJulia): iscsi_volume_boot should be removed from
  45. # the list below once ipxe support is removed from the PXE
  46. # interface.
  47. capabilities = ['iscsi_volume_boot', 'ramdisk_boot', 'ipxe_boot',
  48. 'pxe_boot']
  49. def __init__(self):
  50. # TODO(TheJulia): Once the pxe/ipxe interfaces split is complete,
  51. # this can be removed.
  52. if CONF.pxe.ipxe_enabled:
  53. pxe_utils.create_ipxe_boot_script()
  54. @METRICS.timer('PXEBoot.validate')
  55. def validate(self, task):
  56. """Validate the PXE-specific info for booting deploy/instance images.
  57. This method validates the PXE-specific info for booting the
  58. ramdisk and instance on the node. If invalid, raises an
  59. exception; otherwise returns None.
  60. :param task: a task from TaskManager.
  61. :returns: None
  62. :raises: InvalidParameterValue, if some parameters are invalid.
  63. :raises: MissingParameterValue, if some required parameters are
  64. missing.
  65. """
  66. node = task.node
  67. if not driver_utils.get_node_mac_addresses(task):
  68. raise exception.MissingParameterValue(
  69. _("Node %s does not have any port associated with it.")
  70. % node.uuid)
  71. # TODO(TheJulia): Once ipxe support is remove from the pxe
  72. # interface, this can be removed.
  73. if CONF.pxe.ipxe_enabled:
  74. if (not CONF.deploy.http_url
  75. or not CONF.deploy.http_root):
  76. raise exception.MissingParameterValue(_(
  77. "iPXE boot is enabled but no HTTP URL or HTTP "
  78. "root was specified."))
  79. # Check the trusted_boot capabilities value.
  80. deploy_utils.validate_capabilities(node)
  81. if deploy_utils.is_trusted_boot_requested(node):
  82. # Check if 'boot_option' and boot mode is compatible with
  83. # trusted boot.
  84. validate_boot_parameters_for_trusted_boot(node)
  85. pxe_utils.parse_driver_info(node)
  86. # NOTE(TheJulia): If we're not writing an image, we can skip
  87. # the remainder of this method.
  88. if (not task.driver.storage.should_write_image(task)):
  89. return
  90. d_info = deploy_utils.get_image_instance_info(node)
  91. if (node.driver_internal_info.get('is_whole_disk_image')
  92. or deploy_utils.get_boot_option(node) == 'local'):
  93. props = []
  94. elif service_utils.is_glance_image(d_info['image_source']):
  95. props = ['kernel_id', 'ramdisk_id']
  96. else:
  97. props = ['kernel', 'ramdisk']
  98. deploy_utils.validate_image_properties(task.context, d_info, props)
  99. @METRICS.timer('PXEBoot.prepare_ramdisk')
  100. def prepare_ramdisk(self, task, ramdisk_params):
  101. """Prepares the boot of Ironic ramdisk using PXE.
  102. This method prepares the boot of the deploy or rescue kernel/ramdisk
  103. after reading relevant information from the node's driver_info and
  104. instance_info.
  105. :param task: a task from TaskManager.
  106. :param ramdisk_params: the parameters to be passed to the ramdisk.
  107. pxe driver passes these parameters as kernel command-line
  108. arguments.
  109. :returns: None
  110. :raises: MissingParameterValue, if some information is missing in
  111. node's driver_info or instance_info.
  112. :raises: InvalidParameterValue, if some information provided is
  113. invalid.
  114. :raises: IronicException, if some power or set boot boot device
  115. operation failed on the node.
  116. """
  117. node = task.node
  118. # Label indicating a deploy or rescue operation being carried out on
  119. # the node, 'deploy' or 'rescue'. Unless the node is in a rescue like
  120. # state, the mode is set to 'deploy', indicating deploy operation is
  121. # being carried out.
  122. mode = deploy_utils.rescue_or_deploy_mode(node)
  123. ipxe_enabled = CONF.pxe.ipxe_enabled
  124. if ipxe_enabled:
  125. # NOTE(mjturek): At this point, the ipxe boot script should
  126. # already exist as it is created at startup time. However, we
  127. # call the boot script create method here to assert its
  128. # existence and handle the unlikely case that it wasn't created
  129. # or was deleted.
  130. pxe_utils.create_ipxe_boot_script()
  131. dhcp_opts = pxe_utils.dhcp_options_for_instance(
  132. task, ipxe_enabled=ipxe_enabled)
  133. provider = dhcp_factory.DHCPFactory()
  134. provider.update_dhcp(task, dhcp_opts)
  135. pxe_info = pxe_utils.get_image_info(node, mode=mode)
  136. # NODE: Try to validate and fetch instance images only
  137. # if we are in DEPLOYING state.
  138. if node.provision_state == states.DEPLOYING:
  139. pxe_info.update(pxe_utils.get_instance_image_info(task))
  140. boot_mode_utils.sync_boot_mode(task)
  141. pxe_options = pxe_utils.build_pxe_config_options(
  142. task, pxe_info, ipxe_enabled=ipxe_enabled,
  143. ramdisk_params=ramdisk_params)
  144. # TODO(dtantsur): backwards compability hack, remove in the V release
  145. if ramdisk_params.get("ipa-api-url"):
  146. pxe_options["ipa-api-url"] = ramdisk_params["ipa-api-url"]
  147. pxe_config_template = deploy_utils.get_pxe_config_template(node)
  148. pxe_utils.create_pxe_config(task, pxe_options,
  149. pxe_config_template,
  150. ipxe_enabled=CONF.pxe.ipxe_enabled)
  151. persistent = self._persistent_ramdisk_boot(node)
  152. manager_utils.node_set_boot_device(task, boot_devices.PXE,
  153. persistent=persistent)
  154. if CONF.pxe.ipxe_enabled and CONF.pxe.ipxe_use_swift:
  155. kernel_label = '%s_kernel' % mode
  156. ramdisk_label = '%s_ramdisk' % mode
  157. pxe_info.pop(kernel_label, None)
  158. pxe_info.pop(ramdisk_label, None)
  159. if pxe_info:
  160. pxe_utils.cache_ramdisk_kernel(task, pxe_info,
  161. ipxe_enabled=CONF.pxe.ipxe_enabled)
  162. LOG.debug('Ramdisk PXE boot for node %(node)s has been prepared '
  163. 'with kernel params %(params)s',
  164. {'node': node.uuid, 'params': pxe_options})
  165. @METRICS.timer('PXEBoot.prepare_instance')
  166. def prepare_instance(self, task):
  167. """Prepares the boot of instance.
  168. This method prepares the boot of the instance after reading
  169. relevant information from the node's instance_info. In case of netboot,
  170. it updates the dhcp entries and switches the PXE config. In case of
  171. localboot, it cleans up the PXE config.
  172. :param task: a task from TaskManager.
  173. :returns: None
  174. """
  175. ipxe_enabled = CONF.pxe.ipxe_enabled
  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(task)
  183. pxe_utils.cache_ramdisk_kernel(task, instance_image_info,
  184. ipxe_enabled=CONF.pxe.ipxe_enabled)
  185. if deploy_utils.is_iscsi_boot(task) or boot_option == "ramdisk":
  186. pxe_utils.prepare_instance_pxe_config(
  187. task, instance_image_info,
  188. iscsi_boot=deploy_utils.is_iscsi_boot(task),
  189. ramdisk_boot=(boot_option == "ramdisk"),
  190. ipxe_enabled=CONF.pxe.ipxe_enabled)
  191. boot_device = boot_devices.PXE
  192. elif boot_option != "local":
  193. if task.driver.storage.should_write_image(task):
  194. # Make sure that the instance kernel/ramdisk is cached.
  195. # This is for the takeover scenario for active nodes.
  196. instance_image_info = pxe_utils.get_instance_image_info(task)
  197. pxe_utils.cache_ramdisk_kernel(
  198. task, instance_image_info,
  199. ipxe_enabled=CONF.pxe.ipxe_enabled)
  200. # If it's going to PXE boot we need to update the DHCP server
  201. dhcp_opts = pxe_utils.dhcp_options_for_instance(
  202. task, ipxe_enabled)
  203. provider = dhcp_factory.DHCPFactory()
  204. provider.update_dhcp(task, dhcp_opts)
  205. iwdi = task.node.driver_internal_info.get('is_whole_disk_image')
  206. try:
  207. root_uuid_or_disk_id = task.node.driver_internal_info[
  208. 'root_uuid_or_disk_id'
  209. ]
  210. except KeyError:
  211. if not task.driver.storage.should_write_image(task):
  212. pass
  213. elif not iwdi:
  214. LOG.warning("The UUID for the root partition can't be "
  215. "found, unable to switch the pxe config from "
  216. "deployment mode to service (boot) mode for "
  217. "node %(node)s", {"node": task.node.uuid})
  218. else:
  219. LOG.warning("The disk id for the whole disk image can't "
  220. "be found, unable to switch the pxe config "
  221. "from deployment mode to service (boot) mode "
  222. "for node %(node)s. Booting the instance "
  223. "from disk.", {"node": task.node.uuid})
  224. pxe_utils.clean_up_pxe_config(
  225. task, ipxe_enabled=CONF.pxe.ipxe_enabled)
  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=ipxe_enabled)
  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(
  238. task, ipxe_enabled=CONF.pxe.ipxe_enabled)
  239. boot_device = boot_devices.DISK
  240. # NOTE(pas-ha) do not re-set boot device on ACTIVE nodes
  241. # during takeover
  242. if boot_device and task.node.provision_state != states.ACTIVE:
  243. persistent = True
  244. if node.driver_info.get('force_persistent_boot_device',
  245. 'Default') == 'Never':
  246. persistent = False
  247. manager_utils.node_set_boot_device(task, boot_device,
  248. persistent=persistent)
  249. @METRICS.timer('PXEBoot.clean_up_instance')
  250. def clean_up_instance(self, task):
  251. """Cleans up the boot of instance.
  252. This method cleans up the environment that was setup for booting
  253. the instance. It unlinks the instance kernel/ramdisk in node's
  254. directory in tftproot and removes the PXE config.
  255. :param task: a task from TaskManager.
  256. :returns: None
  257. """
  258. node = task.node
  259. try:
  260. images_info = pxe_utils.get_instance_image_info(task)
  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)
  267. class PXERamdiskDeploy(agent.AgentDeploy):
  268. def validate(self, task):
  269. if 'ramdisk_boot' not in task.driver.boot.capabilities:
  270. raise exception.InvalidParameterValue(
  271. message=_('Invalid configuration: The boot interface '
  272. 'must have the `ramdisk_boot` capability. '
  273. 'You are using an incompatible boot interface.'))
  274. task.driver.boot.validate(task)
  275. # Validate node capabilities
  276. deploy_utils.validate_capabilities(task.node)
  277. @METRICS.timer('RamdiskDeploy.deploy')
  278. @base.deploy_step(priority=100)
  279. @task_manager.require_exclusive_lock
  280. def deploy(self, task):
  281. if 'configdrive' in task.node.instance_info:
  282. LOG.warning('A configuration drive is present with '
  283. 'in the deployment request of node %(node)s. '
  284. 'The configuration drive will be ignored for '
  285. 'this deployment.',
  286. {'node': task.node})
  287. manager_utils.node_power_action(task, states.POWER_OFF)
  288. # Tenant neworks must enable connectivity to the boot
  289. # location, as reboot() can otherwise be very problematic.
  290. # IDEA(TheJulia): Maybe a "trusted environment" mode flag
  291. # that we otherwise fail validation on for drivers that
  292. # require explicit security postures.
  293. power_state_to_restore = manager_utils.power_on_node_if_needed(task)
  294. task.driver.network.configure_tenant_networks(task)
  295. manager_utils.restore_power_state_if_needed(
  296. task, power_state_to_restore)
  297. # calling boot.prepare_instance will also set the node
  298. # to PXE boot, and update PXE templates accordingly
  299. task.driver.boot.prepare_instance(task)
  300. # Power-on the instance, with PXE prepared, we're done.
  301. manager_utils.node_power_action(task, states.POWER_ON)
  302. LOG.info('Deployment setup for node %s done', task.node.uuid)
  303. return None
  304. @METRICS.timer('RamdiskDeploy.prepare')
  305. @task_manager.require_exclusive_lock
  306. def prepare(self, task):
  307. node = task.node
  308. # Log a warning if the boot_option is wrong... and
  309. # otherwise reset it.
  310. boot_option = deploy_utils.get_boot_option(node)
  311. if boot_option != 'ramdisk':
  312. LOG.warning('Incorrect "boot_option" set for node %(node)s '
  313. 'and will be overridden to "ramdisk" as to '
  314. 'match the deploy interface. Found: %(boot_opt)s.',
  315. {'node': node.uuid,
  316. 'boot_opt': boot_option})
  317. i_info = task.node.instance_info
  318. i_info.update({'capabilities': {'boot_option': 'ramdisk'}})
  319. node.instance_info = i_info
  320. node.save()
  321. deploy_utils.populate_storage_driver_internal_info(task)
  322. if node.provision_state == states.DEPLOYING:
  323. # Ask the network interface to validate itself so
  324. # we can ensure we are able to proceed.
  325. task.driver.network.validate(task)
  326. manager_utils.node_power_action(task, states.POWER_OFF)
  327. # NOTE(TheJulia): If this was any other interface, we would
  328. # unconfigure tenant networks, add provisioning networks, etc.
  329. task.driver.storage.attach_volumes(task)
  330. if node.provision_state in (states.ACTIVE, states.UNRESCUING):
  331. # In the event of takeover or unrescue.
  332. task.driver.boot.prepare_instance(task)