def test_copy_image_to_web_server(self, copy_mock, chmod_mock): CONF.deploy.http_url = "http://x.y.z.a/webserver/" CONF.deploy.http_root = "/webserver" expected_url = "http://x.y.z.a/webserver/image-UUID" source = "tmp_image_file" destination = "image-UUID" image_path = "/webserver/image-UUID" actual_url = ilo_common.copy_image_to_web_server(source, destination) self.assertEqual(expected_url, actual_url) copy_mock.assert_called_once_with(source, image_path) chmod_mock.assert_called_once_with(image_path, 0o644)
def _extract_fw_from_file(node, target_file): """Extracts firmware image file. Extracts the firmware image file thru proliantutils and uploads it to the conductor webserver, if needed. :param node: an Ironic node object. :param target_file: firmware file to be extracted from :returns: tuple of: a) wrapper object of raw firmware image location b) a boolean, depending upon whether the raw firmware file was already in raw format(same file remains, no need to extract) or compact format (thereby extracted and hence different file). If uploaded then, then also its a different file. :raises: ImageUploadFailed, if upload to web server fails. :raises: SwiftOperationError, if upload to Swift fails. :raises: IloOperationError, on failure to process firmware file. """ ilo_object = ilo_common.get_ilo_object(node) try: # Note(deray): Based upon different iLO firmwares, the firmware file # which needs to be updated has to be either an http/https or a simple # file location. If it has to be a http/https location, then conductor # will take care of uploading the firmware file to web server or # swift (providing a temp url). fw_image_location, to_upload, is_extracted = ( proliantutils_utils.process_firmware_image(target_file, ilo_object)) except (proliantutils_error.InvalidInputError, proliantutils_error.ImageExtractionFailed) as proliantutils_exc: operation = _("Firmware file extracting as part of manual cleaning") raise exception.IloOperationError(operation=operation, error=proliantutils_exc) is_different_file = is_extracted fw_image_filename = os.path.basename(fw_image_location) fw_image_location_obj = FirmwareImageLocation(fw_image_location, fw_image_filename) if to_upload: is_different_file = True try: if CONF.ilo.use_web_server_for_images: # upload firmware image file to conductor webserver LOG.debug("For firmware update on node %(node)s, hosting " "firmware file %(firmware_image)s on web server ...", {'firmware_image': fw_image_location, 'node': node.uuid}) fw_image_uploaded_url = ilo_common.copy_image_to_web_server( fw_image_location, fw_image_filename) fw_image_location_obj.fw_image_location = fw_image_uploaded_url fw_image_location_obj.remove = types.MethodType( _remove_webserver_based_me, fw_image_location_obj) else: # upload firmware image file to swift LOG.debug("For firmware update on node %(node)s, hosting " "firmware file %(firmware_image)s on swift ...", {'firmware_image': fw_image_location, 'node': node.uuid}) fw_image_uploaded_url = ilo_common.copy_image_to_swift( fw_image_location, fw_image_filename) fw_image_location_obj.fw_image_location = fw_image_uploaded_url fw_image_location_obj.remove = types.MethodType( _remove_swift_based_me, fw_image_location_obj) finally: if is_extracted: # Note(deray): remove the file `fw_image_location` irrespective # of status of uploading (success or failure) and only if # extracted (and not passed as in plain binary format). If the # file is passed in binary format, then the invoking method # takes care of handling the deletion of the file. ilo_common.remove_single_or_list_of_files(fw_image_location) LOG.debug("For firmware update on node %(node)s, hosting firmware " "file: %(fw_image_location)s ... done. Hosted firmware " "file: %(fw_image_uploaded_url)s", {'fw_image_location': fw_image_location, 'node': node.uuid, 'fw_image_uploaded_url': fw_image_uploaded_url}) else: fw_image_location_obj.remove = types.MethodType( _remove_file_based_me, fw_image_location_obj) return fw_image_location_obj, is_different_file
def _get_boot_iso(task, root_uuid): """This method returns a boot ISO to boot the node. It chooses one of the three options in the order as below: 1. Does nothing if 'ilo_boot_iso' is present in node's instance_info and 'boot_iso_created_in_web_server' is not set in 'driver_internal_info'. 2. Image deployed has a meta-property 'boot_iso' in Glance. This should refer to the UUID of the boot_iso which exists in Glance. 3. Generates a boot ISO on the fly using kernel and ramdisk mentioned in the image deployed. It uploads the generated boot ISO to Swift. :param task: a TaskManager instance containing the node to act on. :param root_uuid: the uuid of the root partition. :returns: boot ISO URL. Should be either of below: * A Swift object - It should be of format 'swift:<object-name>'. It is assumed that the image object is present in CONF.ilo.swift_ilo_container; * A Glance image - It should be format 'glance://<glance-image-uuid>' or just <glance-image-uuid>; * An HTTP URL. On error finding the boot iso, it returns None. :raises: MissingParameterValue, if any of the required parameters are missing in the node's driver_info or instance_info. :raises: InvalidParameterValue, if any of the parameters have invalid value in the node's driver_info or instance_info. :raises: SwiftOperationError, if operation with Swift fails. :raises: ImageCreationFailed, if creation of boot ISO failed. :raises: exception.ImageRefValidationFailed if ilo_boot_iso is not HTTP(S) URL. """ LOG.debug("Trying to get a boot ISO to boot the baremetal node") # Option 1 - Check if user has provided ilo_boot_iso in node's # instance_info driver_internal_info = task.node.driver_internal_info boot_iso_created_in_web_server = ( driver_internal_info.get('boot_iso_created_in_web_server')) if (task.node.instance_info.get('ilo_boot_iso') and not boot_iso_created_in_web_server): LOG.debug("Using ilo_boot_iso provided in node's instance_info") boot_iso = task.node.instance_info['ilo_boot_iso'] if not service_utils.is_glance_image(boot_iso): try: image_service.HttpImageService().validate_href(boot_iso) except exception.ImageRefValidationFailed: with excutils.save_and_reraise_exception(): LOG.error("Virtual media deploy accepts only Glance " "images or HTTP(S) URLs as " "instance_info['ilo_boot_iso']. Either %s " "is not a valid HTTP(S) URL or is " "not reachable.", boot_iso) return task.node.instance_info['ilo_boot_iso'] # Option 2 - Check if user has provided a boot_iso in Glance. If boot_iso # is a supported non-glance href execution will proceed to option 3. deploy_info = _parse_deploy_info(task.node) image_href = deploy_info['image_source'] image_properties = ( images.get_image_properties( task.context, image_href, ['boot_iso', 'kernel_id', 'ramdisk_id'])) boot_iso_uuid = image_properties.get('boot_iso') kernel_href = (task.node.instance_info.get('kernel') or image_properties.get('kernel_id')) ramdisk_href = (task.node.instance_info.get('ramdisk') or image_properties.get('ramdisk_id')) if boot_iso_uuid: LOG.debug("Found boot_iso %s in Glance", boot_iso_uuid) return boot_iso_uuid if not kernel_href or not ramdisk_href: LOG.error("Unable to find kernel or ramdisk for " "image %(image)s to generate boot ISO for %(node)s", {'image': image_href, 'node': task.node.uuid}) return # NOTE(rameshg87): Functionality to share the boot ISOs created for # similar instances (instances with same deployed image) is # not implemented as of now. Creation/Deletion of such a shared boot ISO # will require synchronisation across conductor nodes for the shared boot # ISO. Such a synchronisation mechanism doesn't exist in ironic as of now. # Option 3 - Create boot_iso from kernel/ramdisk, upload to Swift # or web server and provide its name. deploy_iso_uuid = deploy_info['ilo_deploy_iso'] boot_mode = boot_mode_utils.get_boot_mode_for_deploy(task.node) boot_iso_object_name = _get_boot_iso_object_name(task.node) kernel_params = "" if deploy_utils.get_boot_option(task.node) == "ramdisk": i_info = task.node.instance_info kernel_params = "root=/dev/ram0 text " kernel_params += i_info.get("ramdisk_kernel_arguments", "") else: kernel_params = CONF.pxe.pxe_append_params with tempfile.NamedTemporaryFile(dir=CONF.tempdir) as fileobj: boot_iso_tmp_file = fileobj.name images.create_boot_iso(task.context, boot_iso_tmp_file, kernel_href, ramdisk_href, deploy_iso_uuid, root_uuid, kernel_params, boot_mode) if CONF.ilo.use_web_server_for_images: boot_iso_url = ( ilo_common.copy_image_to_web_server(boot_iso_tmp_file, boot_iso_object_name)) driver_internal_info = task.node.driver_internal_info driver_internal_info['boot_iso_created_in_web_server'] = True task.node.driver_internal_info = driver_internal_info task.node.save() LOG.debug("Created boot_iso %(boot_iso)s for node %(node)s", {'boot_iso': boot_iso_url, 'node': task.node.uuid}) return boot_iso_url else: container = CONF.ilo.swift_ilo_container swift_api = swift.SwiftAPI() swift_api.create_object(container, boot_iso_object_name, boot_iso_tmp_file) LOG.debug("Created boot_iso %s in Swift", boot_iso_object_name) return 'swift:%s' % boot_iso_object_name
def _get_boot_iso(task, root_uuid): """This method returns a boot ISO to boot the node. It chooses one of the three options in the order as below: 1. Does nothing if 'ilo_boot_iso' is present in node's instance_info and 'boot_iso_created_in_web_server' is not set in 'driver_internal_info'. 2. Image deployed has a meta-property 'boot_iso' in Glance. This should refer to the UUID of the boot_iso which exists in Glance. 3. Generates a boot ISO on the fly using kernel and ramdisk mentioned in the image deployed. It uploads the generated boot ISO to Swift. :param task: a TaskManager instance containing the node to act on. :param root_uuid: the uuid of the root partition. :returns: boot ISO URL. Should be either of below: * A Swift object - It should be of format 'swift:<object-name>'. It is assumed that the image object is present in CONF.ilo.swift_ilo_container; * A Glance image - It should be format 'glance://<glance-image-uuid>' or just <glance-image-uuid>; * An HTTP URL. On error finding the boot iso, it returns None. :raises: MissingParameterValue, if any of the required parameters are missing in the node's driver_info or instance_info. :raises: InvalidParameterValue, if any of the parameters have invalid value in the node's driver_info or instance_info. :raises: SwiftOperationError, if operation with Swift fails. :raises: ImageCreationFailed, if creation of boot ISO failed. :raises: exception.ImageRefValidationFailed if ilo_boot_iso is not HTTP(S) URL. """ LOG.debug("Trying to get a boot ISO to boot the baremetal node") # Option 1 - Check if user has provided ilo_boot_iso in node's # instance_info driver_internal_info = task.node.driver_internal_info boot_iso_created_in_web_server = ( driver_internal_info.get('boot_iso_created_in_web_server')) if (task.node.instance_info.get('ilo_boot_iso') and not boot_iso_created_in_web_server): LOG.debug("Using ilo_boot_iso provided in node's instance_info") boot_iso = task.node.instance_info['ilo_boot_iso'] if not service_utils.is_glance_image(boot_iso): try: image_service.HttpImageService().validate_href(boot_iso) except exception.ImageRefValidationFailed: with excutils.save_and_reraise_exception(): LOG.error("Virtual media deploy accepts only Glance " "images or HTTP(S) URLs as " "instance_info['ilo_boot_iso']. Either %s " "is not a valid HTTP(S) URL or is " "not reachable.", boot_iso) return task.node.instance_info['ilo_boot_iso'] # Option 2 - Check if user has provided a boot_iso in Glance. If boot_iso # is a supported non-glance href execution will proceed to option 3. deploy_info = _parse_deploy_info(task.node) image_href = deploy_info['image_source'] image_properties = ( images.get_image_properties( task.context, image_href, ['boot_iso', 'kernel_id', 'ramdisk_id'])) boot_iso_uuid = image_properties.get('boot_iso') kernel_href = (task.node.instance_info.get('kernel') or image_properties.get('kernel_id')) ramdisk_href = (task.node.instance_info.get('ramdisk') or image_properties.get('ramdisk_id')) if boot_iso_uuid: LOG.debug("Found boot_iso %s in Glance", boot_iso_uuid) return boot_iso_uuid if not kernel_href or not ramdisk_href: LOG.error("Unable to find kernel or ramdisk for " "image %(image)s to generate boot ISO for %(node)s", {'image': image_href, 'node': task.node.uuid}) return # NOTE(rameshg87): Functionality to share the boot ISOs created for # similar instances (instances with same deployed image) is # not implemented as of now. Creation/Deletion of such a shared boot ISO # will require synchronisation across conductor nodes for the shared boot # ISO. Such a synchronisation mechanism doesn't exist in ironic as of now. # Option 3 - Create boot_iso from kernel/ramdisk, upload to Swift # or web server and provide its name. deploy_iso_uuid = deploy_info['ilo_deploy_iso'] boot_mode = boot_mode_utils.get_boot_mode(task.node) boot_iso_object_name = _get_boot_iso_object_name(task.node) kernel_params = "" if deploy_utils.get_boot_option(task.node) == "ramdisk": i_info = task.node.instance_info kernel_params = "root=/dev/ram0 text " kernel_params += i_info.get("ramdisk_kernel_arguments", "") else: kernel_params = CONF.pxe.pxe_append_params with tempfile.NamedTemporaryFile(dir=CONF.tempdir) as fileobj: boot_iso_tmp_file = fileobj.name images.create_boot_iso(task.context, boot_iso_tmp_file, kernel_href, ramdisk_href, deploy_iso_href=deploy_iso_uuid, root_uuid=root_uuid, kernel_params=kernel_params, boot_mode=boot_mode) if CONF.ilo.use_web_server_for_images: boot_iso_url = ( ilo_common.copy_image_to_web_server(boot_iso_tmp_file, boot_iso_object_name)) driver_internal_info = task.node.driver_internal_info driver_internal_info['boot_iso_created_in_web_server'] = True task.node.driver_internal_info = driver_internal_info task.node.save() LOG.debug("Created boot_iso %(boot_iso)s for node %(node)s", {'boot_iso': boot_iso_url, 'node': task.node.uuid}) return boot_iso_url else: container = CONF.ilo.swift_ilo_container swift_api = swift.SwiftAPI() swift_api.create_object(container, boot_iso_object_name, boot_iso_tmp_file) LOG.debug("Created boot_iso %s in Swift", boot_iso_object_name) return 'swift:%s' % boot_iso_object_name
def _extract_fw_from_file(node, target_file): """Extracts firmware image file. Extracts the firmware image file thru proliantutils and uploads it to the conductor webserver, if needed. :param node: an Ironic node object. :param target_file: firmware file to be extracted from :returns: tuple of: a) wrapper object of raw firmware image location b) a boolean, depending upon whether the raw firmware file was already in raw format(same file remains, no need to extract) or compact format (thereby extracted and hence different file). If uploaded then, then also its a different file. :raises: ImageUploadFailed, if upload to web server fails. :raises: SwiftOperationError, if upload to Swift fails. :raises: IloOperationError, on failure to process firmware file. """ ilo_object = ilo_common.get_ilo_object(node) try: # Note(deray): Based upon different iLO firmwares, the firmware file # which needs to be updated has to be either an http/https or a simple # file location. If it has to be a http/https location, then conductor # will take care of uploading the firmware file to web server or # swift (providing a temp url). fw_image_location, to_upload, is_extracted = ( proliantutils_utils.process_firmware_image(target_file, ilo_object)) except (proliantutils_error.InvalidInputError, proliantutils_error.ImageExtractionFailed) as proliantutils_exc: operation = _("Firmware file extracting as part of manual cleaning") raise exception.IloOperationError(operation=operation, error=proliantutils_exc) is_different_file = is_extracted fw_image_filename = os.path.basename(fw_image_location) fw_image_location_obj = FirmwareImageLocation(fw_image_location, fw_image_filename) if to_upload: is_different_file = True try: if CONF.ilo.use_web_server_for_images: # upload firmware image file to conductor webserver LOG.debug( "For firmware update on node %(node)s, hosting " "firmware file %(firmware_image)s on web server ...", { 'firmware_image': fw_image_location, 'node': node.uuid }) fw_image_uploaded_url = ilo_common.copy_image_to_web_server( fw_image_location, fw_image_filename) fw_image_location_obj.fw_image_location = fw_image_uploaded_url fw_image_location_obj.remove = types.MethodType( _remove_webserver_based_me, fw_image_location_obj) else: # upload firmware image file to swift LOG.debug( "For firmware update on node %(node)s, hosting " "firmware file %(firmware_image)s on swift ...", { 'firmware_image': fw_image_location, 'node': node.uuid }) fw_image_uploaded_url = ilo_common.copy_image_to_swift( fw_image_location, fw_image_filename) fw_image_location_obj.fw_image_location = fw_image_uploaded_url fw_image_location_obj.remove = types.MethodType( _remove_swift_based_me, fw_image_location_obj) finally: if is_extracted: # Note(deray): remove the file `fw_image_location` irrespective # of status of uploading (success or failure) and only if # extracted (and not passed as in plain binary format). If the # file is passed in binary format, then the invoking method # takes care of handling the deletion of the file. ilo_common.remove_single_or_list_of_files(fw_image_location) LOG.debug( "For firmware update on node %(node)s, hosting firmware " "file: %(fw_image_location)s ... done. Hosted firmware " "file: %(fw_image_uploaded_url)s", { 'fw_image_location': fw_image_location, 'node': node.uuid, 'fw_image_uploaded_url': fw_image_uploaded_url }) else: fw_image_location_obj.remove = types.MethodType( _remove_file_based_me, fw_image_location_obj) return fw_image_location_obj, is_different_file