From e72fbe637f4ef622eca5412c4780931e14a67225 Mon Sep 17 00:00:00 2001 From: jichenjc Date: Thu, 12 Apr 2018 02:16:54 +0800 Subject: [PATCH] Sync patch from in tree Change-Id: I9088ff34129feed57757d57824f65a3d784801dd --- .../tests/unit/virt/zvm/test_configdrive.py | 41 +- nova_zvm/tests/unit/virt/zvm/test_driver.py | 609 ++++-------------- nova_zvm/tests/unit/virt/zvm/test_guest.py | 426 ++++++++++++ .../tests/unit/virt/zvm/test_hypervisor.py | 92 +++ nova_zvm/tests/unit/virt/zvm/test_utils.py | 141 ++++ nova_zvm/virt/zvm/__init__.py | 16 +- nova_zvm/virt/zvm/configdrive.py | 10 +- nova_zvm/virt/zvm/const.py | 30 - nova_zvm/virt/zvm/driver.py | 488 +++----------- nova_zvm/virt/zvm/guest.py | 306 +++++++++ nova_zvm/virt/zvm/hypervisor.py | 139 ++++ nova_zvm/virt/zvm/utils.py | 198 +++--- 12 files changed, 1404 insertions(+), 1092 deletions(-) create mode 100644 nova_zvm/tests/unit/virt/zvm/test_guest.py create mode 100644 nova_zvm/tests/unit/virt/zvm/test_hypervisor.py create mode 100644 nova_zvm/tests/unit/virt/zvm/test_utils.py delete mode 100644 nova_zvm/virt/zvm/const.py create mode 100644 nova_zvm/virt/zvm/guest.py create mode 100644 nova_zvm/virt/zvm/hypervisor.py diff --git a/nova_zvm/tests/unit/virt/zvm/test_configdrive.py b/nova_zvm/tests/unit/virt/zvm/test_configdrive.py index 4511f48..e1f1b40 100644 --- a/nova_zvm/tests/unit/virt/zvm/test_configdrive.py +++ b/nova_zvm/tests/unit/virt/zvm/test_configdrive.py @@ -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) diff --git a/nova_zvm/tests/unit/virt/zvm/test_driver.py b/nova_zvm/tests/unit/virt/zvm/test_driver.py index 8d9019c..2c73fe3 100644 --- a/nova_zvm/tests/unit/virt/zvm/test_driver.py +++ b/nova_zvm/tests/unit/virt/zvm/test_driver.py @@ -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) diff --git a/nova_zvm/tests/unit/virt/zvm/test_guest.py b/nova_zvm/tests/unit/virt/zvm/test_guest.py new file mode 100644 index 0000000..48867eb --- /dev/null +++ b/nova_zvm/tests/unit/virt/zvm/test_guest.py @@ -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) diff --git a/nova_zvm/tests/unit/virt/zvm/test_hypervisor.py b/nova_zvm/tests/unit/virt/zvm/test_hypervisor.py new file mode 100644 index 0000000..076f781 --- /dev/null +++ b/nova_zvm/tests/unit/virt/zvm/test_hypervisor.py @@ -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) diff --git a/nova_zvm/tests/unit/virt/zvm/test_utils.py b/nova_zvm/tests/unit/virt/zvm/test_utils.py new file mode 100644 index 0000000..1525352 --- /dev/null +++ b/nova_zvm/tests/unit/virt/zvm/test_utils.py @@ -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') diff --git a/nova_zvm/virt/zvm/__init__.py b/nova_zvm/virt/zvm/__init__.py index 1b55f06..183da42 100644 --- a/nova_zvm/virt/zvm/__init__.py +++ b/nova_zvm/virt/zvm/__init__.py @@ -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 diff --git a/nova_zvm/virt/zvm/configdrive.py b/nova_zvm/virt/zvm/configdrive.py index 5824f02..e872d45 100644 --- a/nova_zvm/virt/zvm/configdrive.py +++ b/nova_zvm/virt/zvm/configdrive.py @@ -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() diff --git a/nova_zvm/virt/zvm/const.py b/nova_zvm/virt/zvm/const.py deleted file mode 100644 index e3c1fed..0000000 --- a/nova_zvm/virt/zvm/const.py +++ /dev/null @@ -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' diff --git a/nova_zvm/virt/zvm/driver.py b/nova_zvm/virt/zvm/driver.py index bebea10..ac27724 100644 --- a/nova_zvm/virt/zvm/driver.py +++ b/nova_zvm/virt/zvm/driver.py @@ -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) diff --git a/nova_zvm/virt/zvm/guest.py b/nova_zvm/virt/zvm/guest.py new file mode 100644 index 0000000..b0f084d --- /dev/null +++ b/nova_zvm/virt/zvm/guest.py @@ -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) diff --git a/nova_zvm/virt/zvm/hypervisor.py b/nova_zvm/virt/zvm/hypervisor.py new file mode 100644 index 0000000..44ae4fc --- /dev/null +++ b/nova_zvm/virt/zvm/hypervisor.py @@ -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) diff --git a/nova_zvm/virt/zvm/utils.py b/nova_zvm/virt/zvm/utils.py index f2314bd..6089f94 100644 --- a/nova_zvm/virt/zvm/utils.py +++ b/nova_zvm/virt/zvm/utils.py @@ -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)