Merge "PowerVM driver: ovs vif"

This commit is contained in:
Zuul 2018-01-17 18:15:26 +00:00 committed by Gerrit Code Review
commit c03bbc142f
14 changed files with 1231 additions and 26 deletions

View File

@ -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

View 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))

View File

@ -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):

View File

@ -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)

View File

@ -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))

View 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)

View File

@ -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"))

View File

@ -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

View File

@ -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.

View 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)

View File

@ -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

View File

@ -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
View 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

View File

@ -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'