PowerVM Driver: spawn/destroy #3: TaskFlow
This change set builds on I85f740999b8d085e803a39c35cc1897c0fb063ad, introducing the TaskFlow framework for spawn and destroy. It should be functionally equivalent to the aforementioned, but sets us up for the more complex TaskFlow usage in subsequent additions to the PowerVM driver implementation. Change-Id: Idfefc2db18d0f473a028b7bb8b593d39067e090d Partially-Implements: blueprint powervm-nova-compute-driver
This commit is contained in:
parent
29ef20bf8b
commit
64914fb15e
|
@ -0,0 +1,107 @@
|
|||
# Copyright 2015, 2017 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 mock
|
||||
from pypowervm import const as pvmc
|
||||
from taskflow import engines as tf_eng
|
||||
from taskflow.patterns import linear_flow as tf_lf
|
||||
from taskflow import task as tf_tsk
|
||||
|
||||
from nova import exception
|
||||
from nova import test
|
||||
from nova.virt.powervm.tasks import vm as tf_vm
|
||||
|
||||
|
||||
class TestVMTasks(test.TestCase):
|
||||
def setUp(self):
|
||||
super(TestVMTasks, self).setUp()
|
||||
self.apt = mock.Mock()
|
||||
self.instance = mock.Mock()
|
||||
|
||||
@mock.patch('pypowervm.tasks.partition.build_active_vio_feed_task',
|
||||
autospec=True)
|
||||
@mock.patch('pypowervm.tasks.storage.add_lpar_storage_scrub_tasks',
|
||||
autospec=True)
|
||||
@mock.patch('nova.virt.powervm.vm.create_lpar')
|
||||
def test_create(self, mock_vm_crt, mock_stg, mock_bld):
|
||||
lpar_entry = mock.Mock()
|
||||
|
||||
crt = tf_vm.Create(self.apt, 'host_wrapper', self.instance)
|
||||
mock_vm_crt.return_value = lpar_entry
|
||||
crt.execute()
|
||||
|
||||
mock_vm_crt.assert_called_once_with(self.apt, 'host_wrapper',
|
||||
self.instance)
|
||||
|
||||
mock_bld.assert_called_once_with(
|
||||
self.apt, name='create_scrubber',
|
||||
xag={pvmc.XAG.VIO_SMAP, pvmc.XAG.VIO_FMAP})
|
||||
mock_stg.assert_called_once_with(
|
||||
[lpar_entry.id], mock_bld.return_value, lpars_exist=True)
|
||||
mock_bld.return_value.execute.assert_called_once_with()
|
||||
|
||||
@mock.patch('nova.virt.powervm.vm.power_on')
|
||||
def test_power_on(self, mock_pwron):
|
||||
pwron = tf_vm.PowerOn(self.apt, self.instance)
|
||||
pwron.execute()
|
||||
mock_pwron.assert_called_once_with(self.apt, self.instance)
|
||||
|
||||
@mock.patch('nova.virt.powervm.vm.power_on')
|
||||
@mock.patch('nova.virt.powervm.vm.power_off')
|
||||
def test_power_on_revert(self, mock_pwroff, mock_pwron):
|
||||
flow = tf_lf.Flow('revert_power_on')
|
||||
pwron = tf_vm.PowerOn(self.apt, self.instance)
|
||||
flow.add(pwron)
|
||||
|
||||
# Dummy Task that fails, triggering flow revert
|
||||
def failure(*a, **k):
|
||||
raise ValueError()
|
||||
flow.add(tf_tsk.FunctorTask(failure))
|
||||
|
||||
# When PowerOn.execute doesn't fail, revert calls power_off
|
||||
self.assertRaises(ValueError, tf_eng.run, flow)
|
||||
mock_pwron.assert_called_once_with(self.apt, self.instance)
|
||||
mock_pwroff.assert_called_once_with(self.apt, self.instance,
|
||||
force_immediate=True)
|
||||
|
||||
mock_pwron.reset_mock()
|
||||
mock_pwroff.reset_mock()
|
||||
|
||||
# When PowerOn.execute fails, revert doesn't call power_off
|
||||
mock_pwron.side_effect = exception.NovaException()
|
||||
self.assertRaises(exception.NovaException, tf_eng.run, flow)
|
||||
mock_pwron.assert_called_once_with(self.apt, self.instance)
|
||||
mock_pwroff.assert_not_called()
|
||||
|
||||
@mock.patch('nova.virt.powervm.vm.power_off')
|
||||
def test_power_off(self, mock_pwroff):
|
||||
# Default force_immediate
|
||||
pwroff = tf_vm.PowerOff(self.apt, self.instance)
|
||||
pwroff.execute()
|
||||
mock_pwroff.assert_called_once_with(self.apt, self.instance,
|
||||
force_immediate=False)
|
||||
|
||||
mock_pwroff.reset_mock()
|
||||
|
||||
# Explicit force_immediate
|
||||
pwroff = tf_vm.PowerOff(self.apt, self.instance, force_immediate=True)
|
||||
pwroff.execute()
|
||||
mock_pwroff.assert_called_once_with(self.apt, self.instance,
|
||||
force_immediate=True)
|
||||
|
||||
@mock.patch('nova.virt.powervm.vm.delete_lpar')
|
||||
def test_delete(self, mock_dlt):
|
||||
delete = tf_vm.Delete(self.apt, self.instance)
|
||||
delete.execute()
|
||||
mock_dlt.assert_called_once_with(self.apt, self.instance)
|
|
@ -16,6 +16,7 @@ from __future__ import absolute_import
|
|||
|
||||
import fixtures
|
||||
import mock
|
||||
from pypowervm import const as pvm_const
|
||||
from pypowervm import exceptions as pvm_exc
|
||||
from pypowervm.helpers import log_helper as pvm_hlp_log
|
||||
from pypowervm.helpers import vios_busy as pvm_hlp_vbusy
|
||||
|
@ -87,12 +88,22 @@ class TestPowerVMDriver(test.NoDBTestCase):
|
|||
self.assertEqual('sys', self.drv.host_wrapper)
|
||||
|
||||
@mock.patch('nova.virt.powervm.vm.create_lpar')
|
||||
@mock.patch('pypowervm.tasks.partition.build_active_vio_feed_task',
|
||||
autospec=True)
|
||||
@mock.patch('pypowervm.tasks.storage.add_lpar_storage_scrub_tasks',
|
||||
autospec=True)
|
||||
@mock.patch('nova.virt.powervm.vm.power_on')
|
||||
def test_spawn_ops(self, mock_pwron, mock_crt_lpar):
|
||||
def test_spawn_ops(self, mock_pwron, mock_scrub, mock_ftsk, mock_crt_lpar):
|
||||
"""Validates the 'typical' spawn flow of the spawn of an instance. """
|
||||
self.drv.host_wrapper = 'sys'
|
||||
self.drv.spawn('context', self.inst, 'img_meta', 'files', 'password')
|
||||
mock_crt_lpar.assert_called_once_with(self.adp, 'sys', self.inst)
|
||||
mock_ftsk.assert_called_once_with(
|
||||
self.adp, name='create_scrubber', xag={pvm_const.XAG.VIO_SMAP,
|
||||
pvm_const.XAG.VIO_FMAP})
|
||||
mock_scrub.assert_called_once_with(
|
||||
[mock_crt_lpar.return_value.id], mock_ftsk.return_value,
|
||||
lpars_exist=True)
|
||||
mock_pwron.assert_called_once_with(self.adp, self.inst)
|
||||
|
||||
@mock.patch('nova.virt.powervm.vm.delete_lpar')
|
||||
|
|
|
@ -21,10 +21,13 @@ from pypowervm.helpers import vios_busy as vio_hlp
|
|||
from pypowervm.tasks import partition as pvm_par
|
||||
from pypowervm.wrappers import managed_system as pvm_ms
|
||||
import six
|
||||
from taskflow.patterns import linear_flow as tf_lf
|
||||
|
||||
from nova import exception as exc
|
||||
from nova.virt import driver
|
||||
from nova.virt.powervm import host
|
||||
from nova.virt.powervm.tasks import base as tf_base
|
||||
from nova.virt.powervm.tasks import vm as tf_vm
|
||||
from nova.virt.powervm import vm
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
@ -145,14 +148,17 @@ class PowerVMDriver(driver.ComputeDriver):
|
|||
attached to the instance.
|
||||
"""
|
||||
self._log_operation('spawn', instance)
|
||||
|
||||
# TODO(efried): Use TaskFlow
|
||||
vm.create_lpar(self.adapter, self.host_wrapper, instance)
|
||||
# Define the flow
|
||||
flow_spawn = tf_lf.Flow("spawn")
|
||||
flow_spawn.add(tf_vm.Create(self.adapter, self.host_wrapper, instance))
|
||||
# TODO(thorst, efried) Plug the VIFs
|
||||
# TODO(thorst, efried) Create/Connect the disk
|
||||
# TODO(thorst, efried) Add the config drive
|
||||
# Last step is to power on the system.
|
||||
vm.power_on(self.adapter, instance)
|
||||
flow_spawn.add(tf_vm.PowerOn(self.adapter, instance))
|
||||
|
||||
# Run the flow.
|
||||
tf_base.run(flow_spawn, instance=instance)
|
||||
|
||||
def destroy(self, context, instance, network_info, block_device_info=None,
|
||||
destroy_disks=True, migrate_data=None):
|
||||
|
@ -172,15 +178,27 @@ class PowerVMDriver(driver.ComputeDriver):
|
|||
"""
|
||||
# TODO(thorst, efried) Add resize checks for destroy
|
||||
self._log_operation('destroy', instance)
|
||||
try:
|
||||
# TODO(efried): Use TaskFlow
|
||||
vm.power_off(self.adapter, instance, force_immediate=destroy_disks)
|
||||
|
||||
def _setup_flow_and_run():
|
||||
# Define the flow
|
||||
flow = tf_lf.Flow("destroy")
|
||||
|
||||
# Power Off the LPAR. If its disks are about to be deleted, issue a
|
||||
# hard shutdown.
|
||||
flow.add(tf_vm.PowerOff(self.adapter, instance,
|
||||
force_immediate=destroy_disks))
|
||||
# TODO(thorst, efried) Add unplug vifs task
|
||||
# TODO(thorst, efried) Add config drive tasks
|
||||
# TODO(thorst, efried) Add volume disconnect tasks
|
||||
# TODO(thorst, efried) Add disk disconnect/destroy tasks
|
||||
# TODO(thorst, efried) Add LPAR id based scsi map clean up task
|
||||
vm.delete_lpar(self.adapter, instance)
|
||||
flow.add(tf_vm.Delete(self.adapter, instance))
|
||||
|
||||
# Build the engine & run!
|
||||
tf_base.run(flow, instance=instance)
|
||||
|
||||
try:
|
||||
_setup_flow_and_run()
|
||||
except exc.InstanceNotFound:
|
||||
LOG.debug('VM was not found during destroy operation.',
|
||||
instance=instance)
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
# Copyright 2016, 2017 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.
|
||||
from oslo_log import log as logging
|
||||
from taskflow import engines as tf_eng
|
||||
from taskflow.listeners import timing as tf_tm
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def run(flow, instance=None):
|
||||
"""Run a TaskFlow Flow with task timing and logging with instance.
|
||||
|
||||
:param flow: A taskflow.flow.Flow to run.
|
||||
:param instance: A nova instance, for logging.
|
||||
:return: The result of taskflow.engines.run(), a dictionary of named
|
||||
results of the Flow's execution.
|
||||
"""
|
||||
def log_with_instance(*args, **kwargs):
|
||||
"""Wrapper for LOG.info(*args, **kwargs, instance=instance)."""
|
||||
if instance is not None:
|
||||
kwargs['instance'] = instance
|
||||
LOG.info(*args, **kwargs)
|
||||
|
||||
eng = tf_eng.load(flow)
|
||||
with tf_tm.PrintingDurationListener(eng, printer=log_with_instance):
|
||||
return eng.run()
|
|
@ -0,0 +1,136 @@
|
|||
# Copyright 2015, 2017 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.
|
||||
|
||||
from oslo_log import log as logging
|
||||
from pypowervm import const as pvm_const
|
||||
from pypowervm import exceptions as pvm_exc
|
||||
from pypowervm.tasks import partition as pvm_tpar
|
||||
from pypowervm.tasks import storage as pvm_stg
|
||||
from taskflow import task
|
||||
from taskflow.types import failure as task_fail
|
||||
|
||||
from nova.virt.powervm import vm
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Create(task.Task):
|
||||
"""The task for creating a VM."""
|
||||
|
||||
def __init__(self, adapter, host_wrapper, instance):
|
||||
"""Creates the Task for creating a VM.
|
||||
|
||||
The revert method only needs to do something for failed rebuilds.
|
||||
Since the rebuild and build methods have different flows, it is
|
||||
necessary to clean up the destination LPAR on fails during rebuild.
|
||||
|
||||
The revert method is not implemented for build because the compute
|
||||
manager calls the driver destroy operation for spawn errors. By
|
||||
not deleting the lpar, it's a cleaner flow through the destroy
|
||||
operation and accomplishes the same result.
|
||||
|
||||
Any stale storage associated with the new VM's (possibly recycled) ID
|
||||
will be cleaned up. The cleanup work will be delegated to the FeedTask
|
||||
represented by the stg_ftsk parameter.
|
||||
|
||||
:param adapter: The adapter for the pypowervm API
|
||||
:param host_wrapper: The managed system wrapper
|
||||
:param instance: The nova instance.
|
||||
"""
|
||||
super(Create, self).__init__('crt_vm')
|
||||
self.instance = instance
|
||||
self.adapter = adapter
|
||||
self.host_wrapper = host_wrapper
|
||||
|
||||
def execute(self):
|
||||
wrap = vm.create_lpar(self.adapter, self.host_wrapper, self.instance)
|
||||
# Get rid of any stale storage and/or mappings associated with the new
|
||||
# LPAR's ID, so it doesn't accidentally have access to something it
|
||||
# oughtn't.
|
||||
ftsk = pvm_tpar.build_active_vio_feed_task(
|
||||
self.adapter, name='create_scrubber',
|
||||
xag={pvm_const.XAG.VIO_SMAP, pvm_const.XAG.VIO_FMAP})
|
||||
pvm_stg.add_lpar_storage_scrub_tasks([wrap.id], ftsk, lpars_exist=True)
|
||||
LOG.info('Scrubbing stale storage.', instance=self.instance)
|
||||
ftsk.execute()
|
||||
|
||||
|
||||
class PowerOn(task.Task):
|
||||
"""The task to power on the instance."""
|
||||
|
||||
def __init__(self, adapter, instance):
|
||||
"""Create the Task for the power on of the LPAR.
|
||||
|
||||
:param adapter: The pypowervm adapter.
|
||||
:param instance: The nova instance.
|
||||
"""
|
||||
super(PowerOn, self).__init__('pwr_vm')
|
||||
self.adapter = adapter
|
||||
self.instance = instance
|
||||
|
||||
def execute(self):
|
||||
vm.power_on(self.adapter, self.instance)
|
||||
|
||||
def revert(self, result, flow_failures):
|
||||
if isinstance(result, task_fail.Failure):
|
||||
# The power on itself failed...can't power off.
|
||||
LOG.debug('Power on failed. Not performing power off.',
|
||||
instance=self.instance)
|
||||
return
|
||||
|
||||
LOG.warning('Powering off instance.', instance=self.instance)
|
||||
try:
|
||||
vm.power_off(self.adapter, self.instance, force_immediate=True)
|
||||
except pvm_exc.Error:
|
||||
# Don't raise revert exceptions
|
||||
LOG.exception("Power-off failed during revert.",
|
||||
instance=self.instance)
|
||||
|
||||
|
||||
class PowerOff(task.Task):
|
||||
"""The task to power off a VM."""
|
||||
|
||||
def __init__(self, adapter, instance, force_immediate=False):
|
||||
"""Creates the Task to power off an LPAR.
|
||||
|
||||
:param adapter: The adapter for the pypowervm API
|
||||
:param instance: The nova instance.
|
||||
:param force_immediate: Boolean. Perform a VSP hard power off.
|
||||
"""
|
||||
super(PowerOff, self).__init__('pwr_off_vm')
|
||||
self.instance = instance
|
||||
self.adapter = adapter
|
||||
self.force_immediate = force_immediate
|
||||
|
||||
def execute(self):
|
||||
vm.power_off(self.adapter, self.instance,
|
||||
force_immediate=self.force_immediate)
|
||||
|
||||
|
||||
class Delete(task.Task):
|
||||
"""The task to delete the instance from the system."""
|
||||
|
||||
def __init__(self, adapter, instance):
|
||||
"""Create the Task to delete the VM from the system.
|
||||
|
||||
:param adapter: The adapter for the pypowervm API.
|
||||
:param instance: The nova instance.
|
||||
"""
|
||||
super(Delete, self).__init__('dlt_vm')
|
||||
self.adapter = adapter
|
||||
self.instance = instance
|
||||
|
||||
def execute(self):
|
||||
vm.delete_lpar(self.adapter, self.instance)
|
Loading…
Reference in New Issue