Implement instance rescue
Add driver method to implement the rescue operation. Change-Id: I4ef3ec26e2ab6ae47a82004eaa6247b7ba1cc48b
This commit is contained in:
parent
a754bcc07c
commit
7359b32cb3
|
@ -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()
|
||||
|
|
|
@ -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."""
|
||||
|
|
|
@ -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
|
||||
"""
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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)
|
|
@ -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)
|
|
@ -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)
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue