Implement instance rescue

Add driver method to implement the rescue operation.

Change-Id: I4ef3ec26e2ab6ae47a82004eaa6247b7ba1cc48b
This commit is contained in:
Kyle L. Henderson 2015-02-19 15:05:47 -06:00
parent a754bcc07c
commit 7359b32cb3
9 changed files with 385 additions and 296 deletions

View File

@ -63,3 +63,4 @@ class PowerVMComputeDriver(fixtures.Fixture):
self.drv = driver.PowerVMDriver(fake.FakeVirtAPI())
self._init_host()
self.drv.adapter = self.pypvm.apt
self.drv.image_api = mock.Mock()

View File

@ -110,7 +110,6 @@ class TestPowerVMDriver(test.TestCase):
inst = objects.Instance(**powervm.TEST_INSTANCE)
my_flavor = inst.get_flavor()
mock_get_flv.return_value = my_flavor
mock_crt.return_value = mock.MagicMock()
mock_cfg_drv.return_value = False
# Invoke the method.
@ -189,15 +188,15 @@ class TestPowerVMDriver(test.TestCase):
self.assertTrue(mock_dlt.called)
@mock.patch('nova_powervm.virt.powervm.vm.dlt_lpar')
@mock.patch('nova_powervm.virt.powervm.vm.power_off')
@mock.patch('nova_powervm.virt.powervm.media.ConfigDrivePowerVM.'
'dlt_vopt')
@mock.patch('nova_powervm.virt.powervm.media.ConfigDrivePowerVM.'
'_validate_vopt_vg')
@mock.patch('nova_powervm.virt.powervm.vm.get_pvm_uuid')
@mock.patch('nova.objects.flavor.Flavor.get_by_id')
@mock.patch('pypowervm.jobs.power.power_off')
def test_destroy(self, mock_pwroff, mock_get_flv,
mock_pvmuuid, mock_val_vopt, mock_dlt_vopt, mock_dlt):
def test_destroy(self, mock_get_flv, mock_pvmuuid, mock_val_vopt,
mock_dlt_vopt, mock_pwroff, mock_dlt):
"""Validates the basic PowerVM destroy."""
# Set up the mocks to the tasks.
@ -259,6 +258,31 @@ class TestPowerVMDriver(test.TestCase):
self.drv.block_dvr.extend_volume.assert_called_with(
'context', inst, dict(type='boot'), 12)
@mock.patch('nova.objects.flavor.Flavor.get_by_id')
@mock.patch('nova_powervm.virt.powervm.driver.vm')
@mock.patch('nova_powervm.virt.powervm.tasks.vm.vm')
@mock.patch('nova_powervm.virt.powervm.tasks.vm.power')
def test_rescue(self, mock_task_pwr, mock_task_vm,
mock_dvr_vm, mock_get_flv):
"""Validates the PowerVM driver rescue operation."""
# Set up the mocks to the tasks.
inst = objects.Instance(**powervm.TEST_INSTANCE)
# my_flavor = inst.get_flavor()
# mock_get_flv.return_value = my_flavor
self.drv.block_dvr = mock.Mock()
# Invoke the method.
self.drv.rescue('context', inst, mock.MagicMock(),
mock.MagicMock(), 'rescue_psswd')
self.assertTrue(mock_task_vm.power_off.called)
self.assertTrue(self.drv.block_dvr.create_volume_from_image.called)
self.assertTrue(self.drv.block_dvr.connect_volume.called)
# TODO(IBM): Power on not called until bootmode=sms is supported
# self.assertTrue(mock_task_pwr.power_on.called)
@mock.patch('nova_powervm.virt.powervm.driver.LOG')
def test_log_op(self, mock_log):
"""Validates the log_operations."""

View File

@ -69,7 +69,8 @@ class StorageAdapter(object):
"""
pass
def create_volume_from_image(self, context, instance, image, disk_size):
def create_volume_from_image(self, context, instance, image, disk_size,
image_type='boot'):
"""Creates a Volume and copies the specified image to it
:param context: nova context used to retrieve image from glance
@ -79,6 +80,7 @@ class StorageAdapter(object):
than the image, it will be ignored (as the disk
must be at least as big as the image). Must be an
int.
:param image_type: the image type. Can be 'boot', 'rescue'
:returns: dictionary with the name of the created
disk device in 'device_name' key
"""

View File

@ -168,13 +168,14 @@ class LocalStorage(blockdev.StorageAdapter):
# Return the mappings that we just removed.
return existing_maps
def create_volume_from_image(self, context, instance, image, disk_size):
def create_volume_from_image(self, context, instance, image, disk_size,
image_type='boot'):
LOG.info(_LI('Create volume.'))
# Transfer the image
chunks = self.image_api.download(context, image['id'])
stream = IterableToFileAdapter(chunks)
vol_name = self._get_disk_name('boot', instance)
vol_name = self._get_disk_name(image_type, instance)
# Disk size to API is in bytes. Input from method is in Gb
disk_bytes = disk_size * units.Gi

View File

@ -17,6 +17,7 @@
from nova.compute import task_states
from nova import context as ctx
from nova import exception
from nova import image
from nova.i18n import _LI, _
from nova.objects import flavor as flavor_obj
from nova.virt import configdrive
@ -37,8 +38,8 @@ from pypowervm.wrappers import managed_system as msentry_wrapper
from nova_powervm.virt.powervm.disk import localdisk as blk_lcl
from nova_powervm.virt.powervm import host as pvm_host
from nova_powervm.virt.powervm.tasks import destroy as tf_destroy
from nova_powervm.virt.powervm.tasks import spawn as tf_spawn
from nova_powervm.virt.powervm.tasks import storage as tf_stg
from nova_powervm.virt.powervm.tasks import vm as tf_vm
from nova_powervm.virt.powervm import vios
from nova_powervm.virt.powervm import vm
@ -67,6 +68,7 @@ class PowerVMDriver(driver.ComputeDriver):
self._get_vios_uuid()
# Initialize the disk adapter
self._get_blockdev_driver()
self.image_api = image.API()
LOG.info(_LI("The compute driver has been initialized."))
def _get_adapter(self):
@ -167,25 +169,25 @@ class PowerVMDriver(driver.ComputeDriver):
flow = lf.Flow("spawn")
# Create the LPAR
flow.add(tf_spawn.CreateVM(self.adapter, self.host_uuid, instance,
flavor))
flow.add(tf_vm.Create(self.adapter, self.host_uuid, instance,
flavor))
# Creates the boot image.
flow.add(tf_spawn.CreateVolumeForImg(self.block_dvr, context,
instance, image_meta,
block_device_info, flavor))
flow.add(tf_stg.CreateVolumeForImg(
self.block_dvr, context, instance, image_meta,
disk_size=flavor.root_gb))
# Connects up the volume to the LPAR
flow.add(tf_spawn.ConnectVol(self.block_dvr, context, instance))
flow.add(tf_stg.ConnectVol(self.block_dvr, context, instance))
# If the config drive is needed, add those steps.
if configdrive.required_by(instance):
flow.add(tf_spawn.CreateCfgDrive(self.adapter, self.host_uuid,
self.vios_uuid, instance,
injected_files, network_info,
admin_password))
flow.add(tf_spawn.ConnectCfgDrive(self.adapter, instance,
self.vios_uuid, CONF.vios_name))
flow.add(tf_stg.CreateCfgDrive(self.adapter, self.host_uuid,
self.vios_uuid, instance,
injected_files, network_info,
admin_password))
flow.add(tf_stg.ConnectCfgDrive(self.adapter, instance,
self.vios_uuid, CONF.vios_name))
# Plug the VIFs
vif_plug_info = {'instance': instance, 'network_info': network_info}
@ -195,7 +197,7 @@ class PowerVMDriver(driver.ComputeDriver):
# Last step is to power on the system.
# Note: If moving to a Graph Flow, will need to change to depend on
# the prior step.
flow.add(tf_spawn.PowerOnVM(self.adapter, self.host_uuid, instance))
flow.add(tf_vm.PowerOn(self.adapter, self.host_uuid, instance))
# Build the engine & run!
engine = taskflow.engines.load(flow)
@ -232,27 +234,26 @@ class PowerVMDriver(driver.ComputeDriver):
flow = lf.Flow("destroy")
# Power Off the LPAR
flow.add(tf_destroy.PowerOffVM(self.adapter, self.host_uuid,
pvm_inst_uuid, instance))
flow.add(tf_vm.PowerOff(self.adapter, self.host_uuid,
pvm_inst_uuid, instance))
# Delete the virtual optical
flow.add(tf_destroy.DeleteVOpt(self.adapter, self.host_uuid,
self.vios_uuid, instance,
pvm_inst_uuid))
flow.add(tf_stg.DeleteVOpt(self.adapter, self.host_uuid,
self.vios_uuid, instance,
pvm_inst_uuid))
# Detach the storage adapters
flow.add(tf_destroy.DetachStorage(self.block_dvr, context,
instance, pvm_inst_uuid))
flow.add(
tf_stg.Detach(self.block_dvr, context, instance, pvm_inst_uuid))
# Delete the storage devices
if destroy_disks:
flow.add(tf_destroy.DeleteStorage(self.block_dvr, context,
instance))
flow.add(tf_stg.Delete(self.block_dvr, context, instance))
# Last step is to delete the LPAR from the system.
# Note: If moving to a Graph Flow, will need to change to depend on
# the prior step.
flow.add(tf_destroy.DeleteVM(self.adapter, pvm_inst_uuid, instance))
flow.add(tf_vm.Delete(self.adapter, pvm_inst_uuid, instance))
# Build the engine & run!
engine = taskflow.engines.load(flow)
@ -279,6 +280,56 @@ class PowerVMDriver(driver.ComputeDriver):
self._log_operation('snapshot', instance)
# TODO(IBM): Implement snapshot
def rescue(self, context, instance, network_info, image_meta,
rescue_password):
"""Rescue the specified instance.
:param instance: nova.objects.instance.Instance
"""
self._log_operation('rescue', instance)
# We need the image size, which isn't in the system meta data
# so get the all the info.
image_meta = self.image_api.get(context, image_meta['id'])
pvm_inst_uuid = vm.get_pvm_uuid(instance)
# Define the flow
flow = lf.Flow("rescue")
# Get the LPAR Wrapper
flow.add(tf_vm.Get(self.adapter, self.host_uuid, instance))
# Power Off the LPAR
flow.add(tf_vm.PowerOff(self.adapter, self.host_uuid,
pvm_inst_uuid, instance))
# Creates the boot image.
flow.add(tf_stg.CreateVolumeForImg(
self.block_dvr, context, instance,
image_meta, image_type='rescue'))
# Connects up the volume to the LPAR
flow.add(tf_stg.ConnectVol(self.block_dvr, context, instance))
# Last step is to power on the system.
# TODO(IBM): Currently, sending the bootmode=sms options causes
# the poweron job to fail. Bypass it for now. The VM can be
# powered on manually to sms.
# flow.add(tf_vm.PowerOn(self.adapter, self.host_uuid,
# instance, pwr_opts=dict(bootmode='sms')))
# Build the engine & run!
engine = taskflow.engines.load(flow)
engine.run()
def unrescue(self, instance, network_info):
"""Unrescue the specified instance.
:param instance: nova.objects.instance.Instance
"""
# TODO(Vek): Need to pass context in for access to auth_token
raise NotImplementedError()
def power_off(self, instance, timeout=0, retry_interval=0):
"""Power off the specified instance.

View File

@ -1,156 +0,0 @@
# Copyright 2015 IBM Corp.
#
# All Rights Reserved.
#
# 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.
from nova.i18n import _LI
from pypowervm.jobs import power
from pypowervm.wrappers import logical_partition as lpar_w
from taskflow import task
from nova_powervm.virt.powervm import media
from nova_powervm.virt.powervm import vm
from oslo_log import log as logging
LOG = logging.getLogger(__name__)
class PowerOffVM(task.Task):
"""The task to power off a VM."""
def __init__(self, adapter, host_uuid, lpar_uuid, instance):
"""Creates the Task to power off an LPAR.
:param adapter: The adapter for the pypowervm API
:param host_uuid: The host UUID
:param lpar_uuid: The UUID of the lpar that has media.
:param instance: The nova instance.
"""
super(PowerOffVM, self).__init__(name='pwr_off_lpar')
self.adapter = adapter
self.host_uuid = host_uuid
self.lpar_uuid = lpar_uuid
self.instance = instance
def execute(self):
LOG.info(_LI('Powering off instance %s for deletion')
% self.instance.name)
resp = self.adapter.read(lpar_w.LPAR_ROOT, self.lpar_uuid)
lpar = lpar_w.LogicalPartition.load_from_response(resp)
power.power_off(self.adapter, lpar, self.host_uuid,
force_immediate=True)
class DeleteVOpt(task.Task):
"""The task to delete the virtual optical."""
def __init__(self, adapter, host_uuid, vios_uuid, instance, lpar_uuid):
"""Creates the Task to delete the instances virtual optical media.
:param adapter: The adapter for the pypowervm API
:param host_uuid: The host UUID of the system.
:param vios_uuid: The VIOS UUID the media is being deleted from.
:param instance: The nova instance.
:param lpar_uuid: The UUID of the lpar that has media.
"""
super(DeleteVOpt, self).__init__(name='vopt_delete')
self.adapter = adapter
self.host_uuid = host_uuid
self.vios_uuid = vios_uuid
self.instance = instance
self.lpar_uuid = lpar_uuid
def execute(self):
LOG.info(_LI('Deleting Virtual Optical Media for instance %s')
% self.instance.name)
media_builder = media.ConfigDrivePowerVM(self.adapter, self.host_uuid,
self.vios_uuid)
media_builder.dlt_vopt(self.lpar_uuid)
class DetachStorage(task.Task):
"""The task to detach the storage from the instance."""
def __init__(self, block_dvr, context, instance, lpar_uuid):
"""Creates the Task to detach the storage adapters.
Provides the stor_adpt_mappings. A list of pypowervm
VirtualSCSIMappings or VirtualFCMappings (depending on the storage
adapter).
:param block_dvr: The StorageAdapter for the VM.
:param context: The nova context.
:param instance: The nova instance.
:param lpar_uuid: The UUID of the lpar..
"""
super(DetachStorage, self).__init__(name='detach_storage',
provides='stor_adpt_mappings')
self.block_dvr = block_dvr
self.context = context
self.instance = instance
self.lpar_uuid = lpar_uuid
def execute(self):
LOG.info(_LI('Detaching disk storage adapters for instance %s')
% self.instance.name)
return self.block_dvr.disconnect_image_volume(self.context,
self.instance,
self.lpar_uuid)
class DeleteStorage(task.Task):
"""The task to delete the backing storage."""
def __init__(self, block_dvr, context, instance):
"""Creates the Task to delete the storage from the system.
Requires the stor_adpt_mappings.
:param block_dvr: The StorageAdapter for the VM.
:param context: The nova context.
:param instance: The nova instance.
"""
req = ['stor_adpt_mappings']
super(DeleteStorage, self).__init__(name='dlt_storage',
requires=req)
self.block_dvr = block_dvr
self.context = context
self.instance = instance
def execute(self, stor_adpt_mappings):
LOG.info(_LI('Deleting storage for instance %s.') % self.instance.name)
self.block_dvr.delete_volumes(self.context, self.instance,
stor_adpt_mappings)
class DeleteVM(task.Task):
"""The task to delete the instance from the system."""
def __init__(self, adapter, lpar_uuid, instance):
"""Create the Task to delete the VM from the system.
:param adapter: The adapter for the pypowervm API.
:param lpar_uuid: The VM's PowerVM UUID.
:param instance: The nova instance.
"""
super(DeleteVM, self).__init__(name='dlt_lpar')
self.adapter = adapter
self.lpar_uuid = lpar_uuid
self.instance = instance
def execute(self):
LOG.info(_LI('Deleting instance %s from system.') % self.instance.name)
vm.dlt_lpar(self.adapter, self.lpar_uuid)

View File

@ -14,10 +14,7 @@
# License for the specific language governing permissions and limitations
# under the License.
from nova.i18n import _LI
from nova.i18n import _LW
from pypowervm.jobs import power
from pypowervm.wrappers import logical_partition as pvm_lpar
from nova.i18n import _LI, _LW
from oslo_log import log as logging
from taskflow import task
@ -25,66 +22,15 @@ from taskflow.types import failure as task_fail
from nova_powervm.virt.powervm import media
from nova_powervm.virt.powervm import vios
from nova_powervm.virt.powervm import vm
LOG = logging.getLogger(__name__)
class CreateVM(task.Task):
"""The task for the Create VM step of the spawn."""
def __init__(self, adapter, host_uuid, instance, flavor):
"""Creates the Task for the Create VM step of the spawn.
Provides the 'lpar_crt_response' for other tasks.
:param adapter: The adapter for the pypowervm API
:param host_uuid: The host UUID
:param instance: The nova instance.
:param flavor: The nova flavor.
"""
super(CreateVM, self).__init__(name='crt_lpar',
provides='lpar_crt_resp')
self.adapter = adapter
self.host_uuid = host_uuid
self.instance = instance
self.flavor = flavor
def execute(self):
LOG.info(_LI('Creating instance: %s') % self.instance.name)
resp = vm.crt_lpar(self.adapter, self.host_uuid, self.instance,
self.flavor)
return resp
def revert(self, result, flow_failures):
# The parameters have to match the execute method, plus the response +
# failures even if only a subset are used.
LOG.warn(_LW('Instance %s to be undefined off host') %
self.instance.name)
if isinstance(result, task_fail.Failure):
# No response, nothing to do
LOG.info(_LI('Create failed. No delete of LPAR needed for '
'instance %s') % self.instance.name)
return
if result is None or result.entry is None:
# No response, nothing to do
LOG.info(_LI('Instance %s not found on host. No update needed.') %
self.instance.name)
return
# Wrap to the actual delete.
lpar = pvm_lpar.LogicalPartition(result.entry)
vm.dlt_lpar(self.adapter, lpar.uuid)
LOG.info(_LI('Instance %s removed from system') % self.instance.name)
class CreateVolumeForImg(task.Task):
"""The Task to create the volume from an Image in the storage."""
def __init__(self, block_dvr, context, instance, image_meta,
block_device_info, flavor):
disk_size=0, image_type='boot'):
"""Create the Task.
Provides the 'vol_dev_info' for other tasks. Comes from the block_dvr
@ -94,9 +40,9 @@ class CreateVolumeForImg(task.Task):
:param context: The context passed into the spawn method.
:param instance: The nova instance.
:param image_meta: The image metadata.
:param block_device_info: Information about block devices to be
attached to the instance.
:param flavor: The flavor for the instance to be spawned.
:param disk_size: The size of volume to create. If the size is
smaller than the image, the image size will be used.
:param image_type: The image type, for example 'boot', 'rescue'
"""
super(CreateVolumeForImg, self).__init__(name='crt_vol_from_img',
provides='vol_dev_info')
@ -104,15 +50,14 @@ class CreateVolumeForImg(task.Task):
self.context = context
self.instance = instance
self.image_meta = image_meta
self.block_device_info = block_device_info
self.flavor = flavor
self.disk_size = disk_size
self.image_type = image_type
def execute(self):
LOG.info(_LI('Creating disk for instance: %s') % self.instance.name)
return self.block_dvr.create_volume_from_image(self.context,
self.instance,
self.image_meta,
self.flavor.root_gb)
return self.block_dvr.create_volume_from_image(
self.context, self.instance, self.image_meta, self.disk_size,
image_type=self.image_type)
def revert(self, result, flow_failures):
# The parameters have to match the execute method, plus the response +
@ -132,8 +77,7 @@ class ConnectVol(task.Task):
def __init__(self, block_dvr, context, instance):
"""Create the Task for the connect volume to instance method.
Requires LPAR info through requirement of lpar_crt_resp (provided by
tf_crt_lpar).
Requires LPAR info through requirement of lpar_wrap.
Requires volume info through requirement of vol_dev_info (provided by
tf_crt_vol_from_img)
@ -143,18 +87,17 @@ class ConnectVol(task.Task):
:param instance: The nova instance.
"""
super(ConnectVol, self).__init__(name='connect_vol',
requires=['lpar_crt_resp',
requires=['lpar_wrap',
'vol_dev_info'])
self.block_dvr = block_dvr
self.context = context
self.instance = instance
def execute(self, lpar_crt_resp, vol_dev_info):
LOG.info(_LI('Connecting boot disk to instance: %s') %
def execute(self, lpar_wrap, vol_dev_info):
LOG.info(_LI('Connecting disk to instance: %s') %
self.instance.name)
lpar = pvm_lpar.LogicalPartition(lpar_crt_resp.entry)
self.block_dvr.connect_volume(self.context, self.instance,
vol_dev_info, lpar.uuid)
vol_dev_info, lpar_wrap.uuid)
class CreateCfgDrive(task.Task):
@ -164,7 +107,7 @@ class CreateCfgDrive(task.Task):
network_info, admin_pass):
"""Create the Task that creates the config drive.
Requires the 'lpar_crt_resp'.
Requires the 'lpar_wrap'.
Provides the 'cfg_drv_vscsi_map' which is an element to later map
the vscsi drive.
@ -178,7 +121,7 @@ class CreateCfgDrive(task.Task):
:param admin_pass: Optional password to inject for the VM.
"""
super(CreateCfgDrive, self).__init__(name='cfg_drive',
requires=['lpar_crt_resp'],
requires=['lpar_wrap'],
provides='cfg_drv_vscsi_map')
self.adapter = adapter
self.host_uuid = host_uuid
@ -188,16 +131,15 @@ class CreateCfgDrive(task.Task):
self.network_info = network_info
self.ad_pass = admin_pass
def execute(self, lpar_crt_resp):
def execute(self, lpar_wrap):
LOG.info(_LI('Creating Config Drive for instance: %s') %
self.instance.name)
lpar = pvm_lpar.LogicalPartition(lpar_crt_resp.entry)
media_builder = media.ConfigDrivePowerVM(self.adapter, self.host_uuid,
self.vios_uuid)
vscsi_map = media_builder.create_cfg_drv_vopt(self.instance,
self.injected_files,
self.network_info,
lpar.uuid,
lpar_wrap.uuid,
admin_pass=self.ad_pass)
return vscsi_map
@ -230,40 +172,82 @@ class ConnectCfgDrive(task.Task):
cfg_drv_vscsi_map._element)
class PowerOnVM(task.Task):
"""The task to power on the instance."""
class DeleteVOpt(task.Task):
"""The task to delete the virtual optical."""
def __init__(self, adapter, host_uuid, instance):
"""Create the Task for the power on of the LPAR.
def __init__(self, adapter, host_uuid, vios_uuid, instance, lpar_uuid):
"""Creates the Task to delete the instances virtual optical media.
Obtains LPAR info through requirement of lpar_crt_resp (provided by
tf_crt_lpar).
:param adapter: The pypowervm adapter.
:param host_uuid: The host UUID.
:param adapter: The adapter for the pypowervm API
:param host_uuid: The host UUID of the system.
:param vios_uuid: The VIOS UUID the media is being deleted from.
:param instance: The nova instance.
:param lpar_uuid: The UUID of the lpar that has media.
"""
super(PowerOnVM, self).__init__(name='pwr_lpar',
requires=['lpar_crt_resp'])
super(DeleteVOpt, self).__init__(name='vopt_delete')
self.adapter = adapter
self.host_uuid = host_uuid
self.vios_uuid = vios_uuid
self.instance = instance
self.lpar_uuid = lpar_uuid
def execute(self):
LOG.info(_LI('Deleting Virtual Optical Media for instance %s')
% self.instance.name)
media_builder = media.ConfigDrivePowerVM(self.adapter, self.host_uuid,
self.vios_uuid)
media_builder.dlt_vopt(self.lpar_uuid)
class Detach(task.Task):
"""The task to detach the storage from the instance."""
def __init__(self, block_dvr, context, instance, lpar_uuid):
"""Creates the Task to detach the storage adapters.
Provides the stor_adpt_mappings. A list of pypowervm
VirtualSCSIMappings or VirtualFCMappings (depending on the storage
adapter).
:param block_dvr: The StorageAdapter for the VM.
:param context: The nova context.
:param instance: The nova instance.
:param lpar_uuid: The UUID of the lpar..
"""
super(Detach, self).__init__(name='detach_storage',
provides='stor_adpt_mappings')
self.block_dvr = block_dvr
self.context = context
self.instance = instance
self.lpar_uuid = lpar_uuid
def execute(self):
LOG.info(_LI('Detaching disk storage adapters for instance %s')
% self.instance.name)
return self.block_dvr.disconnect_image_volume(self.context,
self.instance,
self.lpar_uuid)
class Delete(task.Task):
"""The task to delete the backing storage."""
def __init__(self, block_dvr, context, instance):
"""Creates the Task to delete the storage from the system.
Requires the stor_adpt_mappings.
:param block_dvr: The StorageAdapter for the VM.
:param context: The nova context.
:param instance: The nova instance.
"""
req = ['stor_adpt_mappings']
super(Delete, self).__init__(name='dlt_storage', requires=req)
self.block_dvr = block_dvr
self.context = context
self.instance = instance
def execute(self, lpar_crt_resp):
LOG.info(_LI('Powering on instance: %s') % self.instance.name)
power.power_on(self.adapter,
pvm_lpar.LogicalPartition(lpar_crt_resp.entry),
self.host_uuid)
def revert(self, lpar_crt_resp, result, flow_failures):
LOG.info(_LI('Powering off instance: %s') % self.instance.name)
if isinstance(result, task_fail.Failure):
# The power on itself failed...can't power off.
LOG.debug('Power on failed. Not performing power off.')
return
power.power_off(self.adapter,
pvm_lpar.LogicalPartition(lpar_crt_resp.entry),
self.host_uuid,
force_immediate=True)
def execute(self, stor_adpt_mappings):
LOG.info(_LI('Deleting storage for instance %s.') % self.instance.name)
self.block_dvr.delete_volumes(self.context, self.instance,
stor_adpt_mappings)

View File

@ -0,0 +1,182 @@
# Copyright 2015 IBM Corp.
#
# All Rights Reserved.
#
# 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.
from nova.i18n import _LI
from nova.i18n import _LW
from pypowervm.jobs import power
from pypowervm.wrappers import logical_partition as pvm_lpar
from oslo_log import log as logging
from taskflow import task
from taskflow.types import failure as task_fail
from nova_powervm.virt.powervm import vm
LOG = logging.getLogger(__name__)
class Get(task.Task):
"""The task for getting a VM entry."""
def __init__(self, adapter, host_uuid, instance):
"""Creates the Task for getting a VM entry.
Provides the 'lpar_wrap' for other tasks.
:param adapter: The adapter for the pypowervm API
:param host_uuid: The host UUID
:param instance: The nova instance.
"""
super(Get, self).__init__(name='get_lpar', provides='lpar_wrap')
self.adapter = adapter
self.host_uuid = host_uuid
self.instance = instance
def execute(self):
lpar_wrap = vm.get_instance_wrapper(
self.adapter, self.instance, self.host_uuid)
return lpar_wrap
class Create(task.Task):
"""The task for creating a VM."""
def __init__(self, adapter, host_uuid, instance, flavor):
"""Creates the Task for creating a VM.
Provides the 'lpar_wrap' for other tasks.
:param adapter: The adapter for the pypowervm API
:param host_uuid: The host UUID
:param instance: The nova instance.
:param flavor: The nova flavor.
"""
super(Create, self).__init__(name='crt_lpar',
provides='lpar_wrap')
self.adapter = adapter
self.host_uuid = host_uuid
self.instance = instance
self.flavor = flavor
def execute(self):
LOG.info(_LI('Creating instance: %s') % self.instance.name)
resp = vm.crt_lpar(self.adapter, self.host_uuid, self.instance,
self.flavor)
return pvm_lpar.LogicalPartition.load_from_response(resp)
def revert(self, result, flow_failures):
# The parameters have to match the execute method, plus the response +
# failures even if only a subset are used.
LOG.warn(_LW('Instance %s to be undefined off host') %
self.instance.name)
if isinstance(result, task_fail.Failure):
# No response, nothing to do
LOG.info(_LI('Create failed. No delete of LPAR needed for '
'instance %s') % self.instance.name)
return
if result is None:
# No response, nothing to do
LOG.info(_LI('Instance %s not found on host. No update needed.') %
self.instance.name)
return
# The result is a lpar wrapper.
lpar = result
vm.dlt_lpar(self.adapter, lpar.uuid)
LOG.info(_LI('Instance %s removed from system') % self.instance.name)
class PowerOn(task.Task):
"""The task to power on the instance."""
def __init__(self, adapter, host_uuid, instance, pwr_opts=None):
"""Create the Task for the power on of the LPAR.
Obtains LPAR info through requirement of lpar_wrap (provided by
tf_crt_lpar).
:param adapter: The pypowervm adapter.
:param host_uuid: The host UUID.
:param instance: The nova instance.
"""
super(PowerOn, self).__init__(name='pwr_lpar',
requires=['lpar_wrap'])
self.adapter = adapter
self.host_uuid = host_uuid
self.instance = instance
self.pwr_opts = pwr_opts
def execute(self, lpar_wrap):
LOG.info(_LI('Powering on instance: %s') % self.instance.name)
power.power_on(self.adapter, lpar_wrap,
self.host_uuid, add_parms=self.pwr_opts)
def revert(self, lpar_wrap, result, flow_failures):
LOG.info(_LI('Powering off instance: %s') % self.instance.name)
if isinstance(result, task_fail.Failure):
# The power on itself failed...can't power off.
LOG.debug('Power on failed. Not performing power off.')
return
power.power_off(self.adapter, lpar_wrap, self.host_uuid,
force_immediate=True)
class PowerOff(task.Task):
"""The task to power off a VM."""
def __init__(self, adapter, host_uuid, lpar_uuid, instance):
"""Creates the Task to power off an LPAR.
:param adapter: The adapter for the pypowervm API
:param host_uuid: The host UUID
:param lpar_uuid: The UUID of the lpar that has media.
:param instance: The nova instance.
"""
super(PowerOff, self).__init__(name='pwr_off_lpar')
self.adapter = adapter
self.host_uuid = host_uuid
self.lpar_uuid = lpar_uuid
self.instance = instance
def execute(self):
LOG.info(_LI('Powering off instance %s.')
% self.instance.name)
vm.power_off(self.adapter, self.instance, self.host_uuid,
add_parms=dict(immediate='true'))
class Delete(task.Task):
"""The task to delete the instance from the system."""
def __init__(self, adapter, lpar_uuid, instance):
"""Create the Task to delete the VM from the system.
:param adapter: The adapter for the pypowervm API.
:param lpar_uuid: The VM's PowerVM UUID.
:param instance: The nova instance.
"""
super(Delete, self).__init__(name='dlt_lpar')
self.adapter = adapter
self.lpar_uuid = lpar_uuid
self.instance = instance
def execute(self):
LOG.info(_LI('Deleting instance %s from system.') % self.instance.name)
vm.dlt_lpar(self.adapter, self.lpar_uuid)

View File

@ -325,14 +325,14 @@ def power_on(adapter, instance, host_uuid, entry=None):
return False
def power_off(adapter, instance, host_uuid, entry=None):
def power_off(adapter, instance, host_uuid, entry=None, add_parms=None):
if entry is None:
entry = get_instance_wrapper(adapter, instance, host_uuid)
# Get the current state and see if we can stop the VM
if entry.state in POWERVM_STOPABLE_STATE:
# Now stop the lpar
power.power_off(adapter, entry, host_uuid)
power.power_off(adapter, entry, host_uuid, add_parms=add_parms)
return True
return False