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:
parent
1f44fde35e
commit
ed525cc403
@ -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
|
||||
|
@ -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')
|
||||
|
@ -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',
|
||||
|
@ -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.
|
||||
|
||||
|
@ -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."""
|
||||
|
||||
|
@ -0,0 +1,5 @@
|
||||
---
|
||||
features:
|
||||
- |
|
||||
The PowerVM driver now supports hot plugging/unplugging of network
|
||||
interfaces.
|
Loading…
Reference in New Issue
Block a user