|
21 | 21 | from ironic.common import exception
|
22 | 22 | from ironic.common.i18n import _
|
23 | 23 | from ironic.common import states
|
| 24 | +from ironic.common import utils as common_utils |
24 | 25 | from ironic.conductor import utils as manager_utils
|
25 | 26 | from ironic.conf import CONF
|
26 | 27 | from ironic.drivers import base
|
@@ -712,6 +713,11 @@ def _attach_configdrive(self, task, managers):
|
712 | 713 | if not configdrive:
|
713 | 714 | return
|
714 | 715 |
|
| 716 | + if 'ramdisk_boot_configdrive' not in self.capabilities: |
| 717 | + raise exception.InstanceDeployFailure( |
| 718 | + _('Cannot attach a configdrive to node %s, as it is not ' |
| 719 | + 'supported in the driver.') % task.node.uuid) |
| 720 | + |
715 | 721 | _eject_vmedia(task, managers, sushy.VIRTUAL_MEDIA_USBSTICK)
|
716 | 722 | cd_ref = image_utils.prepare_configdrive_image(task, configdrive)
|
717 | 723 | try:
|
@@ -775,3 +781,300 @@ def _set_boot_device(cls, task, device, persistent=False):
|
775 | 781 | ManagementInterface fails.
|
776 | 782 | """
|
777 | 783 | manager_utils.node_set_boot_device(task, device, persistent)
|
| 784 | + |
| 785 | + |
| 786 | +class RedfishHttpsBoot(base.BootInterface): |
| 787 | + """A driver which utilizes UefiHttp like virtual media. |
| 788 | +
|
| 789 | + Utilizes the virtual media image build to craft a ISO image to |
| 790 | + signal to remote BMC to boot. |
| 791 | +
|
| 792 | + This interface comes with some constraints. For example, this |
| 793 | + interface is built under the operating assumption that DHCP is |
| 794 | + used. The UEFI Firmware needs to load some base configuration, |
| 795 | + regardless. Also depending on UEFI Firmware, and how it handles |
| 796 | + UefiHttp Boot, additional ISO contents, such as "configuration drive" |
| 797 | + materials might be unavailable. A similar constraint exists with |
| 798 | + ``ramdisk`` deployment. |
| 799 | + """ |
| 800 | + |
| 801 | + capabilities = ['ramdisk_boot'] |
| 802 | + |
| 803 | + def get_properties(self): |
| 804 | + """Return the properties of the interface. |
| 805 | +
|
| 806 | + :returns: dictionary of <property name>:<property description> entries. |
| 807 | + """ |
| 808 | + return REQUIRED_PROPERTIES |
| 809 | + |
| 810 | + def _validate_driver_info(self, task): |
| 811 | + """Validate the prerequisites for Redfish HTTPS based boot. |
| 812 | +
|
| 813 | + This method validates whether the 'driver_info' property of the |
| 814 | + supplied node contains the required information for this driver. |
| 815 | +
|
| 816 | + :param task: a TaskManager instance containing the node to act on. |
| 817 | + :raises: InvalidParameterValue if any parameters are incorrect |
| 818 | + :raises: MissingParameterValue if some mandatory information |
| 819 | + is missing on the node |
| 820 | + """ |
| 821 | + node = task.node |
| 822 | + |
| 823 | + _parse_driver_info(node) |
| 824 | + # Issue the deprecation warning if needed |
| 825 | + driver_utils.get_agent_iso(node, deprecated_prefix='redfish') |
| 826 | + |
| 827 | + def _validate_instance_info(self, task): |
| 828 | + """Validate instance image information for the task's node. |
| 829 | +
|
| 830 | + This method validates whether the 'instance_info' property of the |
| 831 | + supplied node contains the required information for this driver. |
| 832 | +
|
| 833 | + :param task: a TaskManager instance containing the node to act on. |
| 834 | + :raises: InvalidParameterValue if any parameters are incorrect |
| 835 | + :raises: MissingParameterValue if some mandatory information |
| 836 | + is missing on the node |
| 837 | + """ |
| 838 | + node = task.node |
| 839 | + |
| 840 | + # NOTE(dtantsur): if we're are writing an image with local boot |
| 841 | + # the boot interface does not care about image parameters and |
| 842 | + # must not validate them. |
| 843 | + if (not task.driver.storage.should_write_image(task) |
| 844 | + or deploy_utils.get_boot_option(node) == 'local'): |
| 845 | + return |
| 846 | + |
| 847 | + d_info = _parse_deploy_info(node) |
| 848 | + deploy_utils.validate_image_properties(task, d_info) |
| 849 | + |
| 850 | + def _validate_hardware(self, task): |
| 851 | + """Validates hardware support. |
| 852 | +
|
| 853 | + :param task: a TaskManager instance containing the node to act on. |
| 854 | + :raises: InvalidParameterValue if vendor not supported |
| 855 | + """ |
| 856 | + system = redfish_utils.get_system(task.node) |
| 857 | + if "UefiHttp" not in system.boot.allowed_values: |
| 858 | + raise exception.UnsupportedHardwareFeature( |
| 859 | + node=task.node.uuid, |
| 860 | + feature="UefiHttp boot") |
| 861 | + |
| 862 | + def validate(self, task): |
| 863 | + """Validate the deployment information for the task's node. |
| 864 | +
|
| 865 | + This method validates whether the 'driver_info' and/or 'instance_info' |
| 866 | + properties of the task's node contains the required information for |
| 867 | + this interface to function. |
| 868 | +
|
| 869 | + :param task: A TaskManager instance containing the node to act on. |
| 870 | + :raises: InvalidParameterValue on malformed parameter(s) |
| 871 | + :raises: MissingParameterValue on missing parameter(s) |
| 872 | + """ |
| 873 | + self._validate_hardware(task) |
| 874 | + self._validate_driver_info(task) |
| 875 | + self._validate_instance_info(task) |
| 876 | + |
| 877 | + def validate_inspection(self, task): |
| 878 | + """Validate that the node has required properties for inspection. |
| 879 | +
|
| 880 | + :param task: A TaskManager instance with the node being checked |
| 881 | + :raises: MissingParameterValue if node is missing one or more required |
| 882 | + parameters |
| 883 | + :raises: UnsupportedDriverExtension |
| 884 | + """ |
| 885 | + try: |
| 886 | + self._validate_driver_info(task) |
| 887 | + except exception.MissingParameterValue: |
| 888 | + # Fall back to non-managed in-band inspection |
| 889 | + raise exception.UnsupportedDriverExtension( |
| 890 | + driver=task.node.driver, extension='inspection') |
| 891 | + |
| 892 | + def prepare_ramdisk(self, task, ramdisk_params): |
| 893 | + """Prepares the boot of the agent ramdisk. |
| 894 | +
|
| 895 | + This method prepares the boot of the deploy or rescue ramdisk after |
| 896 | + reading relevant information from the node's driver_info and |
| 897 | + instance_info. |
| 898 | +
|
| 899 | + :param task: A task from TaskManager. |
| 900 | + :param ramdisk_params: the parameters to be passed to the ramdisk. |
| 901 | + :returns: None |
| 902 | + :raises: MissingParameterValue, if some information is missing in |
| 903 | + node's driver_info or instance_info. |
| 904 | + :raises: InvalidParameterValue, if some information provided is |
| 905 | + invalid. |
| 906 | + :raises: IronicException, if some power or set boot boot device |
| 907 | + operation failed on the node. |
| 908 | + """ |
| 909 | + node = task.node |
| 910 | + if not driver_utils.need_prepare_ramdisk(node): |
| 911 | + return |
| 912 | + |
| 913 | + d_info = _parse_driver_info(node) |
| 914 | + |
| 915 | + if manager_utils.is_fast_track(task): |
| 916 | + LOG.debug('Fast track operation for node %s, not setting up ' |
| 917 | + 'a HTTP Boot url', node.uuid) |
| 918 | + return |
| 919 | + |
| 920 | + can_config = d_info.pop('can_provide_config', True) |
| 921 | + if can_config: |
| 922 | + manager_utils.add_secret_token(node, pregenerated=True) |
| 923 | + node.save() |
| 924 | + ramdisk_params['ipa-agent-token'] = \ |
| 925 | + node.driver_internal_info['agent_secret_token'] |
| 926 | + |
| 927 | + manager_utils.node_power_action(task, states.POWER_OFF) |
| 928 | + |
| 929 | + deploy_nic_mac = deploy_utils.get_single_nic_with_vif_port_id(task) |
| 930 | + if deploy_nic_mac is not None: |
| 931 | + ramdisk_params['BOOTIF'] = deploy_nic_mac |
| 932 | + if CONF.debug and 'ipa-debug' not in ramdisk_params: |
| 933 | + ramdisk_params['ipa-debug'] = '1' |
| 934 | + |
| 935 | + # NOTE(TheJulia): This is a mandatory setting for virtual media |
| 936 | + # based deployment operations and boot modes similar where we |
| 937 | + # want the ramdisk to consider embedded configuration. |
| 938 | + ramdisk_params['boot_method'] = 'vmedia' |
| 939 | + |
| 940 | + mode = deploy_utils.rescue_or_deploy_mode(node) |
| 941 | + |
| 942 | + iso_ref = image_utils.prepare_deploy_iso(task, ramdisk_params, |
| 943 | + mode, d_info) |
| 944 | + boot_mode_utils.sync_boot_mode(task) |
| 945 | + |
| 946 | + self._set_boot_device(task, boot_devices.UEFIHTTP, |
| 947 | + http_boot_url=iso_ref) |
| 948 | + |
| 949 | + LOG.debug("Node %(node)s is set to one time boot from " |
| 950 | + "%(device)s", {'node': task.node.uuid, |
| 951 | + 'device': boot_devices.UEFIHTTP}) |
| 952 | + |
| 953 | + def clean_up_ramdisk(self, task): |
| 954 | + """Cleans up the boot of ironic ramdisk. |
| 955 | +
|
| 956 | + This method cleans up the environment that was setup for booting the |
| 957 | + deploy ramdisk. |
| 958 | +
|
| 959 | + :param task: A task from TaskManager. |
| 960 | + :returns: None |
| 961 | + """ |
| 962 | + if manager_utils.is_fast_track(task): |
| 963 | + LOG.debug('Fast track operation for node %s, not ejecting ' |
| 964 | + 'any devices', task.node.uuid) |
| 965 | + return |
| 966 | + |
| 967 | + LOG.debug("Cleaning up deploy boot for " |
| 968 | + "%(node)s", {'node': task.node.uuid}) |
| 969 | + self._clean_up(task) |
| 970 | + |
| 971 | + def prepare_instance(self, task): |
| 972 | + """Prepares the boot of instance over virtual media. |
| 973 | +
|
| 974 | + This method prepares the boot of the instance after reading |
| 975 | + relevant information from the node's instance_info. |
| 976 | +
|
| 977 | + The internal logic is as follows: |
| 978 | +
|
| 979 | + - Cleanup any related files |
| 980 | + - Sync the boot mode with the machine. |
| 981 | + - Configure Secure boot, if required. |
| 982 | + - If local boot, or a whole disk image was deployed, |
| 983 | + set the next boot device as disk. |
| 984 | + - If "ramdisk" is the desired, then the UefiHttp boot |
| 985 | + option is set to the BMC with a request for this to |
| 986 | + be persistent. |
| 987 | +
|
| 988 | + :param task: a task from TaskManager. |
| 989 | + :returns: None |
| 990 | + :raises: InstanceDeployFailure, if its try to boot iSCSI volume in |
| 991 | + 'BIOS' boot mode. |
| 992 | + """ |
| 993 | + node = task.node |
| 994 | + |
| 995 | + self._clean_up(task) |
| 996 | + |
| 997 | + boot_mode_utils.sync_boot_mode(task) |
| 998 | + boot_mode_utils.configure_secure_boot_if_needed(task) |
| 999 | + |
| 1000 | + boot_option = deploy_utils.get_boot_option(node) |
| 1001 | + iwdi = node.driver_internal_info.get('is_whole_disk_image') |
| 1002 | + if boot_option == "local" or iwdi: |
| 1003 | + self._set_boot_device(task, boot_devices.DISK, persistent=True) |
| 1004 | + |
| 1005 | + LOG.debug("Node %(node)s is set to permanently boot from local " |
| 1006 | + "%(device)s", {'node': task.node.uuid, |
| 1007 | + 'device': boot_devices.DISK}) |
| 1008 | + return |
| 1009 | + |
| 1010 | + params = {} |
| 1011 | + |
| 1012 | + if boot_option != 'ramdisk': |
| 1013 | + root_uuid = node.driver_internal_info.get('root_uuid_or_disk_id') |
| 1014 | + if not root_uuid and task.driver.storage.should_write_image(task): |
| 1015 | + LOG.warning( |
| 1016 | + "The UUID of the root partition could not be found for " |
| 1017 | + "node %s. Booting instance from disk anyway.", node.uuid) |
| 1018 | + |
| 1019 | + self._set_boot_device(task, boot_devices.DISK, persistent=True) |
| 1020 | + |
| 1021 | + return |
| 1022 | + |
| 1023 | + params.update(root_uuid=root_uuid) |
| 1024 | + |
| 1025 | + deploy_info = _parse_deploy_info(node) |
| 1026 | + |
| 1027 | + iso_ref = image_utils.prepare_boot_iso(task, deploy_info, **params) |
| 1028 | + self._set_boot_device(task, boot_devices.UEFIHTTP, persistent=True, |
| 1029 | + http_boot_url=iso_ref) |
| 1030 | + |
| 1031 | + LOG.debug("Node %(node)s is set to permanently boot from " |
| 1032 | + "%(device)s", {'node': task.node.uuid, |
| 1033 | + 'device': boot_devices.UEFIHTTP}) |
| 1034 | + |
| 1035 | + def _clean_up(self, task): |
| 1036 | + image_utils.cleanup_iso_image(task) |
| 1037 | + |
| 1038 | + def clean_up_instance(self, task): |
| 1039 | + """Cleans up the boot of instance. |
| 1040 | +
|
| 1041 | + This method cleans up the environment that was setup for booting |
| 1042 | + the instance. |
| 1043 | +
|
| 1044 | + :param task: A task from TaskManager. |
| 1045 | + :returns: None |
| 1046 | + """ |
| 1047 | + LOG.debug("Cleaning up instance boot for " |
| 1048 | + "%(node)s", {'node': task.node.uuid}) |
| 1049 | + self._clean_up(task) |
| 1050 | + boot_mode_utils.deconfigure_secure_boot_if_needed(task) |
| 1051 | + |
| 1052 | + @classmethod |
| 1053 | + def _set_boot_device(cls, task, device, persistent=False, |
| 1054 | + http_boot_url=None): |
| 1055 | + """Set the boot device for a node. |
| 1056 | +
|
| 1057 | + This is a hook method which can be used by other drivers based upon |
| 1058 | + this class, in order to facilitate vendor specific logic, |
| 1059 | + if needed. |
| 1060 | +
|
| 1061 | + Furthermore, we are not considering a *lack* of a URL as fatal. |
| 1062 | + A driver could easily update DHCP and send the message to the BMC. |
| 1063 | +
|
| 1064 | + :param task: a TaskManager instance. |
| 1065 | + :param device: the boot device, one of |
| 1066 | + :mod:`ironic.common.boot_devices`. |
| 1067 | + :param persistent: Whether to set next-boot, or make the change |
| 1068 | + permanent. Default: False. |
| 1069 | + :param http_boot_url: The URL to send to the BMC in order to boot |
| 1070 | + the node via UEFIHTTP. |
| 1071 | + :raises: InvalidParameterValue if the validation of the |
| 1072 | + ManagementInterface fails. |
| 1073 | + """ |
| 1074 | + |
| 1075 | + if http_boot_url: |
| 1076 | + common_utils.set_node_nested_field( |
| 1077 | + task.node, 'driver_internal_info', |
| 1078 | + 'redfish_uefi_http_url', http_boot_url) |
| 1079 | + task.node.save() |
| 1080 | + manager_utils.node_set_boot_device(task, device, persistent) |
0 commit comments