Add get/set_boot_image
to libvirt driver
This change introduces fully-functional Virtual Media emulation. Libvirt domains can be booted off the ISO image inserted into the virtual media device. Story: 2005149 Task: 29858 Change-Id: I8880dd9ae545d963e7bacf9aa78e4c5ea6b769cb
This commit is contained in:
parent
e1c844dfa7
commit
9e0eaf4322
@ -150,6 +150,23 @@ Now you can run `sushy-emulator` with the updated configuration file:
|
|||||||
|
|
||||||
The images you will serve to your VMs need to be UEFI-bootable.
|
The images you will serve to your VMs need to be UEFI-bootable.
|
||||||
|
|
||||||
|
Settable boot image
|
||||||
|
~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
The `libvirt` system emulation backend supports setting custom boot images,
|
||||||
|
so that libvirt domains (representing bare metal nodes) can boot from user
|
||||||
|
images.
|
||||||
|
|
||||||
|
This feature enables system boot from virtual media device.
|
||||||
|
|
||||||
|
The limitations:
|
||||||
|
|
||||||
|
* Only ISO images are supported
|
||||||
|
* Remote libvirt hypervisor is not supported
|
||||||
|
|
||||||
|
See *VirtualMedia* resource section for more information on how to perform
|
||||||
|
virtual media boot.
|
||||||
|
|
||||||
Systems resource driver: OpenStack
|
Systems resource driver: OpenStack
|
||||||
++++++++++++++++++++++++++++++++++
|
++++++++++++++++++++++++++++++++++
|
||||||
|
|
||||||
@ -431,3 +448,116 @@ Redfish client can eject image from virtual media device:
|
|||||||
-H "Content-Type: application/json" \
|
-H "Content-Type: application/json" \
|
||||||
-X POST \
|
-X POST \
|
||||||
http://localhost:8000/redfish/v1/Managers/58893887-8974-2487-2389-841168418919/VirtualMedia/Cd/Actions/VirtualMedia.EjectMedia
|
http://localhost:8000/redfish/v1/Managers/58893887-8974-2487-2389-841168418919/VirtualMedia/Cd/Actions/VirtualMedia.EjectMedia
|
||||||
|
|
||||||
|
Virtual media boot
|
||||||
|
++++++++++++++++++
|
||||||
|
|
||||||
|
To boot a system from a virtual media device the client first needs to figure
|
||||||
|
out which manager is responsible for the system of interest:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
$ curl http://localhost:8000/redfish/v1/Systems/281c2fc3-dd34-439a-9f0f-63df45e2c998
|
||||||
|
{
|
||||||
|
...
|
||||||
|
"Links": {
|
||||||
|
"Chassis": [
|
||||||
|
],
|
||||||
|
"ManagedBy": [
|
||||||
|
{
|
||||||
|
"@odata.id": "/redfish/v1/Managers/58893887-8974-2487-2389-841168418919"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
...
|
||||||
|
|
||||||
|
Exploring the Redfish API links, the client can learn the virtual media devices
|
||||||
|
being offered:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
$ curl http://localhost:8000/redfish/v1/Managers/58893887-894-2487-2389-841168418919/VirtualMedia
|
||||||
|
...
|
||||||
|
"Members": [
|
||||||
|
{
|
||||||
|
"@odata.id": "/redfish/v1/Managers/58893887-8974-2487-2389-841168418919/VirtualMedia/Cd"
|
||||||
|
},
|
||||||
|
...
|
||||||
|
|
||||||
|
Knowing virtual media device name, the client can check out its present state:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
$ curl http://localhost:8000/redfish/v1/Managers/58893887-8974-2487-2389-841168418919/VirtualMedia/Cd
|
||||||
|
{
|
||||||
|
...
|
||||||
|
"Name": "Virtual CD",
|
||||||
|
"MediaTypes": [
|
||||||
|
"CD",
|
||||||
|
"DVD"
|
||||||
|
],
|
||||||
|
"Image": "",
|
||||||
|
"ImageName": "",
|
||||||
|
"ConnectedVia": "URI",
|
||||||
|
"Inserted": false,
|
||||||
|
"WriteProtected": false,
|
||||||
|
...
|
||||||
|
|
||||||
|
Assuming `http://localhost/var/tmp/mini.iso` URL points to a bootable UEFI or
|
||||||
|
hybrid ISO, the following Redfish REST API call will insert the image into the
|
||||||
|
virtual CD drive:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
$ curl -d \
|
||||||
|
'{"Image":"http:://localhost/var/tmp/mini.iso", "Inserted": true}' \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-X POST \
|
||||||
|
http://localhost:8000/redfish/v1/Managers/58893887-8974-2487-2389-841168418919/VirtualMedia/Cd/Actions/VirtualMedia.InsertMedia
|
||||||
|
|
||||||
|
Querying again, the emulator should have it in the drive:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
$ curl http://localhost:8000/redfish/v1/Managers/58893887-8974-2487-2389-841168418919/VirtualMedia/Cd
|
||||||
|
{
|
||||||
|
...
|
||||||
|
"Name": "Virtual CD",
|
||||||
|
"MediaTypes": [
|
||||||
|
"CD",
|
||||||
|
"DVD"
|
||||||
|
],
|
||||||
|
"Image": "http://localhost/var/tmp/mini.iso",
|
||||||
|
"ImageName": "mini.iso",
|
||||||
|
"ConnectedVia": "URI",
|
||||||
|
"Inserted": true,
|
||||||
|
"WriteProtected": true,
|
||||||
|
...
|
||||||
|
|
||||||
|
Next, the node needs to be configured to boot from its local CD drive
|
||||||
|
over UEFI:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
$ curl -X PATCH -H 'Content-Type: application/json' \
|
||||||
|
-d '{
|
||||||
|
"Boot": {
|
||||||
|
"BootSourceOverrideTarget": "Cd",
|
||||||
|
"BootSourceOverrideMode": "Uefi",
|
||||||
|
"BootSourceOverrideEnabled": "Continuous"
|
||||||
|
}
|
||||||
|
}' \
|
||||||
|
http://localhost:8000/redfish/v1/Systems/281c2fc3-dd34-439a-9f0f-63df45e2c998
|
||||||
|
|
||||||
|
By this point the system will boot off the virtual CD drive when powering it on:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
curl -d '{"ResetType":"On"}' \
|
||||||
|
-H "Content-Type: application/json" -X POST \
|
||||||
|
http://localhost:8000/redfish/v1/Systems/281c2fc3-dd34-439a-9f0f-63df45e2c998/Actions/ComputerSystem.Reset
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
ISO files to boot from must be UEFI-bootable, libvirtd should be running on the same
|
||||||
|
machine with sushy-emulator.
|
||||||
|
@ -10,3 +10,10 @@ features:
|
|||||||
Each Manager automatically gets its own instance of the above
|
Each Manager automatically gets its own instance of the above
|
||||||
mentioned virtual media device collection. HTTP/S-hosted images
|
mentioned virtual media device collection. HTTP/S-hosted images
|
||||||
can be inserted into and ejected from virtual media devices.
|
can be inserted into and ejected from virtual media devices.
|
||||||
|
|
||||||
|
If libvirt virtualization backend is being used, once ISO image
|
||||||
|
is inserted into the respective Manager, any System under that
|
||||||
|
Manager can boot from that image by booting from its local ``cdrom``
|
||||||
|
over UEFI.
|
||||||
|
|
||||||
|
The ISO images must be UEFI-bootable or hybrid.
|
||||||
|
@ -378,7 +378,7 @@ def virtual_media_insert(identity, device):
|
|||||||
system, device, boot_image=image_path,
|
system, device, boot_image=image_path,
|
||||||
write_protected=write_protected)
|
write_protected=write_protected)
|
||||||
|
|
||||||
except error.FishyError as ex:
|
except error.NotSupportedError as ex:
|
||||||
app.logger.warning(
|
app.logger.warning(
|
||||||
'System %s failed to set boot image %s on device %s: '
|
'System %s failed to set boot image %s on device %s: '
|
||||||
'%s', system, image_path, device, ex)
|
'%s', system, image_path, device, ex)
|
||||||
@ -402,7 +402,7 @@ def virtual_media_eject(identity, device):
|
|||||||
try:
|
try:
|
||||||
resources.systems.set_boot_image(system, device)
|
resources.systems.set_boot_image(system, device)
|
||||||
|
|
||||||
except error.FishyError as ex:
|
except error.NotSupportedError as ex:
|
||||||
app.logger.warning(
|
app.logger.warning(
|
||||||
'System %s failed to remove boot image from device %s: '
|
'System %s failed to remove boot image from device %s: '
|
||||||
'%s', system, device, ex)
|
'%s', system, device, ex)
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
|
|
||||||
from collections import namedtuple
|
from collections import namedtuple
|
||||||
import logging
|
import logging
|
||||||
|
import os
|
||||||
import uuid
|
import uuid
|
||||||
import xml.etree.ElementTree as ET
|
import xml.etree.ElementTree as ET
|
||||||
|
|
||||||
@ -100,11 +101,39 @@ class LibvirtDriver(AbstractSystemsDriver):
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DEVICE_TYPE_MAP = {
|
||||||
|
constants.DEVICE_TYPE_CD: 'cdrom',
|
||||||
|
constants.DEVICE_TYPE_FLOPPY: 'floppy',
|
||||||
|
}
|
||||||
|
|
||||||
|
DEVICE_TYPE_MAP_REV = {v: k for k, v in DEVICE_TYPE_MAP.items()}
|
||||||
|
|
||||||
|
# target device, controller ID, controller unit number
|
||||||
|
DEVICE_TARGET_MAP = {
|
||||||
|
constants.DEVICE_TYPE_FLOPPY: ('fda', 'fdc', '0'),
|
||||||
|
constants.DEVICE_TYPE_CD: ('hdc', 'ide', '1')
|
||||||
|
}
|
||||||
|
|
||||||
DEFAULT_BIOS_ATTRIBUTES = {"BootMode": "Uefi",
|
DEFAULT_BIOS_ATTRIBUTES = {"BootMode": "Uefi",
|
||||||
"EmbeddedSata": "Raid",
|
"EmbeddedSata": "Raid",
|
||||||
"NicBoot1": "NetworkBoot",
|
"NicBoot1": "NetworkBoot",
|
||||||
"ProcTurboMode": "Enabled"}
|
"ProcTurboMode": "Enabled"}
|
||||||
|
|
||||||
|
STORAGE_POOL = 'default'
|
||||||
|
|
||||||
|
STORAGE_POOL_XML = """
|
||||||
|
<volume type='file'>
|
||||||
|
<name>%(name)s</name>
|
||||||
|
<key>%(name)s</key>
|
||||||
|
<capacity unit='bytes'>%(size)i</capacity>
|
||||||
|
<physical unit='bytes'>%(size)i</physical>
|
||||||
|
<target>
|
||||||
|
<path>%(path)s</path>
|
||||||
|
<format type='raw'/>
|
||||||
|
</target>
|
||||||
|
</volume>
|
||||||
|
"""
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def initialize(cls, config, uri=None):
|
def initialize(cls, config, uri=None):
|
||||||
cls._config = config
|
cls._config = config
|
||||||
@ -611,7 +640,174 @@ class LibvirtDriver(AbstractSystemsDriver):
|
|||||||
:returns: a `tuple` of (boot_image, write_protected, inserted)
|
:returns: a `tuple` of (boot_image, write_protected, inserted)
|
||||||
:raises: `error.FishyError` if boot device can't be accessed
|
:raises: `error.FishyError` if boot device can't be accessed
|
||||||
"""
|
"""
|
||||||
raise error.FishyError('Not implemented')
|
domain = self._get_domain(identity, readonly=True)
|
||||||
|
|
||||||
|
tree = ET.fromstring(domain.XMLDesc())
|
||||||
|
|
||||||
|
device_element = tree.find('devices')
|
||||||
|
if device_element is None:
|
||||||
|
msg = ('Missing "devices" tag in the libvirt domain '
|
||||||
|
'"%(identity)s" configuration' % {'identity': identity})
|
||||||
|
raise error.FishyError(msg)
|
||||||
|
|
||||||
|
for disk_element in device_element.findall('disk'):
|
||||||
|
dev_type = disk_element.attrib.get('device')
|
||||||
|
if (dev_type not in self.DEVICE_TYPE_MAP_REV or
|
||||||
|
dev_type != self.DEVICE_TYPE_MAP.get(device)):
|
||||||
|
continue
|
||||||
|
|
||||||
|
source_element = disk_element.find('source')
|
||||||
|
if source_element is None:
|
||||||
|
continue
|
||||||
|
|
||||||
|
boot_image = source_element.attrib.get('file')
|
||||||
|
if boot_image is None:
|
||||||
|
continue
|
||||||
|
|
||||||
|
read_only = disk_element.find('readonly') or False
|
||||||
|
|
||||||
|
inserted = (self.get_boot_device(identity) ==
|
||||||
|
constants.DEVICE_TYPE_CD)
|
||||||
|
if inserted:
|
||||||
|
inserted = self.get_boot_mode(identity) == 'Uefi'
|
||||||
|
|
||||||
|
return boot_image, read_only, inserted
|
||||||
|
|
||||||
|
return '', False, False
|
||||||
|
|
||||||
|
def _upload_image(self, domain, conn, boot_image):
|
||||||
|
pool = conn.storagePoolLookupByName(self.STORAGE_POOL)
|
||||||
|
|
||||||
|
pool_tree = ET.fromstring(pool.XMLDesc())
|
||||||
|
|
||||||
|
# Find out path to images
|
||||||
|
pool_path_element = pool_tree.find('target/path')
|
||||||
|
if pool_path_element is None:
|
||||||
|
msg = ('Missing "target/path" tag in the libvirt '
|
||||||
|
'storage pool "%(pool)s"'
|
||||||
|
'' % {'pool': self.STORAGE_POOL})
|
||||||
|
raise error.FishyError(msg)
|
||||||
|
|
||||||
|
image_name = '%s-%s.img' % (
|
||||||
|
os.path.basename(boot_image).replace('.', '-'),
|
||||||
|
domain.UUIDString())
|
||||||
|
|
||||||
|
image_path = os.path.join(
|
||||||
|
pool_path_element.text, image_name)
|
||||||
|
|
||||||
|
image_size = os.stat(boot_image).st_size
|
||||||
|
|
||||||
|
# Remove already existing volume
|
||||||
|
|
||||||
|
volumes_names = [v.name() for v in pool.listAllVolumes()]
|
||||||
|
if image_name in volumes_names:
|
||||||
|
volume = pool.storageVolLookupByName(image_name)
|
||||||
|
volume.delete()
|
||||||
|
|
||||||
|
# Create new volume
|
||||||
|
|
||||||
|
volume = pool.createXML(
|
||||||
|
self.STORAGE_POOL_XML % {
|
||||||
|
'name': image_name, 'path': image_path,
|
||||||
|
'size': image_size})
|
||||||
|
|
||||||
|
# Upload image to hypervisor
|
||||||
|
|
||||||
|
stream = conn.newStream()
|
||||||
|
volume.upload(stream, 0, image_size)
|
||||||
|
|
||||||
|
def read_file(stream, nbytes, fl):
|
||||||
|
return fl.read(nbytes)
|
||||||
|
|
||||||
|
stream.sendAll(read_file, open(boot_image, 'rb'))
|
||||||
|
|
||||||
|
stream.finish()
|
||||||
|
|
||||||
|
return image_path
|
||||||
|
|
||||||
|
def _add_boot_image(self, domain, domain_tree, device,
|
||||||
|
boot_image, write_protected):
|
||||||
|
|
||||||
|
identity = domain.UUIDString()
|
||||||
|
|
||||||
|
device_element = domain_tree.find('devices')
|
||||||
|
if device_element is None:
|
||||||
|
msg = ('Missing "devices" tag in the libvirt domain '
|
||||||
|
'"%(identity)s" configuration' % {'identity': identity})
|
||||||
|
raise error.FishyError(msg)
|
||||||
|
|
||||||
|
with libvirt_open(self._uri) as conn:
|
||||||
|
|
||||||
|
image_path = self._upload_image(domain, conn, boot_image)
|
||||||
|
|
||||||
|
try:
|
||||||
|
lv_device = self.BOOT_DEVICE_MAP[device]
|
||||||
|
|
||||||
|
except KeyError:
|
||||||
|
raise error.FishyError(
|
||||||
|
'Unknown device %s at %s' % (device, identity))
|
||||||
|
|
||||||
|
# Add disk element pointing to the boot image
|
||||||
|
|
||||||
|
disk_element = ET.SubElement(device_element, 'disk')
|
||||||
|
disk_element.set('type', 'file')
|
||||||
|
disk_element.set('device', lv_device)
|
||||||
|
|
||||||
|
tgt_dev, tgt_bus, tgt_unit = self.DEVICE_TARGET_MAP[device]
|
||||||
|
|
||||||
|
target_element = ET.SubElement(disk_element, 'target')
|
||||||
|
target_element.set('dev', tgt_dev)
|
||||||
|
target_element.set('bus', tgt_bus)
|
||||||
|
|
||||||
|
driver_element = ET.SubElement(disk_element, 'driver')
|
||||||
|
driver_element.set('name', 'qemu')
|
||||||
|
driver_element.set('type', 'raw')
|
||||||
|
|
||||||
|
address_element = ET.SubElement(disk_element, 'address')
|
||||||
|
address_element.set('type', 'drive')
|
||||||
|
address_element.set('controller', '0')
|
||||||
|
address_element.set('bus', '0')
|
||||||
|
address_element.set('target', '0')
|
||||||
|
address_element.set('unit', tgt_unit)
|
||||||
|
|
||||||
|
source_element = ET.SubElement(disk_element, 'source')
|
||||||
|
source_element.set('file', image_path)
|
||||||
|
|
||||||
|
if write_protected:
|
||||||
|
ET.SubElement(disk_element, 'readonly')
|
||||||
|
|
||||||
|
conn.defineXML(ET.tostring(domain_tree).decode('utf-8'))
|
||||||
|
|
||||||
|
def _remove_boot_images(self, domain, domain_tree, device):
|
||||||
|
|
||||||
|
identity = domain.UUIDString()
|
||||||
|
|
||||||
|
device_element = domain_tree.find('devices')
|
||||||
|
if device_element is None:
|
||||||
|
msg = ('Missing "devices" tag in the libvirt domain '
|
||||||
|
'"%(identity)s" configuration' % {'identity': identity})
|
||||||
|
raise error.FishyError(msg)
|
||||||
|
|
||||||
|
try:
|
||||||
|
lv_device = self.BOOT_DEVICE_MAP[device]
|
||||||
|
|
||||||
|
except KeyError:
|
||||||
|
raise error.FishyError(
|
||||||
|
'Unknown device %s at %s' % (device, identity))
|
||||||
|
|
||||||
|
device_element = domain_tree.find('devices')
|
||||||
|
if device_element is None:
|
||||||
|
msg = ('Missing "devices" tag in the libvirt domain '
|
||||||
|
'"%(identity)s" configuration' % {'identity': identity})
|
||||||
|
raise error.FishyError(msg)
|
||||||
|
|
||||||
|
# First, remove all existing devices
|
||||||
|
disk_elements = device_element.findall('disk')
|
||||||
|
|
||||||
|
for disk_element in disk_elements:
|
||||||
|
dev_type = disk_element.attrib.get('device')
|
||||||
|
if dev_type == lv_device:
|
||||||
|
device_element.remove(disk_element)
|
||||||
|
|
||||||
def set_boot_image(self, identity, device, boot_image=None,
|
def set_boot_image(self, identity, device, boot_image=None,
|
||||||
write_protected=True):
|
write_protected=True):
|
||||||
@ -626,4 +822,20 @@ class LibvirtDriver(AbstractSystemsDriver):
|
|||||||
|
|
||||||
:raises: `error.FishyError` if boot device can't be set
|
:raises: `error.FishyError` if boot device can't be set
|
||||||
"""
|
"""
|
||||||
raise error.FishyError('Not implemented')
|
domain = self._get_domain(identity)
|
||||||
|
|
||||||
|
domain_tree = ET.fromstring(domain.XMLDesc())
|
||||||
|
|
||||||
|
self._remove_boot_images(domain, domain_tree, device)
|
||||||
|
|
||||||
|
if boot_image:
|
||||||
|
|
||||||
|
try:
|
||||||
|
self._add_boot_image(domain, domain_tree, device,
|
||||||
|
boot_image, write_protected)
|
||||||
|
|
||||||
|
except libvirt.libvirtError as e:
|
||||||
|
msg = ('Error changing boot image at libvirt URI "%(uri)s": '
|
||||||
|
'%(error)s' % {'uri': self._uri, 'error': e})
|
||||||
|
|
||||||
|
raise error.FishyError(msg)
|
||||||
|
@ -355,7 +355,7 @@ class OpenStackDriver(AbstractSystemsDriver):
|
|||||||
:returns: a `tuple` of (boot_image, write_protected, inserted)
|
:returns: a `tuple` of (boot_image, write_protected, inserted)
|
||||||
:raises: `error.FishyError` if boot device can't be accessed
|
:raises: `error.FishyError` if boot device can't be accessed
|
||||||
"""
|
"""
|
||||||
raise error.FishyError('Not implemented')
|
raise error.NotSupportedError('Not implemented')
|
||||||
|
|
||||||
def set_boot_image(self, identity, device, boot_image=None,
|
def set_boot_image(self, identity, device, boot_image=None,
|
||||||
write_protected=True):
|
write_protected=True):
|
||||||
@ -370,4 +370,4 @@ class OpenStackDriver(AbstractSystemsDriver):
|
|||||||
|
|
||||||
:raises: `error.FishyError` if boot device can't be set
|
:raises: `error.FishyError` if boot device can't be set
|
||||||
"""
|
"""
|
||||||
raise error.FishyError('Not implemented')
|
raise error.NotSupportedError('Not implemented')
|
||||||
|
@ -20,3 +20,7 @@ class FishyError(Exception):
|
|||||||
|
|
||||||
class AliasAccessError(FishyError):
|
class AliasAccessError(FishyError):
|
||||||
"""Node access attempted via an alias, not UUID"""
|
"""Node access attempted via an alias, not UUID"""
|
||||||
|
|
||||||
|
|
||||||
|
class NotSupportedError(FishyError):
|
||||||
|
"""Feature not supported by resource driver"""
|
||||||
|
17
sushy_tools/tests/unit/emulator/pool.xml
Normal file
17
sushy_tools/tests/unit/emulator/pool.xml
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
<pool type='dir'>
|
||||||
|
<name>default</name>
|
||||||
|
<uuid>267e1242-d53f-46dd-adb3-9f3992c55f6f</uuid>
|
||||||
|
<capacity unit='bytes'>166318571520</capacity>
|
||||||
|
<allocation unit='bytes'>13143412736</allocation>
|
||||||
|
<available unit='bytes'>153175158784</available>
|
||||||
|
<source>
|
||||||
|
</source>
|
||||||
|
<target>
|
||||||
|
<path>/var/lib/libvirt/images</path>
|
||||||
|
<permissions>
|
||||||
|
<mode>0711</mode>
|
||||||
|
<owner>0</owner>
|
||||||
|
<group>0</group>
|
||||||
|
</permissions>
|
||||||
|
</target>
|
||||||
|
</pool>
|
@ -341,6 +341,55 @@ class LibvirtDriverTestCase(base.BaseTestCase):
|
|||||||
# NOTE(etingof): should enforce default loader
|
# NOTE(etingof): should enforce default loader
|
||||||
self.assertIsNone(os_element.find('loader'))
|
self.assertIsNone(os_element.find('loader'))
|
||||||
|
|
||||||
|
@mock.patch('libvirt.openReadOnly', autospec=True)
|
||||||
|
def test_get_boot_image(self, libvirt_mock):
|
||||||
|
with open('sushy_tools/tests/unit/emulator/domain.xml', 'r') as f:
|
||||||
|
data = f.read()
|
||||||
|
|
||||||
|
conn_mock = libvirt_mock.return_value
|
||||||
|
domain_mock = conn_mock.lookupByUUID.return_value
|
||||||
|
domain_mock.XMLDesc.return_value = data
|
||||||
|
|
||||||
|
image_info = self.test_driver.get_boot_image(self.uuid, 'Cd')
|
||||||
|
|
||||||
|
expected = '/home/user/boot.iso', False, False
|
||||||
|
|
||||||
|
self.assertEqual(expected, image_info)
|
||||||
|
|
||||||
|
@mock.patch('sushy_tools.emulator.resources.systems.libvirtdriver'
|
||||||
|
'.os.stat', autospec=True)
|
||||||
|
@mock.patch('sushy_tools.emulator.resources.systems.libvirtdriver'
|
||||||
|
'.open')
|
||||||
|
@mock.patch('libvirt.open', autospec=True)
|
||||||
|
@mock.patch('libvirt.openReadOnly', autospec=True)
|
||||||
|
def test_set_boot_image(self, libvirt_mock, libvirt_rw_mock,
|
||||||
|
open_mock, stat_mock):
|
||||||
|
with open('sushy_tools/tests/unit/emulator/domain.xml', 'r') as f:
|
||||||
|
data = f.read()
|
||||||
|
|
||||||
|
conn_mock = libvirt_rw_mock.return_value
|
||||||
|
domain_mock = conn_mock.lookupByUUID.return_value
|
||||||
|
domain_mock.XMLDesc.return_value = data
|
||||||
|
|
||||||
|
pool_mock = conn_mock.storagePoolLookupByName.return_value
|
||||||
|
|
||||||
|
with open('sushy_tools/tests/unit/emulator/pool.xml', 'r') as f:
|
||||||
|
data = f.read()
|
||||||
|
|
||||||
|
pool_mock.XMLDesc.return_value = data
|
||||||
|
|
||||||
|
self.test_driver.set_boot_image(self.uuid, 'Cd', '/tmp/image.iso')
|
||||||
|
|
||||||
|
conn_mock = libvirt_rw_mock.return_value
|
||||||
|
pool_mock.listAllVolumes.assert_called_once_with()
|
||||||
|
stat_mock.assert_called_once_with('/tmp/image.iso')
|
||||||
|
pool_mock.createXML.assert_called_once_with(mock.ANY)
|
||||||
|
|
||||||
|
volume_mock = pool_mock.createXML.return_value
|
||||||
|
volume_mock.upload.assert_called_once_with(mock.ANY, 0, mock.ANY)
|
||||||
|
|
||||||
|
conn_mock.defineXML.assert_called_once_with(mock.ANY)
|
||||||
|
|
||||||
@mock.patch('libvirt.openReadOnly', autospec=True)
|
@mock.patch('libvirt.openReadOnly', autospec=True)
|
||||||
def test_get_total_memory(self, libvirt_mock):
|
def test_get_total_memory(self, libvirt_mock):
|
||||||
conn_mock = libvirt_mock.return_value
|
conn_mock = libvirt_mock.return_value
|
||||||
|
Loading…
x
Reference in New Issue
Block a user