PowerVM Driver: Network interface attach/detach

This adds the ability to hotplug network interfaces for the powervm
virt driver.

Blueprint: powervm-network-hotplug
Change-Id: I78b94c9731c35e3291d46b9bf9f5554e21c2429e
This commit is contained in:
esberglu 2018-02-21 17:33:14 -06:00 committed by Matthew Edmonds
parent 1f44fde35e
commit ed525cc403
6 changed files with 158 additions and 6 deletions

View File

@ -208,7 +208,7 @@ driver-notes-hyperv=Works without issue if instance is off. When
driver-impl-ironic=complete
driver-impl-libvirt-vz-vm=complete
driver-impl-libvirt-vz-ct=complete
driver-impl-powervm=missing
driver-impl-powervm=complete
[operation.attach-tagged-interface]
title=Attach tagged virtual network interface to instance
@ -252,7 +252,7 @@ driver-notes-hyperv=Works without issue if instance is off. When
driver-impl-ironic=complete
driver-impl-libvirt-vz-vm=complete
driver-impl-libvirt-vz-ct=complete
driver-impl-powervm=missing
driver-impl-powervm=complete
[operation.maintenance-mode]
title=Set the host in a maintenance mode

View File

@ -1,4 +1,4 @@
# Copyright 2015, 2017 IBM Corp.
# Copyright 2015, 2018 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
@ -28,6 +28,17 @@ class TestVMTasks(test.NoDBTestCase):
self.apt = mock.Mock()
self.instance = mock.Mock()
@mock.patch('nova.virt.powervm.vm.get_instance_wrapper', autospec=True)
def test_get(self, mock_get_wrap):
get = tf_vm.Get(self.apt, self.instance)
get.execute()
mock_get_wrap.assert_called_once_with(self.apt, self.instance)
# Validate args on taskflow.task.Task instantiation
with mock.patch('taskflow.task.Task.__init__') as tf:
tf_vm.Get(self.apt, self.instance)
tf.assert_called_once_with(name='get_vm', provides='lpar_wrap')
@mock.patch('pypowervm.tasks.storage.add_lpar_storage_scrub_tasks',
autospec=True)
@mock.patch('nova.virt.powervm.vm.create_lpar')

View File

@ -22,6 +22,7 @@ from pypowervm.helpers import log_helper as pvm_hlp_log
from pypowervm.helpers import vios_busy as pvm_hlp_vbusy
from pypowervm.utils import transaction as pvm_tx
from pypowervm.wrappers import virtual_io_server as pvm_vios
import six
from nova import exception
from nova import test
@ -61,7 +62,7 @@ class TestPowerVMDriver(test.NoDBTestCase):
self.assertFalse(self.drv.capabilities['supports_recreate'])
self.assertFalse(
self.drv.capabilities['supports_migrate_to_same_host'])
self.assertFalse(self.drv.capabilities['supports_attach_interface'])
self.assertTrue(self.drv.capabilities['supports_attach_interface'])
self.assertFalse(self.drv.capabilities['supports_device_tagging'])
self.assertFalse(
self.drv.capabilities['supports_tagged_attach_interface'])
@ -316,6 +317,65 @@ class TestPowerVMDriver(test.NoDBTestCase):
self.drv.reboot('context', inst, 'network_info', 'HARD')
mock_reboot.assert_called_once_with(self.adp, inst, True)
@mock.patch('nova.virt.powervm.driver.PowerVMDriver.plug_vifs')
def test_attach_interface(self, mock_plug_vifs):
self.drv.attach_interface('context', 'inst', 'image_meta', 'vif')
mock_plug_vifs.assert_called_once_with('inst', ['vif'])
@mock.patch('nova.virt.powervm.driver.PowerVMDriver.unplug_vifs')
def test_detach_interface(self, mock_unplug_vifs):
self.drv.detach_interface('context', 'inst', 'vif')
mock_unplug_vifs.assert_called_once_with('inst', ['vif'])
@mock.patch('nova.virt.powervm.tasks.vm.Get', autospec=True)
@mock.patch('nova.virt.powervm.tasks.base.run', autospec=True)
@mock.patch('nova.virt.powervm.tasks.network.PlugVifs', autospec=True)
@mock.patch('taskflow.patterns.linear_flow.Flow', autospec=True)
def test_plug_vifs(self, mock_tf, mock_plug_vifs, mock_tf_run, mock_get):
# Successful plug
mock_inst = mock.Mock()
self.drv.plug_vifs(mock_inst, 'net_info')
mock_get.assert_called_once_with(self.adp, mock_inst)
mock_plug_vifs.assert_called_once_with(
self.drv.virtapi, self.adp, mock_inst, 'net_info')
add_calls = [mock.call(mock_get.return_value),
mock.call(mock_plug_vifs.return_value)]
mock_tf.return_value.add.assert_has_calls(add_calls)
mock_tf_run.assert_called_once_with(
mock_tf.return_value, instance=mock_inst)
# InstanceNotFound and generic exception both raise
mock_tf_run.side_effect = exception.InstanceNotFound('id')
exc = self.assertRaises(exception.VirtualInterfacePlugException,
self.drv.plug_vifs, mock_inst, 'net_info')
self.assertIn('instance', six.text_type(exc))
mock_tf_run.side_effect = Exception
exc = self.assertRaises(exception.VirtualInterfacePlugException,
self.drv.plug_vifs, mock_inst, 'net_info')
self.assertIn('unexpected', six.text_type(exc))
@mock.patch('nova.virt.powervm.tasks.base.run', autospec=True)
@mock.patch('nova.virt.powervm.tasks.network.UnplugVifs', autospec=True)
@mock.patch('taskflow.patterns.linear_flow.Flow', autospec=True)
def test_unplug_vifs(self, mock_tf, mock_unplug_vifs, mock_tf_run):
# Successful unplug
mock_inst = mock.Mock()
self.drv.unplug_vifs(mock_inst, 'net_info')
mock_unplug_vifs.assert_called_once_with(self.adp, mock_inst,
'net_info')
mock_tf.return_value.add.assert_called_once_with(
mock_unplug_vifs.return_value)
mock_tf_run.assert_called_once_with(mock_tf.return_value, mock_inst)
# InstanceNotFound should pass
mock_tf_run.side_effect = exception.InstanceNotFound(instance_id='1')
self.drv.unplug_vifs(mock_inst, 'net_info')
# Raise InterfaceDetachFailed otherwise
mock_tf_run.side_effect = Exception
self.assertRaises(exception.InterfaceDetachFailed,
self.drv.unplug_vifs, mock_inst, 'net_info')
@mock.patch('pypowervm.tasks.vterm.open_remotable_vnc_vterm',
autospec=True)
@mock.patch('nova.virt.powervm.vm.get_pvm_uuid',

View File

@ -30,6 +30,7 @@ from taskflow.patterns import linear_flow as tf_lf
from nova import conf as cfg
from nova.console import type as console_type
from nova import exception as exc
from nova.i18n import _
from nova import image
from nova.virt import configdrive
from nova.virt import driver
@ -58,7 +59,7 @@ class PowerVMDriver(driver.ComputeDriver):
'has_imagecache': False,
'supports_recreate': False,
'supports_migrate_to_same_host': False,
'supports_attach_interface': False,
'supports_attach_interface': True,
'supports_device_tagging': False,
'supports_tagged_attach_interface': False,
'supports_tagged_attach_volume': False,
@ -341,6 +342,61 @@ class PowerVMDriver(driver.ComputeDriver):
# pypowervm exceptions are sufficient to indicate real failure.
# Otherwise, pypowervm thinks the instance is up.
def attach_interface(self, context, instance, image_meta, vif):
"""Attach an interface to the instance."""
self.plug_vifs(instance, [vif])
def detach_interface(self, context, instance, vif):
"""Detach an interface from the instance."""
self.unplug_vifs(instance, [vif])
def plug_vifs(self, instance, network_info):
"""Plug VIFs into networks."""
self._log_operation('plug_vifs', instance)
# Define the flow
flow = tf_lf.Flow("plug_vifs")
# Get the LPAR Wrapper
flow.add(tf_vm.Get(self.adapter, instance))
# Run the attach
flow.add(tf_net.PlugVifs(self.virtapi, self.adapter, instance,
network_info))
# Run the flow
try:
tf_base.run(flow, instance=instance)
except exc.InstanceNotFound:
raise exc.VirtualInterfacePlugException(
_("Plug vif failed because instance %s was not found.")
% instance.name)
except Exception:
LOG.exception("PowerVM error plugging vifs.", instance=instance)
raise exc.VirtualInterfacePlugException(
_("Plug vif failed because of an unexpected error."))
def unplug_vifs(self, instance, network_info):
"""Unplug VIFs from networks."""
self._log_operation('unplug_vifs', instance)
# Define the flow
flow = tf_lf.Flow("unplug_vifs")
# Run the detach
flow.add(tf_net.UnplugVifs(self.adapter, instance, network_info))
# Run the flow
try:
tf_base.run(flow, instance=instance)
except exc.InstanceNotFound:
LOG.warning('VM was not found during unplug operation as it is '
'already possibly deleted.', instance=instance)
except Exception:
LOG.exception("PowerVM error trying to unplug vifs.",
instance=instance)
raise exc.InterfaceDetachFailed(instance_uuid=instance.uuid)
def get_vnc_console(self, context, instance):
"""Get connection info for a vnc console.

View File

@ -1,4 +1,4 @@
# Copyright 2015, 2017 IBM Corp.
# Copyright 2015, 2018 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
@ -24,6 +24,26 @@ from nova.virt.powervm import vm
LOG = logging.getLogger(__name__)
class Get(task.Task):
"""The task for getting a VM entry."""
def __init__(self, adapter, 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 instance: The nova instance.
"""
super(Get, self).__init__(name='get_vm', provides='lpar_wrap')
self.adapter = adapter
self.instance = instance
def execute(self):
return vm.get_instance_wrapper(self.adapter, self.instance)
class Create(task.Task):
"""The task for creating a VM."""

View File

@ -0,0 +1,5 @@
---
features:
- |
The PowerVM driver now supports hot plugging/unplugging of network
interfaces.