Merge "PowerVM Driver: spawn/destroy #2: functional"
This commit is contained in:
commit
3cc34c2aa3
|
@ -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.")
|
||||
|
||||
|
|
|
@ -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,
|
||||
)
|
|
@ -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={})
|
||||
|
|
|
@ -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)
|
|
@ -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))
|
||||
|
|
|
@ -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
|
Loading…
Reference in New Issue