Sync patch from in tree

Change-Id: I9088ff34129feed57757d57824f65a3d784801dd
This commit is contained in:
jichenjc 2018-04-12 02:16:54 +08:00
parent e26ae01680
commit e72fbe637f
12 changed files with 1404 additions and 1092 deletions

View File

@ -1,4 +1,4 @@
# Copyright 2017 IBM Corp.
# Copyright 2017,2018 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
@ -13,16 +13,14 @@
# under the License.
"""Test suite for ZVM configure drive."""
import os
import shutil
from nova import exception
from nova import test
from oslo_utils import fileutils
from nova_zvm.virt.zvm import conf
from nova_zvm.virt.zvm import configdrive as zvmconfigdrive
from nova import conf
from nova import test
from nova.virt.zvm import configdrive as zvmconfigdrive
CONF = conf.CONF
@ -39,29 +37,20 @@ class ZVMConfigDriveTestCase(test.NoDBTestCase):
super(ZVMConfigDriveTestCase, self).setUp()
self.flags(config_drive_format='iso9660',
tempdir='/tmp/os')
self._file_path = CONF.tempdir
self._file_name = self._file_path + '/cfgdrive.tgz'
self.inst_md = FakeInstMeta()
def tearDown(self):
super(ZVMConfigDriveTestCase, self).tearDown()
shutil.rmtree(self._file_path)
def test_create_configdrive_tgz(self):
self._file_path = CONF.tempdir
fileutils.ensure_tree(self._file_path)
try:
with zvmconfigdrive.ZVMConfigDriveBuilder(
instance_md=self.inst_md) as c:
c.make_drive(self._file_name)
self._file_name = self._file_path + '/cfgdrive.tgz'
with zvmconfigdrive.ZVMConfigDriveBuilder(
instance_md=self.inst_md) as c:
c.make_drive(self._file_name)
self.assertTrue(os.path.exists(self._file_name))
finally:
fileutils.remove_path_on_error(self._file_path)
def test_make_drive_unknown_format(self):
self.flags(config_drive_format='vfat')
try:
with zvmconfigdrive.ZVMConfigDriveBuilder(
instance_md=self.inst_md) as c:
self.assertRaises(exception.ConfigDriveUnknownFormat,
c.make_drive, self._file_name)
finally:
fileutils.remove_path_on_error(self._file_path)

View File

@ -1,4 +1,4 @@
# Copyright 2017 IBM Corp.
# Copyright 2017,2018 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,541 +12,152 @@
# License for the specific language governing permissions and limitations
# under the License.
import copy
import eventlet
import mock
import os
import six
if six.PY2:
import __builtin__ as builtins
elif six.PY3:
import builtins
from nova.compute import power_state
from nova import context
from nova import exception
from nova.network import model as network_model
from nova import objects
from nova import test
from nova.tests.unit import fake_instance
from nova.tests import uuidsentinel
from nova_zvm.virt.zvm import conf
from nova_zvm.virt.zvm import const
from nova_zvm.virt.zvm import driver as zvmdriver
from nova_zvm.virt.zvm import utils as zvmutils
CONF = conf.CONF
from nova.virt.zvm import driver as zvmdriver
class TestZVMDriver(test.NoDBTestCase):
def setUp(self):
super(TestZVMDriver, self).setUp()
self.flags(zvm_cloud_connector_url='https://1.1.1.1:1111',
zvm_image_tmp_path='/test/image',
zvm_reachable_timeout=300)
self.flags(my_ip='192.168.1.1',
instance_name_template='test%04x')
with mock.patch('nova_zvm.virt.zvm.utils.'
'zVMConnectorRequestHandler.call') as mcall:
self.flags(instance_name_template='abc%05d')
self.flags(cloud_connector_url='https://1.1.1.1:1111', group='zvm')
with mock.patch('nova.virt.zvm.utils.'
'ConnectorClient.call') as mcall:
mcall.return_value = {'hypervisor_hostname': 'TESTHOST',
'ipl_time': 'TESTTIME'}
self.driver = zvmdriver.ZVMDriver('virtapi')
'ipl_time': 'IPL at 11/14/17 10:47:44 EST'}
self._driver = zvmdriver.ZVMDriver('virtapi')
self._hypervisor = self._driver._hypervisor
self._context = context.RequestContext('fake_user', 'fake_project')
self._uuid = uuidsentinel.foo
self._image_id = uuidsentinel.foo
self._instance_values = {
'display_name': 'test',
'uuid': self._uuid,
'vcpus': 1,
'memory_mb': 1024,
'image_ref': self._image_id,
'root_gb': 0,
}
self._instance = fake_instance.fake_instance_obj(
self._context, **self._instance_values)
self._flavor = objects.Flavor(name='testflavor', memory_mb=512,
vcpus=1, root_gb=3, ephemeral_gb=10,
swap=0, extra_specs={})
self._instance.flavor = self._flavor
self._instance = fake_instance.fake_instance_obj(self._context)
self._eph_disks = [{'guest_format': u'ext3',
'device_name': u'/dev/sdb',
'disk_bus': None,
'device_type': None,
'size': 1},
{'guest_format': u'ext4',
'device_name': u'/dev/sdc',
'disk_bus': None,
'device_type': None,
'size': 2}]
self._block_device_info = {'swap': None,
'root_device_name': u'/dev/sda',
'ephemerals': self._eph_disks,
'block_device_mapping': []}
fake_image_meta = {'status': 'active',
'properties': {'os_distro': 'rhel7.2'},
'name': 'rhel72eckdimage',
'deleted': False,
'container_format': 'bare',
'disk_format': 'raw',
'id': self._image_id,
'owner': 'cfc26f9d6af948018621ab00a1675310',
'checksum': 'b026cd083ef8e9610a29eaf71459cc',
'min_disk': 0,
'is_public': False,
'deleted_at': None,
'min_ram': 0,
'size': 465448142}
self._image_meta = objects.ImageMeta.from_dict(fake_image_meta)
subnet_4 = network_model.Subnet(cidr='192.168.0.1/24',
dns=[network_model.IP('192.168.0.1')],
gateway=
network_model.IP('192.168.0.1'),
ips=[
network_model.IP('192.168.0.100')],
routes=None)
network = network_model.Network(id=0,
bridge='fa0',
label='fake',
subnets=[subnet_4],
vlan=None,
bridge_interface=None,
injected=True)
self._network_values = {
'id': None,
'address': 'DE:AD:BE:EF:00:00',
'network': network,
'type': network_model.VIF_TYPE_OVS,
'devname': None,
'ovs_interfaceid': None,
'rxtx_cap': 3
}
self._network_info = network_model.NetworkInfo([
network_model.VIF(**self._network_values)
])
def test_driver_init_no_url(self):
self.flags(cloud_connector_url=None, group='zvm')
self.assertRaises(exception.ZVMDriverException,
zvmdriver.ZVMDriver, 'virtapi')
self.mock_update_task_state = mock.Mock()
def test_driver_init(self):
self.assertEqual(self.driver._hypervisor_hostname, 'TESTHOST')
self.assertIsInstance(self.driver._reqh,
zvmutils.zVMConnectorRequestHandler)
self.assertIsInstance(self.driver._vmutils, zvmutils.VMUtils)
self.assertIsInstance(self.driver._imageop_semaphore,
eventlet.semaphore.Semaphore)
@mock.patch('nova_zvm.virt.zvm.utils.zVMConnectorRequestHandler.call')
def test_list_instance(self, call):
call.return_value = ['vm1', 'vm2']
inst_list = self.driver.list_instances()
self.assertEqual(['vm1', 'vm2'], inst_list)
@mock.patch('nova_zvm.virt.zvm.utils.zVMConnectorRequestHandler.call')
def test_get_available_resource(self, call):
host_info = {'disk_available': 1144,
'ipl_time': u'IPL at 11/14/17 10:47:44 EST',
'vcpus_used': 4,
'hypervisor_type': u'zvm',
'disk_total': 2000,
'zvm_host': u'TESTHOST',
'memory_mb': 78192.0,
'cpu_info': {u'cec_model': u'2827',
u'architecture': u's390x'},
'vcpus': 84,
'hypervisor_hostname': u'TESTHOST',
'hypervisor_version': 640,
'disk_used': 856,
'memory_mb_used': 8192.0}
call.return_value = host_info
results = self.driver.get_available_resource()
self.assertEqual(84, results['vcpus'])
self.assertEqual(8192.0, results['memory_mb_used'])
self.assertEqual(1144, results['disk_available_least'])
self.assertEqual('TESTHOST', results['hypervisor_hostname'])
@mock.patch('nova_zvm.virt.zvm.utils.zVMConnectorRequestHandler.call')
@mock.patch('nova.virt.zvm.utils.ConnectorClient.call')
def test_get_available_resource_err_case(self, call):
call.side_effect = exception.NovaException
results = self.driver.get_available_resource()
call.side_effect = exception.ZVMDriverException(error='dummy')
results = self._driver.get_available_resource()
self.assertEqual(0, results['vcpus'])
self.assertEqual(0, results['memory_mb_used'])
self.assertEqual(0, results['disk_available_least'])
self.assertEqual('', results['hypervisor_hostname'])
self.assertEqual('TESTHOST', results['hypervisor_hostname'])
def test_get_available_nodes(self):
nodes = self.driver.get_available_nodes()
self.assertEqual(['TESTHOST'], nodes)
def test_driver_template_validation(self):
self.flags(instance_name_template='abc%6d')
self.assertRaises(exception.ZVMDriverException,
self._driver._validate_options)
def test_private_mapping_power_stat(self):
status = self.driver._mapping_power_stat('on')
self.assertEqual(power_state.RUNNING, status)
status = self.driver._mapping_power_stat('off')
self.assertEqual(power_state.SHUTDOWN, status)
status = self.driver._mapping_power_stat('bad')
self.assertEqual(power_state.NOSTATE, status)
@mock.patch('nova.virt.zvm.guest.Guest.get_info')
def test_get_info(self, mock_get):
self._driver.get_info(self._instance)
mock_get.assert_called_once_with()
@mock.patch('nova_zvm.virt.zvm.utils.zVMConnectorRequestHandler.call')
def test_get_info_err_InstanceNotFound(self, call):
call.side_effect = exception.NovaException(results={'overallRC': 404})
self.assertRaises(exception.InstanceNotFound, self.driver.get_info,
self._instance)
@mock.patch('nova.virt.zvm.guest.Guest.spawn')
def test_spawn(self, mock_spawn):
image_meta = {}
injected_files = {}
admin_password = 'dummy'
allocations = {}
self._driver.spawn(self._context, self._instance, image_meta,
injected_files, admin_password, allocations)
mock_spawn.assert_called_once_with(self._context,
image_meta, injected_files, admin_password, allocations,
network_info=None, block_device_info=None, flavor=None)
@mock.patch('nova_zvm.virt.zvm.utils.zVMConnectorRequestHandler.call')
def test_get_info_err_general(self, call):
call.side_effect = exception.NovaException(results={'overallRC': 500})
self.assertRaises(exception.NovaException, self.driver.get_info,
self._instance)
@mock.patch('nova.virt.zvm.guest.Guest.destroy')
def test_destroy(self, mock_spawn):
self._driver.destroy(self._context, self._instance)
mock_spawn.assert_called_once_with(self._context,
network_info=None, block_device_info=None, destroy_disks=False)
@mock.patch('nova.virt.hardware.InstanceInfo')
@mock.patch('nova_zvm.virt.zvm.utils.zVMConnectorRequestHandler.call')
def test_get_info(self, call, InstanceInfo):
call.return_value = 'on'
self.driver.get_info(self._instance)
call.assert_called_once_with('guest_get_power_state',
self._instance['name'])
InstanceInfo.assert_called_once_with(power_state.RUNNING)
@mock.patch('nova.virt.zvm.guest.Guest.snapshot')
def test_snapshot(self, mock_snapshot):
def fake_update_task_state():
pass
@mock.patch('nova_zvm.virt.zvm.driver.ZVMDriver.list_instances')
def test_private_instance_exists_True(self, list_instances):
list_instances.return_value = ['VM1', 'VM2']
res = self.driver._instance_exists('vm1')
self.assertTrue(res)
image_id = uuidsentinel.image_id
self._driver.snapshot(self._context, self._instance, image_id,
fake_update_task_state)
mock_snapshot.assert_called_once_with(self._context, image_id,
fake_update_task_state)
@mock.patch('nova_zvm.virt.zvm.driver.ZVMDriver.list_instances')
def test_private_instance_exists_False(self, list_instances):
list_instances.return_value = ['VM1', 'VM2']
res = self.driver._instance_exists('vm3')
self.assertFalse(res)
@mock.patch('nova.virt.driver.ComputeDriver.instance_exists')
@mock.patch('nova.virt.zvm.guest.Guest.guest_power_action')
def test_guest_power_action(self, call, mock_exists):
mock_exists.return_value = True
self._driver._guest_power_action(self._instance, 'guest_start')
call.assert_called_once_with('guest_start')
@mock.patch('nova_zvm.virt.zvm.driver.ZVMDriver._instance_exists')
def test_instance_exists(self, is_exists):
is_exists_response = []
is_exists_response.append(True)
is_exists_response.append(False)
is_exists.side_effect = is_exists_response
res = self.driver.instance_exists(self._instance)
is_exists.assert_any_call(self._instance.name)
self.assertTrue(res)
@mock.patch('nova.virt.driver.ComputeDriver.instance_exists')
@mock.patch('nova.virt.zvm.utils.ConnectorClient.call')
def test_guest_power_action_not_exist(self, call, mock_exists):
mock_exists.return_value = False
self._driver._guest_power_action(self._instance, 'guest_start')
self.assertEqual(0, call.called)
res = self.driver.instance_exists(self._instance)
is_exists.assert_any_call(self._instance.name)
self.assertFalse(res)
@mock.patch('nova.virt.driver.ComputeDriver.instance_exists')
@mock.patch('nova.virt.zvm.guest.Guest.guest_power_action')
def test_power_off(self, ipa, mock_exists):
mock_exists.return_value = True
self._driver.power_off(self._instance)
ipa.assert_called_once_with('guest_softstop')
@mock.patch('nova_zvm.virt.zvm.utils.zVMConnectorRequestHandler.call')
def test_private_get_image_info_err(self, call):
call.side_effect = exception.NovaException(results={'overallRC': 500})
self.assertRaises(exception.NovaException, self.driver._get_image_info,
'context', 'image_meta_id', 'os_distro')
@mock.patch('nova.virt.driver.ComputeDriver.instance_exists')
@mock.patch('nova.virt.zvm.guest.Guest.guest_power_action')
def test_power_off_with_timeout_interval(self, ipa, mock_exists):
mock_exists.return_value = True
self._driver.power_off(self._instance, 60, 10)
ipa.assert_called_once_with('guest_softstop',
timeout=60, poll_interval=10)
@mock.patch('nova_zvm.virt.zvm.utils.zVMConnectorRequestHandler.call')
@mock.patch('nova_zvm.virt.zvm.driver.ZVMDriver._import_spawn_image')
def test_private_get_image_info(self, image_import, call):
call_response = []
call_response.append(exception.NovaException(results=
{'overallRC': 404}))
call_response.append('Query_Result')
call.side_effect = call_response
self.driver._get_image_info('context', 'image_meta_id', 'os_distro')
call.assert_any_call('image_query', imagename='image_meta_id')
image_import.assert_called_once_with('context', 'image_meta_id',
'os_distro')
call.assert_any_call('image_query', imagename='image_meta_id')
@mock.patch('nova.virt.driver.ComputeDriver.instance_exists')
@mock.patch('nova.virt.zvm.guest.Guest.guest_power_action')
def test_power_on(self, ipa, mock_exists):
mock_exists.return_value = True
self._driver.power_on(None, self._instance, None)
ipa.assert_called_once_with('guest_start')
@mock.patch('nova_zvm.virt.zvm.utils.zVMConnectorRequestHandler.call')
def test_private_get_image_info_exist(self, call):
call.return_value = 'image-info'
res = self.driver._get_image_info('context', 'image_meta_id',
'os_distro')
call.assert_any_call('image_query', imagename='image_meta_id')
self.assertEqual('image-info', res)
@mock.patch('nova.virt.driver.ComputeDriver.instance_exists')
@mock.patch('nova.virt.zvm.guest.Guest.guest_power_action')
def test_pause(self, ipa, mock_exists):
mock_exists.return_value = True
self._driver.pause(self._instance)
ipa.assert_called_once_with('guest_pause')
@mock.patch('nova_zvm.virt.zvm.utils.zVMConnectorRequestHandler.call')
def _test_set_disk_list(self, call, has_get_root_units=False,
has_eph_disks=False):
disk_list = [{'is_boot_disk': True, 'size': '3g'}]
eph_disk_list = [{'format': u'ext3', 'size': '1g'},
{'format': u'ext4', 'size': '2g'}]
_inst = copy.copy(self._instance)
_bdi = copy.copy(self._block_device_info)
@mock.patch('nova.virt.driver.ComputeDriver.instance_exists')
@mock.patch('nova.virt.zvm.guest.Guest.guest_power_action')
def test_unpause(self, ipa, mock_exists):
mock_exists.return_value = True
self._driver.unpause(self._instance)
ipa.assert_called_once_with('guest_unpause')
if has_get_root_units:
# overwrite
disk_list = [{'is_boot_disk': True, 'size': '3338'}]
call.return_value = '3338'
_inst['root_gb'] = 0
else:
_inst['root_gb'] = 3
@mock.patch('nova.virt.driver.ComputeDriver.instance_exists')
@mock.patch('nova.virt.zvm.guest.Guest.guest_power_action')
def test_reboot_soft(self, ipa, mock_exists):
mock_exists.return_value = True
self._driver.reboot(None, self._instance, None, 'SOFT')
ipa.assert_called_once_with('guest_reboot')
if has_eph_disks:
disk_list += eph_disk_list
else:
_bdi['ephemerals'] = []
eph_disk_list = []
@mock.patch('nova.virt.driver.ComputeDriver.instance_exists')
@mock.patch('nova.virt.zvm.guest.Guest.guest_power_action')
def test_reboot_hard(self, ipa, mock_exists):
mock_exists.return_value = True
self._driver.reboot(None, self._instance, None, 'HARD')
ipa.assert_called_once_with('guest_reset')
res1, res2 = self.driver._set_disk_list(_inst, self._image_meta.id,
_bdi)
if has_get_root_units:
call.assert_called_once_with('image_get_root_disk_size',
self._image_meta.id)
self.assertEqual(disk_list, res1)
self.assertEqual(eph_disk_list, res2)
def test_private_set_disk_list_simple(self):
self._test_set_disk_list()
def test_private_set_disk_list_with_eph_disks(self):
self._test_set_disk_list(has_eph_disks=True)
def test_private_set_disk_list_with_get_root_units(self):
self._test_set_disk_list(has_get_root_units=True)
@mock.patch('nova_zvm.virt.zvm.utils.zVMConnectorRequestHandler.call')
def test_private_setup_network(self, call):
inst_nets = []
_net = {'ip_addr': '192.168.0.100',
'gateway_addr': '192.168.0.1',
'cidr': '192.168.0.1/24',
'mac_addr': 'DE:AD:BE:EF:00:00',
'nic_id': None}
inst_nets.append(_net)
self.driver._setup_network('vm_name', 'os_distro', self._network_info,
self._instance)
call.assert_any_call('guest_create_network_interface',
'vm_name', 'os_distro', inst_nets)
def test_private_nic_coupled(self):
user_direct = {'user_direct':
['User TEST',
"NICDEF 1000 TYPE QDIO LAN SYSTEM TESTVS"]}
res = self.driver._nic_coupled(user_direct, '1000', 'TESTVS')
self.assertTrue(res)
res = self.driver._nic_coupled(user_direct, '2000', 'TESTVS')
self.assertFalse(res)
res = self.driver._nic_coupled(user_direct, '1000', None)
self.assertFalse(res)
@mock.patch('pwd.getpwuid')
def test_private_get_host(self, getpwuid):
class FakePwuid(object):
def __init__(self):
self.pw_name = 'test'
getpwuid.return_value = FakePwuid()
res = self.driver._get_host()
self.assertEqual('test@192.168.1.1', res)
@mock.patch('nova.virt.images.fetch')
@mock.patch('os.path.exists')
@mock.patch('nova_zvm.virt.zvm.driver.ZVMDriver._get_host')
@mock.patch('nova_zvm.virt.zvm.utils.zVMConnectorRequestHandler.call')
def test_private_import_spawn_image(self, call, get_host, exists, fetch):
get_host.return_value = 'test@192.168.1.1'
exists.return_value = False
image_url = "file:///test/image/image_name"
image_meta = {'os_version': 'os_version'}
self.driver._import_spawn_image(self._context, 'image_name',
'os_version')
fetch.assert_called_once_with(self._context, 'image_name',
"/test/image/image_name")
get_host.assert_called_once_with()
call.assert_called_once_with('image_import', 'image_name', image_url,
image_meta, remote_host='test@192.168.1.1')
@mock.patch('nova_zvm.virt.zvm.driver.ZVMDriver._instance_exists')
@mock.patch('nova_zvm.virt.zvm.utils.zVMConnectorRequestHandler.call')
def test_destroy(self, call, instance_exists):
instance_exists.return_value = True
self.driver.destroy(self._context, self._instance,
network_info=self._network_info)
call.assert_called_once_with('guest_delete', self._instance['name'])
def test_get_host_uptime(self):
with mock.patch('nova_zvm.virt.zvm.utils.'
'zVMConnectorRequestHandler.call') as mcall:
mcall.return_value = {'hypervisor_hostname': 'TESTHOST',
'ipl_time': 'TESTTIME'}
time = self.driver.get_host_uptime()
self.assertEqual('TESTTIME', time)
def test_spawn_invalid_userid(self):
self.flags(instance_name_template='test%05x')
self.addCleanup(self.flags, instance_name_template='test%04x')
invalid_inst = fake_instance.fake_instance_obj(self._context,
name='123456789')
self.assertRaises(exception.InvalidInput, self.driver.spawn,
self._context, invalid_inst, self._image_meta,
injected_files=None, admin_password=None,
allocations=None, network_info=self._network_info,
block_device_info=self._block_device_info,
flavor=self._flavor)
@mock.patch('nova_zvm.virt.zvm.driver.ZVMDriver._wait_network_ready')
@mock.patch('nova_zvm.virt.zvm.driver.ZVMDriver._setup_network')
@mock.patch('nova_zvm.virt.zvm.driver.ZVMDriver._get_host')
@mock.patch('nova_zvm.virt.zvm.driver.ZVMDriver._set_disk_list')
@mock.patch.object(zvmutils.VMUtils, 'generate_configdrive')
@mock.patch('nova_zvm.virt.zvm.driver.ZVMDriver._get_image_info')
@mock.patch('nova_zvm.virt.zvm.utils.zVMConnectorRequestHandler.call')
def test_spawn(self, call, get_image_info, gen_conf_file, set_disk_list,
get_host, setup_network, wait_ready):
_inst = copy.copy(self._instance)
_bdi = copy.copy(self._block_device_info)
get_image_info.return_value = [{'imagename': 'image_name'}]
gen_conf_file.return_value = 'transportfiles'
set_disk_list.return_value = 'disk_list', 'eph_list'
get_host.return_value = 'test@192.168.1.1'
setup_network.return_value = ''
wait_ready.return_value = ''
call_resp = ['', '', '', '']
call.side_effect = call_resp
self.driver.spawn(self._context, _inst, self._image_meta,
injected_files=None, admin_password=None,
allocations=None, network_info=self._network_info,
block_device_info=_bdi, flavor=self._flavor)
gen_conf_file.assert_called_once_with(self._context, _inst,
None, None)
get_image_info.assert_called_once_with(self._context,
self._image_meta.id,
self._image_meta.properties.os_distro)
set_disk_list.assert_called_once_with(_inst, 'image_name', _bdi)
call.assert_any_call('guest_create', _inst['name'],
1, 1024, disk_list='disk_list')
get_host.assert_called_once_with()
call.assert_any_call('guest_deploy', _inst['name'], 'image_name',
transportfiles='transportfiles',
remotehost='test@192.168.1.1')
setup_network.assert_called_once_with(_inst['name'],
self._image_meta.properties.os_distro,
self._network_info, _inst)
call.assert_any_call('guest_config_minidisks', _inst['name'],
'eph_list')
wait_ready.assert_called_once_with(_inst)
call.assert_any_call('guest_start', _inst['name'])
@mock.patch('nova_zvm.virt.zvm.driver.ZVMDriver._nic_coupled')
@mock.patch('nova_zvm.virt.zvm.utils.zVMConnectorRequestHandler.call')
def test_private_wait_network_ready(self, call, nic_coupled):
call_resp = []
switch_dict = {'1000': 'TESTVM'}
user_direct = {'user_direct':
['User TEST',
"NICDEF 1000 TYPE QDIO LAN SYSTEM TESTVS"]}
call_resp.append(switch_dict)
call_resp.append(user_direct)
call.side_effect = call_resp
nic_coupled.return_value = True
self.driver._wait_network_ready(self._instance)
call.assert_any_call('guest_get_nic_vswitch_info',
self._instance['name'])
call.assert_any_call('guest_get_definition_info',
self._instance['name'])
@mock.patch('nova_zvm.virt.zvm.utils.zVMConnectorRequestHandler.call')
def test_instance_power_action(self, call):
call.side_effect = [['TEST0001', 'TEST0002'], None]
self.driver._instance_power_action(self._instance, 'guest_start')
call.assert_any_call('guest_list')
call.assert_any_call('guest_start', 'test0001')
@mock.patch('nova_zvm.virt.zvm.utils.zVMConnectorRequestHandler.call')
def test_instance_power_action_not_exist(self, call):
call.return_value = ['test0002', 'test0003']
self.driver._instance_power_action(self._instance, 'guest_start')
call.assert_any_call('guest_list')
@mock.patch('nova_zvm.virt.zvm.driver.ZVMDriver._instance_power_action')
def test_power_off(self, ipa):
self.driver.power_off(self._instance)
ipa.assert_called_once_with(self._instance, 'guest_softstop')
self.driver.power_off(self._instance, 60, 10)
ipa.assert_any_call(self._instance, 'guest_softstop',
timeout=60, poll_interval=10)
@mock.patch('nova_zvm.virt.zvm.driver.ZVMDriver._instance_power_action')
def test_power_on(self, ipa):
self.driver.power_on(None, self._instance, None)
ipa.assert_called_once_with(self._instance, 'guest_start')
@mock.patch('nova_zvm.virt.zvm.driver.ZVMDriver._instance_power_action')
def test_pause(self, ipa):
self.driver.pause(self._instance)
ipa.assert_called_once_with(self._instance, 'guest_pause')
@mock.patch('nova_zvm.virt.zvm.driver.ZVMDriver._instance_power_action')
def test_unpause(self, ipa):
self.driver.unpause(self._instance)
ipa.assert_called_once_with(self._instance, 'guest_unpause')
@mock.patch('nova_zvm.virt.zvm.driver.ZVMDriver._instance_power_action')
def test_reboot_soft(self, ipa):
self.driver.reboot(self._context, self._instance, self._network_info,
'SOFT')
ipa.assert_called_once_with(self._instance, 'guest_reboot')
@mock.patch('nova_zvm.virt.zvm.driver.ZVMDriver._instance_power_action')
def test_reboot_hard(self, ipa):
self.driver.reboot(self._context, self._instance, self._network_info,
'HARD')
ipa.assert_called_once_with(self._instance, 'guest_reset')
@mock.patch('nova_zvm.virt.zvm.utils.zVMConnectorRequestHandler.call')
@mock.patch('nova.virt.zvm.utils.ConnectorClient.call')
def test_get_console_output(self, call):
call.return_value = 'console output'
outputs = self.driver.get_console_output(None, self._instance)
call.test_assert_called_once_with('guest_get_console_output',
'test0001')
outputs = self._driver.get_console_output(None, self._instance)
call.assert_called_once_with('guest_get_console_output', 'abc00001')
self.assertEqual('console output', outputs)
@mock.patch.object(builtins, 'open')
@mock.patch('nova_zvm.virt.zvm.driver.ZVMDriver._get_host')
@mock.patch('nova.image.glance.get_remote_image_service', )
@mock.patch('nova_zvm.virt.zvm.utils.zVMConnectorRequestHandler.call')
def test_snapshot(self, call, get_image_service, get_host, mock_open):
image_service = mock.Mock()
image_id = 'e9ee1562-3ea1-4cb1-9f4c-f2033000eab1'
get_image_service.return_value = (image_service, image_id)
host_info = 'nova@192.168.99.1'
get_host.return_value = host_info
call_resp = ['', {"os_version": "rhel7.2",
"dest_url": "file:///path/to/target"}, '']
call.side_effect = call_resp
new_image_meta = {
'is_public': False,
'status': 'active',
'properties': {
'image_location': 'snapshot',
'image_state': 'available',
'owner_id': self._instance['project_id'],
'os_distro': call_resp[1]['os_version'],
'architecture': const.ARCHITECTURE,
'hypervisor_type': const.HYPERVISOR_TYPE
},
'disk_format': 'raw',
'container_format': 'bare',
}
image_path = os.path.join(os.path.normpath(
CONF.zvm_image_tmp_path), image_id)
dest_path = "file://" + image_path
self.driver.snapshot(self._context, self._instance, image_id,
self.mock_update_task_state)
get_image_service.assert_called_with(self._context, image_id)
call.assert_any_call('guest_capture',
self._instance['name'], image_id)
mock_open.assert_called_once_with(image_path, 'r')
image_service.update.assert_called_once_with(self._context,
image_id,
new_image_meta,
mock_open.return_value.__enter__.return_value,
purge_props=False)
call.assert_any_call('image_export', image_id, dest_path,
remote_host=host_info)
call.assert_any_call('image_delete', image_id)

View File

@ -0,0 +1,426 @@
# Copyright 2017,2018 IBM Corp.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import copy
import mock
import os
import six
from nova.compute import power_state as compute_power_state
from nova import conf
from nova import context
from nova import exception
from nova.network import model as network_model
from nova import objects
from nova import test
from nova.tests.unit import fake_instance
from nova.tests import uuidsentinel
from nova.virt import fake
from nova.virt.zvm import driver
from nova.virt.zvm import guest
CONF = conf.CONF
class TestZVMGuestOp(test.NoDBTestCase):
def setUp(self):
super(TestZVMGuestOp, self).setUp()
self.flags(cloud_connector_url='https://1.1.1.1:1111',
image_tmp_path='/test/image',
reachable_timeout=300, group='zvm')
self.flags(my_ip='192.168.1.1',
instance_name_template='test%04x')
with test.nested(
mock.patch('nova.virt.zvm.utils.ConnectorClient.call'),
mock.patch('pwd.getpwuid'),
) as (mcall, getpwuid):
getpwuid.return_value = mock.Mock(pw_name='test')
mcall.return_value = {'hypervisor_hostname': 'TESTHOST',
'ipl_time': 'TESTTIME'}
self._driver = driver.ZVMDriver(fake.FakeVirtAPI())
self._hypervisor = self._driver._hypervisor
self._context = context.RequestContext('fake_user', 'fake_project')
self._image_id = uuidsentinel.imag_id
self._instance_values = {
'display_name': 'test',
'uuid': uuidsentinel.inst_id,
'vcpus': 1,
'memory_mb': 1024,
'image_ref': self._image_id,
'root_gb': 0,
}
self._instance = fake_instance.fake_instance_obj(
self._context, **self._instance_values)
self._guest = guest.Guest(self._hypervisor, self._instance,
self._driver.virtapi)
self._flavor = objects.Flavor(name='testflavor', memory_mb=512,
vcpus=1, root_gb=3, ephemeral_gb=10,
swap=0, extra_specs={})
self._instance.flavor = self._flavor
self._eph_disks = [{'guest_format': u'ext3',
'device_name': u'/dev/sdb',
'disk_bus': None,
'device_type': None,
'size': 1},
{'guest_format': u'ext4',
'device_name': u'/dev/sdc',
'disk_bus': None,
'device_type': None,
'size': 2}]
self._block_device_info = {'swap': None,
'root_device_name': u'/dev/sda',
'ephemerals': self._eph_disks,
'block_device_mapping': []}
fake_image_meta = {'status': 'active',
'properties': {'os_distro': 'rhel7.2'},
'name': 'rhel72eckdimage',
'deleted': False,
'container_format': 'bare',
'disk_format': 'raw',
'id': self._image_id,
'owner': 'cfc26f9d6af948018621ab00a1675310',
'checksum': 'b026cd083ef8e9610a29eaf71459cc',
'min_disk': 0,
'is_public': False,
'deleted_at': None,
'min_ram': 0,
'size': 465448142}
self._image_meta = objects.ImageMeta.from_dict(fake_image_meta)
subnet_4 = network_model.Subnet(cidr='192.168.0.1/24',
dns=[network_model.IP('192.168.0.1')],
gateway=
network_model.IP('192.168.0.1'),
ips=[
network_model.IP('192.168.0.100')],
routes=None)
network = network_model.Network(id=0,
bridge='fa0',
label='fake',
subnets=[subnet_4],
vlan=None,
bridge_interface=None,
injected=True)
self._network_values = {
'id': None,
'address': 'DE:AD:BE:EF:00:00',
'network': network,
'type': network_model.VIF_TYPE_OVS,
'devname': None,
'ovs_interfaceid': None,
'rxtx_cap': 3
}
self._network_info = network_model.NetworkInfo([
network_model.VIF(**self._network_values)
])
self.mock_update_task_state = mock.Mock()
def test_private_mapping_power_state(self):
status = self._guest._mapping_power_state('on')
self.assertEqual(compute_power_state.RUNNING, status)
status = self._guest._mapping_power_state('off')
self.assertEqual(compute_power_state.SHUTDOWN, status)
status = self._guest._mapping_power_state('bad')
self.assertEqual(compute_power_state.NOSTATE, status)
@mock.patch('nova.virt.zvm.utils.ConnectorClient.call')
def test_get_info_err_InstanceNotFound(self, call):
call.side_effect = exception.NovaException(results={'overallRC': 404})
self.assertRaises(exception.InstanceNotFound, self._guest.get_info)
@mock.patch('nova.virt.zvm.utils.ConnectorClient.call')
def test_get_info_err_general(self, call):
call.side_effect = exception.NovaException(results={'overallRC': 500})
self.assertRaises(exception.NovaException, self._guest.get_info)
@mock.patch('nova.virt.zvm.utils.ConnectorClient.call')
def test_get_info(self, call):
call.return_value = 'on'
info = self._guest.get_info()
call.assert_called_once_with('guest_get_power_state',
self._instance['name'])
self.assertEqual(info.state, compute_power_state.RUNNING)
@mock.patch('nova.virt.zvm.utils.ConnectorClient.call')
def test_private_get_image_info_err(self, call):
call.side_effect = exception.NovaException(results={'overallRC': 500})
self.assertRaises(exception.NovaException,
self._guest._get_image_info,
'context', 'image_meta_id', 'os_distro')
@mock.patch('nova.virt.zvm.utils.ConnectorClient.call')
@mock.patch('nova.virt.zvm.guest.Guest._import_spawn_image')
def test_private_get_image_info(self, image_import, call):
call_response = []
call_response.append(exception.NovaException(results=
{'overallRC': 404}))
call_response.append('Query_Result')
call.side_effect = call_response
self._guest._get_image_info('context', 'image_meta_id', 'os_distro')
image_import.assert_called_once_with('context', 'image_meta_id',
'os_distro')
call.assert_has_calls([
mock.call('image_query', imagename='image_meta_id'),
mock.call('image_query', imagename='image_meta_id')
])
@mock.patch('nova.virt.zvm.utils.ConnectorClient.call')
def test_private_get_image_info_exist(self, call):
call.return_value = 'image-info'
res = self._guest._get_image_info('context', 'image_meta_id',
'os_distro')
call.assert_called_once_with('image_query', imagename='image_meta_id')
self.assertEqual('image-info', res)
@mock.patch('nova.virt.zvm.utils.ConnectorClient.call')
def _test_set_disk_list(self, call, has_get_root_units=False,
has_eph_disks=False):
disk_list = [{'is_boot_disk': True, 'size': '3g'}]
eph_disk_list = [{'format': u'ext3', 'size': '1g'},
{'format': u'ext4', 'size': '2g'}]
_inst = copy.copy(self._instance)
_bdi = copy.copy(self._block_device_info)
if has_get_root_units:
# overwrite
disk_list = [{'is_boot_disk': True, 'size': '3338'}]
call.return_value = '3338'
_inst['root_gb'] = 0
else:
_inst['root_gb'] = 3
if has_eph_disks:
disk_list += eph_disk_list
else:
_bdi['ephemerals'] = []
eph_disk_list = []
res1, res2 = self._guest._set_disk_list(_inst, self._image_meta.id,
_bdi)
if has_get_root_units:
call.assert_called_once_with('image_get_root_disk_size',
self._image_meta.id)
self.assertEqual(disk_list, res1)
self.assertEqual(eph_disk_list, res2)
def test_private_set_disk_list_simple(self):
self._test_set_disk_list()
def test_private_set_disk_list_with_eph_disks(self):
self._test_set_disk_list(has_eph_disks=True)
def test_private_set_disk_list_with_get_root_units(self):
self._test_set_disk_list(has_get_root_units=True)
@mock.patch('nova.virt.zvm.utils.ConnectorClient.call')
def test_private_setup_network(self, call):
inst_nets = []
_net = {'ip_addr': '192.168.0.100',
'gateway_addr': '192.168.0.1',
'cidr': '192.168.0.1/24',
'mac_addr': 'DE:AD:BE:EF:00:00',
'nic_id': None}
inst_nets.append(_net)
self._guest._setup_network('vm_name', 'os_distro',
self._network_info,
self._instance)
call.assert_called_once_with('guest_create_network_interface',
'vm_name', 'os_distro', inst_nets)
@mock.patch('nova.virt.images.fetch')
@mock.patch('nova.virt.zvm.utils.ConnectorClient.call')
def test_private_import_spawn_image(self, call, fetch):
image_name = CONF.zvm.image_tmp_path + '/image_name'
image_url = "file://" + image_name
image_meta = {'os_version': 'os_version'}
with mock.patch('os.path.exists', side_effect=[False]):
self._guest._import_spawn_image(self._context, 'image_name',
'os_version')
fetch.assert_called_once_with(self._context, 'image_name',
image_name)
call.assert_called_once_with('image_import', 'image_name', image_url,
image_meta, 'test@192.168.1.1')
@mock.patch('nova.virt.zvm.hypervisor.Hypervisor.guest_exists')
@mock.patch('nova.virt.zvm.utils.ConnectorClient.call')
def test_destroy(self, call, guest_exists):
guest_exists.return_value = True
self._guest.destroy(self._context, network_info=self._network_info)
call.assert_called_once_with('guest_delete', self._instance['name'])
@mock.patch('nova.virt.zvm.hypervisor.Hypervisor.guest_exists')
@mock.patch('nova.compute.manager.ComputeVirtAPI.wait_for_instance_event')
@mock.patch('nova.virt.zvm.guest.Guest._setup_network')
@mock.patch('nova.virt.zvm.guest.Guest._set_disk_list')
@mock.patch('nova.virt.zvm.utils.generate_configdrive')
@mock.patch('nova.virt.zvm.guest.Guest._get_image_info')
@mock.patch('nova.virt.zvm.utils.ConnectorClient.call')
def test_spawn(self, call, get_image_info, gen_conf_file, set_disk_list,
setup_network, mock_wait, mock_exists):
_bdi = copy.copy(self._block_device_info)
get_image_info.return_value = [{'imagename': 'image_name'}]
gen_conf_file.return_value = 'transportfiles'
set_disk_list.return_value = 'disk_list', 'eph_list'
mock_exists.return_value = False
self._guest.spawn(self._context, self._image_meta,
injected_files=None, admin_password=None,
allocations=None, network_info=self._network_info,
block_device_info=_bdi, flavor=self._flavor)
gen_conf_file.assert_called_once_with(self._context, self._instance,
None, None)
get_image_info.assert_called_once_with(self._context,
self._image_meta.id,
self._image_meta.properties.os_distro)
set_disk_list.assert_called_once_with(self._instance, 'image_name',
_bdi)
setup_network.assert_called_once_with(self._instance.name,
self._image_meta.properties.os_distro,
self._network_info, self._instance)
call.assert_has_calls([
mock.call('guest_create', self._instance.name,
1, 1024, disk_list='disk_list'),
mock.call('guest_deploy', self._instance.name, 'image_name',
'transportfiles', 'test@192.168.1.1'),
mock.call('guest_config_minidisks', self._instance.name,
'eph_list'),
mock.call('guest_start', self._instance.name)
])
@mock.patch.object(six.moves.builtins, 'open')
@mock.patch('nova.image.glance.get_remote_image_service')
@mock.patch('nova.virt.zvm.utils.ConnectorClient.call')
def test_snapshot(self, call, get_image_service, mock_open):
image_service = mock.Mock()
image_id = 'e9ee1562-3ea1-4cb1-9f4c-f2033000eab1'
get_image_service.return_value = (image_service, image_id)
call_resp = ['', {"os_version": "rhel7.2",
"dest_url": "file:///path/to/target"}, '']
call.side_effect = call_resp
new_image_meta = {
'is_public': False,
'status': 'active',
'properties': {
'image_location': 'snapshot',
'image_state': 'available',
'owner_id': self._instance['project_id'],
'os_distro': call_resp[1]['os_version'],
'architecture': 's390x',
'hypervisor_type': 'zvm'
},
'disk_format': 'raw',
'container_format': 'bare',
}
image_path = os.path.join(os.path.normpath(
CONF.zvm.image_tmp_path), image_id)
dest_path = "file://" + image_path
self._guest.snapshot(self._context, image_id,
self.mock_update_task_state)
get_image_service.assert_called_with(self._context, image_id)
mock_open.assert_called_once_with(image_path, 'r')
ret_file = mock_open.return_value.__enter__.return_value
image_service.update.assert_called_once_with(self._context,
image_id,
new_image_meta,
ret_file,
purge_props=False)
self.mock_update_task_state.assert_has_calls([
mock.call(task_state='image_pending_upload'),
mock.call(expected_state='image_pending_upload',
task_state='image_uploading')
])
call.assert_has_calls([
mock.call('guest_capture', self._instance.name, image_id),
mock.call('image_export', image_id, dest_path, 'test@192.168.1.1'),
mock.call('image_delete', image_id)
])
@mock.patch('nova.image.glance.get_remote_image_service')
@mock.patch('nova.virt.zvm.hypervisor.Hypervisor.guest_capture')
def test_snapshot_capture_fail(self, mock_capture, get_image_service):
image_service = mock.Mock()
image_id = 'e9ee1562-3ea1-4cb1-9f4c-f2033000eab1'
get_image_service.return_value = (image_service, image_id)
mock_capture.side_effect = exception.ZVMDriverException(error='error')
self.assertRaises(exception.ZVMDriverException, self._guest.snapshot,
self._context, image_id, self.mock_update_task_state)
self.mock_update_task_state.assert_called_once_with(
task_state='image_pending_upload')
image_service.delete.assert_called_once_with(self._context, image_id)
@mock.patch('nova.image.glance.get_remote_image_service')
@mock.patch('nova.virt.zvm.utils.ConnectorClient.call')
@mock.patch('nova.virt.zvm.hypervisor.Hypervisor.image_delete')
@mock.patch('nova.virt.zvm.hypervisor.Hypervisor.image_export')
def test_snapshot_import_fail(self, mock_import, mock_delete,
call, get_image_service):
image_service = mock.Mock()
image_id = 'e9ee1562-3ea1-4cb1-9f4c-f2033000eab1'
get_image_service.return_value = (image_service, image_id)
mock_import.side_effect = exception.ZVMDriverException(error='error')
self.assertRaises(exception.ZVMDriverException, self._guest.snapshot,
self._context, image_id, self.mock_update_task_state)
self.mock_update_task_state.assert_called_once_with(
task_state='image_pending_upload')
get_image_service.assert_called_with(self._context, image_id)
call.assert_called_once_with('guest_capture',
self._instance.name, image_id)
mock_delete.assert_called_once_with(image_id)
image_service.delete.assert_called_once_with(self._context, image_id)
@mock.patch.object(six.moves.builtins, 'open')
@mock.patch('nova.image.glance.get_remote_image_service')
@mock.patch('nova.virt.zvm.utils.ConnectorClient.call')
@mock.patch('nova.virt.zvm.hypervisor.Hypervisor.image_delete')
@mock.patch('nova.virt.zvm.hypervisor.Hypervisor.image_export')
def test_snapshot_update_fail(self, mock_import, mock_delete, call,
get_image_service, mock_open):
image_service = mock.Mock()
image_id = 'e9ee1562-3ea1-4cb1-9f4c-f2033000eab1'
get_image_service.return_value = (image_service, image_id)
image_service.update.side_effect = exception.ImageNotAuthorized(
image_id='dummy')
image_path = os.path.join(os.path.normpath(
CONF.zvm.image_tmp_path), image_id)
self.assertRaises(exception.ImageNotAuthorized, self._guest.snapshot,
self._context, image_id, self.mock_update_task_state)
mock_open.assert_called_once_with(image_path, 'r')
get_image_service.assert_called_with(self._context, image_id)
mock_delete.assert_called_once_with(image_id)
image_service.delete.assert_called_once_with(self._context, image_id)
self.mock_update_task_state.assert_has_calls([
mock.call(task_state='image_pending_upload'),
mock.call(expected_state='image_pending_upload',
task_state='image_uploading')
])
call.assert_called_once_with('guest_capture', self._instance.name,
image_id)

View File

@ -0,0 +1,92 @@
# Copyright 2017,2018 IBM Corp.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import mock
from nova import context
from nova import exception
from nova import test
from nova.tests.unit import fake_instance
from nova.virt.zvm import driver as zvmdriver
class TestZVMHypervisor(test.NoDBTestCase):
def setUp(self):
super(TestZVMHypervisor, self).setUp()
self.flags(instance_name_template='abc%5d')
self.flags(cloud_connector_url='https://1.1.1.1:1111', group='zvm')
with mock.patch('nova.virt.zvm.utils.'
'ConnectorClient.call') as mcall:
mcall.return_value = {'hypervisor_hostname': 'TESTHOST',
'ipl_time': 'IPL at 11/14/17 10:47:44 EST'}
driver = zvmdriver.ZVMDriver('virtapi')
self._hypervisor = driver._hypervisor
self._context = context.RequestContext('fake_user', 'fake_project')
@mock.patch('nova.virt.zvm.utils.ConnectorClient.call')
def test_get_available_resource(self, call):
host_info = {'disk_available': 1144,
'ipl_time': 'IPL at 11/14/17 10:47:44 EST',
'vcpus_used': 4,
'hypervisor_type': 'zvm',
'disk_total': 2000,
'zvm_host': 'TESTHOST',
'memory_mb': 78192.0,
'cpu_info': {'cec_model': '2827',
'architecture': 's390x'},
'vcpus': 84,
'hypervisor_hostname': 'TESTHOST',
'hypervisor_version': 640,
'disk_used': 856,
'memory_mb_used': 8192.0}
call.return_value = host_info
results = self._hypervisor.get_available_resource()
self.assertEqual(host_info, results)
@mock.patch('nova.virt.zvm.utils.ConnectorClient.call')
def test_get_available_resource_err_case(self, call):
call.side_effect = exception.ZVMDriverException(error='dummy')
results = self._hypervisor.get_available_resource()
# Should return an empty dict
self.assertFalse(results)
def test_get_available_nodes(self):
nodes = self._hypervisor.get_available_nodes()
self.assertEqual(['TESTHOST'], nodes)
@mock.patch('nova.virt.zvm.utils.ConnectorClient.call')
def test_list_names(self, call):
call.return_value = ['vm1', 'vm2']
inst_list = self._hypervisor.list_names()
self.assertEqual(['vm1', 'vm2'], inst_list)
def test_get_host_uptime(self):
time = self._hypervisor.get_host_uptime()
self.assertEqual('IPL at 11/14/17 10:47:44 EST', time)
@mock.patch('nova.virt.zvm.hypervisor.Hypervisor.list_names')
def test_private_guest_exists_true(self, list_names):
instance = fake_instance.fake_instance_obj(self._context)
list_names.return_value = [instance.name, 'test0002']
res = self._hypervisor.guest_exists(instance)
self.assertTrue(res)
@mock.patch('nova.virt.zvm.hypervisor.Hypervisor.list_names')
def test_private_guest_exists_false(self, list_names):
list_names.return_value = ['dummy1', 'dummy2']
instance = fake_instance.fake_instance_obj(self._context)
res = self._hypervisor.guest_exists(instance)
self.assertFalse(res)

View File

@ -0,0 +1,141 @@
# Copyright 2017,2018 IBM Corp.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import mock
from zvmconnector import connector
from nova import context
from nova import exception
from nova import test
from nova.tests.unit import fake_instance
from nova.virt.zvm import utils as zvmutils
class TestZVMUtils(test.NoDBTestCase):
def setUp(self):
super(TestZVMUtils, self).setUp()
self.flags(cloud_connector_url='http://127.0.0.1', group='zvm')
self._url = 'http://127.0.0.1'
def test_connector_request_handler_invalid_url(self):
rh = zvmutils.ConnectorClient('http://invalid')
self.assertRaises(exception.ZVMDriverException, rh.call, 'guest_list')
@mock.patch('zvmconnector.connector.ZVMConnector.__init__',
return_value=None)
def test_connector_request_handler_https(self, mock_init):
rh = zvmutils.ConnectorClient('https://127.0.0.1:80',
ca_file='/tmp/file')
mock_init.assert_called_once_with('127.0.0.1', 80, ssl_enabled=True,
verify='/tmp/file')
self.assertIsInstance(rh._conn, connector.ZVMConnector)
@mock.patch('zvmconnector.connector.ZVMConnector.__init__',
return_value=None)
def test_connector_request_handler_https_noca(self, mock_init):
rh = zvmutils.ConnectorClient('https://127.0.0.1:80')
mock_init.assert_called_once_with('127.0.0.1', 80, ssl_enabled=True,
verify=False)
self.assertIsInstance(rh._conn, connector.ZVMConnector)
@mock.patch('zvmconnector.connector.ZVMConnector.__init__',
return_value=None)
def test_connector_request_handler_http(self, mock_init):
rh = zvmutils.ConnectorClient('http://127.0.0.1:80')
mock_init.assert_called_once_with('127.0.0.1', 80, ssl_enabled=False,
verify=False)
self.assertIsInstance(rh._conn, connector.ZVMConnector)
@mock.patch('zvmconnector.connector.ZVMConnector.send_request')
def test_connector_request_handler(self, mock_send):
mock_send.return_value = {'overallRC': 0, 'output': 'data'}
rh = zvmutils.ConnectorClient(self._url)
res = rh.call('guest_list')
self.assertEqual('data', res)
@mock.patch('zvmconnector.connector.ZVMConnector.send_request')
def test_connector_request_handler_error(self, mock_send):
expected = {'overallRC': 1, 'errmsg': 'err'}
mock_send.return_value = expected
rh = zvmutils.ConnectorClient(self._url)
exc = self.assertRaises(exception.ZVMDriverException, rh.call,
'guest_list')
self.assertEqual('zVM Cloud Connector request failed',
exc.format_message())
self.assertEqual(expected, exc.kwargs['results'])
@mock.patch('nova.virt.zvm.utils._get_instances_path')
def test_get_instance_path(self, fake_get):
fake_get.return_value = '/test/tmp'
with mock.patch('os.path.exists') as fake_exist:
fake_exist.return_value = True
folder = zvmutils.get_instance_path('fake_uuid')
self.assertEqual('/test/tmp/fake_uuid', folder)
fake_get.assert_called_once_with()
@mock.patch('nova.virt.configdrive.required_by')
@mock.patch('nova.virt.zvm.utils._create_config_drive')
@mock.patch('nova.virt.zvm.utils.get_instance_path')
def test_generate_configdrive(self, get, create, required):
get.return_value = '/test/tmp/fake_uuid'
create.return_value = '/test/cfgdrive.tgz'
required.return_value = True
ctxt = context.RequestContext('fake_user', 'fake_project')
instance = fake_instance.fake_instance_obj(ctxt)
file = zvmutils.generate_configdrive('context', instance,
'injected_files',
'admin_password')
required.assert_called_once_with(instance)
create.assert_called_once_with('context', '/test/tmp/fake_uuid',
instance, 'injected_files',
'admin_password')
self.assertEqual('/test/cfgdrive.tgz', file)
@mock.patch('nova.api.metadata.base.InstanceMetadata')
@mock.patch('nova.virt.zvm.configdrive.ZVMConfigDriveBuilder.make_drive')
def test_create_config_drive(self, make_drive, mock_instance_metadata):
class FakeInstanceMetadata(object):
def __init__(self):
self.network_metadata = None
def metadata_for_config_drive(self):
return []
mock_instance_metadata.return_value = FakeInstanceMetadata()
self.flags(config_drive_format='iso9660')
extra_md = {'admin_pass': 'admin_password'}
zvmutils._create_config_drive('context', '/instance_path',
'instance', 'injected_files',
'admin_password')
mock_instance_metadata.assert_called_once_with('instance',
content='injected_files',
extra_md=extra_md,
request_context='context')
make_drive.assert_called_once_with('/instance_path/cfgdrive.tgz')
def test_create_config_drive_invalid_format(self):
self.flags(config_drive_format='vfat')
self.assertRaises(exception.ConfigDriveUnsupportedFormat,
zvmutils._create_config_drive, 'context',
'/instance_path', 'instance', 'injected_files',
'admin_password')

View File

@ -1,4 +1,4 @@
# Copyright 2013 IBM Corp.
# Copyright 2017,2018 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,18 +12,6 @@
# License for the specific language governing permissions and limitations
# under the License.
"""A connection to an IBM z/VM Virtualization system.
Generally, OpenStack z/VM virt driver will call xCat REST API to operate
to z/VM hypervisors.xCat has a control point(a virtual machine) in z/VM
system, which enables xCat management node to control the z/VM system.
OpenStack z/VM driver will communicate with xCat management node through
xCat REST API. Thus OpenStack can operate to z/VM system indirectly.
"""
from nova_zvm.virt.zvm import driver
from nova.virt.zvm import driver
ZVMDriver = driver.ZVMDriver

View File

@ -1,4 +1,4 @@
# Copyright 2013 IBM Corp.
# Copyright 2017,2018 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,7 +12,6 @@
# License for the specific language governing permissions and limitations
# under the License.
import os
import six
import tarfile
@ -42,6 +41,11 @@ class ZVMConfigDriveBuilder(configdrive.ConfigDriveBuilder):
"""
if CONF.config_drive_format in ['iso9660']:
# cloud-init only support iso9660 and vfat, but in z/VM
# implementation, can't link a disk to VM as iso9660 before it's
# boot ,so create a tgz file then send to the VM deployed, and
# during startup process, the tgz file will be extracted and
# mounted as iso9660 format then cloud-init is able to consume it
self._make_tgz(path)
else:
raise exception.ConfigDriveUnknownFormat(
@ -63,6 +67,6 @@ class ZVMConfigDriveBuilder(configdrive.ConfigDriveBuilder):
os.chdir(olddir)
except Exception as e:
emsg = six.text_type(e)
LOG.debug('exception in _make_tgz %s', emsg)
LOG.debug('exception in chdir: %s', emsg)
tar.close()

View File

@ -1,30 +0,0 @@
# Copyright 2013 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
HYPERVISOR_TYPE = 'zvm'
ARCHITECTURE = 's390x'
ALLOWED_VM_TYPE = 'zLinux'
DEFAULT_EPH_DISK_FMT = 'ext3'
ZVM_POWER_STAT = {
'on': power_state.RUNNING,
'off': power_state.SHUTDOWN,
}
REBOOT_TYPE_SOFT = 'SOFT'
REBOOT_TYPE_HARD = 'HARD'

View File

@ -1,4 +1,4 @@
# Copyright 2013 IBM Corp.
# Copyright 2017,2018 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,485 +12,151 @@
# License for the specific language governing permissions and limitations
# under the License.
import datetime
import eventlet
import os
import pwd
import time
from nova.compute import power_state
from nova.compute import task_states
from nova import exception
from nova.i18n import _
from nova.image import glance
from nova.objects import fields as obj_fields
from nova.virt import driver
from nova.virt import hardware
from nova.virt import images
from oslo_log import log as logging
from oslo_serialization import jsonutils
from oslo_service import loopingcall
from oslo_utils import excutils
from oslo_utils import timeutils
from nova_zvm.virt.zvm import conf
from nova_zvm.virt.zvm import const
from nova_zvm.virt.zvm import utils as zvmutils
from nova import conf
from nova import exception
from nova.i18n import _
from nova.objects import fields as obj_fields
from nova.virt import driver
from nova.virt.zvm import guest
from nova.virt.zvm import hypervisor
LOG = logging.getLogger(__name__)
CONF = conf.CONF
class ZVMDriver(driver.ComputeDriver):
"""z/VM implementation of ComputeDriver."""
capabilities = {
"has_imagecache": True,
"supports_recreate": False,
"supports_migrate_to_same_host": True,
"supports_attach_interface": False
}
def __init__(self, virtapi):
super(ZVMDriver, self).__init__(virtapi)
self._reqh = zvmutils.zVMConnectorRequestHandler()
self._vmutils = zvmutils.VMUtils()
self._pathutils = zvmutils.PathUtils()
self._imageop_semaphore = eventlet.semaphore.Semaphore(1)
# get hypervisor host name
res = self._reqh.call('host_get_info')
self._hypervisor_hostname = res['hypervisor_hostname']
self._virtapi = virtapi
self._validate_options()
self._hypervisor = hypervisor.Hypervisor(
CONF.zvm.cloud_connector_url, ca_file=CONF.zvm.ca_file)
LOG.info("The zVM compute driver has been initialized.")
def _validate_options(self):
if not CONF.zvm.cloud_connector_url:
error = _('Must specify cloud_connector_url in zvm config '
'group to use compute_driver=zvm.driver.ZVMDriver')
raise exception.ZVMDriverException(error=error)
# Try a test to ensure length of give guest is smaller than 8
_test_instance = CONF.instance_name_template % 0
# For zVM instance, limit the maximum length of instance name to 8
if len(_test_instance) > 8:
msg = _("Can't spawn instance with template '%s', "
"The zVM hypervisor does not support instance names "
"longer than 8 characters. Please change your config of "
"instance_name_template.") % CONF.instance_name_template
raise exception.ZVMDriverException(error=msg)
def init_host(self, host):
"""Initialize anything that is necessary for the driver to function,
including catching up with currently running VM's on the given host.
"""
pass
def list_instances(self):
"""Return the names of all the instances known to the virtualization
layer, as a list.
"""
return self._reqh.call('guest_list')
def _get_host_status(self):
LOG.debug("Getting host status for %s", CONF.host)
info = self._reqh.call('host_get_info')
host_status = {'host': CONF.host,
'allowed_vm_type': const.ALLOWED_VM_TYPE}
host_status['vcpus'] = info['vcpus']
host_status['vcpus_used'] = info['vcpus_used']
host_status['cpu_info'] = info['cpu_info']
host_status['disk_total'] = info['disk_total']
host_status['disk_used'] = info['disk_used']
host_status['disk_available'] = info['disk_available']
host_status['host_memory_total'] = info['memory_mb']
host_status['host_memory_free'] = (info['memory_mb'] -
info['memory_mb_used'])
host_status['hypervisor_type'] = info['hypervisor_type']
host_status['hypervisor_version'] = info['hypervisor_version']
host_status['hypervisor_hostname'] = info['hypervisor_hostname']
host_status['supported_instances'] = [(const.ARCHITECTURE,
const.HYPERVISOR_TYPE,
obj_fields.VMMode.HVM)]
host_status['ipl_time'] = info['ipl_time']
return host_status
return self._hypervisor.list_names()
def get_available_resource(self, nodename=None):
LOG.debug("Getting available resource for %s", CONF.host)
try:
host_stats = self._reqh.call('host_get_info')
except exception.NovaException:
host_stats = {}
host_stats = self._hypervisor.get_available_resource()
hypervisor_hostname = self._hypervisor.get_available_nodes()[0]
res = {
'vcpus': host_stats.get('vcpus', 0),
'memory_mb': host_stats.get('memory_mb', 0),
'local_gb': host_stats.get('disk_total', 0),
'vcpus_used': 0,
'vcpus_used': host_stats.get('vcpus_used', 0),
'memory_mb_used': host_stats.get('memory_mb_used', 0),
'local_gb_used': host_stats.get('disk_used', 0),
'hypervisor_type': host_stats.get('hypervisor_type', 'zvm'),
'hypervisor_type': host_stats.get('hypervisor_type',
obj_fields.HVType.ZVM),
'hypervisor_version': host_stats.get('hypervisor_version', ''),
'hypervisor_hostname': host_stats.get('hypervisor_hostname', ''),
'hypervisor_hostname': host_stats.get('hypervisor_hostname',
hypervisor_hostname),
'cpu_info': jsonutils.dumps(host_stats.get('cpu_info', {})),
'disk_available_least': host_stats.get('disk_available', 0),
'supported_instances': [(const.ARCHITECTURE,
const.HYPERVISOR_TYPE,
'supported_instances': [(obj_fields.Architecture.S390X,
obj_fields.HVType.ZVM,
obj_fields.VMMode.HVM)],
'numa_topology': None,
}
LOG.debug("Getting available resource for %(host)s:%(nodename)s",
{'host': CONF.host, 'nodename': nodename})
return res
def get_available_nodes(self, refresh=False):
return [self._hypervisor_hostname]
def _mapping_power_stat(self, power_stat):
"""Translate power state to OpenStack defined constants."""
return const.ZVM_POWER_STAT.get(power_stat, power_state.NOSTATE)
return self._hypervisor.get_available_nodes(refresh=refresh)
def get_info(self, instance):
"""Get the current status of an instance."""
power_stat = ''
try:
power_stat = self._reqh.call('guest_get_power_state',
instance['name'])
except exception.NovaException as err:
if err.kwargs['results']['overallRC'] == 404:
# instance not exists
LOG.warning("Get power state of non-exist instance: %s",
instance['name'])
raise exception.InstanceNotFound(instance_id=instance['name'])
else:
raise
power_stat = self._mapping_power_stat(power_stat)
_instance_info = hardware.InstanceInfo(power_stat)
return _instance_info
def _instance_exists(self, instance_name):
"""Overwrite this to using instance name as input parameter."""
return instance_name.upper() in self.list_instances()
def instance_exists(self, instance):
"""Overwrite this to using instance name as input parameter."""
return self._instance_exists(instance.name)
_guest = guest.Guest(self._hypervisor, instance)
return _guest.get_info()
def spawn(self, context, instance, image_meta, injected_files,
admin_password, allocations, network_info=None,
block_device_info=None, flavor=None):
LOG.info(_("Spawning new instance %s on zVM hypervisor"),
instance['name'], instance=instance)
# For zVM instance, limit the maximum length of instance name to \ 8
if len(instance['name']) > 8:
msg = (_("Don't support spawn vm on zVM hypervisor with instance "
"name: %s, please change your instance_name_template to make "
"sure the length of instance name is not longer than 8 "
"characters") % instance['name'])
raise exception.InvalidInput(reason=msg)
try:
spawn_start = time.time()
os_distro = image_meta.properties.os_distro
transportfiles = self._vmutils.generate_configdrive(
context, instance, injected_files, admin_password)
_guest = guest.Guest(self._hypervisor, instance, virtapi=self.virtapi)
resp = self._get_image_info(context, image_meta.id, os_distro)
spawn_image_name = resp[0]['imagename']
disk_list, eph_list = self._set_disk_list(instance,
spawn_image_name,
block_device_info)
# Create the guest vm
self._reqh.call('guest_create', instance['name'],
instance['vcpus'], instance['memory_mb'],
disk_list=disk_list)
# Deploy image to the guest vm
remotehost = self._get_host()
self._reqh.call('guest_deploy', instance['name'],
spawn_image_name, transportfiles=transportfiles,
remotehost=remotehost)
# Setup network for z/VM instance
self._setup_network(instance['name'], os_distro, network_info,
instance)
# Handle ephemeral disks
if eph_list:
self._reqh.call('guest_config_minidisks',
instance['name'], eph_list)
self._wait_network_ready(instance)
self._reqh.call('guest_start', instance['name'])
spawn_time = time.time() - spawn_start
LOG.info(_("Instance spawned succeeded in %s seconds"),
spawn_time, instance=instance)
except Exception as err:
with excutils.save_and_reraise_exception():
LOG.error(_("Deploy image to instance %(instance)s "
"failed with reason: %(err)s"),
{'instance': instance['name'], 'err': err},
instance=instance)
self.destroy(context, instance, network_info,
block_device_info)
def _get_image_info(self, context, image_meta_id, os_distro):
spawn_image_exist = False
try:
spawn_image_exist = self._reqh.call('image_query',
imagename=image_meta_id)
except exception.NovaException as err:
if err.kwargs['results']['overallRC'] == 404:
# image not exist, nothing to do
pass
else:
raise err
if not spawn_image_exist:
with self._imageop_semaphore:
self._import_spawn_image(context, image_meta_id, os_distro)
return self._reqh.call('image_query', imagename=image_meta_id)
else:
return spawn_image_exist
def _set_disk_list(self, instance, image_name, block_device_info):
if instance['root_gb'] == 0:
root_disk_size = self._reqh.call('image_get_root_disk_size',
image_name)
else:
root_disk_size = '%ig' % instance['root_gb']
disk_list = []
root_disk = {'size': root_disk_size,
'is_boot_disk': True
}
disk_list.append(root_disk)
ephemeral_disks_info = block_device_info.get('ephemerals', [])
eph_list = []
for eph in ephemeral_disks_info:
eph_dict = {'size': '%ig' % eph['size'],
'format': (eph['guest_format'] or
CONF.default_ephemeral_format or
const.DEFAULT_EPH_DISK_FMT)}
eph_list.append(eph_dict)
if eph_list:
disk_list.extend(eph_list)
return disk_list, eph_list
def _setup_network(self, vm_name, os_distro, network_info, instance):
LOG.debug("Creating NICs for vm %s", vm_name)
inst_nets = []
for vif in network_info:
subnet = vif['network']['subnets'][0]
_net = {'ip_addr': subnet['ips'][0]['address'],
'gateway_addr': subnet['gateway']['address'],
'cidr': subnet['cidr'],
'mac_addr': vif['address'],
'nic_id': vif['id']}
inst_nets.append(_net)
if inst_nets:
self._reqh.call('guest_create_network_interface',
vm_name, os_distro, inst_nets)
def _wait_network_ready(self, instance):
"""Wait until neutron zvm-agent add all NICs to vm"""
inst_name = instance['name']
def _wait_for_nics_add_in_vm(inst_name, expiration):
if (CONF.zvm_reachable_timeout and
timeutils.utcnow() > expiration):
msg = _("NIC update check failed "
"on instance:%s") % instance.uuid
raise exception.NovaException(message=msg)
try:
switch_dict = self._reqh.call('guest_get_nic_vswitch_info',
inst_name)
if switch_dict and None not in switch_dict.values():
for key, value in switch_dict.items():
user_direct = self._reqh.call(
'guest_get_definition_info',
inst_name)
if not self._nic_coupled(user_direct, key, value):
return
else:
# In this case, the nic switch info is not ready yet
# need another loop to check until time out or find it
return
except Exception as e:
# Ignore any zvm driver exceptions
LOG.info(_('encounter error %s during get vswitch info'),
e.format_message(), instance=instance)
return
# Enter here means all NIC granted
LOG.info(_("All NICs are added in user direct for "
"instance %s."), inst_name, instance=instance)
raise loopingcall.LoopingCallDone()
expiration = timeutils.utcnow() + datetime.timedelta(
seconds=CONF.zvm_reachable_timeout)
LOG.info(_("Wait neturon-zvm-agent to add NICs to %s user direct."),
inst_name, instance=instance)
timer = loopingcall.FixedIntervalLoopingCall(
_wait_for_nics_add_in_vm, inst_name, expiration)
timer.start(interval=10).wait()
def _nic_coupled(self, user_direct, vdev, vswitch):
if vswitch is None:
return False
direct_info = user_direct['user_direct']
nic_str = ("NICDEF %s TYPE QDIO LAN SYSTEM %s" %
(vdev.upper(), vswitch.upper()))
for info in direct_info:
if nic_str in info:
return True
return False
def _get_host(self):
return ''.join([pwd.getpwuid(os.geteuid()).pw_name, '@', CONF.my_ip])
def _import_spawn_image(self, context, image_href, image_os_version):
LOG.debug("Downloading the image %s from glance to nova compute "
"server", image_href)
image_path = os.path.join(os.path.normpath(CONF.zvm_image_tmp_path),
image_href)
if not os.path.exists(image_path):
images.fetch(context, image_href, image_path)
image_url = "file://" + image_path
image_meta = {'os_version': image_os_version}
remote_host = self._get_host()
self._reqh.call('image_import', image_href, image_url,
image_meta, remote_host=remote_host)
_guest.spawn(context, image_meta, injected_files,
admin_password, allocations,
network_info=network_info,
block_device_info=block_device_info, flavor=flavor)
def destroy(self, context, instance, network_info=None,
block_device_info=None, destroy_disks=False):
inst_name = instance['name']
if self._instance_exists(inst_name):
LOG.info(_("Destroying instance %s"), inst_name,
instance=instance)
self._reqh.call('guest_delete', inst_name)
else:
LOG.warning(_('Instance %s does not exist'), inst_name,
instance=instance)
_guest = guest.Guest(self._hypervisor, instance)
_guest.destroy(context, network_info=network_info,
block_device_info=block_device_info,
destroy_disks=destroy_disks)
def get_host_uptime(self):
res = self._reqh.call('host_get_info')
return res['ipl_time']
return self._hypervisor.get_host_uptime()
def _instance_power_action(self, instance, action, *args, **kwargs):
inst_name = instance['name']
if self._instance_exists(inst_name):
LOG.info(_("Power action %(action)s to instance %(inst_name)s") %
{'action': action, 'inst_name': inst_name},
def snapshot(self, context, instance, image_id, update_task_state):
_guest = guest.Guest(self._hypervisor, instance)
_guest.snapshot(context, image_id, update_task_state)
def _guest_power_action(self, instance, action, *args, **kwargs):
if self.instance_exists(instance):
LOG.info("Power action %(action)s to instance", {'action': action},
instance=instance)
self._reqh.call(action, inst_name)
_guest = guest.Guest(self._hypervisor, instance)
_guest.guest_power_action(action, *args, **kwargs)
else:
LOG.warning(_('Instance %s does not exist'), inst_name,
instance=instance)
LOG.warning("Instance does not exist", instance=instance)
def power_off(self, instance, timeout=0, retry_interval=0):
"""Power off the specified instance."""
if timeout >= 0 and retry_interval > 0:
self._instance_power_action(instance, 'guest_softstop',
timeout=timeout,
poll_interval=retry_interval)
self._guest_power_action(instance, 'guest_softstop',
timeout=timeout,
poll_interval=retry_interval)
else:
self._instance_power_action(instance, 'guest_softstop')
self._guest_power_action(instance, 'guest_softstop')
def power_on(self, context, instance, network_info,
block_device_info=None):
"""Power on the specified instance."""
self._instance_power_action(instance, 'guest_start')
self._guest_power_action(instance, 'guest_start')
def pause(self, instance):
"""Pause the z/VM instance."""
self._instance_power_action(instance, 'guest_pause')
self._guest_power_action(instance, 'guest_pause')
def unpause(self, instance):
"""Unpause the z/VM instance."""
self._instance_power_action(instance, 'guest_unpause')
self._guest_power_action(instance, 'guest_unpause')
def reboot(self, context, instance, network_info, reboot_type,
block_device_info=None, bad_volumes_callback=None):
if reboot_type == const.REBOOT_TYPE_SOFT:
self._instance_power_action(instance, 'guest_reboot')
if reboot_type == 'SOFT':
self._guest_power_action(instance, 'guest_reboot')
else:
self._instance_power_action(instance, 'guest_reset')
self._guest_power_action(instance, 'guest_reset')
def get_console_output(self, context, instance):
return self._reqh.call('guest_get_console_output', instance.name)
def snapshot(self, context, instance, image_id, update_task_state):
"""Create snapshot from a running VM instance.
:param context: security context
:param instance: nova.objects.instance.Instance
:param image_id: Reference to a pre-created image that will
hold the snapshot.
:param update_task_state: Callback function to update the task_state
on the instance while the snapshot operation progresses. The
function takes a task_state argument and an optional
expected_task_state kwarg which defaults to
nova.compute.task_states.IMAGE_SNAPSHOT. See
nova.objects.instance.Instance.save for expected_task_state usage.
"""
# Check the image status
(image_service, image_id) = glance.get_remote_image_service(
context, image_id)
# Update the instance task_state to image_pending_upload
update_task_state(task_state=task_states.IMAGE_PENDING_UPLOAD)
# Call zvmsdk guest_capture to generate the image
try:
self._reqh.call('guest_capture', instance['name'], image_id)
except Exception as err:
with excutils.save_and_reraise_exception():
LOG.error(_("Failed to capture the instance %(instance)s "
"to generate an image with reason: %(err)s"),
{'instance': instance['name'], 'err': err},
instance=instance)
# Clean up the image from glance
image_service.delete(context, image_id)
# Export the image to nova-compute server temporary
image_path = os.path.join(os.path.normpath(
CONF.zvm_image_tmp_path), image_id)
dest_path = "file://" + image_path
try:
resp = self._reqh.call('image_export', image_id,
dest_path,
remote_host=self._get_host())
except Exception as err:
LOG.error(_("Failed to export image %s from SDK server to nova "
"compute server") % image_id)
self._reqh.call('image_delete', image_id)
# Save image to glance
new_image_meta = {
'is_public': False,
'status': 'active',
'properties': {
'image_location': 'snapshot',
'image_state': 'available',
'owner_id': instance['project_id'],
'os_distro': resp['os_version'],
'architecture': const.ARCHITECTURE,
'hypervisor_type': const.HYPERVISOR_TYPE
},
'disk_format': 'raw',
'container_format': 'bare',
}
update_task_state(task_state=task_states.IMAGE_UPLOADING,
expected_state=task_states.IMAGE_PENDING_UPLOAD)
# Save the image to glance
try:
with open(image_path, 'r') as image_file:
image_service.update(context,
image_id,
new_image_meta,
image_file,
purge_props=False)
except Exception:
with excutils.save_and_reraise_exception():
image_service.delete(context, image_id)
finally:
self._pathutils.clean_up_file(image_path)
self._reqh.call('image_delete', image_id)
LOG.debug("Snapshot image upload complete", instance=instance)
return self._hypervisor.guest_get_console_output(instance.name)

306
nova_zvm/virt/zvm/guest.py Normal file
View File

@ -0,0 +1,306 @@
# Copyright 2017,2018 IBM Corp.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import eventlet
import os
import six
import time
from oslo_concurrency import lockutils
from oslo_log import log as logging
from oslo_utils import excutils
from nova.compute import power_state as compute_power_state
from nova.compute import task_states
from nova import conf
from nova import exception
from nova.image import glance
from nova.objects import fields as obj_fields
from nova import utils
from nova.virt import hardware
from nova.virt import images
from nova.virt.zvm import utils as zvmutils
LOG = logging.getLogger(__name__)
CONF = conf.CONF
DEFAULT_EPH_DISK_FMT = 'ext3'
ZVM_POWER_STATE = {
'on': compute_power_state.RUNNING,
'off': compute_power_state.SHUTDOWN,
}
class Guest(object):
"""z/VM implementation of ComputeDriver."""
def __init__(self, hypervisor, instance, virtapi=None):
super(Guest, self).__init__()
self.virtapi = virtapi
self._hypervisor = hypervisor
self._instance = instance
def _mapping_power_state(self, power_state):
"""Translate power state to OpenStack defined constants."""
return ZVM_POWER_STATE.get(power_state, compute_power_state.NOSTATE)
def get_info(self):
"""Get the current status of an instance."""
power_state = self._mapping_power_state(
self._hypervisor.guest_get_power_state(
self._instance.name))
return hardware.InstanceInfo(power_state)
def spawn(self, context, image_meta, injected_files,
admin_password, allocations, network_info=None,
block_device_info=None, flavor=None):
LOG.info("Spawning new instance %s on zVM hypervisor",
self._instance.name, instance=self._instance)
if self._exists_in_hypervisor():
raise exception.InstanceExists(name=self._instance.name)
try:
spawn_start = time.time()
os_distro = image_meta.properties.os_distro
transportfiles = zvmutils.generate_configdrive(context,
self._instance, injected_files, admin_password)
resp = self._get_image_info(context, image_meta.id, os_distro)
spawn_image_name = resp[0]['imagename']
disk_list, eph_list = self._set_disk_list(self._instance,
spawn_image_name,
block_device_info)
# Create the guest vm
self._hypervisor.guest_create(self._instance.name,
self._instance.vcpus, self._instance.memory_mb,
disk_list)
# Deploy image to the guest vm
self._hypervisor.guest_deploy(self._instance.name,
spawn_image_name, transportfiles=transportfiles)
# Handle ephemeral disks
if eph_list:
self._hypervisor.guest_config_minidisks(self._instance.name,
eph_list)
# Setup network for z/VM instance
self._wait_vif_plug_events(self._instance.name, os_distro,
network_info, self._instance)
self._hypervisor.guest_start(self._instance.name)
spawn_time = time.time() - spawn_start
LOG.info("Instance spawned successfully in %s seconds",
spawn_time, instance=self._instance)
except Exception as err:
with excutils.save_and_reraise_exception():
LOG.error("Deploy instance %(instance)s "
"failed with reason: %(err)s",
{'instance': self._instance.name, 'err': err},
instance=self._instance)
try:
self.destroy(context, self._instance, network_info,
block_device_info)
except Exception as err:
LOG.exception("Failed to destroy instance",
instance=self._instance)
@lockutils.synchronized('IMAGE_INFO_SEMAPHORE')
def _get_image_info(self, context, image_meta_id, os_distro):
try:
return self._hypervisor.image_query(imagename=image_meta_id)
except exception.NovaException as err:
with excutils.save_and_reraise_exception() as sare:
if err.kwargs['results']['overallRC'] == 404:
sare.reraise = False
self._import_spawn_image(context, image_meta_id, os_distro)
return self._hypervisor.image_query(
imagename=image_meta_id)
def _set_disk_list(self, instance, image_name, block_device_info):
if instance.root_gb == 0:
root_disk_size = self._hypervisor.image_get_root_disk_size(
image_name)
else:
root_disk_size = '%ig' % instance.root_gb
disk_list = []
root_disk = {'size': root_disk_size,
'is_boot_disk': True
}
disk_list.append(root_disk)
ephemeral_disks_info = block_device_info.get('ephemerals', [])
eph_list = []
for eph in ephemeral_disks_info:
eph_dict = {'size': '%ig' % eph['size'],
'format': (eph['guest_format'] or
CONF.default_ephemeral_format or
DEFAULT_EPH_DISK_FMT)}
eph_list.append(eph_dict)
if eph_list:
disk_list.extend(eph_list)
return disk_list, eph_list
def _setup_network(self, vm_name, os_distro, network_info, instance):
LOG.debug("Creating NICs for vm %s", vm_name)
inst_nets = []
for vif in network_info:
subnet = vif['network']['subnets'][0]
_net = {'ip_addr': subnet['ips'][0]['address'],
'gateway_addr': subnet['gateway']['address'],
'cidr': subnet['cidr'],
'mac_addr': vif['address'],
'nic_id': vif['id']}
inst_nets.append(_net)
if inst_nets:
self._hypervisor.guest_create_network_interface(vm_name,
os_distro, inst_nets)
def _get_neutron_event(self, instance, network_info):
if utils.is_neutron() and CONF.vif_plugging_timeout:
return [('network-vif-plugged', vif['id'])
for vif in network_info if vif.get('active') is False]
else:
return []
def _neutron_failed_callback(self, event_name, instance):
LOG.error("Neutron Reported failure on event %s for instance",
event_name, instance=instance)
if CONF.vif_plugging_is_fatal:
raise exception.VirtualInterfaceCreateException()
def _wait_vif_plug_events(self, vm_name, os_distro, network_info,
instance):
timeout = CONF.vif_plugging_timeout
try:
event = self._get_neutron_event(instance, network_info)
with self.virtapi.wait_for_instance_event(
instance, event, deadline=timeout,
error_callback=self._neutron_failed_callback):
self._setup_network(vm_name, os_distro, network_info, instance)
except eventlet.timeout.Timeout:
LOG.warning("Timeout waiting for vif plugging callback.",
instance=instance)
if CONF.vif_plugging_is_fatal:
raise exception.VirtualInterfaceCreateException()
except Exception as err:
LOG.error("Failed for vif plugging: %s", six.text_type(err),
instance=instance)
raise
def _import_spawn_image(self, context, image_meta_id, image_os_version):
LOG.debug("Downloading the image %s from glance to nova compute "
"server", image_meta_id)
image_path = os.path.join(os.path.normpath(CONF.zvm.image_tmp_path),
image_meta_id)
if not os.path.exists(image_path):
images.fetch(context, image_meta_id, image_path)
image_url = "file://" + image_path
image_meta = {'os_version': image_os_version}
self._hypervisor.image_import(image_meta_id, image_url, image_meta)
def _exists_in_hypervisor(self):
return self._hypervisor.guest_exists(self._instance)
def destroy(self, context, network_info=None,
block_device_info=None, destroy_disks=False):
if self._exists_in_hypervisor():
LOG.info("Destroying instance", instance=self._instance)
try:
self._hypervisor.guest_delete(self._instance.name)
except exception.NovaException as err:
if err.kwargs['results']['overallRC'] == 404:
LOG.info("guest disappear during destroy",
instance=self._instance)
else:
raise
else:
LOG.warning("Instance does not exist", instance=self._instance)
def snapshot(self, context, image_id, update_task_state):
(image_service, image_id) = glance.get_remote_image_service(
context, image_id)
update_task_state(task_state=task_states.IMAGE_PENDING_UPLOAD)
instance = self._instance
try:
self._hypervisor.guest_capture(instance.name, image_id)
except Exception as err:
with excutils.save_and_reraise_exception():
LOG.error("Failed to capture the instance "
"to generate an image with reason: %(err)s",
{'err': err}, instance=instance)
# Clean up the image from glance
image_service.delete(context, image_id)
# Export the image to nova-compute server temporary
image_path = os.path.join(os.path.normpath(
CONF.zvm.image_tmp_path), image_id)
dest_path = "file://" + image_path
try:
resp = self._hypervisor.image_export(image_id, dest_path)
except Exception:
with excutils.save_and_reraise_exception():
LOG.error("Failed to export image %s from SDK server to "
"nova compute server", image_id)
image_service.delete(context, image_id)
self._hypervisor.image_delete(image_id)
# Save image to glance
new_image_meta = {
'is_public': False,
'status': 'active',
'properties': {
'image_location': 'snapshot',
'image_state': 'available',
'owner_id': instance['project_id'],
'os_distro': resp['os_version'],
'architecture': obj_fields.Architecture.S390X,
'hypervisor_type': obj_fields.HVType.ZVM,
},
'disk_format': 'raw',
'container_format': 'bare',
}
update_task_state(task_state=task_states.IMAGE_UPLOADING,
expected_state=task_states.IMAGE_PENDING_UPLOAD)
# Save the image to glance
try:
with open(image_path, 'r') as image_file:
image_service.update(context,
image_id,
new_image_meta,
image_file,
purge_props=False)
except Exception:
with excutils.save_and_reraise_exception():
image_service.delete(context, image_id)
finally:
zvmutils.clean_up_file(image_path)
self._hypervisor.image_delete(image_id)
LOG.debug("Snapshot image upload complete", instance=instance)
def guest_power_action(self, action, *args, **kwargs):
self._hypervisor.guest_power_action(action, self._instance.name,
*args, **kwargs)

View File

@ -0,0 +1,139 @@
# Copyright 2017,2018 IBM Corp.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import os
import pwd
from oslo_log import log as logging
from nova.compute import power_state as compute_power_state
from nova import conf
from nova import exception
from nova.virt.zvm import utils as zvmutils
LOG = logging.getLogger(__name__)
CONF = conf.CONF
class Hypervisor(object):
"""z/VM implementation of Hypervisor."""
def __init__(self, zcc_url, ca_file=None):
super(Hypervisor, self).__init__()
self._reqh = zvmutils.ConnectorClient(zcc_url,
ca_file=ca_file)
host_info = self._get_host_info()
self._hypervisor_hostname = host_info['hypervisor_hostname']
self._ipl_time = host_info['ipl_time']
self._rhost = ''.join([pwd.getpwuid(os.geteuid()).pw_name, '@',
CONF.my_ip])
def _get_host_info(self):
host_stats = {}
try:
host_stats = self._reqh.call('host_get_info')
except exception.ZVMDriverException as e:
LOG.warning("Failed to get host stats: %s", e)
return host_stats
def get_available_resource(self):
return self._get_host_info()
def get_available_nodes(self, refresh=False):
# It's not expected that the hostname change, no need to take
# 'refresh' into account.
return [self._hypervisor_hostname]
def list_names(self):
return self._reqh.call('guest_list')
def get_host_uptime(self):
return self._ipl_time
def guest_exists(self, instance):
return instance.name in self.list_names()
def guest_get_power_state(self, name):
power_state = compute_power_state.NOSTATE
try:
power_state = self._reqh.call('guest_get_power_state', name)
except exception.NovaException as err:
if err.kwargs['results']['overallRC'] == 404:
# instance does not exist
LOG.warning("Get power state of nonexistent instance: %s",
name)
raise exception.InstanceNotFound(instance_id=name)
else:
raise
return power_state
def guest_create(self, name, vcpus, memory_mb, disk_list):
self._reqh.call('guest_create', name, vcpus, memory_mb,
disk_list=disk_list)
def guest_deploy(self, name, image_name, transportfiles):
self._reqh.call('guest_deploy', name, image_name,
transportfiles, self._rhost)
def guest_delete(self, name):
self._reqh.call('guest_delete', name)
def guest_start(self, name):
self._reqh.call('guest_start', name)
def guest_create_network_interface(self, name, distro, nets):
self._reqh.call('guest_create_network_interface',
name, distro, nets)
def guest_get_definition_info(self, name):
return self._reqh.call('guest_get_definition_info', name)
def guest_get_nic_vswitch_info(self, name):
return self._reqh.call('guest_get_nic_vswitch_info', name)
def guest_config_minidisks(self, name, disk_list):
self._reqh.call('guest_config_minidisks', name, disk_list)
def guest_capture(self, name, image_id):
self._reqh.call('guest_capture', name, image_id)
def guest_power_action(self, action, name, *args, **kwargs):
self._reqh.call(action, name, *args, **kwargs)
def guest_get_console_output(self, name):
return self._reqh.call('guest_get_console_output', name)
def image_query(self, imagename):
return self._reqh.call('image_query', imagename=imagename)
def image_get_root_disk_size(self, imagename):
return self._reqh.call('image_get_root_disk_size', imagename)
def image_import(self, image_href, image_url, image_meta):
self._reqh.call('image_import', image_href, image_url,
image_meta, self._rhost)
def image_export(self, image_id, dest_path):
resp = self._reqh.call('image_export', image_id,
dest_path, self._rhost)
return resp
def image_delete(self, image_id):
self._reqh.call('image_delete', image_id)

View File

@ -1,4 +1,4 @@
# Copyright 2013 IBM Corp.
# Copyright 2017,2018 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,143 +12,123 @@
# License for the specific language governing permissions and limitations
# under the License.
import os
import shutil
from nova.api.metadata import base as instance_metadata
from nova.compute import power_state
from nova import exception
from nova.i18n import _
from nova.virt import configdrive
from oslo_log import log as logging
import six
import six.moves.urllib.parse as urlparse
from zvmconnector import connector
from nova_zvm.virt.zvm import conf
from nova_zvm.virt.zvm import configdrive as zvmconfigdrive
from nova_zvm.virt.zvm import const
from nova.api.metadata import base as instance_metadata
from nova import conf
from nova import exception
from nova.i18n import _
from nova.virt import configdrive
from nova.virt.zvm import configdrive as zvmconfigdrive
LOG = logging.getLogger(__name__)
CONF = conf.CONF
CONF.import_opt('instances_path', 'nova.compute.manager')
LOG = logging.getLogger(__name__)
def mapping_power_stat(power_stat):
"""Translate power state to OpenStack defined constants."""
return const.ZVM_POWER_STAT.get(power_stat, power_state.NOSTATE)
class ConnectorClient(object):
"""Request handler to zVM cloud connector"""
def __init__(self, zcc_url, ca_file=None):
_url = urlparse.urlparse(zcc_url)
class zVMConnectorRequestHandler(object):
_ssl_enabled = False
def __init__(self):
_url = urlparse.urlparse(CONF.zvm_cloud_connector_url)
_ca_file = CONF.zvm_ca_file
_token_path = CONF.zvm_token_path
kwargs = {}
# http or https
if _url.scheme == 'https':
kwargs['ssl_enabled'] = True
_ssl_enabled = True
if _ssl_enabled and ca_file:
self._conn = connector.ZVMConnector(_url.hostname, _url.port,
ssl_enabled=_ssl_enabled,
verify=ca_file)
else:
kwargs['ssl_enabled'] = False
# token file exist or not
if _token_path:
kwargs['token_path'] = _token_path
# CA file exist or not
if kwargs['ssl_enabled'] and _ca_file:
kwargs['verify'] = _ca_file
else:
kwargs['verify'] = False
self._conn = connector.ZVMConnector(_url.hostname, _url.port, **kwargs)
self._conn = connector.ZVMConnector(_url.hostname, _url.port,
ssl_enabled=_ssl_enabled,
verify=False)
def call(self, func_name, *args, **kwargs):
results = self._conn.send_request(func_name, *args, **kwargs)
if results['overallRC'] == 0:
return results['output']
else:
msg = ("SDK request %(api)s failed with parameters: %(args)s "
"%(kwargs)s . Results: %(results)s" %
{'api': func_name, 'args': str(args), 'kwargs': str(kwargs),
'results': str(results)})
LOG.debug(msg)
raise exception.NovaException(message=msg, results=results)
if results['overallRC'] != 0:
LOG.debug("zVM Cloud Connector request %(api)s failed with "
"parameters: %(args)s %(kwargs)s . Results: %(results)s",
{'api': func_name, 'args': six.text_type(args),
'kwargs': six.text_type(kwargs),
'results': six.text_type(results)})
raise exception.ZVMDriverException("zVM Cloud Connector request "
"failed", results=results)
return results['output']
class PathUtils(object):
def _get_instances_path(self):
return os.path.normpath(CONF.instances_path)
def get_instance_path(self, instance_uuid):
instance_folder = os.path.join(self._get_instances_path(),
instance_uuid)
if not os.path.exists(instance_folder):
LOG.debug("Creating the instance path %s", instance_folder)
os.makedirs(instance_folder)
return instance_folder
def clean_up_folder(self, fpath):
if os.path.isdir(fpath):
shutil.rmtree(fpath)
def clean_up_file(self, filepath):
if os.path.exists(filepath):
os.remove(filepath)
def _get_instances_path():
return os.path.normpath(CONF.instances_path)
class VMUtils(object):
def __init__(self):
self._pathutils = PathUtils()
def get_instance_path(instance_uuid):
instance_folder = os.path.join(_get_instances_path(),
instance_uuid)
if not os.path.exists(instance_folder):
LOG.debug("Creating the instance path %s", instance_folder)
os.makedirs(instance_folder)
return instance_folder
# Prepare and create configdrive for instance
def generate_configdrive(self, context, instance, injected_files,
admin_password):
# Create network configuration files
LOG.debug('Creating network configuration files '
'for instance: %s', instance['name'], instance=instance)
instance_path = self._pathutils.get_instance_path(instance['uuid'])
def _create_config_drive(context, instance_path, instance,
injected_files, admin_password):
if CONF.config_drive_format not in ['iso9660']:
msg = (_("Invalid config drive format %s") %
CONF.config_drive_format)
LOG.debug(msg)
raise exception.ConfigDriveUnsupportedFormat(
format=CONF.config_drive_format)
transportfiles = None
if configdrive.required_by(instance):
transportfiles = self._create_config_drive(context, instance_path,
instance,
injected_files,
admin_password)
return transportfiles
LOG.debug('Using config drive', instance=instance)
def _create_config_drive(self, context, instance_path, instance,
injected_files, admin_password):
if CONF.config_drive_format not in ['tgz', 'iso9660']:
msg = (_("Invalid config drive format %s") %
CONF.config_drive_format)
LOG.debug(msg)
raise exception.ConfigDriveUnsupportedFormat(
format=CONF.config_drive_format)
extra_md = {}
if admin_password:
extra_md['admin_pass'] = admin_password
LOG.debug('Using config drive', instance=instance)
inst_md = instance_metadata.InstanceMetadata(instance,
content=injected_files,
extra_md=extra_md,
request_context=context)
# network_metadata will prevent the hostname of the instance from
# being set correctly, so clean the value
inst_md.network_metadata = None
extra_md = {}
if admin_password:
extra_md['admin_pass'] = admin_password
configdrive_tgz = os.path.join(instance_path, 'cfgdrive.tgz')
inst_md = instance_metadata.InstanceMetadata(instance,
content=injected_files,
extra_md=extra_md,
request_context=context)
# network_metadata will prevent the hostname of the instance from
# being set correctly, so clean the value
inst_md.network_metadata = None
LOG.debug('Creating config drive at %s', configdrive_tgz,
instance=instance)
with zvmconfigdrive.ZVMConfigDriveBuilder(instance_md=inst_md) as cdb:
cdb.make_drive(configdrive_tgz)
configdrive_tgz = os.path.join(instance_path, 'cfgdrive.tgz')
return configdrive_tgz
LOG.debug('Creating config drive at %s', configdrive_tgz,
instance=instance)
with zvmconfigdrive.ZVMConfigDriveBuilder(instance_md=inst_md) as cdb:
cdb.make_drive(configdrive_tgz)
return configdrive_tgz
# Prepare and create configdrive for instance
def generate_configdrive(context, instance, injected_files,
admin_password):
# Create network configuration files
LOG.debug('Creating network configuration files '
'for instance: %s', instance.name, instance=instance)
instance_path = get_instance_path(instance.uuid)
transportfiles = None
if configdrive.required_by(instance):
transportfiles = _create_config_drive(context, instance_path,
instance,
injected_files,
admin_password)
return transportfiles
def clean_up_file(filepath):
if os.path.exists(filepath):
os.remove(filepath)