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:
parent
496bfb6287
commit
3cd409483d
|
@ -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')
|
|
@ -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)
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue