sushy-tools/sushy_tools/emulator/drivers/novadriver.py

348 lines
11 KiB
Python

# Copyright 2018 Red Hat, Inc.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import logging
import math
from sushy_tools.emulator.drivers.base import AbstractDriver
from sushy_tools.emulator.drivers import memoize
from sushy_tools import error
try:
import openstack
except ImportError:
openstack = None
is_loaded = bool(openstack)
logger = logging.getLogger(__name__)
class OpenStackDriver(AbstractDriver):
"""OpenStack driver"""
NOVA_POWER_STATE_ON = 1
BOOT_DEVICE_MAP = {
'Pxe': 'network',
'Hdd': 'hd',
'Cd': 'cdrom',
}
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()}
PERMANENT_CACHE = {}
@classmethod
def initialize(cls, config, os_cloud, readonly=False):
cls._cc = openstack.connect(cloud=os_cloud)
cls._os_cloud = os_cloud
cls._config = config
return cls
@memoize.memoize()
def _get_instance(self, identity):
instance = self._cc.get_server(identity)
if instance:
if identity != instance.id:
raise error.AliasAccessError(instance.id)
return instance
msg = ('Error finding instance by UUID "%(identity)s" at OS '
'cloud %(os_cloud)s"' % {'identity': identity,
'os_cloud': self._os_cloud})
logger.debug(msg)
raise error.FishyError(msg)
@memoize.memoize(permanent_cache=PERMANENT_CACHE)
def _get_flavor(self, identity):
instance = self._get_instance(identity)
return self._cc.get_flavor(instance.flavor.id)
@memoize.memoize(permanent_cache=PERMANENT_CACHE)
def _get_image_info(self, identity):
return self._cc.image.find_image(identity)
def _get_server_metadata(self, identity):
return self._cc.compute.get_server_metadata(identity).to_dict()
def _set_server_metadata(self, identity, metadata):
self._cc.compute.set_server_metadata(identity, metadata)
@property
def driver(self):
"""Return human-friendly driver description
:returns: driver description as `str`
"""
return '<OpenStack compute>'
@property
def systems(self):
"""Return available computer systems
:returns: list of UUIDs representing the systems
"""
return [server.id for server in self._cc.list_servers()]
def uuid(self, identity):
"""Get computer system UUID by name
:param identity: OpenStack instance name or ID
:returns: computer system UUID
"""
instance = self._get_instance(identity)
return instance.id
def name(self, identity):
"""Get computer system name by name
:param identity: OpenStack instance name or ID
:returns: computer system name
"""
instance = self._get_instance(identity)
return instance.name
def get_power_state(self, identity):
"""Get computer system power state
:param identity: OpenStack instance name or ID
:returns: *On* or *Off*`str` or `None`
if power state can't be determined
"""
try:
instance = self._get_instance(identity)
except error.FishyError:
return
if instance.power_state == self.NOVA_POWER_STATE_ON:
return 'On'
return 'Off'
def set_power_state(self, identity, state):
"""Set computer system power state
:param identity: OpenStack instance name or ID
:param state: optional string literal requesting power state
transition If not specified, current system power state is
returned. Valid values are: *On*, *ForceOn*, *ForceOff*,
*GracefulShutdown*, *GracefulRestart*, *ForceRestart*, *Nmi*.
:raises: `error.FishyError` if power state can't be set
"""
instance = self._get_instance(identity)
if state in ('On', 'ForceOn'):
if instance.power_state != self.NOVA_POWER_STATE_ON:
self._cc.compute.start_server(instance.id)
elif state == 'ForceOff':
if instance.power_state == self.NOVA_POWER_STATE_ON:
self._cc.compute.stop_server(instance.id)
elif state == 'GracefulShutdown':
if instance.power_state == self.NOVA_POWER_STATE_ON:
self._cc.compute.stop_server(instance.id)
elif state == 'GracefulRestart':
if instance.power_state == self.NOVA_POWER_STATE_ON:
self._cc.compute.reboot_server(
instance.id, reboot_type='SOFT'
)
elif state == 'ForceRestart':
if instance.power_state == self.NOVA_POWER_STATE_ON:
self._cc.compute.reboot_server(
instance.id, reboot_type='HARD'
)
# NOTE(etingof) can't support `state == "Nmi"` as
# openstacksdk does not seem to support that
else:
raise error.FishyError(
'Unknown ResetType "%(state)s"' % {'state': state})
def get_boot_device(self, identity):
"""Get computer system boot device name
:param identity: OpenStack instance name or ID
:returns: boot device name as `str` or `None` if device name
can't be determined. Valid values are: *Pxe*, *Hdd*, *Cd*.
"""
try:
instance = self._get_instance(identity)
except error.FishyError:
return
metadata = self._get_server_metadata(instance.id)
# NOTE(etingof): the following probably only works with
# libvirt-backed compute nodes
if metadata.get('libvirt:pxe-first'):
return self.BOOT_DEVICE_MAP_REV['network']
else:
return self.BOOT_DEVICE_MAP_REV['hd']
def set_boot_device(self, identity, boot_source):
"""Set computer system boot device name
:param identity: OpenStack instance name or ID
:param boot_source: optional string literal requesting boot device
change on the system. If not specified, current boot device is
returned. Valid values are: *Pxe*, *Hdd*, *Cd*.
:raises: `error.FishyError` if boot device can't be set
"""
instance = self._get_instance(identity)
try:
target = self.BOOT_DEVICE_MAP[boot_source]
except KeyError:
msg = ('Unknown power state requested: '
'%(boot_source)s' % {'boot_source': boot_source})
raise error.FishyError(msg)
# NOTE(etingof): the following probably only works with
# libvirt-backed compute nodes
self._cc.compute.set_server_metadata(
instance.id, **{'libvirt:pxe-first': '1'
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._get_image_info(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: `error.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 error.FishyError(msg)
def get_total_memory(self, identity):
"""Get computer system total memory
:param identity: OpenStack instance name or ID
:returns: available RAM in GiB as `int` or `None` if total memory
count can't be determined
"""
try:
flavor = self._get_flavor(identity)
except error.FishyError:
return
return int(math.ceil(flavor.ram / 1024.))
def get_total_cpus(self, identity):
"""Get computer system total count of available CPUs
:param identity: OpenStack instance name or ID
:returns: available CPU count as `int` or `None`
if total memory count can't be determined
"""
try:
flavor = self._get_flavor(identity)
except error.FishyError:
return
return flavor.vcpus
def get_bios(self, identity):
"""Not supported as Openstack SDK does not expose API for BIOS"""
raise error.FishyError(
'Operation not supported by the virtualization driver')
def set_bios(self, identity, attributes):
"""Not supported as Openstack SDK does not expose API for BIOS"""
raise error.FishyError(
'Operation not supported by the virtualization driver')
def reset_bios(self, identity):
"""Not supported as Openstack SDK does not expose API for BIOS"""
raise error.FishyError(
'Operation not supported by the virtualization driver')
def get_nics(self, identity):
"""Get server's network interfaces
Use MAC address as network interface's id
:param identity: OpenStack instance name or ID
:returns: list of dictionaries with NIC attributes (id and mac)
"""
instance = self._get_instance(identity)
macs = set()
if not instance.addresses:
return macs
for addresses in instance.addresses.values():
for adr in addresses:
try:
macs.add(adr['OS-EXT-IPS-MAC:mac_addr'])
except KeyError:
logger.warning('Could not find MAC address in %s', adr)
return [{'id': mac, 'mac': mac}
for mac in macs]