349 lines
14 KiB
Python
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
|