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

404 lines
16 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 datetime
import eventlet
import six
import time
from nova import exception as nova_exception
from nova.i18n import _
from nova.image import api as image_api
from nova.objects import fields as obj_fields
from nova.virt import driver
from nova.virt import hardware
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 zvmsdk import api as sdkapi
from zvmsdk import exception as sdkexception
from nova_zvm.virt.zvm import conf
from nova_zvm.virt.zvm import const
from nova_zvm.virt.zvm import exception
from nova_zvm.virt.zvm import utils as zvmutils
LOG = logging.getLogger(__name__)
CONF = conf.CONF
CONF.import_opt('default_ephemeral_format', 'nova.conf')
CONF.import_opt('host', 'nova.conf')
CONF.import_opt('my_ip', 'nova.conf')
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._sdk_api = sdkapi.SDKAPI()
self._vmutils = zvmutils.VMUtils()
self._image_api = image_api.API()
self._pathutils = zvmutils.PathUtils()
self._imageutils = zvmutils.ImageUtils()
self._networkutils = zvmutils.NetworkUtils()
self._imageop_semaphore = eventlet.semaphore.Semaphore(1)
# incremental sleep interval list
_inc_slp = [5, 10, 20, 30, 60]
_slp = 5
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 = _("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)
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.
"""
pass
def _get_instance_info(self, instance):
power_stat = self._sdk_api.guest_get_power_state(instance['name'])
power_stat = zvmutils.mapping_power_stat(power_stat)
_instance_info = hardware.InstanceInfo(power_stat)
return _instance_info
def get_info(self, instance):
"""Get the current status of an instance."""
inst_name = instance['name']
try:
return self._get_instance_info(instance)
except sdkexception.ZVMVirtualMachineNotExist:
LOG.warning(_("z/VM instance %s does not exist") % inst_name,
instance=instance)
raise nova_exception.InstanceNotFound(instance_id=inst_name)
except Exception as err:
# TODO(YDY): raise nova_exception.InstanceNotFound
LOG.warning(_("Failed to get the info of z/VM instance %s") %
inst_name, instance=instance)
raise err
def list_instances(self):
"""Return the names of all the instances known to the virtualization
layer, as a list.
"""
return self._sdk_api.guest_list()
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, allocations, network_info=None,
block_device_info=None, flavor=None):
LOG.info(_("Spawning new instance %s on zVM hypervisor") %
instance['name'], instance=instance)
# For zVM instance, limit the maximum length of instance name to \ 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)
try:
spawn_start = time.time()
os_distro = image_meta.properties.os_distro
# TODO(YaLian) will remove network files from this
transportfiles = self._vmutils.generate_configdrive(
context, instance, os_distro, network_info,
injected_files, admin_password)
with self._imageop_semaphore:
spawn_image_exist = self._sdk_api.image_query(
image_meta.id)
if not spawn_image_exist:
self._imageutils.import_spawn_image(
context, image_meta.id, os_distro)
spawn_image_name = self._sdk_api.image_query(
image_meta.id)[0]
if instance['root_gb'] == 0:
root_disk_size = self._sdk_api.image_get_root_disk_size(
spawn_image_name)
else:
root_disk_size = '%ig' % instance['root_gb']
disk_list = []
root_disk = {'size': root_disk_size,
'is_boot_disk': True
}
disk_list.append(root_disk)
ephemeral_disks_info = block_device_info.get('ephemerals', [])
eph_list = []
for eph in ephemeral_disks_info:
eph_dict = {'size': '%ig' % eph['size'],
'format': (eph['guest_format'] or
CONF.default_ephemeral_format or
const.DEFAULT_EPH_DISK_FMT)}
eph_list.append(eph_dict)
if eph_list:
disk_list.extend(eph_list)
self._sdk_api.guest_create(instance['name'], instance['vcpus'],
instance['memory_mb'], disk_list)
# Setup network for z/VM instance
self._setup_network(instance['name'], network_info)
self._sdk_api.guest_deploy(instance['name'], spawn_image_name,
transportfiles, zvmutils.get_host())
# Handle ephemeral disks
if eph_list:
self._sdk_api.guest_config_minidisks(instance['name'],
eph_list)
self._wait_network_ready(instance['name'], instance)
self._sdk_api.guest_start(instance['name'])
spawn_time = time.time() - spawn_start
LOG.info(_("Instance spawned succeeded in %s seconds") %
spawn_time, instance=instance)
except Exception as err:
with excutils.save_and_reraise_exception():
LOG.error(_("Deploy image to instance %(instance)s "
"failed with reason: %(err)s") %
{'instance': instance['name'], 'err': err},
instance=instance)
self.destroy(context, instance, network_info,
block_device_info)
def _setup_network(self, vm_name, network_info):
LOG.debug("Creating NICs for vm %s", vm_name)
manage_IP_set = False
for vif in network_info:
if not manage_IP_set:
network = vif['network']
ip_addr = network['subnets'][0]['ips'][0]['address']
self._sdk_api.guest_create_nic(vm_name, nic_id=vif['id'],
mac_addr=vif['address'],
ip_addr=ip_addr)
manage_IP_set = True
else:
self._sdk_api.guest_create_nic(vm_name, nic_id=vif['id'],
mac_addr=vif['address'])
def _wait_network_ready(self, inst_name, instance):
"""Wait until neutron zvm-agent add all NICs to vm"""
def _wait_for_nics_add_in_vm(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._sdk_api.guest_get_nic_vswitch_info(
inst_name)
if switch_dict and '' not in switch_dict.values():
for key in switch_dict:
result = self._sdk_api.guest_get_definition_info(
inst_name, nic_coupled=key)
if not result['nic_coupled']:
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(_('encounter error %s during get vswitch info'),
e.format_message(), instance=instance)
return
# Enter here means all NIC granted
LOG.info(_("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(_("Wait neturon-zvm-agent to add NICs to %s user direct."),
inst_name, instance=instance)
timer = loopingcall.FixedIntervalLoopingCall(
_wait_for_nics_add_in_vm, inst_name, expiration)
timer.start(interval=10).wait()
@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):
inst_name = instance['name']
if self._instance_exists(inst_name):
LOG.info(_("Destroying instance %s"), inst_name,
instance=instance)
self._sdk_api.guest_delete(inst_name)
else:
LOG.warning(_('Instance %s does not exist'), inst_name,
instance=instance)
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."""
pass
def detach_volume(self, connection_info, instance, mountpoint=None,
encryption=None):
"""Detach the disk attached to the instance."""
pass
def power_off(self, instance, timeout=0, retry_interval=0):
"""Power off the specified instance."""
inst_name = instance['name']
if self._instance_exists(inst_name):
LOG.info(_("Powering OFF instance %s"), inst_name,
instance=instance)
self._sdk_api.guest_stop(inst_name, timeout, retry_interval)
else:
LOG.warning(_('Instance %s does not exist'), inst_name,
instance=instance)
def power_on(self, context, instance, network_info,
block_device_info=None):
"""Power on the specified instance."""
inst_name = instance['name']
if self._instance_exists(inst_name):
LOG.info(_("Powering ON instance %s"), inst_name,
instance=instance)
self._sdk_api.guest_start(inst_name)
else:
LOG.warning(_('Instance %s does not exist'), inst_name,
instance=instance)
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.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 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.host)
caps = []
info = self._sdk_api.host_get_info()
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,
obj_fields.VMMode.HVM)]
data['ipl_time'] = info['ipl_time']
caps.append(data)
return caps
def get_console_output(self, context, instance):
return self._sdk_api.guest_get_console_output(instance.name)
def get_host_uptime(self):
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)]