nova/nova/virt/powervm/driver.py

349 lines
14 KiB
Python

# vim: tabstop=4 shiftwidth=4 softtabstop=4
# 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 socket
import time
from oslo.config import cfg
from nova.compute import flavors
from nova.compute import utils as compute_utils
from nova.image import glance
from nova.openstack.common.gettextutils import _
from nova.openstack.common import log as logging
from nova.virt import driver
from nova.virt.powervm import exception
from nova.virt.powervm import operator
LOG = logging.getLogger(__name__)
powervm_opts = [
cfg.StrOpt('powervm_mgr_type',
default='ivm',
help='PowerVM manager type (ivm, hmc)'),
cfg.StrOpt('powervm_mgr',
help='PowerVM manager host or ip'),
cfg.StrOpt('powervm_mgr_user',
help='PowerVM manager user name'),
cfg.StrOpt('powervm_mgr_passwd',
help='PowerVM manager user password',
secret=True),
cfg.StrOpt('powervm_img_remote_path',
default='/home/padmin',
help='PowerVM image remote path where images will be moved.'
' Make sure this path can fit your biggest image in glance'),
cfg.StrOpt('powervm_img_local_path',
default='/tmp',
help='Local directory to download glance images to.'
' Make sure this path can fit your biggest image in glance')
]
CONF = cfg.CONF
CONF.register_opts(powervm_opts)
class PowerVMDriver(driver.ComputeDriver):
"""PowerVM Implementation of Compute Driver."""
def __init__(self, virtapi):
super(PowerVMDriver, self).__init__(virtapi)
self._powervm = operator.PowerVMOperator()
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_info(self, instance):
"""Get the current status of an instance."""
return self._powervm.get_info(instance['name'])
def get_num_instances(self):
return len(self.list_instances())
def instance_exists(self, instance_name):
return self._powervm.instance_exists(instance_name)
def list_instances(self):
return self._powervm.list_instances()
def get_host_stats(self, refresh=False):
"""Return currently known host stats."""
return self._powervm.get_host_stats(refresh=refresh)
def get_host_uptime(self, host):
"""Returns the result of calling "uptime" on the target host."""
return self._powervm.get_host_uptime(host)
def plug_vifs(self, instance, network_info):
"""Plug VIFs into networks."""
LOG.debug(_('Network injection is not supported by the '
'PowerVM driver.'), instance)
pass
def macs_for_instance(self, instance):
return self._powervm.macs_for_instance(instance)
def spawn(self, context, instance, image_meta, injected_files,
admin_password, network_info=None, block_device_info=None):
"""Create a new instance/VM/domain on powerVM."""
self._powervm.spawn(context, instance, image_meta['id'], network_info)
def destroy(self, instance, network_info, block_device_info=None,
destroy_disks=True, context=None):
"""Destroy (shutdown and delete) the specified instance."""
self._powervm.destroy(instance['name'], destroy_disks)
def reboot(self, context, instance, network_info, reboot_type,
block_device_info=None, bad_volumes_callback=None):
"""Reboot the specified instance.
: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
"""
if reboot_type == 'SOFT':
LOG.debug(_('Soft reboot is not supported for PowerVM.'),
instance=instance)
return
self.power_off(instance)
self.power_on(context, instance, network_info, block_device_info)
def get_host_ip_addr(self):
"""Retrieves the IP address of the hypervisor host."""
LOG.debug(_("In get_host_ip_addr"))
# TODO(mrodden): use operator get_hostname instead
hostname = CONF.powervm_mgr
LOG.debug(_("Attempting to resolve %s") % hostname)
ip_addr = socket.gethostbyname(hostname)
LOG.debug(_("%(hostname)s was successfully resolved to %(ip_addr)s") %
{'hostname': hostname, 'ip_addr': ip_addr})
return ip_addr
def snapshot(self, context, instance, image_id, update_task_state):
"""Snapshots the specified instance.
:param context: security context
:param instance: Instance object as returned by DB layer.
:param image_id: Reference to a pre-created image that will
hold the snapshot.
:param update_task_state: Function reference that allows for updates
to the instance task state.
"""
snapshot_start = time.time()
# get current image info
glance_service, old_image_id = glance.get_remote_image_service(
context, instance['image_ref'])
image_meta = compute_utils.get_image_metadata(
context, glance_service, old_image_id, instance)
# build updated snapshot metadata
snapshot_meta = glance_service.show(context, image_id)
new_snapshot_meta = {'is_public': False,
'name': snapshot_meta['name'],
'status': 'active',
'properties': {'image_location': 'snapshot',
'image_state': 'available',
'owner_id': instance['project_id']
},
'disk_format': image_meta['disk_format'],
'container_format': image_meta['container_format']
}
# disk capture and glance upload
self._powervm.capture_image(context, instance, image_id,
new_snapshot_meta, update_task_state)
snapshot_time = time.time() - snapshot_start
inst_name = instance['name']
LOG.info(_("%(inst_name)s captured in %(snapshot_time)s seconds"),
{'inst_name': inst_name, 'snapshot_time': snapshot_time})
def pause(self, instance):
"""Pause the specified instance."""
msg = _("pause is not supported for PowerVM")
raise NotImplementedError(msg)
def unpause(self, instance):
"""Unpause paused VM instance."""
msg = _("unpause is not supported for PowerVM")
raise NotImplementedError(msg)
def suspend(self, instance):
"""suspend the specified instance."""
raise NotImplementedError(_("Suspend is not supported by the"
"PowerVM driver."))
def resume(self, context, instance, network_info, block_device_info=None):
"""resume the specified instance."""
raise NotImplementedError(_("Resume is not supported by the"
"PowerVM driver."))
def power_off(self, instance):
"""Power off the specified instance."""
self._powervm.power_off(instance['name'])
def power_on(self, context, instance, network_info,
block_device_info=None):
"""Power on the specified instance."""
self._powervm.power_on(instance['name'])
def get_available_resource(self, nodename):
"""Retrieve resource info."""
return self._powervm.get_available_resource()
def host_power_action(self, host, action):
"""Reboots, shuts down or powers up the host."""
raise NotImplementedError(_("Host power action is not supported by the"
"PowerVM driver."))
def migrate_disk_and_power_off(self, context, instance, dest,
instance_type, network_info,
block_device_info=None):
"""Transfers the disk of a running instance in multiple phases, turning
off the instance before the end.
:returns: disk_info dictionary that is passed as the
disk_info parameter to finish_migration
on the destination nova-compute host
"""
src_host = self.get_host_ip_addr()
pvm_op = self._powervm._operator
lpar_obj = pvm_op.get_lpar(instance['name'])
vhost = pvm_op.get_vhost_by_instance_id(lpar_obj['lpar_id'])
diskname = pvm_op.get_disk_name_by_vhost(vhost)
self._powervm.power_off(instance['name'], timeout=120)
# NOTE(ldbragst) Here we need to check if the resize or migrate is
# happening on the same host. If yes, then we need to assign a temp
# mac address to the source LPAR so we don't have a conflict when
# another LPAR is booted with the same mac address as the
# original LPAR
if src_host == dest:
macs = self.macs_for_instance(instance)
temp_mac = macs.pop()
self._powervm._operator.set_lpar_mac_base_value(instance['name'],
temp_mac)
disk_info = self._powervm.migrate_disk(
diskname, src_host, dest, CONF.powervm_img_remote_path,
instance['name'])
disk_info['old_lv_size'] = pvm_op.get_logical_vol_size(diskname)
new_name = self._get_resize_name(instance['name'])
pvm_op.rename_lpar(instance['name'], new_name)
return disk_info
def _get_resize_name(self, instance_name):
"""Rename the instance to be migrated to avoid naming conflicts
:param instance_name: name of instance to be migrated
:returns: the new instance name
"""
name_tag = 'rsz_'
# if the current name would overflow with new tag
if ((len(instance_name) + len(name_tag)) > 31):
# remove enough chars for the tag to fit
num_chars = len(name_tag)
old_name = instance_name[num_chars:]
else:
old_name = instance_name
return ''.join([name_tag, old_name])
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
"""
lpar_obj = self._powervm._create_lpar_instance(instance, network_info)
instance_type = flavors.extract_flavor(instance)
new_lv_size = instance_type['root_gb']
old_lv_size = disk_info['old_lv_size']
if 'root_disk_file' in disk_info:
disk_size = max(int(new_lv_size), int(old_lv_size))
disk_size_bytes = disk_size * 1024 * 1024 * 1024
self._powervm.deploy_from_migrated_file(
lpar_obj, disk_info['root_disk_file'], disk_size_bytes,
power_on)
else:
# this shouldn't get hit unless someone forgot to handle
# a certain migration type
raise exception.PowerVMUnrecognizedRootDevice(disk_info=disk_info)
def confirm_migration(self, migration, instance, network_info):
"""Confirms a resize, destroying the source VM."""
new_name = self._get_resize_name(instance['name'])
self._powervm.destroy(new_name)
def finish_revert_migration(self, instance, network_info,
block_device_info=None, power_on=True):
"""Finish reverting a resize."""
new_name = self._get_resize_name(instance['name'])
# NOTE(ldbragst) In the case of a resize_revert on the same host
# we reassign the original mac address, replacing the temp mac
# on the old instance that will be started
# NOTE(guochbo) We can't judge if a resize_revert on the same host
# due to the instance on destination host has been destoryed.
# Original mac address is always kept in network_info, we can
# reassign the original mac address here without negative effects
# even the old instance kept the original mac address
if self.instance_exists(new_name):
original_mac = network_info[0]['address']
self._powervm._operator.set_lpar_mac_base_value(new_name,
original_mac)
# Make sure we don't have a failed same-host migration still
# hanging around
if self.instance_exists(instance['name']):
self._powervm.destroy(instance['name'])
# undo instance rename and start
self._powervm._operator.rename_lpar(new_name, instance['name'])
if power_on:
self._powervm.power_on(instance['name'])
def add_to_aggregate(self, context, aggregate, host, **kwargs):
"""Add a compute host to an aggregate."""
pass
def remove_from_aggregate(self, context, aggregate, host, **kwargs):
"""Remove a compute host from an aggregate."""
pass
def undo_aggregate_operation(self, context, op, aggregate,
host, set_error=True):
"""Undo for Resource Pools."""
pass