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:
parent
e5acf4f961
commit
3e1692b966
|
@ -15,6 +15,9 @@
|
||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
|
|
||||||
|
|
||||||
|
from nova.conf import paths
|
||||||
|
|
||||||
|
|
||||||
zvm_opt_group = cfg.OptGroup('zvm',
|
zvm_opt_group = cfg.OptGroup('zvm',
|
||||||
title='zVM Options',
|
title='zVM Options',
|
||||||
help="""
|
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
|
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.
|
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
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
|
import copy
|
||||||
import mock
|
import mock
|
||||||
|
|
||||||
|
from nova import conf
|
||||||
|
from nova import context
|
||||||
from nova import exception
|
from nova import exception
|
||||||
|
from nova.network import model as network_model
|
||||||
|
from nova import objects
|
||||||
from nova import test
|
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
|
from nova.virt.zvm import driver as zvmdriver
|
||||||
|
|
||||||
|
|
||||||
|
CONF = conf.CONF
|
||||||
|
|
||||||
|
|
||||||
class TestZVMDriver(test.NoDBTestCase):
|
class TestZVMDriver(test.NoDBTestCase):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(TestZVMDriver, self).setUp()
|
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')
|
self.flags(cloud_connector_url='https://1.1.1.1:1111', group='zvm')
|
||||||
with mock.patch('nova.virt.zvm.utils.'
|
|
||||||
'ConnectorClient.call') as mcall:
|
with mock.patch('nova.virt.zvm.utils.ConnectorClient.call') as mcall, \
|
||||||
mcall.return_value = {'hypervisor_hostname': 'TESTHOST'}
|
mock.patch('pwd.getpwuid', return_value=mock.Mock(pw_name='test')):
|
||||||
self._driver = zvmdriver.ZVMDriver('virtapi')
|
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):
|
def test_driver_init_no_url(self):
|
||||||
self.flags(cloud_connector_url=None, group='zvm')
|
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['memory_mb_used'])
|
||||||
self.assertEqual(0, results['disk_available_least'])
|
self.assertEqual(0, results['disk_available_least'])
|
||||||
self.assertEqual('TESTHOST', results['hypervisor_hostname'])
|
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)
|
||||||
|
|
|
@ -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
|
import mock
|
||||||
|
|
||||||
|
from nova import context
|
||||||
from nova import exception
|
from nova import exception
|
||||||
from nova import test
|
from nova import test
|
||||||
|
from nova.tests.unit import fake_instance
|
||||||
from nova.virt.zvm import driver as zvmdriver
|
from nova.virt.zvm import driver as zvmdriver
|
||||||
|
|
||||||
|
|
||||||
|
@ -23,13 +25,17 @@ class TestZVMHypervisor(test.NoDBTestCase):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(TestZVMHypervisor, self).setUp()
|
super(TestZVMHypervisor, self).setUp()
|
||||||
|
self.flags(instance_name_template='abc%5d')
|
||||||
self.flags(cloud_connector_url='https://1.1.1.1:1111', group='zvm')
|
self.flags(cloud_connector_url='https://1.1.1.1:1111', group='zvm')
|
||||||
with mock.patch('nova.virt.zvm.utils.'
|
with mock.patch('nova.virt.zvm.utils.'
|
||||||
'ConnectorClient.call') as mcall:
|
'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')
|
driver = zvmdriver.ZVMDriver('virtapi')
|
||||||
self._hypervisor = driver._hypervisor
|
self._hypervisor = driver._hypervisor
|
||||||
|
|
||||||
|
self._context = context.RequestContext('fake_user', 'fake_project')
|
||||||
|
|
||||||
@mock.patch('nova.virt.zvm.utils.ConnectorClient.call')
|
@mock.patch('nova.virt.zvm.utils.ConnectorClient.call')
|
||||||
def test_get_available_resource(self, call):
|
def test_get_available_resource(self, call):
|
||||||
host_info = {'disk_available': 1144,
|
host_info = {'disk_available': 1144,
|
||||||
|
@ -67,3 +73,27 @@ class TestZVMHypervisor(test.NoDBTestCase):
|
||||||
call.return_value = ['vm1', 'vm2']
|
call.return_value = ['vm1', 'vm2']
|
||||||
inst_list = self._hypervisor.list_names()
|
inst_list = self._hypervisor.list_names()
|
||||||
self.assertEqual(['vm1', 'vm2'], inst_list)
|
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 zvmconnector import connector
|
||||||
|
|
||||||
|
from nova import context
|
||||||
from nova import exception
|
from nova import exception
|
||||||
from nova import test
|
from nova import test
|
||||||
|
from nova.tests.unit import fake_instance
|
||||||
from nova.virt.zvm import utils as zvmutils
|
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['rc'], exc.rc)
|
||||||
self.assertEqual(expected['rs'], exc.rs)
|
self.assertEqual(expected['rs'], exc.rs)
|
||||||
self.assertEqual(expected['errmsg'], exc.errmsg)
|
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
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# 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_log import log as logging
|
||||||
from oslo_serialization import jsonutils
|
from oslo_serialization import jsonutils
|
||||||
|
from oslo_utils import excutils
|
||||||
|
|
||||||
from nova import conf
|
from nova import conf
|
||||||
from nova import exception
|
from nova import exception
|
||||||
from nova.i18n import _
|
from nova.i18n import _
|
||||||
from nova.objects import fields as obj_fields
|
from nova.objects import fields as obj_fields
|
||||||
|
from nova import utils
|
||||||
from nova.virt import driver
|
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 hypervisor
|
||||||
|
from nova.virt.zvm import utils as zvmutils
|
||||||
|
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
CONF = conf.CONF
|
CONF = conf.CONF
|
||||||
|
|
||||||
|
|
||||||
|
DEFAULT_EPH_DISK_FMT = 'ext3'
|
||||||
|
|
||||||
|
|
||||||
class ZVMDriver(driver.ComputeDriver):
|
class ZVMDriver(driver.ComputeDriver):
|
||||||
"""z/VM implementation of ComputeDriver."""
|
"""z/VM implementation of ComputeDriver."""
|
||||||
|
|
||||||
def __init__(self, virtapi):
|
def __init__(self, virtapi):
|
||||||
super(ZVMDriver, self).__init__(virtapi)
|
super(ZVMDriver, self).__init__(virtapi)
|
||||||
|
|
||||||
if not CONF.zvm.cloud_connector_url:
|
self._validate_options()
|
||||||
error = _('Must specify cloud_connector_url in zvm config '
|
|
||||||
'group to use compute_driver=zvm.driver.ZVMDriver')
|
|
||||||
raise exception.ZVMDriverException(error=error)
|
|
||||||
|
|
||||||
self._hypervisor = hypervisor.Hypervisor(
|
self._hypervisor = hypervisor.Hypervisor(
|
||||||
CONF.zvm.cloud_connector_url, ca_file=CONF.zvm.ca_file)
|
CONF.zvm.cloud_connector_url, ca_file=CONF.zvm.ca_file)
|
||||||
|
|
||||||
LOG.info("The zVM compute driver has been initialized.")
|
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):
|
def init_host(self, host):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@ -80,3 +114,188 @@ class ZVMDriver(driver.ComputeDriver):
|
||||||
|
|
||||||
def get_available_nodes(self, refresh=False):
|
def get_available_nodes(self, refresh=False):
|
||||||
return self._hypervisor.get_available_nodes(refresh=refresh)
|
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()
|
||||||
|
|
|
@ -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
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
|
import os
|
||||||
|
import pwd
|
||||||
|
|
||||||
from oslo_log import log as logging
|
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 import exception
|
||||||
from nova.virt.zvm import utils as zvmutils
|
from nova.virt.zvm import utils as zvmutils
|
||||||
|
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
|
CONF = conf.CONF
|
||||||
|
|
||||||
|
|
||||||
class Hypervisor(object):
|
class Hypervisor(object):
|
||||||
|
@ -29,12 +35,15 @@ class Hypervisor(object):
|
||||||
|
|
||||||
self._reqh = zvmutils.ConnectorClient(zcc_url,
|
self._reqh = zvmutils.ConnectorClient(zcc_url,
|
||||||
ca_file=ca_file)
|
ca_file=ca_file)
|
||||||
|
host_info = self._get_host_info()
|
||||||
|
|
||||||
# Very very unlikely the hostname will be changed, so when create
|
# Very very unlikely the hostname will be changed, so when create
|
||||||
# hypervisor object, store the information in the cache and after
|
# hypervisor object, store the information in the cache and after
|
||||||
# that we can use it directly without query again from connectorclient
|
# that we can use it directly without query again from connectorclient
|
||||||
self._hypervisor_hostname = self._get_host_info().get(
|
self._hypervisor_hostname = host_info['hypervisor_hostname']
|
||||||
'hypervisor_hostname')
|
|
||||||
|
self._rhost = ''.join([pwd.getpwuid(os.geteuid()).pw_name, '@',
|
||||||
|
CONF.my_ip])
|
||||||
|
|
||||||
def _get_host_info(self):
|
def _get_host_info(self):
|
||||||
host_stats = {}
|
host_stats = {}
|
||||||
|
@ -55,3 +64,81 @@ class Hypervisor(object):
|
||||||
def list_names(self):
|
def list_names(self):
|
||||||
"""list names of the servers in the hypervisor"""
|
"""list names of the servers in the hypervisor"""
|
||||||
return self._reqh.call('guest_list')
|
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
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
|
import os
|
||||||
from oslo_log import log as logging
|
from oslo_log import log as logging
|
||||||
import six
|
import six
|
||||||
import six.moves.urllib.parse as urlparse
|
import six.moves.urllib.parse as urlparse
|
||||||
from zvmconnector import connector
|
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 import exception
|
||||||
|
from nova.virt import configdrive
|
||||||
|
|
||||||
|
|
||||||
|
CONF = conf.CONF
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
@ -59,3 +66,56 @@ class ConnectorClient(object):
|
||||||
raise exception.ZVMConnectorError(results=results)
|
raise exception.ZVMConnectorError(results=results)
|
||||||
|
|
||||||
return results['output']
|
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
|
||||||
|
|
Loading…
Reference in New Issue