A service for managing and provisioning Bare Metal servers.
Ви не можете вибрати більше 25 тем Теми мають розпочинатися з літери або цифри, можуть містити дефіси (-) і не повинні перевищувати 35 символів.

386 рядки
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)