
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
903 lines
36 KiB
Python
903 lines
36 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 binascii
|
|
import six
|
|
import time
|
|
|
|
from nova.compute import power_state
|
|
from nova import exception as nova_exception
|
|
from nova.i18n import _, _LW
|
|
from nova.virt import hardware
|
|
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
|
|
|
|
|
|
CONF.import_opt('default_ephemeral_format', 'nova.conf')
|
|
|
|
|
|
class CopiedInstance(object):
|
|
_KWS = ('name', 'image_ref', 'uuid', 'user_id', 'project_id',
|
|
'power_state', 'system_metadata', 'memory_mb', 'vcpus',
|
|
'root_gb', 'ephemeral_gb')
|
|
|
|
def __init__(self, inst_obj):
|
|
"""Dictionary compatible instance object for zVM driver internal use.
|
|
|
|
:inst_obj: instance object of nova.objects.instance.Instance.
|
|
"""
|
|
for k in self._KWS:
|
|
if inst_obj.get(k, None):
|
|
setattr(self, k, inst_obj[k])
|
|
|
|
def get(self, name, default=None):
|
|
return getattr(self, name, default)
|
|
|
|
def __setitem__(self, name, value):
|
|
setattr(self, name, value)
|
|
|
|
def __getitem__(self, name):
|
|
return getattr(self, name, None)
|
|
|
|
|
|
class ZVMInstance(object):
|
|
'''OpenStack instance that running on of z/VM hypervisor.'''
|
|
|
|
def __init__(self, driver, instance=None):
|
|
"""Initialize instance attributes for database."""
|
|
instance = instance or {}
|
|
self._xcat_url = zvmutils.get_xcat_url()
|
|
self._instance = instance
|
|
self._name = instance['name']
|
|
self._volumeop = volumeop.VolumeOperator()
|
|
self._dist_manager = dist.ListDistManager()
|
|
self._driver = driver
|
|
|
|
def power_off(self, timeout=0, retry_interval=10):
|
|
"""Power off z/VM instance."""
|
|
try:
|
|
self._power_state("PUT", "softoff")
|
|
except exception.ZVMXCATInternalError as err:
|
|
err_str = err.format_message()
|
|
if ("Return Code: 200" in err_str and
|
|
"Reason Code: 12" in err_str):
|
|
# Instance already not active
|
|
LOG.warning(_LW("z/VM instance %s not active"), self._name)
|
|
return
|
|
else:
|
|
msg = _("Failed to power off instance: %s") % err_str
|
|
LOG.error(msg)
|
|
raise nova_exception.InstancePowerOffFailure(reason=msg)
|
|
|
|
timeout = timeout or CONF.shutdown_timeout
|
|
retry_interval = retry_interval or 10
|
|
retry_count = timeout // retry_interval
|
|
while (retry_count > 0):
|
|
if self._check_power_stat() == power_state.SHUTDOWN:
|
|
# In shutdown state already
|
|
return
|
|
else:
|
|
time.sleep(retry_interval)
|
|
retry_count -= 1
|
|
|
|
LOG.warning(_LW("Failed to shutdown instance %(inst)s in %(time)d "
|
|
"seconds"), {'inst': self._name, 'time': timeout})
|
|
|
|
def power_on(self):
|
|
""""Power on z/VM instance."""
|
|
try:
|
|
self._power_state("PUT", "on")
|
|
except exception.ZVMXCATInternalError as err:
|
|
err_str = err.format_message()
|
|
if ("Return Code: 200" in err_str and
|
|
"Reason Code: 8" in err_str):
|
|
# Instance already active
|
|
LOG.warning(_LW("z/VM instance %s already active"), self._name)
|
|
return
|
|
raise nova_exception.InstancePowerOnFailure(reason=err_str)
|
|
|
|
self._wait_for_reachable()
|
|
if not self._reachable:
|
|
LOG.error(_("Failed to power on instance %s: timeout"), self._name)
|
|
raise nova_exception.InstancePowerOnFailure(reason="timeout")
|
|
|
|
def is_powered_off(self):
|
|
"""Return True if the instance is powered off."""
|
|
return self._check_power_stat() == power_state.SHUTDOWN
|
|
|
|
def reset(self):
|
|
"""Hard reboot z/VM instance."""
|
|
try:
|
|
self._power_state("PUT", "reset")
|
|
except exception.ZVMXCATInternalError as err:
|
|
err_str = err.format_message()
|
|
if ("Return Code: 200" in err_str and
|
|
"Reason Code: 12" in err_str):
|
|
# Be able to reset in power state of SHUTDOWN
|
|
LOG.warning(_LW("Reset z/VM instance %s from SHUTDOWN state"),
|
|
self._name)
|
|
return
|
|
else:
|
|
raise err
|
|
self._wait_for_reachable()
|
|
|
|
def reboot(self):
|
|
"""Soft reboot z/VM instance."""
|
|
self._power_state("PUT", "reboot")
|
|
self._wait_for_reachable()
|
|
|
|
def pause(self):
|
|
"""Pause the z/VM instance."""
|
|
self._power_state("PUT", "pause")
|
|
|
|
def unpause(self):
|
|
"""Unpause the z/VM instance."""
|
|
self._power_state("PUT", "unpause")
|
|
self._wait_for_reachable()
|
|
|
|
def attach_volume(self, volumeop, context, connection_info, instance,
|
|
mountpoint, is_active, rollback=True):
|
|
volumeop.attach_volume_to_instance(context, connection_info,
|
|
instance, mountpoint,
|
|
is_active, rollback)
|
|
|
|
def detach_volume(self, volumeop, connection_info, instance, mountpoint,
|
|
is_active, rollback=True):
|
|
volumeop.detach_volume_from_instance(connection_info,
|
|
instance, mountpoint,
|
|
is_active, rollback)
|
|
|
|
def get_info(self):
|
|
cpumempowerstat_version = const.XCAT_RINV_SUPPORT_CPUMEMPOWERSTAT
|
|
# new version has cpumempowerstat support in order gain performance
|
|
if self._driver.has_min_version(cpumempowerstat_version):
|
|
return self._get_info_cpumempowerstat()
|
|
else:
|
|
return self._get_info_cpumem()
|
|
|
|
def _get_info_cpumempowerstat(self):
|
|
"""Get current status of an z/VM instance through cpumempowerstat."""
|
|
_instance_info = hardware.InstanceInfo()
|
|
max_mem_kb = int(self._instance['memory_mb']) * 1024
|
|
|
|
try:
|
|
rec_list = self._get_rinv_info('cpumempowerstat')
|
|
except exception.ZVMXCATInternalError:
|
|
raise nova_exception.InstanceNotFound(instance_id=self._name)
|
|
|
|
mem = self._get_current_memory(rec_list)
|
|
num_cpu = self._get_guest_cpus(rec_list)
|
|
cpu_time = self._get_cpu_used_time(rec_list)
|
|
power_stat = self._get_power_stat(rec_list)
|
|
|
|
if ((power_stat == power_state.RUNNING) and
|
|
(self._instance['power_state'] == power_state.PAUSED)):
|
|
# return paused state only previous power state is paused
|
|
_instance_info.state = power_state.PAUSED
|
|
else:
|
|
_instance_info.state = power_stat
|
|
|
|
# TODO(jichenjc): set max mem through SMAPI result
|
|
_instance_info.max_mem_kb = max_mem_kb
|
|
_instance_info.mem_kb = mem
|
|
_instance_info.num_cpu = num_cpu
|
|
_instance_info.cpu_time_ns = cpu_time
|
|
return _instance_info
|
|
|
|
def _get_info_cpumem(self):
|
|
"""Get current status of an z/VM instance through cpumem."""
|
|
_instance_info = hardware.InstanceInfo()
|
|
|
|
power_stat = self._check_power_stat()
|
|
is_reachable = self.is_reachable()
|
|
|
|
max_mem_kb = int(self._instance['memory_mb']) * 1024
|
|
if is_reachable:
|
|
try:
|
|
rec_list = self._get_rinv_info('cpumem')
|
|
except exception.ZVMXCATInternalError:
|
|
raise nova_exception.InstanceNotFound(instance_id=self._name)
|
|
|
|
try:
|
|
mem = self._get_current_memory(rec_list)
|
|
num_cpu = self._get_cpu_count(rec_list)
|
|
cpu_time = self._get_cpu_used_time(rec_list)
|
|
_instance_info.state = power_stat
|
|
_instance_info.max_mem_kb = max_mem_kb
|
|
_instance_info.mem_kb = mem
|
|
_instance_info.num_cpu = num_cpu
|
|
_instance_info.cpu_time_ns = cpu_time
|
|
|
|
except exception.ZVMInvalidXCATResponseDataError:
|
|
LOG.warning(_LW("Failed to get inventory info for %s"),
|
|
self._name)
|
|
_instance_info.state = power_stat
|
|
_instance_info.max_mem_kb = max_mem_kb
|
|
_instance_info.mem_kb = max_mem_kb
|
|
_instance_info.num_cpu = self._instance['vcpus']
|
|
_instance_info.cpu_time_ns = 0
|
|
|
|
else:
|
|
# Since xCAT rinv can't get info from a server that in power state
|
|
# of SHUTDOWN or PAUSED
|
|
if ((power_stat == power_state.RUNNING) and
|
|
(self._instance['power_state'] == power_state.PAUSED)):
|
|
# return paused state only previous power state is paused
|
|
_instance_info.state = power_state.PAUSED
|
|
_instance_info.max_mem_kb = max_mem_kb
|
|
_instance_info.mem_kb = max_mem_kb
|
|
_instance_info.num_cpu = self._instance['vcpus']
|
|
_instance_info.cpu_time_ns = 0
|
|
else:
|
|
# otherwise return xcat returned state
|
|
_instance_info.state = power_stat
|
|
_instance_info.max_mem_kb = max_mem_kb
|
|
_instance_info.mem_kb = 0
|
|
_instance_info.num_cpu = self._instance['vcpus']
|
|
_instance_info.cpu_time_ns = 0
|
|
return _instance_info
|
|
|
|
def create_xcat_node(self, zhcp, userid=None):
|
|
"""Create xCAT node for z/VM instance."""
|
|
LOG.debug("Creating xCAT node for %s", self._name)
|
|
|
|
user_id = userid or self._name
|
|
body = ['userid=%s' % user_id,
|
|
'hcp=%s' % zhcp,
|
|
'mgt=zvm',
|
|
'groups=%s' % CONF.zvm_xcat_group]
|
|
url = self._xcat_url.mkdef('/' + self._name)
|
|
|
|
with zvmutils.except_xcat_call_failed_and_reraise(
|
|
exception.ZVMXCATCreateNodeFailed, node=self._name):
|
|
zvmutils.xcat_request("POST", url, body)
|
|
|
|
def _create_user_id_body(self, boot_from_volume):
|
|
kwprofile = 'profile=%s' % CONF.zvm_user_profile
|
|
body = [kwprofile,
|
|
'password=%s' % CONF.zvm_user_default_password,
|
|
'cpu=%i' % self._instance['vcpus'],
|
|
'memory=%im' % self._instance['memory_mb'],
|
|
'privilege=%s' % CONF.zvm_user_default_privilege]
|
|
|
|
# if mkvm in lower version xcat won't support it
|
|
# they will ignore this param.
|
|
if not boot_from_volume:
|
|
body.append('ipl=%s' % CONF.zvm_user_root_vdev)
|
|
|
|
return body
|
|
|
|
def _check_set_ipl(self):
|
|
xcat_version = self._driver._xcat_version
|
|
|
|
if not zvmutils.xcat_support_mkvm_ipl_param(xcat_version):
|
|
self._set_ipl(CONF.zvm_user_root_vdev)
|
|
|
|
def create_userid(self, block_device_info, image_meta, context,
|
|
os_image=None):
|
|
"""Create z/VM userid into user directory for a z/VM instance."""
|
|
# We do not support boot from volume currently
|
|
LOG.debug("Creating the z/VM user entry for instance %s", self._name)
|
|
|
|
boot_from_volume = zvmutils.is_boot_from_volume(block_device_info)[1]
|
|
|
|
eph_disks = block_device_info.get('ephemerals', [])
|
|
|
|
body = self._create_user_id_body(boot_from_volume)
|
|
|
|
if not boot_from_volume:
|
|
# image_meta passed from spawn is a dict, in resize is a object
|
|
if isinstance(image_meta, dict):
|
|
if 'name' in image_meta.keys():
|
|
kwimage = 'imagename=%s' % image_meta['name']
|
|
body.append(kwimage)
|
|
else:
|
|
image_name = getattr(image_meta, 'name')
|
|
if image_name:
|
|
kwimage = 'imagename=%s' % image_name
|
|
body.append(kwimage)
|
|
|
|
if os_image:
|
|
kwimage = 'osimage=%s' % os_image
|
|
body.append(kwimage)
|
|
|
|
# Versions of xCAT that do not understand the instance ID and
|
|
# request ID will silently ignore them.
|
|
url = self._xcat_url.mkvm('/' + self._name, self._instance.uuid,
|
|
context)
|
|
|
|
# Note: driver.py:spawn() has already checked that the root disk units
|
|
# and the type of disks in the pool are compatible.
|
|
try:
|
|
zvmutils.xcat_request("POST", url, body)
|
|
|
|
if not boot_from_volume:
|
|
size = '%ig' % self._instance['root_gb']
|
|
# If the flavor specifies 0 for the root disk size, use the
|
|
# size in the image's metadata
|
|
if size == '0g':
|
|
root_disk_units = image_meta['properties'][
|
|
'root_disk_units']
|
|
size = root_disk_units.split(":")[0]
|
|
|
|
# Add root disk and set ipl
|
|
self.add_mdisk(CONF.zvm_diskpool,
|
|
CONF.zvm_user_root_vdev,
|
|
size)
|
|
self._check_set_ipl()
|
|
|
|
# Add additional ephemeral disk
|
|
if self._instance['ephemeral_gb'] != 0:
|
|
if eph_disks == []:
|
|
# Create ephemeral disk according to flavor
|
|
fmt = (CONF.default_ephemeral_format or
|
|
const.DEFAULT_EPH_DISK_FMT)
|
|
self.add_mdisk(CONF.zvm_diskpool,
|
|
CONF.zvm_user_adde_vdev,
|
|
'%ig' % self._instance['ephemeral_gb'],
|
|
fmt)
|
|
else:
|
|
# Create ephemeral disks according --ephemeral option
|
|
for idx, eph in enumerate(eph_disks):
|
|
vdev = (eph.get('vdev') or
|
|
zvmutils.generate_eph_vdev(idx))
|
|
size = eph['size']
|
|
size_in_units = eph.get('size_in_units', False)
|
|
if not size_in_units:
|
|
size = '%ig' % size
|
|
fmt = (eph.get('guest_format') or
|
|
CONF.default_ephemeral_format or
|
|
const.DEFAULT_EPH_DISK_FMT)
|
|
self.add_mdisk(CONF.zvm_diskpool, vdev, size, fmt)
|
|
except (exception.ZVMXCATRequestFailed,
|
|
exception.ZVMInvalidXCATResponseDataError,
|
|
exception.ZVMXCATInternalError,
|
|
exception.ZVMDriverError) as err:
|
|
msg = _("Failed to create z/VM userid: %s") % err.format_message()
|
|
LOG.error(msg)
|
|
raise exception.ZVMXCATCreateUserIdFailed(instance=self._name,
|
|
msg=msg)
|
|
|
|
def prepare_volume_boot(self, context, instance, block_device_mapping,
|
|
root_device, volume_meta):
|
|
try:
|
|
connection_info = self._volumeop.get_root_volume_connection_info(
|
|
block_device_mapping, root_device)
|
|
(lun, wwpn, size,
|
|
fcp) = self._volumeop.extract_connection_info(context,
|
|
connection_info)
|
|
if len(wwpn) > 16:
|
|
# a wwpn list similar to:
|
|
# 5005076800aa0001;5005076800aa0002;5005076800aa0003
|
|
wwpn = wwpn.split(';')[0]
|
|
|
|
(kernel_parm_string, scpdata) = self._forge_hex_scpdata(fcp,
|
|
wwpn, lun, volume_meta)
|
|
|
|
loaddev_str = "%(wwpn)s %(lun)s 1 %(scpdata)s" % {'wwpn': wwpn,
|
|
'lun': lun, 'scpdata': scpdata}
|
|
|
|
self._volumeop.volume_boot_init(instance, fcp)
|
|
self._set_ipl(fcp)
|
|
self._set_loaddev(loaddev_str)
|
|
except (exception.ZVMXCATRequestFailed,
|
|
exception.ZVMInvalidXCATResponseDataError,
|
|
exception.ZVMXCATInternalError,
|
|
exception.ZVMVolumeError,
|
|
exception.ZVMDriverError) as err:
|
|
msg = _("Failed to prepare volume to boot") % err.format_message()
|
|
LOG.error(msg)
|
|
raise exception.ZVMVolumeError(msg=msg)
|
|
|
|
return (lun, wwpn, size, fcp)
|
|
|
|
def clean_volume_boot(self, context, instance, block_device_mapping,
|
|
root_device):
|
|
try:
|
|
connection_info = self._volumeop.get_root_volume_connection_info(
|
|
block_device_mapping, root_device)
|
|
(lun, wwpn, size,
|
|
fcp) = self._volumeop.extract_connection_info(context,
|
|
connection_info)
|
|
self._volumeop.volume_boot_cleanup(instance, fcp)
|
|
except (exception.ZVMXCATRequestFailed,
|
|
exception.ZVMInvalidXCATResponseDataError,
|
|
exception.ZVMXCATInternalError,
|
|
exception.ZVMVolumeError,
|
|
exception.ZVMDriverError) as err:
|
|
emsg = err.format_message()
|
|
msg = _LW("Failed to clean boot from volume "
|
|
"preparations: %s") % emsg
|
|
LOG.warning(msg)
|
|
raise exception.ZVMVolumeError(msg=msg)
|
|
|
|
def _forge_hex_scpdata(self, fcp, wwpn, lun, volume_meta):
|
|
"""Forge scpdata in string form and HEX form."""
|
|
root = volume_meta['root']
|
|
os_version = volume_meta['os_version']
|
|
linux_dist = self._dist_manager.get_linux_dist(os_version)()
|
|
scp_string = linux_dist.get_scp_string(root, fcp, wwpn, lun)
|
|
|
|
# Convert to HEX string
|
|
# Without Encode / Decode it will still work for python 2.6 but not for
|
|
# Python 3
|
|
try:
|
|
scp_string_ascii = scp_string.encode('ascii')
|
|
scp_string_hex = binascii.hexlify(scp_string_ascii)
|
|
scp_data = scp_string_hex.decode('ascii')
|
|
except Exception as err:
|
|
errmsg = _("Failed to forge hex scpdata: %s") % err
|
|
LOG.error(errmsg)
|
|
raise exception.ZVMDriverError(msg=errmsg)
|
|
|
|
return (scp_string, scp_data)
|
|
|
|
def _set_ipl(self, ipl_state):
|
|
body = ["--setipl %s" % ipl_state]
|
|
url = self._xcat_url.chvm('/' + self._name)
|
|
zvmutils.xcat_request("PUT", url, body)
|
|
|
|
def _set_loaddev(self, loaddev):
|
|
body = ["--setloaddev %s" % loaddev]
|
|
url = self._xcat_url.chvm('/' + self._name)
|
|
zvmutils.xcat_request("PUT", url, body)
|
|
|
|
def get_userid(self):
|
|
return zvmutils.get_userid(self._name)
|
|
|
|
def unlock_userid(self, zhcp_node):
|
|
_uid = self.get_userid()
|
|
cmd = "/opt/zhcp/bin/smcli Image_Unlock_DM -T %s" % _uid
|
|
zvmutils.xdsh(zhcp_node, cmd)
|
|
|
|
def unlock_devices(self, zhcp_node):
|
|
_uid = self.get_userid()
|
|
cmd = "/opt/zhcp/bin/smcli Image_Lock_Query_DM -T %s" % _uid
|
|
resp = zvmutils.xdsh(zhcp_node, cmd)
|
|
with zvmutils.expect_invalid_xcat_resp_data(resp):
|
|
resp_str = resp['data'][0][0]
|
|
|
|
if resp_str.__contains__("is Unlocked..."):
|
|
# unlocked automatically, do nothing
|
|
return
|
|
|
|
def _unlock_device(vdev):
|
|
cmd = ("/opt/zhcp/bin/smcli Image_Unlock_DM -T %(uid)s -v %(vdev)s"
|
|
% {'uid': _uid, 'vdev': vdev})
|
|
zvmutils.xdsh(zhcp_node, cmd)
|
|
|
|
resp_list = resp_str.split('\n')
|
|
for s in resp_list:
|
|
if s.__contains__('Device address:'):
|
|
vdev = s.rpartition(':')[2].strip()
|
|
_unlock_device(vdev)
|
|
|
|
def _delete_userid(self, url):
|
|
try:
|
|
zvmutils.xcat_request("DELETE", url)
|
|
except exception.ZVMXCATInternalError as err:
|
|
emsg = err.format_message()
|
|
LOG.debug("error emsg in delete_userid: %s", emsg)
|
|
if (emsg.__contains__("Return Code: 400") and
|
|
emsg.__contains__("Reason Code: 4")):
|
|
# zVM user definition not found, delete xCAT node directly
|
|
self.delete_xcat_node()
|
|
else:
|
|
raise
|
|
|
|
def delete_userid(self, zhcp_node, context):
|
|
"""Delete z/VM userid for the instance.This will remove xCAT node
|
|
at same time.
|
|
"""
|
|
# Versions of xCAT that do not understand the instance ID and
|
|
# request ID will silently ignore them.
|
|
url = self._xcat_url.rmvm('/' + self._name, self._instance.uuid,
|
|
context)
|
|
|
|
try:
|
|
self._delete_userid(url)
|
|
except exception.ZVMXCATInternalError as err:
|
|
emsg = err.format_message()
|
|
if (emsg.__contains__("Return Code: 400") and
|
|
emsg.__contains__("Reason Code: 12")):
|
|
# The vm was locked. Unlock before deleting
|
|
self.unlock_userid(zhcp_node)
|
|
elif (emsg.__contains__("Return Code: 408") and
|
|
emsg.__contains__("Reason Code: 12")):
|
|
# The vm device was locked. Unlock the device before deleting
|
|
self.unlock_devices(zhcp_node)
|
|
else:
|
|
LOG.debug("exception not able to handle in delete_userid "
|
|
"%s", self._name)
|
|
raise err
|
|
# delete the vm after unlock
|
|
self._delete_userid(url)
|
|
except exception.ZVMXCATRequestFailed as err:
|
|
emsg = err.format_message()
|
|
if (emsg.__contains__("Invalid nodes and/or groups") and
|
|
emsg.__contains__("Forbidden")):
|
|
# Assume neither zVM userid nor xCAT node exist in this case
|
|
return
|
|
else:
|
|
raise err
|
|
|
|
def delete_xcat_node(self):
|
|
"""Remove xCAT node for z/VM instance."""
|
|
url = self._xcat_url.rmdef('/' + self._name)
|
|
try:
|
|
zvmutils.xcat_request("DELETE", url)
|
|
except exception.ZVMXCATInternalError as err:
|
|
if err.format_message().__contains__("Could not find an object"):
|
|
# The xCAT node not exist
|
|
return
|
|
else:
|
|
raise err
|
|
|
|
def add_mdisk(self, diskpool, vdev, size, fmt=None):
|
|
"""Add a 3390 mdisk for a z/VM user.
|
|
|
|
NOTE: No read, write and multi password specified, and
|
|
access mode default as 'MR'.
|
|
|
|
"""
|
|
disk_type = CONF.zvm_diskpool_type
|
|
if (disk_type == 'ECKD'):
|
|
action = '--add3390'
|
|
elif (disk_type == 'FBA'):
|
|
action = '--add9336'
|
|
else:
|
|
errmsg = _("Disk type %s is not supported.") % disk_type
|
|
LOG.error(errmsg)
|
|
raise exception.ZVMDriverError(msg=errmsg)
|
|
|
|
if fmt:
|
|
body = [" ".join([action, diskpool, vdev, size, "MR", "''", "''",
|
|
"''", fmt])]
|
|
else:
|
|
body = [" ".join([action, diskpool, vdev, size])]
|
|
url = self._xcat_url.chvm('/' + self._name)
|
|
zvmutils.xcat_request("PUT", url, body)
|
|
|
|
def _power_state(self, method, state):
|
|
"""Invoke xCAT REST API to set/get power state for a instance."""
|
|
body = [state]
|
|
url = self._xcat_url.rpower('/' + self._name)
|
|
return zvmutils.xcat_request(method, url, body)
|
|
|
|
def _check_power_stat(self):
|
|
"""Get power status of a z/VM instance."""
|
|
LOG.debug('Query power stat of %s', self._name)
|
|
res_dict = self._power_state("GET", "stat")
|
|
|
|
@zvmutils.wrap_invalid_xcat_resp_data_error
|
|
def _get_power_string(d):
|
|
tempstr = d['info'][0][0]
|
|
return tempstr[(tempstr.find(':') + 2):].strip()
|
|
|
|
power_stat = _get_power_string(res_dict)
|
|
return zvmutils.mapping_power_stat(power_stat)
|
|
|
|
def _get_rinv_info(self, command):
|
|
"""get rinv result and return in a list."""
|
|
field = '&field=%s' % command
|
|
url = self._xcat_url.rinv('/' + self._name, field)
|
|
LOG.debug('Remote inventory of %s', self._name)
|
|
res_info = zvmutils.xcat_request("GET", url)['info']
|
|
|
|
with zvmutils.expect_invalid_xcat_resp_data(res_info):
|
|
rinv_info = res_info[0][0].split('\n')
|
|
|
|
return rinv_info
|
|
|
|
@zvmutils.wrap_invalid_xcat_resp_data_error
|
|
def _modify_storage_format(self, mem):
|
|
"""modify storage from 'G' ' M' to 'K'."""
|
|
# Only special case for 0
|
|
if mem == '0':
|
|
return 0
|
|
|
|
new_mem = 0
|
|
if mem.endswith('G'):
|
|
new_mem = int(mem[:-1]) * 1024 * 1024
|
|
elif mem.endswith('M'):
|
|
new_mem = int(mem[:-1]) * 1024
|
|
elif mem.endswith('K'):
|
|
new_mem = int(mem[:-1])
|
|
else:
|
|
exp = "ending with a 'G', 'M' or 'K'"
|
|
errmsg = _("Invalid memory format: %(invalid)s; Expected: "
|
|
"%(exp)s") % {'invalid': mem, 'exp': exp}
|
|
LOG.error(errmsg)
|
|
raise exception.ZVMInvalidXCATResponseDataError(msg=errmsg)
|
|
return new_mem
|
|
|
|
@zvmutils.wrap_invalid_xcat_resp_data_error
|
|
def _get_current_memory(self, rec_list):
|
|
"""Return the max memory can be used."""
|
|
_mem = None
|
|
|
|
for rec in rec_list:
|
|
if rec.__contains__("Total Memory: "):
|
|
tmp_list = rec.split()
|
|
_mem = tmp_list[3]
|
|
|
|
_mem = self._modify_storage_format(_mem)
|
|
return _mem
|
|
|
|
@zvmutils.wrap_invalid_xcat_resp_data_error
|
|
def _get_cpu_count(self, rec_list):
|
|
"""Return the virtual cpu count."""
|
|
_cpu_flag = False
|
|
num_cpu = 0
|
|
|
|
for rec in rec_list:
|
|
if (_cpu_flag is True):
|
|
tmp_list = rec.split()
|
|
if (len(tmp_list) > 1):
|
|
if (tmp_list[1] == "CPU"):
|
|
num_cpu += 1
|
|
else:
|
|
_cpu_flag = False
|
|
if rec.__contains__("Processors: "):
|
|
_cpu_flag = True
|
|
|
|
return num_cpu
|
|
|
|
@zvmutils.wrap_invalid_xcat_resp_data_error
|
|
def _get_cpu_used_time(self, rec_list):
|
|
"""Return the cpu used time in."""
|
|
cpu_time = 0
|
|
|
|
for rec in rec_list:
|
|
if rec.__contains__("CPU Used Time: "):
|
|
tmp_list = rec.split()
|
|
cpu_time = tmp_list[4]
|
|
|
|
return float(cpu_time)
|
|
|
|
@zvmutils.wrap_invalid_xcat_resp_data_error
|
|
def _get_guest_cpus(self, rec_list):
|
|
"""Return the processer count, used by cpumempowerstat"""
|
|
guest_cpus = 0
|
|
|
|
for rec in rec_list:
|
|
if rec.__contains__("Guest CPUs: "):
|
|
tmp_list = rec.split()
|
|
guest_cpus = tmp_list[3]
|
|
|
|
return int(guest_cpus)
|
|
|
|
@zvmutils.wrap_invalid_xcat_resp_data_error
|
|
def _get_power_stat(self, rec_list):
|
|
"""Return the power stat, used by cpumempowerstat"""
|
|
power_stat = None
|
|
|
|
for rec in rec_list:
|
|
if rec.__contains__("Power state: "):
|
|
tmp_list = rec.split()
|
|
power_stat = tmp_list[3]
|
|
|
|
return zvmutils.mapping_power_stat(power_stat)
|
|
|
|
@zvmutils.wrap_invalid_xcat_resp_data_error
|
|
def is_reachable(self):
|
|
"""Check whether IUCV connection works well."""
|
|
if zvmutils.xcat_support_iucv(self._driver._xcat_version):
|
|
LOG.debug("Check whether VM %s is reachable.", self._name)
|
|
result = self._power_state("PUT", "isreachable")
|
|
if ': reachable' in result['info'][0][0]:
|
|
|
|
return True
|
|
else:
|
|
url = self._xcat_url.nodestat('/' + self._name)
|
|
LOG.debug('Get instance status of %s', self._name)
|
|
res_dict = zvmutils.xcat_request("GET", url)
|
|
|
|
with zvmutils.expect_invalid_xcat_resp_data():
|
|
status = res_dict['node'][0][0]['data'][0]
|
|
|
|
if status is not None:
|
|
if status.__contains__('sshd'):
|
|
return True
|
|
return False
|
|
|
|
def _wait_for_reachable(self):
|
|
"""Called at an interval until the instance is reachable."""
|
|
self._reachable = False
|
|
|
|
def _check_reachable():
|
|
if not self.is_reachable():
|
|
raise exception.ZVMRetryException()
|
|
else:
|
|
self._reachable = True
|
|
|
|
zvmutils.looping_call(_check_reachable, 5, 5, 30,
|
|
CONF.zvm_reachable_timeout,
|
|
exception.ZVMRetryException)
|
|
|
|
def update_node_info(self, image_meta):
|
|
LOG.debug("Update the node info for instance %s", self._name)
|
|
|
|
image_name = ''.join(i for i in image_meta['name'] if i.isalnum())
|
|
image_id = image_meta['id']
|
|
os_type = image_meta['properties']['os_version']
|
|
os_arch = image_meta['properties']['architecture']
|
|
prov_method = image_meta['properties']['provisioning_method']
|
|
profile_name = '_'.join((image_name, image_id.replace('-', '_')))
|
|
|
|
body = ['noderes.netboot=%s' % const.HYPERVISOR_TYPE,
|
|
'nodetype.os=%s' % os_type,
|
|
'nodetype.arch=%s' % os_arch,
|
|
'nodetype.provmethod=%s' % prov_method,
|
|
'nodetype.profile=%s' % profile_name]
|
|
url = self._xcat_url.chtab('/' + self._name)
|
|
|
|
with zvmutils.except_xcat_call_failed_and_reraise(
|
|
exception.ZVMXCATUpdateNodeFailed, node=self._name):
|
|
zvmutils.xcat_request("PUT", url, body)
|
|
|
|
def update_node_info_resize(self, image_name_xcat):
|
|
LOG.debug("Update the nodetype for instance %s", self._name)
|
|
|
|
name_section = image_name_xcat.split("-")
|
|
os_type = name_section[0]
|
|
os_arch = name_section[1]
|
|
profile_name = name_section[3]
|
|
|
|
body = ['noderes.netboot=%s' % const.HYPERVISOR_TYPE,
|
|
'nodetype.os=%s' % os_type,
|
|
'nodetype.arch=%s' % os_arch,
|
|
'nodetype.provmethod=%s' % 'sysclone',
|
|
'nodetype.profile=%s' % profile_name]
|
|
|
|
url = self._xcat_url.chtab('/' + self._name)
|
|
|
|
with zvmutils.except_xcat_call_failed_and_reraise(
|
|
exception.ZVMXCATUpdateNodeFailed, node=self._name):
|
|
zvmutils.xcat_request("PUT", url, body)
|
|
|
|
def get_provmethod(self):
|
|
addp = "&col=node=%s&attribute=provmethod" % self._name
|
|
url = self._xcat_url.gettab('/nodetype', addp)
|
|
res_info = zvmutils.xcat_request("GET", url)
|
|
return res_info['data'][0][0]
|
|
|
|
def update_node_provmethod(self, provmethod):
|
|
LOG.debug("Update the nodetype for instance %s", self._name)
|
|
|
|
body = ['nodetype.provmethod=%s' % provmethod]
|
|
|
|
url = self._xcat_url.chtab('/' + self._name)
|
|
|
|
with zvmutils.except_xcat_call_failed_and_reraise(
|
|
exception.ZVMXCATUpdateNodeFailed, node=self._name):
|
|
zvmutils.xcat_request("PUT", url, body)
|
|
|
|
def update_node_def(self, hcp, userid):
|
|
"""Update xCAT node definition."""
|
|
|
|
body = ['zvm.hcp=%s' % hcp,
|
|
'zvm.userid=%s' % userid]
|
|
url = self._xcat_url.chtab('/' + self._name)
|
|
|
|
with zvmutils.except_xcat_call_failed_and_reraise(
|
|
exception.ZVMXCATUpdateNodeFailed, node=self._name):
|
|
zvmutils.xcat_request("PUT", url, body)
|
|
|
|
def deploy_node(self, image_name, transportfiles=None, vdev=None):
|
|
LOG.debug("Begin to deploy image on instance %s", self._name)
|
|
vdev = vdev or CONF.zvm_user_root_vdev
|
|
remote_host_info = zvmutils.get_host()
|
|
body = ['netboot',
|
|
'device=%s' % vdev,
|
|
'osimage=%s' % image_name]
|
|
|
|
if transportfiles:
|
|
body.append('transport=%s' % transportfiles)
|
|
body.append('remotehost=%s' % remote_host_info)
|
|
|
|
url = self._xcat_url.nodeset('/' + self._name)
|
|
|
|
with zvmutils.except_xcat_call_failed_and_reraise(
|
|
exception.ZVMXCATDeployNodeFailed, node=self._name):
|
|
zvmutils.xcat_request("PUT", url, body)
|
|
|
|
def copy_xcat_node(self, source_node_name):
|
|
"""Create xCAT node from an existing z/VM instance."""
|
|
LOG.debug("Creating xCAT node %s from existing node", self._name)
|
|
|
|
url = self._xcat_url.lsdef_node('/' + source_node_name)
|
|
res_info = zvmutils.xcat_request("GET", url)['info'][0]
|
|
|
|
body = []
|
|
for info in res_info:
|
|
if ("=" in info and ("postbootscripts" not in info) and
|
|
("postscripts" not in info) and ("hostnames" not in info)):
|
|
body.append(info.lstrip())
|
|
|
|
url = self._xcat_url.mkdef('/' + self._name)
|
|
|
|
with zvmutils.except_xcat_call_failed_and_reraise(
|
|
exception.ZVMXCATCreateNodeFailed, node=self._name):
|
|
zvmutils.xcat_request("POST", url, body)
|
|
|
|
def get_console_log(self, logsize):
|
|
"""get console log."""
|
|
url = self._xcat_url.rinv('/' + self._name, '&field=--consoleoutput'
|
|
'&field=%s') % logsize
|
|
|
|
# Because we might have logs in the console, we need ignore the warning
|
|
res_info = zvmutils.xcat_request("GET", url, ignore_warning=True)
|
|
|
|
with zvmutils.expect_invalid_xcat_resp_data(res_info):
|
|
log_data = res_info['info'][0][0]
|
|
|
|
return log_data
|
|
|
|
def collect_diagnostics(self, context, reason):
|
|
xcat_version = self._driver._xcat_version
|
|
|
|
if zvmutils.xcat_support_deployment_failure_diagnostics(xcat_version):
|
|
# Diagnostics request is only supported >= xCAT 2.3.8.16
|
|
# On older versions of xCAT, do nothing. If the request is issued
|
|
# on an older version, xCAT will treat it as an error.
|
|
url = self._xcat_url.mkdiag('/', self._name, self._instance.uuid,
|
|
context)
|
|
# Some body properties will appear to duplicate information
|
|
# carried elsewhere, for example the request ID which is a URL
|
|
# query parameter as well. This is intentional. The request body
|
|
# is considered opaque to xCAT, data there is simply passed through
|
|
# into the diagnostics blob as "upstream context". Other parts
|
|
# of the request, such as the URL query parameters, are NOT opaque
|
|
# and xCAT uses them to filter the data that is captured.
|
|
body = ['reason=%s' % reason,
|
|
'openstack_nova_instance_uuid=%s' % self._instance.uuid]
|
|
if context is not None:
|
|
try:
|
|
body.append('openstack_request_id=%s' % context.request_id)
|
|
except Exception as err:
|
|
# Cannot use format_message() in this context, because the
|
|
# Exception class does not implement that method.
|
|
msg = _("Failed to add request ID to message body: %(err)s"
|
|
) % {'err': six.text_type(err)}
|
|
LOG.error(msg)
|
|
# Continue and return the original URL once the error is
|
|
# logged. Failing the request over this is NOT desired.
|
|
try:
|
|
zvmutils.xcat_request("POST", url, body)
|
|
except (exception.ZVMXCATRequestFailed,
|
|
exception.ZVMInvalidXCATResponseDataError,
|
|
exception.ZVMXCATInternalError,
|
|
exception.ZVMDriverError) as err:
|
|
msg = _("Failed to collect deployment timeout diagnostics: %s"
|
|
) % err.format_message()
|
|
LOG.error(msg)
|
|
else:
|
|
msg = _("Skipping diagnostics collection; xCAT version %(actual)s "
|
|
"does not support that function. The first xCAT version "
|
|
"supporting that function is %(required)s."
|
|
) % {'actual': xcat_version, 'required':
|
|
const.XCAT_SUPPORT_COLLECT_DIAGNOSTICS_DEPLOYFAILED}
|
|
LOG.debug(msg)
|