Merge "z/VM Driver: Spawn and destroy function of z/VM driver"

This commit is contained in:
Zuul 2018-07-16 16:34:27 +00:00 committed by Gerrit Code Review
commit b2dafb1ee8
9 changed files with 907 additions and 11 deletions

View File

@ -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.
"""),
]

View File

@ -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)

View 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)

View File

@ -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)

View File

@ -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')

View File

@ -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
View 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)

View File

@ -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)

View File

@ -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