Merge "PowerVM driver: ovs vif"
This commit is contained in:
commit
c03bbc142f
@ -1180,7 +1180,7 @@ driver-impl-hyperv=missing
|
||||
driver-impl-ironic=missing
|
||||
driver-impl-libvirt-vz-vm=complete
|
||||
driver-impl-libvirt-vz-ct=complete
|
||||
driver-impl-powervm=missing
|
||||
driver-impl-powervm=complete
|
||||
|
||||
[networking.routing]
|
||||
title=Network routing
|
||||
@ -1199,7 +1199,7 @@ driver-impl-hyperv=missing
|
||||
driver-impl-ironic=complete
|
||||
driver-impl-libvirt-vz-vm=complete
|
||||
driver-impl-libvirt-vz-ct=complete
|
||||
driver-impl-powervm=missing
|
||||
driver-impl-powervm=complete
|
||||
|
||||
[networking.securitygroups]
|
||||
title=Network security groups
|
||||
@ -1225,7 +1225,7 @@ driver-impl-hyperv=missing
|
||||
driver-impl-ironic=missing
|
||||
driver-impl-libvirt-vz-vm=complete
|
||||
driver-impl-libvirt-vz-ct=complete
|
||||
driver-impl-powervm=missing
|
||||
driver-impl-powervm=complete
|
||||
|
||||
[networking.topology.flat]
|
||||
title=Flat networking
|
||||
@ -1247,7 +1247,7 @@ driver-impl-hyperv=complete
|
||||
driver-impl-ironic=complete
|
||||
driver-impl-libvirt-vz-vm=complete
|
||||
driver-impl-libvirt-vz-ct=complete
|
||||
driver-impl-powervm=missing
|
||||
driver-impl-powervm=complete
|
||||
|
||||
[networking.topology.vlan]
|
||||
title=VLAN networking
|
||||
@ -1268,7 +1268,7 @@ driver-impl-hyperv=missing
|
||||
driver-impl-ironic=missing
|
||||
driver-impl-libvirt-vz-vm=complete
|
||||
driver-impl-libvirt-vz-ct=complete
|
||||
driver-impl-powervm=missing
|
||||
driver-impl-powervm=complete
|
||||
|
||||
[operation.uefi-boot]
|
||||
title=uefi boot
|
||||
|
306
nova/tests/unit/virt/powervm/tasks/test_network.py
Normal file
306
nova/tests/unit/virt/powervm/tasks/test_network.py
Normal file
@ -0,0 +1,306 @@
|
||||
# Copyright 2015, 2017 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.
|
||||
|
||||
import copy
|
||||
|
||||
import eventlet
|
||||
import mock
|
||||
from pypowervm.wrappers import network as pvm_net
|
||||
|
||||
from nova import exception
|
||||
from nova import test
|
||||
from nova.tests.unit.virt import powervm
|
||||
from nova.virt.powervm.tasks import network as tf_net
|
||||
|
||||
|
||||
def cna(mac):
|
||||
"""Builds a mock Client Network Adapter for unit tests."""
|
||||
return mock.MagicMock(mac=mac, vswitch_uri='fake_href')
|
||||
|
||||
|
||||
class TestNetwork(test.NoDBTestCase):
|
||||
def setUp(self):
|
||||
super(TestNetwork, self).setUp()
|
||||
self.flags(host='host1')
|
||||
self.apt = mock.Mock()
|
||||
|
||||
self.mock_lpar_wrap = mock.MagicMock()
|
||||
self.mock_lpar_wrap.can_modify_io.return_value = True, None
|
||||
|
||||
@mock.patch('nova.virt.powervm.vm.get_instance_wrapper')
|
||||
@mock.patch('nova.virt.powervm.vif.unplug')
|
||||
@mock.patch('nova.virt.powervm.vm.get_cnas')
|
||||
def test_unplug_vifs(self, mock_vm_get, mock_unplug, mock_get_wrap):
|
||||
"""Tests that a delete of the vif can be done."""
|
||||
inst = powervm.TEST_INSTANCE
|
||||
|
||||
# Mock up the CNA responses.
|
||||
cnas = [cna('AABBCCDDEEFF'), cna('AABBCCDDEE11'), cna('AABBCCDDEE22')]
|
||||
mock_vm_get.return_value = cnas
|
||||
|
||||
# Mock up the network info. This also validates that they will be
|
||||
# sanitized to upper case.
|
||||
net_info = [
|
||||
{'address': 'aa:bb:cc:dd:ee:ff'}, {'address': 'aa:bb:cc:dd:ee:22'},
|
||||
{'address': 'aa:bb:cc:dd:ee:33'}
|
||||
]
|
||||
|
||||
# Mock out the instance wrapper
|
||||
mock_get_wrap.return_value = self.mock_lpar_wrap
|
||||
|
||||
# Mock out the vif driver
|
||||
def validate_unplug(adapter, instance, vif, cna_w_list=None):
|
||||
self.assertEqual(adapter, self.apt)
|
||||
self.assertEqual(instance, inst)
|
||||
self.assertIn(vif, net_info)
|
||||
self.assertEqual(cna_w_list, cnas)
|
||||
|
||||
mock_unplug.side_effect = validate_unplug
|
||||
|
||||
# Run method
|
||||
p_vifs = tf_net.UnplugVifs(self.apt, inst, net_info)
|
||||
p_vifs.execute()
|
||||
|
||||
# Make sure the unplug was invoked, so that we know that the validation
|
||||
# code was called
|
||||
self.assertEqual(3, mock_unplug.call_count)
|
||||
|
||||
@mock.patch('nova.virt.powervm.vm.get_instance_wrapper')
|
||||
def test_unplug_vifs_invalid_state(self, mock_get_wrap):
|
||||
"""Tests that the delete raises an exception if bad VM state."""
|
||||
inst = powervm.TEST_INSTANCE
|
||||
|
||||
# Mock out the instance wrapper
|
||||
mock_get_wrap.return_value = self.mock_lpar_wrap
|
||||
|
||||
# Mock that the state is incorrect
|
||||
self.mock_lpar_wrap.can_modify_io.return_value = False, 'bad'
|
||||
|
||||
# Run method
|
||||
p_vifs = tf_net.UnplugVifs(self.apt, inst, mock.Mock())
|
||||
self.assertRaises(exception.VirtualInterfaceUnplugException,
|
||||
p_vifs.execute)
|
||||
|
||||
@mock.patch('nova.virt.powervm.vif.plug')
|
||||
@mock.patch('nova.virt.powervm.vm.get_cnas')
|
||||
def test_plug_vifs_rmc(self, mock_cna_get, mock_plug):
|
||||
"""Tests that a crt vif can be done with secure RMC."""
|
||||
inst = powervm.TEST_INSTANCE
|
||||
|
||||
# Mock up the CNA response. One should already exist, the other
|
||||
# should not.
|
||||
pre_cnas = [cna('AABBCCDDEEFF'), cna('AABBCCDDEE11')]
|
||||
mock_cna_get.return_value = copy.deepcopy(pre_cnas)
|
||||
|
||||
# Mock up the network info. This also validates that they will be
|
||||
# sanitized to upper case.
|
||||
net_info = [
|
||||
{'address': 'aa:bb:cc:dd:ee:ff', 'vnic_type': 'normal'},
|
||||
{'address': 'aa:bb:cc:dd:ee:22', 'vnic_type': 'normal'},
|
||||
]
|
||||
|
||||
# First run the CNA update, then the CNA create.
|
||||
mock_new_cna = mock.Mock(spec=pvm_net.CNA)
|
||||
mock_plug.side_effect = ['upd_cna', mock_new_cna]
|
||||
|
||||
# Run method
|
||||
p_vifs = tf_net.PlugVifs(mock.MagicMock(), self.apt, inst, net_info)
|
||||
|
||||
all_cnas = p_vifs.execute(self.mock_lpar_wrap)
|
||||
|
||||
# new vif should be created twice.
|
||||
mock_plug.assert_any_call(self.apt, inst, net_info[0], new_vif=False)
|
||||
mock_plug.assert_any_call(self.apt, inst, net_info[1], new_vif=True)
|
||||
|
||||
# The Task provides the list of original CNAs plus only CNAs that were
|
||||
# created.
|
||||
self.assertEqual(pre_cnas + [mock_new_cna], all_cnas)
|
||||
|
||||
@mock.patch('nova.virt.powervm.vif.plug')
|
||||
@mock.patch('nova.virt.powervm.vm.get_cnas')
|
||||
def test_plug_vifs_rmc_no_create(self, mock_vm_get, mock_plug):
|
||||
"""Verifies if no creates are needed, none are done."""
|
||||
inst = powervm.TEST_INSTANCE
|
||||
|
||||
# Mock up the CNA response. Both should already exist.
|
||||
mock_vm_get.return_value = [cna('AABBCCDDEEFF'), cna('AABBCCDDEE11')]
|
||||
|
||||
# Mock up the network info. This also validates that they will be
|
||||
# sanitized to upper case. This also validates that we don't call
|
||||
# get_vnics if no nets have vnic_type 'direct'.
|
||||
net_info = [
|
||||
{'address': 'aa:bb:cc:dd:ee:ff', 'vnic_type': 'normal'},
|
||||
{'address': 'aa:bb:cc:dd:ee:11', 'vnic_type': 'normal'}
|
||||
]
|
||||
|
||||
# Run method
|
||||
p_vifs = tf_net.PlugVifs(mock.MagicMock(), self.apt, inst, net_info)
|
||||
p_vifs.execute(self.mock_lpar_wrap)
|
||||
|
||||
# The create should have been called with new_vif as False.
|
||||
mock_plug.assert_any_call(self.apt, inst, net_info[0], new_vif=False)
|
||||
mock_plug.assert_any_call(self.apt, inst, net_info[1], new_vif=False)
|
||||
|
||||
@mock.patch('nova.virt.powervm.vif.plug')
|
||||
@mock.patch('nova.virt.powervm.vm.get_cnas')
|
||||
def test_plug_vifs_invalid_state(self, mock_vm_get, mock_plug):
|
||||
"""Tests that a crt_vif fails when the LPAR state is bad."""
|
||||
inst = powervm.TEST_INSTANCE
|
||||
|
||||
# Mock up the CNA response. Only doing one for simplicity
|
||||
mock_vm_get.return_value = []
|
||||
net_info = [{'address': 'aa:bb:cc:dd:ee:ff', 'vnic_type': 'normal'}]
|
||||
|
||||
# Mock that the state is incorrect
|
||||
self.mock_lpar_wrap.can_modify_io.return_value = False, 'bad'
|
||||
|
||||
# Run method
|
||||
p_vifs = tf_net.PlugVifs(mock.MagicMock(), self.apt, inst, net_info)
|
||||
self.assertRaises(exception.VirtualInterfaceCreateException,
|
||||
p_vifs.execute, self.mock_lpar_wrap)
|
||||
|
||||
# The create should not have been invoked
|
||||
self.assertEqual(0, mock_plug.call_count)
|
||||
|
||||
@mock.patch('nova.virt.powervm.vif.plug')
|
||||
@mock.patch('nova.virt.powervm.vm.get_cnas')
|
||||
def test_plug_vifs_timeout(self, mock_vm_get, mock_plug):
|
||||
"""Tests that crt vif failure via loss of neutron callback."""
|
||||
inst = powervm.TEST_INSTANCE
|
||||
|
||||
# Mock up the CNA response. Only doing one for simplicity
|
||||
mock_vm_get.return_value = [cna('AABBCCDDEE11')]
|
||||
|
||||
# Mock up the network info.
|
||||
net_info = [{'address': 'aa:bb:cc:dd:ee:ff', 'vnic_type': 'normal'}]
|
||||
|
||||
# Ensure that an exception is raised by a timeout.
|
||||
mock_plug.side_effect = eventlet.timeout.Timeout()
|
||||
|
||||
# Run method
|
||||
p_vifs = tf_net.PlugVifs(mock.MagicMock(), self.apt, inst, net_info)
|
||||
self.assertRaises(exception.VirtualInterfaceCreateException,
|
||||
p_vifs.execute, self.mock_lpar_wrap)
|
||||
|
||||
# The create should have only been called once.
|
||||
self.assertEqual(1, mock_plug.call_count)
|
||||
|
||||
@mock.patch('nova.virt.powervm.vif.unplug')
|
||||
@mock.patch('nova.virt.powervm.vif.plug')
|
||||
@mock.patch('nova.virt.powervm.vm.get_cnas')
|
||||
def test_plug_vifs_revert(self, mock_vm_get, mock_plug, mock_unplug):
|
||||
"""Tests that the revert flow works properly."""
|
||||
inst = powervm.TEST_INSTANCE
|
||||
|
||||
# Fake CNA list. The one pre-existing VIF should *not* get reverted.
|
||||
cna_list = [cna('AABBCCDDEEFF'), cna('FFEEDDCCBBAA')]
|
||||
mock_vm_get.return_value = cna_list
|
||||
|
||||
# Mock up the network info. Three roll backs.
|
||||
net_info = [
|
||||
{'address': 'aa:bb:cc:dd:ee:ff', 'vnic_type': 'normal'},
|
||||
{'address': 'aa:bb:cc:dd:ee:22', 'vnic_type': 'normal'},
|
||||
{'address': 'aa:bb:cc:dd:ee:33', 'vnic_type': 'normal'}
|
||||
]
|
||||
|
||||
# Make sure we test raising an exception
|
||||
mock_unplug.side_effect = [exception.NovaException(), None]
|
||||
|
||||
# Run method
|
||||
p_vifs = tf_net.PlugVifs(mock.MagicMock(), self.apt, inst, net_info)
|
||||
p_vifs.execute(self.mock_lpar_wrap)
|
||||
p_vifs.revert(self.mock_lpar_wrap, mock.Mock(), mock.Mock())
|
||||
|
||||
# The unplug should be called twice. The exception shouldn't stop the
|
||||
# second call.
|
||||
self.assertEqual(2, mock_unplug.call_count)
|
||||
|
||||
# Make sure each call is invoked correctly. The first plug was not a
|
||||
# new vif, so it should not be reverted.
|
||||
c2 = mock.call(self.apt, inst, net_info[1], cna_w_list=cna_list)
|
||||
c3 = mock.call(self.apt, inst, net_info[2], cna_w_list=cna_list)
|
||||
mock_unplug.assert_has_calls([c2, c3])
|
||||
|
||||
@mock.patch('pypowervm.tasks.cna.crt_cna')
|
||||
@mock.patch('pypowervm.wrappers.network.VSwitch.search')
|
||||
@mock.patch('nova.virt.powervm.vif.plug')
|
||||
@mock.patch('nova.virt.powervm.vm.get_cnas')
|
||||
def test_plug_mgmt_vif(self, mock_vm_get, mock_plug, mock_vs_search,
|
||||
mock_crt_cna):
|
||||
"""Tests that a mgmt vif can be created."""
|
||||
inst = powervm.TEST_INSTANCE
|
||||
|
||||
# Mock up the rmc vswitch
|
||||
vswitch_w = mock.MagicMock()
|
||||
vswitch_w.href = 'fake_mgmt_uri'
|
||||
mock_vs_search.return_value = [vswitch_w]
|
||||
|
||||
# Run method such that it triggers a fresh CNA search
|
||||
p_vifs = tf_net.PlugMgmtVif(self.apt, inst)
|
||||
p_vifs.execute(None)
|
||||
|
||||
# With the default get_cnas mock (which returns a Mock()), we think we
|
||||
# found an existing management CNA.
|
||||
mock_crt_cna.assert_not_called()
|
||||
mock_vm_get.assert_called_once_with(
|
||||
self.apt, inst, vswitch_uri='fake_mgmt_uri')
|
||||
|
||||
# Now mock get_cnas to return no hits
|
||||
mock_vm_get.reset_mock()
|
||||
mock_vm_get.return_value = []
|
||||
p_vifs.execute(None)
|
||||
|
||||
# Get was called; and since it didn't have the mgmt CNA, so was plug.
|
||||
self.assertEqual(1, mock_crt_cna.call_count)
|
||||
mock_vm_get.assert_called_once_with(
|
||||
self.apt, inst, vswitch_uri='fake_mgmt_uri')
|
||||
|
||||
# Now pass CNAs, but not the mgmt vif, "from PlugVifs"
|
||||
cnas = [mock.Mock(vswitch_uri='uri1'), mock.Mock(vswitch_uri='uri2')]
|
||||
mock_crt_cna.reset_mock()
|
||||
mock_vm_get.reset_mock()
|
||||
p_vifs.execute(cnas)
|
||||
|
||||
# Get wasn't called, since the CNAs were passed "from PlugVifs"; but
|
||||
# since the mgmt vif wasn't included, plug was called.
|
||||
mock_vm_get.assert_not_called()
|
||||
mock_crt_cna.assert_called()
|
||||
|
||||
# Finally, pass CNAs including the mgmt.
|
||||
cnas.append(mock.Mock(vswitch_uri='fake_mgmt_uri'))
|
||||
mock_crt_cna.reset_mock()
|
||||
p_vifs.execute(cnas)
|
||||
|
||||
# Neither get nor plug was called.
|
||||
mock_vm_get.assert_not_called()
|
||||
mock_crt_cna.assert_not_called()
|
||||
|
||||
def test_get_vif_events(self):
|
||||
# Set up common mocks.
|
||||
inst = powervm.TEST_INSTANCE
|
||||
net_info = [mock.MagicMock(), mock.MagicMock()]
|
||||
net_info[0]['id'] = 'a'
|
||||
net_info[0].get.return_value = False
|
||||
net_info[1]['id'] = 'b'
|
||||
net_info[1].get.return_value = True
|
||||
|
||||
# Set up the runner.
|
||||
p_vifs = tf_net.PlugVifs(mock.MagicMock(), self.apt, inst, net_info)
|
||||
p_vifs.crt_network_infos = net_info
|
||||
resp = p_vifs._get_vif_events()
|
||||
|
||||
# Only one should be returned since only one was active.
|
||||
self.assertEqual(1, len(resp))
|
@ -38,14 +38,14 @@ class TestStorage(test.NoDBTestCase):
|
||||
task = tf_stg.CreateAndConnectCfgDrive(
|
||||
self.adapter, self.instance, 'injected_files',
|
||||
'network_info', 'stg_ftsk', admin_pass='admin_pass')
|
||||
task.execute()
|
||||
task.execute('mgmt_cna')
|
||||
self.mock_cfg_drv.assert_called_once_with(self.adapter)
|
||||
self.mock_mb.create_cfg_drv_vopt.assert_called_once_with(
|
||||
self.instance, 'injected_files', 'network_info', 'stg_ftsk',
|
||||
admin_pass='admin_pass')
|
||||
admin_pass='admin_pass', mgmt_cna='mgmt_cna')
|
||||
|
||||
# Normal revert
|
||||
task.revert('result', 'flow_failures')
|
||||
task.revert('mgmt_cna', 'result', 'flow_failures')
|
||||
self.mock_mb.dlt_vopt.assert_called_once_with(self.instance,
|
||||
'stg_ftsk')
|
||||
|
||||
@ -53,14 +53,14 @@ class TestStorage(test.NoDBTestCase):
|
||||
|
||||
# Revert when dlt_vopt fails
|
||||
self.mock_mb.dlt_vopt.side_effect = pvm_exc.Error('fake-exc')
|
||||
task.revert('result', 'flow_failures')
|
||||
task.revert('mgmt_cna', 'result', 'flow_failures')
|
||||
self.mock_mb.dlt_vopt.assert_called_once()
|
||||
|
||||
self.mock_mb.reset_mock()
|
||||
|
||||
# Revert when media builder not created
|
||||
task.mb = None
|
||||
task.revert('result', 'flow_failures')
|
||||
task.revert('mgmt_cna', 'result', 'flow_failures')
|
||||
self.mock_mb.assert_not_called()
|
||||
|
||||
def test_delete_vopt(self):
|
||||
|
@ -111,6 +111,8 @@ class TestPowerVMDriver(test.NoDBTestCase):
|
||||
mock_bhrfm.assert_called_once_with('sys')
|
||||
self.assertEqual('sys', self.drv.host_wrapper)
|
||||
|
||||
@mock.patch('nova.virt.powervm.tasks.network.PlugMgmtVif.execute')
|
||||
@mock.patch('nova.virt.powervm.tasks.network.PlugVifs.execute')
|
||||
@mock.patch('nova.virt.powervm.media.ConfigDrivePowerVM')
|
||||
@mock.patch('nova.virt.configdrive.required_by')
|
||||
@mock.patch('nova.virt.powervm.vm.create_lpar')
|
||||
@ -119,10 +121,11 @@ class TestPowerVMDriver(test.NoDBTestCase):
|
||||
@mock.patch('pypowervm.tasks.storage.add_lpar_storage_scrub_tasks',
|
||||
autospec=True)
|
||||
def test_spawn_ops(self, mock_scrub, mock_bldftsk, mock_crt_lpar,
|
||||
mock_cdrb, mock_cfg_drv):
|
||||
mock_cdrb, mock_cfg_drv, mock_plug_vifs,
|
||||
mock_plug_mgmt_vif):
|
||||
"""Validates the 'typical' spawn flow of the spawn of an instance. """
|
||||
mock_cdrb.return_value = True
|
||||
self.drv.host_wrapper = mock.Mock(uuid='host_uuid')
|
||||
self.drv.host_wrapper = mock.Mock()
|
||||
self.drv.disk_dvr = mock.create_autospec(ssp.SSPDiskAdapter,
|
||||
instance=True)
|
||||
mock_ftsk = pvm_tx.FeedTask('fake', [mock.Mock(spec=pvm_vios.VIOS)])
|
||||
@ -133,6 +136,8 @@ class TestPowerVMDriver(test.NoDBTestCase):
|
||||
self.adp, self.drv.host_wrapper, self.inst)
|
||||
mock_bldftsk.assert_called_once_with(
|
||||
self.adp, xag={pvm_const.XAG.VIO_SMAP, pvm_const.XAG.VIO_FMAP})
|
||||
self.assertTrue(mock_plug_vifs.called)
|
||||
self.assertTrue(mock_plug_mgmt_vif.called)
|
||||
mock_scrub.assert_called_once_with(
|
||||
[mock_crt_lpar.return_value.id], mock_ftsk, lpars_exist=True)
|
||||
self.drv.disk_dvr.create_disk_from_image.assert_called_once_with(
|
||||
@ -142,7 +147,8 @@ class TestPowerVMDriver(test.NoDBTestCase):
|
||||
mock_ftsk)
|
||||
mock_cfg_drv.assert_called_once_with(self.adp)
|
||||
mock_cfg_drv.return_value.create_cfg_drv_vopt.assert_called_once_with(
|
||||
self.inst, 'files', 'netinfo', mock_ftsk, admin_pass='password')
|
||||
self.inst, 'files', 'netinfo', mock_ftsk, admin_pass='password',
|
||||
mgmt_cna=mock.ANY)
|
||||
self.pwron.assert_called_once_with(self.adp, self.inst)
|
||||
|
||||
mock_cfg_drv.reset_mock()
|
||||
@ -153,17 +159,19 @@ class TestPowerVMDriver(test.NoDBTestCase):
|
||||
'allocs')
|
||||
mock_cfg_drv.assert_not_called()
|
||||
|
||||
@mock.patch('nova.virt.powervm.tasks.network.UnplugVifs.execute')
|
||||
@mock.patch('nova.virt.powervm.vm.delete_lpar')
|
||||
@mock.patch('nova.virt.powervm.media.ConfigDrivePowerVM')
|
||||
@mock.patch('nova.virt.configdrive.required_by')
|
||||
@mock.patch('pypowervm.tasks.partition.build_active_vio_feed_task',
|
||||
autospec=True)
|
||||
def test_destroy(self, mock_bldftsk, mock_cdrb, mock_cfgdrv,
|
||||
mock_dlt_lpar):
|
||||
mock_dlt_lpar, mock_unplug):
|
||||
"""Validates PowerVM destroy."""
|
||||
self.drv.host_wrapper = mock.Mock(uuid='host_uuid')
|
||||
self.drv.host_wrapper = mock.Mock()
|
||||
self.drv.disk_dvr = mock.create_autospec(ssp.SSPDiskAdapter,
|
||||
instance=True)
|
||||
|
||||
mock_ftsk = pvm_tx.FeedTask('fake', [mock.Mock(spec=pvm_vios.VIOS)])
|
||||
mock_bldftsk.return_value = mock_ftsk
|
||||
|
||||
@ -174,6 +182,7 @@ class TestPowerVMDriver(test.NoDBTestCase):
|
||||
self.adp, self.inst, force_immediate=True)
|
||||
mock_bldftsk.assert_called_once_with(
|
||||
self.adp, xag=[pvm_const.XAG.VIO_SMAP])
|
||||
mock_unplug.assert_called_once()
|
||||
mock_cdrb.assert_called_once_with(self.inst)
|
||||
mock_cfgdrv.assert_called_once_with(self.adp)
|
||||
mock_cfgdrv.return_value.dlt_vopt.assert_called_once_with(
|
||||
@ -186,6 +195,7 @@ class TestPowerVMDriver(test.NoDBTestCase):
|
||||
|
||||
self.pwroff.reset_mock()
|
||||
mock_bldftsk.reset_mock()
|
||||
mock_unplug.reset_mock()
|
||||
mock_cdrb.reset_mock()
|
||||
mock_cfgdrv.reset_mock()
|
||||
self.drv.disk_dvr.detach_disk.reset_mock()
|
||||
@ -204,6 +214,7 @@ class TestPowerVMDriver(test.NoDBTestCase):
|
||||
self.adp, self.inst, force_immediate=False)
|
||||
mock_bldftsk.assert_called_once_with(
|
||||
self.adp, xag=[pvm_const.XAG.VIO_SMAP])
|
||||
mock_unplug.assert_called_once()
|
||||
mock_cdrb.assert_called_once_with(self.inst)
|
||||
mock_cfgdrv.assert_not_called()
|
||||
mock_cfgdrv.return_value.dlt_vopt.assert_not_called()
|
||||
@ -214,6 +225,7 @@ class TestPowerVMDriver(test.NoDBTestCase):
|
||||
|
||||
self.pwroff.reset_mock()
|
||||
mock_bldftsk.reset_mock()
|
||||
mock_unplug.reset_mock()
|
||||
mock_cdrb.reset_mock()
|
||||
mock_cfgdrv.reset_mock()
|
||||
self.drv.disk_dvr.detach_disk.reset_mock()
|
||||
@ -228,11 +240,13 @@ class TestPowerVMDriver(test.NoDBTestCase):
|
||||
self.pwroff.assert_called_once_with(
|
||||
self.adp, self.inst, force_immediate=False)
|
||||
self.drv.disk_dvr.detach_disk.assert_not_called()
|
||||
mock_unplug.assert_not_called()
|
||||
self.drv.disk_dvr.delete_disks.assert_not_called()
|
||||
mock_dlt_lpar.assert_not_called()
|
||||
|
||||
self.pwroff.reset_mock()
|
||||
self.pwroff.side_effect = None
|
||||
mock_unplug.reset_mock()
|
||||
|
||||
# Convertible (PowerVM) exception
|
||||
mock_dlt_lpar.side_effect = pvm_exc.TimeoutError("Timed out")
|
||||
@ -243,6 +257,7 @@ class TestPowerVMDriver(test.NoDBTestCase):
|
||||
# Everything got called
|
||||
self.pwroff.assert_called_once_with(
|
||||
self.adp, self.inst, force_immediate=True)
|
||||
mock_unplug.assert_called_once()
|
||||
self.drv.disk_dvr.detach_disk.assert_called_once_with(self.inst)
|
||||
self.drv.disk_dvr.delete_disks.assert_called_once_with(
|
||||
self.drv.disk_dvr.detach_disk.return_value)
|
||||
@ -304,3 +319,7 @@ class TestPowerVMDriver(test.NoDBTestCase):
|
||||
mock_vterm.side_effect = pvm_exc.HttpError(mock.Mock(status=404))
|
||||
self.assertRaises(exception.InstanceNotFound, self.drv.get_vnc_console,
|
||||
mock.ANY, self.inst)
|
||||
|
||||
def test_deallocate_networks_on_reschedule(self):
|
||||
candeallocate = self.drv.deallocate_networks_on_reschedule(mock.Mock())
|
||||
self.assertTrue(candeallocate)
|
||||
|
@ -21,6 +21,7 @@ import mock
|
||||
from pypowervm.tasks import scsi_mapper as tsk_map
|
||||
from pypowervm.tests import test_fixtures as pvm_fx
|
||||
from pypowervm.utils import transaction as pvm_tx
|
||||
from pypowervm.wrappers import network as pvm_net
|
||||
from pypowervm.wrappers import storage as pvm_stg
|
||||
from pypowervm.wrappers import virtual_io_server as pvm_vios
|
||||
import six
|
||||
@ -187,3 +188,34 @@ class TestConfigDrivePowerVM(test.NoDBTestCase):
|
||||
cfg_dr.dlt_vopt('inst', ftsk)
|
||||
mock_functask.assert_called_once()
|
||||
ftsk.add_post_execute.assert_called_once_with('functor_task')
|
||||
|
||||
def test_mgmt_cna_to_vif(self):
|
||||
mock_cna = mock.Mock(spec=pvm_net.CNA, mac="FAD4433ED120")
|
||||
|
||||
# Run
|
||||
cfg_dr_builder = m.ConfigDrivePowerVM(self.apt)
|
||||
vif = cfg_dr_builder._mgmt_cna_to_vif(mock_cna)
|
||||
|
||||
# Validate
|
||||
self.assertEqual(vif.get('address'), "fa:d4:43:3e:d1:20")
|
||||
self.assertEqual(vif.get('id'), 'mgmt_vif')
|
||||
self.assertIsNotNone(vif.get('network'))
|
||||
self.assertEqual(1, len(vif.get('network').get('subnets')))
|
||||
subnet = vif.get('network').get('subnets')[0]
|
||||
self.assertEqual(6, subnet.get('version'))
|
||||
self.assertEqual('fe80::/64', subnet.get('cidr'))
|
||||
ip = subnet.get('ips')[0]
|
||||
self.assertEqual('fe80::f8d4:43ff:fe3e:d120', ip.get('address'))
|
||||
|
||||
def test_mac_to_link_local(self):
|
||||
mac = 'fa:d4:43:3e:d1:20'
|
||||
self.assertEqual('fe80::f8d4:43ff:fe3e:d120',
|
||||
m.ConfigDrivePowerVM._mac_to_link_local(mac))
|
||||
|
||||
mac = '00:00:00:00:00:00'
|
||||
self.assertEqual('fe80::0200:00ff:fe00:0000',
|
||||
m.ConfigDrivePowerVM._mac_to_link_local(mac))
|
||||
|
||||
mac = 'ff:ff:ff:ff:ff:ff'
|
||||
self.assertEqual('fe80::fdff:ffff:feff:ffff',
|
||||
m.ConfigDrivePowerVM._mac_to_link_local(mac))
|
||||
|
225
nova/tests/unit/virt/powervm/test_vif.py
Normal file
225
nova/tests/unit/virt/powervm/test_vif.py
Normal file
@ -0,0 +1,225 @@
|
||||
# Copyright 2017 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.
|
||||
|
||||
import mock
|
||||
from oslo_config import cfg
|
||||
from pypowervm import exceptions as pvm_ex
|
||||
from pypowervm.wrappers import network as pvm_net
|
||||
|
||||
from nova import exception
|
||||
from nova.network import model
|
||||
from nova import test
|
||||
from nova.virt.powervm import vif
|
||||
|
||||
CONF = cfg.CONF
|
||||
|
||||
|
||||
def cna(mac):
|
||||
"""Builds a mock Client Network Adapter for unit tests."""
|
||||
return mock.Mock(spec=pvm_net.CNA, mac=mac, vswitch_uri='fake_href')
|
||||
|
||||
|
||||
class TestVifFunctions(test.NoDBTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestVifFunctions, self).setUp()
|
||||
|
||||
self.adpt = mock.Mock()
|
||||
|
||||
@mock.patch('nova.virt.powervm.vif.PvmOvsVifDriver')
|
||||
def test_build_vif_driver(self, mock_driver):
|
||||
# Valid vif type
|
||||
driver = vif._build_vif_driver(self.adpt, 'instance', {'type': 'ovs'})
|
||||
self.assertEqual(mock_driver.return_value, driver)
|
||||
|
||||
mock_driver.reset_mock()
|
||||
|
||||
# Fail if no vif type
|
||||
self.assertRaises(exception.VirtualInterfacePlugException,
|
||||
vif._build_vif_driver, self.adpt, 'instance',
|
||||
{'type': None})
|
||||
mock_driver.assert_not_called()
|
||||
|
||||
# Fail if invalid vif type
|
||||
self.assertRaises(exception.VirtualInterfacePlugException,
|
||||
vif._build_vif_driver, self.adpt, 'instance',
|
||||
{'type': 'bad_type'})
|
||||
mock_driver.assert_not_called()
|
||||
|
||||
@mock.patch('nova.virt.powervm.vif._build_vif_driver')
|
||||
def test_plug(self, mock_bld_drv):
|
||||
"""Test the top-level plug method."""
|
||||
mock_vif = {'address': 'MAC', 'type': 'pvm_sea'}
|
||||
|
||||
# 1) With new_vif=True (default)
|
||||
vnet = vif.plug(self.adpt, 'instance', mock_vif)
|
||||
|
||||
mock_bld_drv.assert_called_once_with(self.adpt, 'instance', mock_vif)
|
||||
mock_bld_drv.return_value.plug.assert_called_once_with(mock_vif,
|
||||
new_vif=True)
|
||||
self.assertEqual(mock_bld_drv.return_value.plug.return_value, vnet)
|
||||
|
||||
# Clean up
|
||||
mock_bld_drv.reset_mock()
|
||||
mock_bld_drv.return_value.plug.reset_mock()
|
||||
|
||||
# 2) Plug returns None (which it should IRL whenever new_vif=False).
|
||||
mock_bld_drv.return_value.plug.return_value = None
|
||||
vnet = vif.plug(self.adpt, 'instance', mock_vif, new_vif=False)
|
||||
|
||||
mock_bld_drv.assert_called_once_with(self.adpt, 'instance', mock_vif)
|
||||
mock_bld_drv.return_value.plug.assert_called_once_with(mock_vif,
|
||||
new_vif=False)
|
||||
self.assertIsNone(vnet)
|
||||
|
||||
@mock.patch('nova.virt.powervm.vif._build_vif_driver')
|
||||
def test_plug_raises(self, mock_vif_drv):
|
||||
"""HttpError is converted to VirtualInterfacePlugException."""
|
||||
vif_drv = mock.Mock(plug=mock.Mock(side_effect=pvm_ex.HttpError(
|
||||
resp=mock.Mock())))
|
||||
mock_vif_drv.return_value = vif_drv
|
||||
mock_vif = {'address': 'vifaddr'}
|
||||
self.assertRaises(exception.VirtualInterfacePlugException,
|
||||
vif.plug, 'adap', 'inst', mock_vif,
|
||||
new_vif='new_vif')
|
||||
mock_vif_drv.assert_called_once_with('adap', 'inst', mock_vif)
|
||||
vif_drv.plug.assert_called_once_with(mock_vif, new_vif='new_vif')
|
||||
|
||||
@mock.patch('nova.virt.powervm.vif._build_vif_driver')
|
||||
def test_unplug(self, mock_bld_drv):
|
||||
"""Test the top-level unplug method."""
|
||||
mock_vif = {'address': 'MAC', 'type': 'pvm_sea'}
|
||||
|
||||
# 1) With default cna_w_list
|
||||
mock_bld_drv.return_value.unplug.return_value = 'vnet_w'
|
||||
vif.unplug(self.adpt, 'instance', mock_vif)
|
||||
mock_bld_drv.assert_called_once_with(self.adpt, 'instance', mock_vif)
|
||||
mock_bld_drv.return_value.unplug.assert_called_once_with(
|
||||
mock_vif, cna_w_list=None)
|
||||
|
||||
# Clean up
|
||||
mock_bld_drv.reset_mock()
|
||||
mock_bld_drv.return_value.unplug.reset_mock()
|
||||
|
||||
# 2) With specified cna_w_list
|
||||
mock_bld_drv.return_value.unplug.return_value = None
|
||||
vif.unplug(self.adpt, 'instance', mock_vif, cna_w_list='cnalist')
|
||||
mock_bld_drv.assert_called_once_with(self.adpt, 'instance', mock_vif)
|
||||
mock_bld_drv.return_value.unplug.assert_called_once_with(
|
||||
mock_vif, cna_w_list='cnalist')
|
||||
|
||||
@mock.patch('nova.virt.powervm.vif._build_vif_driver')
|
||||
def test_unplug_raises(self, mock_vif_drv):
|
||||
"""HttpError is converted to VirtualInterfacePlugException."""
|
||||
vif_drv = mock.Mock(unplug=mock.Mock(side_effect=pvm_ex.HttpError(
|
||||
resp=mock.Mock())))
|
||||
mock_vif_drv.return_value = vif_drv
|
||||
mock_vif = {'address': 'vifaddr'}
|
||||
self.assertRaises(exception.VirtualInterfaceUnplugException,
|
||||
vif.unplug, 'adap', 'inst', mock_vif,
|
||||
cna_w_list='cna_w_list')
|
||||
mock_vif_drv.assert_called_once_with('adap', 'inst', mock_vif)
|
||||
vif_drv.unplug.assert_called_once_with(
|
||||
mock_vif, cna_w_list='cna_w_list')
|
||||
|
||||
|
||||
class TestVifOvsDriver(test.NoDBTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestVifOvsDriver, self).setUp()
|
||||
|
||||
self.adpt = mock.Mock()
|
||||
self.inst = mock.MagicMock(uuid='inst_uuid')
|
||||
self.drv = vif.PvmOvsVifDriver(self.adpt, self.inst)
|
||||
|
||||
@mock.patch('pypowervm.tasks.cna.crt_p2p_cna', autospec=True)
|
||||
@mock.patch('pypowervm.tasks.partition.get_this_partition', autospec=True)
|
||||
@mock.patch('nova.virt.powervm.vm.get_pvm_uuid')
|
||||
def test_plug(self, mock_pvm_uuid, mock_mgmt_lpar, mock_p2p_cna,):
|
||||
# Mock the data
|
||||
mock_pvm_uuid.return_value = 'lpar_uuid'
|
||||
mock_mgmt_lpar.return_value = mock.Mock(uuid='mgmt_uuid')
|
||||
# mock_trunk_dev_name.return_value = 'device'
|
||||
|
||||
cna_w, trunk_wraps = mock.MagicMock(), [mock.MagicMock()]
|
||||
mock_p2p_cna.return_value = cna_w, trunk_wraps
|
||||
|
||||
# Run the plug
|
||||
network_model = model.Model({'bridge': 'br0', 'meta': {'mtu': 1450}})
|
||||
mock_vif = model.VIF(address='aa:bb:cc:dd:ee:ff', id='vif_id',
|
||||
network=network_model, devname='device')
|
||||
self.drv.plug(mock_vif)
|
||||
|
||||
# Validate the calls
|
||||
ovs_ext_ids = ('iface-id=vif_id,iface-status=active,'
|
||||
'attached-mac=aa:bb:cc:dd:ee:ff,vm-uuid=inst_uuid')
|
||||
mock_p2p_cna.assert_called_once_with(
|
||||
self.adpt, None, 'lpar_uuid', ['mgmt_uuid'],
|
||||
'NovaLinkVEABridge', configured_mtu=1450, crt_vswitch=True,
|
||||
mac_addr='aa:bb:cc:dd:ee:ff', dev_name='device', ovs_bridge='br0',
|
||||
ovs_ext_ids=ovs_ext_ids)
|
||||
|
||||
@mock.patch('pypowervm.tasks.partition.get_this_partition', autospec=True)
|
||||
@mock.patch('nova.virt.powervm.vm.get_pvm_uuid')
|
||||
@mock.patch('nova.virt.powervm.vm.get_cnas')
|
||||
@mock.patch('pypowervm.tasks.cna.find_trunks', autospec=True)
|
||||
def test_plug_existing_vif(self, mock_find_trunks, mock_get_cnas,
|
||||
mock_pvm_uuid, mock_mgmt_lpar):
|
||||
# Mock the data
|
||||
t1, t2 = mock.MagicMock(), mock.MagicMock()
|
||||
mock_find_trunks.return_value = [t1, t2]
|
||||
|
||||
mock_cna = mock.Mock(mac='aa:bb:cc:dd:ee:ff')
|
||||
mock_get_cnas.return_value = [mock_cna]
|
||||
|
||||
mock_pvm_uuid.return_value = 'lpar_uuid'
|
||||
|
||||
mock_mgmt_lpar.return_value = mock.Mock(uuid='mgmt_uuid')
|
||||
|
||||
self.inst = mock.MagicMock(uuid='c2e7ff9f-b9b6-46fa-8716-93bbb795b8b4')
|
||||
self.drv = vif.PvmOvsVifDriver(self.adpt, self.inst)
|
||||
|
||||
# Run the plug
|
||||
network_model = model.Model({'bridge': 'br0', 'meta': {'mtu': 1500}})
|
||||
mock_vif = model.VIF(address='aa:bb:cc:dd:ee:ff', id='vif_id',
|
||||
network=network_model, devname='devname')
|
||||
resp = self.drv.plug(mock_vif, new_vif=False)
|
||||
|
||||
self.assertIsNone(resp)
|
||||
|
||||
# Validate if trunk.update got invoked for all trunks of CNA of vif
|
||||
self.assertTrue(t1.update.called)
|
||||
self.assertTrue(t2.update.called)
|
||||
|
||||
@mock.patch('pypowervm.tasks.cna.find_trunks')
|
||||
@mock.patch('nova.virt.powervm.vm.get_cnas')
|
||||
def test_unplug(self, mock_get_cnas, mock_find_trunks):
|
||||
# Set up the mocks
|
||||
mock_cna = mock.Mock(mac='aa:bb:cc:dd:ee:ff')
|
||||
mock_get_cnas.return_value = [mock_cna]
|
||||
|
||||
t1, t2 = mock.MagicMock(), mock.MagicMock()
|
||||
mock_find_trunks.return_value = [t1, t2]
|
||||
|
||||
# Call the unplug
|
||||
mock_vif = {'address': 'aa:bb:cc:dd:ee:ff',
|
||||
'network': {'bridge': 'br-int'}}
|
||||
self.drv.unplug(mock_vif)
|
||||
|
||||
# The trunks and the cna should have been deleted
|
||||
self.assertTrue(t1.delete.called)
|
||||
self.assertTrue(t2.delete.called)
|
||||
self.assertTrue(mock_cna.delete.called)
|
@ -536,3 +536,31 @@ class TestVM(test.NoDBTestCase):
|
||||
self.apt.read.side_effect = pvm_exc.Error("message", response=resp)
|
||||
self.assertRaises(pvm_exc.Error, vm.get_vm_qp, self.apt,
|
||||
'lpar_uuid', log_errors=False)
|
||||
|
||||
@mock.patch('nova.virt.powervm.vm.get_pvm_uuid')
|
||||
@mock.patch('pypowervm.wrappers.network.CNA.search')
|
||||
@mock.patch('pypowervm.wrappers.network.CNA.get')
|
||||
def test_get_cnas(self, mock_get, mock_search, mock_uuid):
|
||||
# No kwargs: get
|
||||
self.assertEqual(mock_get.return_value, vm.get_cnas(self.apt, 'inst'))
|
||||
mock_uuid.assert_called_once_with('inst')
|
||||
mock_get.assert_called_once_with(self.apt, parent_type=pvm_lpar.LPAR,
|
||||
parent_uuid=mock_uuid.return_value)
|
||||
mock_search.assert_not_called()
|
||||
# With kwargs: search
|
||||
mock_get.reset_mock()
|
||||
mock_uuid.reset_mock()
|
||||
self.assertEqual(mock_search.return_value, vm.get_cnas(
|
||||
self.apt, 'inst', one=2, three=4))
|
||||
mock_uuid.assert_called_once_with('inst')
|
||||
mock_search.assert_called_once_with(
|
||||
self.apt, parent_type=pvm_lpar.LPAR,
|
||||
parent_uuid=mock_uuid.return_value, one=2, three=4)
|
||||
mock_get.assert_not_called()
|
||||
|
||||
def test_norm_mac(self):
|
||||
EXPECTED = "12:34:56:78:90:ab"
|
||||
self.assertEqual(EXPECTED, vm.norm_mac("12:34:56:78:90:ab"))
|
||||
self.assertEqual(EXPECTED, vm.norm_mac("1234567890ab"))
|
||||
self.assertEqual(EXPECTED, vm.norm_mac("12:34:56:78:90:AB"))
|
||||
self.assertEqual(EXPECTED, vm.norm_mac("1234567890AB"))
|
||||
|
@ -36,6 +36,7 @@ from nova.virt import driver
|
||||
from nova.virt.powervm.disk import ssp
|
||||
from nova.virt.powervm import host as pvm_host
|
||||
from nova.virt.powervm.tasks import base as tf_base
|
||||
from nova.virt.powervm.tasks import network as tf_net
|
||||
from nova.virt.powervm.tasks import storage as tf_stg
|
||||
from nova.virt.powervm.tasks import vm as tf_vm
|
||||
from nova.virt.powervm import vm
|
||||
@ -179,7 +180,11 @@ class PowerVMDriver(driver.ComputeDriver):
|
||||
flow_spawn.add(tf_vm.Create(
|
||||
self.adapter, self.host_wrapper, instance, stg_ftsk))
|
||||
|
||||
# TODO(thorst, efried) Plug the VIFs
|
||||
# Create a flow for the IO
|
||||
flow_spawn.add(tf_net.PlugVifs(
|
||||
self.virtapi, self.adapter, instance, network_info))
|
||||
flow_spawn.add(tf_net.PlugMgmtVif(
|
||||
self.adapter, instance))
|
||||
|
||||
# Create the boot image.
|
||||
flow_spawn.add(tf_stg.CreateDiskForImg(
|
||||
@ -232,13 +237,16 @@ class PowerVMDriver(driver.ComputeDriver):
|
||||
# hard shutdown.
|
||||
flow.add(tf_vm.PowerOff(self.adapter, instance,
|
||||
force_immediate=destroy_disks))
|
||||
# TODO(thorst, efried) Add unplug vifs task
|
||||
|
||||
# The FeedTask accumulates storage disconnection tasks to be run in
|
||||
# parallel.
|
||||
stg_ftsk = pvm_par.build_active_vio_feed_task(
|
||||
self.adapter, xag=[pvm_const.XAG.VIO_SMAP])
|
||||
|
||||
# Call the unplug VIFs task. While CNAs get removed from the LPAR
|
||||
# directly on the destroy, this clears up the I/O Host side.
|
||||
flow.add(tf_net.UnplugVifs(self.adapter, instance, network_info))
|
||||
|
||||
# Add the disconnect/deletion of the vOpt to the transaction
|
||||
# manager.
|
||||
if configdrive.required_by(instance):
|
||||
@ -349,3 +357,11 @@ class PowerVMDriver(driver.ComputeDriver):
|
||||
if e.response.status == 404:
|
||||
sare.reraise = False
|
||||
raise exc.InstanceNotFound(instance_id=instance.uuid)
|
||||
|
||||
def deallocate_networks_on_reschedule(self, instance):
|
||||
"""Does the driver want networks deallocated on reschedule?
|
||||
|
||||
:param instance: the instance object.
|
||||
:returns: Boolean value. If True deallocate networks on reschedule.
|
||||
"""
|
||||
return True
|
||||
|
@ -30,6 +30,7 @@ import retrying
|
||||
from taskflow import task
|
||||
|
||||
from nova.api.metadata import base as instance_metadata
|
||||
from nova.network import model as network_model
|
||||
from nova.virt import configdrive
|
||||
from nova.virt.powervm import vm
|
||||
|
||||
@ -134,9 +135,8 @@ class ConfigDrivePowerVM(object):
|
||||
LOG.exception("Config drive ISO could not be built",
|
||||
instance=instance)
|
||||
|
||||
# TODO(esberglu) Add mgmt_cna when networking is introduced
|
||||
def create_cfg_drv_vopt(self, instance, injected_files, network_info,
|
||||
stg_ftsk, admin_pass=None):
|
||||
stg_ftsk, admin_pass=None, mgmt_cna=None):
|
||||
"""Create the config drive virtual optical and attach to VM.
|
||||
|
||||
:param instance: The VM instance from OpenStack.
|
||||
@ -145,7 +145,14 @@ class ConfigDrivePowerVM(object):
|
||||
:param network_info: The network_info from the nova spawn method.
|
||||
:param stg_ftsk: FeedTask to defer storage connectivity operations.
|
||||
:param admin_pass: (Optional) password to inject for the VM.
|
||||
:param mgmt_cna: (Optional) The management (RMC) CNA wrapper.
|
||||
"""
|
||||
# If there is a management client network adapter, then we should
|
||||
# convert that to a VIF and add it to the network info
|
||||
if mgmt_cna is not None:
|
||||
network_info = copy.deepcopy(network_info)
|
||||
network_info.append(self._mgmt_cna_to_vif(mgmt_cna))
|
||||
|
||||
iso_path, file_name = self._create_cfg_dr_iso(
|
||||
instance, injected_files, network_info, admin_pass=admin_pass)
|
||||
|
||||
@ -169,6 +176,39 @@ class ConfigDrivePowerVM(object):
|
||||
# Add the subtask to create the mapping when the FeedTask runs
|
||||
stg_ftsk.wrapper_tasks[self.vios_uuid].add_functor_subtask(add_func)
|
||||
|
||||
def _mgmt_cna_to_vif(self, cna):
|
||||
"""Converts the mgmt CNA to VIF format for network injection."""
|
||||
mac = vm.norm_mac(cna.mac)
|
||||
ipv6_link_local = self._mac_to_link_local(mac)
|
||||
|
||||
subnet = network_model.Subnet(
|
||||
version=6, cidr=_LLA_SUBNET,
|
||||
ips=[network_model.FixedIP(address=ipv6_link_local)])
|
||||
network = network_model.Network(id='mgmt', subnets=[subnet],
|
||||
injected='yes')
|
||||
return network_model.VIF(id='mgmt_vif', address=mac,
|
||||
network=network)
|
||||
|
||||
@staticmethod
|
||||
def _mac_to_link_local(mac):
|
||||
# Convert the address to IPv6. The first step is to separate out the
|
||||
# mac address
|
||||
splits = mac.split(':')
|
||||
|
||||
# Create EUI-64 id per RFC 4291 Appendix A
|
||||
splits.insert(3, 'ff')
|
||||
splits.insert(4, 'fe')
|
||||
|
||||
# Create modified EUI-64 id via bit flip per RFC 4291 Appendix A
|
||||
splits[0] = "%.2x" % (int(splits[0], 16) ^ 0b00000010)
|
||||
|
||||
# Convert to the IPv6 link local format. The prefix is fe80::. Join
|
||||
# the hexes together at every other digit.
|
||||
ll = ['fe80:']
|
||||
ll.extend([splits[x] + splits[x + 1]
|
||||
for x in range(0, len(splits), 2)])
|
||||
return ':'.join(ll)
|
||||
|
||||
def dlt_vopt(self, instance, stg_ftsk):
|
||||
"""Deletes the virtual optical and scsi mappings for a VM.
|
||||
|
||||
|
259
nova/virt/powervm/tasks/network.py
Normal file
259
nova/virt/powervm/tasks/network.py
Normal file
@ -0,0 +1,259 @@
|
||||
# Copyright 2015, 2017 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.
|
||||
|
||||
import eventlet
|
||||
from oslo_log import log as logging
|
||||
from pypowervm.tasks import cna as pvm_cna
|
||||
from pypowervm.wrappers import managed_system as pvm_ms
|
||||
from pypowervm.wrappers import network as pvm_net
|
||||
from taskflow import task
|
||||
|
||||
from nova import conf as cfg
|
||||
from nova import exception
|
||||
from nova.virt.powervm import vif
|
||||
from nova.virt.powervm import vm
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
CONF = cfg.CONF
|
||||
|
||||
SECURE_RMC_VSWITCH = 'MGMTSWITCH'
|
||||
SECURE_RMC_VLAN = 4094
|
||||
|
||||
|
||||
class PlugVifs(task.Task):
|
||||
|
||||
"""The task to plug the Virtual Network Interfaces to a VM."""
|
||||
|
||||
def __init__(self, virt_api, adapter, instance, network_infos):
|
||||
"""Create the task.
|
||||
|
||||
Provides 'vm_cnas' - the list of the Virtual Machine's Client Network
|
||||
Adapters as they stand after all VIFs are plugged. May be None, in
|
||||
which case the Task requiring 'vm_cnas' should discover them afresh.
|
||||
|
||||
:param virt_api: The VirtAPI for the operation.
|
||||
:param adapter: The pypowervm adapter.
|
||||
:param instance: The nova instance.
|
||||
:param network_infos: The network information containing the nova
|
||||
VIFs to create.
|
||||
"""
|
||||
self.virt_api = virt_api
|
||||
self.adapter = adapter
|
||||
self.instance = instance
|
||||
self.network_infos = network_infos or []
|
||||
self.crt_network_infos, self.update_network_infos = [], []
|
||||
# Cache of CNAs that is filled on initial _vif_exists() call.
|
||||
self.cnas = None
|
||||
|
||||
super(PlugVifs, self).__init__(
|
||||
'plug_vifs', provides='vm_cnas', requires=['lpar_wrap'])
|
||||
|
||||
def _vif_exists(self, network_info):
|
||||
"""Does the instance have a CNA for a given net?
|
||||
|
||||
:param network_info: A network information dict. This method expects
|
||||
it to contain key 'address' (MAC address).
|
||||
:return: True if a CNA with the network_info's MAC address exists on
|
||||
the instance. False otherwise.
|
||||
"""
|
||||
if self.cnas is None:
|
||||
self.cnas = vm.get_cnas(self.adapter, self.instance)
|
||||
vifs = self.cnas
|
||||
|
||||
return network_info['address'] in [vm.norm_mac(v.mac) for v in vifs]
|
||||
|
||||
def execute(self, lpar_wrap):
|
||||
# Check to see if the LPAR is OK to add VIFs to.
|
||||
modifiable, reason = lpar_wrap.can_modify_io()
|
||||
if not modifiable:
|
||||
LOG.error("Unable to create VIF(s) for instance in the system's "
|
||||
"current state. The reason from the system is: %s",
|
||||
reason, instance=self.instance)
|
||||
raise exception.VirtualInterfaceCreateException()
|
||||
|
||||
# We will have two types of network infos. One is for newly created
|
||||
# vifs. The others are those that exist, but should be re-'treated'
|
||||
for network_info in self.network_infos:
|
||||
if self._vif_exists(network_info):
|
||||
self.update_network_infos.append(network_info)
|
||||
else:
|
||||
self.crt_network_infos.append(network_info)
|
||||
|
||||
# If there are no vifs to create or update, then just exit immediately.
|
||||
if not self.crt_network_infos and not self.update_network_infos:
|
||||
return []
|
||||
|
||||
# For existing VIFs that we just need to update, run the plug but do
|
||||
# not wait for the neutron event as that likely won't be sent (it was
|
||||
# already done).
|
||||
for network_info in self.update_network_infos:
|
||||
LOG.info("Updating VIF with mac %s for instance.",
|
||||
network_info['address'], instance=self.instance)
|
||||
vif.plug(self.adapter, self.instance, network_info, new_vif=False)
|
||||
|
||||
# For the new VIFs, run the creates (and wait for the events back)
|
||||
try:
|
||||
with self.virt_api.wait_for_instance_event(
|
||||
self.instance, self._get_vif_events(),
|
||||
deadline=CONF.vif_plugging_timeout,
|
||||
error_callback=self._vif_callback_failed):
|
||||
for network_info in self.crt_network_infos:
|
||||
LOG.info('Creating VIF with mac %s for instance.',
|
||||
network_info['address'], instance=self.instance)
|
||||
new_vif = vif.plug(
|
||||
self.adapter, self.instance, network_info,
|
||||
new_vif=True)
|
||||
if self.cnas is not None:
|
||||
self.cnas.append(new_vif)
|
||||
except eventlet.timeout.Timeout:
|
||||
LOG.error('Error waiting for VIF to be created for instance.',
|
||||
instance=self.instance)
|
||||
raise exception.VirtualInterfaceCreateException()
|
||||
|
||||
return self.cnas
|
||||
|
||||
def _vif_callback_failed(self, event_name, instance):
|
||||
LOG.error('VIF Plug failure for callback on event %s for instance.',
|
||||
event_name, instance=self.instance)
|
||||
if CONF.vif_plugging_is_fatal:
|
||||
raise exception.VirtualInterfaceCreateException()
|
||||
|
||||
def _get_vif_events(self):
|
||||
"""Returns the VIF events that need to be received for a VIF plug.
|
||||
|
||||
In order for a VIF plug to be successful, certain events should be
|
||||
received from other components within the OpenStack ecosystem. This
|
||||
method returns the events neutron needs for a given deploy.
|
||||
"""
|
||||
# See libvirt's driver.py -> _get_neutron_events method for
|
||||
# more information.
|
||||
if CONF.vif_plugging_is_fatal and CONF.vif_plugging_timeout:
|
||||
return [('network-vif-plugged', network_info['id'])
|
||||
for network_info in self.crt_network_infos
|
||||
if not network_info.get('active', True)]
|
||||
|
||||
def revert(self, lpar_wrap, result, flow_failures):
|
||||
if not self.network_infos:
|
||||
return
|
||||
|
||||
LOG.warning('VIF creation being rolled back for instance.',
|
||||
instance=self.instance)
|
||||
|
||||
# Get the current adapters on the system
|
||||
cna_w_list = vm.get_cnas(self.adapter, self.instance)
|
||||
for network_info in self.crt_network_infos:
|
||||
try:
|
||||
vif.unplug(self.adapter, self.instance, network_info,
|
||||
cna_w_list=cna_w_list)
|
||||
except Exception:
|
||||
LOG.exception("An exception occurred during an unplug in the "
|
||||
"vif rollback. Ignoring.",
|
||||
instance=self.instance)
|
||||
|
||||
|
||||
class UnplugVifs(task.Task):
|
||||
|
||||
"""The task to unplug Virtual Network Interfaces from a VM."""
|
||||
|
||||
def __init__(self, adapter, instance, network_infos):
|
||||
"""Create the task.
|
||||
|
||||
:param adapter: The pypowervm adapter.
|
||||
:param instance: The nova instance.
|
||||
:param network_infos: The network information containing the nova
|
||||
VIFs to create.
|
||||
"""
|
||||
self.adapter = adapter
|
||||
self.instance = instance
|
||||
self.network_infos = network_infos or []
|
||||
|
||||
super(UnplugVifs, self).__init__('unplug_vifs')
|
||||
|
||||
def execute(self):
|
||||
# If the LPAR is not in an OK state for deleting, then throw an
|
||||
# error up front.
|
||||
lpar_wrap = vm.get_instance_wrapper(self.adapter, self.instance)
|
||||
modifiable, reason = lpar_wrap.can_modify_io()
|
||||
if not modifiable:
|
||||
LOG.error("Unable to remove VIFs from instance in the system's "
|
||||
"current state. The reason reported by the system is: "
|
||||
"%s", reason, instance=self.instance)
|
||||
raise exception.VirtualInterfaceUnplugException(reason=reason)
|
||||
|
||||
# Get all the current Client Network Adapters (CNA) on the VM itself.
|
||||
cna_w_list = vm.get_cnas(self.adapter, self.instance)
|
||||
|
||||
# Walk through the VIFs and delete the corresponding CNA on the VM.
|
||||
for network_info in self.network_infos:
|
||||
vif.unplug(self.adapter, self.instance, network_info,
|
||||
cna_w_list=cna_w_list)
|
||||
|
||||
|
||||
class PlugMgmtVif(task.Task):
|
||||
|
||||
"""The task to plug the Management VIF into a VM."""
|
||||
|
||||
def __init__(self, adapter, instance):
|
||||
"""Create the task.
|
||||
|
||||
Requires 'vm_cnas' from PlugVifs. If None, this Task will retrieve the
|
||||
VM's list of CNAs.
|
||||
|
||||
Provides the mgmt_cna. This may be None if no management device was
|
||||
created. This is the CNA of the mgmt vif for the VM.
|
||||
|
||||
:param adapter: The pypowervm adapter.
|
||||
:param instance: The nova instance.
|
||||
"""
|
||||
self.adapter = adapter
|
||||
self.instance = instance
|
||||
|
||||
super(PlugMgmtVif, self).__init__(
|
||||
'plug_mgmt_vif', provides='mgmt_cna', requires=['vm_cnas'])
|
||||
|
||||
def execute(self, vm_cnas):
|
||||
LOG.info('Plugging the Management Network Interface to instance.',
|
||||
instance=self.instance)
|
||||
# Determine if we need to create the secure RMC VIF. This should only
|
||||
# be needed if there is not a VIF on the secure RMC vSwitch
|
||||
vswitch = None
|
||||
vswitches = pvm_net.VSwitch.search(
|
||||
self.adapter, parent_type=pvm_ms.System.schema_type,
|
||||
parent_uuid=self.adapter.sys_uuid, name=SECURE_RMC_VSWITCH)
|
||||
if len(vswitches) == 1:
|
||||
vswitch = vswitches[0]
|
||||
|
||||
if vswitch is None:
|
||||
LOG.warning('No management VIF created for instance due to lack '
|
||||
'of Management Virtual Switch', instance=self.instance)
|
||||
return None
|
||||
|
||||
# This next check verifies that there are no existing NICs on the
|
||||
# vSwitch, so that the VM does not end up with multiple RMC VIFs.
|
||||
if vm_cnas is None:
|
||||
has_mgmt_vif = vm.get_cnas(self.adapter, self.instance,
|
||||
vswitch_uri=vswitch.href)
|
||||
else:
|
||||
has_mgmt_vif = vswitch.href in [cna.vswitch_uri for cna in vm_cnas]
|
||||
|
||||
if has_mgmt_vif:
|
||||
LOG.debug('Management VIF already created for instance',
|
||||
instance=self.instance)
|
||||
return None
|
||||
|
||||
lpar_uuid = vm.get_pvm_uuid(self.instance)
|
||||
return pvm_cna.crt_cna(self.adapter, None, lpar_uuid, SECURE_RMC_VLAN,
|
||||
vswitch=SECURE_RMC_VSWITCH, crt_vswitch=True)
|
@ -146,8 +146,7 @@ class CreateAndConnectCfgDrive(task.Task):
|
||||
network_info, stg_ftsk, admin_pass=None):
|
||||
"""Create the Task that creates and connects the config drive.
|
||||
|
||||
Provides the 'cfg_drv_vscsi_map' which is an element to later map
|
||||
the vscsi drive.
|
||||
Requires the 'mgmt_cna'
|
||||
|
||||
:param adapter: The adapter for the pypowervm API
|
||||
:param instance: The nova instance
|
||||
@ -158,7 +157,8 @@ class CreateAndConnectCfgDrive(task.Task):
|
||||
:param admin_pass (Optional, Default None): Password to inject for the
|
||||
VM.
|
||||
"""
|
||||
super(CreateAndConnectCfgDrive, self).__init__(instance, 'cfg_drive')
|
||||
super(CreateAndConnectCfgDrive, self).__init__(
|
||||
instance, 'cfg_drive', requires=['mgmt_cna'])
|
||||
self.adapter = adapter
|
||||
self.instance = instance
|
||||
self.injected_files = injected_files
|
||||
@ -167,13 +167,13 @@ class CreateAndConnectCfgDrive(task.Task):
|
||||
self.ad_pass = admin_pass
|
||||
self.mb = None
|
||||
|
||||
def execute(self):
|
||||
def execute(self, mgmt_cna):
|
||||
self.mb = media.ConfigDrivePowerVM(self.adapter)
|
||||
self.mb.create_cfg_drv_vopt(self.instance, self.injected_files,
|
||||
self.network_info, self.stg_ftsk,
|
||||
admin_pass=self.ad_pass)
|
||||
admin_pass=self.ad_pass, mgmt_cna=mgmt_cna)
|
||||
|
||||
def revert(self, result, flow_failures):
|
||||
def revert(self, mgmt_cna, result, flow_failures):
|
||||
# No media builder, nothing to do
|
||||
if self.mb is None:
|
||||
return
|
||||
|
@ -48,7 +48,7 @@ class Create(task.Task):
|
||||
:param instance: The nova instance.
|
||||
:param stg_ftsk: FeedTask to defer storage connectivity operations.
|
||||
"""
|
||||
super(Create, self).__init__('crt_vm')
|
||||
super(Create, self).__init__('crt_vm', provides='lpar_wrap')
|
||||
self.instance = instance
|
||||
self.adapter = adapter
|
||||
self.host_wrapper = host_wrapper
|
||||
|
247
nova/virt/powervm/vif.py
Normal file
247
nova/virt/powervm/vif.py
Normal file
@ -0,0 +1,247 @@
|
||||
# Copyright 2016, 2017 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 oslo_config import cfg
|
||||
from oslo_log import log
|
||||
from oslo_utils import importutils
|
||||
from pypowervm import exceptions as pvm_ex
|
||||
from pypowervm.tasks import cna as pvm_cna
|
||||
from pypowervm.tasks import partition as pvm_par
|
||||
import six
|
||||
|
||||
from nova import exception
|
||||
from nova.network import model as network_model
|
||||
from nova.virt.powervm import vm
|
||||
|
||||
LOG = log.getLogger(__name__)
|
||||
|
||||
NOVALINK_VSWITCH = 'NovaLinkVEABridge'
|
||||
|
||||
VIF_TYPE_PVM_OVS = 'ovs'
|
||||
VIF_MAPPING = {VIF_TYPE_PVM_OVS:
|
||||
'nova.virt.powervm.vif.PvmOvsVifDriver'}
|
||||
|
||||
CONF = cfg.CONF
|
||||
|
||||
|
||||
def _build_vif_driver(adapter, instance, vif):
|
||||
"""Returns the appropriate VIF Driver for the given VIF.
|
||||
:param adapter: The pypowervm adapter API interface.
|
||||
:param instance: The nova instance.
|
||||
:param vif: The virtual interface.
|
||||
:return: The appropriate PvmVifDriver for the VIF.
|
||||
"""
|
||||
if vif.get('type') is None:
|
||||
LOG.exception("Failed to build vif driver. Missing vif type.",
|
||||
instance=instance)
|
||||
raise exception.VirtualInterfacePlugException()
|
||||
|
||||
# Check the type to the implementations
|
||||
if VIF_MAPPING.get(vif['type']):
|
||||
return importutils.import_object(
|
||||
VIF_MAPPING.get(vif['type']), adapter, instance)
|
||||
|
||||
# No matching implementation, raise error.
|
||||
LOG.exception("Failed to build vif driver. Invalid vif type provided.",
|
||||
instance=instance)
|
||||
raise exception.VirtualInterfacePlugException()
|
||||
|
||||
|
||||
def plug(adapter, instance, vif, new_vif=True):
|
||||
"""Plugs a virtual interface (network) into a VM.
|
||||
|
||||
:param adapter: The pypowervm adapter.
|
||||
:param instance: The nova instance object.
|
||||
:param vif: The virtual interface to plug into the instance.
|
||||
:param new_vif: (Optional, Default: True) If set, indicates that it is
|
||||
a brand new VIF. If False, it indicates that the VIF
|
||||
is already on the client but should be treated on the
|
||||
bridge.
|
||||
:return: The wrapper (CNA) representing the plugged virtual network. None
|
||||
if the vnet was not created.
|
||||
"""
|
||||
vif_drv = _build_vif_driver(adapter, instance, vif)
|
||||
|
||||
try:
|
||||
return vif_drv.plug(vif, new_vif=new_vif)
|
||||
except pvm_ex.HttpError:
|
||||
LOG.exception('VIF plug failed for instance.', instance=instance)
|
||||
raise exception.VirtualInterfacePlugException()
|
||||
# Other exceptions are (hopefully) custom VirtualInterfacePlugException
|
||||
# generated lower in the call stack.
|
||||
|
||||
|
||||
def unplug(adapter, instance, vif, cna_w_list=None):
|
||||
"""Unplugs a virtual interface (network) from a VM.
|
||||
|
||||
:param adapter: The pypowervm adapter.
|
||||
:param instance: The nova instance object.
|
||||
:param vif: The virtual interface to plug into the instance.
|
||||
:param cna_w_list: (Optional, Default: None) The list of Client Network
|
||||
Adapters from pypowervm. Providing this input
|
||||
allows for an improvement in operation speed.
|
||||
"""
|
||||
vif_drv = _build_vif_driver(adapter, instance, vif)
|
||||
try:
|
||||
vif_drv.unplug(vif, cna_w_list=cna_w_list)
|
||||
except pvm_ex.HttpError as he:
|
||||
LOG.exception('VIF unplug failed for instance', instance=instance)
|
||||
raise exception.VirtualInterfaceUnplugException(reason=he.args[0])
|
||||
|
||||
|
||||
class PvmOvsVifDriver(object):
|
||||
"""The Open vSwitch VIF driver for PowerVM."""
|
||||
|
||||
def __init__(self, adapter, instance):
|
||||
"""Initializes a VIF Driver.
|
||||
|
||||
:param adapter: The pypowervm adapter API interface.
|
||||
:param instance: The nova instance that the vif action will be run
|
||||
against.
|
||||
"""
|
||||
self.adapter = adapter
|
||||
self.instance = instance
|
||||
|
||||
def plug(self, vif, new_vif=True):
|
||||
"""Plugs a virtual interface (network) into a VM.
|
||||
|
||||
Creates a 'peer to peer' connection between the Management partition
|
||||
hosting the Linux I/O and the client VM. There will be one trunk
|
||||
adapter for a given client adapter.
|
||||
|
||||
The device will be 'up' on the mgmt partition.
|
||||
|
||||
Will make sure that the trunk device has the appropriate metadata (e.g.
|
||||
port id) set on it so that the Open vSwitch agent picks it up properly.
|
||||
|
||||
:param vif: The virtual interface to plug into the instance.
|
||||
:param new_vif: (Optional, Default: True) If set, indicates that it is
|
||||
a brand new VIF. If False, it indicates that the VIF
|
||||
is already on the client but should be treated on the
|
||||
bridge.
|
||||
:return: The new vif that was created. Only returned if new_vif is
|
||||
set to True. Otherwise None is expected.
|
||||
"""
|
||||
|
||||
# Create the trunk and client adapter.
|
||||
lpar_uuid = vm.get_pvm_uuid(self.instance)
|
||||
mgmt_uuid = pvm_par.get_this_partition(self.adapter).uuid
|
||||
|
||||
mtu = vif['network'].get_meta('mtu')
|
||||
if 'devname' in vif:
|
||||
dev_name = vif['devname']
|
||||
else:
|
||||
dev_name = ("nic" + vif['id'])[:network_model.NIC_NAME_LEN]
|
||||
|
||||
meta_attrs = ','.join([
|
||||
'iface-id=%s' % (vif.get('ovs_interfaceid') or vif['id']),
|
||||
'iface-status=active',
|
||||
'attached-mac=%s' % vif['address'],
|
||||
'vm-uuid=%s' % self.instance.uuid])
|
||||
|
||||
if new_vif:
|
||||
return pvm_cna.crt_p2p_cna(
|
||||
self.adapter, None, lpar_uuid, [mgmt_uuid], NOVALINK_VSWITCH,
|
||||
crt_vswitch=True, mac_addr=vif['address'], dev_name=dev_name,
|
||||
ovs_bridge=vif['network']['bridge'],
|
||||
ovs_ext_ids=meta_attrs, configured_mtu=mtu)[0]
|
||||
else:
|
||||
# Bug : https://bugs.launchpad.net/nova-powervm/+bug/1731548
|
||||
# When a host is rebooted, something is discarding tap devices for
|
||||
# VMs deployed with OVS vif. To prevent VMs losing network
|
||||
# connectivity, this is fixed by recreating the tap devices during
|
||||
# init of the nova compute service, which will call vif plug with
|
||||
# new_vif==False.
|
||||
|
||||
# Find the CNA for this vif.
|
||||
# TODO(esberglu) improve performance by caching VIOS wrapper(s) and
|
||||
# CNA lists (in case >1 vif per VM).
|
||||
cna_w_list = vm.get_cnas(self.adapter, self.instance)
|
||||
cna_w = self._find_cna_for_vif(cna_w_list, vif)
|
||||
if not cna_w:
|
||||
LOG.warning('Unable to plug VIF with mac %s for instance. The '
|
||||
'VIF was not found on the instance.',
|
||||
vif['address'], instance=self.instance)
|
||||
return None
|
||||
|
||||
# Find the corresponding trunk adapter
|
||||
trunks = pvm_cna.find_trunks(self.adapter, cna_w)
|
||||
for trunk in trunks:
|
||||
# Set MTU, OVS external ids, and OVS bridge metadata
|
||||
trunk.configured_mtu = mtu
|
||||
trunk.ovs_ext_ids = meta_attrs
|
||||
trunk.ovs_bridge = vif['network']['bridge']
|
||||
# Updating the trunk adapter will cause NovaLink to reassociate
|
||||
# the tap device.
|
||||
trunk.update()
|
||||
|
||||
def unplug(self, vif, cna_w_list=None):
|
||||
"""Unplugs a virtual interface (network) from a VM.
|
||||
|
||||
This will remove the adapter from the Open vSwitch and delete the
|
||||
trunk adapters.
|
||||
|
||||
:param vif: The virtual interface to plug into the instance.
|
||||
:param cna_w_list: (Optional, Default: None) The list of Client Network
|
||||
Adapters from pypowervm. Providing this input
|
||||
allows for an improvement in operation speed.
|
||||
:return cna_w: The deleted Client Network Adapter or None if the CNA
|
||||
is not found.
|
||||
"""
|
||||
# Need to find the adapters if they were not provided
|
||||
if not cna_w_list:
|
||||
cna_w_list = vm.get_cnas(self.adapter, self.instance)
|
||||
|
||||
# Find the CNA for this vif.
|
||||
cna_w = self._find_cna_for_vif(cna_w_list, vif)
|
||||
|
||||
if not cna_w:
|
||||
LOG.warning('Unable to unplug VIF with mac %s for instance. The '
|
||||
'VIF was not found on the instance.', vif['address'],
|
||||
instance=self.instance)
|
||||
return None
|
||||
|
||||
# Find and delete the trunk adapters
|
||||
trunks = pvm_cna.find_trunks(self.adapter, cna_w)
|
||||
for trunk in trunks:
|
||||
trunk.delete()
|
||||
|
||||
# Now delete the client CNA
|
||||
LOG.info('Deleting VIF with mac %s for instance.', vif['address'],
|
||||
instance=self.instance)
|
||||
try:
|
||||
cna_w.delete()
|
||||
except Exception as e:
|
||||
LOG.error('Unable to unplug VIF with mac %s for instance.',
|
||||
vif['address'], instance=self.instance)
|
||||
raise exception.VirtualInterfaceUnplugException(
|
||||
reason=six.text_type(e))
|
||||
return cna_w
|
||||
|
||||
@staticmethod
|
||||
def _find_cna_for_vif(cna_w_list, vif):
|
||||
"""Finds the PowerVM CNA for a given Nova VIF.
|
||||
|
||||
:param cna_w_list: The list of Client Network Adapter wrappers from
|
||||
pypowervm.
|
||||
:param vif: The Nova Virtual Interface (virtual network interface).
|
||||
:return: The CNA that corresponds to the VIF. None if one is not
|
||||
part of the cna_w_list.
|
||||
"""
|
||||
for cna_w in cna_w_list:
|
||||
if vm.norm_mac(cna_w.mac) == vif['address']:
|
||||
return cna_w
|
||||
return None
|
@ -30,6 +30,7 @@ from pypowervm.utils import uuid as pvm_uuid
|
||||
from pypowervm.utils import validation as pvm_vldn
|
||||
from pypowervm.wrappers import base_partition as pvm_bp
|
||||
from pypowervm.wrappers import logical_partition as pvm_lpar
|
||||
from pypowervm.wrappers import network as pvm_net
|
||||
from pypowervm.wrappers import shared_proc_pool as pvm_spp
|
||||
import six
|
||||
|
||||
@ -73,6 +74,23 @@ _POWERVM_TO_NOVA_STATE = {
|
||||
pvm_bp.LPARState.ERROR: power_state.CRASHED}
|
||||
|
||||
|
||||
def get_cnas(adapter, instance, **search):
|
||||
"""Returns the (possibly filtered) current CNAs on the instance.
|
||||
|
||||
The Client Network Adapters are the Ethernet adapters for a VM.
|
||||
|
||||
:param adapter: The pypowervm adapter.
|
||||
:param instance: The nova instance.
|
||||
:param search: Keyword arguments for CNA.search. If omitted, all CNAs are
|
||||
returned.
|
||||
:return The CNA wrappers that represent the ClientNetworkAdapters on the VM
|
||||
"""
|
||||
meth = pvm_net.CNA.search if search else pvm_net.CNA.get
|
||||
|
||||
return meth(adapter, parent_type=pvm_lpar.LPAR,
|
||||
parent_uuid=get_pvm_uuid(instance), **search)
|
||||
|
||||
|
||||
def get_lpar_names(adp):
|
||||
"""Get a list of the LPAR names.
|
||||
|
||||
@ -326,6 +344,21 @@ def get_vm_info(adapter, instance):
|
||||
return hardware.InstanceInfo(nova_state)
|
||||
|
||||
|
||||
def norm_mac(mac):
|
||||
"""Normalizes a MAC address from pypowervm format to OpenStack.
|
||||
|
||||
That means that the format will be converted to lower case and will
|
||||
have colons added.
|
||||
|
||||
:param mac: A pypowervm mac address. Ex. 1234567890AB
|
||||
:return: A mac that matches the standard neutron format.
|
||||
Ex. 12:34:56:78:90:ab
|
||||
"""
|
||||
# Need the replacement if the mac is already normalized.
|
||||
mac = mac.lower().replace(':', '')
|
||||
return ':'.join(mac[i:i + 2] for i in range(0, len(mac), 2))
|
||||
|
||||
|
||||
class VMBuilder(object):
|
||||
"""Converts a Nova Instance/Flavor into a pypowervm LPARBuilder."""
|
||||
_PVM_PROC_COMPAT = 'powervm:processor_compatibility'
|
||||
|
Loading…
x
Reference in New Issue
Block a user