Merge "Add system boot mode support"
This commit is contained in:
commit
0260a11155
@ -64,6 +64,7 @@ class AbstractDriver(object):
|
|||||||
If not specified, current system power state is returned.
|
If not specified, current system power state is returned.
|
||||||
Valid values are: *On*, *ForceOn*, *ForceOff*, *GracefulShutdown*,
|
Valid values are: *On*, *ForceOn*, *ForceOff*, *GracefulShutdown*,
|
||||||
*GracefulRestart*, *ForceRestart*, *Nmi*.
|
*GracefulRestart*, *ForceRestart*, *Nmi*.
|
||||||
|
|
||||||
:raises: `FishyError` if power state can't be set
|
:raises: `FishyError` if power state can't be set
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@ -82,9 +83,27 @@ class AbstractDriver(object):
|
|||||||
:param boot_source: string literal requesting boot device change on the
|
:param boot_source: string literal requesting boot device change on the
|
||||||
system. If not specified, current boot device is returned.
|
system. If not specified, current boot device is returned.
|
||||||
Valid values are: *Pxe*, *Hdd*, *Cd*.
|
Valid values are: *Pxe*, *Hdd*, *Cd*.
|
||||||
|
|
||||||
:raises: `FishyError` if boot device can't be set
|
:raises: `FishyError` if boot device can't be set
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
def get_boot_mode(self, identity):
|
||||||
|
"""Get computer system boot mode.
|
||||||
|
|
||||||
|
:returns: either *Uefi* or *Legacy* as `str` or `None` if
|
||||||
|
current boot mode can't be determined
|
||||||
|
"""
|
||||||
|
|
||||||
|
def set_boot_mode(self, identity, boot_mode):
|
||||||
|
"""Set computer system boot mode.
|
||||||
|
|
||||||
|
:param boot_mode: optional string literal requesting boot mode
|
||||||
|
change on the system. If not specified, current boot mode is
|
||||||
|
returned. Valid values are: *Uefi*, *Legacy*.
|
||||||
|
|
||||||
|
:raises: `FishyError` if boot mode can't be set
|
||||||
|
"""
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def get_total_memory(self, identity):
|
def get_total_memory(self, identity):
|
||||||
"""Get computer system total memory
|
"""Get computer system total memory
|
||||||
|
@ -47,6 +47,8 @@ class libvirt_open(object):
|
|||||||
class LibvirtDriver(AbstractDriver):
|
class LibvirtDriver(AbstractDriver):
|
||||||
"""Libvirt driver"""
|
"""Libvirt driver"""
|
||||||
|
|
||||||
|
# XML schema: https://libvirt.org/formatdomain.html#elementsOSBIOS
|
||||||
|
|
||||||
BOOT_DEVICE_MAP = {
|
BOOT_DEVICE_MAP = {
|
||||||
'Pxe': 'network',
|
'Pxe': 'network',
|
||||||
'Hdd': 'hd',
|
'Hdd': 'hd',
|
||||||
@ -57,6 +59,13 @@ class LibvirtDriver(AbstractDriver):
|
|||||||
|
|
||||||
LIBVIRT_URI = 'qemu:///system'
|
LIBVIRT_URI = 'qemu:///system'
|
||||||
|
|
||||||
|
BOOT_MODE_MAP = {
|
||||||
|
'Legacy': 'rom',
|
||||||
|
'Uefi': 'pflash',
|
||||||
|
}
|
||||||
|
|
||||||
|
BOOT_MODE_MAP_REV = {v: k for k, v in BOOT_MODE_MAP.items()}
|
||||||
|
|
||||||
def __init__(self, uri=None):
|
def __init__(self, uri=None):
|
||||||
self._uri = uri or self.LIBVIRT_URI
|
self._uri = uri or self.LIBVIRT_URI
|
||||||
|
|
||||||
@ -173,6 +182,7 @@ class LibvirtDriver(AbstractDriver):
|
|||||||
|
|
||||||
domain = conn.lookupByName(identity)
|
domain = conn.lookupByName(identity)
|
||||||
|
|
||||||
|
# XML schema: https://libvirt.org/formatdomain.html#elementsOSBIOS
|
||||||
tree = ET.fromstring(domain.XMLDesc())
|
tree = ET.fromstring(domain.XMLDesc())
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@ -202,6 +212,71 @@ class LibvirtDriver(AbstractDriver):
|
|||||||
|
|
||||||
raise FishyError(msg)
|
raise FishyError(msg)
|
||||||
|
|
||||||
|
def get_boot_mode(self, identity):
|
||||||
|
"""Get computer system boot mode.
|
||||||
|
|
||||||
|
:returns: either *Uefi* or *Legacy* as `str` or `None` if
|
||||||
|
current boot mode can't be determined
|
||||||
|
"""
|
||||||
|
with libvirt_open(self._uri, readonly=True) as conn:
|
||||||
|
|
||||||
|
domain = conn.lookupByName(identity)
|
||||||
|
|
||||||
|
# XML schema: https://libvirt.org/formatdomain.html#elementsOSBIOS
|
||||||
|
tree = ET.fromstring(domain.XMLDesc())
|
||||||
|
|
||||||
|
loader_element = tree.find('.//loader')
|
||||||
|
|
||||||
|
if loader_element is not None:
|
||||||
|
boot_mode = (
|
||||||
|
self.BOOT_MODE_MAP_REV.get(loader_element.get('type'))
|
||||||
|
)
|
||||||
|
|
||||||
|
return boot_mode
|
||||||
|
|
||||||
|
def set_boot_mode(self, identity, boot_mode):
|
||||||
|
"""Set computer system boot mode.
|
||||||
|
|
||||||
|
:param boot_mode: optional string literal requesting boot mode
|
||||||
|
change on the system. If not specified, current boot mode is
|
||||||
|
returned. Valid values are: *Uefi*, *Legacy*.
|
||||||
|
|
||||||
|
:raises: `FishyError` if boot mode can't be set
|
||||||
|
"""
|
||||||
|
with libvirt_open(self._uri) as conn:
|
||||||
|
|
||||||
|
domain = conn.lookupByName(identity)
|
||||||
|
|
||||||
|
# XML schema: https://libvirt.org/formatdomain.html#elementsOSBIOS
|
||||||
|
tree = ET.fromstring(domain.XMLDesc())
|
||||||
|
|
||||||
|
try:
|
||||||
|
target = self.BOOT_MODE_MAP[boot_mode]
|
||||||
|
|
||||||
|
except KeyError:
|
||||||
|
msg = ('Unknown boot mode requested: '
|
||||||
|
'%(boot_mode)s' % {'boot_mode': boot_mode})
|
||||||
|
|
||||||
|
raise FishyError(msg)
|
||||||
|
|
||||||
|
for os_element in tree.findall('os'):
|
||||||
|
# Remove all "boot" elements
|
||||||
|
for loader_element in os_element.findall('loader'):
|
||||||
|
os_element.remove(loader_element)
|
||||||
|
|
||||||
|
# Add a new loader element with the request boot mode
|
||||||
|
loader_element = ET.SubElement(os_element, 'loader')
|
||||||
|
loader_element.set('type', target)
|
||||||
|
|
||||||
|
try:
|
||||||
|
self._conn.defineXML(ET.tostring(tree).decode('utf-8'))
|
||||||
|
|
||||||
|
except libvirt.libvirtError as e:
|
||||||
|
msg = ('Error changing boot mode at libvirt URI "%(uri)s": '
|
||||||
|
'%(error)s' % {'uri': self._uri, 'error': e})
|
||||||
|
|
||||||
|
raise FishyError(msg)
|
||||||
|
|
||||||
def get_total_memory(self, identity):
|
def get_total_memory(self, identity):
|
||||||
"""Get computer system total memory
|
"""Get computer system total memory
|
||||||
|
|
||||||
|
@ -38,6 +38,13 @@ class OpenStackDriver(AbstractDriver):
|
|||||||
|
|
||||||
BOOT_DEVICE_MAP_REV = {v: k for k, v in BOOT_DEVICE_MAP.items()}
|
BOOT_DEVICE_MAP_REV = {v: k for k, v in BOOT_DEVICE_MAP.items()}
|
||||||
|
|
||||||
|
BOOT_MODE_MAP = {
|
||||||
|
'Legacy': 'bios',
|
||||||
|
'Uefi': 'uefi',
|
||||||
|
}
|
||||||
|
|
||||||
|
BOOT_MODE_MAP_REV = {v: k for k, v in BOOT_MODE_MAP.items()}
|
||||||
|
|
||||||
def __init__(self, os_cloud, readonly=False):
|
def __init__(self, os_cloud, readonly=False):
|
||||||
self._cc = openstack.connect(cloud=os_cloud)
|
self._cc = openstack.connect(cloud=os_cloud)
|
||||||
self._os_cloud = os_cloud
|
self._os_cloud = os_cloud
|
||||||
@ -188,6 +195,37 @@ class OpenStackDriver(AbstractDriver):
|
|||||||
if target == 'network' else ''}
|
if target == 'network' else ''}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def get_boot_mode(self, identity):
|
||||||
|
"""Get computer system boot mode.
|
||||||
|
|
||||||
|
:returns: either *Uefi* or *Legacy* as `str` or `None` if
|
||||||
|
current boot mode can't be determined
|
||||||
|
"""
|
||||||
|
instance = self._get_instance(identity)
|
||||||
|
|
||||||
|
image = self._nc.glance.find_image(instance.image['id'])
|
||||||
|
|
||||||
|
hw_firmware_type = getattr(image, 'hw_firmware_type', None)
|
||||||
|
|
||||||
|
return self.BOOT_MODE_MAP_REV.get(hw_firmware_type)
|
||||||
|
|
||||||
|
def set_boot_mode(self, identity, boot_mode):
|
||||||
|
"""Set computer system boot mode.
|
||||||
|
|
||||||
|
:param boot_mode: optional string literal requesting boot mode
|
||||||
|
change on the system. If not specified, current boot mode is
|
||||||
|
returned. Valid values are: *Uefi*, *Legacy*.
|
||||||
|
|
||||||
|
:raises: `FishyError` if boot mode can't be set
|
||||||
|
"""
|
||||||
|
# just to make sure passed identity exists
|
||||||
|
self._get_instance(identity)
|
||||||
|
|
||||||
|
msg = ('The cloud driver %(driver)s does not allow changing boot '
|
||||||
|
'mode through Redfish' % {'driver': self.driver})
|
||||||
|
|
||||||
|
raise FishyError(msg)
|
||||||
|
|
||||||
def get_total_memory(self, identity):
|
def get_total_memory(self, identity):
|
||||||
"""Get computer system total memory
|
"""Get computer system total memory
|
||||||
|
|
||||||
|
@ -125,7 +125,8 @@ def system_resource(identity):
|
|||||||
power_state=driver.get_power_state(identity),
|
power_state=driver.get_power_state(identity),
|
||||||
total_memory_gb=driver.get_total_memory(identity),
|
total_memory_gb=driver.get_total_memory(identity),
|
||||||
total_cpus=driver.get_total_cpus(identity),
|
total_cpus=driver.get_total_cpus(identity),
|
||||||
boot_source_target=driver.get_boot_device(identity)
|
boot_source_target=driver.get_boot_device(identity),
|
||||||
|
boot_source_mode=driver.get_boot_mode(identity)
|
||||||
)
|
)
|
||||||
|
|
||||||
elif flask.request.method == 'PATCH':
|
elif flask.request.method == 'PATCH':
|
||||||
@ -134,21 +135,29 @@ def system_resource(identity):
|
|||||||
return 'PATCH only works for the Boot element', 400
|
return 'PATCH only works for the Boot element', 400
|
||||||
|
|
||||||
target = boot.get('BootSourceOverrideTarget')
|
target = boot.get('BootSourceOverrideTarget')
|
||||||
if not target:
|
|
||||||
return 'Missing the BootSourceOverrideTarget element', 400
|
|
||||||
|
|
||||||
|
if target:
|
||||||
# NOTE(lucasagomes): In libvirt we always set the boot
|
# NOTE(lucasagomes): In libvirt we always set the boot
|
||||||
# device frequency to "continuous" so, we are ignoring the
|
# device frequency to "continuous" so, we are ignoring the
|
||||||
# BootSourceOverrideEnabled element here
|
# BootSourceOverrideEnabled element here
|
||||||
|
|
||||||
# TODO(lucasagomes): We should allow changing the boot mode from
|
|
||||||
# BIOS to UEFI (and vice-versa)
|
|
||||||
|
|
||||||
driver.set_boot_device(identity, target)
|
driver.set_boot_device(identity, target)
|
||||||
|
|
||||||
app.logger.info('Set boot device to "%s" for system "%s"',
|
app.logger.info('Set boot device to "%s" for system "%s"',
|
||||||
target, identity)
|
target, identity)
|
||||||
|
|
||||||
|
mode = boot.get('BootSourceOverrideMode')
|
||||||
|
|
||||||
|
if mode:
|
||||||
|
driver.set_boot_mode(identity, mode)
|
||||||
|
|
||||||
|
app.logger.info('Set boot mode to "%s" for system "%s"',
|
||||||
|
mode, identity)
|
||||||
|
|
||||||
|
if not target and not mode:
|
||||||
|
return ('Missing the BootSourceOverrideTarget and/or '
|
||||||
|
'BootSourceOverrideMode element', 400)
|
||||||
|
|
||||||
return '', 204
|
return '', 204
|
||||||
|
|
||||||
|
|
||||||
@ -205,14 +214,14 @@ def main():
|
|||||||
if args.os_cloud:
|
if args.os_cloud:
|
||||||
if not novadriver:
|
if not novadriver:
|
||||||
app.logger.error('Nova driver not loaded')
|
app.logger.error('Nova driver not loaded')
|
||||||
sys.exit(1)
|
return 1
|
||||||
|
|
||||||
driver = novadriver.OpenStackDriver(args.os_cloud)
|
driver = novadriver.OpenStackDriver(args.os_cloud)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
if not libvirtdriver:
|
if not libvirtdriver:
|
||||||
app.logger.error('libvirt driver not loaded')
|
app.logger.error('libvirt driver not loaded')
|
||||||
sys.exit(1)
|
return 1
|
||||||
|
|
||||||
driver = libvirtdriver.LibvirtDriver(args.libvirt_uri)
|
driver = libvirtdriver.LibvirtDriver(args.libvirt_uri)
|
||||||
|
|
||||||
|
@ -22,17 +22,27 @@
|
|||||||
"PowerState": "{{ power_state }}",
|
"PowerState": "{{ power_state }}",
|
||||||
{%- endif %}
|
{%- endif %}
|
||||||
"Boot": {
|
"Boot": {
|
||||||
"BootSourceOverrideEnabled": "Continuous",
|
|
||||||
{%- if boot_source_target %}
|
{%- if boot_source_target %}
|
||||||
|
"BootSourceOverrideEnabled": "Continuous",
|
||||||
"BootSourceOverrideTarget": "{{ boot_source_target }}",
|
"BootSourceOverrideTarget": "{{ boot_source_target }}",
|
||||||
"BootSourceOverrideTarget@Redfish.AllowableValues": [
|
"BootSourceOverrideTarget@Redfish.AllowableValues": [
|
||||||
"Pxe",
|
"Pxe",
|
||||||
"Cd",
|
"Cd",
|
||||||
"Hdd"
|
"Hdd"
|
||||||
|
{%- if boot_source_mode %}
|
||||||
],
|
],
|
||||||
{%- endif %}
|
{%- if 'uefi' in boot_source_mode.lower() %}
|
||||||
"BootSourceOverrideMode": "UEFI",
|
"BootSourceOverrideMode": "{{ boot_source_mode }}",
|
||||||
"UefiTargetBootSourceOverride": "/0x31/0x33/0x01/0x01"
|
"UefiTargetBootSourceOverride": "/0x31/0x33/0x01/0x01"
|
||||||
|
{%- else %}
|
||||||
|
"BootSourceOverrideMode": "{{ boot_source_mode }}"
|
||||||
|
{%- endif %}
|
||||||
|
{%- else %}
|
||||||
|
]
|
||||||
|
{%- endif %}
|
||||||
|
{%- else %}
|
||||||
|
"BootSourceOverrideEnabled": "Continuous"
|
||||||
|
{%- endif %}
|
||||||
},
|
},
|
||||||
"TrustedModules": [
|
"TrustedModules": [
|
||||||
{
|
{
|
||||||
|
@ -7,6 +7,7 @@
|
|||||||
<os>
|
<os>
|
||||||
<type arch='i686' machine='pc'>hvm</type>
|
<type arch='i686' machine='pc'>hvm</type>
|
||||||
<boot dev='cdrom'/>
|
<boot dev='cdrom'/>
|
||||||
|
<loader type='rom'/>
|
||||||
</os>
|
</os>
|
||||||
<devices>
|
<devices>
|
||||||
<emulator>/usr/bin/qemu-system-x86_64</emulator>
|
<emulator>/usr/bin/qemu-system-x86_64</emulator>
|
||||||
|
@ -63,7 +63,7 @@ class EmulatorTestCase(base.BaseTestCase):
|
|||||||
render_mock.assert_called_once_with(
|
render_mock.assert_called_once_with(
|
||||||
'system.json', identity='xxxx-yyyy-zzzz', uuid='zzzz-yyyy-xxxx',
|
'system.json', identity='xxxx-yyyy-zzzz', uuid='zzzz-yyyy-xxxx',
|
||||||
power_state='On', total_memory_gb=1, total_cpus=2,
|
power_state='On', total_memory_gb=1, total_cpus=2,
|
||||||
boot_source_target='Cd')
|
boot_source_target='Cd', boot_source_mode='Legacy')
|
||||||
|
|
||||||
@mock.patch('libvirt.open', autospec=True)
|
@mock.patch('libvirt.open', autospec=True)
|
||||||
def test_system_resource_patch(self, libvirt_mock):
|
def test_system_resource_patch(self, libvirt_mock):
|
||||||
|
Loading…
x
Reference in New Issue
Block a user