PowerVM Driver: spawn/delete #1: no-ops

Initial change set introducing the PowerVM compute driver.  This change
set supplies the basic ComputeDriver methods to allow the n-cpu process
to start successfully; and no-op spawn & delete methods.

Subsequent change sets will build up to the functional spawn & delete
support found in https://review.openstack.org/#/c/391288

Change-Id: Ic45bb064f4315ea9e63698a7c0e541c5b0de5051
Partially-Implements: blueprint powervm-nova-compute-driver
This commit is contained in:
Eric Fried 2017-02-24 17:28:26 -06:00
parent 496bfb6287
commit 3cd409483d
7 changed files with 415 additions and 0 deletions

View File

View File

@ -0,0 +1,85 @@
# Copyright 2017 IBM Corp.
#
# 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 mock
from pypowervm.helpers import log_helper as pvm_hlp_log
from pypowervm.helpers import vios_busy as pvm_hlp_vbusy
from pypowervm.wrappers import managed_system as pvm_ms
from nova.compute import power_state
from nova import test
from nova.virt import hardware
from nova.virt.powervm import driver
class TestPowerVMDriver(test.NoDBTestCase):
def setUp(self):
super(TestPowerVMDriver, self).setUp()
self.drv = driver.PowerVMDriver('virtapi')
@mock.patch('pypowervm.adapter.Adapter', autospec=True)
@mock.patch('pypowervm.adapter.Session', autospec=True)
@mock.patch('pypowervm.tasks.partition.validate_vios_ready', autospec=True)
@mock.patch('pypowervm.wrappers.managed_system.System', autospec=True)
def test_init_host(self, mock_sys, mock_vvr, mock_sess, mock_adp):
mock_sys.get.return_value = ['host_wrapper']
self.drv.init_host('host')
mock_sess.assert_called_once_with(conn_tries=60)
mock_adp.assert_called_once_with(
mock_sess.return_value, helpers=[
pvm_hlp_log.log_helper, pvm_hlp_vbusy.vios_busy_retry_helper])
mock_vvr.assert_called_once_with(mock_adp.return_value)
self.assertEqual('host_wrapper', self.drv.host_wrapper)
def test_get_info(self):
info = self.drv.get_info('inst')
self.assertIsInstance(info, hardware.InstanceInfo)
self.assertEqual(power_state.NOSTATE, info.state)
def test_list_instances(self):
self.assertEqual([], self.drv.list_instances())
def test_get_available_nodes(self):
self.drv.host_wrapper = mock.create_autospec(pvm_ms.System,
instance=True)
self.assertEqual([self.drv.host_wrapper.mtms.mtms_str],
self.drv.get_available_nodes('node'))
@mock.patch('pypowervm.wrappers.managed_system.System', autospec=True)
@mock.patch('nova.virt.powervm.host.build_host_resource_from_ms')
def test_get_available_resource(self, mock_bhrfm, mock_sys):
mock_sys.get.return_value = ['sys']
self.drv.adapter = 'adap'
mock_bhrfm.return_value = {'foo': 'bar'}
self.assertEqual(
{'foo': 'bar', 'local_gb': 100000, 'local_gb_used': 10},
self.drv.get_available_resource('node'))
mock_sys.get.assert_called_once_with('adap')
mock_bhrfm.assert_called_once_with('sys')
self.assertEqual('sys', self.drv.host_wrapper)
def test_spawn(self):
# TODO(efried): Real UT once spawn is implemented.
inst = mock.Mock()
self.drv.spawn('ctx', inst, 'img_meta', 'inj_files', 'admin_pass')
self.drv.spawn('ctx', inst, 'img_meta', 'inj_files', 'admin_pass',
network_info='net_info', block_device_info='bdm')
def test_destroy(self):
# TODO(efried): Real UT once destroy is implemented.
inst = mock.Mock()
self.drv.destroy('ctx', inst, 'net_info')
self.drv.destroy('ctx', inst, 'net_info', block_device_info='bdm',
destroy_disks=False, migrate_data='mig_data')

View File

@ -0,0 +1,65 @@
# Copyright 2016 IBM Corp.
#
# 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 mock
from pypowervm.wrappers import managed_system as pvm_ms
from pypowervm.wrappers import mtms as pvm_mtms
from nova import test
from nova.virt.powervm import host as pvm_host
class TestPowerVMHost(test.NoDBTestCase):
def test_host_resources(self):
# Create objects to test with
ms_wrapper = mock.create_autospec(pvm_ms.System, spec_set=True)
mtms = mock.create_autospec(pvm_mtms.MTMS, spec_set=True)
mtms.configure_mock(mtms_str='8484923A123456')
asio = mock.create_autospec(pvm_ms.ASIOConfig, spec_set=True)
ms_wrapper.configure_mock(
proc_units_configurable=500,
proc_units_avail=500,
memory_configurable=5242880,
memory_free=5242752,
mtms=mtms,
memory_region_size='big',
asio_config=asio)
# Run the actual test
stats = pvm_host.build_host_resource_from_ms(ms_wrapper)
self.assertIsNotNone(stats)
# Check for the presence of fields
fields = (('vcpus', 500), ('vcpus_used', 0),
('memory_mb', 5242880), ('memory_mb_used', 128),
'hypervisor_type', 'hypervisor_version',
'hypervisor_hostname', 'cpu_info',
'supported_instances', 'stats')
for fld in fields:
if isinstance(fld, tuple):
value = stats.get(fld[0], None)
self.assertEqual(value, fld[1])
else:
value = stats.get(fld, None)
self.assertIsNotNone(value)
# Check for individual stats
hstats = (('proc_units', '500.00'), ('proc_units_used', '0.00'))
for stat in hstats:
if isinstance(stat, tuple):
value = stats['stats'].get(stat[0], None)
self.assertEqual(value, stat[1])
else:
value = stats['stats'].get(stat, None)
self.assertIsNotNone(value)

View File

@ -0,0 +1,17 @@
# Copyright 2017 IBM Corp.
#
# 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.
from nova.virt.powervm import driver
PowerVMDriver = driver.PowerVMDriver

182
nova/virt/powervm/driver.py Normal file
View File

@ -0,0 +1,182 @@
# Copyright 2014, 2017 IBM Corp.
#
# 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.
"""Connection to PowerVM hypervisor through NovaLink."""
from oslo_log import log as logging
from pypowervm import adapter as pvm_apt
from pypowervm.helpers import log_helper as log_hlp
from pypowervm.helpers import vios_busy as vio_hlp
from pypowervm.tasks import partition as pvm_par
from pypowervm.wrappers import managed_system as pvm_ms
from nova.compute import power_state
from nova.virt import driver
from nova.virt import hardware
from nova.virt.powervm import host
LOG = logging.getLogger(__name__)
class PowerVMDriver(driver.ComputeDriver):
"""PowerVM NovaLink Implementation of Compute Driver.
https://wiki.openstack.org/wiki/PowerVM
"""
def __init__(self, virtapi):
super(PowerVMDriver, self).__init__(virtapi)
def init_host(self, host):
"""Initialize anything that is necessary for the driver to function.
Includes catching up with currently running VMs on the given host.
"""
# Build the adapter. May need to attempt the connection multiple times
# in case the PowerVM management API service is starting.
# TODO(efried): Implement async compute service enable/disable like
# I73a34eb6e0ca32d03e54d12a5e066b2ed4f19a61
self.adapter = pvm_apt.Adapter(
pvm_apt.Session(conn_tries=60),
helpers=[log_hlp.log_helper, vio_hlp.vios_busy_retry_helper])
# Make sure the Virtual I/O Server(s) are available.
pvm_par.validate_vios_ready(self.adapter)
self.host_wrapper = pvm_ms.System.get(self.adapter)[0]
LOG.info("The PowerVM compute driver has been initialized.")
@staticmethod
def _log_operation(op, instance):
"""Log entry point of driver operations."""
LOG.info('Operation: %(op)s. Virtual machine display name: '
'%(display_name)s, name: %(name)s',
{'op': op, 'display_name': instance.display_name,
'name': instance.name}, instance=instance)
def get_info(self, instance):
"""Get the current status of an instance, by name (not ID!)
:param instance: nova.objects.instance.Instance object
Returns a InstanceInfo object containing:
:state: the running state, one of the power_state codes
:max_mem_kb: (int) the maximum memory in KBytes allowed
:mem_kb: (int) the memory in KBytes used by the domain
:num_cpu: (int) the number of virtual CPUs for the domain
:cpu_time_ns: (int) the CPU time used in nanoseconds
:id: a unique ID for the instance
"""
# TODO(efried): Implement
return hardware.InstanceInfo(state=power_state.NOSTATE)
def list_instances(self):
"""Return the names of all the instances known to the virt host.
:return: VM Names as a list.
"""
# TODO(efried): Get the LPAR names
return []
def get_available_nodes(self, refresh=False):
"""Returns nodenames of all nodes managed by the compute service.
This method is for multi compute-nodes support. If a driver supports
multi compute-nodes, this method returns a list of nodenames managed
by the service. Otherwise, this method should return
[hypervisor_hostname].
"""
return [self.host_wrapper.mtms.mtms_str]
def get_available_resource(self, nodename):
"""Retrieve resource information.
This method is called when nova-compute launches, and as part of a
periodic task.
:param nodename: Node from which the caller wants to get resources.
A driver that manages only one node can safely ignore
this.
:return: Dictionary describing resources.
"""
# TODO(efried): Switch to get_inventory, per blueprint
# custom-resource-classes-pike
# Do this here so it refreshes each time this method is called.
self.host_wrapper = pvm_ms.System.get(self.adapter)[0]
# Get host information
data = host.build_host_resource_from_ms(self.host_wrapper)
# Add the disk information
# TODO(efried): Get real stats when disk support is added.
data["local_gb"] = 100000
data["local_gb_used"] = 10
return data
def spawn(self, context, instance, image_meta, injected_files,
admin_password, network_info=None, block_device_info=None):
"""Create a new instance/VM/domain on the virtualization platform.
Once this successfully completes, the instance should be
running (power_state.RUNNING).
If this fails, any partial instance should be completely
cleaned up, and the virtualization platform should be in the state
that it was before this call began.
:param context: security context
:param instance: nova.objects.instance.Instance
This function should use the data there to guide
the creation of the new instance.
:param nova.objects.ImageMeta image_meta:
The metadata of the image of the instance.
:param injected_files: User files to inject into instance.
:param admin_password: Administrator password to set in instance.
:param network_info: instance network information
:param block_device_info: Information about block devices to be
attached to the instance.
"""
self._log_operation('spawn', instance)
# TODO(efried): Take flavor extra specs into account
# TODO(efried): Use TaskFlow
# TODO(efried): Create the LPAR
# TODO(thorst, efried) Plug the VIFs
# TODO(thorst, efried) Create/Connect the disk
# TODO(thorst, efried) Add the config drive
# Last step is to power on the system.
# TODO(efried): Power on the LPAR
def destroy(self, context, instance, network_info, block_device_info=None,
destroy_disks=True, migrate_data=None):
"""Destroy the specified instance from the Hypervisor.
If the instance is not found (for example if networking failed), this
function should still succeed. It's probably a good idea to log a
warning in that case.
:param context: security context
:param instance: Instance object as returned by DB layer.
:param network_info: instance network information
:param block_device_info: Information about block devices that should
be detached from the instance.
:param destroy_disks: Indicates if disks should be destroyed
:param migrate_data: implementation specific params
"""
# TODO(thorst, efried) Add resize checks for destroy
self._log_operation('destroy', instance)
# TODO(efried): Use TaskFlow
# TODO(efried): Power off the LPAR
# TODO(thorst, efried) Add unplug vifs task
# TODO(thorst, efried) Add config drive tasks
# TODO(thorst, efried) Add volume disconnect tasks
# TODO(thorst, efried) Add disk disconnect/destroy tasks
# TODO(thorst, efried) Add LPAR id based scsi map clean up task
# TODO(efried): Delete the LPAR

65
nova/virt/powervm/host.py Normal file
View File

@ -0,0 +1,65 @@
# Copyright 2014, 2017 IBM Corp.
#
# 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 math
from oslo_log import log as logging
from oslo_serialization import jsonutils
from nova.objects import fields
LOG = logging.getLogger(__name__)
# Power VM hypervisor info
# Normally, the hypervisor version is a string in the form of '8.0.0' and
# converted to an int with nova.virt.utils.convert_version_to_int() however
# there isn't currently a mechanism to retrieve the exact version.
# Complicating this is the fact that nova conductor only allows live migration
# from the source host to the destination if the source is equal to or less
# than the destination version. PowerVM live migration limitations are
# checked by the PowerVM capabilities flags and not specific version levels.
# For that reason, we'll just publish the major level.
IBM_POWERVM_HYPERVISOR_VERSION = 8
# The types of LPARS that are supported.
POWERVM_SUPPORTED_INSTANCES = [
(fields.Architecture.PPC64, fields.HVType.PHYP, fields.VMMode.HVM),
(fields.Architecture.PPC64LE, fields.HVType.PHYP, fields.VMMode.HVM)]
def build_host_resource_from_ms(ms_w):
"""Build the host resource dict from a ManagedSystem PowerVM wrapper.
:param ms_w: The pypowervm System wrapper describing the managed system.
"""
data = {}
# Calculate the vcpus
proc_units = ms_w.proc_units_configurable
pu_used = float(proc_units) - float(ms_w.proc_units_avail)
data['vcpus'] = int(math.ceil(float(proc_units)))
data['vcpus_used'] = int(math.ceil(pu_used))
data['memory_mb'] = ms_w.memory_configurable
data['memory_mb_used'] = (ms_w.memory_configurable -
ms_w.memory_free)
data["hypervisor_type"] = fields.HVType.PHYP
data["hypervisor_version"] = IBM_POWERVM_HYPERVISOR_VERSION
data["hypervisor_hostname"] = ms_w.mtms.mtms_str
data["cpu_info"] = jsonutils.dumps({'vendor': 'ibm', 'arch': 'ppc64'})
data["numa_topology"] = None
data["supported_instances"] = POWERVM_SUPPORTED_INSTANCES
stats = {'proc_units': '%.2f' % float(proc_units),
'proc_units_used': '%.2f' % pu_used,
'memory_region_size': ms_w.memory_region_size}
data["stats"] = stats
return data

View File

@ -61,3 +61,4 @@ microversion-parse>=0.1.2 # Apache-2.0
os-xenapi>=0.1.1 # Apache-2.0
tooz>=1.47.0 # Apache-2.0
cursive>=0.1.2 # Apache-2.0
pypowervm>=1.1.1 # Apache-2.0