IBMi VM depends on tagged io, ipl source and keylock position to determine PowerOn mode. During deploying an new IBMi VM, tagged io, IPL source and keylock position must be set. This change set will add a new task in spawn flow to set those required attributes for IBMi. Change-Id: Idd5a305ff9aa6de11920fbc61099a10a0b2df748
450 lines
19 KiB
Python
450 lines
19 KiB
Python
# Copyright 2014, 2015 IBM Corp.
|
|
#
|
|
# All Rights Reserved.
|
|
#
|
|
# 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 logging
|
|
|
|
import mock
|
|
|
|
from nova.compute import power_state
|
|
from nova import exception
|
|
from nova import objects
|
|
from nova import test
|
|
from pypowervm import exceptions as pvm_exc
|
|
from pypowervm.helpers import log_helper as pvm_log
|
|
from pypowervm.tests import test_fixtures as pvm_fx
|
|
from pypowervm.tests.test_utils import pvmhttp
|
|
from pypowervm.wrappers import base_partition as pvm_bp
|
|
from pypowervm.wrappers import logical_partition as pvm_lpar
|
|
from pypowervm.wrappers import network as pvm_net
|
|
|
|
from nova_powervm.tests.virt import powervm
|
|
from nova_powervm.virt.powervm import vm
|
|
|
|
LPAR_HTTPRESP_FILE = "lpar.txt"
|
|
LPAR_MAPPING = (
|
|
{
|
|
'z3-9-5-126-127-00000001': '089ffb20-5d19-4a8c-bb80-13650627d985',
|
|
'z3-9-5-126-208-000001f0': '668b0882-c24a-4ae9-91c8-297e95e3fe29'
|
|
})
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
logging.basicConfig()
|
|
|
|
|
|
class FakeAdapterResponse(object):
|
|
def __init__(self, status):
|
|
self.status = status
|
|
|
|
|
|
class TestVMBuilder(test.TestCase):
|
|
|
|
def setUp(self):
|
|
super(TestVMBuilder, self).setUp()
|
|
|
|
self.adpt = mock.MagicMock()
|
|
self.host_w = mock.MagicMock()
|
|
self.lpar_b = vm.VMBuilder(self.host_w, self.adpt)
|
|
|
|
def test_conf_values(self):
|
|
# Test driver CONF values are passed to the standardizer
|
|
self.flags(uncapped_proc_weight=75, proc_units_factor=.25,
|
|
group='powervm')
|
|
lpar_bldr = vm.VMBuilder(self.host_w, self.adpt)
|
|
self.assertEqual(75, lpar_bldr.stdz.uncapped_weight)
|
|
self.assertEqual(.25, lpar_bldr.stdz.proc_units_factor)
|
|
|
|
def test_format_flavor(self):
|
|
"""Perform tests against _format_flavor."""
|
|
instance = objects.Instance(**powervm.TEST_INSTANCE)
|
|
flavor = instance.get_flavor()
|
|
lpar_attrs = {'memory': 2048,
|
|
'name': 'instance-00000001',
|
|
'uuid': '49629a5c-f4c4-4721-9511-9725786ff2e5',
|
|
'vcpu': 1}
|
|
|
|
# Test dedicated procs
|
|
flavor.extra_specs = {'powervm:dedicated_proc': 'true'}
|
|
test_attrs = dict(lpar_attrs, **{'dedicated_proc': 'true'})
|
|
|
|
self.assertEqual(self.lpar_b._format_flavor(instance, flavor),
|
|
test_attrs)
|
|
|
|
# Test dedicated procs, min/max vcpu and sharing mode
|
|
flavor.extra_specs = {'powervm:dedicated_proc': 'true',
|
|
'powervm:dedicated_sharing_mode':
|
|
'share_idle_procs_active',
|
|
'powervm:min_vcpu': '1',
|
|
'powervm:max_vcpu': '3'}
|
|
test_attrs = dict(lpar_attrs,
|
|
**{'dedicated_proc': 'true',
|
|
'sharing_mode': 'sre idle procs active',
|
|
'min_vcpu': '1', 'max_vcpu': '3'})
|
|
self.assertEqual(self.lpar_b._format_flavor(instance, flavor),
|
|
test_attrs)
|
|
|
|
# Test shared proc sharing mode
|
|
flavor.extra_specs = {'powervm:uncapped': 'true'}
|
|
test_attrs = dict(lpar_attrs, **{'sharing_mode': 'uncapped'})
|
|
self.assertEqual(self.lpar_b._format_flavor(instance, flavor),
|
|
test_attrs)
|
|
|
|
# Test availability priority
|
|
flavor.extra_specs = {'powervm:availability_priority': '150'}
|
|
test_attrs = dict(lpar_attrs, **{'avail_priority': '150'})
|
|
self.assertEqual(self.lpar_b._format_flavor(instance, flavor),
|
|
test_attrs)
|
|
|
|
# Test processor compatibility
|
|
flavor.extra_specs = {'powervm:processor_compatibility': 'POWER8'}
|
|
test_attrs = dict(lpar_attrs, **{'processor_compatibility': 'POWER8'})
|
|
self.assertEqual(self.lpar_b._format_flavor(instance, flavor),
|
|
test_attrs)
|
|
|
|
# Test min, max proc units
|
|
flavor.extra_specs = {'powervm:min_proc_units': '0.5',
|
|
'powervm:max_proc_units': '2.0'}
|
|
test_attrs = dict(lpar_attrs, **{'min_proc_units': '0.5',
|
|
'max_proc_units': '2.0'})
|
|
self.assertEqual(self.lpar_b._format_flavor(instance, flavor),
|
|
test_attrs)
|
|
|
|
# Test min, max mem
|
|
flavor.extra_specs = {'powervm:min_mem': '1024',
|
|
'powervm:max_mem': '4096'}
|
|
test_attrs = dict(lpar_attrs, **{'min_mem': '1024', 'max_mem': '4096'})
|
|
self.assertEqual(self.lpar_b._format_flavor(instance, flavor),
|
|
test_attrs)
|
|
|
|
@mock.patch('pypowervm.wrappers.shared_proc_pool.SharedProcPool.search')
|
|
def test_spp_pool_id(self, mock_search):
|
|
# The default pool is always zero. Validate the path.
|
|
self.assertEqual(0, self.lpar_b._spp_pool_id('DefaultPool'))
|
|
self.assertEqual(0, self.lpar_b._spp_pool_id(None))
|
|
|
|
# Further invocations require calls to the adapter. Build a minimal
|
|
# mocked SPP wrapper
|
|
spp = mock.MagicMock()
|
|
spp.id = 1
|
|
|
|
# Three invocations. First has too many elems. Second has none.
|
|
# Third is just right. :-)
|
|
mock_search.side_effect = [[spp, spp], [], [spp]]
|
|
|
|
self.assertRaises(exception.ValidationError, self.lpar_b._spp_pool_id,
|
|
'fake_name')
|
|
self.assertRaises(exception.ValidationError, self.lpar_b._spp_pool_id,
|
|
'fake_name')
|
|
|
|
self.assertEqual(1, self.lpar_b._spp_pool_id('fake_name'))
|
|
|
|
def test_flavor_bool(self):
|
|
true_iterations = ['true', 't', 'yes', 'y', 'TrUe', 'YeS', 'Y', 'T']
|
|
for t in true_iterations:
|
|
self.assertTrue(self.lpar_b._flavor_bool(t, 'key'))
|
|
|
|
false_iterations = ['false', 'f', 'no', 'n', 'FaLSe', 'nO', 'F', 'N']
|
|
for f in false_iterations:
|
|
self.assertFalse(self.lpar_b._flavor_bool(f, 'key'))
|
|
|
|
raise_iterations = ['NotGood', '', 'invalid']
|
|
for r in raise_iterations:
|
|
self.assertRaises(exception.ValidationError,
|
|
self.lpar_b._flavor_bool, r, 'key')
|
|
|
|
|
|
class TestVM(test.TestCase):
|
|
def setUp(self):
|
|
super(TestVM, self).setUp()
|
|
self.apt = self.useFixture(pvm_fx.AdapterFx(
|
|
traits=pvm_fx.LocalPVMTraits)).adpt
|
|
self.apt.helpers = [pvm_log.log_helper]
|
|
|
|
lpar_http = pvmhttp.load_pvm_resp(LPAR_HTTPRESP_FILE, adapter=self.apt)
|
|
self.assertNotEqual(lpar_http, None,
|
|
"Could not load %s " %
|
|
LPAR_HTTPRESP_FILE)
|
|
|
|
self.resp = lpar_http.response
|
|
|
|
def test_instance_info(self):
|
|
|
|
# Test at least one state translation
|
|
self.assertEqual(vm._translate_vm_state('running'),
|
|
power_state.RUNNING)
|
|
|
|
inst_info = vm.InstanceInfo(self.apt, 'inst_name', '1234')
|
|
# Test the static properties
|
|
self.assertEqual(inst_info.id, '1234')
|
|
self.assertEqual(inst_info.cpu_time_ns, 0)
|
|
|
|
# Check that we raise an exception if the instance is gone.
|
|
exc = pvm_exc.Error('Not found', response=FakeAdapterResponse(404))
|
|
self.apt.read.side_effect = exc
|
|
self.assertRaises(exception.InstanceNotFound,
|
|
inst_info.__getattribute__, 'state')
|
|
|
|
# Reset the test inst_info
|
|
inst_info = vm.InstanceInfo(self.apt, 'inst_name', '1234')
|
|
|
|
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
|
|
inst_info1 = vm.InstanceInfo(self.apt, 'inst_name', '1234')
|
|
inst_info2 = vm.InstanceInfo(self.apt, 'inst_name', '1234')
|
|
self.assertEqual(inst_info1, inst_info2)
|
|
inst_info2 = vm.InstanceInfo(self.apt, 'name', '4321')
|
|
self.assertNotEqual(inst_info1, inst_info2)
|
|
|
|
def test_get_lpars(self):
|
|
self.apt.read.return_value = self.resp
|
|
lpars = vm.get_lpars(self.apt)
|
|
# One of the LPARs is a management partition, so one less than the
|
|
# total length should be returned.
|
|
self.assertEqual(len(self.resp.feed.entries) - 1, len(lpars))
|
|
|
|
exc = pvm_exc.Error('Not found', response=FakeAdapterResponse(404))
|
|
self.apt.read.side_effect = exc
|
|
self.assertRaises(pvm_exc.Error, vm.get_lpars, self.apt)
|
|
|
|
def test_get_lpar_names(self):
|
|
self.apt.read.return_value = self.resp
|
|
lpar_list = vm.get_lpar_names(self.apt)
|
|
# Check the first one in the feed and the length of the feed
|
|
self.assertEqual(lpar_list[0], 'z3-9-5-126-208-000001f0')
|
|
self.assertEqual(len(lpar_list), 20)
|
|
|
|
@mock.patch('pypowervm.tasks.vterm.close_vterm')
|
|
def test_dlt_lpar(self, mock_vterm):
|
|
"""Performs a delete LPAR test."""
|
|
vm.dlt_lpar(self.apt, '12345')
|
|
self.assertEqual(1, self.apt.delete.call_count)
|
|
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.dlt_lpar, self.apt, '12345')
|
|
self.assertEqual(1, mock_vterm.call_count)
|
|
self.assertEqual(1, self.apt.delete.call_count)
|
|
|
|
@mock.patch('nova_powervm.virt.powervm.vm.VMBuilder._add_IBMi_attrs')
|
|
@mock.patch('pypowervm.utils.lpar_builder.DefaultStandardize')
|
|
@mock.patch('pypowervm.utils.lpar_builder.LPARBuilder.build')
|
|
@mock.patch('pypowervm.utils.validation.LPARWrapperValidator.validate_all')
|
|
def test_crt_lpar(self, mock_vld_all, mock_bld, mock_stdz, mock_ibmi):
|
|
instance = objects.Instance(**powervm.TEST_INSTANCE)
|
|
flavor = instance.get_flavor()
|
|
flavor.extra_specs = {'powervm:dedicated_proc': 'true'}
|
|
|
|
host_wrapper = mock.Mock()
|
|
lparw = pvm_lpar.LPAR.wrap(self.resp.feed.entries[0])
|
|
mock_bld.return_value = lparw
|
|
self.apt.create.return_value = lparw.entry
|
|
vm.crt_lpar(self.apt, host_wrapper, instance, flavor)
|
|
self.assertTrue(self.apt.create.called)
|
|
self.assertTrue(mock_vld_all.called)
|
|
|
|
flavor.extra_specs = {'powervm:BADATTR': 'true'}
|
|
host_wrapper = mock.Mock()
|
|
self.assertRaises(exception.InvalidAttribute, vm.crt_lpar,
|
|
self.apt, host_wrapper, instance, flavor)
|
|
|
|
def test_add_IBMi_attrs(self):
|
|
inst = mock.Mock()
|
|
# Non-ibmi distro
|
|
attrs = {}
|
|
inst.system_metadata = {'image_os_distro': 'rhel'}
|
|
bldr = vm.VMBuilder(mock.Mock(), mock.Mock())
|
|
bldr._add_IBMi_attrs(inst, attrs)
|
|
self.assertDictEqual(attrs, {})
|
|
|
|
inst.system_metadata = {}
|
|
bldr._add_IBMi_attrs(inst, attrs)
|
|
self.assertDictEqual(attrs, {})
|
|
|
|
# ibmi distro
|
|
inst.system_metadata = {'image_os_distro': 'ibmi'}
|
|
bldr._add_IBMi_attrs(inst, attrs)
|
|
self.assertDictEqual(attrs, {'env': 'OS400'})
|
|
|
|
@mock.patch('pypowervm.tasks.power.power_off')
|
|
def test_power_off(self, mock_power_off):
|
|
self.assertFalse(vm.power_off(
|
|
None, None, 'host_uuid',
|
|
mock.Mock(state=pvm_bp.LPARState.NOT_ACTIVATED)))
|
|
self.assertFalse(mock_power_off.called)
|
|
|
|
stop_states = [pvm_bp.LPARState.RUNNING, pvm_bp.LPARState.STARTING,
|
|
pvm_bp.LPARState.OPEN_FIRMWARE, pvm_bp.LPARState.ERROR,
|
|
pvm_bp.LPARState.RESUMING]
|
|
for stop_state in stop_states:
|
|
mock_power_off.reset_mock()
|
|
self.assertTrue(vm.power_off(
|
|
None, None, 'host_uuid', mock.Mock(state=stop_state)))
|
|
self.assertTrue(mock_power_off.called)
|
|
|
|
@mock.patch('nova_powervm.virt.powervm.vm.get_pvm_uuid')
|
|
@mock.patch('pypowervm.tasks.cna.crt_cna')
|
|
def test_crt_vif(self, mock_crt_cna, mock_pvm_uuid):
|
|
"""Tests that a VIF can be created."""
|
|
|
|
# Set up the mocks
|
|
fake_vif = {'network': {'meta': {'vlan': 5}},
|
|
'address': 'aabbccddeeff'}
|
|
|
|
def validate_of_crt(*kargs, **kwargs):
|
|
self.assertEqual('fake_host', kargs[1])
|
|
self.assertEqual(5, kargs[3])
|
|
self.assertEqual('aabbccddeeff', kwargs['mac_addr'])
|
|
return pvm_net.CNA.bld(self.apt, 5, 'fake_host')
|
|
mock_crt_cna.side_effect = validate_of_crt
|
|
|
|
# Invoke
|
|
resp = vm.crt_vif(self.apt, mock.MagicMock(), 'fake_host', fake_vif)
|
|
|
|
# Validate (along with validate method above)
|
|
self.assertEqual(1, mock_crt_cna.call_count)
|
|
self.assertIsNotNone(resp)
|
|
self.assertIsInstance(resp, pvm_net.CNA)
|
|
|
|
@mock.patch('nova_powervm.virt.powervm.vm.get_pvm_uuid')
|
|
@mock.patch('nova_powervm.virt.powervm.vm.get_vm_qp')
|
|
def test_instance_exists(self, mock_getvmqp, mock_getuuid):
|
|
# Try the good case where it exists
|
|
mock_getvmqp.side_effect = 'fake_state'
|
|
mock_parms = (mock.Mock(), mock.Mock(), mock.Mock())
|
|
self.assertTrue(vm.instance_exists(*mock_parms))
|
|
|
|
# Test the scenario where it does not exist.
|
|
mock_getvmqp.side_effect = exception.InstanceNotFound(instance_id=123)
|
|
self.assertFalse(vm.instance_exists(*mock_parms))
|
|
|
|
def test_get_vm_qp(self):
|
|
def adapter_read(root_type, root_id=None, suffix_type=None,
|
|
suffix_parm=None, helpers=None):
|
|
json_str = (u'{"IsVirtualServiceAttentionLEDOn":"false","Migration'
|
|
u'State":"Not_Migrating","CurrentProcessingUnits":0.1,'
|
|
u'"ProgressState":null,"PartitionType":"AIX/Linux","Pa'
|
|
u'rtitionID":1,"AllocatedVirtualProcessors":1,"Partiti'
|
|
u'onState":"not activated","RemoteRestartState":"Inval'
|
|
u'id","OperatingSystemVersion":"Unknown","AssociatedMa'
|
|
u'nagedSystem":"https://9.1.2.3:12443/rest/api/uom/Man'
|
|
u'agedSystem/98498bed-c78a-3a4f-b90a-4b715418fcb6","RM'
|
|
u'CState":"inactive","PowerManagementMode":null,"Parti'
|
|
u'tionName":"lpar-1-06674231-lpar","HasDedicatedProces'
|
|
u'sors":"false","ResourceMonitoringIPAddress":null,"Re'
|
|
u'ferenceCode":"00000000","CurrentProcessors":null,"Cu'
|
|
u'rrentMemory":512,"SharingMode":"uncapped"}')
|
|
self.assertEqual('LogicalPartition', root_type)
|
|
self.assertEqual('lpar_uuid', root_id)
|
|
self.assertEqual('quick', suffix_type)
|
|
resp = mock.MagicMock()
|
|
if suffix_parm is None:
|
|
resp.body = json_str
|
|
elif suffix_parm == 'PartitionID':
|
|
resp.body = '1'
|
|
elif suffix_parm == 'CurrentProcessingUnits':
|
|
resp.body = '0.1'
|
|
elif suffix_parm == 'AssociatedManagedSystem':
|
|
# The double quotes are important
|
|
resp.body = ('"https://9.1.2.3:12443/rest/api/uom/ManagedSyste'
|
|
'm/98498bed-c78a-3a4f-b90a-4b715418fcb6"')
|
|
else:
|
|
self.fail('Unhandled quick property key %s' % suffix_parm)
|
|
return resp
|
|
|
|
def adpt_read_no_log(*args, **kwds):
|
|
helpers = kwds['helpers']
|
|
try:
|
|
helpers.index(pvm_log.log_helper)
|
|
except ValueError:
|
|
# Successful path since the logger shouldn't be there
|
|
return adapter_read(*args, **kwds)
|
|
|
|
self.fail('Log helper was found when it should not be')
|
|
|
|
ms_href = ('https://9.1.2.3:12443/rest/api/uom/ManagedSystem/98498bed-'
|
|
'c78a-3a4f-b90a-4b715418fcb6')
|
|
self.apt.read.side_effect = adapter_read
|
|
self.assertEqual(1, vm.get_vm_id(self.apt, 'lpar_uuid'))
|
|
self.assertEqual(ms_href, vm.get_vm_qp(self.apt, 'lpar_uuid',
|
|
'AssociatedManagedSystem'))
|
|
self.apt.read.side_effect = adpt_read_no_log
|
|
self.assertEqual(0.1, vm.get_vm_qp(self.apt, 'lpar_uuid',
|
|
'CurrentProcessingUnits',
|
|
log_errors=False))
|
|
qp_dict = vm.get_vm_qp(self.apt, 'lpar_uuid', log_errors=False)
|
|
self.assertEqual(ms_href, qp_dict['AssociatedManagedSystem'])
|
|
self.assertEqual(1, qp_dict['PartitionID'])
|
|
self.assertEqual(0.1, qp_dict['CurrentProcessingUnits'])
|
|
|
|
resp = mock.MagicMock()
|
|
resp.status = 404
|
|
self.apt.read.side_effect = pvm_exc.Error('message', response=resp)
|
|
self.assertRaises(exception.InstanceNotFound, 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)
|
|
|
|
def test_norm_mac(self):
|
|
EXPECTED = "12:34:56:78:90:ab"
|
|
self.assertEqual(EXPECTED, vm.norm_mac("12:34:56:78:90:ab"))
|
|
self.assertEqual(EXPECTED, vm.norm_mac("1234567890ab"))
|
|
self.assertEqual(EXPECTED, vm.norm_mac("12:34:56:78:90:AB"))
|
|
self.assertEqual(EXPECTED, vm.norm_mac("1234567890AB"))
|
|
|
|
@mock.patch('pypowervm.tasks.ibmi.update_ibmi_settings')
|
|
@mock.patch('nova_powervm.virt.powervm.vm.get_instance_wrapper')
|
|
def test_update_ibmi_settings(self, mock_lparw, mock_ibmi):
|
|
instance = mock.MagicMock()
|
|
# Test update load source with vscsi boot
|
|
boot_type = 'vscsi'
|
|
vm.update_ibmi_settings(
|
|
self.apt, instance, 'host-uuid', boot_type)
|
|
mock_ibmi.assert_called_once_with(
|
|
self.apt, mock.ANY, 'vscsi')
|
|
mock_ibmi.reset_mock()
|
|
# Test update load source with npiv boot
|
|
boot_type = 'npiv'
|
|
vm.update_ibmi_settings(
|
|
self.apt, instance, 'host-uuid', boot_type)
|
|
mock_ibmi.assert_called_once_with(
|
|
self.apt, mock.ANY, 'npiv')
|