From 2e3d781d869675b9257cc7ab101faa578fdc41e0 Mon Sep 17 00:00:00 2001 From: Huang Rui Date: Wed, 22 Mar 2017 18:03:12 +0800 Subject: [PATCH] Move nova zvm virt driver to separate namespace Currently, nova zvm virt driver is in nova namespace. This result in some troubles when generating config file, generating docs and packaging. All changes in this commit are trying to move nova zvm virt driver code into new namespace - nova_zvm . Change-Id: I5251069dfd24ff4e337e9308439415482eb2234c --- .gitignore | 4 + .testr.conf | 2 +- nova/__init__.py | 18 + nova/virt/__init__.py | 18 + nova/virt/zvm/__init__.py | 29 - nova/virt/zvm/driver.py | 2040 +--------------- .../unit/virt/zvm => nova_zvm}/__init__.py | 0 nova_zvm/tests/__init__.py | 0 nova_zvm/tests/unit/__init__.py | 0 nova_zvm/tests/unit/virt/zvm/__init__.py | 0 .../tests/unit/virt/zvm/test_zvm.py | 107 +- nova_zvm/virt/__init__.py | 0 nova_zvm/virt/zvm/__init__.py | 29 + {nova => nova_zvm}/virt/zvm/conf.py | 0 {nova => nova_zvm}/virt/zvm/configdrive.py | 8 +- {nova => nova_zvm}/virt/zvm/const.py | 0 {nova => nova_zvm}/virt/zvm/dist.py | 4 +- nova_zvm/virt/zvm/driver.py | 2045 +++++++++++++++++ {nova => nova_zvm}/virt/zvm/exception.py | 0 {nova => nova_zvm}/virt/zvm/imageop.py | 14 +- {nova => nova_zvm}/virt/zvm/instance.py | 16 +- {nova => nova_zvm}/virt/zvm/networkop.py | 4 +- {nova => nova_zvm}/virt/zvm/opts.py | 9 +- {nova => nova_zvm}/virt/zvm/utils.py | 16 +- {nova => nova_zvm}/virt/zvm/volumeop.py | 19 +- setup.cfg | 3 +- tox.ini | 17 +- 27 files changed, 2235 insertions(+), 2167 deletions(-) create mode 100644 nova/__init__.py create mode 100644 nova/virt/__init__.py rename {nova/tests/unit/virt/zvm => nova_zvm}/__init__.py (100%) create mode 100644 nova_zvm/tests/__init__.py create mode 100644 nova_zvm/tests/unit/__init__.py create mode 100644 nova_zvm/tests/unit/virt/zvm/__init__.py rename {nova => nova_zvm}/tests/unit/virt/zvm/test_zvm.py (98%) create mode 100644 nova_zvm/virt/__init__.py create mode 100644 nova_zvm/virt/zvm/__init__.py rename {nova => nova_zvm}/virt/zvm/conf.py (100%) rename {nova => nova_zvm}/virt/zvm/configdrive.py (97%) rename {nova => nova_zvm}/virt/zvm/const.py (100%) rename {nova => nova_zvm}/virt/zvm/dist.py (99%) create mode 100644 nova_zvm/virt/zvm/driver.py rename {nova => nova_zvm}/virt/zvm/exception.py (100%) rename {nova => nova_zvm}/virt/zvm/imageop.py (99%) rename {nova => nova_zvm}/virt/zvm/instance.py (99%) rename {nova => nova_zvm}/virt/zvm/networkop.py (98%) rename {nova => nova_zvm}/virt/zvm/opts.py (82%) rename {nova => nova_zvm}/virt/zvm/utils.py (99%) rename {nova => nova_zvm}/virt/zvm/volumeop.py (99%) diff --git a/.gitignore b/.gitignore index 991c14d..8e4503e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,9 @@ *.log *.pyc +*.swp .autogenerated .coverage +.eggs .project .pydevproject .settings @@ -16,5 +18,7 @@ cover/* covhtml dist/ doc/build/ +doc/source/_static/ +etc/*.sample nova_zvm_virt_driver.egg-info/ .python-version diff --git a/.testr.conf b/.testr.conf index ec0d96c..700f7a5 100644 --- a/.testr.conf +++ b/.testr.conf @@ -3,7 +3,7 @@ test_command=OS_STDOUT_CAPTURE=${OS_STDOUT_CAPTURE:-1} \ OS_STDERR_CAPTURE=${OS_STDERR_CAPTURE:-1} \ OS_TEST_TIMEOUT=${OS_TEST_TIMEOUT:-60} \ ${PYTHON:-python} -m subunit.run discover \ - -t ./nova/tests/unit/virt/zvm/ ./nova/tests/unit/virt/zvm \ + -t ./nova_zvm/tests/unit/virt/zvm/ ./nova_zvm/tests/unit/virt/zvm \ $LISTOPT $IDOPTION test_id_option=--load-list $IDFILE test_list_option=--list diff --git a/nova/__init__.py b/nova/__init__.py new file mode 100644 index 0000000..232b0c2 --- /dev/null +++ b/nova/__init__.py @@ -0,0 +1,18 @@ +# Copyright 2017 IBM Corp. +# +# All Rights Reserved. +# +# 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. + +# Required to play nicely with namespace composition (PEP420). +__import__('pkg_resources').declare_namespace(__name__) diff --git a/nova/virt/__init__.py b/nova/virt/__init__.py new file mode 100644 index 0000000..232b0c2 --- /dev/null +++ b/nova/virt/__init__.py @@ -0,0 +1,18 @@ +# Copyright 2017 IBM Corp. +# +# All Rights Reserved. +# +# 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. + +# Required to play nicely with namespace composition (PEP420). +__import__('pkg_resources').declare_namespace(__name__) diff --git a/nova/virt/zvm/__init__.py b/nova/virt/zvm/__init__.py index f16dc50..e69de29 100644 --- a/nova/virt/zvm/__init__.py +++ b/nova/virt/zvm/__init__.py @@ -1,29 +0,0 @@ -# Copyright 2013 IBM Corp. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -"""A connection to an IBM z/VM Virtualization system. - -Generally, OpenStack z/VM virt driver will call xCat REST API to operate -to z/VM hypervisors.xCat has a control point(a virtual machine) in z/VM -system, which enables xCat management node to control the z/VM system. -OpenStack z/VM driver will communicate with xCat management node through -xCat REST API. Thus OpenStack can operate to z/VM system indirectly. - -""" - - -from nova.virt.zvm import driver - - -ZVMDriver = driver.ZVMDriver diff --git a/nova/virt/zvm/driver.py b/nova/virt/zvm/driver.py index e7dcb0c..3461f7d 100644 --- a/nova/virt/zvm/driver.py +++ b/nova/virt/zvm/driver.py @@ -1,4 +1,6 @@ -# Copyright 2013 IBM Corp. +# Copyright 2017 IBM Corp. +# +# All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain @@ -12,2034 +14,18 @@ # License for the specific language governing permissions and limitations # under the License. -import contextlib -import datetime -import eventlet -import itertools -import operator -import os -import six -import time +"""Shim layer for nova_zvm.virt.zvm.driver.PowerVMDriver. -from oslo_log import log as logging -from oslo_serialization import jsonutils -from oslo_service import loopingcall -from oslo_utils import excutils -from oslo_utils import timeutils -from oslo_utils import units -from oslo_utils import uuidutils -from oslo_utils import versionutils +Duplicate all public symbols. This is necessary for the constants as well as +the classes - because instances of the classes need to be able to resolve +references to the constants. +""" -from nova.api.metadata import base as instance_metadata -from nova.compute import power_state -from nova.compute import task_states -from nova.compute import utils as compute_utils -from nova.compute import vm_states -from nova import exception as nova_exception -from nova.i18n import _, _LI, _LW -from nova.image import api as image_api -from nova.image import glance -from nova.objects import fields -from nova import utils -from nova.virt import configdrive -from nova.virt import driver -from nova.virt.zvm import conf -from nova.virt.zvm import configdrive as zvmconfigdrive -from nova.virt.zvm import const -from nova.virt.zvm import dist -from nova.virt.zvm import exception -from nova.virt.zvm import imageop -from nova.virt.zvm import instance as zvminstance -from nova.virt.zvm import networkop -from nova.virt.zvm import utils as zvmutils -from nova.virt.zvm import volumeop -from nova.volume import cinder +import nova_zvm.virt.zvm.driver as real_drv -LOG = logging.getLogger(__name__) -CONF = conf.CONF -CONF.import_opt('host', 'nova.conf') -CONF.import_opt('my_ip', 'nova.conf') - -ZVMInstance = zvminstance.ZVMInstance - - -class ZVMDriver(driver.ComputeDriver): - """z/VM implementation of ComputeDriver.""" - - capabilities = { - "has_imagecache": True, - "supports_recreate": False, - "supports_migrate_to_same_host": True, - "supports_attach_interface": False - } - - def __init__(self, virtapi): - super(ZVMDriver, self).__init__(virtapi) - self._xcat_url = zvmutils.get_xcat_url() - - # incremental sleep interval list - _inc_slp = [5, 10, 20, 30, 60] - _slp = 5 - - # TODO(jichenjc): update _xcat_version when xcat reboot - self._xcat_version = self._get_xcat_version() - version_ok = self.has_min_version(const.XCAT_MINIMUM_VERSION) - while (not version_ok): - LOG.warning(_LW("WARNING: the xcat version communicating with is " - "%(xcat_version)s, but the minimum requested " - "version by openstack zvm driver is %(minimum)s " - "will sleep some time and check again"), - {'xcat_version': self._xcat_version, - 'minimum': const.XCAT_MINIMUM_VERSION}) - self._xcat_version = self._get_xcat_version() - version_ok = self.has_min_version(const.XCAT_MINIMUM_VERSION) - - _slp = len(_inc_slp) != 0 and _inc_slp.pop(0) or _slp - time.sleep(_slp) - - self._host_stats = [] - _slp = 5 - - while (self._host_stats == []): - try: - self._host_stats = self.update_host_status() - except Exception as e: - # Ignore any exceptions and log as warning - _slp = len(_inc_slp) != 0 and _inc_slp.pop(0) or _slp - msg = _LW("Failed to get host stats while initializing zVM " - "driver due to reason %(reason)s, will re-try in " - "%(slp)d seconds") - LOG.warning(msg, {'reason': six.text_type(e), - 'slp': _slp}) - time.sleep(_slp) - - self._networkop = networkop.NetworkOperator() - self._zvm_images = imageop.ZVMImages() - self._pathutils = zvmutils.PathUtils() - self._networkutils = zvmutils.NetworkUtils() - self._volumeop = volumeop.VolumeOperator() - self._volume_api = cinder.API() - self._dist_manager = dist.ListDistManager() - self._image_api = image_api.API() - - # booting multi instances at sametime will failed because we will - # import image to nova if root_disk_units not set,so multi threads - # might work on same image and the lastest one will overwrite the - # former ones' image data.The former ones can't find the data any more - # so add this semaphore to synchronize between those threads - self._imageop_semaphore = eventlet.semaphore.Semaphore(1) - - def init_host(self, host): - """Initialize anything that is necessary for the driver to function, - including catching up with currently running VM's on the given host. - """ - try: - self._volumeop.init_host(self._host_stats) - except Exception as e: - emsg = zvmutils.format_exception_msg(e) - LOG.warning(_LW("Exception raised while initializing z/VM driver: " - "%s"), emsg) - - def get_info(self, instance): - """Get the current status of an instance, by name (not ID!) - - Returns a dict containing: - :state: the running state, one of the power_state codes - :max_mem: (int) the maximum memory in KBytes allowed - :mem: (int) the memory in KBytes used by the domain - :num_cpu: (int) the number of virtual CPUs for the domain - :cpu_time: (int) the CPU time used in nanoseconds - - """ - inst_name = instance['name'] - zvm_inst = ZVMInstance(self, instance) - - try: - return zvm_inst.get_info() - except exception.ZVMXCATRequestFailed as err: - emsg = err.format_message() - if (emsg.__contains__("Invalid nodes and/or groups") and - emsg.__contains__("Forbidden")): - LOG.warning(_LW("z/VM instance %s does not exist"), inst_name, - instance=instance) - raise nova_exception.InstanceNotFound(instance_id=inst_name) - else: - raise err - - def list_instances(self): - """Return the names of all the instances known to the virtualization - layer, as a list. - """ - zvm_host = CONF.zvm_host - hcp_base = self._get_hcp_info()['hostname'] - - url = self._xcat_url.tabdump("/zvm") - res_dict = zvmutils.xcat_request("GET", url) - - instances = [] - - with zvmutils.expect_invalid_xcat_resp_data(res_dict): - data_entries = res_dict['data'][0][1:] - for data in data_entries: - l = data.split(",") - node, hcp = l[0].strip("\""), l[1].strip("\"") - hcp_short = hcp_base.partition('.')[0] - - # zvm host and zhcp are not included in the list - if (hcp.upper() == hcp_base.upper() and - node.upper() not in (zvm_host.upper(), - hcp_short.upper(), CONF.zvm_xcat_master.upper())): - instances.append(node) - - return instances - - def _instance_exists(self, instance_name): - """Overwrite this to using instance name as input parameter.""" - return instance_name in self.list_instances() - - def instance_exists(self, instance): - """Overwrite this to using instance name as input parameter.""" - return self._instance_exists(instance.name) - - def spawn(self, context, instance, image_meta, injected_files, - admin_password, network_info=None, block_device_info=None, - flavor=None): - """Create a new instance/VM/domain on the virtualization platform. - - Once this successfully completes, the instance should be - running (power_state.RUNNING). - - If this fails, any partial instance should be completely - cleaned up, and the virtualization platform should be in the state - that it was before this call began. - - :param context: security context - :param instance: Instance object as returned by DB layer. - This function should use the data there to guide - the creation of the new instance. - :param image_meta: image object returned by nova.image.glance that - defines the image from which to boot this instance - :param injected_files: User files to inject into instance. - :param admin_password: Administrator password to set in instance. - :param network_info: - :py:meth:`~nova.network.manager.NetworkManager.get_instance_nw_info` - :param block_device_info: Information about block devices to be - attached to the instance. - - """ - # For zVM instance, limit the maximum length of instance name to be 8 - if len(instance['name']) > 8: - msg = (_("Don't support spawn vm on zVM hypervisor with instance " - "name: %s, please change your instance_name_template to make " - "sure the length of instance name is not longer than 8 " - "characters") % instance['name']) - raise nova_exception.InvalidInput(reason=msg) - - root_mount_device, boot_from_volume = zvmutils.is_boot_from_volume( - block_device_info) - bdm = driver.block_device_info_get_mapping(block_device_info) - - # Update the root device name in instance table - root_device_name = '/dev/' + const.ZVM_DEFAULT_ROOT_DISK - if boot_from_volume: - root_device_name = root_mount_device - instance.root_device_name = root_device_name - instance.save() - - if not network_info: - msg = _("Not support boot without a NIC.") - raise exception.ZVMDriverError(msg=msg) - - # Use image_type to distinguish the image that is for normal deploy - # and volume snapshot image - image_type = '' - # Ensure the used image is a valid zVM image - if not boot_from_volume: - # This is because commit fbe31e461ac3f16edb795993558a2314b4c16b52 - # changes the image_meta from dict to object, we have several - # unique property can't be handled well - # see bug 1537921 for detail info - image_meta = self._image_api.get(context, image_meta.id) - self._zvm_images.zimage_check(image_meta) - if 'image_comments' not in image_meta['properties']: - image_type = 'xcatconf4z' - else: - image_type = 'cloudimg' - else: - image_type = 'volume-snapshot' - - compute_node = CONF.zvm_host - hcp_info = self._get_hcp_info() - zhcp = hcp_info['hostname'] - zhcp_userid = hcp_info['userid'] - - zvm_inst = ZVMInstance(self, instance) - instance_path = self._pathutils.get_instance_path(compute_node, - zvm_inst._name) - # Create network configuration files - LOG.debug('Creating network configuration files ' - 'for instance: %s', zvm_inst._name, instance=instance) - base_nic_vdev = CONF.zvm_default_nic_vdev - - if not boot_from_volume: - os_version = image_meta['properties']['os_version'] - else: - volume_id = self._extract_volume_id(bdm, root_mount_device) - volume_summery = self._volume_api.get(context, volume_id) - volume_meta = volume_summery['volume_metadata'] - os_version = volume_meta['os_version'] - instance.system_metadata['image_os_version'] = os_version - instance.save() - - linuxdist = self._dist_manager.get_linux_dist(os_version)() - files_and_cmds = linuxdist.create_network_configuration_files( - instance_path, network_info, base_nic_vdev) - (net_conf_files, net_conf_cmds) = files_and_cmds - # Add network configure files to inject_files - if len(net_conf_files) > 0: - injected_files.extend(net_conf_files) - - # Create configure drive - if not CONF.zvm_config_drive_inject_password: - admin_password = CONF.zvm_image_default_password - transportfiles = None - if configdrive.required_by(instance): - transportfiles = self._create_config_drive(context, instance_path, - instance, injected_files, admin_password, - net_conf_cmds, linuxdist, image_type) - - LOG.info(_LI("The instance %(name)s is spawning at %(node)s"), - {'name': zvm_inst._name, 'node': compute_node}, - instance=instance) - - spawn_start = time.time() - - try: - deploy_image_name = None - if not boot_from_volume: - tmp_file_fn = None - bundle_file_path = None - - with self._imageop_semaphore: - root_disk_units = image_meta['properties'].get( - 'root_disk_units', '') - # Currently, disk unit values have the form number:units, - # for example: '3338:CYL'. Images captured using older - # versions of the driver may lack the colon delimiter and - # units. If the unit-less form is found, convert it to the - # new form by adding the units. - if ':' not in root_disk_units: - (tmp_file_fn, image_file_path, - bundle_file_path) = self._import_image_to_nova( - context, - instance, image_meta) - image_meta = self._zvm_images.\ - set_image_root_disk_units( - context, image_meta, image_file_path) - root_disk_units = image_meta['properties'][ - 'root_disk_units'] - disk_units = root_disk_units.split(":")[1] - if ((disk_units == "CYL" and CONF.zvm_diskpool_type == "FBA") - or (disk_units == "BLK" and - CONF.zvm_diskpool_type == "ECKD")): - msg = (_("The image's disk size units is: %(diskunits)s," - " it doesn't match the specified disk type" - " %(disktype)s in nova.conf."), - {'diskunits': disk_units, - 'disktype': CONF.zvm_diskpool_type}) - raise exception.ZVMImageError(msg=msg) - - image_in_xcat = self._zvm_images.image_exist_xcat( - instance['image_ref']) - if not image_in_xcat: - self._import_image_to_xcat(context, instance, image_meta, - tmp_file_fn) - elif bundle_file_path is not None: - self._pathutils.clean_temp_folder(bundle_file_path) - - deploy_image_name = self._zvm_images.get_imgname_xcat( - instance['image_ref']) - - # Create xCAT node and userid for the instance - zvm_inst.create_xcat_node(zhcp) - zvm_inst.create_userid(block_device_info, image_meta, context, - deploy_image_name) - - # Setup network for z/VM instance - self._preset_instance_network(zvm_inst._name, network_info) - self._add_nic_to_table(zvm_inst._name, network_info) - - # Call nodeset restapi to deploy image on node - if not boot_from_volume: - zvm_inst.update_node_info(image_meta) - zvm_inst.deploy_node(deploy_image_name, transportfiles) - else: - zvmutils.punch_configdrive_file(transportfiles, zvm_inst._name) - - if image_type in ['xcatconf4z', 'volume-snapshot']: - # Change vm's admin password during spawn - zvmutils.punch_adminpass_file(instance_path, zvm_inst._name, - admin_password, linuxdist) - if zvmutils.xcat_support_iucv(self._xcat_version): - # Punch IUCV server files to reader. - zvmutils.punch_iucv_file(os_version, zhcp, zhcp_userid, - zvm_inst._name, instance_path) - - # punch ephemeral disk info to the instance - if instance['ephemeral_gb'] != 0: - eph_disks = block_device_info.get('ephemerals', []) - if eph_disks == []: - zvmutils.process_eph_disk(zvm_inst._name) - else: - for idx, eph in enumerate(eph_disks): - vdev = zvmutils.generate_eph_vdev(idx) - fmt = eph.get('guest_format') - mount_dir = ''.join([CONF.zvm_default_ephemeral_mntdir, - str(idx)]) - zvmutils.process_eph_disk(zvm_inst._name, vdev, fmt, - mount_dir) - - # Wait neutron zvm-agent add NIC information to user direct. - self._wait_and_get_nic_direct(zvm_inst._name, instance) - - # Attach persistent volume, exclude root volume - bdm_attach = list(bdm) - bdm_attach = self._exclude_root_volume_bdm(bdm_attach, - root_mount_device) - self._attach_volume_to_instance(context, instance, bdm_attach) - - # 1. Prepare for booting from volume - # 2. Write the zipl.conf file and issue zipl - if boot_from_volume: - (lun, wwpn, size, fcp) = zvm_inst.prepare_volume_boot(context, - instance, bdm, root_mount_device, - volume_meta) - zvmutils.punch_zipl_file(instance_path, zvm_inst._name, - lun, wwpn, fcp, volume_meta) - - # Power on the instance, then put MN's public key into instance - zvm_inst.power_on() - spawn_time = time.time() - spawn_start - LOG.info(_LI("Instance spawned succeeded in %s seconds"), - spawn_time, instance=instance) - except (exception.ZVMXCATCreateNodeFailed, - exception.ZVMImageError): - with excutils.save_and_reraise_exception(): - zvm_inst.delete_xcat_node() - except (exception.ZVMXCATCreateUserIdFailed, - exception.ZVMNetworkError, - exception.ZVMVolumeError, - exception.ZVMXCATUpdateNodeFailed, - exception.ZVMXCATDeployNodeFailed): - with excutils.save_and_reraise_exception(): - self.destroy(context, instance, network_info, - block_device_info) - except Exception as err: - # Just a error log then re-raise - with excutils.save_and_reraise_exception(): - LOG.error(_("Deploy image to instance %(instance)s " - "failed with reason: %(err)s"), - {'instance': zvm_inst._name, 'err': err}, - instance=instance) - finally: - self._pathutils.clean_temp_folder(instance_path) - - # Update image last deploy date in xCAT osimage table - if not boot_from_volume: - self._zvm_images.update_last_use_date(deploy_image_name) - - def _create_config_drive(self, context, instance_path, instance, - injected_files, admin_password, commands, - linuxdist, image_type=''): - if CONF.config_drive_format not in ['tgz', 'iso9660']: - msg = (_("Invalid config drive format %s") % - CONF.config_drive_format) - raise exception.ZVMConfigDriveError(msg=msg) - - LOG.debug('Using config drive', instance=instance) - - extra_md = {} - if CONF.zvm_config_drive_inject_password: - extra_md['admin_pass'] = admin_password - - udev_settle = '' - if image_type in ['xcatconf4z', 'volume-snapshot']: - udev_settle = linuxdist.get_znetconfig_contents() - if udev_settle: - if len(commands) == 0: - znetconfig = '\n'.join(('#!/bin/bash', udev_settle)) - else: - znetconfig = '\n'.join(('#!/bin/bash', commands, udev_settle)) - znetconfig += '\nrm -rf /tmp/znetconfig.sh\n' - # Create a temp file in instance to execute above commands - net_cmd_file = [] - net_cmd_file.append(('/tmp/znetconfig.sh', znetconfig)) # nosec - injected_files.extend(net_cmd_file) - # injected_files.extend(('/tmp/znetconfig.sh', znetconfig)) - - inst_md = instance_metadata.InstanceMetadata(instance, - content=injected_files, - extra_md=extra_md, - request_context=context) - # network_metadata will prevent the hostname of the instance from - # being set correctly, so clean the value - inst_md.network_metadata = None - - configdrive_tgz = os.path.join(instance_path, 'cfgdrive.tgz') - - LOG.debug('Creating config drive at %s', configdrive_tgz, - instance=instance) - with zvmconfigdrive.ZVMConfigDriveBuilder(instance_md=inst_md) as cdb: - cdb.make_drive(configdrive_tgz) - - return configdrive_tgz - - def _preset_instance_network(self, instance_name, network_info): - self._networkop.config_xcat_mac(instance_name) - LOG.debug("Add ip/host name on xCAT MN for instance %s", - instance_name) - try: - network = network_info[0]['network'] - ip_addr = network['subnets'][0]['ips'][0]['address'] - except Exception: - if network_info: - msg = _("Invalid network info: %s") % str(network_info) - else: - msg = _("Network info is Empty") - raise exception.ZVMNetworkError(msg=msg) - - self._networkop.add_xcat_host(instance_name, ip_addr, instance_name) - self._networkop.makehosts() - - def _import_image_to_nova(self, context, instance, image_meta): - image_file_name = image_meta['properties']['image_file_name'] - disk_file = ''.join(j for j in image_file_name.split(".img")[0] - if j.isalnum()) + ".img" - tmp_file_fn = self._pathutils.make_time_stamp() - bundle_file_path = self._pathutils.get_bundle_tmp_path(tmp_file_fn) - image_file_path = self._pathutils.get_img_path( - bundle_file_path, disk_file) - - LOG.debug("Downloading the image %s from glance to nova compute " - "server", image_meta['id'], instance=instance) - self._zvm_images.fetch_image(context, - image_meta['id'], - image_file_path, - instance['user_id'], - instance['project_id']) - return (tmp_file_fn, image_file_path, bundle_file_path) - - def _import_image_to_xcat(self, context, instance, image_meta, tmp_f_fn): - # Format the image name and image disk file in case user named them - # with special characters - image_name = ''.join(i for i in image_meta['name'] if i.isalnum()) - spawn_path = self._pathutils.get_spawn_folder() - image_file_name = image_meta['properties']['image_file_name'] - disk_file = ''.join(j for j in image_file_name.split(".img")[0] - if j.isalnum()) + ".img" - if tmp_f_fn is None: - tmp_f_fn = self._pathutils.make_time_stamp() - bundle_file_path = self._pathutils.get_bundle_tmp_path(tmp_f_fn) - image_file_path = self._pathutils.get_img_path( - bundle_file_path, disk_file) - LOG.debug("Downloading the image %s from glance to nova compute " - "server", image_meta['id'], instance=instance) - self._zvm_images.fetch_image(context, - image_meta['id'], - image_file_path, - instance['user_id'], - instance['project_id']) - else: - bundle_file_path = self._pathutils.get_bundle_tmp_path(tmp_f_fn) - image_file_path = self._pathutils.get_img_path( - bundle_file_path, disk_file) - - LOG.debug("Generating the manifest.xml as a part of bundle file for " - "image %s", image_meta['id'], instance=instance) - - image_name = zvmutils.remove_prefix_of_unicode(image_name) - - self._zvm_images.generate_manifest_file(image_meta, image_name, - disk_file, bundle_file_path) - - LOG.debug("Generating bundle file for image %s", image_meta['id'], - instance=instance) - image_bundle_package = self._zvm_images.generate_image_bundle( - spawn_path, tmp_f_fn, image_name) - - LOG.debug("Importing the image %s to xCAT", image_meta['id'], - instance=instance) - profile_str = image_name, instance['image_ref'].replace('-', '_') - image_profile = '_'.join(profile_str) - self._zvm_images.check_space_imgimport_xcat(context, instance, - image_bundle_package, CONF.xcat_free_space_threshold, - CONF.zvm_xcat_master) - self._zvm_images.put_image_to_xcat(image_bundle_package, - image_profile) - - @property - def need_legacy_block_device_info(self): - return False - - def destroy(self, context, instance, network_info=None, - block_device_info=None, destroy_disks=False): - """Destroy (shutdown and delete) the specified instance. - - If the instance is not found (for example if networking failed), this - function should still succeed. It's probably a good idea to log a - warning in that case. - - :param instance: Instance object as returned by DB layer. - :param network_info: - :py:meth:`~nova.network.manager.NetworkManager.get_instance_nw_info` - :param block_device_info: Information about block devices that should - be detached from the instance. - :param destroy_disks: Indicates if disks should be destroyed - - """ - inst_name = instance['name'] - root_mount_device, boot_from_volume = zvmutils.is_boot_from_volume( - block_device_info) - zvm_inst = ZVMInstance(self, instance) - - if self._instance_exists(inst_name): - LOG.info(_LI("Destroying instance %s"), inst_name, - instance=instance) - - # Collect diagnostics when the instance is unreachable, since this - # is most often caused by a deployment failure but the deployment - # artifacts are often needed to debug the root cause. - if zvm_inst.is_reachable(): - LOG.debug(("Node %s is reachable, " - "skipping diagnostics collection"), inst_name, - instance=instance) - elif zvm_inst.is_powered_off(): - LOG.debug(("Node %s is powered off, " - "skipping diagnostics collection"), inst_name, - instance=instance) - else: - LOG.debug(("Node %s is powered on but unreachable, " - "collecting diagnostics for failed deployment"), - inst_name, - instance=instance) - zvm_inst.collect_diagnostics(context, - const.DIAGNOSTICS_RSN_DEPLOYMENT_TIMEOUT) - - bdm = driver.block_device_info_get_mapping(block_device_info) - try: - bdm_det = list(bdm) - bdm_det = self._exclude_root_volume_bdm(bdm_det, - root_mount_device) - self._detach_volume_from_instance(instance, bdm_det) - if boot_from_volume: - zvm_inst.clean_volume_boot(context, instance, bdm, - root_mount_device) - except exception.ZVMBaseException as err: - LOG.warning(_LW("Failed to detach volume: %s"), - err.format_message(), instance=instance) - - if network_info: - try: - for vif in network_info: - self._networkop.clean_mac_switch_host(inst_name) - except exception.ZVMNetworkError: - LOG.warning(_LW("Clean MAC and VSWITCH failed while " - "destroying z/VM instance %s"), inst_name, - instance=instance) - - zvm_inst.delete_userid(self._get_hcp_info()['nodename'], context) - else: - LOG.warning(_LW('Instance %s does not exist'), inst_name, - instance=instance) - - def manage_image_cache(self, context, filtered_instances): - """Clean the image cache in xCAT MN.""" - LOG.info(_LI("Check and clean image cache in xCAT")) - clean_period = CONF.xcat_image_clean_period - self._zvm_images.clean_image_cache_xcat(clean_period) - - def reboot(self, context, instance, network_info, reboot_type, - block_device_info=None, bad_volumes_callback=None): - """Reboot the specified instance. - - After this is called successfully, the instance's state - goes back to power_state.RUNNING. The virtualization - platform should ensure that the reboot action has completed - successfully even in cases in which the underlying domain/vm - is paused or halted/stopped. - - :param instance: Instance object as returned by DB layer. - :param network_info: - :py:meth:`~nova.network.manager.NetworkManager.get_instance_nw_info` - :param reboot_type: Either a HARD or SOFT reboot - :param block_device_info: Info pertaining to attached volumes - :param bad_volumes_callback: Function to handle any bad volumes - encountered - - """ - zvm_inst = ZVMInstance(self, instance) - if reboot_type == 'SOFT': - zvm_inst.reboot() - else: - zvm_inst.reset() - - if not zvm_inst._reachable: - LOG.error(_("Failed to reboot instance %s: timeout"), - zvm_inst._name, instance=instance) - raise nova_exception.InstanceRebootFailure(reason=_("timeout")) - - def get_host_ip_addr(self): - """Retrieves the IP address of the dom0.""" - return CONF.my_ip - - def _format_mountpoint(self, mountpoint): - """Change mountpoint from /dev/sdX to /dev/vdX. - - When a SCSI device is pluged in, the system will create a file node - /dev/sdX for the SCSI device. If the file node exists already as a - link to another file, the link will be overlayed and the file node - will be seized by the SCSI device. - - For example, if the first SCSI device is pluged in and the mountpoint - is specified as /dev/sdb, the SCSI device will be attached to /dev/sda - and /dev/sdb is created as a link to /dev/sda. Then the second SCSI - device is pluged in. It will be attached to /dev/sdb and the link will - no longer exist. - - To avoid this case, if mountpoint is /dev/sdX, it will be changed to - /dev/vdX. Otherwize it will keep as it is. - - When instance's root_device_name is /dev/dasdX, the mountpoint will be - changed to /dev/dX. That's not what is expected. Format mountpoint to - /dev/vdX in this case. - - :param mountpoint: The file node name of the mountpoint. - - """ - - mountpoint = mountpoint.lower() - mountpoint = mountpoint.replace('/dev/d', '/dev/sd') - return mountpoint.replace('/dev/s', '/dev/v') - - def attach_volume(self, context, connection_info, instance, mountpoint, - disk_bus=None, device_type=None, encryption=None): - """Attach the disk to the instance at mountpoint using info.""" - if instance.vm_state == vm_states.PAUSED: - msg = _("Attaching to a paused instance is not supported.") - raise exception.ZVMDriverError(msg=msg) - if mountpoint: - mountpoint = self._format_mountpoint(mountpoint) - if self._instance_exists(instance['name']): - zvm_inst = ZVMInstance(self, instance) - is_active = zvm_inst.is_reachable() - - zvm_inst.attach_volume(self._volumeop, context, connection_info, - instance, mountpoint, is_active) - - def detach_volume(self, connection_info, instance, mountpoint=None, - encryption=None): - """Detach the disk attached to the instance.""" - if instance.vm_state == vm_states.PAUSED: - msg = _("Detaching from a paused instance is not supported.") - raise exception.ZVMDriverError(msg=msg) - if mountpoint: - mountpoint = self._format_mountpoint(mountpoint) - if self._instance_exists(instance['name']): - zvm_inst = ZVMInstance(self, instance) - is_active = zvm_inst.is_reachable() - - zvm_inst.detach_volume(self._volumeop, connection_info, instance, - mountpoint, is_active) - - def _reset_power_state(self, state, instance): - # If the instance's power_state is "RUNNING", power it on after - # capture. If "PAUSED", pause it after capture. - if state == power_state.RUNNING or state == power_state.PAUSED: - try: - self.power_on({}, instance, []) - except nova_exception.InstancePowerOnFailure as err: - LOG.warning(_LW("Power On instance %(inst)s fail after " - "capture, please check manually. The error is: %(err)s"), - {'inst': instance['name'], 'err': err.format_message()}, - instance=instance) - if state == power_state.PAUSED: - try: - self.pause(instance) - except (exception.ZVMXCATRequestFailed, - exception.ZVMInvalidXCATResponseDataError, - exception.ZVMXCATInternalError) as err: - LOG.warning(_LW("Pause instance %(inst)s fail after capture, " - "please check manually. The error is: %(err)s"), - {'inst': instance['name'], 'err': err.format_message()}, - instance=instance) - - def _get_xcat_image_file_path(self, image_name_xcat): - """Get image file path from image name in xCAT.""" - image_path = self._zvm_images.get_image_file_path_from_image_name( - image_name_xcat) - image_name = self._zvm_images.get_image_file_name(image_path) - return '/'.join((image_path, image_name)) - - def _is_shared_image_repo(self, image_name_xcat): - """To check whether nova can access xCAT image repo.""" - try: - image_file_path = self._get_xcat_image_file_path(image_name_xcat) - except exception.ZVMImageError: - # image path not exist or image file not found - return False - - return os.stat(image_file_path).st_mode & 4 == 4 - - def snapshot(self, context, instance, image_href, update_task_state): - """Snapshots the specified instance. - - :param context: security context - :param instance: Instance object as returned by DB layer. - :param image_href: Reference to a pre-created image that will - hold the snapshot. - """ - # Check the image status - (image_service, image_id) = glance.get_remote_image_service(context, - image_href) - image_meta = image_service.show(context, image_href) - - # remove user names special characters, this name will only be used - # to pass to xcat and combine with UUID in xcat. - image_name = ''.join(i for i in image_meta['name'] if i.isalnum()) - image_name = zvmutils.remove_prefix_of_unicode(image_name) - image_name_xcat = None - - # Make sure the instance's power_state is running and unpaused before - # doing a capture. - state = instance['power_state'] - if (state == power_state.NOSTATE or state == power_state.CRASHED or - state == power_state.SUSPENDED): - raise nova_exception.InstanceNotReady(instance_id=instance['name']) - elif state == power_state.SHUTDOWN: - self.power_on({}, instance, []) - elif state == power_state.PAUSED: - self.unpause(instance) - - # Check xCAT free space and invoke the zvmimages.create_zvm_image() - try: - free_space_xcat = self._zvm_images.get_free_space_xcat( - CONF.xcat_free_space_threshold, - CONF.zvm_xcat_master) - user_dict = self._get_user_directory(instance['name']) - imgcapture_needed = self._zvm_images.get_imgcapture_needed( - instance, user_dict) - if (free_space_xcat - imgcapture_needed) < 0: - larger = max(CONF.xcat_free_space_threshold, imgcapture_needed) - size_needed = float(larger - free_space_xcat) - self._zvm_images.prune_image_xcat(context, size_needed, - imgcapture_needed) - image_name_xcat = self._zvm_images.create_zvm_image(instance, - image_name, - image_href) - # Update image last create date in xCAT osimage table - self._zvm_images.update_last_use_date(image_name_xcat) - except (exception.ZVMImageError, - exception.ZVMXCATXdshFailed): - with excutils.save_and_reraise_exception(): - self._reset_power_state(state, instance) - self._zvm_images.delete_image_glance(image_service, context, - image_href) - - self._reset_power_state(state, instance) - shared_image_repo = self._is_shared_image_repo(image_name_xcat) - - if not shared_image_repo: - # The image will be exported from xCAT and imported to nova after - # successfully captured. - snapshot_time_path = self._zvm_images.get_snapshot_time_path() - try: - image_bundle = self._zvm_images.get_image_from_xcat( - image_name_xcat, image_name, snapshot_time_path) - except exception.ZVMImageError: - with excutils.save_and_reraise_exception(): - self._zvm_images.delete_image_glance(image_service, - context, image_href) - self._zvm_images.clean_up_snapshot_time_path( - snapshot_time_path) - self._zvm_images.delete_image_from_xcat(image_name_xcat) - - # The image in the xCAT MN will be removed after imported to nova - # Invoke rmimage REST API twice to remove image and object - self._zvm_images.delete_image_from_xcat(image_name_xcat) - - # Untar the image_bundle and parse manifest.xml - image_package_path = os.path.join(snapshot_time_path, - image_name_xcat) - try: - self._zvm_images.untar_image_bundle(snapshot_time_path, - image_bundle) - manifest = self._zvm_images.parse_manifest_xml( - image_package_path) - image_file_name = self._zvm_images.get_image_file_name( - image_package_path) - image_file_path = '/'.join((image_package_path, - image_file_name)) - except exception.ZVMImageError: - with excutils.save_and_reraise_exception(): - self._zvm_images.delete_image_glance(image_service, - context, image_href) - self._zvm_images.clean_up_snapshot_time_path( - snapshot_time_path) - else: - image_file_path = self._get_xcat_image_file_path(image_name_xcat) - (image_package_path, _toss, - image_file_name) = image_file_path.rpartition('/') - manifest = self._zvm_images.get_image_menifest(image_name_xcat) - - root_disk_units = self._zvm_images.get_root_disk_units(image_file_path) - - # Before upload, update the instance task_state to image_pending_upload - update_task_state(task_state=task_states.IMAGE_PENDING_UPLOAD) - - # manifest.xml contributes some new image meta - LOG.debug("Snapshot extracted, beginning image upload", - instance=instance) - new_image_meta = { - 'is_public': False, - 'status': 'active', - 'properties': { - 'image_location': 'snapshot', - 'image_state': 'available', - 'owner_id': instance['project_id'], - 'image_type_xcat': manifest['imagetype'], - 'type': 'snapshot', - 'architecture': manifest['osarch'], - 'os_name': manifest['osname'], - 'os_version': manifest['osvers'], - 'image_profile': manifest['profile'], - 'provisioning_method': manifest['provmethod'], - 'image_file_name': image_file_name, - 'hypervisor_type': const.HYPERVISOR_TYPE, - 'root_disk_units': root_disk_units - }, - 'disk_format': 'raw', - 'container_format': 'bare', - } - - # Upload that image to the image service - image_path = os.path.join(image_package_path, image_file_name) - update_task_state(task_state=task_states.IMAGE_UPLOADING, - expected_state=task_states.IMAGE_PENDING_UPLOAD) - - def cleanup_temp_image(): - if not shared_image_repo: - # Clean up temp image in nova snapshot temp folder - self._zvm_images.clean_up_snapshot_time_path( - snapshot_time_path) - else: - # Clean up image from xCAT image repo for all-in-one mode - self._zvm_images.delete_image_from_xcat(image_name_xcat) - - try: - with open(image_path, 'r') as image_file: - image_service.update(context, - image_href, - new_image_meta, - image_file, - purge_props=False) - except Exception: - with excutils.save_and_reraise_exception(): - self._zvm_images.delete_image_glance(image_service, context, - image_href) - cleanup_temp_image() - - LOG.debug("Snapshot image upload complete", instance=instance) - - cleanup_temp_image() - - LOG.info(_LI("Snapshot complete successfully"), instance=instance) - - def pause(self, instance): - """Pause the specified instance.""" - LOG.debug('Pausing %s', instance['name'], instance=instance) - zvm_inst = ZVMInstance(self, instance) - zvm_inst.pause() - - def unpause(self, instance): - """Unpause paused VM instance.""" - LOG.debug('Un-pausing %s', instance['name'], instance=instance) - zvm_inst = ZVMInstance(self, instance) - zvm_inst.unpause() - - def power_off(self, instance, timeout=0, retry_interval=0): - """Power off the specified instance.""" - LOG.debug('Stopping z/VM instance %s', instance['name'], - instance=instance) - zvm_inst = ZVMInstance(self, instance) - zvm_inst.power_off(timeout, retry_interval) - - def power_on(self, context, instance, network_info, - block_device_info=None): - """Power on the specified instance.""" - LOG.debug('Starting z/VM instance %s', instance['name'], - instance=instance) - zvm_inst = ZVMInstance(self, instance) - zvm_inst.power_on() - - def get_available_resource(self, nodename=None): - """Retrieve resource information. - - This method is called when nova-compute launches, and - as part of a periodic task - - :param nodename: - node which the caller want to get resources from - a driver that manages only one node can safely ignore this - :returns: Dictionary describing resources - - """ - LOG.debug("Getting available resource for %s", CONF.zvm_host) - stats = self.update_host_status()[0] - - mem_used = stats['host_memory_total'] - stats['host_memory_free'] - supported_instances = stats['supported_instances'] - dic = { - 'vcpus': stats['vcpus'], - 'memory_mb': stats['host_memory_total'], - 'local_gb': stats['disk_total'], - 'vcpus_used': stats['vcpus_used'], - 'memory_mb_used': mem_used, - 'local_gb_used': stats['disk_used'], - 'hypervisor_type': stats['hypervisor_type'], - 'hypervisor_version': stats['hypervisor_version'], - 'hypervisor_hostname': stats['hypervisor_hostname'], - 'cpu_info': jsonutils.dumps(stats['cpu_info']), - 'disk_available_least': stats['disk_available'], - 'supported_instances': supported_instances, - 'numa_topology': None, - } - - return dic - - def check_can_live_migrate_destination(self, ctxt, instance_ref, - src_compute_info, dst_compute_info, - block_migration=False, - disk_over_commit=False): - """Check if it is possible to execute live migration. - - This runs checks on the destination host, and then calls - back to the source host to check the results. - - :param ctxt: security context - :param instance_ref: nova.db.sqlalchemy.models.Instance - :param src_compute_info: Info about the sending machine - :param dst_compute_info: Info about the receiving machine - :param block_migration: if true, prepare for block migration - :param disk_over_commit: if true, allow disk over commit - """ - # For z/VM, all live migration check will be done in - # check_can_live_migration_source, so just return a dst_compute_info. - # And we only support shared storage live migration. - migrate_data = {'dest_host': dst_compute_info['hypervisor_hostname'], - 'is_shared_storage': True} - dest_check_data = {'migrate_data': migrate_data} - - return dest_check_data - - def check_can_live_migrate_source(self, ctxt, instance_ref, - dest_check_data, block_device_info=None): - """Check if it is possible to execute live migration. - - This checks if the live migration can succeed, based on the - results from check_can_live_migrate_destination. - - :param context: security context - :param instance_ref: nova.db.sqlalchemy.models.Instance - :param dest_check_data: result of check_can_live_migrate_destination - - """ - LOG.info(_LI("Checking source host for live-migration for %s"), - instance_ref['name'], instance=instance_ref) - - migrate_data = dest_check_data.get('migrate_data', {}) - dest_host = migrate_data.get('dest_host', None) - userid = zvmutils.get_userid(instance_ref['name']) - migrate_data.update({'source_xcat_mn': CONF.zvm_xcat_server, - 'zvm_userid': userid}) - - if dest_host is not None: - try: - self._vmrelocate(dest_host, instance_ref['name'], 'test') - except nova_exception.MigrationError as err: - emsg = err.format_message() - if isinstance(CONF.zvm_vmrelocate_force, str): - force = CONF.zvm_vmrelocate_force.lower() - if ('domain' in force) or ('architecture' in force): - if '1944' in emsg: - # force domain/architecture in effect, ignore - return migrate_data - LOG.error(_("Live-migrating check failed: %s"), emsg, - instance=instance_ref) - raise nova_exception.MigrationPreCheckError(reason=emsg) - - return migrate_data - else: - reason = _("Invalid migration data") - raise nova_exception.MigrationPreCheckError(reason=reason) - - def check_can_live_migrate_destination_cleanup(self, ctxt, - dest_check_data): - """Do required cleanup on dest host after check_can_live_migrate calls - - :param ctxt: security context - :param dest_check_data: result of check_can_live_migrate_destination - - """ - # For z/VM, nothing needed to be cleanup - return - - def pre_live_migration(self, ctxt, instance_ref, block_device_info, - network_info, disk_info, migrate_data=None): - """Prepare an instance for live migration - - :param ctxt: security context - :param instance_ref: instance object that will be migrated - :param block_device_info: instance block device information - :param network_info: instance network information - :param migrate_data: implementation specific data dict. - """ - if block_device_info in ([], None, {}): - msg = _("Not supported live-migration with persistent " - "volume attached") - LOG.error(msg, instance=instance_ref) - raise nova_exception.MigrationError(reason=msg) - - zvm_inst = ZVMInstance(self, instance_ref) - source_xcat_mn = migrate_data.get('source_xcat_mn', '') - userid = migrate_data.get('zvm_userid') - hcp = self._get_hcp_info()['hostname'] - same_xcat_mn = source_xcat_mn == CONF.zvm_xcat_server - dest_diff_mn_key = None - - if not same_xcat_mn: - # The two z/VM system managed by two different xCAT MN - zvm_inst.create_xcat_node(hcp, userid) - dest_diff_mn_key = zvmutils.get_mn_pub_key() - if network_info is not None: - network = network_info[0]['network'] - ip_addr = network['subnets'][0]['ips'][0]['address'] - self._networkop.add_xcat_host(zvm_inst._name, ip_addr, - zvm_inst._name) - self._networkop.makehosts() - - return {'same_xcat_mn': same_xcat_mn, - 'dest_diff_mn_key': dest_diff_mn_key} - - def pre_block_migration(self, ctxt, instance_ref, disk_info): - """Prepare a block device for migration - - :param ctxt: security context - :param instance_ref: instance object that will have its disk migrated - :param disk_info: information about disk to be migrated (as returned - from get_instance_disk_info()) - """ - # We don't support block_migration - return - - def live_migration(self, ctxt, instance_ref, dest, - post_method, recover_method, block_migration=False, - migrate_data=None): - """Live migration of an instance to another host. - - :params ctxt: security context - :params instance_ref: - nova.db.sqlalchemy.models.Instance object - instance object that is migrated. - :params dest: destination host - :params post_method: - post operation method. - expected nova.compute.manager.post_live_migration. - :params recover_method: - recovery method when any exception occurs. - expected nova.compute.manager.recover_live_migration. - :params block_migration: if true, migrate VM disk. - :params migrate_data: implementation specific params. - - """ - inst_name = instance_ref['name'] - dest_host = migrate_data['dest_host'] - LOG.info(_LI("Live-migrating %(inst)s to %(dest)s"), - {'inst': inst_name, 'dest': dest_host}, instance=instance_ref) - - same_mn = migrate_data['pre_live_migration_result']['same_xcat_mn'] - dest_diff_mn_key = migrate_data['pre_live_migration_result'].get( - 'dest_diff_mn_key', None) - - if not same_mn and dest_diff_mn_key: - auth_command = ('echo "%s" >> /root/.ssh/authorized_keys' % - dest_diff_mn_key) - zvmutils.xdsh(inst_name, auth_command) - - try: - self._vmrelocate(dest_host, inst_name, 'move') - except nova_exception.MigrationError as err: - LOG.error(_("Live-migration failed: %s"), err.format_message(), - instance=instance_ref) - with excutils.save_and_reraise_exception(): - recover_method(ctxt, instance_ref, dest, - block_migration, migrate_data) - - if not same_mn: - # Delete node definition at source xCAT MN - zvm_inst = ZVMInstance(self, instance_ref) - self._networkop.clean_mac_switch_host(zvm_inst._name) - zvm_inst.delete_xcat_node() - - post_method(ctxt, instance_ref, dest, - block_migration, migrate_data) - - def post_live_migration_at_destination(self, ctxt, instance_ref, - network_info, - block_migration=False, - block_device_info=None): - """Post operation of live migration at destination host. - - :param ctxt: security context - :param instance_ref: instance object that is migrated - :param network_info: instance network information - :param block_migration: if true, post operation of block_migration. - - """ - inst_name = instance_ref['name'] - nic_vdev = CONF.zvm_default_nic_vdev - zhcp = self._get_hcp_info()['hostname'] - - for vif in network_info: - LOG.debug('Create nic for instance: %(inst)s, MAC: ' - '%(mac)s Network: %(network)s Vdev: %(vdev)s', - {'inst': inst_name, 'mac': vif['address'], - 'network': vif['network']['label'], 'vdev': nic_vdev}, - instance=instance_ref) - self._networkop.add_xcat_mac(inst_name, nic_vdev, - vif['address'], zhcp) - self._networkop.add_xcat_switch(inst_name, vif['id'], - nic_vdev, zhcp) - nic_vdev = str(hex(int(nic_vdev, 16) + 3))[2:] - - def unfilter_instance(self, instance, network_info): - """Stop filtering instance.""" - # Not supported for now - return - - def _vmrelocate(self, dest_host, instance_name, action): - """Perform live-migration.""" - body = ['destination=%s' % dest_host, - 'action=%s' % action, - 'immediate=%s' % CONF.zvm_vmrelocate_immediate, - 'max_total=%s' % CONF.zvm_vmrelocate_max_total, - 'max_quiesce=%s' % CONF.zvm_vmrelocate_max_quiesce] - if CONF.zvm_vmrelocate_force is not None: - body.append('force=%s' % CONF.zvm_vmrelocate_force) - - url = self._xcat_url.rmigrate('/' + instance_name) - try: - res = zvmutils.xcat_request("PUT", url, body) - except (exception.ZVMXCATRequestFailed, - exception.ZVMInvalidXCATResponseDataError, - exception.ZVMXCATInternalError) as err: - raise nova_exception.MigrationError(reason=err.format_message()) - - res_info = res['info'] - if not (len(res_info) > 0 and - len(res_info[0]) > 0 and - res_info[-1][0].__contains__("Done")): - msg = _("Live-migration failed: %s") % str(res_info) - LOG.error(msg) - raise nova_exception.MigrationError(reason=msg) - - def reset_network(self, instance): - """reset networking for specified instance.""" - # TODO(rui): to implement this later. - pass - - def inject_network_info(self, instance, nw_info): - """inject network info for specified instance.""" - # TODO(rui): to implement this later. - pass - - def plug_vifs(self, instance, network_info): - """Plug VIFs into networks.""" - # TODO(rui): to implement this later. - pass - - def unplug_vifs(self, instance, network_info): - """Unplug VIFs from networks.""" - # TODO(rui): to implement this later - pass - - def ensure_filtering_rules_for_instance(self, instance_ref, network_info): - # It enforces security groups on host initialization and live - # migration. In z/VM we do not assume instances running upon host - # initialization - return - - def update_host_status(self): - """Refresh host stats. One compute service entry possibly - manages several hypervisors, so will return a list of host - status information. - """ - LOG.debug("Updating host status for %s", CONF.zvm_host) - - caps = [] - host = CONF.zvm_host - - info = self._get_host_inventory_info(host) - - data = {'host': CONF.host, - 'allowed_vm_type': const.ALLOWED_VM_TYPE} - data['vcpus'] = info['vcpus'] - data['vcpus_used'] = info['vcpus_used'] - data['cpu_info'] = info['cpu_info'] - data['disk_total'] = info['disk_total'] - data['disk_used'] = info['disk_used'] - data['disk_available'] = info['disk_available'] - data['host_memory_total'] = info['memory_mb'] - data['host_memory_free'] = (info['memory_mb'] - - info['memory_mb_used']) - data['hypervisor_type'] = info['hypervisor_type'] - data['hypervisor_version'] = info['hypervisor_version'] - data['hypervisor_hostname'] = info['hypervisor_hostname'] - data['supported_instances'] = [(const.ARCHITECTURE, - const.HYPERVISOR_TYPE, - fields.VMMode.HVM)] - data['zhcp'] = self._get_hcp_info(info['zhcp']) - data['ipl_time'] = info['ipl_time'] - - caps.append(data) - - return caps - - def _get_hcp_info(self, hcp_hostname=None): - if self._host_stats != []: - return self._host_stats[0]['zhcp'] - else: - if hcp_hostname is not None: - hcp_node = hcp_hostname.partition('.')[0] - return {'hostname': hcp_hostname, - 'nodename': hcp_node, - 'userid': zvmutils.get_userid(hcp_node)} - else: - self._host_stats = self.update_host_status() - return self._host_stats[0]['zhcp'] - - def get_volume_connector(self, instance): - """Get connector information for the instance for attaching to volumes. - - Connector information is a dictionary representing the ip of the - machine that will be making the connection, the name of the iscsi - initiator and the hostname of the machine as follows:: - - { - 'zvm_fcp': fcp - 'wwpns': [wwpn] - 'host': host - } - """ - LOG.debug("Getting volume connector") - - res = self._volumeop.get_volume_connector(instance) - - return res - - def _get_host_inventory_info(self, host): - url = self._xcat_url.rinv('/' + host) - inv_info_raw = zvmutils.xcat_request("GET", url)['info'][0] - inv_keys = const.XCAT_RINV_HOST_KEYWORDS - inv_info = zvmutils.translate_xcat_resp(inv_info_raw[0], inv_keys) - dp_info = self._get_diskpool_info(host) - - host_info = {} - - with zvmutils.expect_invalid_xcat_resp_data(inv_info): - host_info['vcpus'] = int(inv_info['lpar_cpu_total']) - host_info['vcpus_used'] = int(inv_info['lpar_cpu_used']) - host_info['cpu_info'] = {} - host_info['cpu_info'] = {'architecture': const.ARCHITECTURE, - 'cec_model': inv_info['cec_model'], } - host_info['disk_total'] = dp_info['disk_total'] - host_info['disk_used'] = dp_info['disk_used'] - host_info['disk_available'] = dp_info['disk_available'] - mem_mb = zvmutils.convert_to_mb(inv_info['lpar_memory_total']) - host_info['memory_mb'] = mem_mb - mem_mb_used = zvmutils.convert_to_mb(inv_info['lpar_memory_used']) - host_info['memory_mb_used'] = mem_mb_used - host_info['hypervisor_type'] = const.HYPERVISOR_TYPE - verl = inv_info['hypervisor_os'].split()[1].split('.') - version = int(''.join(verl)) - host_info['hypervisor_version'] = version - host_info['hypervisor_hostname'] = inv_info['hypervisor_name'] - host_info['zhcp'] = inv_info['zhcp'] - host_info['ipl_time'] = inv_info['ipl_time'] - - return host_info - - def _get_diskpool_info(self, host): - addp = '&field=--diskpoolspace&field=' + CONF.zvm_diskpool - url = self._xcat_url.rinv('/' + host, addp) - res_dict = zvmutils.xcat_request("GET", url) - - dp_info_raw = res_dict['info'][0] - dp_keys = const.XCAT_DISKPOOL_KEYWORDS - dp_info = zvmutils.translate_xcat_resp(dp_info_raw[0], dp_keys) - - with zvmutils.expect_invalid_xcat_resp_data(dp_info): - for k in list(dp_info.keys()): - s = dp_info[k].strip().upper() - if s.endswith('G'): - sl = s[:-1].split('.') - n1, n2 = int(sl[0]), int(sl[1]) - if n2 >= 5: - n1 += 1 - dp_info[k] = n1 - elif s.endswith('M'): - n_mb = int(s[:-1]) - n_gb, n_ad = n_mb / 1024, n_mb % 1024 - if n_ad >= 512: - n_gb += 1 - dp_info[k] = n_gb - else: - exp = "ending with a 'G' or 'M'" - errmsg = _("Invalid diskpool size format: %(invalid)s; " - "Expected: %(exp)s"), {'invalid': s, 'exp': exp} - LOG.error(errmsg) - raise exception.ZVMDriverError(msg=errmsg) - - return dp_info - - def migrate_disk_and_power_off(self, context, instance, dest, - instance_type, network_info, - block_device_info=None, - timeout=0, retry_interval=0): - """Transfers the disk of a running instance in multiple phases, turning - off the instance before the end. - """ - is_volume_base = zvmutils.is_boot_from_volume(block_device_info)[1] - - if is_volume_base: - msg = _("Not support boot from volume.") - LOG.error(msg, instance=instance) - raise nova_exception.InstanceFaultRollback( - nova_exception.MigrationError(reason=msg)) - - new_root_disk_size = instance_type.root_gb - new_eph_disk_size = instance_type.ephemeral_gb - old_root_disk_size = instance.root_gb - old_eph_disk_size = instance.ephemeral_gb - - if (new_root_disk_size < old_root_disk_size or - new_eph_disk_size < old_eph_disk_size): - err = _("Not support shrink disk") - LOG.error(err, instance=instance) - raise nova_exception.InstanceFaultRollback( - nova_exception.MigrationError(reason=err)) - - # Make sure the instance's power_state is running and unpaused before - # doing a capture. - state = instance['power_state'] - if (state == power_state.NOSTATE or state == power_state.CRASHED or - state == power_state.SUSPENDED): - raise nova_exception.InstanceNotReady(instance_id=instance['name']) - elif state == power_state.SHUTDOWN: - self.power_on({}, instance, []) - elif state == power_state.PAUSED: - self.unpause(instance) - - inst_name = instance['name'] - LOG.debug("Starting to migrate instance %s", inst_name, - instance=instance) - - disk_owner = zvmutils.get_userid(inst_name) - eph_disk_info = self._get_eph_disk_info(inst_name) - - bdm = driver.block_device_info_get_mapping(block_device_info) - self._detach_volume_from_instance(instance, bdm) - - image_name_xcat = self._capture_disk_for_instance(context, instance) - - image_bundle = '' - shared_image_repo = self._is_shared_image_repo(image_name_xcat) - if not shared_image_repo: - # Export image from xCAT to compute node if image repo not shared - snapshot_time_path = self._zvm_images.get_snapshot_time_path() - - try: - image_bundle = self._zvm_images.get_image_from_xcat( - image_name_xcat, - image_name_xcat, - snapshot_time_path) - except exception.ZVMImageError: - with excutils.save_and_reraise_exception(): - self._zvm_images.clean_up_snapshot_time_path( - snapshot_time_path) - - source_image = "".join([zvmutils.get_host(), ":", image_bundle]) - disk_info = { - 'disk_type': CONF.zvm_diskpool_type, - 'disk_source_mn': CONF.zvm_xcat_server, - 'disk_source_image': source_image, - 'disk_image_name': image_name_xcat, - 'disk_owner': disk_owner, - 'disk_eph_size_old': old_eph_disk_size, - 'disk_eph_size_new': new_eph_disk_size, - 'eph_disk_info': eph_disk_info, - 'shared_image_repo': shared_image_repo, - } - - return jsonutils.dumps(disk_info) - - def _get_eph_disk_info(self, inst_name): - user_dict = self._get_user_directory(inst_name) - exl = ''.join(['MDISK ', CONF.zvm_user_root_vdev]) - eph_disks = [mdisk for mdisk in user_dict - if (mdisk.__contains__('MDISK ') and - not mdisk.__contains__(exl))] - - eph_disk_info = [] - with zvmutils.expect_invalid_xcat_resp_data(eph_disks): - for eph in eph_disks: - eph_l = eph.rpartition(" MDISK ")[2].split(' ') - eph_disk_info.append({'vdev': eph_l[0], - 'size': eph_l[3], - 'guest_format': None, - 'size_in_units': True, - 'device_name': eph_l[0]}) - - return eph_disk_info - - def _detach_volume_from_instance(self, instance, block_device_mapping): - for bd in block_device_mapping: - connection_info = bd['connection_info'] - mountpoint = bd['mount_device'] - if mountpoint: - mountpoint = self._format_mountpoint(mountpoint) - - if self._instance_exists(instance['name']): - zvm_inst = ZVMInstance(self, instance) - is_active = zvm_inst.is_reachable() - try: - zvm_inst.detach_volume(self._volumeop, connection_info, - instance, mountpoint, is_active, - rollback=False) - except exception.ZVMVolumeError as err: - LOG.warning(_LW("Failed to detach volume from %(inst)s: " - "%(err)s"), - {'inst': instance['name'], - 'err': err.format_message()}, - instance=instance) - - def _capture_disk_for_instance(self, context, instance): - """Capture disk.""" - zvm_inst = ZVMInstance(self, instance) - image_name = ''.join('rsz' + instance['name']) - image_uuid = str(uuidutils.generate_uuid()) - image_href = image_uuid.replace('-', '_') - - # Capture - orig_provmethod = zvm_inst.get_provmethod() - if orig_provmethod != 'sysclone': - zvm_inst.update_node_provmethod('sysclone') - image_name_xcat = self._zvm_images.create_zvm_image(instance, - image_name, - image_href) - if orig_provmethod != 'sysclone': - zvm_inst.update_node_provmethod(orig_provmethod) - - self._zvm_images.update_last_use_date(image_name_xcat) - - return image_name_xcat - - @contextlib.contextmanager - def cleanup_xcat_image_for_migration(self, image_name_xcat): - """Cleanup xcat image that imported by migrate_disk_and_power_off.""" - try: - yield - except (nova_exception.MigrationError, - exception.ZVMBaseException): - LOG.debug("Cleanup image from xCAT image repository") - with excutils.save_and_reraise_exception(): - self._zvm_images.delete_image_from_xcat(image_name_xcat) - - def finish_migration(self, context, migration, instance, disk_info, - network_info, image_meta, resize_instance, - block_device_info=None, power_on=True): - """Completes a resize, turning on the migrated instance - - :param network_info: - :py:meth:`~nova.network.manager.NetworkManager.get_instance_nw_info` - :param image_meta: image object returned by nova.image.glance that - defines the image from which this instance - was created - """ - # This is because commit fbe31e461ac3f16edb795993558a2314b4c16b52 - # changes the image_meta from dict to object, we have several - # unique property can't be handled well - # see bug 1537921 for detail info - image_meta = self._image_api.get(context, image_meta.id) - - disk_info = jsonutils.loads(disk_info) - - source_xcat_mn = disk_info['disk_source_mn'].encode('gbk') - source_image = disk_info['disk_source_image'].encode('gbk') - image_name_xcat = disk_info['disk_image_name'].encode('gbk') - disk_type = disk_info['disk_type'].encode('gbk') - disk_eph_size_old = disk_info['disk_eph_size_old'] - disk_eph_size_new = disk_info['disk_eph_size_new'] - eph_disk_info = disk_info['eph_disk_info'] - shared_image_repo = disk_info['shared_image_repo'] - - old_eph_info = block_device_info['ephemerals'] - new_eph_info = [eph for eph in eph_disk_info - if eph['vdev'] != CONF.zvm_user_adde_vdev] - - block_device_info = block_device_info or {} - block_device_info['ephemerals'] = new_eph_info - - if (len(old_eph_info) == 1 and - old_eph_info[0]['size'] == disk_eph_size_old): - # Enlarge the only ephemeral disk - block_device_info['ephemerals'][0]['size'] = disk_eph_size_new - block_device_info['ephemerals'][0]['size_in_units'] = False - - old_userid = disk_info['disk_owner'].encode('gbk') - new_userid = None - if old_userid.startswith("rsz"): - new_userid = instance['name'] - else: - new_userid = ''.join(("rsz", old_userid[3:])) - - same_xcat_mn = source_xcat_mn == CONF.zvm_xcat_server - if disk_type != CONF.zvm_diskpool_type: - if same_xcat_mn: - self._zvm_images.delete_image_from_xcat(image_name_xcat) - msg = _("Can not migration between different disk type" - "current is %(c)s, target is %(t)s") % {'t': disk_type, - 'c': CONF.zvm_diskpool_type} - LOG.error(msg, instance=instance) - raise nova_exception.MigrationError(reason=msg) - - profile = image_name_xcat.split('-')[3] - source_host, t, image_bundle = source_image.partition(":") - source_ip = source_host.rpartition("@")[2] - source_image_time_path = image_bundle.rpartition('/')[0] - local_ip = self.get_host_ip_addr() - - same_os = local_ip == source_ip - - hcp_info = self._get_hcp_info() - zhcp = hcp_info['hostname'] - zhcp_userid = hcp_info['userid'] - - new_inst = ZVMInstance(self, instance) - if same_xcat_mn: - # Same xCAT MN - # cleanup networking, will re-configure later - for vif in network_info: - self._networkop.clean_mac_switch_host(new_inst._name) - - if not shared_image_repo: - # cleanup image bundle from source compute node - if not same_os: - utils.execute('ssh', source_host, 'rm', '-rf', - source_image_time_path) - else: - self._pathutils.clean_temp_folder(source_image_time_path) - - # Create a xCAT node poin - old_instance = self._copy_instance(instance) - old_instance['name'] = ''.join(('rsz', instance['name'])) - old_inst = ZVMInstance(self, old_instance) - - with self.cleanup_xcat_image_for_migration(image_name_xcat): - old_inst.copy_xcat_node(new_inst._name) - try: - new_inst.update_node_def(zhcp, new_userid) - except exception.ZVMBaseException: - with excutils.save_and_reraise_exception(): - old_inst.delete_xcat_node() - else: - # Different xCAT MN - new_inst.create_xcat_node(zhcp) - if new_userid != new_inst._name: - try: - new_inst.update_node_def(zhcp, new_userid) - except exception.ZVMBaseException: - with excutils.save_and_reraise_exception(): - old_inst.delete_xcat_node() - - if not same_os: - snapshot_time_path = self._pathutils.get_snapshot_time_path() - dest_image_path = os.path.join(snapshot_time_path, - image_name_xcat + '.tgz') - utils.execute('scp', source_image, snapshot_time_path) - utils.execute('ssh', source_host, - 'rm', '-rf', source_image_time_path) - else: - snapshot_time_path = source_image_time_path - dest_image_path = image_bundle - - try: - self._zvm_images.put_image_to_xcat(dest_image_path, profile) - except exception.ZVMImageError: - with excutils.save_and_reraise_exception(): - new_inst.delete_xcat_node() - - self._zvm_images.clean_up_snapshot_time_path(snapshot_time_path) - - try: - # Pre-config network and create zvm userid - self._preset_instance_network(new_inst._name, network_info) - new_inst.create_userid(block_device_info, image_meta, context) - - if disk_eph_size_old == 0 and disk_eph_size_new > 0: - # Punch ephemeral disk info to the new instance - zvmutils.process_eph_disk(new_inst._name) - - # Add nic and deploy the image - self._add_nic_to_table(new_inst._name, network_info) - self._deploy_root_and_ephemeral(new_inst, image_name_xcat) - self._wait_and_get_nic_direct(new_inst._name, instance) - except exception.ZVMBaseException: - with excutils.save_and_reraise_exception(): - self._zvm_images.delete_image_from_xcat(image_name_xcat) - - if not same_xcat_mn: - try: - for vif in network_info: - self._networkop.clean_mac_switch_host( - new_inst._name) - except exception.ZVMNetworkError as e: - emsg = zvmutils.format_exception_msg(e) - LOG.debug('clean_mac_switch_host error: %s', emsg) - - new_inst.delete_userid(self._get_hcp_info()['nodename'], - context) - new_inst.delete_xcat_node() - - if same_xcat_mn: - new_inst.copy_xcat_node(old_inst._name) - old_inst.delete_xcat_node() - - # re-configure the networking - self._reconfigure_networking(new_inst._name, network_info, - instance, userid=old_userid) - if power_on: - new_inst.power_on() - - # Cleanup image from xCAT image repository - self._zvm_images.delete_image_from_xcat(image_name_xcat) - - bdm = driver.block_device_info_get_mapping(block_device_info) - try: - if zvmutils.xcat_support_iucv(self._xcat_version): - if same_xcat_mn: - zvmutils.punch_iucv_authorized_file(old_inst._name, - new_inst._name, zhcp_userid) - else: - zvmutils.punch_iucv_authorized_file(new_inst._name, - new_inst._name, zhcp_userid) - new_inst.power_on() - self._attach_volume_to_instance(context, instance, bdm) - - if not power_on: - new_inst.power_off() - except exception.ZVMBaseException: - with excutils.save_and_reraise_exception(): - self.destroy(context, instance, network_info, - block_device_info) - if same_xcat_mn: - new_inst.copy_xcat_node(old_inst._name) - old_inst.delete_xcat_node() - - def _reconfigure_networking(self, inst_name, network_info, instance, - userid=None): - self._preset_instance_network(inst_name, network_info) - self._add_nic_to_table(inst_name, network_info) - self._wait_and_get_nic_direct(inst_name, instance) - - def _copy_instance(self, instance): - return zvminstance.CopiedInstance(instance) - - def _attach_volume_to_instance(self, context, instance, - block_device_mapping): - for bd in block_device_mapping: - connection_info = bd['connection_info'] - mountpoint = bd['mount_device'] - self.attach_volume(context, connection_info, instance, mountpoint) - - def _add_nic_to_table(self, inst_name, network_info): - nic_vdev = CONF.zvm_default_nic_vdev - zhcpnode = self._get_hcp_info()['nodename'] - for vif in network_info: - LOG.debug('Create xcat table value about nic: ' - 'ID is %(id)s, address is %(address)s, ' - 'vdev is %(vdev)s', - {'id': vif['id'], 'address': vif['address'], - 'vdev': nic_vdev}) - self._networkop.create_xcat_table_about_nic(zhcpnode, - inst_name, - vif['id'], - vif['address'], - nic_vdev) - nic_vdev = str(hex(int(nic_vdev, 16) + 3))[2:] - - def _deploy_root_and_ephemeral(self, instance, image_name_xcat): - - # Update the nodetype - instance.update_node_info_resize(image_name_xcat) - - # Deploy - instance.deploy_node(image_name_xcat) - - self._zvm_images.update_last_use_date(image_name_xcat) - - def confirm_migration(self, context, migration, instance, network_info): - """Confirms a resize, destroying the source VM.""" - # Point to old instance - old_instance = self._copy_instance(instance) - old_instance['name'] = ''.join(('rsz', instance['name'])) - old_inst = ZVMInstance(self, old_instance) - - if self._instance_exists(old_inst._name): - # Same xCAT MN: - self.destroy(context, old_instance) - else: - # Different xCAT MN: - self.destroy(context, instance) - self._zvm_images.cleanup_image_after_migration(instance['name']) - - def finish_revert_migration(self, context, instance, network_info, - block_device_info=None, power_on=True): - """Finish reverting a resize, powering back on the instance.""" - new_instance = self._copy_instance(instance) - new_instance['name'] = ''.join(('rsz', instance['name'])) - zvm_inst = ZVMInstance(self, new_instance) - bdm = driver.block_device_info_get_mapping(block_device_info) - - if self._instance_exists(zvm_inst._name): - # Same xCAT MN: - old_inst = ZVMInstance(self, instance) - old_inst.copy_xcat_node(new_instance['name']) - if zvmutils.xcat_support_iucv(self._xcat_version): - zvmutils.copy_zvm_table_status(instance['name'], - new_instance['name']) - zvm_inst.delete_xcat_node() - - self._reconfigure_networking(instance['name'], network_info, - instance) - else: - # Different xCAT MN: - self._zvm_images.cleanup_image_after_migration(instance['name']) - - self._attach_volume_to_instance({}, instance, bdm) - - if power_on: - self.power_on({}, instance, []) - - def _get_nic_switch_info(self, inst_name): - url = self._xcat_url.tabdump("/switch") - with zvmutils.expect_invalid_xcat_resp_data(): - switch_info = zvmutils.xcat_request("GET", url)['data'][0] - switch_info.pop(0) - switch_dict = {} - for item in switch_info: - switch_list = item.split(',') - if switch_list[0].strip('"') == inst_name: - switch_dict[switch_list[4].strip('"')] = \ - switch_list[1].strip('"') - - LOG.debug("Switch info the %(inst_name)s is %(switch_dict)s", - {"inst_name": inst_name, "switch_dict": switch_dict}) - return switch_dict - - def _get_user_directory(self, inst_name): - url = self._xcat_url.lsvm('/' + inst_name) - user_dict = zvmutils.xcat_request("GET", url) - - with zvmutils.expect_invalid_xcat_resp_data(user_dict): - dict_str = user_dict['info'][0][0] - - return dict_str.split("\n") - - def set_admin_password(self, instance, new_pass=None): - """Set the root password on the specified instance. - - The first parameter is an instance of nova.compute.service.Instance, - and so the instance is being specified as instance.name. The second - parameter is the value of the new password. - """ - if new_pass is not None: - self._set_admin_password(instance['name'], new_pass) - - def _set_admin_password(self, inst_name, password): - command = "echo 'root:%s' | chpasswd" % password - try: - if zvmutils.xcat_support_iucv(self._xcat_version): - # After support IUCV, will use execcmdonvm to replace xdsh. - zvmutils.execcmdonvm(inst_name, command) - else: - zvmutils.xdsh(inst_name, command) - except exception.ZVMXCATXdshFailed as err: - LOG.error(_("Setting root password for instance %(instance)s " - "failed with reason: %(err)s"), - {'instance': inst_name, 'err': err.format_message()}) - raise err - - def _wait_and_get_nic_direct(self, inst_name, instance): - """Wait until neutron zvm-agent add NICs into user direct done.""" - def _wait_for_nic_add_in_direct(inst_name, expiration): - if (CONF.zvm_reachable_timeout and - timeutils.utcnow() > expiration): - msg = _("NIC update check failed " - "on instance:%s") % instance.uuid - raise exception.ZVMNetworkError(msg=msg) - - try: - switch_dict = self._get_nic_switch_info(inst_name) - if switch_dict and '' not in switch_dict.values(): - for key, value in switch_dict.items(): - args = '&checknics=' + key - url = self._xcat_url.lsvm('/' + inst_name) - url = url + args - res_info = zvmutils.xcat_request("GET", url) - with zvmutils.expect_invalid_xcat_resp_data(res_info): - if ("errorcode" in res_info and - (len(res_info["errorcode"]) > 0) and - res_info["errorcode"][0] != '0'): - # we didn't found the definition - return - else: - # in this case, the nic switch info is not ready yet - # need another loop to check until time out or find it - return - - except exception.ZVMBaseException as e: - # Ignore any zvm driver exceptions - LOG.info(_LI('encounter error %s during get vswitch info'), - e.format_message(), instance=instance) - return - - # Enter here means all NIC granted - LOG.info(_LI("All NICs are added in user direct for " - "instance %s."), inst_name, instance=instance) - raise loopingcall.LoopingCallDone() - - expiration = timeutils.utcnow() + datetime.timedelta( - seconds=CONF.zvm_reachable_timeout) - LOG.info(_LI("Wait neturon-zvm-agent to add NICs to %s user direct."), - inst_name, instance=instance) - timer = loopingcall.FixedIntervalLoopingCall( - _wait_for_nic_add_in_direct, inst_name, expiration) - timer.start(interval=10).wait() - - def get_console_output(self, context, instance): - """Get console output for an instance.""" - - def append_to_log(log_data, log_path): - LOG.debug('log_data: %(log_data)r, log_path: %(log_path)r', - {'log_data': log_data, 'log_path': log_path}) - fp = open(log_path, 'a+') - fp.write(log_data) - fp.close() - return log_path - - zvm_inst = ZVMInstance(self, instance) - logsize = CONF.zvm_console_log_size * units.Ki - console_log = "" - - try: - console_log = zvm_inst.get_console_log(logsize) - except exception.ZVMXCATInternalError: - # Ignore no console log avaiable error - LOG.info(_LI("No new console log avaiable.")) - log_path = self._pathutils.get_console_log_path(CONF.zvm_host, - zvm_inst._name) - # TODO(jichenjc): need consider shrink log file size - append_to_log(console_log, log_path) - - log_fp = file(log_path, 'rb') - log_data, remaining = utils.last_bytes(log_fp, logsize) - if remaining > 0: - LOG.info(_LI('Truncated console log returned, %d bytes ignored'), - remaining, instance=instance) - - return log_data - - def get_host_uptime(self): - """Get host uptime.""" - with zvmutils.expect_invalid_xcat_resp_data(self._host_stats): - return self._host_stats[0]['ipl_time'] - - def get_available_nodes(self, refresh=False): - return [d['hypervisor_hostname'] for d in self._host_stats - if (d.get('hypervisor_hostname') is not None)] - - def _extract_volume_id(self, bdm, root_device): - for bd in bdm: - mount_point = bd['mount_device'] - is_root = zvmutils.is_volume_root(root_device, mount_point) - if is_root: - return bd['connection_info']['serial'] - - errmsg = _("Failed to extract volume id from block device mapping." - "%s") % str(bdm) - raise exception.ZVMDriverError(msg=errmsg) - - def _exclude_root_volume_bdm(self, bdm, root_mount_device): - for bd in bdm: - mountpoint = bd['mount_device'] - is_root = zvmutils.is_volume_root(root_mount_device, mountpoint) - if is_root: - bdm.remove(bd) - return bdm - - def _get_xcat_version(self): - url = self._xcat_url.version() - version_info = zvmutils.xcat_request("GET", url) - with zvmutils.expect_invalid_xcat_resp_data(version_info): - dict_str = version_info['data'][0][0] - version = dict_str.split()[1] - return version - - def _version_check(self, req_ver=None, op=operator.lt): - try: - if req_ver is not None: - cur = versionutils.convert_version_to_int(self._xcat_version) - req = versionutils.convert_version_to_int(req_ver) - if op(cur, req): - return False - return True - except Exception: - return False - - def has_min_version(self, req_ver=None): - return self._version_check(req_ver=req_ver, op=operator.lt) - - def has_version(self, req_ver=None): - return self._version_check(req_ver=req_ver, op=operator.ne) - - def default_device_names_for_instance(self, instance, root_device_name, - *block_device_lists): - """Generate missing device names for an instance. - Default are /dev/vdN. - """ - compute_utils.default_device_names_for_instance( - instance, root_device_name, *block_device_lists) - for bdm in itertools.chain(*block_device_lists): - bdm.device_name = self._format_mountpoint(bdm.device_name) - bdm.save() - - def get_device_name_for_instance(self, instance, bdms, block_device_obj): - """Validates (or generates) a device name for instance. - Default are /dev/vdN. - """ - device_name = block_device_obj.get("device_name") - device_name = compute_utils.get_device_name_for_instance( - instance, bdms, device_name) - return self._format_mountpoint(device_name) +LOG = real_drv.LOG +CONF = real_drv.CONF +ZVMInstance = real_drv.ZVMInstance +ZVMDriver = real_drv.ZVMDriver diff --git a/nova/tests/unit/virt/zvm/__init__.py b/nova_zvm/__init__.py similarity index 100% rename from nova/tests/unit/virt/zvm/__init__.py rename to nova_zvm/__init__.py diff --git a/nova_zvm/tests/__init__.py b/nova_zvm/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/nova_zvm/tests/unit/__init__.py b/nova_zvm/tests/unit/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/nova_zvm/tests/unit/virt/zvm/__init__.py b/nova_zvm/tests/unit/virt/zvm/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/nova/tests/unit/virt/zvm/test_zvm.py b/nova_zvm/tests/unit/virt/zvm/test_zvm.py similarity index 98% rename from nova/tests/unit/virt/zvm/test_zvm.py rename to nova_zvm/tests/unit/virt/zvm/test_zvm.py index c757fd0..7629fcf 100644 --- a/nova/tests/unit/virt/zvm/test_zvm.py +++ b/nova_zvm/tests/unit/virt/zvm/test_zvm.py @@ -21,11 +21,6 @@ import time import mock from mox3 import mox -from oslo_concurrency import processutils -from oslo_config import cfg -from oslo_serialization import jsonutils -from oslo_utils import fileutils - from nova.compute import power_state from nova.compute import vm_states from nova import context @@ -38,15 +33,20 @@ from nova.tests.unit import fake_instance from nova.virt import configdrive as virt_configdrive from nova.virt import fake from nova.virt import hardware -from nova.virt.zvm import configdrive -from nova.virt.zvm import dist -from nova.virt.zvm import driver -from nova.virt.zvm import exception -from nova.virt.zvm import imageop -from nova.virt.zvm import instance -from nova.virt.zvm import networkop -from nova.virt.zvm import utils as zvmutils -from nova.virt.zvm import volumeop +from oslo_concurrency import processutils +from oslo_config import cfg +from oslo_serialization import jsonutils +from oslo_utils import fileutils + +from nova_zvm.virt.zvm import configdrive +from nova_zvm.virt.zvm import dist +from nova_zvm.virt.zvm import driver +from nova_zvm.virt.zvm import exception +from nova_zvm.virt.zvm import imageop +from nova_zvm.virt.zvm import instance +from nova_zvm.virt.zvm import networkop +from nova_zvm.virt.zvm import utils as zvmutils +from nova_zvm.virt.zvm import volumeop CONF = cfg.CONF @@ -250,7 +250,7 @@ class ZVMDriverTestCases(ZVMTestCase): self._old_inst[k] = '' @mock.patch.object(driver.ZVMDriver, 'update_host_status') - @mock.patch('nova.virt.zvm.driver.ZVMDriver._get_xcat_version') + @mock.patch('nova_zvm.virt.zvm.driver.ZVMDriver._get_xcat_version') def setUp(self, mock_version, mock_update): super(ZVMDriverTestCases, self).setUp() self._setup_fake_inst_obj() @@ -348,7 +348,7 @@ class ZVMDriverTestCases(ZVMTestCase): # return {"data": [{"node": [{"name": ["os000001"], "data": [stat]}]}]} return self._set_reachable(stat) - @mock.patch('nova.virt.zvm.instance.ZVMInstance.get_info') + @mock.patch('nova_zvm.virt.zvm.instance.ZVMInstance.get_info') def test_get_info(self, mk_get_info): _fake_inst_obj = hardware.InstanceInfo(state=0x01, mem_kb=131072, num_cpu=4, cpu_time_ns=330528353, @@ -362,9 +362,10 @@ class ZVMDriverTestCases(ZVMTestCase): self.assertEqual(330528353, inst_info.cpu_time_ns) self.assertEqual(1048576, inst_info.max_mem_kb) - @mock.patch('nova.virt.zvm.exception.ZVMXCATRequestFailed.format_message') - @mock.patch('nova.virt.zvm.exception.ZVMXCATRequestFailed.msg_fmt') - @mock.patch('nova.virt.zvm.instance.ZVMInstance.get_info') + @mock.patch('nova_zvm.virt.zvm.exception.ZVMXCATRequestFailed.' + 'format_message') + @mock.patch('nova_zvm.virt.zvm.exception.ZVMXCATRequestFailed.msg_fmt') + @mock.patch('nova_zvm.virt.zvm.instance.ZVMInstance.get_info') def test_get_info_not_exist(self, mk_get_info, mk_msg_fmt, mk_fm): mk_get_info.side_effect = exception.ZVMXCATRequestFailed _emsg = "Forbidden Invalid nodes and/or groups" @@ -374,8 +375,8 @@ class ZVMDriverTestCases(ZVMTestCase): self.assertRaises(nova_exception.InstanceNotFound, self.driver.get_info, self.instance) - @mock.patch('nova.virt.zvm.exception.ZVMXCATRequestFailed.msg_fmt') - @mock.patch('nova.virt.zvm.instance.ZVMInstance.get_info') + @mock.patch('nova_zvm.virt.zvm.exception.ZVMXCATRequestFailed.msg_fmt') + @mock.patch('nova_zvm.virt.zvm.instance.ZVMInstance.get_info') def test_get_info_other_err(self, mk_get_info, mk_msg_fmt): mk_get_info.side_effect = exception.ZVMXCATRequestFailed mk_msg_fmt.return_value = "Other error" @@ -898,20 +899,20 @@ class ZVMDriverTestCases(ZVMTestCase): self.driver.reboot, self.context, self.instance, {}, "SOFT") - @mock.patch('nova.virt.zvm.instance.ZVMInstance.power_off') + @mock.patch('nova_zvm.virt.zvm.instance.ZVMInstance.power_off') def test_power_off(self, poff): self.driver.power_off(self.instance, 60, 10) poff.assert_called_once_with(60, 10) - @mock.patch('nova.virt.zvm.instance.ZVMInstance._power_state') + @mock.patch('nova_zvm.virt.zvm.instance.ZVMInstance._power_state') def test_power_off_inactive(self, pst): pst.side_effect = exception.ZVMXCATInternalError( "Return Code: 200 Reason Code: 12") self.driver.power_off(self.instance, 60, 10) pst.assert_called_once_with("PUT", "softoff") - @mock.patch('nova.virt.zvm.instance.ZVMInstance._check_power_stat') - @mock.patch('nova.virt.zvm.instance.ZVMInstance._power_state') + @mock.patch('nova_zvm.virt.zvm.instance.ZVMInstance._check_power_stat') + @mock.patch('nova_zvm.virt.zvm.instance.ZVMInstance._power_state') def test_power_off_retry(self, pst, get_pst): get_pst.side_effect = [0x00, 0x04] self.driver.power_off(self.instance, 60, 10) @@ -1934,15 +1935,15 @@ class ZVMInstanceTestCases(ZVMTestCase): self._set_fake_xcat_responses([self._generate_xcat_resp(info)]) self._instance.update_node_info(image_meta) - @mock.patch('nova.virt.zvm.utils.xcat_request') - @mock.patch('nova.virt.zvm.utils.get_host') + @mock.patch('nova_zvm.virt.zvm.utils.xcat_request') + @mock.patch('nova_zvm.virt.zvm.utils.get_host') def test_deploy_node(self, get_host, xcat_req): get_host.return_value = 'fake@fakehost' xcat_req.return_value = 'fake' self._instance.deploy_node('fakeimg', '/fake/file', '0100') - @mock.patch('nova.virt.zvm.utils.xcat_request') - @mock.patch('nova.virt.zvm.utils.get_host') + @mock.patch('nova_zvm.virt.zvm.utils.xcat_request') + @mock.patch('nova_zvm.virt.zvm.utils.get_host') def test_deploy_node_failed(self, get_host, xcat_req): get_host.return_value = 'fake@fakehost' xcat_req.side_effect = exception.ZVMXCATDeployNodeFailed(node="fake", @@ -1950,8 +1951,8 @@ class ZVMInstanceTestCases(ZVMTestCase): self.assertRaises(exception.ZVMXCATDeployNodeFailed, self._instance.deploy_node, 'fakeimg', '/fake/file') - @mock.patch('nova.virt.zvm.instance.ZVMInstance.unlock_userid') - @mock.patch('nova.virt.zvm.instance.ZVMInstance._delete_userid') + @mock.patch('nova_zvm.virt.zvm.instance.ZVMInstance.unlock_userid') + @mock.patch('nova_zvm.virt.zvm.instance.ZVMInstance._delete_userid') def test_delete_userid_is_locked(self, delete_uid, unlock_uid): delete_uid.side_effect = [exception.ZVMXCATInternalError( 'Return Code: 400\nReason Code: 12\n'), @@ -1960,8 +1961,8 @@ class ZVMInstanceTestCases(ZVMTestCase): delete_uid.assert_called() unlock_uid.assert_called_once_with('fakehcp') - @mock.patch('nova.virt.zvm.instance.ZVMInstance.unlock_devices') - @mock.patch('nova.virt.zvm.instance.ZVMInstance._delete_userid') + @mock.patch('nova_zvm.virt.zvm.instance.ZVMInstance.unlock_devices') + @mock.patch('nova_zvm.virt.zvm.instance.ZVMInstance._delete_userid') def test_delete_userid_device_is_locked(self, delete_uid, unlock_dev): delete_uid.side_effect = [exception.ZVMXCATInternalError( 'Return Code: 408\nReason Code: 12\n'), @@ -1974,9 +1975,9 @@ class ZVMInstanceTestCases(ZVMTestCase): mem = self._instance._modify_storage_format('0') self.assertEqual(0, mem) - @mock.patch('nova.virt.zvm.instance.ZVMInstance._get_rinv_info') - @mock.patch('nova.virt.zvm.instance.ZVMInstance.is_reachable') - @mock.patch('nova.virt.zvm.instance.ZVMInstance._check_power_stat') + @mock.patch('nova_zvm.virt.zvm.instance.ZVMInstance._get_rinv_info') + @mock.patch('nova_zvm.virt.zvm.instance.ZVMInstance.is_reachable') + @mock.patch('nova_zvm.virt.zvm.instance.ZVMInstance._check_power_stat') def test_get_info_cpumem(self, mk_get_ps, mk_is_reach, mk_get_rinv_info): mk_get_ps.return_value = power_state.RUNNING @@ -1993,7 +1994,7 @@ class ZVMInstanceTestCases(ZVMTestCase): self.assertEqual(330528353, inst_info.cpu_time_ns) self.assertEqual(1048576, inst_info.max_mem_kb) - @mock.patch('nova.virt.zvm.instance.ZVMInstance._get_rinv_info') + @mock.patch('nova_zvm.virt.zvm.instance.ZVMInstance._get_rinv_info') def test_get_info_cpumempowerstat(self, mk_get_rinv_info): mk_get_rinv_info.return_value = self._fake_inst_info_list_cpumempower @@ -2007,10 +2008,10 @@ class ZVMInstanceTestCases(ZVMTestCase): self.assertEqual(3305.3, inst_info.cpu_time_ns) self.assertEqual(1048576, inst_info.max_mem_kb) - @mock.patch('nova.virt.zvm.exception.ZVMXCATInternalError.msg_fmt') - @mock.patch('nova.virt.zvm.instance.ZVMInstance._get_rinv_info') - @mock.patch('nova.virt.zvm.instance.ZVMInstance.is_reachable') - @mock.patch('nova.virt.zvm.instance.ZVMInstance._check_power_stat') + @mock.patch('nova_zvm.virt.zvm.exception.ZVMXCATInternalError.msg_fmt') + @mock.patch('nova_zvm.virt.zvm.instance.ZVMInstance._get_rinv_info') + @mock.patch('nova_zvm.virt.zvm.instance.ZVMInstance.is_reachable') + @mock.patch('nova_zvm.virt.zvm.instance.ZVMInstance._check_power_stat') def test_get_info_inv_err(self, mk_get_ps, mk_is_reach, mk_get_rinv_info, mk_msg_fmt): mk_get_ps.return_value = power_state.RUNNING @@ -2023,12 +2024,12 @@ class ZVMInstanceTestCases(ZVMTestCase): self.assertRaises(nova_exception.InstanceNotFound, self._instance.get_info) - @mock.patch('nova.virt.zvm.exception.ZVMInvalidXCATResponseDataError.' + @mock.patch('nova_zvm.virt.zvm.exception.ZVMInvalidXCATResponseDataError.' 'msg_fmt') - @mock.patch('nova.virt.zvm.instance.ZVMInstance._get_current_memory') - @mock.patch('nova.virt.zvm.instance.ZVMInstance._get_rinv_info') - @mock.patch('nova.virt.zvm.instance.ZVMInstance.is_reachable') - @mock.patch('nova.virt.zvm.instance.ZVMInstance._check_power_stat') + @mock.patch('nova_zvm.virt.zvm.instance.ZVMInstance._get_current_memory') + @mock.patch('nova_zvm.virt.zvm.instance.ZVMInstance._get_rinv_info') + @mock.patch('nova_zvm.virt.zvm.instance.ZVMInstance.is_reachable') + @mock.patch('nova_zvm.virt.zvm.instance.ZVMInstance._check_power_stat') def test_get_info_invalid_data(self, mk_get_ps, mk_is_reach, mk_get_rinv_info, mk_get_mem, mk_msg_fmt): mk_get_ps.return_value = power_state.RUNNING @@ -2047,9 +2048,9 @@ class ZVMInstanceTestCases(ZVMTestCase): self.assertEqual(0, inst_info.cpu_time_ns) self.assertEqual(1048576, inst_info.max_mem_kb) - @mock.patch('nova.virt.zvm.instance.ZVMInstance._get_rinv_info') - @mock.patch('nova.virt.zvm.instance.ZVMInstance.is_reachable') - @mock.patch('nova.virt.zvm.instance.ZVMInstance._check_power_stat') + @mock.patch('nova_zvm.virt.zvm.instance.ZVMInstance._get_rinv_info') + @mock.patch('nova_zvm.virt.zvm.instance.ZVMInstance.is_reachable') + @mock.patch('nova_zvm.virt.zvm.instance.ZVMInstance._check_power_stat') def test_get_info_down(self, mk_get_ps, mk_is_reach, mk_get_rinv_info): mk_get_ps.return_value = power_state.SHUTDOWN mk_is_reach.return_value = False @@ -2065,8 +2066,8 @@ class ZVMInstanceTestCases(ZVMTestCase): self.assertEqual(0, inst_info.cpu_time_ns) self.assertEqual(1048576, inst_info.max_mem_kb) - @mock.patch('nova.virt.zvm.instance.ZVMInstance.is_reachable') - @mock.patch('nova.virt.zvm.instance.ZVMInstance._check_power_stat') + @mock.patch('nova_zvm.virt.zvm.instance.ZVMInstance.is_reachable') + @mock.patch('nova_zvm.virt.zvm.instance.ZVMInstance._check_power_stat') def test_get_info_paused(self, mk_get_ps, mk_is_reach): mk_get_ps.return_value = power_state.RUNNING mk_is_reach.return_value = False @@ -2085,8 +2086,8 @@ class ZVMInstanceTestCases(ZVMTestCase): self.assertEqual(0, inst_info.cpu_time_ns) self.assertEqual(1048576, inst_info.max_mem_kb) - @mock.patch('nova.virt.zvm.utils.xdsh') - @mock.patch('nova.virt.zvm.instance.ZVMInstance.get_userid') + @mock.patch('nova_zvm.virt.zvm.utils.xdsh') + @mock.patch('nova_zvm.virt.zvm.instance.ZVMInstance.get_userid') def test_unlock_devices(self, get_uid, xdsh): get_uid.return_value = 'fakeuid' xdshv1 = {'data': [['Locked type: DEVICE\nDevice address: 0100\n' diff --git a/nova_zvm/virt/__init__.py b/nova_zvm/virt/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/nova_zvm/virt/zvm/__init__.py b/nova_zvm/virt/zvm/__init__.py new file mode 100644 index 0000000..1b55f06 --- /dev/null +++ b/nova_zvm/virt/zvm/__init__.py @@ -0,0 +1,29 @@ +# Copyright 2013 IBM Corp. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +"""A connection to an IBM z/VM Virtualization system. + +Generally, OpenStack z/VM virt driver will call xCat REST API to operate +to z/VM hypervisors.xCat has a control point(a virtual machine) in z/VM +system, which enables xCat management node to control the z/VM system. +OpenStack z/VM driver will communicate with xCat management node through +xCat REST API. Thus OpenStack can operate to z/VM system indirectly. + +""" + + +from nova_zvm.virt.zvm import driver + + +ZVMDriver = driver.ZVMDriver diff --git a/nova/virt/zvm/conf.py b/nova_zvm/virt/zvm/conf.py similarity index 100% rename from nova/virt/zvm/conf.py rename to nova_zvm/virt/zvm/conf.py diff --git a/nova/virt/zvm/configdrive.py b/nova_zvm/virt/zvm/configdrive.py similarity index 97% rename from nova/virt/zvm/configdrive.py rename to nova_zvm/virt/zvm/configdrive.py index 2f51998..276d690 100644 --- a/nova/virt/zvm/configdrive.py +++ b/nova_zvm/virt/zvm/configdrive.py @@ -15,13 +15,13 @@ import os import tarfile -from oslo_config import cfg -from oslo_log import log as logging - from nova import exception from nova import utils from nova.virt import configdrive -from nova.virt.zvm import utils as zvmutils +from oslo_config import cfg +from oslo_log import log as logging + +from nova_zvm.virt.zvm import utils as zvmutils LOG = logging.getLogger(__name__) diff --git a/nova/virt/zvm/const.py b/nova_zvm/virt/zvm/const.py similarity index 100% rename from nova/virt/zvm/const.py rename to nova_zvm/virt/zvm/const.py diff --git a/nova/virt/zvm/dist.py b/nova_zvm/virt/zvm/dist.py similarity index 99% rename from nova/virt/zvm/dist.py rename to nova_zvm/virt/zvm/dist.py index f76ba23..15f1973 100644 --- a/nova/virt/zvm/dist.py +++ b/nova_zvm/virt/zvm/dist.py @@ -14,12 +14,12 @@ import abc +from nova.i18n import _ from oslo_config import cfg from oslo_log import log as logging import six -from nova.i18n import _ -from nova.virt.zvm import exception +from nova_zvm.virt.zvm import exception CONF = cfg.CONF diff --git a/nova_zvm/virt/zvm/driver.py b/nova_zvm/virt/zvm/driver.py new file mode 100644 index 0000000..373fbc2 --- /dev/null +++ b/nova_zvm/virt/zvm/driver.py @@ -0,0 +1,2045 @@ +# Copyright 2013 IBM Corp. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import contextlib +import datetime +import eventlet +import itertools +import operator +import os +import six +import time + +from nova.api.metadata import base as instance_metadata +from nova.compute import power_state +from nova.compute import task_states +from nova.compute import utils as compute_utils +from nova.compute import vm_states +from nova import exception as nova_exception +from nova.i18n import _, _LI, _LW +from nova.image import api as image_api +from nova.image import glance +from nova.objects import fields +from nova import utils +from nova.virt import configdrive +from nova.virt import driver +from nova.volume import cinder +from oslo_log import log as logging +from oslo_serialization import jsonutils +from oslo_service import loopingcall +from oslo_utils import excutils +from oslo_utils import timeutils +from oslo_utils import units +from oslo_utils import uuidutils +from oslo_utils import versionutils + +from nova_zvm.virt.zvm import conf +from nova_zvm.virt.zvm import configdrive as zvmconfigdrive +from nova_zvm.virt.zvm import const +from nova_zvm.virt.zvm import dist +from nova_zvm.virt.zvm import exception +from nova_zvm.virt.zvm import imageop +from nova_zvm.virt.zvm import instance as zvminstance +from nova_zvm.virt.zvm import networkop +from nova_zvm.virt.zvm import utils as zvmutils +from nova_zvm.virt.zvm import volumeop + + +LOG = logging.getLogger(__name__) + +CONF = conf.CONF +CONF.import_opt('host', 'nova.conf') +CONF.import_opt('my_ip', 'nova.conf') + +ZVMInstance = zvminstance.ZVMInstance + + +class ZVMDriver(driver.ComputeDriver): + """z/VM implementation of ComputeDriver.""" + + capabilities = { + "has_imagecache": True, + "supports_recreate": False, + "supports_migrate_to_same_host": True, + "supports_attach_interface": False + } + + def __init__(self, virtapi): + super(ZVMDriver, self).__init__(virtapi) + self._xcat_url = zvmutils.get_xcat_url() + + # incremental sleep interval list + _inc_slp = [5, 10, 20, 30, 60] + _slp = 5 + + # TODO(jichenjc): update _xcat_version when xcat reboot + self._xcat_version = self._get_xcat_version() + version_ok = self.has_min_version(const.XCAT_MINIMUM_VERSION) + while (not version_ok): + LOG.warning(_LW("WARNING: the xcat version communicating with is " + "%(xcat_version)s, but the minimum requested " + "version by openstack zvm driver is %(minimum)s " + "will sleep some time and check again"), + {'xcat_version': self._xcat_version, + 'minimum': const.XCAT_MINIMUM_VERSION}) + self._xcat_version = self._get_xcat_version() + version_ok = self.has_min_version(const.XCAT_MINIMUM_VERSION) + + _slp = len(_inc_slp) != 0 and _inc_slp.pop(0) or _slp + time.sleep(_slp) + + self._host_stats = [] + _slp = 5 + + while (self._host_stats == []): + try: + self._host_stats = self.update_host_status() + except Exception as e: + # Ignore any exceptions and log as warning + _slp = len(_inc_slp) != 0 and _inc_slp.pop(0) or _slp + msg = _LW("Failed to get host stats while initializing zVM " + "driver due to reason %(reason)s, will re-try in " + "%(slp)d seconds") + LOG.warning(msg, {'reason': six.text_type(e), + 'slp': _slp}) + time.sleep(_slp) + + self._networkop = networkop.NetworkOperator() + self._zvm_images = imageop.ZVMImages() + self._pathutils = zvmutils.PathUtils() + self._networkutils = zvmutils.NetworkUtils() + self._volumeop = volumeop.VolumeOperator() + self._volume_api = cinder.API() + self._dist_manager = dist.ListDistManager() + self._image_api = image_api.API() + + # booting multi instances at sametime will failed because we will + # import image to nova if root_disk_units not set,so multi threads + # might work on same image and the lastest one will overwrite the + # former ones' image data.The former ones can't find the data any more + # so add this semaphore to synchronize between those threads + self._imageop_semaphore = eventlet.semaphore.Semaphore(1) + + def init_host(self, host): + """Initialize anything that is necessary for the driver to function, + including catching up with currently running VM's on the given host. + """ + try: + self._volumeop.init_host(self._host_stats) + except Exception as e: + emsg = zvmutils.format_exception_msg(e) + LOG.warning(_LW("Exception raised while initializing z/VM driver: " + "%s"), emsg) + + def get_info(self, instance): + """Get the current status of an instance, by name (not ID!) + + Returns a dict containing: + :state: the running state, one of the power_state codes + :max_mem: (int) the maximum memory in KBytes allowed + :mem: (int) the memory in KBytes used by the domain + :num_cpu: (int) the number of virtual CPUs for the domain + :cpu_time: (int) the CPU time used in nanoseconds + + """ + inst_name = instance['name'] + zvm_inst = ZVMInstance(self, instance) + + try: + return zvm_inst.get_info() + except exception.ZVMXCATRequestFailed as err: + emsg = err.format_message() + if (emsg.__contains__("Invalid nodes and/or groups") and + emsg.__contains__("Forbidden")): + LOG.warning(_LW("z/VM instance %s does not exist"), inst_name, + instance=instance) + raise nova_exception.InstanceNotFound(instance_id=inst_name) + else: + raise err + + def list_instances(self): + """Return the names of all the instances known to the virtualization + layer, as a list. + """ + zvm_host = CONF.zvm_host + hcp_base = self._get_hcp_info()['hostname'] + + url = self._xcat_url.tabdump("/zvm") + res_dict = zvmutils.xcat_request("GET", url) + + instances = [] + + with zvmutils.expect_invalid_xcat_resp_data(res_dict): + data_entries = res_dict['data'][0][1:] + for data in data_entries: + l = data.split(",") + node, hcp = l[0].strip("\""), l[1].strip("\"") + hcp_short = hcp_base.partition('.')[0] + + # zvm host and zhcp are not included in the list + if (hcp.upper() == hcp_base.upper() and + node.upper() not in (zvm_host.upper(), + hcp_short.upper(), CONF.zvm_xcat_master.upper())): + instances.append(node) + + return instances + + def _instance_exists(self, instance_name): + """Overwrite this to using instance name as input parameter.""" + return instance_name in self.list_instances() + + def instance_exists(self, instance): + """Overwrite this to using instance name as input parameter.""" + return self._instance_exists(instance.name) + + def spawn(self, context, instance, image_meta, injected_files, + admin_password, network_info=None, block_device_info=None, + flavor=None): + """Create a new instance/VM/domain on the virtualization platform. + + Once this successfully completes, the instance should be + running (power_state.RUNNING). + + If this fails, any partial instance should be completely + cleaned up, and the virtualization platform should be in the state + that it was before this call began. + + :param context: security context + :param instance: Instance object as returned by DB layer. + This function should use the data there to guide + the creation of the new instance. + :param image_meta: image object returned by nova.image.glance that + defines the image from which to boot this instance + :param injected_files: User files to inject into instance. + :param admin_password: Administrator password to set in instance. + :param network_info: + :py:meth:`~nova.network.manager.NetworkManager.get_instance_nw_info` + :param block_device_info: Information about block devices to be + attached to the instance. + + """ + # For zVM instance, limit the maximum length of instance name to be 8 + if len(instance['name']) > 8: + msg = (_("Don't support spawn vm on zVM hypervisor with instance " + "name: %s, please change your instance_name_template to make " + "sure the length of instance name is not longer than 8 " + "characters") % instance['name']) + raise nova_exception.InvalidInput(reason=msg) + + root_mount_device, boot_from_volume = zvmutils.is_boot_from_volume( + block_device_info) + bdm = driver.block_device_info_get_mapping(block_device_info) + + # Update the root device name in instance table + root_device_name = '/dev/' + const.ZVM_DEFAULT_ROOT_DISK + if boot_from_volume: + root_device_name = root_mount_device + instance.root_device_name = root_device_name + instance.save() + + if not network_info: + msg = _("Not support boot without a NIC.") + raise exception.ZVMDriverError(msg=msg) + + # Use image_type to distinguish the image that is for normal deploy + # and volume snapshot image + image_type = '' + # Ensure the used image is a valid zVM image + if not boot_from_volume: + # This is because commit fbe31e461ac3f16edb795993558a2314b4c16b52 + # changes the image_meta from dict to object, we have several + # unique property can't be handled well + # see bug 1537921 for detail info + image_meta = self._image_api.get(context, image_meta.id) + self._zvm_images.zimage_check(image_meta) + if 'image_comments' not in image_meta['properties']: + image_type = 'xcatconf4z' + else: + image_type = 'cloudimg' + else: + image_type = 'volume-snapshot' + + compute_node = CONF.zvm_host + hcp_info = self._get_hcp_info() + zhcp = hcp_info['hostname'] + zhcp_userid = hcp_info['userid'] + + zvm_inst = ZVMInstance(self, instance) + instance_path = self._pathutils.get_instance_path(compute_node, + zvm_inst._name) + # Create network configuration files + LOG.debug('Creating network configuration files ' + 'for instance: %s', zvm_inst._name, instance=instance) + base_nic_vdev = CONF.zvm_default_nic_vdev + + if not boot_from_volume: + os_version = image_meta['properties']['os_version'] + else: + volume_id = self._extract_volume_id(bdm, root_mount_device) + volume_summery = self._volume_api.get(context, volume_id) + volume_meta = volume_summery['volume_metadata'] + os_version = volume_meta['os_version'] + instance.system_metadata['image_os_version'] = os_version + instance.save() + + linuxdist = self._dist_manager.get_linux_dist(os_version)() + files_and_cmds = linuxdist.create_network_configuration_files( + instance_path, network_info, base_nic_vdev) + (net_conf_files, net_conf_cmds) = files_and_cmds + # Add network configure files to inject_files + if len(net_conf_files) > 0: + injected_files.extend(net_conf_files) + + # Create configure drive + if not CONF.zvm_config_drive_inject_password: + admin_password = CONF.zvm_image_default_password + transportfiles = None + if configdrive.required_by(instance): + transportfiles = self._create_config_drive(context, instance_path, + instance, injected_files, admin_password, + net_conf_cmds, linuxdist, image_type) + + LOG.info(_LI("The instance %(name)s is spawning at %(node)s"), + {'name': zvm_inst._name, 'node': compute_node}, + instance=instance) + + spawn_start = time.time() + + try: + deploy_image_name = None + if not boot_from_volume: + tmp_file_fn = None + bundle_file_path = None + + with self._imageop_semaphore: + root_disk_units = image_meta['properties'].get( + 'root_disk_units', '') + # Currently, disk unit values have the form number:units, + # for example: '3338:CYL'. Images captured using older + # versions of the driver may lack the colon delimiter and + # units. If the unit-less form is found, convert it to the + # new form by adding the units. + if ':' not in root_disk_units: + (tmp_file_fn, image_file_path, + bundle_file_path) = self._import_image_to_nova( + context, + instance, image_meta) + image_meta = self._zvm_images.\ + set_image_root_disk_units( + context, image_meta, image_file_path) + root_disk_units = image_meta['properties'][ + 'root_disk_units'] + disk_units = root_disk_units.split(":")[1] + if ((disk_units == "CYL" and CONF.zvm_diskpool_type == "FBA") + or (disk_units == "BLK" and + CONF.zvm_diskpool_type == "ECKD")): + msg = (_("The image's disk size units is: %(diskunits)s," + " it doesn't match the specified disk type" + " %(disktype)s in nova.conf."), + {'diskunits': disk_units, + 'disktype': CONF.zvm_diskpool_type}) + raise exception.ZVMImageError(msg=msg) + + image_in_xcat = self._zvm_images.image_exist_xcat( + instance['image_ref']) + if not image_in_xcat: + self._import_image_to_xcat(context, instance, image_meta, + tmp_file_fn) + elif bundle_file_path is not None: + self._pathutils.clean_temp_folder(bundle_file_path) + + deploy_image_name = self._zvm_images.get_imgname_xcat( + instance['image_ref']) + + # Create xCAT node and userid for the instance + zvm_inst.create_xcat_node(zhcp) + zvm_inst.create_userid(block_device_info, image_meta, context, + deploy_image_name) + + # Setup network for z/VM instance + self._preset_instance_network(zvm_inst._name, network_info) + self._add_nic_to_table(zvm_inst._name, network_info) + + # Call nodeset restapi to deploy image on node + if not boot_from_volume: + zvm_inst.update_node_info(image_meta) + zvm_inst.deploy_node(deploy_image_name, transportfiles) + else: + zvmutils.punch_configdrive_file(transportfiles, zvm_inst._name) + + if image_type in ['xcatconf4z', 'volume-snapshot']: + # Change vm's admin password during spawn + zvmutils.punch_adminpass_file(instance_path, zvm_inst._name, + admin_password, linuxdist) + if zvmutils.xcat_support_iucv(self._xcat_version): + # Punch IUCV server files to reader. + zvmutils.punch_iucv_file(os_version, zhcp, zhcp_userid, + zvm_inst._name, instance_path) + + # punch ephemeral disk info to the instance + if instance['ephemeral_gb'] != 0: + eph_disks = block_device_info.get('ephemerals', []) + if eph_disks == []: + zvmutils.process_eph_disk(zvm_inst._name) + else: + for idx, eph in enumerate(eph_disks): + vdev = zvmutils.generate_eph_vdev(idx) + fmt = eph.get('guest_format') + mount_dir = ''.join([CONF.zvm_default_ephemeral_mntdir, + str(idx)]) + zvmutils.process_eph_disk(zvm_inst._name, vdev, fmt, + mount_dir) + + # Wait neutron zvm-agent add NIC information to user direct. + self._wait_and_get_nic_direct(zvm_inst._name, instance) + + # Attach persistent volume, exclude root volume + bdm_attach = list(bdm) + bdm_attach = self._exclude_root_volume_bdm(bdm_attach, + root_mount_device) + self._attach_volume_to_instance(context, instance, bdm_attach) + + # 1. Prepare for booting from volume + # 2. Write the zipl.conf file and issue zipl + if boot_from_volume: + (lun, wwpn, size, fcp) = zvm_inst.prepare_volume_boot(context, + instance, bdm, root_mount_device, + volume_meta) + zvmutils.punch_zipl_file(instance_path, zvm_inst._name, + lun, wwpn, fcp, volume_meta) + + # Power on the instance, then put MN's public key into instance + zvm_inst.power_on() + spawn_time = time.time() - spawn_start + LOG.info(_LI("Instance spawned succeeded in %s seconds"), + spawn_time, instance=instance) + except (exception.ZVMXCATCreateNodeFailed, + exception.ZVMImageError): + with excutils.save_and_reraise_exception(): + zvm_inst.delete_xcat_node() + except (exception.ZVMXCATCreateUserIdFailed, + exception.ZVMNetworkError, + exception.ZVMVolumeError, + exception.ZVMXCATUpdateNodeFailed, + exception.ZVMXCATDeployNodeFailed): + with excutils.save_and_reraise_exception(): + self.destroy(context, instance, network_info, + block_device_info) + except Exception as err: + # Just a error log then re-raise + with excutils.save_and_reraise_exception(): + LOG.error(_("Deploy image to instance %(instance)s " + "failed with reason: %(err)s"), + {'instance': zvm_inst._name, 'err': err}, + instance=instance) + finally: + self._pathutils.clean_temp_folder(instance_path) + + # Update image last deploy date in xCAT osimage table + if not boot_from_volume: + self._zvm_images.update_last_use_date(deploy_image_name) + + def _create_config_drive(self, context, instance_path, instance, + injected_files, admin_password, commands, + linuxdist, image_type=''): + if CONF.config_drive_format not in ['tgz', 'iso9660']: + msg = (_("Invalid config drive format %s") % + CONF.config_drive_format) + raise exception.ZVMConfigDriveError(msg=msg) + + LOG.debug('Using config drive', instance=instance) + + extra_md = {} + if CONF.zvm_config_drive_inject_password: + extra_md['admin_pass'] = admin_password + + udev_settle = '' + if image_type in ['xcatconf4z', 'volume-snapshot']: + udev_settle = linuxdist.get_znetconfig_contents() + if udev_settle: + if len(commands) == 0: + znetconfig = '\n'.join(('#!/bin/bash', udev_settle)) + else: + znetconfig = '\n'.join(('#!/bin/bash', commands, udev_settle)) + znetconfig += '\nrm -rf /tmp/znetconfig.sh\n' + # Create a temp file in instance to execute above commands + net_cmd_file = [] + net_cmd_file.append(('/tmp/znetconfig.sh', znetconfig)) # nosec + injected_files.extend(net_cmd_file) + # injected_files.extend(('/tmp/znetconfig.sh', znetconfig)) + + inst_md = instance_metadata.InstanceMetadata(instance, + content=injected_files, + extra_md=extra_md, + request_context=context) + # network_metadata will prevent the hostname of the instance from + # being set correctly, so clean the value + inst_md.network_metadata = None + + configdrive_tgz = os.path.join(instance_path, 'cfgdrive.tgz') + + LOG.debug('Creating config drive at %s', configdrive_tgz, + instance=instance) + with zvmconfigdrive.ZVMConfigDriveBuilder(instance_md=inst_md) as cdb: + cdb.make_drive(configdrive_tgz) + + return configdrive_tgz + + def _preset_instance_network(self, instance_name, network_info): + self._networkop.config_xcat_mac(instance_name) + LOG.debug("Add ip/host name on xCAT MN for instance %s", + instance_name) + try: + network = network_info[0]['network'] + ip_addr = network['subnets'][0]['ips'][0]['address'] + except Exception: + if network_info: + msg = _("Invalid network info: %s") % str(network_info) + else: + msg = _("Network info is Empty") + raise exception.ZVMNetworkError(msg=msg) + + self._networkop.add_xcat_host(instance_name, ip_addr, instance_name) + self._networkop.makehosts() + + def _import_image_to_nova(self, context, instance, image_meta): + image_file_name = image_meta['properties']['image_file_name'] + disk_file = ''.join(j for j in image_file_name.split(".img")[0] + if j.isalnum()) + ".img" + tmp_file_fn = self._pathutils.make_time_stamp() + bundle_file_path = self._pathutils.get_bundle_tmp_path(tmp_file_fn) + image_file_path = self._pathutils.get_img_path( + bundle_file_path, disk_file) + + LOG.debug("Downloading the image %s from glance to nova compute " + "server", image_meta['id'], instance=instance) + self._zvm_images.fetch_image(context, + image_meta['id'], + image_file_path, + instance['user_id'], + instance['project_id']) + return (tmp_file_fn, image_file_path, bundle_file_path) + + def _import_image_to_xcat(self, context, instance, image_meta, tmp_f_fn): + # Format the image name and image disk file in case user named them + # with special characters + image_name = ''.join(i for i in image_meta['name'] if i.isalnum()) + spawn_path = self._pathutils.get_spawn_folder() + image_file_name = image_meta['properties']['image_file_name'] + disk_file = ''.join(j for j in image_file_name.split(".img")[0] + if j.isalnum()) + ".img" + if tmp_f_fn is None: + tmp_f_fn = self._pathutils.make_time_stamp() + bundle_file_path = self._pathutils.get_bundle_tmp_path(tmp_f_fn) + image_file_path = self._pathutils.get_img_path( + bundle_file_path, disk_file) + LOG.debug("Downloading the image %s from glance to nova compute " + "server", image_meta['id'], instance=instance) + self._zvm_images.fetch_image(context, + image_meta['id'], + image_file_path, + instance['user_id'], + instance['project_id']) + else: + bundle_file_path = self._pathutils.get_bundle_tmp_path(tmp_f_fn) + image_file_path = self._pathutils.get_img_path( + bundle_file_path, disk_file) + + LOG.debug("Generating the manifest.xml as a part of bundle file for " + "image %s", image_meta['id'], instance=instance) + + image_name = zvmutils.remove_prefix_of_unicode(image_name) + + self._zvm_images.generate_manifest_file(image_meta, image_name, + disk_file, bundle_file_path) + + LOG.debug("Generating bundle file for image %s", image_meta['id'], + instance=instance) + image_bundle_package = self._zvm_images.generate_image_bundle( + spawn_path, tmp_f_fn, image_name) + + LOG.debug("Importing the image %s to xCAT", image_meta['id'], + instance=instance) + profile_str = image_name, instance['image_ref'].replace('-', '_') + image_profile = '_'.join(profile_str) + self._zvm_images.check_space_imgimport_xcat(context, instance, + image_bundle_package, CONF.xcat_free_space_threshold, + CONF.zvm_xcat_master) + self._zvm_images.put_image_to_xcat(image_bundle_package, + image_profile) + + @property + def need_legacy_block_device_info(self): + return False + + def destroy(self, context, instance, network_info=None, + block_device_info=None, destroy_disks=False): + """Destroy (shutdown and delete) the specified instance. + + If the instance is not found (for example if networking failed), this + function should still succeed. It's probably a good idea to log a + warning in that case. + + :param instance: Instance object as returned by DB layer. + :param network_info: + :py:meth:`~nova.network.manager.NetworkManager.get_instance_nw_info` + :param block_device_info: Information about block devices that should + be detached from the instance. + :param destroy_disks: Indicates if disks should be destroyed + + """ + inst_name = instance['name'] + root_mount_device, boot_from_volume = zvmutils.is_boot_from_volume( + block_device_info) + zvm_inst = ZVMInstance(self, instance) + + if self._instance_exists(inst_name): + LOG.info(_LI("Destroying instance %s"), inst_name, + instance=instance) + + # Collect diagnostics when the instance is unreachable, since this + # is most often caused by a deployment failure but the deployment + # artifacts are often needed to debug the root cause. + if zvm_inst.is_reachable(): + LOG.debug(("Node %s is reachable, " + "skipping diagnostics collection"), inst_name, + instance=instance) + elif zvm_inst.is_powered_off(): + LOG.debug(("Node %s is powered off, " + "skipping diagnostics collection"), inst_name, + instance=instance) + else: + LOG.debug(("Node %s is powered on but unreachable, " + "collecting diagnostics for failed deployment"), + inst_name, + instance=instance) + zvm_inst.collect_diagnostics(context, + const.DIAGNOSTICS_RSN_DEPLOYMENT_TIMEOUT) + + bdm = driver.block_device_info_get_mapping(block_device_info) + try: + bdm_det = list(bdm) + bdm_det = self._exclude_root_volume_bdm(bdm_det, + root_mount_device) + self._detach_volume_from_instance(instance, bdm_det) + if boot_from_volume: + zvm_inst.clean_volume_boot(context, instance, bdm, + root_mount_device) + except exception.ZVMBaseException as err: + LOG.warning(_LW("Failed to detach volume: %s"), + err.format_message(), instance=instance) + + if network_info: + try: + for vif in network_info: + self._networkop.clean_mac_switch_host(inst_name) + except exception.ZVMNetworkError: + LOG.warning(_LW("Clean MAC and VSWITCH failed while " + "destroying z/VM instance %s"), inst_name, + instance=instance) + + zvm_inst.delete_userid(self._get_hcp_info()['nodename'], context) + else: + LOG.warning(_LW('Instance %s does not exist'), inst_name, + instance=instance) + + def manage_image_cache(self, context, filtered_instances): + """Clean the image cache in xCAT MN.""" + LOG.info(_LI("Check and clean image cache in xCAT")) + clean_period = CONF.xcat_image_clean_period + self._zvm_images.clean_image_cache_xcat(clean_period) + + def reboot(self, context, instance, network_info, reboot_type, + block_device_info=None, bad_volumes_callback=None): + """Reboot the specified instance. + + After this is called successfully, the instance's state + goes back to power_state.RUNNING. The virtualization + platform should ensure that the reboot action has completed + successfully even in cases in which the underlying domain/vm + is paused or halted/stopped. + + :param instance: Instance object as returned by DB layer. + :param network_info: + :py:meth:`~nova.network.manager.NetworkManager.get_instance_nw_info` + :param reboot_type: Either a HARD or SOFT reboot + :param block_device_info: Info pertaining to attached volumes + :param bad_volumes_callback: Function to handle any bad volumes + encountered + + """ + zvm_inst = ZVMInstance(self, instance) + if reboot_type == 'SOFT': + zvm_inst.reboot() + else: + zvm_inst.reset() + + if not zvm_inst._reachable: + LOG.error(_("Failed to reboot instance %s: timeout"), + zvm_inst._name, instance=instance) + raise nova_exception.InstanceRebootFailure(reason=_("timeout")) + + def get_host_ip_addr(self): + """Retrieves the IP address of the dom0.""" + return CONF.my_ip + + def _format_mountpoint(self, mountpoint): + """Change mountpoint from /dev/sdX to /dev/vdX. + + When a SCSI device is pluged in, the system will create a file node + /dev/sdX for the SCSI device. If the file node exists already as a + link to another file, the link will be overlayed and the file node + will be seized by the SCSI device. + + For example, if the first SCSI device is pluged in and the mountpoint + is specified as /dev/sdb, the SCSI device will be attached to /dev/sda + and /dev/sdb is created as a link to /dev/sda. Then the second SCSI + device is pluged in. It will be attached to /dev/sdb and the link will + no longer exist. + + To avoid this case, if mountpoint is /dev/sdX, it will be changed to + /dev/vdX. Otherwize it will keep as it is. + + When instance's root_device_name is /dev/dasdX, the mountpoint will be + changed to /dev/dX. That's not what is expected. Format mountpoint to + /dev/vdX in this case. + + :param mountpoint: The file node name of the mountpoint. + + """ + + mountpoint = mountpoint.lower() + mountpoint = mountpoint.replace('/dev/d', '/dev/sd') + return mountpoint.replace('/dev/s', '/dev/v') + + def attach_volume(self, context, connection_info, instance, mountpoint, + disk_bus=None, device_type=None, encryption=None): + """Attach the disk to the instance at mountpoint using info.""" + if instance.vm_state == vm_states.PAUSED: + msg = _("Attaching to a paused instance is not supported.") + raise exception.ZVMDriverError(msg=msg) + if mountpoint: + mountpoint = self._format_mountpoint(mountpoint) + if self._instance_exists(instance['name']): + zvm_inst = ZVMInstance(self, instance) + is_active = zvm_inst.is_reachable() + + zvm_inst.attach_volume(self._volumeop, context, connection_info, + instance, mountpoint, is_active) + + def detach_volume(self, connection_info, instance, mountpoint=None, + encryption=None): + """Detach the disk attached to the instance.""" + if instance.vm_state == vm_states.PAUSED: + msg = _("Detaching from a paused instance is not supported.") + raise exception.ZVMDriverError(msg=msg) + if mountpoint: + mountpoint = self._format_mountpoint(mountpoint) + if self._instance_exists(instance['name']): + zvm_inst = ZVMInstance(self, instance) + is_active = zvm_inst.is_reachable() + + zvm_inst.detach_volume(self._volumeop, connection_info, instance, + mountpoint, is_active) + + def _reset_power_state(self, state, instance): + # If the instance's power_state is "RUNNING", power it on after + # capture. If "PAUSED", pause it after capture. + if state == power_state.RUNNING or state == power_state.PAUSED: + try: + self.power_on({}, instance, []) + except nova_exception.InstancePowerOnFailure as err: + LOG.warning(_LW("Power On instance %(inst)s fail after " + "capture, please check manually. The error is: %(err)s"), + {'inst': instance['name'], 'err': err.format_message()}, + instance=instance) + if state == power_state.PAUSED: + try: + self.pause(instance) + except (exception.ZVMXCATRequestFailed, + exception.ZVMInvalidXCATResponseDataError, + exception.ZVMXCATInternalError) as err: + LOG.warning(_LW("Pause instance %(inst)s fail after capture, " + "please check manually. The error is: %(err)s"), + {'inst': instance['name'], 'err': err.format_message()}, + instance=instance) + + def _get_xcat_image_file_path(self, image_name_xcat): + """Get image file path from image name in xCAT.""" + image_path = self._zvm_images.get_image_file_path_from_image_name( + image_name_xcat) + image_name = self._zvm_images.get_image_file_name(image_path) + return '/'.join((image_path, image_name)) + + def _is_shared_image_repo(self, image_name_xcat): + """To check whether nova can access xCAT image repo.""" + try: + image_file_path = self._get_xcat_image_file_path(image_name_xcat) + except exception.ZVMImageError: + # image path not exist or image file not found + return False + + return os.stat(image_file_path).st_mode & 4 == 4 + + def snapshot(self, context, instance, image_href, update_task_state): + """Snapshots the specified instance. + + :param context: security context + :param instance: Instance object as returned by DB layer. + :param image_href: Reference to a pre-created image that will + hold the snapshot. + """ + # Check the image status + (image_service, image_id) = glance.get_remote_image_service(context, + image_href) + image_meta = image_service.show(context, image_href) + + # remove user names special characters, this name will only be used + # to pass to xcat and combine with UUID in xcat. + image_name = ''.join(i for i in image_meta['name'] if i.isalnum()) + image_name = zvmutils.remove_prefix_of_unicode(image_name) + image_name_xcat = None + + # Make sure the instance's power_state is running and unpaused before + # doing a capture. + state = instance['power_state'] + if (state == power_state.NOSTATE or state == power_state.CRASHED or + state == power_state.SUSPENDED): + raise nova_exception.InstanceNotReady(instance_id=instance['name']) + elif state == power_state.SHUTDOWN: + self.power_on({}, instance, []) + elif state == power_state.PAUSED: + self.unpause(instance) + + # Check xCAT free space and invoke the zvmimages.create_zvm_image() + try: + free_space_xcat = self._zvm_images.get_free_space_xcat( + CONF.xcat_free_space_threshold, + CONF.zvm_xcat_master) + user_dict = self._get_user_directory(instance['name']) + imgcapture_needed = self._zvm_images.get_imgcapture_needed( + instance, user_dict) + if (free_space_xcat - imgcapture_needed) < 0: + larger = max(CONF.xcat_free_space_threshold, imgcapture_needed) + size_needed = float(larger - free_space_xcat) + self._zvm_images.prune_image_xcat(context, size_needed, + imgcapture_needed) + image_name_xcat = self._zvm_images.create_zvm_image(instance, + image_name, + image_href) + # Update image last create date in xCAT osimage table + self._zvm_images.update_last_use_date(image_name_xcat) + except (exception.ZVMImageError, + exception.ZVMXCATXdshFailed): + with excutils.save_and_reraise_exception(): + self._reset_power_state(state, instance) + self._zvm_images.delete_image_glance(image_service, context, + image_href) + + self._reset_power_state(state, instance) + shared_image_repo = self._is_shared_image_repo(image_name_xcat) + + if not shared_image_repo: + # The image will be exported from xCAT and imported to nova after + # successfully captured. + snapshot_time_path = self._zvm_images.get_snapshot_time_path() + try: + image_bundle = self._zvm_images.get_image_from_xcat( + image_name_xcat, image_name, snapshot_time_path) + except exception.ZVMImageError: + with excutils.save_and_reraise_exception(): + self._zvm_images.delete_image_glance(image_service, + context, image_href) + self._zvm_images.clean_up_snapshot_time_path( + snapshot_time_path) + self._zvm_images.delete_image_from_xcat(image_name_xcat) + + # The image in the xCAT MN will be removed after imported to nova + # Invoke rmimage REST API twice to remove image and object + self._zvm_images.delete_image_from_xcat(image_name_xcat) + + # Untar the image_bundle and parse manifest.xml + image_package_path = os.path.join(snapshot_time_path, + image_name_xcat) + try: + self._zvm_images.untar_image_bundle(snapshot_time_path, + image_bundle) + manifest = self._zvm_images.parse_manifest_xml( + image_package_path) + image_file_name = self._zvm_images.get_image_file_name( + image_package_path) + image_file_path = '/'.join((image_package_path, + image_file_name)) + except exception.ZVMImageError: + with excutils.save_and_reraise_exception(): + self._zvm_images.delete_image_glance(image_service, + context, image_href) + self._zvm_images.clean_up_snapshot_time_path( + snapshot_time_path) + else: + image_file_path = self._get_xcat_image_file_path(image_name_xcat) + (image_package_path, _toss, + image_file_name) = image_file_path.rpartition('/') + manifest = self._zvm_images.get_image_menifest(image_name_xcat) + + root_disk_units = self._zvm_images.get_root_disk_units(image_file_path) + + # Before upload, update the instance task_state to image_pending_upload + update_task_state(task_state=task_states.IMAGE_PENDING_UPLOAD) + + # manifest.xml contributes some new image meta + LOG.debug("Snapshot extracted, beginning image upload", + instance=instance) + new_image_meta = { + 'is_public': False, + 'status': 'active', + 'properties': { + 'image_location': 'snapshot', + 'image_state': 'available', + 'owner_id': instance['project_id'], + 'image_type_xcat': manifest['imagetype'], + 'type': 'snapshot', + 'architecture': manifest['osarch'], + 'os_name': manifest['osname'], + 'os_version': manifest['osvers'], + 'image_profile': manifest['profile'], + 'provisioning_method': manifest['provmethod'], + 'image_file_name': image_file_name, + 'hypervisor_type': const.HYPERVISOR_TYPE, + 'root_disk_units': root_disk_units + }, + 'disk_format': 'raw', + 'container_format': 'bare', + } + + # Upload that image to the image service + image_path = os.path.join(image_package_path, image_file_name) + update_task_state(task_state=task_states.IMAGE_UPLOADING, + expected_state=task_states.IMAGE_PENDING_UPLOAD) + + def cleanup_temp_image(): + if not shared_image_repo: + # Clean up temp image in nova snapshot temp folder + self._zvm_images.clean_up_snapshot_time_path( + snapshot_time_path) + else: + # Clean up image from xCAT image repo for all-in-one mode + self._zvm_images.delete_image_from_xcat(image_name_xcat) + + try: + with open(image_path, 'r') as image_file: + image_service.update(context, + image_href, + new_image_meta, + image_file, + purge_props=False) + except Exception: + with excutils.save_and_reraise_exception(): + self._zvm_images.delete_image_glance(image_service, context, + image_href) + cleanup_temp_image() + + LOG.debug("Snapshot image upload complete", instance=instance) + + cleanup_temp_image() + + LOG.info(_LI("Snapshot complete successfully"), instance=instance) + + def pause(self, instance): + """Pause the specified instance.""" + LOG.debug('Pausing %s', instance['name'], instance=instance) + zvm_inst = ZVMInstance(self, instance) + zvm_inst.pause() + + def unpause(self, instance): + """Unpause paused VM instance.""" + LOG.debug('Un-pausing %s', instance['name'], instance=instance) + zvm_inst = ZVMInstance(self, instance) + zvm_inst.unpause() + + def power_off(self, instance, timeout=0, retry_interval=0): + """Power off the specified instance.""" + LOG.debug('Stopping z/VM instance %s', instance['name'], + instance=instance) + zvm_inst = ZVMInstance(self, instance) + zvm_inst.power_off(timeout, retry_interval) + + def power_on(self, context, instance, network_info, + block_device_info=None): + """Power on the specified instance.""" + LOG.debug('Starting z/VM instance %s', instance['name'], + instance=instance) + zvm_inst = ZVMInstance(self, instance) + zvm_inst.power_on() + + def get_available_resource(self, nodename=None): + """Retrieve resource information. + + This method is called when nova-compute launches, and + as part of a periodic task + + :param nodename: + node which the caller want to get resources from + a driver that manages only one node can safely ignore this + :returns: Dictionary describing resources + + """ + LOG.debug("Getting available resource for %s", CONF.zvm_host) + stats = self.update_host_status()[0] + + mem_used = stats['host_memory_total'] - stats['host_memory_free'] + supported_instances = stats['supported_instances'] + dic = { + 'vcpus': stats['vcpus'], + 'memory_mb': stats['host_memory_total'], + 'local_gb': stats['disk_total'], + 'vcpus_used': stats['vcpus_used'], + 'memory_mb_used': mem_used, + 'local_gb_used': stats['disk_used'], + 'hypervisor_type': stats['hypervisor_type'], + 'hypervisor_version': stats['hypervisor_version'], + 'hypervisor_hostname': stats['hypervisor_hostname'], + 'cpu_info': jsonutils.dumps(stats['cpu_info']), + 'disk_available_least': stats['disk_available'], + 'supported_instances': supported_instances, + 'numa_topology': None, + } + + return dic + + def check_can_live_migrate_destination(self, ctxt, instance_ref, + src_compute_info, dst_compute_info, + block_migration=False, + disk_over_commit=False): + """Check if it is possible to execute live migration. + + This runs checks on the destination host, and then calls + back to the source host to check the results. + + :param ctxt: security context + :param instance_ref: nova.db.sqlalchemy.models.Instance + :param src_compute_info: Info about the sending machine + :param dst_compute_info: Info about the receiving machine + :param block_migration: if true, prepare for block migration + :param disk_over_commit: if true, allow disk over commit + """ + # For z/VM, all live migration check will be done in + # check_can_live_migration_source, so just return a dst_compute_info. + # And we only support shared storage live migration. + migrate_data = {'dest_host': dst_compute_info['hypervisor_hostname'], + 'is_shared_storage': True} + dest_check_data = {'migrate_data': migrate_data} + + return dest_check_data + + def check_can_live_migrate_source(self, ctxt, instance_ref, + dest_check_data, block_device_info=None): + """Check if it is possible to execute live migration. + + This checks if the live migration can succeed, based on the + results from check_can_live_migrate_destination. + + :param context: security context + :param instance_ref: nova.db.sqlalchemy.models.Instance + :param dest_check_data: result of check_can_live_migrate_destination + + """ + LOG.info(_LI("Checking source host for live-migration for %s"), + instance_ref['name'], instance=instance_ref) + + migrate_data = dest_check_data.get('migrate_data', {}) + dest_host = migrate_data.get('dest_host', None) + userid = zvmutils.get_userid(instance_ref['name']) + migrate_data.update({'source_xcat_mn': CONF.zvm_xcat_server, + 'zvm_userid': userid}) + + if dest_host is not None: + try: + self._vmrelocate(dest_host, instance_ref['name'], 'test') + except nova_exception.MigrationError as err: + emsg = err.format_message() + if isinstance(CONF.zvm_vmrelocate_force, str): + force = CONF.zvm_vmrelocate_force.lower() + if ('domain' in force) or ('architecture' in force): + if '1944' in emsg: + # force domain/architecture in effect, ignore + return migrate_data + LOG.error(_("Live-migrating check failed: %s"), emsg, + instance=instance_ref) + raise nova_exception.MigrationPreCheckError(reason=emsg) + + return migrate_data + else: + reason = _("Invalid migration data") + raise nova_exception.MigrationPreCheckError(reason=reason) + + def check_can_live_migrate_destination_cleanup(self, ctxt, + dest_check_data): + """Do required cleanup on dest host after check_can_live_migrate calls + + :param ctxt: security context + :param dest_check_data: result of check_can_live_migrate_destination + + """ + # For z/VM, nothing needed to be cleanup + return + + def pre_live_migration(self, ctxt, instance_ref, block_device_info, + network_info, disk_info, migrate_data=None): + """Prepare an instance for live migration + + :param ctxt: security context + :param instance_ref: instance object that will be migrated + :param block_device_info: instance block device information + :param network_info: instance network information + :param migrate_data: implementation specific data dict. + """ + if block_device_info in ([], None, {}): + msg = _("Not supported live-migration with persistent " + "volume attached") + LOG.error(msg, instance=instance_ref) + raise nova_exception.MigrationError(reason=msg) + + zvm_inst = ZVMInstance(self, instance_ref) + source_xcat_mn = migrate_data.get('source_xcat_mn', '') + userid = migrate_data.get('zvm_userid') + hcp = self._get_hcp_info()['hostname'] + same_xcat_mn = source_xcat_mn == CONF.zvm_xcat_server + dest_diff_mn_key = None + + if not same_xcat_mn: + # The two z/VM system managed by two different xCAT MN + zvm_inst.create_xcat_node(hcp, userid) + dest_diff_mn_key = zvmutils.get_mn_pub_key() + if network_info is not None: + network = network_info[0]['network'] + ip_addr = network['subnets'][0]['ips'][0]['address'] + self._networkop.add_xcat_host(zvm_inst._name, ip_addr, + zvm_inst._name) + self._networkop.makehosts() + + return {'same_xcat_mn': same_xcat_mn, + 'dest_diff_mn_key': dest_diff_mn_key} + + def pre_block_migration(self, ctxt, instance_ref, disk_info): + """Prepare a block device for migration + + :param ctxt: security context + :param instance_ref: instance object that will have its disk migrated + :param disk_info: information about disk to be migrated (as returned + from get_instance_disk_info()) + """ + # We don't support block_migration + return + + def live_migration(self, ctxt, instance_ref, dest, + post_method, recover_method, block_migration=False, + migrate_data=None): + """Live migration of an instance to another host. + + :params ctxt: security context + :params instance_ref: + nova.db.sqlalchemy.models.Instance object + instance object that is migrated. + :params dest: destination host + :params post_method: + post operation method. + expected nova.compute.manager.post_live_migration. + :params recover_method: + recovery method when any exception occurs. + expected nova.compute.manager.recover_live_migration. + :params block_migration: if true, migrate VM disk. + :params migrate_data: implementation specific params. + + """ + inst_name = instance_ref['name'] + dest_host = migrate_data['dest_host'] + LOG.info(_LI("Live-migrating %(inst)s to %(dest)s"), + {'inst': inst_name, 'dest': dest_host}, instance=instance_ref) + + same_mn = migrate_data['pre_live_migration_result']['same_xcat_mn'] + dest_diff_mn_key = migrate_data['pre_live_migration_result'].get( + 'dest_diff_mn_key', None) + + if not same_mn and dest_diff_mn_key: + auth_command = ('echo "%s" >> /root/.ssh/authorized_keys' % + dest_diff_mn_key) + zvmutils.xdsh(inst_name, auth_command) + + try: + self._vmrelocate(dest_host, inst_name, 'move') + except nova_exception.MigrationError as err: + LOG.error(_("Live-migration failed: %s"), err.format_message(), + instance=instance_ref) + with excutils.save_and_reraise_exception(): + recover_method(ctxt, instance_ref, dest, + block_migration, migrate_data) + + if not same_mn: + # Delete node definition at source xCAT MN + zvm_inst = ZVMInstance(self, instance_ref) + self._networkop.clean_mac_switch_host(zvm_inst._name) + zvm_inst.delete_xcat_node() + + post_method(ctxt, instance_ref, dest, + block_migration, migrate_data) + + def post_live_migration_at_destination(self, ctxt, instance_ref, + network_info, + block_migration=False, + block_device_info=None): + """Post operation of live migration at destination host. + + :param ctxt: security context + :param instance_ref: instance object that is migrated + :param network_info: instance network information + :param block_migration: if true, post operation of block_migration. + + """ + inst_name = instance_ref['name'] + nic_vdev = CONF.zvm_default_nic_vdev + zhcp = self._get_hcp_info()['hostname'] + + for vif in network_info: + LOG.debug('Create nic for instance: %(inst)s, MAC: ' + '%(mac)s Network: %(network)s Vdev: %(vdev)s', + {'inst': inst_name, 'mac': vif['address'], + 'network': vif['network']['label'], 'vdev': nic_vdev}, + instance=instance_ref) + self._networkop.add_xcat_mac(inst_name, nic_vdev, + vif['address'], zhcp) + self._networkop.add_xcat_switch(inst_name, vif['id'], + nic_vdev, zhcp) + nic_vdev = str(hex(int(nic_vdev, 16) + 3))[2:] + + def unfilter_instance(self, instance, network_info): + """Stop filtering instance.""" + # Not supported for now + return + + def _vmrelocate(self, dest_host, instance_name, action): + """Perform live-migration.""" + body = ['destination=%s' % dest_host, + 'action=%s' % action, + 'immediate=%s' % CONF.zvm_vmrelocate_immediate, + 'max_total=%s' % CONF.zvm_vmrelocate_max_total, + 'max_quiesce=%s' % CONF.zvm_vmrelocate_max_quiesce] + if CONF.zvm_vmrelocate_force is not None: + body.append('force=%s' % CONF.zvm_vmrelocate_force) + + url = self._xcat_url.rmigrate('/' + instance_name) + try: + res = zvmutils.xcat_request("PUT", url, body) + except (exception.ZVMXCATRequestFailed, + exception.ZVMInvalidXCATResponseDataError, + exception.ZVMXCATInternalError) as err: + raise nova_exception.MigrationError(reason=err.format_message()) + + res_info = res['info'] + if not (len(res_info) > 0 and + len(res_info[0]) > 0 and + res_info[-1][0].__contains__("Done")): + msg = _("Live-migration failed: %s") % str(res_info) + LOG.error(msg) + raise nova_exception.MigrationError(reason=msg) + + def reset_network(self, instance): + """reset networking for specified instance.""" + # TODO(rui): to implement this later. + pass + + def inject_network_info(self, instance, nw_info): + """inject network info for specified instance.""" + # TODO(rui): to implement this later. + pass + + def plug_vifs(self, instance, network_info): + """Plug VIFs into networks.""" + # TODO(rui): to implement this later. + pass + + def unplug_vifs(self, instance, network_info): + """Unplug VIFs from networks.""" + # TODO(rui): to implement this later + pass + + def ensure_filtering_rules_for_instance(self, instance_ref, network_info): + # It enforces security groups on host initialization and live + # migration. In z/VM we do not assume instances running upon host + # initialization + return + + def update_host_status(self): + """Refresh host stats. One compute service entry possibly + manages several hypervisors, so will return a list of host + status information. + """ + LOG.debug("Updating host status for %s", CONF.zvm_host) + + caps = [] + host = CONF.zvm_host + + info = self._get_host_inventory_info(host) + + data = {'host': CONF.host, + 'allowed_vm_type': const.ALLOWED_VM_TYPE} + data['vcpus'] = info['vcpus'] + data['vcpus_used'] = info['vcpus_used'] + data['cpu_info'] = info['cpu_info'] + data['disk_total'] = info['disk_total'] + data['disk_used'] = info['disk_used'] + data['disk_available'] = info['disk_available'] + data['host_memory_total'] = info['memory_mb'] + data['host_memory_free'] = (info['memory_mb'] - + info['memory_mb_used']) + data['hypervisor_type'] = info['hypervisor_type'] + data['hypervisor_version'] = info['hypervisor_version'] + data['hypervisor_hostname'] = info['hypervisor_hostname'] + data['supported_instances'] = [(const.ARCHITECTURE, + const.HYPERVISOR_TYPE, + fields.VMMode.HVM)] + data['zhcp'] = self._get_hcp_info(info['zhcp']) + data['ipl_time'] = info['ipl_time'] + + caps.append(data) + + return caps + + def _get_hcp_info(self, hcp_hostname=None): + if self._host_stats != []: + return self._host_stats[0]['zhcp'] + else: + if hcp_hostname is not None: + hcp_node = hcp_hostname.partition('.')[0] + return {'hostname': hcp_hostname, + 'nodename': hcp_node, + 'userid': zvmutils.get_userid(hcp_node)} + else: + self._host_stats = self.update_host_status() + return self._host_stats[0]['zhcp'] + + def get_volume_connector(self, instance): + """Get connector information for the instance for attaching to volumes. + + Connector information is a dictionary representing the ip of the + machine that will be making the connection, the name of the iscsi + initiator and the hostname of the machine as follows:: + + { + 'zvm_fcp': fcp + 'wwpns': [wwpn] + 'host': host + } + """ + LOG.debug("Getting volume connector") + + res = self._volumeop.get_volume_connector(instance) + + return res + + def _get_host_inventory_info(self, host): + url = self._xcat_url.rinv('/' + host) + inv_info_raw = zvmutils.xcat_request("GET", url)['info'][0] + inv_keys = const.XCAT_RINV_HOST_KEYWORDS + inv_info = zvmutils.translate_xcat_resp(inv_info_raw[0], inv_keys) + dp_info = self._get_diskpool_info(host) + + host_info = {} + + with zvmutils.expect_invalid_xcat_resp_data(inv_info): + host_info['vcpus'] = int(inv_info['lpar_cpu_total']) + host_info['vcpus_used'] = int(inv_info['lpar_cpu_used']) + host_info['cpu_info'] = {} + host_info['cpu_info'] = {'architecture': const.ARCHITECTURE, + 'cec_model': inv_info['cec_model'], } + host_info['disk_total'] = dp_info['disk_total'] + host_info['disk_used'] = dp_info['disk_used'] + host_info['disk_available'] = dp_info['disk_available'] + mem_mb = zvmutils.convert_to_mb(inv_info['lpar_memory_total']) + host_info['memory_mb'] = mem_mb + mem_mb_used = zvmutils.convert_to_mb(inv_info['lpar_memory_used']) + host_info['memory_mb_used'] = mem_mb_used + host_info['hypervisor_type'] = const.HYPERVISOR_TYPE + verl = inv_info['hypervisor_os'].split()[1].split('.') + version = int(''.join(verl)) + host_info['hypervisor_version'] = version + host_info['hypervisor_hostname'] = inv_info['hypervisor_name'] + host_info['zhcp'] = inv_info['zhcp'] + host_info['ipl_time'] = inv_info['ipl_time'] + + return host_info + + def _get_diskpool_info(self, host): + addp = '&field=--diskpoolspace&field=' + CONF.zvm_diskpool + url = self._xcat_url.rinv('/' + host, addp) + res_dict = zvmutils.xcat_request("GET", url) + + dp_info_raw = res_dict['info'][0] + dp_keys = const.XCAT_DISKPOOL_KEYWORDS + dp_info = zvmutils.translate_xcat_resp(dp_info_raw[0], dp_keys) + + with zvmutils.expect_invalid_xcat_resp_data(dp_info): + for k in list(dp_info.keys()): + s = dp_info[k].strip().upper() + if s.endswith('G'): + sl = s[:-1].split('.') + n1, n2 = int(sl[0]), int(sl[1]) + if n2 >= 5: + n1 += 1 + dp_info[k] = n1 + elif s.endswith('M'): + n_mb = int(s[:-1]) + n_gb, n_ad = n_mb / 1024, n_mb % 1024 + if n_ad >= 512: + n_gb += 1 + dp_info[k] = n_gb + else: + exp = "ending with a 'G' or 'M'" + errmsg = _("Invalid diskpool size format: %(invalid)s; " + "Expected: %(exp)s"), {'invalid': s, 'exp': exp} + LOG.error(errmsg) + raise exception.ZVMDriverError(msg=errmsg) + + return dp_info + + def migrate_disk_and_power_off(self, context, instance, dest, + instance_type, network_info, + block_device_info=None, + timeout=0, retry_interval=0): + """Transfers the disk of a running instance in multiple phases, turning + off the instance before the end. + """ + is_volume_base = zvmutils.is_boot_from_volume(block_device_info)[1] + + if is_volume_base: + msg = _("Not support boot from volume.") + LOG.error(msg, instance=instance) + raise nova_exception.InstanceFaultRollback( + nova_exception.MigrationError(reason=msg)) + + new_root_disk_size = instance_type.root_gb + new_eph_disk_size = instance_type.ephemeral_gb + old_root_disk_size = instance.root_gb + old_eph_disk_size = instance.ephemeral_gb + + if (new_root_disk_size < old_root_disk_size or + new_eph_disk_size < old_eph_disk_size): + err = _("Not support shrink disk") + LOG.error(err, instance=instance) + raise nova_exception.InstanceFaultRollback( + nova_exception.MigrationError(reason=err)) + + # Make sure the instance's power_state is running and unpaused before + # doing a capture. + state = instance['power_state'] + if (state == power_state.NOSTATE or state == power_state.CRASHED or + state == power_state.SUSPENDED): + raise nova_exception.InstanceNotReady(instance_id=instance['name']) + elif state == power_state.SHUTDOWN: + self.power_on({}, instance, []) + elif state == power_state.PAUSED: + self.unpause(instance) + + inst_name = instance['name'] + LOG.debug("Starting to migrate instance %s", inst_name, + instance=instance) + + disk_owner = zvmutils.get_userid(inst_name) + eph_disk_info = self._get_eph_disk_info(inst_name) + + bdm = driver.block_device_info_get_mapping(block_device_info) + self._detach_volume_from_instance(instance, bdm) + + image_name_xcat = self._capture_disk_for_instance(context, instance) + + image_bundle = '' + shared_image_repo = self._is_shared_image_repo(image_name_xcat) + if not shared_image_repo: + # Export image from xCAT to compute node if image repo not shared + snapshot_time_path = self._zvm_images.get_snapshot_time_path() + + try: + image_bundle = self._zvm_images.get_image_from_xcat( + image_name_xcat, + image_name_xcat, + snapshot_time_path) + except exception.ZVMImageError: + with excutils.save_and_reraise_exception(): + self._zvm_images.clean_up_snapshot_time_path( + snapshot_time_path) + + source_image = "".join([zvmutils.get_host(), ":", image_bundle]) + disk_info = { + 'disk_type': CONF.zvm_diskpool_type, + 'disk_source_mn': CONF.zvm_xcat_server, + 'disk_source_image': source_image, + 'disk_image_name': image_name_xcat, + 'disk_owner': disk_owner, + 'disk_eph_size_old': old_eph_disk_size, + 'disk_eph_size_new': new_eph_disk_size, + 'eph_disk_info': eph_disk_info, + 'shared_image_repo': shared_image_repo, + } + + return jsonutils.dumps(disk_info) + + def _get_eph_disk_info(self, inst_name): + user_dict = self._get_user_directory(inst_name) + exl = ''.join(['MDISK ', CONF.zvm_user_root_vdev]) + eph_disks = [mdisk for mdisk in user_dict + if (mdisk.__contains__('MDISK ') and + not mdisk.__contains__(exl))] + + eph_disk_info = [] + with zvmutils.expect_invalid_xcat_resp_data(eph_disks): + for eph in eph_disks: + eph_l = eph.rpartition(" MDISK ")[2].split(' ') + eph_disk_info.append({'vdev': eph_l[0], + 'size': eph_l[3], + 'guest_format': None, + 'size_in_units': True, + 'device_name': eph_l[0]}) + + return eph_disk_info + + def _detach_volume_from_instance(self, instance, block_device_mapping): + for bd in block_device_mapping: + connection_info = bd['connection_info'] + mountpoint = bd['mount_device'] + if mountpoint: + mountpoint = self._format_mountpoint(mountpoint) + + if self._instance_exists(instance['name']): + zvm_inst = ZVMInstance(self, instance) + is_active = zvm_inst.is_reachable() + try: + zvm_inst.detach_volume(self._volumeop, connection_info, + instance, mountpoint, is_active, + rollback=False) + except exception.ZVMVolumeError as err: + LOG.warning(_LW("Failed to detach volume from %(inst)s: " + "%(err)s"), + {'inst': instance['name'], + 'err': err.format_message()}, + instance=instance) + + def _capture_disk_for_instance(self, context, instance): + """Capture disk.""" + zvm_inst = ZVMInstance(self, instance) + image_name = ''.join('rsz' + instance['name']) + image_uuid = str(uuidutils.generate_uuid()) + image_href = image_uuid.replace('-', '_') + + # Capture + orig_provmethod = zvm_inst.get_provmethod() + if orig_provmethod != 'sysclone': + zvm_inst.update_node_provmethod('sysclone') + image_name_xcat = self._zvm_images.create_zvm_image(instance, + image_name, + image_href) + if orig_provmethod != 'sysclone': + zvm_inst.update_node_provmethod(orig_provmethod) + + self._zvm_images.update_last_use_date(image_name_xcat) + + return image_name_xcat + + @contextlib.contextmanager + def cleanup_xcat_image_for_migration(self, image_name_xcat): + """Cleanup xcat image that imported by migrate_disk_and_power_off.""" + try: + yield + except (nova_exception.MigrationError, + exception.ZVMBaseException): + LOG.debug("Cleanup image from xCAT image repository") + with excutils.save_and_reraise_exception(): + self._zvm_images.delete_image_from_xcat(image_name_xcat) + + def finish_migration(self, context, migration, instance, disk_info, + network_info, image_meta, resize_instance, + block_device_info=None, power_on=True): + """Completes a resize, turning on the migrated instance + + :param network_info: + :py:meth:`~nova.network.manager.NetworkManager.get_instance_nw_info` + :param image_meta: image object returned by nova.image.glance that + defines the image from which this instance + was created + """ + # This is because commit fbe31e461ac3f16edb795993558a2314b4c16b52 + # changes the image_meta from dict to object, we have several + # unique property can't be handled well + # see bug 1537921 for detail info + image_meta = self._image_api.get(context, image_meta.id) + + disk_info = jsonutils.loads(disk_info) + + source_xcat_mn = disk_info['disk_source_mn'].encode('gbk') + source_image = disk_info['disk_source_image'].encode('gbk') + image_name_xcat = disk_info['disk_image_name'].encode('gbk') + disk_type = disk_info['disk_type'].encode('gbk') + disk_eph_size_old = disk_info['disk_eph_size_old'] + disk_eph_size_new = disk_info['disk_eph_size_new'] + eph_disk_info = disk_info['eph_disk_info'] + shared_image_repo = disk_info['shared_image_repo'] + + old_eph_info = block_device_info['ephemerals'] + new_eph_info = [eph for eph in eph_disk_info + if eph['vdev'] != CONF.zvm_user_adde_vdev] + + block_device_info = block_device_info or {} + block_device_info['ephemerals'] = new_eph_info + + if (len(old_eph_info) == 1 and + old_eph_info[0]['size'] == disk_eph_size_old): + # Enlarge the only ephemeral disk + block_device_info['ephemerals'][0]['size'] = disk_eph_size_new + block_device_info['ephemerals'][0]['size_in_units'] = False + + old_userid = disk_info['disk_owner'].encode('gbk') + new_userid = None + if old_userid.startswith("rsz"): + new_userid = instance['name'] + else: + new_userid = ''.join(("rsz", old_userid[3:])) + + same_xcat_mn = source_xcat_mn == CONF.zvm_xcat_server + if disk_type != CONF.zvm_diskpool_type: + if same_xcat_mn: + self._zvm_images.delete_image_from_xcat(image_name_xcat) + msg = _("Can not migration between different disk type" + "current is %(c)s, target is %(t)s") % {'t': disk_type, + 'c': CONF.zvm_diskpool_type} + LOG.error(msg, instance=instance) + raise nova_exception.MigrationError(reason=msg) + + profile = image_name_xcat.split('-')[3] + source_host, t, image_bundle = source_image.partition(":") + source_ip = source_host.rpartition("@")[2] + source_image_time_path = image_bundle.rpartition('/')[0] + local_ip = self.get_host_ip_addr() + + same_os = local_ip == source_ip + + hcp_info = self._get_hcp_info() + zhcp = hcp_info['hostname'] + zhcp_userid = hcp_info['userid'] + + new_inst = ZVMInstance(self, instance) + if same_xcat_mn: + # Same xCAT MN + # cleanup networking, will re-configure later + for vif in network_info: + self._networkop.clean_mac_switch_host(new_inst._name) + + if not shared_image_repo: + # cleanup image bundle from source compute node + if not same_os: + utils.execute('ssh', source_host, 'rm', '-rf', + source_image_time_path) + else: + self._pathutils.clean_temp_folder(source_image_time_path) + + # Create a xCAT node poin + old_instance = self._copy_instance(instance) + old_instance['name'] = ''.join(('rsz', instance['name'])) + old_inst = ZVMInstance(self, old_instance) + + with self.cleanup_xcat_image_for_migration(image_name_xcat): + old_inst.copy_xcat_node(new_inst._name) + try: + new_inst.update_node_def(zhcp, new_userid) + except exception.ZVMBaseException: + with excutils.save_and_reraise_exception(): + old_inst.delete_xcat_node() + else: + # Different xCAT MN + new_inst.create_xcat_node(zhcp) + if new_userid != new_inst._name: + try: + new_inst.update_node_def(zhcp, new_userid) + except exception.ZVMBaseException: + with excutils.save_and_reraise_exception(): + old_inst.delete_xcat_node() + + if not same_os: + snapshot_time_path = self._pathutils.get_snapshot_time_path() + dest_image_path = os.path.join(snapshot_time_path, + image_name_xcat + '.tgz') + utils.execute('scp', source_image, snapshot_time_path) + utils.execute('ssh', source_host, + 'rm', '-rf', source_image_time_path) + else: + snapshot_time_path = source_image_time_path + dest_image_path = image_bundle + + try: + self._zvm_images.put_image_to_xcat(dest_image_path, profile) + except exception.ZVMImageError: + with excutils.save_and_reraise_exception(): + new_inst.delete_xcat_node() + + self._zvm_images.clean_up_snapshot_time_path(snapshot_time_path) + + try: + # Pre-config network and create zvm userid + self._preset_instance_network(new_inst._name, network_info) + new_inst.create_userid(block_device_info, image_meta, context) + + if disk_eph_size_old == 0 and disk_eph_size_new > 0: + # Punch ephemeral disk info to the new instance + zvmutils.process_eph_disk(new_inst._name) + + # Add nic and deploy the image + self._add_nic_to_table(new_inst._name, network_info) + self._deploy_root_and_ephemeral(new_inst, image_name_xcat) + self._wait_and_get_nic_direct(new_inst._name, instance) + except exception.ZVMBaseException: + with excutils.save_and_reraise_exception(): + self._zvm_images.delete_image_from_xcat(image_name_xcat) + + if not same_xcat_mn: + try: + for vif in network_info: + self._networkop.clean_mac_switch_host( + new_inst._name) + except exception.ZVMNetworkError as e: + emsg = zvmutils.format_exception_msg(e) + LOG.debug('clean_mac_switch_host error: %s', emsg) + + new_inst.delete_userid(self._get_hcp_info()['nodename'], + context) + new_inst.delete_xcat_node() + + if same_xcat_mn: + new_inst.copy_xcat_node(old_inst._name) + old_inst.delete_xcat_node() + + # re-configure the networking + self._reconfigure_networking(new_inst._name, network_info, + instance, userid=old_userid) + if power_on: + new_inst.power_on() + + # Cleanup image from xCAT image repository + self._zvm_images.delete_image_from_xcat(image_name_xcat) + + bdm = driver.block_device_info_get_mapping(block_device_info) + try: + if zvmutils.xcat_support_iucv(self._xcat_version): + if same_xcat_mn: + zvmutils.punch_iucv_authorized_file(old_inst._name, + new_inst._name, zhcp_userid) + else: + zvmutils.punch_iucv_authorized_file(new_inst._name, + new_inst._name, zhcp_userid) + new_inst.power_on() + self._attach_volume_to_instance(context, instance, bdm) + + if not power_on: + new_inst.power_off() + except exception.ZVMBaseException: + with excutils.save_and_reraise_exception(): + self.destroy(context, instance, network_info, + block_device_info) + if same_xcat_mn: + new_inst.copy_xcat_node(old_inst._name) + old_inst.delete_xcat_node() + + def _reconfigure_networking(self, inst_name, network_info, instance, + userid=None): + self._preset_instance_network(inst_name, network_info) + self._add_nic_to_table(inst_name, network_info) + self._wait_and_get_nic_direct(inst_name, instance) + + def _copy_instance(self, instance): + return zvminstance.CopiedInstance(instance) + + def _attach_volume_to_instance(self, context, instance, + block_device_mapping): + for bd in block_device_mapping: + connection_info = bd['connection_info'] + mountpoint = bd['mount_device'] + self.attach_volume(context, connection_info, instance, mountpoint) + + def _add_nic_to_table(self, inst_name, network_info): + nic_vdev = CONF.zvm_default_nic_vdev + zhcpnode = self._get_hcp_info()['nodename'] + for vif in network_info: + LOG.debug('Create xcat table value about nic: ' + 'ID is %(id)s, address is %(address)s, ' + 'vdev is %(vdev)s', + {'id': vif['id'], 'address': vif['address'], + 'vdev': nic_vdev}) + self._networkop.create_xcat_table_about_nic(zhcpnode, + inst_name, + vif['id'], + vif['address'], + nic_vdev) + nic_vdev = str(hex(int(nic_vdev, 16) + 3))[2:] + + def _deploy_root_and_ephemeral(self, instance, image_name_xcat): + + # Update the nodetype + instance.update_node_info_resize(image_name_xcat) + + # Deploy + instance.deploy_node(image_name_xcat) + + self._zvm_images.update_last_use_date(image_name_xcat) + + def confirm_migration(self, context, migration, instance, network_info): + """Confirms a resize, destroying the source VM.""" + # Point to old instance + old_instance = self._copy_instance(instance) + old_instance['name'] = ''.join(('rsz', instance['name'])) + old_inst = ZVMInstance(self, old_instance) + + if self._instance_exists(old_inst._name): + # Same xCAT MN: + self.destroy(context, old_instance) + else: + # Different xCAT MN: + self.destroy(context, instance) + self._zvm_images.cleanup_image_after_migration(instance['name']) + + def finish_revert_migration(self, context, instance, network_info, + block_device_info=None, power_on=True): + """Finish reverting a resize, powering back on the instance.""" + new_instance = self._copy_instance(instance) + new_instance['name'] = ''.join(('rsz', instance['name'])) + zvm_inst = ZVMInstance(self, new_instance) + bdm = driver.block_device_info_get_mapping(block_device_info) + + if self._instance_exists(zvm_inst._name): + # Same xCAT MN: + old_inst = ZVMInstance(self, instance) + old_inst.copy_xcat_node(new_instance['name']) + if zvmutils.xcat_support_iucv(self._xcat_version): + zvmutils.copy_zvm_table_status(instance['name'], + new_instance['name']) + zvm_inst.delete_xcat_node() + + self._reconfigure_networking(instance['name'], network_info, + instance) + else: + # Different xCAT MN: + self._zvm_images.cleanup_image_after_migration(instance['name']) + + self._attach_volume_to_instance({}, instance, bdm) + + if power_on: + self.power_on({}, instance, []) + + def _get_nic_switch_info(self, inst_name): + url = self._xcat_url.tabdump("/switch") + with zvmutils.expect_invalid_xcat_resp_data(): + switch_info = zvmutils.xcat_request("GET", url)['data'][0] + switch_info.pop(0) + switch_dict = {} + for item in switch_info: + switch_list = item.split(',') + if switch_list[0].strip('"') == inst_name: + switch_dict[switch_list[4].strip('"')] = \ + switch_list[1].strip('"') + + LOG.debug("Switch info the %(inst_name)s is %(switch_dict)s", + {"inst_name": inst_name, "switch_dict": switch_dict}) + return switch_dict + + def _get_user_directory(self, inst_name): + url = self._xcat_url.lsvm('/' + inst_name) + user_dict = zvmutils.xcat_request("GET", url) + + with zvmutils.expect_invalid_xcat_resp_data(user_dict): + dict_str = user_dict['info'][0][0] + + return dict_str.split("\n") + + def set_admin_password(self, instance, new_pass=None): + """Set the root password on the specified instance. + + The first parameter is an instance of nova.compute.service.Instance, + and so the instance is being specified as instance.name. The second + parameter is the value of the new password. + """ + if new_pass is not None: + self._set_admin_password(instance['name'], new_pass) + + def _set_admin_password(self, inst_name, password): + command = "echo 'root:%s' | chpasswd" % password + try: + if zvmutils.xcat_support_iucv(self._xcat_version): + # After support IUCV, will use execcmdonvm to replace xdsh. + zvmutils.execcmdonvm(inst_name, command) + else: + zvmutils.xdsh(inst_name, command) + except exception.ZVMXCATXdshFailed as err: + LOG.error(_("Setting root password for instance %(instance)s " + "failed with reason: %(err)s"), + {'instance': inst_name, 'err': err.format_message()}) + raise err + + def _wait_and_get_nic_direct(self, inst_name, instance): + """Wait until neutron zvm-agent add NICs into user direct done.""" + def _wait_for_nic_add_in_direct(inst_name, expiration): + if (CONF.zvm_reachable_timeout and + timeutils.utcnow() > expiration): + msg = _("NIC update check failed " + "on instance:%s") % instance.uuid + raise exception.ZVMNetworkError(msg=msg) + + try: + switch_dict = self._get_nic_switch_info(inst_name) + if switch_dict and '' not in switch_dict.values(): + for key, value in switch_dict.items(): + args = '&checknics=' + key + url = self._xcat_url.lsvm('/' + inst_name) + url = url + args + res_info = zvmutils.xcat_request("GET", url) + with zvmutils.expect_invalid_xcat_resp_data(res_info): + if ("errorcode" in res_info and + (len(res_info["errorcode"]) > 0) and + res_info["errorcode"][0] != '0'): + # we didn't found the definition + return + else: + # in this case, the nic switch info is not ready yet + # need another loop to check until time out or find it + return + + except exception.ZVMBaseException as e: + # Ignore any zvm driver exceptions + LOG.info(_LI('encounter error %s during get vswitch info'), + e.format_message(), instance=instance) + return + + # Enter here means all NIC granted + LOG.info(_LI("All NICs are added in user direct for " + "instance %s."), inst_name, instance=instance) + raise loopingcall.LoopingCallDone() + + expiration = timeutils.utcnow() + datetime.timedelta( + seconds=CONF.zvm_reachable_timeout) + LOG.info(_LI("Wait neturon-zvm-agent to add NICs to %s user direct."), + inst_name, instance=instance) + timer = loopingcall.FixedIntervalLoopingCall( + _wait_for_nic_add_in_direct, inst_name, expiration) + timer.start(interval=10).wait() + + def get_console_output(self, context, instance): + """Get console output for an instance.""" + + def append_to_log(log_data, log_path): + LOG.debug('log_data: %(log_data)r, log_path: %(log_path)r', + {'log_data': log_data, 'log_path': log_path}) + fp = open(log_path, 'a+') + fp.write(log_data) + fp.close() + return log_path + + zvm_inst = ZVMInstance(self, instance) + logsize = CONF.zvm_console_log_size * units.Ki + console_log = "" + + try: + console_log = zvm_inst.get_console_log(logsize) + except exception.ZVMXCATInternalError: + # Ignore no console log avaiable error + LOG.info(_LI("No new console log avaiable.")) + log_path = self._pathutils.get_console_log_path(CONF.zvm_host, + zvm_inst._name) + # TODO(jichenjc): need consider shrink log file size + append_to_log(console_log, log_path) + + log_fp = file(log_path, 'rb') + log_data, remaining = utils.last_bytes(log_fp, logsize) + if remaining > 0: + LOG.info(_LI('Truncated console log returned, %d bytes ignored'), + remaining, instance=instance) + + return log_data + + def get_host_uptime(self): + """Get host uptime.""" + with zvmutils.expect_invalid_xcat_resp_data(self._host_stats): + return self._host_stats[0]['ipl_time'] + + def get_available_nodes(self, refresh=False): + return [d['hypervisor_hostname'] for d in self._host_stats + if (d.get('hypervisor_hostname') is not None)] + + def _extract_volume_id(self, bdm, root_device): + for bd in bdm: + mount_point = bd['mount_device'] + is_root = zvmutils.is_volume_root(root_device, mount_point) + if is_root: + return bd['connection_info']['serial'] + + errmsg = _("Failed to extract volume id from block device mapping." + "%s") % str(bdm) + raise exception.ZVMDriverError(msg=errmsg) + + def _exclude_root_volume_bdm(self, bdm, root_mount_device): + for bd in bdm: + mountpoint = bd['mount_device'] + is_root = zvmutils.is_volume_root(root_mount_device, mountpoint) + if is_root: + bdm.remove(bd) + return bdm + + def _get_xcat_version(self): + url = self._xcat_url.version() + version_info = zvmutils.xcat_request("GET", url) + with zvmutils.expect_invalid_xcat_resp_data(version_info): + dict_str = version_info['data'][0][0] + version = dict_str.split()[1] + return version + + def _version_check(self, req_ver=None, op=operator.lt): + try: + if req_ver is not None: + cur = versionutils.convert_version_to_int(self._xcat_version) + req = versionutils.convert_version_to_int(req_ver) + if op(cur, req): + return False + return True + except Exception: + return False + + def has_min_version(self, req_ver=None): + return self._version_check(req_ver=req_ver, op=operator.lt) + + def has_version(self, req_ver=None): + return self._version_check(req_ver=req_ver, op=operator.ne) + + def default_device_names_for_instance(self, instance, root_device_name, + *block_device_lists): + """Generate missing device names for an instance. + Default are /dev/vdN. + """ + compute_utils.default_device_names_for_instance( + instance, root_device_name, *block_device_lists) + for bdm in itertools.chain(*block_device_lists): + bdm.device_name = self._format_mountpoint(bdm.device_name) + bdm.save() + + def get_device_name_for_instance(self, instance, bdms, block_device_obj): + """Validates (or generates) a device name for instance. + Default are /dev/vdN. + """ + device_name = block_device_obj.get("device_name") + device_name = compute_utils.get_device_name_for_instance( + instance, bdms, device_name) + return self._format_mountpoint(device_name) diff --git a/nova/virt/zvm/exception.py b/nova_zvm/virt/zvm/exception.py similarity index 100% rename from nova/virt/zvm/exception.py rename to nova_zvm/virt/zvm/exception.py diff --git a/nova/virt/zvm/imageop.py b/nova_zvm/virt/zvm/imageop.py similarity index 99% rename from nova/virt/zvm/imageop.py rename to nova_zvm/virt/zvm/imageop.py index a6d4b51..73ff734 100644 --- a/nova/virt/zvm/imageop.py +++ b/nova_zvm/virt/zvm/imageop.py @@ -20,18 +20,18 @@ import shutil import tarfile import xml.dom.minidom as Dom -from oslo_concurrency import processutils -from oslo_config import cfg -from oslo_log import log as logging -from oslo_utils import excutils - from nova import exception as nova_exception from nova.i18n import _, _LW from nova.image import glance from nova import utils from nova.virt import images -from nova.virt.zvm import exception -from nova.virt.zvm import utils as zvmutils +from oslo_concurrency import processutils +from oslo_config import cfg +from oslo_log import log as logging +from oslo_utils import excutils + +from nova_zvm.virt.zvm import exception +from nova_zvm.virt.zvm import utils as zvmutils LOG = logging.getLogger(__name__) CONF = cfg.CONF diff --git a/nova/virt/zvm/instance.py b/nova_zvm/virt/zvm/instance.py similarity index 99% rename from nova/virt/zvm/instance.py rename to nova_zvm/virt/zvm/instance.py index 8e54342..7eb374f 100644 --- a/nova/virt/zvm/instance.py +++ b/nova_zvm/virt/zvm/instance.py @@ -17,18 +17,18 @@ import binascii import six import time -from oslo_config import cfg -from oslo_log import log as logging - from nova.compute import power_state from nova import exception as nova_exception from nova.i18n import _, _LW from nova.virt import hardware -from nova.virt.zvm import const -from nova.virt.zvm import dist -from nova.virt.zvm import exception -from nova.virt.zvm import utils as zvmutils -from nova.virt.zvm import volumeop +from oslo_config import cfg +from oslo_log import log as logging + +from nova_zvm.virt.zvm import const +from nova_zvm.virt.zvm import dist +from nova_zvm.virt.zvm import exception +from nova_zvm.virt.zvm import utils as zvmutils +from nova_zvm.virt.zvm import volumeop LOG = logging.getLogger(__name__) CONF = cfg.CONF diff --git a/nova/virt/zvm/networkop.py b/nova_zvm/virt/zvm/networkop.py similarity index 98% rename from nova/virt/zvm/networkop.py rename to nova_zvm/virt/zvm/networkop.py index 688a117..e539589 100644 --- a/nova/virt/zvm/networkop.py +++ b/nova_zvm/virt/zvm/networkop.py @@ -15,8 +15,8 @@ from oslo_config import cfg -from nova.virt.zvm import exception -from nova.virt.zvm import utils as zvmutils +from nova_zvm.virt.zvm import exception +from nova_zvm.virt.zvm import utils as zvmutils CONF = cfg.CONF diff --git a/nova/virt/zvm/opts.py b/nova_zvm/virt/zvm/opts.py similarity index 82% rename from nova/virt/zvm/opts.py rename to nova_zvm/virt/zvm/opts.py index 705ee07..141114e 100644 --- a/nova/virt/zvm/opts.py +++ b/nova_zvm/virt/zvm/opts.py @@ -12,9 +12,10 @@ # See the License for the specific language governing permissions and # limitations under the License. + import itertools -import nova.virt.zvm.driver +from nova_zvm.virt.zvm import conf as zvm_conf def list_opts(): @@ -23,8 +24,8 @@ def list_opts(): # we keep this into DEFAULT. ('DEFAULT', itertools.chain( - nova.virt.zvm.conf.zvm_image_opts, - nova.virt.zvm.conf.zvm_opts, - nova.virt.zvm.conf.zvm_user_opts, + zvm_conf.zvm_image_opts, + zvm_conf.zvm_opts, + zvm_conf.zvm_user_opts, )), ] diff --git a/nova/virt/zvm/utils.py b/nova_zvm/virt/zvm/utils.py similarity index 99% rename from nova/virt/zvm/utils.py rename to nova_zvm/virt/zvm/utils.py index f231e7a..6c77663 100644 --- a/nova/virt/zvm/utils.py +++ b/nova_zvm/virt/zvm/utils.py @@ -23,19 +23,19 @@ import socket import ssl import time -from oslo_config import cfg -from oslo_log import log as logging -from oslo_serialization import jsonutils -from oslo_utils import excutils - from nova import block_device from nova.compute import power_state from nova import exception as nova_exception from nova.i18n import _, _LE, _LI, _LW from nova.virt import driver -from nova.virt.zvm import const -from nova.virt.zvm import dist -from nova.virt.zvm import exception +from oslo_config import cfg +from oslo_log import log as logging +from oslo_serialization import jsonutils +from oslo_utils import excutils + +from nova_zvm.virt.zvm import const +from nova_zvm.virt.zvm import dist +from nova_zvm.virt.zvm import exception LOG = logging.getLogger(__name__) diff --git a/nova/virt/zvm/volumeop.py b/nova_zvm/virt/zvm/volumeop.py similarity index 99% rename from nova/virt/zvm/volumeop.py rename to nova_zvm/virt/zvm/volumeop.py index 591ad93..3c352b3 100644 --- a/nova/virt/zvm/volumeop.py +++ b/nova_zvm/virt/zvm/volumeop.py @@ -17,21 +17,20 @@ import re import six import time +import nova.context +from nova.i18n import _, _LW +from nova.objects import block_device as block_device_obj +from nova.objects import instance as instance_obj +from nova.volume import cinder from oslo_config import cfg from oslo_log import log as logging from oslo_serialization import jsonutils from oslo_utils import excutils -import nova.context -from nova.i18n import _, _LW -from nova.objects import block_device as block_device_obj -from nova.objects import instance as instance_obj -from nova.virt.zvm import const -from nova.virt.zvm import dist -from nova.virt.zvm import exception -from nova.virt.zvm import utils as zvmutils -from nova.volume import cinder - +from nova_zvm.virt.zvm import const +from nova_zvm.virt.zvm import dist +from nova_zvm.virt.zvm import exception +from nova_zvm.virt.zvm import utils as zvmutils LOG = logging.getLogger(__name__) CONF = cfg.CONF diff --git a/setup.cfg b/setup.cfg index a174ccd..5e12702 100644 --- a/setup.cfg +++ b/setup.cfg @@ -18,6 +18,7 @@ classifier = [files] packages = + nova_zvm nova/virt/zvm [build_sphinx] @@ -44,4 +45,4 @@ output_file = nova_zvm/locale/nova-zvm.pot [entry_points] oslo.config.opts = - nova_zvm = nova.virt.zvm.opts:list_opts + nova_zvm = nova_zvm.virt.zvm.opts:list_opts diff --git a/tox.ini b/tox.ini index 8c88ce9..cefd3fa 100644 --- a/tox.ini +++ b/tox.ini @@ -14,15 +14,13 @@ deps = -r{toxinidir}/requirements.txt commands = python setup.py testr --slowest --testr-args='{posargs}' [testenv:pep8] -commands = flake8 +commands = flake8 {posargs} [testenv:py27] -commands = /bin/cp -r {toxinidir}/nova/virt/zvm {toxinidir}/.tox/py27/src/nova/nova/virt/ - python setup.py testr --slowest --testr-args='{posargs}' +commands = python setup.py testr --slowest --testr-args='{posargs}' [testenv:venv] -commands = /bin/cp -r {toxinidir}/nova/virt/zvm {toxinidir}/.tox/venv/src/nova/nova/virt/ - {posargs} +commands = {posargs} [testenv:cover] commands = python setup.py testr --coverage --testr-args='{posargs}' @@ -44,11 +42,8 @@ deps = bandit commands = bandit -r nova/virt/zvm -n 5 -ll [testenv:genconfig] -commands = - /bin/cp -r {toxinidir}/nova/virt/zvm/ {toxinidir}/.tox/genconfig/src/nova/nova/virt/ - oslo-config-generator --config-file=etc/nova/nova-zvm-oslo-conf-generator.conf +commands = oslo-config-generator --config-file=etc/nova/nova-zvm-oslo-conf-generator.conf [testenv:docs] -commands = - /bin/cp -r {toxinidir}/nova/virt/zvm/ {toxinidir}/.tox/docs/src/nova/nova/virt/ - python setup.py build_sphinx +commands = python setup.py build_sphinx +