nova-zvm-virt-driver/nova_zvm/virt/zvm/driver.py

2051 lines
91 KiB
Python

# 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)
try:
nodename = self._get_hcp_info()['nodename']
zvm_inst.delete_userid(nodename, context)
except exception.ZVMBaseException as err:
LOG.warning(_LW("Failed to delete user node: %s"),
err.format_message(), instance=instance)
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)