336 lines
13 KiB
Python
336 lines
13 KiB
Python
# 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.
|
|
|
|
"""
|
|
Ironic oVirt power manager and management interface.
|
|
|
|
Provides basic power control and management of virtual machines
|
|
via oVirt sdk API.
|
|
|
|
For use in dev and test environments.
|
|
"""
|
|
|
|
from ironic.common import boot_devices
|
|
from ironic.common import exception
|
|
from ironic.common.i18n import _
|
|
from ironic.common import states
|
|
from ironic.conductor import task_manager
|
|
from ironic.drivers import base
|
|
from oslo_config import cfg
|
|
from oslo_log import log as logging
|
|
from oslo_utils import importutils
|
|
|
|
from ironic_staging_drivers.common import exception as staging_exception
|
|
|
|
ovirtsdk = importutils.try_import('ovirtsdk4')
|
|
if ovirtsdk:
|
|
import ovirtsdk4 as sdk
|
|
import ovirtsdk4.types as otypes
|
|
|
|
IRONIC_TO_OVIRT_DEVICE_MAPPING = {
|
|
boot_devices.PXE: 'network',
|
|
boot_devices.DISK: 'hd',
|
|
boot_devices.CDROM: 'cdrom',
|
|
}
|
|
OVIRT_TO_IRONIC_DEVICE_MAPPING = {v: k for k, v in
|
|
IRONIC_TO_OVIRT_DEVICE_MAPPING.items()}
|
|
|
|
OVIRT_TO_IRONIC_POWER_MAPPING = {
|
|
'down': states.POWER_OFF,
|
|
'error': states.ERROR,
|
|
'image_locked': states.POWER_OFF,
|
|
'migrating': states.POWER_ON,
|
|
'not_responding': states.ERROR,
|
|
'paused': states.ERROR,
|
|
'powering_down': states.POWER_OFF,
|
|
'powering_up': states.POWER_ON,
|
|
'reboot_in_progress': states.POWER_ON,
|
|
'wait_for_launch': states.POWER_ON,
|
|
'up': states.POWER_ON
|
|
}
|
|
|
|
opts = [
|
|
cfg.StrOpt('address',
|
|
default='127.0.0.1',
|
|
help='oVirt address'),
|
|
cfg.StrOpt('username',
|
|
default='admin@internal',
|
|
help='oVirt username'),
|
|
cfg.StrOpt('password',
|
|
help='oVirt password'),
|
|
cfg.StrOpt('insecure',
|
|
default=False,
|
|
help='Skips verification of the oVirt host certificate'),
|
|
cfg.StrOpt('ca_file',
|
|
help='oVirt path to a CA file'),
|
|
]
|
|
CONF = cfg.CONF
|
|
CONF.register_opts(opts, group='ovirt')
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
|
|
PROPERTIES = {
|
|
'ovirt_address': _("Address of the oVirt Manager"),
|
|
'ovirt_username': _("oVirt username"),
|
|
'ovirt_password': _("oVirt password"),
|
|
'ovirt_insecure': _("Skips oVirt host certificate's verification"),
|
|
'ovirt_ca_file': _("oVirt path to a CA file"),
|
|
'ovirt_vm_name': _("Name of the VM in oVirt. Required."),
|
|
}
|
|
|
|
|
|
def _parse_driver_info(node):
|
|
"""Gets the driver specific node driver info.
|
|
|
|
This method validates whether the 'driver_info' property of the
|
|
supplied node contains the required information for this driver.
|
|
|
|
:param node: an Ironic Node object.
|
|
:returns: a dict containing information from driver_info (or where
|
|
applicable, config values).
|
|
:raises: MissingParameterValue, if some required parameter(s) are missing
|
|
in the node's driver_info.
|
|
:raises: InvalidParameterValue, if some parameter(s) have invalid value(s)
|
|
in the node's driver_info.
|
|
"""
|
|
conf_info = {attr: getattr(CONF.ovirt, attr) for attr in CONF.ovirt}
|
|
node_info = node.driver_info or {}
|
|
driver_info = {}
|
|
for prop in PROPERTIES:
|
|
node_value = node_info.get(prop)
|
|
conf_value = conf_info.get(prop.replace('ovirt_', ''))
|
|
value = node_value if node_value is not None else conf_value
|
|
if value is None and prop not in ['ovirt_ca_file', 'ovirt_insecure']:
|
|
msg = _("%s is not set in either the configuration or "
|
|
"in the node's driver_info") % prop
|
|
raise exception.MissingParameterValue(msg)
|
|
else:
|
|
driver_info[prop] = value
|
|
insecure = driver_info['ovirt_insecure']
|
|
ovirt_ca_file = driver_info['ovirt_ca_file']
|
|
if not insecure and ovirt_ca_file is None:
|
|
msg = _("Missing ovirt_ca_file in the node's driver_info")
|
|
raise exception.MissingParameterValue(msg)
|
|
return driver_info
|
|
|
|
|
|
def _getvm(driver_info):
|
|
address = driver_info['ovirt_address']
|
|
username = driver_info['ovirt_username']
|
|
password = driver_info['ovirt_password']
|
|
insecure = driver_info['ovirt_insecure']
|
|
ca_file = driver_info['ovirt_ca_file']
|
|
name = driver_info['ovirt_vm_name'].encode('ascii', 'ignore')
|
|
url = "https://%s/ovirt-engine/api" % address
|
|
try:
|
|
connection = sdk.Connection(url=url, username=username,
|
|
password=password, insecure=insecure,
|
|
ca_file=ca_file)
|
|
vms_service = connection.system_service().vms_service()
|
|
vmsearch = vms_service.list(search='name=%s' % name)
|
|
except sdk.Error as e:
|
|
LOG.error("Could not fetch information about VM vm %(name)s, "
|
|
"got error: %(error)s", {'name': name, 'error': e})
|
|
raise staging_exception.oVirtError(err=e)
|
|
if vmsearch:
|
|
return vms_service.vm_service(vmsearch[0].id)
|
|
else:
|
|
raise staging_exception.oVirtError(_("VM with name "
|
|
"%s was not found") % name)
|
|
|
|
|
|
class oVirtPower(base.PowerInterface):
|
|
|
|
def get_properties(self):
|
|
return PROPERTIES
|
|
|
|
def validate(self, task):
|
|
"""Check if node.driver_info contains ovirt_vm_name.
|
|
|
|
:param task: a TaskManager instance.
|
|
:raises: MissingParameterValue, if some of the required parameters are
|
|
missing in the node's driver_info.
|
|
:raises: InvalidParameterValue, if some of the parameters have invalid
|
|
values in the node's driver_info.
|
|
"""
|
|
_parse_driver_info(task.node)
|
|
|
|
def get_power_state(self, task):
|
|
"""Gets the current power state.
|
|
|
|
:param task: a TaskManager instance.
|
|
:returns: one of :mod:`ironic.common.states`
|
|
:raises: MissingParameterValue, if some required parameter(s) are
|
|
missing in the node's driver_info.
|
|
:raises: InvalidParameterValue, if some parameter(s) have invalid
|
|
value(s) in the node's driver_info.
|
|
"""
|
|
driver_info = _parse_driver_info(task.node)
|
|
vm_name = driver_info['ovirt_vm_name']
|
|
vm = _getvm(driver_info)
|
|
status = vm.get().status.value
|
|
if status not in OVIRT_TO_IRONIC_POWER_MAPPING:
|
|
msg = ("oVirt returned unknown state for node %(node)s "
|
|
"and vm %(vm)s")
|
|
LOG.error(msg, {'node': task.node.uuid, 'vm': vm_name})
|
|
return states.ERROR
|
|
else:
|
|
return OVIRT_TO_IRONIC_POWER_MAPPING[status]
|
|
|
|
@task_manager.require_exclusive_lock
|
|
def set_power_state(self, task, target_state, timeout=None):
|
|
"""Turn the current power state on or off.
|
|
|
|
:param task: a TaskManager instance.
|
|
:param target_state: The desired power state POWER_ON, POWER_OFF or
|
|
REBOOT from :mod:`ironic.common.states`.
|
|
:raises: MissingParameterValue, if some required parameter(s) are
|
|
missing in the node's driver_info.
|
|
:raises: InvalidParameterValue, if some parameter(s) have invalid
|
|
value(s) in the node's driver_info OR if an invalid power state
|
|
was specified.
|
|
"""
|
|
driver_info = _parse_driver_info(task.node)
|
|
vm_name = driver_info['ovirt_vm_name']
|
|
vm = _getvm(driver_info)
|
|
try:
|
|
if target_state == states.POWER_OFF:
|
|
vm.stop()
|
|
elif target_state == states.POWER_ON:
|
|
vm.start()
|
|
elif target_state == states.REBOOT:
|
|
status = vm.get().status.value
|
|
if status == 'down':
|
|
vm.start()
|
|
else:
|
|
vm.reboot()
|
|
else:
|
|
msg = _("'set_power_state' called with invalid power "
|
|
"state '%s'") % target_state
|
|
raise exception.InvalidParameterValue(msg)
|
|
except sdk.Error as e:
|
|
LOG.error("Could not change status of VM vm %(name)s "
|
|
"got error: %(error)s", {'name': vm_name, 'error': e})
|
|
raise staging_exception.oVirtError(err=e)
|
|
|
|
@task_manager.require_exclusive_lock
|
|
def reboot(self, task, timeout=None):
|
|
"""Reboot the node.
|
|
|
|
:param task: a TaskManager instance.
|
|
:raises: MissingParameterValue, if some required parameter(s) are
|
|
missing in the node's driver_info.
|
|
:raises: InvalidParameterValue, if some parameter(s) have invalid
|
|
value(s) in the node's driver_info.
|
|
:raises: ovirtsdk4.Error, if error encountered from
|
|
oVirt operation.
|
|
"""
|
|
self.set_power_state(task, states.REBOOT, timeout=timeout)
|
|
|
|
|
|
class oVirtManagement(base.ManagementInterface):
|
|
|
|
def get_properties(self):
|
|
return PROPERTIES
|
|
|
|
def validate(self, task):
|
|
"""Check that 'driver_info' contains ovirt_vm_name.
|
|
|
|
Validates whether the 'driver_info' property of the supplied
|
|
task's node contains the required credentials information.
|
|
|
|
:param task: a task from TaskManager.
|
|
:raises: MissingParameterValue, if some required parameter(s) are
|
|
missing in the node's driver_info.
|
|
:raises: InvalidParameterValue, if some parameter(s) have invalid
|
|
value(s) in the node's driver_info.
|
|
"""
|
|
_parse_driver_info(task.node)
|
|
|
|
def get_supported_boot_devices(self, task):
|
|
"""Get a list of the supported boot devices.
|
|
|
|
:returns: A list with the supported boot devices defined
|
|
in :mod:`ironic.common.boot_devices`.
|
|
"""
|
|
return sorted(list(IRONIC_TO_OVIRT_DEVICE_MAPPING))
|
|
|
|
def get_boot_device(self, task):
|
|
"""Get the current boot device for a node.
|
|
|
|
:param task: a task from TaskManager.
|
|
:returns: a dictionary containing:
|
|
'boot_device': one of the ironic.common.boot_devices or None
|
|
'persistent': True if boot device is persistent, False otherwise
|
|
:raises: MissingParameterValue, if some required parameter(s) are
|
|
missing in the node's driver_info.
|
|
:raises: InvalidParameterValue, if some parameter(s) have invalid
|
|
value(s) in the node's driver_info.
|
|
:raises: oVirtError, if error encountered from
|
|
oVirt operation.
|
|
"""
|
|
driver_info = _parse_driver_info(task.node)
|
|
vm = _getvm(driver_info)
|
|
boot_dev = vm.os.boot[0].get_dev()
|
|
persistent = True
|
|
ironic_boot_dev = OVIRT_TO_IRONIC_DEVICE_MAPPING.get(boot_dev)
|
|
if not ironic_boot_dev:
|
|
persistent = False
|
|
msg = _("oVirt returned unknown boot device '%(device)s' "
|
|
"for node %(node)s")
|
|
LOG.error(msg, {'device': boot_dev, 'node': task.node.uuid})
|
|
raise staging_exception.oVirtError(msg.format(device=boot_dev,
|
|
node=task.node.uuid))
|
|
|
|
return {'boot_device': ironic_boot_dev, 'persistent': persistent}
|
|
|
|
@task_manager.require_exclusive_lock
|
|
def set_boot_device(self, task, device, persistent=False):
|
|
"""Set the boot device for a node.
|
|
|
|
:param task: a task from TaskManager.
|
|
:param device: ironic.common.boot_devices
|
|
:param persistent: This argument is ignored.
|
|
:raises: MissingParameterValue, if some required parameter(s) are
|
|
missing in the node's driver_info.
|
|
:raises: InvalidParameterValue, if some parameter(s) have invalid
|
|
value(s) in the node's driver_info.
|
|
"""
|
|
try:
|
|
boot_dev = IRONIC_TO_OVIRT_DEVICE_MAPPING[device]
|
|
except KeyError:
|
|
raise exception.InvalidParameterValue(_(
|
|
"Invalid boot device %s specified.") % device)
|
|
|
|
driver_info = _parse_driver_info(task.node)
|
|
vm = _getvm(driver_info)
|
|
try:
|
|
boot = otypes.Boot(devices=[otypes.BootDevice(boot_dev)])
|
|
bootos = otypes.OperatingSystem(boot=boot)
|
|
vm.update(otypes.Vm(os=bootos))
|
|
except sdk.Error as e:
|
|
LOG.error("Setting boot device failed for node %(node_id)s "
|
|
"with error: %(error)s",
|
|
{'node_id': task.node.uuid, 'error': e})
|
|
raise staging_exception.oVirtError(err=e)
|
|
|
|
def get_sensors_data(self, task):
|
|
"""Get sensors data.
|
|
|
|
:param task: a TaskManager instance.
|
|
:raises: FailedToGetSensorData when getting the sensor data fails.
|
|
:raises: FailedToParseSensorData when parsing sensor data fails.
|
|
:returns: returns a consistent format dict of sensor data grouped by
|
|
sensor type, which can be processed by Ceilometer.
|
|
"""
|
|
raise NotImplementedError()
|