Merge "PowerVM Driver: spawn/destroy #2: functional"

This commit is contained in:
Jenkins 2017-04-20 23:40:45 +00:00 committed by Gerrit Code Review
commit 3cc34c2aa3
6 changed files with 852 additions and 47 deletions

View File

@ -2138,6 +2138,11 @@ class BadRequirementEmulatorThreadsPolicy(Invalid):
"CPU policy option.")
class PowerVMAPIFailed(NovaException):
msg_fmt = _("PowerVM API failed to complete for instance=%(inst_name)s. "
"%(reason)s")
class TraitNotFound(NotFound):
msg_fmt = _("No such trait %(name)s.")

View File

@ -0,0 +1,45 @@
# 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.
#
from nova.compute import power_state
from nova.compute import vm_states
from nova import objects
from nova.tests import uuidsentinel
TEST_FLAVOR = objects.flavor.Flavor(
memory_mb=2048,
swap=0,
vcpu_weight=None,
root_gb=10, id=2,
name=u'm1.small',
ephemeral_gb=0,
rxtx_factor=1.0,
flavorid=uuidsentinel.flav_id,
vcpus=1)
TEST_INSTANCE = objects.Instance(
id=1,
uuid=uuidsentinel.inst_id,
display_name='Fake Instance',
root_gb=10,
ephemeral_gb=0,
instance_type_id=TEST_FLAVOR.id,
system_metadata={'image_os_distro': 'rhel'},
host='host1',
flavor=TEST_FLAVOR,
task_state=None,
vm_state=vm_states.STOPPED,
power_state=power_state.SHUTDOWN,
)

View File

@ -1,4 +1,4 @@
# Copyright 2017 IBM Corp.
# Copyright 2016, 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
@ -12,13 +12,18 @@
# License for the specific language governing permissions and limitations
# under the License.
from __future__ import absolute_import
import fixtures
import mock
from pypowervm import exceptions as pvm_exc
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 exception
from nova import test
from nova.tests.unit.virt import powervm
from nova.virt import hardware
from nova.virt.powervm import driver
@ -28,28 +33,40 @@ class TestPowerVMDriver(test.NoDBTestCase):
def setUp(self):
super(TestPowerVMDriver, self).setUp()
self.drv = driver.PowerVMDriver('virtapi')
self.adp = self.useFixture(fixtures.MockPatch(
'pypowervm.adapter.Adapter', autospec=True)).mock
self.drv.adapter = self.adp
self.sess = self.useFixture(fixtures.MockPatch(
'pypowervm.adapter.Session', autospec=True)).mock
# Create an instance to test with
self.inst = powervm.TEST_INSTANCE
@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']
@mock.patch('pypowervm.tasks.partition.validate_vios_ready', autospec=True)
def test_init_host(self, mock_vvr, mock_sys):
mock_sys.get.return_value = ['sys']
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=[
self.sess.assert_called_once_with(conn_tries=60)
self.adp.assert_called_once_with(
self.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)
mock_vvr.assert_called_once_with(self.adp.return_value)
self.assertEqual('sys', self.drv.host_wrapper)
def test_get_info(self):
@mock.patch('nova.virt.powervm.vm.get_pvm_uuid')
def test_get_info(self, mock_uuid):
mock_uuid.return_value = 'uuid'
info = self.drv.get_info('inst')
self.assertIsInstance(info, hardware.InstanceInfo)
self.assertEqual(power_state.NOSTATE, info.state)
self.assertEqual('uuid', info.id)
mock_uuid.assert_called_once_with('inst')
def test_list_instances(self):
self.assertEqual([], self.drv.list_instances())
@mock.patch('nova.virt.powervm.vm.get_lpar_names')
def test_list_instances(self, mock_names):
mock_names.return_value = ['one', 'two', 'three']
self.assertEqual(['one', 'two', 'three'], self.drv.list_instances())
mock_names.assert_called_once_with(self.adp)
def test_get_available_nodes(self):
self.drv.host_wrapper = mock.create_autospec(pvm_ms.System,
@ -61,25 +78,59 @@ class TestPowerVMDriver(test.NoDBTestCase):
@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_sys.get.assert_called_once_with(self.adp)
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')
@mock.patch('nova.virt.powervm.vm.create_lpar')
@mock.patch('nova.virt.powervm.vm.power_on')
def test_spawn_ops(self, mock_pwron, mock_crt_lpar):
"""Validates the 'typical' spawn flow of the spawn of an instance. """
self.drv.host_wrapper = 'sys'
self.drv.spawn('context', self.inst, 'img_meta', 'files', 'password')
mock_crt_lpar.assert_called_once_with(self.adp, 'sys', self.inst)
mock_pwron.assert_called_once_with(self.adp, self.inst)
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')
@mock.patch('nova.virt.powervm.vm.delete_lpar')
@mock.patch('nova.virt.powervm.vm.power_off')
def test_destroy(self, mock_pwroff, mock_del):
"""Validates PowerVM destroy."""
# Good path
self.drv.destroy('context', self.inst, [], block_device_info={})
mock_pwroff.assert_called_once_with(
self.adp, self.inst, force_immediate=True)
mock_del.assert_called_once_with(self.adp, self.inst)
mock_pwroff.reset_mock()
mock_del.reset_mock()
# InstanceNotFound exception, non-forced
mock_pwroff.side_effect = exception.InstanceNotFound
self.drv.destroy('context', self.inst, [], block_device_info={},
destroy_disks=False)
mock_pwroff.assert_called_once_with(
self.adp, self.inst, force_immediate=False)
mock_del.assert_not_called()
mock_pwroff.reset_mock()
mock_pwroff.side_effect = None
# Convertible (PowerVM) exception
mock_del.side_effect = pvm_exc.TimeoutError("Timed out")
self.assertRaises(exception.InstanceTerminationFailure,
self.drv.destroy, 'context', self.inst, [],
block_device_info={})
# Everything got called
mock_pwroff.assert_called_once_with(
self.adp, self.inst, force_immediate=True)
mock_del.assert_called_once_with(self.adp, self.inst)
# Other random exception raises directly
mock_del.side_effect = ValueError()
self.assertRaises(ValueError,
self.drv.destroy, 'context', self.inst, [],
block_device_info={})

View File

@ -0,0 +1,369 @@
# 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.
from __future__ import absolute_import
import fixtures
import mock
from pypowervm import exceptions as pvm_exc
from pypowervm.helpers import log_helper as pvm_log
from pypowervm.tasks import power
from pypowervm.tests import test_fixtures as pvm_fx
from pypowervm.utils import lpar_builder as lpar_bld
from pypowervm.wrappers import base_partition as pvm_bp
from pypowervm.wrappers import logical_partition as pvm_lpar
from nova.compute import power_state
from nova import exception
from nova import test
from nova.tests.unit.virt import powervm
from nova.virt.powervm import vm
class TestVM(test.NoDBTestCase):
def setUp(self):
super(TestVM, self).setUp()
self.apt = self.useFixture(pvm_fx.AdapterFx()).adpt
mock_entries = [mock.Mock(), mock.Mock()]
self.resp = mock.MagicMock()
self.resp.feed = mock.MagicMock(entries=mock_entries)
self.get_pvm_uuid = self.useFixture(fixtures.MockPatch(
'nova.virt.powervm.vm.get_pvm_uuid')).mock
self.inst = powervm.TEST_INSTANCE
@mock.patch('pypowervm.utils.lpar_builder.DefaultStandardize',
autospec=True)
@mock.patch('pypowervm.util.sanitize_partition_name_for_api',
autospec=True)
@mock.patch('nova.virt.powervm.vm.get_pvm_uuid')
@mock.patch('pypowervm.utils.lpar_builder.LPARBuilder', autospec=True)
def test_vm_builder(self, mock_lpar_bldr, mock_uuid2pvm, mock_san,
mock_def_stdz):
inst = mock.Mock()
inst.configure_mock(name='lpar_name', uuid='lpar_uuid',
flavor=mock.Mock(memory_mb='mem', vcpus='vcpus'))
vmb = vm.VMBuilder('host', 'adap')
mock_def_stdz.assert_called_once_with('host')
self.assertEqual(mock_lpar_bldr.return_value,
vmb.lpar_builder(inst))
mock_san.assert_called_once_with('lpar_name')
mock_uuid2pvm.assert_called_once_with(inst)
mock_lpar_bldr.assert_called_once_with(
'adap', {'name': mock_san.return_value,
'uuid': mock_uuid2pvm.return_value,
'memory': 'mem',
'vcpu': 'vcpus'}, mock_def_stdz.return_value)
def test_translate_vm_state(self):
self.assertEqual(power_state.RUNNING,
vm._translate_vm_state('running'))
self.assertEqual(power_state.RUNNING,
vm._translate_vm_state('migrating running'))
self.assertEqual(power_state.RUNNING,
vm._translate_vm_state('starting'))
self.assertEqual(power_state.RUNNING,
vm._translate_vm_state('open firmware'))
self.assertEqual(power_state.RUNNING,
vm._translate_vm_state('shutting down'))
self.assertEqual(power_state.RUNNING,
vm._translate_vm_state('suspending'))
self.assertEqual(power_state.SHUTDOWN,
vm._translate_vm_state('migrating not active'))
self.assertEqual(power_state.SHUTDOWN,
vm._translate_vm_state('not activated'))
self.assertEqual(power_state.NOSTATE,
vm._translate_vm_state('unknown'))
self.assertEqual(power_state.NOSTATE,
vm._translate_vm_state('hardware discovery'))
self.assertEqual(power_state.NOSTATE,
vm._translate_vm_state('not available'))
self.assertEqual(power_state.SUSPENDED,
vm._translate_vm_state('resuming'))
self.assertEqual(power_state.SUSPENDED,
vm._translate_vm_state('suspended'))
self.assertEqual(power_state.CRASHED,
vm._translate_vm_state('error'))
def test_instance_info(self):
inst_info = vm.InstanceInfo(self.apt, 'inst')
self.get_pvm_uuid.assert_called_once_with('inst')
# Test the static properties
self.assertEqual(inst_info.id, self.get_pvm_uuid.return_value)
# Check that we raise an exception if the instance is gone.
self.apt.read.side_effect = pvm_exc.HttpError(
mock.MagicMock(status=404))
self.assertRaises(exception.InstanceNotFound,
inst_info.__getattribute__, 'state')
# Reset the test inst_info
inst_info = vm.InstanceInfo(self.apt, 'inst')
class FakeResp2(object):
def __init__(self, body):
self.body = '"%s"' % body
resp = FakeResp2('running')
def return_resp(*args, **kwds):
return resp
self.apt.read.side_effect = return_resp
self.assertEqual(inst_info.state, power_state.RUNNING)
# Check the __eq__ method
self.get_pvm_uuid.return_value = 'pvm-uuid1'
inst_info1 = vm.InstanceInfo(self.apt, 'inst')
inst_info2 = vm.InstanceInfo(self.apt, 'inst')
self.assertEqual(inst_info1, inst_info2)
self.get_pvm_uuid.return_value = 'pvm-uuid2'
inst_info2 = vm.InstanceInfo(self.apt, 'inst2')
self.assertNotEqual(inst_info1, inst_info2)
@mock.patch('pypowervm.wrappers.logical_partition.LPAR', autospec=True)
def test_get_lpar_names(self, mock_lpar):
inst1 = mock.Mock()
inst1.configure_mock(name='inst1')
inst2 = mock.Mock()
inst2.configure_mock(name='inst2')
mock_lpar.search.return_value = [inst1, inst2]
self.assertEqual({'inst1', 'inst2'}, set(vm.get_lpar_names('adap')))
mock_lpar.search.assert_called_once_with(
'adap', is_mgmt_partition=False)
@mock.patch('pypowervm.tasks.vterm.close_vterm', autospec=True)
def test_dlt_lpar(self, mock_vterm):
"""Performs a delete LPAR test."""
vm.delete_lpar(self.apt, 'inst')
self.get_pvm_uuid.assert_called_once_with('inst')
self.apt.delete.assert_called_once_with(
pvm_lpar.LPAR.schema_type, root_id=self.get_pvm_uuid.return_value)
self.assertEqual(1, mock_vterm.call_count)
# Test Failure Path
# build a mock response body with the expected HSCL msg
resp = mock.Mock()
resp.body = 'error msg: HSCL151B more text'
self.apt.delete.side_effect = pvm_exc.Error(
'Mock Error Message', response=resp)
# Reset counters
self.apt.reset_mock()
mock_vterm.reset_mock()
self.assertRaises(pvm_exc.Error, vm.delete_lpar, self.apt, 'inst')
self.assertEqual(1, mock_vterm.call_count)
self.assertEqual(1, self.apt.delete.call_count)
self.apt.reset_mock()
mock_vterm.reset_mock()
# Test HttpError 404
resp.status = 404
self.apt.delete.side_effect = pvm_exc.HttpError(resp=resp)
vm.delete_lpar(self.apt, 'inst')
self.assertEqual(1, mock_vterm.call_count)
self.assertEqual(1, self.apt.delete.call_count)
self.apt.reset_mock()
mock_vterm.reset_mock()
# Test Other HttpError
resp.status = 111
self.apt.delete.side_effect = pvm_exc.HttpError(resp=resp)
self.assertRaises(pvm_exc.HttpError, vm.delete_lpar, self.apt, 'inst')
self.assertEqual(1, mock_vterm.call_count)
self.assertEqual(1, self.apt.delete.call_count)
self.apt.reset_mock()
mock_vterm.reset_mock()
# Test HttpError 404 closing vterm
resp.status = 404
mock_vterm.side_effect = pvm_exc.HttpError(resp=resp)
vm.delete_lpar(self.apt, 'inst')
self.assertEqual(1, mock_vterm.call_count)
self.assertEqual(0, self.apt.delete.call_count)
self.apt.reset_mock()
mock_vterm.reset_mock()
# Test Other HttpError closing vterm
resp.status = 111
mock_vterm.side_effect = pvm_exc.HttpError(resp=resp)
self.assertRaises(pvm_exc.HttpError, vm.delete_lpar, self.apt, 'inst')
self.assertEqual(1, mock_vterm.call_count)
self.assertEqual(0, self.apt.delete.call_count)
@mock.patch('nova.virt.powervm.vm.VMBuilder', autospec=True)
@mock.patch('pypowervm.utils.validation.LPARWrapperValidator',
autospec=True)
def test_crt_lpar(self, mock_vld, mock_vmbldr):
mock_bldr = mock.Mock(spec=lpar_bld.LPARBuilder)
mock_vmbldr.return_value.lpar_builder.return_value = mock_bldr
mock_pend_lpar = mock.create_autospec(pvm_lpar.LPAR, instance=True)
mock_bldr.build.return_value = mock_pend_lpar
vm.create_lpar(self.apt, 'host', self.inst)
mock_vmbldr.assert_called_once_with('host', self.apt)
mock_vmbldr.return_value.lpar_builder.assert_called_once_with(
self.inst)
mock_bldr.build.assert_called_once_with()
mock_vld.assert_called_once_with(mock_pend_lpar, 'host')
mock_vld.return_value.validate_all.assert_called_once_with()
mock_pend_lpar.create.assert_called_once_with(parent='host')
@mock.patch('pypowervm.wrappers.logical_partition.LPAR', autospec=True)
def test_get_instance_wrapper(self, mock_lpar):
resp = mock.Mock(status=404)
mock_lpar.get.side_effect = pvm_exc.Error('message', response=resp)
# vm.get_instance_wrapper(self.apt, instance, 'lpar_uuid')
self.assertRaises(exception.InstanceNotFound, vm.get_instance_wrapper,
self.apt, self.inst)
@mock.patch('pypowervm.tasks.power.power_on', autospec=True)
@mock.patch('oslo_concurrency.lockutils.lock', autospec=True)
@mock.patch('nova.virt.powervm.vm.get_instance_wrapper')
def test_power_on(self, mock_wrap, mock_lock, mock_power_on):
entry = mock.Mock(state=pvm_bp.LPARState.NOT_ACTIVATED)
mock_wrap.return_value = entry
vm.power_on(None, self.inst)
mock_power_on.assert_called_once_with(entry, None)
mock_lock.assert_called_once_with('power_%s' % self.inst.uuid)
mock_power_on.reset_mock()
mock_lock.reset_mock()
stop_states = [
pvm_bp.LPARState.RUNNING, pvm_bp.LPARState.STARTING,
pvm_bp.LPARState.OPEN_FIRMWARE, pvm_bp.LPARState.SHUTTING_DOWN,
pvm_bp.LPARState.ERROR, pvm_bp.LPARState.RESUMING,
pvm_bp.LPARState.SUSPENDING]
for stop_state in stop_states:
entry.state = stop_state
vm.power_on(None, self.inst)
mock_lock.assert_called_once_with('power_%s' % self.inst.uuid)
mock_lock.reset_mock()
self.assertEqual(0, mock_power_on.call_count)
@mock.patch('pypowervm.tasks.power.power_on', autospec=True)
@mock.patch('nova.virt.powervm.vm.get_instance_wrapper')
def test_power_on_negative(self, mock_wrp, mock_power_on):
mock_wrp.return_value = mock.Mock(state=pvm_bp.LPARState.NOT_ACTIVATED)
# Convertible (PowerVM) exception
mock_power_on.side_effect = pvm_exc.VMPowerOnFailure(
reason='Something bad', lpar_nm='TheLPAR')
self.assertRaises(exception.InstancePowerOnFailure,
vm.power_on, None, self.inst)
# Non-pvm error raises directly
mock_power_on.side_effect = ValueError()
self.assertRaises(ValueError, vm.power_on, None, self.inst)
@mock.patch('pypowervm.tasks.power.power_off', autospec=True)
@mock.patch('oslo_concurrency.lockutils.lock', autospec=True)
@mock.patch('nova.virt.powervm.vm.get_instance_wrapper')
def test_power_off(self, mock_wrap, mock_lock, mock_power_off):
entry = mock.Mock(state=pvm_bp.LPARState.NOT_ACTIVATED)
mock_wrap.return_value = entry
vm.power_off(None, self.inst)
self.assertEqual(0, mock_power_off.call_count)
mock_lock.assert_called_once_with('power_%s' % self.inst.uuid)
stop_states = [
pvm_bp.LPARState.RUNNING, pvm_bp.LPARState.STARTING,
pvm_bp.LPARState.OPEN_FIRMWARE, pvm_bp.LPARState.SHUTTING_DOWN,
pvm_bp.LPARState.ERROR, pvm_bp.LPARState.RESUMING,
pvm_bp.LPARState.SUSPENDING]
for stop_state in stop_states:
entry.state = stop_state
mock_power_off.reset_mock()
mock_lock.reset_mock()
vm.power_off(None, self.inst)
mock_power_off.assert_called_once_with(
entry, None, force_immediate=power.Force.ON_FAILURE)
mock_lock.assert_called_once_with('power_%s' % self.inst.uuid)
mock_power_off.reset_mock()
mock_lock.reset_mock()
vm.power_off(None, self.inst, force_immediate=True)
mock_power_off.assert_called_once_with(entry, None,
force_immediate=True)
mock_lock.assert_called_once_with('power_%s' % self.inst.uuid)
@mock.patch('pypowervm.tasks.power.power_off', autospec=True)
@mock.patch('nova.virt.powervm.vm.get_instance_wrapper')
def test_power_off_negative(self, mock_wrap, mock_power_off):
"""Negative tests."""
mock_wrap.return_value = mock.Mock(state=pvm_bp.LPARState.RUNNING)
# Raise the expected pypowervm exception
mock_power_off.side_effect = pvm_exc.VMPowerOffFailure(
reason='Something bad.', lpar_nm='TheLPAR')
# We should get a valid Nova exception that the compute manager expects
self.assertRaises(exception.InstancePowerOffFailure,
vm.power_off, None, self.inst)
# Non-pvm error raises directly
mock_power_off.side_effect = ValueError()
self.assertRaises(ValueError, vm.power_off, None, self.inst)
@mock.patch('oslo_serialization.jsonutils.loads')
def test_get_vm_qp(self, mock_loads):
self.apt.helpers = ['helper1', pvm_log.log_helper, 'helper3']
# Defaults
self.assertEqual(mock_loads.return_value,
vm.get_vm_qp(self.apt, 'lpar_uuid'))
self.apt.read.assert_called_once_with(
'LogicalPartition', root_id='lpar_uuid', suffix_type='quick',
suffix_parm=None)
mock_loads.assert_called_once_with(self.apt.read.return_value.body)
self.apt.read.reset_mock()
mock_loads.reset_mock()
# Specific qprop, no logging errors
self.assertEqual(mock_loads.return_value,
vm.get_vm_qp(self.apt, 'lpar_uuid', qprop='Prop',
log_errors=False))
self.apt.read.assert_called_once_with(
'LogicalPartition', root_id='lpar_uuid', suffix_type='quick',
suffix_parm='Prop', helpers=['helper1', 'helper3'])
resp = mock.MagicMock()
resp.status = 404
self.apt.read.side_effect = pvm_exc.HttpError(resp)
self.assertRaises(exception.InstanceNotFound, vm.get_vm_qp, self.apt,
'lpar_uuid', log_errors=False)
self.apt.read.side_effect = pvm_exc.Error("message", response=None)
self.assertRaises(pvm_exc.Error, vm.get_vm_qp, self.apt,
'lpar_uuid', log_errors=False)
resp.status = 500
self.apt.read.side_effect = pvm_exc.Error("message", response=resp)
self.assertRaises(pvm_exc.Error, vm.get_vm_qp, self.apt,
'lpar_uuid', log_errors=False)

View File

@ -15,15 +15,17 @@
from oslo_log import log as logging
from pypowervm import adapter as pvm_apt
from pypowervm import exceptions as pvm_exc
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
import six
from nova.compute import power_state
from nova import exception as exc
from nova.virt import driver
from nova.virt import hardware
from nova.virt.powervm import host
from nova.virt.powervm import vm
LOG = logging.getLogger(__name__)
@ -76,16 +78,14 @@ class PowerVMDriver(driver.ComputeDriver):
: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)
return vm.InstanceInfo(self.adapter, instance)
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 []
return vm.get_lpar_names(self.adapter)
def get_available_nodes(self, refresh=False):
"""Returns nodenames of all nodes managed by the compute service.
@ -145,14 +145,14 @@ class PowerVMDriver(driver.ComputeDriver):
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
vm.create_lpar(self.adapter, self.host_wrapper, instance)
# 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
vm.power_on(self.adapter, instance)
def destroy(self, context, instance, network_info, block_device_info=None,
destroy_disks=True, migrate_data=None):
@ -172,11 +172,20 @@ class PowerVMDriver(driver.ComputeDriver):
"""
# 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
try:
# TODO(efried): Use TaskFlow
vm.power_off(self.adapter, instance, force_immediate=destroy_disks)
# 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
vm.delete_lpar(self.adapter, instance)
except exc.InstanceNotFound:
LOG.debug('VM was not found during destroy operation.',
instance=instance)
return
except pvm_exc.Error as e:
LOG.exception("PowerVM error during destroy.", instance=instance)
# Convert to a Nova exception
raise exc.InstanceTerminationFailure(reason=six.text_type(e))

326
nova/virt/powervm/vm.py Normal file
View File

@ -0,0 +1,326 @@
# 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.
from oslo_concurrency import lockutils
from oslo_log import log as logging
from oslo_serialization import jsonutils
from oslo_utils import excutils
from pypowervm import exceptions as pvm_exc
from pypowervm.helpers import log_helper as pvm_log
from pypowervm.tasks import power
from pypowervm.tasks import vterm
from pypowervm import util as pvm_u
from pypowervm.utils import lpar_builder as lpar_bldr
from pypowervm.utils import uuid as pvm_uuid
from pypowervm.utils import validation as pvm_vldn
from pypowervm.wrappers import base_partition as pvm_bp
from pypowervm.wrappers import logical_partition as pvm_lpar
import six
from nova.compute import power_state
from nova import conf
from nova import exception as exc
from nova.virt import hardware
LOG = logging.getLogger(__name__)
CONF = conf.CONF
_POWERVM_STARTABLE_STATE = (pvm_bp.LPARState.NOT_ACTIVATED,)
_POWERVM_STOPPABLE_STATE = (
pvm_bp.LPARState.RUNNING, pvm_bp.LPARState.STARTING,
pvm_bp.LPARState.OPEN_FIRMWARE, pvm_bp.LPARState.SHUTTING_DOWN,
pvm_bp.LPARState.ERROR, pvm_bp.LPARState.RESUMING,
pvm_bp.LPARState.SUSPENDING)
_POWERVM_TO_NOVA_STATE = {
pvm_bp.LPARState.MIGRATING_RUNNING: power_state.RUNNING,
pvm_bp.LPARState.RUNNING: power_state.RUNNING,
pvm_bp.LPARState.STARTING: power_state.RUNNING,
# map open firmware state to active since it can be shut down
pvm_bp.LPARState.OPEN_FIRMWARE: power_state.RUNNING,
# It is running until it is off.
pvm_bp.LPARState.SHUTTING_DOWN: power_state.RUNNING,
# It is running until the suspend completes
pvm_bp.LPARState.SUSPENDING: power_state.RUNNING,
pvm_bp.LPARState.MIGRATING_NOT_ACTIVE: power_state.SHUTDOWN,
pvm_bp.LPARState.NOT_ACTIVATED: power_state.SHUTDOWN,
pvm_bp.LPARState.UNKNOWN: power_state.NOSTATE,
pvm_bp.LPARState.HARDWARE_DISCOVERY: power_state.NOSTATE,
pvm_bp.LPARState.NOT_AVAILBLE: power_state.NOSTATE,
# While resuming, we should be considered suspended still. Only once
# resumed will we be active (which is represented by the RUNNING state)
pvm_bp.LPARState.RESUMING: power_state.SUSPENDED,
pvm_bp.LPARState.SUSPENDED: power_state.SUSPENDED,
pvm_bp.LPARState.ERROR: power_state.CRASHED}
def get_lpar_names(adp):
"""Get a list of the LPAR names."""
return [x.name for x in pvm_lpar.LPAR.search(adp, is_mgmt_partition=False)]
def get_pvm_uuid(instance):
"""Get the corresponding PowerVM VM uuid of an instance uuid.
Maps a OpenStack instance uuid to a PowerVM uuid. The UUID between the
Nova instance and PowerVM will be 1 to 1 mapped. This method runs the
algorithm against the instance's uuid to convert it to the PowerVM
UUID.
:param instance: nova.objects.instance.Instance.
:return: The PowerVM UUID for the LPAR corresponding to the instance.
"""
return pvm_uuid.convert_uuid_to_pvm(instance.uuid).upper()
def get_instance_wrapper(adapter, instance):
"""Get the LPAR wrapper for a given Nova instance.
:param adapter: The adapter for the pypowervm API
:param instance: The nova instance.
:return: The pypowervm logical_partition wrapper.
"""
pvm_inst_uuid = get_pvm_uuid(instance)
try:
return pvm_lpar.LPAR.get(adapter, uuid=pvm_inst_uuid)
except pvm_exc.Error as e:
with excutils.save_and_reraise_exception(logger=LOG) as sare:
LOG.exception("Failed to retrieve LPAR associated with instance.",
instance=instance)
if e.response is not None and e.response.status == 404:
sare.reraise = False
raise exc.InstanceNotFound(instance_id=pvm_inst_uuid)
def power_on(adapter, instance):
"""Powers on a VM.
:param adapter: A pypowervm.adapter.Adapter.
:param instance: The nova instance to power on.
:raises: InstancePowerOnFailure
"""
# Synchronize power-on and power-off ops on a given instance
with lockutils.lock('power_%s' % instance.uuid):
entry = get_instance_wrapper(adapter, instance)
# Get the current state and see if we can start the VM
if entry.state in _POWERVM_STARTABLE_STATE:
# Now start the lpar
try:
power.power_on(entry, None)
except pvm_exc.Error as e:
LOG.exception("PowerVM error during power_on.",
instance=instance)
raise exc.InstancePowerOnFailure(reason=six.text_type(e))
def power_off(adapter, instance, force_immediate=False):
"""Powers off a VM.
:param adapter: A pypowervm.adapter.Adapter.
:param instance: The nova instance to power off.
:param force_immediate: (Optional, Default False) Should it be immediately
shut down.
:raises: InstancePowerOffFailure
"""
# Synchronize power-on and power-off ops on a given instance
with lockutils.lock('power_%s' % instance.uuid):
entry = get_instance_wrapper(adapter, instance)
# Get the current state and see if we can stop the VM
LOG.debug("Powering off request for instance %(inst)s which is in "
"state %(state)s. Force Immediate Flag: %(force)s.",
{'inst': instance.name, 'state': entry.state,
'force': force_immediate})
if entry.state in _POWERVM_STOPPABLE_STATE:
# Now stop the lpar
try:
LOG.debug("Power off executing for instance %(inst)s.",
{'inst': instance.name})
force_flag = (power.Force.TRUE if force_immediate
else power.Force.ON_FAILURE)
power.power_off(entry, None, force_immediate=force_flag)
except pvm_exc.Error as e:
LOG.exception("PowerVM error during power_off.",
instance=instance)
raise exc.InstancePowerOffFailure(reason=six.text_type(e))
else:
LOG.debug("Power off not required for instance %(inst)s.",
{'inst': instance.name})
def delete_lpar(adapter, instance):
"""Delete an LPAR.
:param adapter: The adapter for the pypowervm API.
:param instance: The nova instance corresponding to the lpar to delete.
"""
lpar_uuid = get_pvm_uuid(instance)
# Attempt to delete the VM. To avoid failures due to open vterm, we will
# attempt to close the vterm before issuing the delete.
try:
LOG.info('Deleting virtual machine.', instance=instance)
# Ensure any vterms are closed. Will no-op otherwise.
vterm.close_vterm(adapter, lpar_uuid)
# Run the LPAR delete
resp = adapter.delete(pvm_lpar.LPAR.schema_type, root_id=lpar_uuid)
LOG.info('Virtual machine delete status: %d', resp.status,
instance=instance)
return resp
except pvm_exc.HttpError as e:
with excutils.save_and_reraise_exception(logger=LOG) as sare:
if e.response and e.response.status == 404:
# LPAR is already gone - don't fail
sare.reraise = False
LOG.info('Virtual Machine not found', instance=instance)
else:
LOG.error('HttpError deleting virtual machine.',
instance=instance)
except pvm_exc.Error:
with excutils.save_and_reraise_exception(logger=LOG):
# Attempting to close vterm did not help so raise exception
LOG.error('Virtual machine delete failed: LPARID=%s', lpar_uuid)
def create_lpar(adapter, host_w, instance):
"""Create an LPAR based on the host based on the instance.
:param adapter: The adapter for the pypowervm API.
:param host_w: The host's System wrapper.
:param instance: The nova instance.
:return: The LPAR wrapper response from the API.
"""
try:
# Translate the nova flavor into a PowerVM Wrapper Object.
lpar_b = VMBuilder(host_w, adapter).lpar_builder(instance)
pending_lpar_w = lpar_b.build()
# Run validation against it. This is just for nice(r) error messages.
pvm_vldn.LPARWrapperValidator(pending_lpar_w,
host_w).validate_all()
# Create it. The API returns a new wrapper with the actual system data.
return pending_lpar_w.create(parent=host_w)
except lpar_bldr.LPARBuilderException as e:
# Raise the BuildAbortException since LPAR failed to build
raise exc.BuildAbortException(instance_uuid=instance.uuid, reason=e)
except pvm_exc.HttpError as he:
# Raise the API exception
LOG.exception("PowerVM HttpError creating LPAR.", instance=instance)
raise exc.PowerVMAPIFailed(inst_name=instance.name, reason=he)
def _translate_vm_state(pvm_state):
"""Find the current state of the lpar.
:return: The appropriate integer state value from power_state, converted
from the PowerVM state.
"""
if pvm_state is None:
return power_state.NOSTATE
try:
return _POWERVM_TO_NOVA_STATE[pvm_state.lower()]
except KeyError:
return power_state.NOSTATE
def get_vm_qp(adapter, lpar_uuid, qprop=None, log_errors=True):
"""Returns one or all quick properties of an LPAR.
:param adapter: The pypowervm adapter.
:param lpar_uuid: The (powervm) UUID for the LPAR.
:param qprop: The quick property key to return. If specified, that single
property value is returned. If None/unspecified, all quick
properties are returned in a dictionary.
:param log_errors: Indicator whether to log REST data after an exception
:return: Either a single quick property value or a dictionary of all quick
properties.
"""
kwds = dict(root_id=lpar_uuid, suffix_type='quick', suffix_parm=qprop)
if not log_errors:
# Remove the log helper from the list of helpers.
# Note that adapter.helpers returns a copy - the .remove doesn't affect
# the adapter's original helpers list.
helpers = adapter.helpers
try:
helpers.remove(pvm_log.log_helper)
except ValueError:
# It's not an error if we didn't find it.
pass
kwds['helpers'] = helpers
try:
resp = adapter.read(pvm_lpar.LPAR.schema_type, **kwds)
except pvm_exc.HttpError as e:
with excutils.save_and_reraise_exception(logger=LOG) as sare:
# 404 error indicates the LPAR has been deleted
if e.response and e.response.status == 404:
sare.reraise = False
raise exc.InstanceNotFound(instance_id=lpar_uuid)
# else raise the original exception
return jsonutils.loads(resp.body)
class VMBuilder(object):
"""Converts a Nova Instance/Flavor into a pypowervm LPARBuilder."""
def __init__(self, host_w, adapter):
"""Initialize the converter.
:param host_w: The host System wrapper.
:param adapter: The pypowervm.adapter.Adapter for the PowerVM REST API.
"""
self.adapter = adapter
self.stdz = lpar_bldr.DefaultStandardize(host_w)
def lpar_builder(self, inst):
"""Returns the pypowervm LPARBuilder for a given Nova flavor.
:param inst: the VM instance
"""
attrs = {
lpar_bldr.NAME: pvm_u.sanitize_partition_name_for_api(inst.name),
lpar_bldr.UUID: get_pvm_uuid(inst),
lpar_bldr.MEM: inst.flavor.memory_mb,
lpar_bldr.VCPU: inst.flavor.vcpus}
# TODO(efried): Loop through the extra specs and process powervm keys
# TODO(thorst, efried) Add in IBMi attributes
return lpar_bldr.LPARBuilder(self.adapter, attrs, self.stdz)
class InstanceInfo(hardware.InstanceInfo):
"""Instance Information
This object tries to lazy load the attributes since the compute
manager retrieves it a lot just to check the status and doesn't need
all the attributes.
:param adapter: pypowervm adapter
:param instance: nova instance
"""
_QP_STATE = 'PartitionState'
def __init__(self, adapter, instance):
self._adapter = adapter
# This is the PowerVM LPAR UUID (not the instance (UU)ID).
self.id = get_pvm_uuid(instance)
self._state = None
@property
def state(self):
# return the state if we previously loaded it
if self._state is not None:
return self._state
# otherwise, fetch the value now
pvm_state = get_vm_qp(self._adapter, self.id, self._QP_STATE)
self._state = _translate_vm_state(pvm_state)
return self._state