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