Add system boot mode support

This change adds the ability to read and (for some
drivers) change system boot mode via Redfish call.

At present just Legacy and Uefi boot modes are supported.

Story: 1741062
Task: 12045
Change-Id: I077d31b37a34d90fc8bfd87d24b66b227088fd7c
This commit is contained in:
Ilya Etingof 2017-12-05 23:28:08 +01:00
parent 4c73cbaa2b
commit 5b16e8c8e9
7 changed files with 169 additions and 17 deletions

View File

@ -64,6 +64,7 @@ class AbstractDriver(object):
If not specified, current system power state is returned.
Valid values are: *On*, *ForceOn*, *ForceOff*, *GracefulShutdown*,
*GracefulRestart*, *ForceRestart*, *Nmi*.
: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
system. If not specified, current boot device is returned.
Valid values are: *Pxe*, *Hdd*, *Cd*.
: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
def get_total_memory(self, identity):
"""Get computer system total memory

View File

@ -47,6 +47,8 @@ class libvirt_open(object):
class LibvirtDriver(AbstractDriver):
"""Libvirt driver"""
# XML schema: https://libvirt.org/formatdomain.html#elementsOSBIOS
BOOT_DEVICE_MAP = {
'Pxe': 'network',
'Hdd': 'hd',
@ -57,6 +59,13 @@ class LibvirtDriver(AbstractDriver):
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):
self._uri = uri or self.LIBVIRT_URI
@ -173,6 +182,7 @@ class LibvirtDriver(AbstractDriver):
domain = conn.lookupByName(identity)
# XML schema: https://libvirt.org/formatdomain.html#elementsOSBIOS
tree = ET.fromstring(domain.XMLDesc())
try:
@ -202,6 +212,71 @@ class LibvirtDriver(AbstractDriver):
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):
"""Get computer system total memory

View File

@ -38,6 +38,13 @@ class OpenStackDriver(AbstractDriver):
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):
self._cc = openstack.connect(cloud=os_cloud)
self._os_cloud = os_cloud
@ -188,6 +195,37 @@ class OpenStackDriver(AbstractDriver):
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):
"""Get computer system total memory

View File

@ -125,7 +125,8 @@ def system_resource(identity):
power_state=driver.get_power_state(identity),
total_memory_gb=driver.get_total_memory(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':
@ -134,20 +135,28 @@ def system_resource(identity):
return 'PATCH only works for the Boot element', 400
target = boot.get('BootSourceOverrideTarget')
if not target:
return 'Missing the BootSourceOverrideTarget element', 400
# NOTE(lucasagomes): In libvirt we always set the boot
# device frequency to "continuous" so, we are ignoring the
# BootSourceOverrideEnabled element here
if target:
# NOTE(lucasagomes): In libvirt we always set the boot
# device frequency to "continuous" so, we are ignoring the
# 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"',
target, identity)
app.logger.info('Set boot device to "%s" for system "%s"',
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
@ -205,14 +214,14 @@ def main():
if args.os_cloud:
if not novadriver:
app.logger.error('Nova driver not loaded')
sys.exit(1)
return 1
driver = novadriver.OpenStackDriver(args.os_cloud)
else:
if not libvirtdriver:
app.logger.error('libvirt driver not loaded')
sys.exit(1)
return 1
driver = libvirtdriver.LibvirtDriver(args.libvirt_uri)

View File

@ -22,17 +22,27 @@
"PowerState": "{{ power_state }}",
{%- endif %}
"Boot": {
"BootSourceOverrideEnabled": "Continuous",
{%- if boot_source_target %}
"BootSourceOverrideEnabled": "Continuous",
"BootSourceOverrideTarget": "{{ boot_source_target }}",
"BootSourceOverrideTarget@Redfish.AllowableValues": [
"Pxe",
"Cd",
"Hdd"
{%- if boot_source_mode %}
],
{%- endif %}
"BootSourceOverrideMode": "UEFI",
{%- if 'uefi' in boot_source_mode.lower() %}
"BootSourceOverrideMode": "{{ boot_source_mode }}",
"UefiTargetBootSourceOverride": "/0x31/0x33/0x01/0x01"
{%- else %}
"BootSourceOverrideMode": "{{ boot_source_mode }}"
{%- endif %}
{%- else %}
]
{%- endif %}
{%- else %}
"BootSourceOverrideEnabled": "Continuous"
{%- endif %}
},
"TrustedModules": [
{

View File

@ -7,6 +7,7 @@
<os>
<type arch='i686' machine='pc'>hvm</type>
<boot dev='cdrom'/>
<loader type='rom'/>
</os>
<devices>
<emulator>/usr/bin/qemu-system-x86_64</emulator>

View File

@ -63,7 +63,7 @@ class EmulatorTestCase(base.BaseTestCase):
render_mock.assert_called_once_with(
'system.json', identity='xxxx-yyyy-zzzz', uuid='zzzz-yyyy-xxxx',
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)
def test_system_resource_patch(self, libvirt_mock):