z/VM Driver: Spawn and destroy function of z/VM driver
It includes two main function, Spawn is used to deploy an instance on the z/VM, and destroy is used to delete the instance Change-Id: Ie3db769c5e62353b2fa39c1a7e1f025171ff4a4a blueprint: add-zvm-driver-rocky
This commit is contained in:
		@@ -15,6 +15,9 @@
 | 
			
		||||
from oslo_config import cfg
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
from nova.conf import paths
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
zvm_opt_group = cfg.OptGroup('zvm',
 | 
			
		||||
                             title='zVM Options',
 | 
			
		||||
                             help="""
 | 
			
		||||
@@ -38,6 +41,38 @@ URL to be used to communicate with z/VM Cloud Connector.
 | 
			
		||||
CA certificate file to be verified in httpd server with TLS enabled
 | 
			
		||||
 | 
			
		||||
A string, it must be a path to a CA bundle to use.
 | 
			
		||||
"""),
 | 
			
		||||
    cfg.StrOpt('image_tmp_path',
 | 
			
		||||
               default=paths.state_path_def('images'),
 | 
			
		||||
               sample_default="$state_path/images",
 | 
			
		||||
               help="""
 | 
			
		||||
The path at which images will be stored (snapshot, deploy, etc).
 | 
			
		||||
 | 
			
		||||
Images used for deploy and images captured via snapshot
 | 
			
		||||
need to be stored on the local disk of the compute host.
 | 
			
		||||
This configuration identifies the directory location.
 | 
			
		||||
 | 
			
		||||
Possible values:
 | 
			
		||||
    A file system path on the host running the compute service.
 | 
			
		||||
"""),
 | 
			
		||||
    cfg.IntOpt('reachable_timeout',
 | 
			
		||||
               default=300,
 | 
			
		||||
               help="""
 | 
			
		||||
Timeout (seconds) to wait for an instance to start.
 | 
			
		||||
 | 
			
		||||
The z/VM driver relies on communication between the instance and cloud
 | 
			
		||||
connector. After an instance is created, it must have enough time to wait
 | 
			
		||||
for all the network info to be written into the user directory.
 | 
			
		||||
The driver will keep rechecking network status to the instance with the
 | 
			
		||||
timeout value, If setting network failed, it will notify the user that
 | 
			
		||||
starting the instance failed and put the instance in ERROR state.
 | 
			
		||||
The underlying z/VM guest will then be deleted.
 | 
			
		||||
 | 
			
		||||
Possible Values:
 | 
			
		||||
    Any positive integer. Recommended to be at least 300 seconds (5 minutes),
 | 
			
		||||
    but it will vary depending on instance and system load.
 | 
			
		||||
    A value of 0 is used for debug. In this case the underlying z/VM guest
 | 
			
		||||
    will not be deleted when the instance is marked in ERROR state.
 | 
			
		||||
"""),
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -12,22 +12,111 @@
 | 
			
		||||
#    License for the specific language governing permissions and limitations
 | 
			
		||||
#    under the License.
 | 
			
		||||
 | 
			
		||||
import copy
 | 
			
		||||
import mock
 | 
			
		||||
 | 
			
		||||
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 as zvmdriver
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
CONF = conf.CONF
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TestZVMDriver(test.NoDBTestCase):
 | 
			
		||||
 | 
			
		||||
    def setUp(self):
 | 
			
		||||
        super(TestZVMDriver, self).setUp()
 | 
			
		||||
        self.flags(my_ip='192.168.1.1',
 | 
			
		||||
                   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'}
 | 
			
		||||
            self._driver = zvmdriver.ZVMDriver('virtapi')
 | 
			
		||||
 | 
			
		||||
        with mock.patch('nova.virt.zvm.utils.ConnectorClient.call') as mcall, \
 | 
			
		||||
            mock.patch('pwd.getpwuid', return_value=mock.Mock(pw_name='test')):
 | 
			
		||||
            mcall.return_value = {'hypervisor_hostname': 'TESTHOST',
 | 
			
		||||
                                  'ipl_time': 'IPL at 11/14/17 10:47:44 EST'}
 | 
			
		||||
            self._driver = zvmdriver.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._instance.flavor = objects.Flavor(name='testflavor',
 | 
			
		||||
                                vcpus=1, root_gb=3, ephemeral_gb=10,
 | 
			
		||||
                                swap=0, memory_mb=512, extra_specs={})
 | 
			
		||||
 | 
			
		||||
        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')
 | 
			
		||||
@@ -43,3 +132,195 @@ class TestZVMDriver(test.NoDBTestCase):
 | 
			
		||||
        self.assertEqual(0, results['memory_mb_used'])
 | 
			
		||||
        self.assertEqual(0, results['disk_available_least'])
 | 
			
		||||
        self.assertEqual('TESTHOST', results['hypervisor_hostname'])
 | 
			
		||||
 | 
			
		||||
    def test_driver_template_validation(self):
 | 
			
		||||
        self.flags(instance_name_template='abc%6d')
 | 
			
		||||
        self.assertRaises(exception.ZVMDriverException,
 | 
			
		||||
                          self._driver._validate_options)
 | 
			
		||||
 | 
			
		||||
    @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.virt.zvm.utils.ConnectorClient.call')
 | 
			
		||||
    def test_private_get_image_info_err(self, call):
 | 
			
		||||
        res = {'overallRC': 500, 'errmsg': 'err', 'rc': 0, 'rs': 0}
 | 
			
		||||
        call.side_effect = exception.ZVMConnectorError(res)
 | 
			
		||||
        self.assertRaises(exception.ZVMConnectorError,
 | 
			
		||||
                          self._driver._get_image_info,
 | 
			
		||||
                          'context', 'image_meta_id', 'os_distro')
 | 
			
		||||
 | 
			
		||||
    @mock.patch('nova.virt.zvm.utils.ConnectorClient.call')
 | 
			
		||||
    @mock.patch('nova.virt.zvm.driver.ZVMDriver._import_spawn_image')
 | 
			
		||||
    def test_private_get_image_info(self, image_import, call):
 | 
			
		||||
        res = {'overallRC': 404, 'errmsg': 'err', 'rc': 0, 'rs': 0}
 | 
			
		||||
 | 
			
		||||
        call_response = []
 | 
			
		||||
        call_response.append(exception.ZVMConnectorError(results=res))
 | 
			
		||||
        call_response.append([{'imagename': 'image-info'}])
 | 
			
		||||
        call.side_effect = call_response
 | 
			
		||||
        self._driver._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')] * 2
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    @mock.patch('nova.virt.zvm.utils.ConnectorClient.call')
 | 
			
		||||
    def test_private_get_image_info_exist(self, call):
 | 
			
		||||
        call.return_value = [{'imagename': 'image-info'}]
 | 
			
		||||
        res = self._driver._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'ext3', 'size': '2g'}]
 | 
			
		||||
        _inst = copy.deepcopy(self._instance)
 | 
			
		||||
        _bdi = copy.deepcopy(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._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.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._driver._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._driver._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, remote_host='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._driver.destroy(self._context, self._instance,
 | 
			
		||||
                             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.driver.ZVMDriver._setup_network')
 | 
			
		||||
    @mock.patch('nova.virt.zvm.driver.ZVMDriver._set_disk_list')
 | 
			
		||||
    @mock.patch('nova.virt.zvm.utils.generate_configdrive')
 | 
			
		||||
    @mock.patch('nova.virt.zvm.driver.ZVMDriver._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 = 'image_name'
 | 
			
		||||
        gen_conf_file.return_value = 'transportfiles'
 | 
			
		||||
        set_disk_list.return_value = 'disk_list', 'eph_list'
 | 
			
		||||
        mock_exists.return_value = False
 | 
			
		||||
        self._driver.spawn(self._context, self._instance, self._image_meta,
 | 
			
		||||
                          injected_files=None, admin_password=None,
 | 
			
		||||
                          allocations=None, network_info=self._network_info,
 | 
			
		||||
                          block_device_info=_bdi)
 | 
			
		||||
        gen_conf_file.assert_called_once_with(self._context, self._instance,
 | 
			
		||||
                                              None, self._network_info, 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='transportfiles',
 | 
			
		||||
                      remotehost='test@192.168.1.1'),
 | 
			
		||||
            mock.call('guest_config_minidisks', self._instance.name,
 | 
			
		||||
                      'eph_list'),
 | 
			
		||||
            mock.call('guest_start', self._instance.name)
 | 
			
		||||
        ])
 | 
			
		||||
 | 
			
		||||
    @mock.patch('nova.virt.zvm.hypervisor.Hypervisor.guest_exists')
 | 
			
		||||
    @mock.patch('nova.virt.zvm.driver.ZVMDriver._get_image_info')
 | 
			
		||||
    def test_spawn_image_no_distro_empty(self, get_image_info, mock_exists):
 | 
			
		||||
        meta = {'status': 'active',
 | 
			
		||||
                'deleted': False,
 | 
			
		||||
                'properties': {'os_distro': ''},
 | 
			
		||||
                'id': self._image_id,
 | 
			
		||||
                'size': 465448142}
 | 
			
		||||
        self._image_meta = objects.ImageMeta.from_dict(meta)
 | 
			
		||||
        mock_exists.return_value = False
 | 
			
		||||
        self.assertRaises(exception.InvalidInput, self._driver.spawn,
 | 
			
		||||
                          self._context, self._instance, self._image_meta,
 | 
			
		||||
                          injected_files=None, admin_password=None,
 | 
			
		||||
                          allocations=None, network_info=self._network_info,
 | 
			
		||||
                          block_device_info=None)
 | 
			
		||||
 | 
			
		||||
    @mock.patch('nova.virt.zvm.hypervisor.Hypervisor.guest_exists')
 | 
			
		||||
    @mock.patch('nova.virt.zvm.driver.ZVMDriver._get_image_info')
 | 
			
		||||
    def test_spawn_image_no_distro_none(self, get_image_info, mock_exists):
 | 
			
		||||
        meta = {'status': 'active',
 | 
			
		||||
                'deleted': False,
 | 
			
		||||
                'id': self._image_id,
 | 
			
		||||
                'size': 465448142}
 | 
			
		||||
        self._image_meta = objects.ImageMeta.from_dict(meta)
 | 
			
		||||
        mock_exists.return_value = False
 | 
			
		||||
        self.assertRaises(exception.InvalidInput, self._driver.spawn,
 | 
			
		||||
                          self._context, self._instance, self._image_meta,
 | 
			
		||||
                          injected_files=None, admin_password=None,
 | 
			
		||||
                          allocations=None, network_info=self._network_info,
 | 
			
		||||
                          block_device_info=None)
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										77
									
								
								nova/tests/unit/virt/zvm/test_guest.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										77
									
								
								nova/tests/unit/virt/zvm/test_guest.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,77 @@
 | 
			
		||||
# 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.compute import power_state as compute_power_state
 | 
			
		||||
from nova import context
 | 
			
		||||
from nova import exception
 | 
			
		||||
from nova import test
 | 
			
		||||
from nova.tests.unit import fake_instance
 | 
			
		||||
from nova.virt import fake
 | 
			
		||||
from nova.virt.zvm import driver
 | 
			
		||||
from nova.virt.zvm import guest
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
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._instance = fake_instance.fake_instance_obj(
 | 
			
		||||
                                self._context)
 | 
			
		||||
        self._guest = guest.Guest(self._hypervisor, self._instance,
 | 
			
		||||
                                  self._driver.virtapi)
 | 
			
		||||
 | 
			
		||||
    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_instance_not_found(self, call):
 | 
			
		||||
        res = {'overallRC': 404, 'errmsg': 'err', 'rc': 0, 'rs': 0}
 | 
			
		||||
        call.side_effect = exception.ZVMConnectorError(results=res)
 | 
			
		||||
        self.assertRaises(exception.InstanceNotFound, self._guest.get_info)
 | 
			
		||||
 | 
			
		||||
    @mock.patch('nova.virt.zvm.utils.ConnectorClient.call')
 | 
			
		||||
    def test_get_info_err_general(self, call):
 | 
			
		||||
        res = {'overallRC': 500, 'errmsg': 'err', 'rc': 0, 'rs': 0}
 | 
			
		||||
        call.side_effect = exception.ZVMConnectorError(res)
 | 
			
		||||
        self.assertRaises(exception.ZVMConnectorError, 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)
 | 
			
		||||
@@ -14,8 +14,10 @@
 | 
			
		||||
 | 
			
		||||
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
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -23,13 +25,17 @@ 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'}
 | 
			
		||||
            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,
 | 
			
		||||
@@ -67,3 +73,27 @@ class TestZVMHypervisor(test.NoDBTestCase):
 | 
			
		||||
        call.return_value = ['vm1', 'vm2']
 | 
			
		||||
        inst_list = self._hypervisor.list_names()
 | 
			
		||||
        self.assertEqual(['vm1', 'vm2'], inst_list)
 | 
			
		||||
 | 
			
		||||
    @mock.patch('nova.virt.zvm.utils.ConnectorClient.call')
 | 
			
		||||
    def test_get_host_uptime(self, call):
 | 
			
		||||
        host_info = {'disk_available': 1144,
 | 
			
		||||
                     'ipl_time': 'IPL at 11/14/17 10:47:44 EST',
 | 
			
		||||
                     'memory_mb_used': 8192.0}
 | 
			
		||||
        call.return_value = host_info
 | 
			
		||||
 | 
			
		||||
        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.upper(), '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)
 | 
			
		||||
 
 | 
			
		||||
@@ -16,8 +16,10 @@ 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
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -79,3 +81,57 @@ class TestZVMUtils(test.NoDBTestCase):
 | 
			
		||||
        self.assertEqual(expected['rc'], exc.rc)
 | 
			
		||||
        self.assertEqual(expected['rs'], exc.rs)
 | 
			
		||||
        self.assertEqual(expected['errmsg'], exc.errmsg)
 | 
			
		||||
 | 
			
		||||
    @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',
 | 
			
		||||
                                             'network_info',
 | 
			
		||||
                                             'admin_password')
 | 
			
		||||
        required.assert_called_once_with(instance)
 | 
			
		||||
        create.assert_called_once_with('context', '/test/tmp/fake_uuid',
 | 
			
		||||
                                       instance, 'injected_files',
 | 
			
		||||
                                       'network_info', 'admin_password')
 | 
			
		||||
        self.assertEqual('/test/cfgdrive.tgz', file)
 | 
			
		||||
 | 
			
		||||
    @mock.patch('nova.api.metadata.base.InstanceMetadata')
 | 
			
		||||
    @mock.patch('nova.virt.configdrive.ConfigDriveBuilder.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',
 | 
			
		||||
                                      'network_info', 'admin_password')
 | 
			
		||||
        mock_instance_metadata.assert_called_once_with('instance',
 | 
			
		||||
                                                content='injected_files',
 | 
			
		||||
                                                extra_md=extra_md,
 | 
			
		||||
                                                network_info='network_info',
 | 
			
		||||
                                                request_context='context')
 | 
			
		||||
        make_drive.assert_called_once_with('/instance_path/cfgdrive.iso')
 | 
			
		||||
 | 
			
		||||
    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',
 | 
			
		||||
                          'network_info', 'admin_password')
 | 
			
		||||
 
 | 
			
		||||
@@ -12,37 +12,71 @@
 | 
			
		||||
#    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_serialization import jsonutils
 | 
			
		||||
from oslo_utils import excutils
 | 
			
		||||
 | 
			
		||||
from nova import conf
 | 
			
		||||
from nova import exception
 | 
			
		||||
from nova.i18n import _
 | 
			
		||||
from nova.objects import fields as obj_fields
 | 
			
		||||
from nova import utils
 | 
			
		||||
from nova.virt import driver
 | 
			
		||||
from nova.virt import images
 | 
			
		||||
from nova.virt.zvm import guest
 | 
			
		||||
from nova.virt.zvm import hypervisor
 | 
			
		||||
from nova.virt.zvm import utils as zvmutils
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
LOG = logging.getLogger(__name__)
 | 
			
		||||
CONF = conf.CONF
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
DEFAULT_EPH_DISK_FMT = 'ext3'
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ZVMDriver(driver.ComputeDriver):
 | 
			
		||||
    """z/VM implementation of ComputeDriver."""
 | 
			
		||||
 | 
			
		||||
    def __init__(self, virtapi):
 | 
			
		||||
        super(ZVMDriver, self).__init__(virtapi)
 | 
			
		||||
 | 
			
		||||
        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)
 | 
			
		||||
        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.")
 | 
			
		||||
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def _validate_options():
 | 
			
		||||
        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
 | 
			
		||||
        try:
 | 
			
		||||
            _test_instance = CONF.instance_name_template % 0
 | 
			
		||||
        except Exception:
 | 
			
		||||
            msg = _("Template is not usable, the template defined is "
 | 
			
		||||
                    "instance_name_template=%s") % CONF.instance_name_template
 | 
			
		||||
            raise exception.ZVMDriverException(error=msg)
 | 
			
		||||
 | 
			
		||||
        # 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):
 | 
			
		||||
        pass
 | 
			
		||||
 | 
			
		||||
@@ -80,3 +114,188 @@ class ZVMDriver(driver.ComputeDriver):
 | 
			
		||||
 | 
			
		||||
    def get_available_nodes(self, refresh=False):
 | 
			
		||||
        return self._hypervisor.get_available_nodes(refresh=refresh)
 | 
			
		||||
 | 
			
		||||
    def get_info(self, instance):
 | 
			
		||||
        _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):
 | 
			
		||||
 | 
			
		||||
        LOG.info("Spawning new instance %s on zVM hypervisor",
 | 
			
		||||
                 instance.name, instance=instance)
 | 
			
		||||
 | 
			
		||||
        if self._hypervisor.guest_exists(instance):
 | 
			
		||||
            raise exception.InstanceExists(name=instance.name)
 | 
			
		||||
 | 
			
		||||
        os_distro = image_meta.properties.get('os_distro')
 | 
			
		||||
        if os_distro is None or len(os_distro) == 0:
 | 
			
		||||
            reason = _("The `os_distro` image metadata property is required")
 | 
			
		||||
            raise exception.InvalidInput(reason=reason)
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            spawn_start = time.time()
 | 
			
		||||
 | 
			
		||||
            transportfiles = zvmutils.generate_configdrive(context,
 | 
			
		||||
                instance, injected_files, network_info,
 | 
			
		||||
                admin_password)
 | 
			
		||||
 | 
			
		||||
            spawn_image_name = self._get_image_info(context, image_meta.id,
 | 
			
		||||
                                                    os_distro)
 | 
			
		||||
            disk_list, eph_list = self._set_disk_list(instance,
 | 
			
		||||
                                                      spawn_image_name,
 | 
			
		||||
                                                      block_device_info)
 | 
			
		||||
 | 
			
		||||
            # Create the guest vm
 | 
			
		||||
            self._hypervisor.guest_create(instance.name,
 | 
			
		||||
                instance.vcpus, instance.memory_mb,
 | 
			
		||||
                disk_list)
 | 
			
		||||
 | 
			
		||||
            # Deploy image to the guest vm
 | 
			
		||||
            self._hypervisor.guest_deploy(instance.name,
 | 
			
		||||
                spawn_image_name, transportfiles=transportfiles)
 | 
			
		||||
 | 
			
		||||
            # Handle ephemeral disks
 | 
			
		||||
            if eph_list:
 | 
			
		||||
                self._hypervisor.guest_config_minidisks(instance.name,
 | 
			
		||||
                                                        eph_list)
 | 
			
		||||
            # Setup network for z/VM instance
 | 
			
		||||
            self._wait_vif_plug_events(instance.name, os_distro,
 | 
			
		||||
                network_info, instance)
 | 
			
		||||
 | 
			
		||||
            self._hypervisor.guest_start(instance.name)
 | 
			
		||||
            spawn_time = time.time() - spawn_start
 | 
			
		||||
            LOG.info("Instance spawned successfully in %s seconds",
 | 
			
		||||
                     spawn_time, instance=instance)
 | 
			
		||||
        except Exception as err:
 | 
			
		||||
            with excutils.save_and_reraise_exception():
 | 
			
		||||
                LOG.error("Deploy instance %(instance)s "
 | 
			
		||||
                          "failed with reason: %(err)s",
 | 
			
		||||
                          {'instance': instance.name, 'err': err},
 | 
			
		||||
                          instance=instance)
 | 
			
		||||
                try:
 | 
			
		||||
                    self.destroy(context, instance, network_info,
 | 
			
		||||
                                 block_device_info)
 | 
			
		||||
                except Exception as err:
 | 
			
		||||
                    LOG.exception("Failed to destroy instance",
 | 
			
		||||
                                  instance=instance)
 | 
			
		||||
 | 
			
		||||
    @lockutils.synchronized('IMAGE_INFO_SEMAPHORE')
 | 
			
		||||
    def _get_image_info(self, context, image_meta_id, os_distro):
 | 
			
		||||
        try:
 | 
			
		||||
            res = self._hypervisor.image_query(imagename=image_meta_id)
 | 
			
		||||
        except exception.ZVMConnectorError as err:
 | 
			
		||||
            with excutils.save_and_reraise_exception() as sare:
 | 
			
		||||
                if err.overallRC == 404:
 | 
			
		||||
                    sare.reraise = False
 | 
			
		||||
                    self._import_spawn_image(context, image_meta_id, os_distro)
 | 
			
		||||
 | 
			
		||||
                    res = self._hypervisor.image_query(imagename=image_meta_id)
 | 
			
		||||
 | 
			
		||||
        return res[0]['imagename']
 | 
			
		||||
 | 
			
		||||
    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 = driver.block_device_info_get_ephemerals(
 | 
			
		||||
            block_device_info)
 | 
			
		||||
 | 
			
		||||
        eph_list = []
 | 
			
		||||
        for eph in ephemeral_disks_info:
 | 
			
		||||
            eph_dict = {'size': '%ig' % eph['size'],
 | 
			
		||||
                        'format': (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)
 | 
			
		||||
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def _get_neutron_event(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 []
 | 
			
		||||
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    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(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:
 | 
			
		||||
            with excutils.save_and_reraise_exception():
 | 
			
		||||
                LOG.error("Failed for vif plugging: %s", six.text_type(err),
 | 
			
		||||
                          instance=instance)
 | 
			
		||||
 | 
			
		||||
    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 destroy(self, context, instance, network_info=None,
 | 
			
		||||
                block_device_info=None, destroy_disks=False):
 | 
			
		||||
        if self._hypervisor.guest_exists(instance):
 | 
			
		||||
            LOG.info("Destroying instance", instance=instance)
 | 
			
		||||
            try:
 | 
			
		||||
                self._hypervisor.guest_delete(instance.name)
 | 
			
		||||
            except exception.ZVMConnectorError as err:
 | 
			
		||||
                if err.overallRC == 404:
 | 
			
		||||
                    LOG.info("instance disappear during destroying",
 | 
			
		||||
                             instance=instance)
 | 
			
		||||
                else:
 | 
			
		||||
                    raise
 | 
			
		||||
        else:
 | 
			
		||||
            LOG.warning("Instance does not exist", instance=instance)
 | 
			
		||||
 | 
			
		||||
    def get_host_uptime(self):
 | 
			
		||||
        return self._hypervisor.get_host_uptime()
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										51
									
								
								nova/virt/zvm/guest.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								nova/virt/zvm/guest.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,51 @@
 | 
			
		||||
# 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.
 | 
			
		||||
 | 
			
		||||
from oslo_log import log as logging
 | 
			
		||||
 | 
			
		||||
from nova.compute import power_state as compute_power_state
 | 
			
		||||
from nova import conf
 | 
			
		||||
from nova.virt import hardware
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
LOG = logging.getLogger(__name__)
 | 
			
		||||
CONF = conf.CONF
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
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)
 | 
			
		||||
@@ -12,13 +12,19 @@
 | 
			
		||||
#    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):
 | 
			
		||||
@@ -29,12 +35,15 @@ class Hypervisor(object):
 | 
			
		||||
 | 
			
		||||
        self._reqh = zvmutils.ConnectorClient(zcc_url,
 | 
			
		||||
                                              ca_file=ca_file)
 | 
			
		||||
        host_info = self._get_host_info()
 | 
			
		||||
 | 
			
		||||
        # Very very unlikely the hostname will be changed, so when create
 | 
			
		||||
        # hypervisor object, store the information in the cache and after
 | 
			
		||||
        # that we can use it directly without query again from connectorclient
 | 
			
		||||
        self._hypervisor_hostname = self._get_host_info().get(
 | 
			
		||||
            'hypervisor_hostname')
 | 
			
		||||
        self._hypervisor_hostname = host_info['hypervisor_hostname']
 | 
			
		||||
 | 
			
		||||
        self._rhost = ''.join([pwd.getpwuid(os.geteuid()).pw_name, '@',
 | 
			
		||||
                               CONF.my_ip])
 | 
			
		||||
 | 
			
		||||
    def _get_host_info(self):
 | 
			
		||||
        host_stats = {}
 | 
			
		||||
@@ -55,3 +64,81 @@ class Hypervisor(object):
 | 
			
		||||
    def list_names(self):
 | 
			
		||||
        """list names of the servers in the hypervisor"""
 | 
			
		||||
        return self._reqh.call('guest_list')
 | 
			
		||||
 | 
			
		||||
    def get_host_uptime(self):
 | 
			
		||||
        host_info = self._get_host_info()
 | 
			
		||||
 | 
			
		||||
        return host_info['ipl_time']
 | 
			
		||||
 | 
			
		||||
    def guest_exists(self, instance):
 | 
			
		||||
        return instance.name.upper() 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.ZVMConnectorError as err:
 | 
			
		||||
            if err.overallRC == 404:
 | 
			
		||||
                # instance does not exist
 | 
			
		||||
                LOG.warning("Failed to get power state due to 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=transportfiles, remotehost=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):
 | 
			
		||||
        """Get user direct info
 | 
			
		||||
 | 
			
		||||
        :returns: User direct is server definition, it will be
 | 
			
		||||
                  returned in a string format
 | 
			
		||||
        """
 | 
			
		||||
        return self._reqh.call('guest_get_definition_info', name)
 | 
			
		||||
 | 
			
		||||
    def guest_get_nic_vswitch_info(self, name):
 | 
			
		||||
        """Get the nic and vswitch info
 | 
			
		||||
 | 
			
		||||
        :returns: Return the nic and vswitch info in dict
 | 
			
		||||
        """
 | 
			
		||||
        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 image_query(self, imagename):
 | 
			
		||||
        """Check whether image is there or not
 | 
			
		||||
 | 
			
		||||
        :returns: Query the image and returns a dict of the image info
 | 
			
		||||
                  if the image exists or return {}
 | 
			
		||||
        """
 | 
			
		||||
        return self._reqh.call('image_query', imagename=imagename)
 | 
			
		||||
 | 
			
		||||
    def image_get_root_disk_size(self, imagename):
 | 
			
		||||
        """Get the root disk size of image
 | 
			
		||||
 | 
			
		||||
        :returns: return the size (in string) about the root disk of image
 | 
			
		||||
        """
 | 
			
		||||
        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, remote_host=self._rhost)
 | 
			
		||||
 
 | 
			
		||||
@@ -12,14 +12,21 @@
 | 
			
		||||
#    License for the specific language governing permissions and limitations
 | 
			
		||||
#    under the License.
 | 
			
		||||
 | 
			
		||||
import os
 | 
			
		||||
from oslo_log import log as logging
 | 
			
		||||
import six
 | 
			
		||||
import six.moves.urllib.parse as urlparse
 | 
			
		||||
from zvmconnector import connector
 | 
			
		||||
 | 
			
		||||
from oslo_utils import fileutils
 | 
			
		||||
 | 
			
		||||
from nova.api.metadata import base as instance_metadata
 | 
			
		||||
from nova import conf
 | 
			
		||||
from nova import exception
 | 
			
		||||
from nova.virt import configdrive
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
CONF = conf.CONF
 | 
			
		||||
LOG = logging.getLogger(__name__)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -59,3 +66,56 @@ class ConnectorClient(object):
 | 
			
		||||
            raise exception.ZVMConnectorError(results=results)
 | 
			
		||||
 | 
			
		||||
        return results['output']
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def _get_instance_path(instance_uuid):
 | 
			
		||||
    instance_folder = os.path.join(os.path.normpath(CONF.instances_path),
 | 
			
		||||
                                   instance_uuid)
 | 
			
		||||
    fileutils.ensure_tree(instance_folder)
 | 
			
		||||
    return instance_folder
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def _create_config_drive(context, instance_path, instance,
 | 
			
		||||
                         injected_files, network_info, admin_password):
 | 
			
		||||
    if CONF.config_drive_format != 'iso9660':
 | 
			
		||||
        raise exception.ConfigDriveUnsupportedFormat(
 | 
			
		||||
                        format=CONF.config_drive_format)
 | 
			
		||||
 | 
			
		||||
    LOG.debug('Using config drive', instance=instance)
 | 
			
		||||
 | 
			
		||||
    extra_md = {}
 | 
			
		||||
    if admin_password:
 | 
			
		||||
        extra_md['admin_pass'] = admin_password
 | 
			
		||||
 | 
			
		||||
    inst_md = instance_metadata.InstanceMetadata(instance,
 | 
			
		||||
                                                 content=injected_files,
 | 
			
		||||
                                                 extra_md=extra_md,
 | 
			
		||||
                                                 network_info=network_info,
 | 
			
		||||
                                                 request_context=context)
 | 
			
		||||
 | 
			
		||||
    configdrive_iso = os.path.join(instance_path, 'cfgdrive.iso')
 | 
			
		||||
    LOG.debug('Creating config drive at %s', configdrive_iso,
 | 
			
		||||
              instance=instance)
 | 
			
		||||
    with configdrive.ConfigDriveBuilder(instance_md=inst_md) as cdb:
 | 
			
		||||
        cdb.make_drive(configdrive_iso)
 | 
			
		||||
 | 
			
		||||
    return configdrive_iso
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# Prepare and create configdrive for instance
 | 
			
		||||
def generate_configdrive(context, instance, injected_files,
 | 
			
		||||
                         network_info, admin_password):
 | 
			
		||||
    # Create network configuration files
 | 
			
		||||
    LOG.debug('Creating config drive 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,
 | 
			
		||||
                                              network_info,
 | 
			
		||||
                                              admin_password)
 | 
			
		||||
    return transportfiles
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user