Add vif driver framework
This change set adds a new vif driver interface (as well as SEA implementation) for the nova-powervm driver. This is important as the platform looks to new VIF types (ex. qbg or ovs). Change-Id: Ieb250d539e4adad8977d3a2716c2f1cf341c4137
This commit is contained in:
parent
ebbb9ec623
commit
01a452be03
@ -1,4 +1,4 @@
|
|||||||
# Copyright 2015 IBM Corp.
|
# Copyright 2015, 2016 IBM Corp.
|
||||||
#
|
#
|
||||||
# All Rights Reserved.
|
# All Rights Reserved.
|
||||||
#
|
#
|
||||||
@ -43,13 +43,13 @@ class TestNetwork(test.TestCase):
|
|||||||
self.mock_lpar_wrap = mock.MagicMock()
|
self.mock_lpar_wrap = mock.MagicMock()
|
||||||
self.mock_lpar_wrap.can_modify_io.return_value = True, None
|
self.mock_lpar_wrap.can_modify_io.return_value = True, None
|
||||||
|
|
||||||
|
@mock.patch('nova_powervm.virt.powervm.vif.unplug')
|
||||||
@mock.patch('nova_powervm.virt.powervm.vm.get_cnas')
|
@mock.patch('nova_powervm.virt.powervm.vm.get_cnas')
|
||||||
def test_unplug_vifs(self, mock_vm_get):
|
def test_unplug_vifs(self, mock_vm_get, mock_unplug):
|
||||||
"""Tests that a delete of the vif can be done."""
|
"""Tests that a delete of the vif can be done."""
|
||||||
inst = objects.Instance(**powervm.TEST_INSTANCE)
|
inst = objects.Instance(**powervm.TEST_INSTANCE)
|
||||||
|
|
||||||
# Mock up the CNA response. One should already exist, the other
|
# Mock up the CNA responses.
|
||||||
# should not.
|
|
||||||
cnas = [cna('AABBCCDDEEFF'), cna('AABBCCDDEE11'), cna('AABBCCDDEE22')]
|
cnas = [cna('AABBCCDDEEFF'), cna('AABBCCDDEE11'), cna('AABBCCDDEE22')]
|
||||||
mock_vm_get.return_value = cnas
|
mock_vm_get.return_value = cnas
|
||||||
|
|
||||||
@ -60,15 +60,24 @@ class TestNetwork(test.TestCase):
|
|||||||
{'address': 'aa:bb:cc:dd:ee:33'}
|
{'address': 'aa:bb:cc:dd:ee:33'}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
# Mock out the vif driver
|
||||||
|
def validate_unplug(adapter, host_uuid, instance, vif,
|
||||||
|
cna_w_list=None):
|
||||||
|
self.assertEqual(adapter, self.apt)
|
||||||
|
self.assertEqual('host_uuid', host_uuid)
|
||||||
|
self.assertEqual(instance, inst)
|
||||||
|
self.assertIn(vif, net_info)
|
||||||
|
self.assertEqual(cna_w_list, cnas)
|
||||||
|
|
||||||
|
mock_unplug.side_effect = validate_unplug
|
||||||
|
|
||||||
# Run method
|
# Run method
|
||||||
p_vifs = tf_net.UnplugVifs(self.apt, inst, net_info, 'host_uuid')
|
p_vifs = tf_net.UnplugVifs(self.apt, inst, net_info, 'host_uuid')
|
||||||
p_vifs.execute(self.mock_lpar_wrap)
|
p_vifs.execute(self.mock_lpar_wrap)
|
||||||
|
|
||||||
# The delete should have only been called once. The second CNA didn't
|
# Make sure the unplug was invoked, so that we know that the validation
|
||||||
# have a matching mac...so it should be skipped.
|
# code was called
|
||||||
self.assertEqual(1, cnas[0].delete.call_count)
|
self.assertEqual(3, mock_unplug.call_count)
|
||||||
self.assertEqual(0, cnas[1].delete.call_count)
|
|
||||||
self.assertEqual(1, cnas[2].delete.call_count)
|
|
||||||
|
|
||||||
def test_unplug_vifs_invalid_state(self):
|
def test_unplug_vifs_invalid_state(self):
|
||||||
"""Tests that the delete raises an exception if bad VM state."""
|
"""Tests that the delete raises an exception if bad VM state."""
|
||||||
@ -82,9 +91,9 @@ class TestNetwork(test.TestCase):
|
|||||||
self.assertRaises(tf_net.VirtualInterfaceUnplugException,
|
self.assertRaises(tf_net.VirtualInterfaceUnplugException,
|
||||||
p_vifs.execute, self.mock_lpar_wrap)
|
p_vifs.execute, self.mock_lpar_wrap)
|
||||||
|
|
||||||
@mock.patch('nova_powervm.virt.powervm.vm.crt_vif')
|
@mock.patch('nova_powervm.virt.powervm.vif.plug')
|
||||||
@mock.patch('nova_powervm.virt.powervm.vm.get_cnas')
|
@mock.patch('nova_powervm.virt.powervm.vm.get_cnas')
|
||||||
def test_plug_vifs_rmc(self, mock_vm_get, mock_vm_crt):
|
def test_plug_vifs_rmc(self, mock_vm_get, mock_plug):
|
||||||
"""Tests that a crt vif can be done with secure RMC."""
|
"""Tests that a crt vif can be done with secure RMC."""
|
||||||
inst = objects.Instance(**powervm.TEST_INSTANCE)
|
inst = objects.Instance(**powervm.TEST_INSTANCE)
|
||||||
|
|
||||||
@ -105,11 +114,11 @@ class TestNetwork(test.TestCase):
|
|||||||
p_vifs.execute(self.mock_lpar_wrap)
|
p_vifs.execute(self.mock_lpar_wrap)
|
||||||
|
|
||||||
# The create should have only been called once.
|
# The create should have only been called once.
|
||||||
self.assertEqual(2, mock_vm_crt.call_count)
|
self.assertEqual(2, mock_plug.call_count)
|
||||||
|
|
||||||
@mock.patch('nova_powervm.virt.powervm.vm.crt_vif')
|
@mock.patch('nova_powervm.virt.powervm.vif.plug')
|
||||||
@mock.patch('nova_powervm.virt.powervm.vm.get_cnas')
|
@mock.patch('nova_powervm.virt.powervm.vm.get_cnas')
|
||||||
def test_plug_vifs_rmc_no_create(self, mock_vm_get, mock_vm_crt):
|
def test_plug_vifs_rmc_no_create(self, mock_vm_get, mock_plug):
|
||||||
"""Verifies if no creates are needed, none are done."""
|
"""Verifies if no creates are needed, none are done."""
|
||||||
inst = objects.Instance(**powervm.TEST_INSTANCE)
|
inst = objects.Instance(**powervm.TEST_INSTANCE)
|
||||||
|
|
||||||
@ -129,16 +138,16 @@ class TestNetwork(test.TestCase):
|
|||||||
|
|
||||||
# The create should not have been called. The response should have
|
# The create should not have been called. The response should have
|
||||||
# been empty.
|
# been empty.
|
||||||
self.assertEqual(0, mock_vm_crt.call_count)
|
self.assertEqual(0, mock_plug.call_count)
|
||||||
self.assertEqual([], resp)
|
self.assertEqual([], resp)
|
||||||
|
|
||||||
# State check shouldn't have even been invoked as no creates were
|
# State check shouldn't have even been invoked as no creates were
|
||||||
# required
|
# required
|
||||||
self.assertEqual(0, self.mock_lpar_wrap.can_modify_io.call_count)
|
self.assertEqual(0, self.mock_lpar_wrap.can_modify_io.call_count)
|
||||||
|
|
||||||
@mock.patch('nova_powervm.virt.powervm.vm.crt_vif')
|
@mock.patch('nova_powervm.virt.powervm.vif.plug')
|
||||||
@mock.patch('nova_powervm.virt.powervm.vm.get_cnas')
|
@mock.patch('nova_powervm.virt.powervm.vm.get_cnas')
|
||||||
def test_plug_vifs_invalid_state(self, mock_vm_get, mock_vm_crt):
|
def test_plug_vifs_invalid_state(self, mock_vm_get, mock_plug):
|
||||||
"""Tests that a crt_vif fails when the LPAR state is bad."""
|
"""Tests that a crt_vif fails when the LPAR state is bad."""
|
||||||
inst = objects.Instance(**powervm.TEST_INSTANCE)
|
inst = objects.Instance(**powervm.TEST_INSTANCE)
|
||||||
|
|
||||||
@ -156,11 +165,11 @@ class TestNetwork(test.TestCase):
|
|||||||
p_vifs.execute, self.mock_lpar_wrap)
|
p_vifs.execute, self.mock_lpar_wrap)
|
||||||
|
|
||||||
# The create should not have been invoked
|
# The create should not have been invoked
|
||||||
self.assertEqual(0, mock_vm_crt.call_count)
|
self.assertEqual(0, mock_plug.call_count)
|
||||||
|
|
||||||
@mock.patch('nova_powervm.virt.powervm.vm.crt_vif')
|
@mock.patch('nova_powervm.virt.powervm.vif.plug')
|
||||||
@mock.patch('nova_powervm.virt.powervm.vm.get_cnas')
|
@mock.patch('nova_powervm.virt.powervm.vm.get_cnas')
|
||||||
def test_plug_vifs_timeout(self, mock_vm_get, mock_vm_crt):
|
def test_plug_vifs_timeout(self, mock_vm_get, mock_plug):
|
||||||
"""Tests that crt vif failure via loss of neutron callback."""
|
"""Tests that crt vif failure via loss of neutron callback."""
|
||||||
inst = objects.Instance(**powervm.TEST_INSTANCE)
|
inst = objects.Instance(**powervm.TEST_INSTANCE)
|
||||||
|
|
||||||
@ -171,7 +180,7 @@ class TestNetwork(test.TestCase):
|
|||||||
net_info = [{'address': 'aa:bb:cc:dd:ee:ff'}]
|
net_info = [{'address': 'aa:bb:cc:dd:ee:ff'}]
|
||||||
|
|
||||||
# Ensure that an exception is raised by a timeout.
|
# Ensure that an exception is raised by a timeout.
|
||||||
mock_vm_crt.side_effect = eventlet.timeout.Timeout()
|
mock_plug.side_effect = eventlet.timeout.Timeout()
|
||||||
|
|
||||||
# Run method
|
# Run method
|
||||||
p_vifs = tf_net.PlugVifs(mock.MagicMock(), self.apt, inst, net_info,
|
p_vifs = tf_net.PlugVifs(mock.MagicMock(), self.apt, inst, net_info,
|
||||||
@ -180,13 +189,14 @@ class TestNetwork(test.TestCase):
|
|||||||
p_vifs.execute, self.mock_lpar_wrap)
|
p_vifs.execute, self.mock_lpar_wrap)
|
||||||
|
|
||||||
# The create should have only been called once.
|
# The create should have only been called once.
|
||||||
self.assertEqual(1, mock_vm_crt.call_count)
|
self.assertEqual(1, mock_plug.call_count)
|
||||||
|
|
||||||
@mock.patch('nova_powervm.virt.powervm.vm.crt_vif')
|
@mock.patch('nova_powervm.virt.powervm.vif.plug')
|
||||||
@mock.patch('nova_powervm.virt.powervm.vm.get_cnas')
|
@mock.patch('nova_powervm.virt.powervm.vm.get_cnas')
|
||||||
def test_plug_vifs_diff_host(self, mock_vm_get, mock_vm_crt):
|
def test_plug_vifs_diff_host(self, mock_vm_get, mock_plug):
|
||||||
"""Tests that crt vif handles bad inst.host value."""
|
"""Tests that crt vif handles bad inst.host value."""
|
||||||
inst = powervm.TEST_INST1
|
inst = powervm.TEST_INST1
|
||||||
|
|
||||||
# Set this up as a different host from the inst.host
|
# Set this up as a different host from the inst.host
|
||||||
self.flags(host='host2')
|
self.flags(host='host2')
|
||||||
|
|
||||||
@ -203,20 +213,21 @@ class TestNetwork(test.TestCase):
|
|||||||
p_vifs.execute(self.mock_lpar_wrap)
|
p_vifs.execute(self.mock_lpar_wrap)
|
||||||
|
|
||||||
# The create should have only been called once.
|
# The create should have only been called once.
|
||||||
self.assertEqual(1, mock_vm_crt.call_count)
|
self.assertEqual(1, mock_plug.call_count)
|
||||||
# Should have called save to save the new host and then changed it back
|
# Should have called save to save the new host and then changed it back
|
||||||
self.assertEqual(2, mock_inst_save.call_count)
|
self.assertEqual(2, mock_inst_save.call_count)
|
||||||
self.assertEqual('host1', inst.host)
|
self.assertEqual('host1', inst.host)
|
||||||
|
|
||||||
@mock.patch('nova_powervm.virt.powervm.vm.crt_vif')
|
@mock.patch('nova_powervm.virt.powervm.vif.plug')
|
||||||
@mock.patch('nova_powervm.virt.powervm.vm.get_cnas')
|
@mock.patch('nova_powervm.virt.powervm.vm.get_cnas')
|
||||||
def test_plug_vifs_diff_host_except(self, mock_vm_get, mock_vm_crt):
|
def test_plug_vifs_diff_host_except(self, mock_vm_get, mock_plug):
|
||||||
"""Tests that crt vif handles bad inst.host value.
|
"""Tests that crt vif handles bad inst.host value.
|
||||||
|
|
||||||
This test ensures that if we get a timeout exception we still reset
|
This test ensures that if we get a timeout exception we still reset
|
||||||
the inst.host value back to the original value
|
the inst.host value back to the original value
|
||||||
"""
|
"""
|
||||||
inst = powervm.TEST_INST1
|
inst = powervm.TEST_INST1
|
||||||
|
|
||||||
# Set this up as a different host from the inst.host
|
# Set this up as a different host from the inst.host
|
||||||
self.flags(host='host2')
|
self.flags(host='host2')
|
||||||
|
|
||||||
@ -227,7 +238,7 @@ class TestNetwork(test.TestCase):
|
|||||||
net_info = [{'address': 'aa:bb:cc:dd:ee:ff'}]
|
net_info = [{'address': 'aa:bb:cc:dd:ee:ff'}]
|
||||||
|
|
||||||
# Ensure that an exception is raised by a timeout.
|
# Ensure that an exception is raised by a timeout.
|
||||||
mock_vm_crt.side_effect = eventlet.timeout.Timeout()
|
mock_plug.side_effect = eventlet.timeout.Timeout()
|
||||||
|
|
||||||
# Run method
|
# Run method
|
||||||
p_vifs = tf_net.PlugVifs(mock.MagicMock(), self.apt, inst, net_info,
|
p_vifs = tf_net.PlugVifs(mock.MagicMock(), self.apt, inst, net_info,
|
||||||
@ -237,17 +248,17 @@ class TestNetwork(test.TestCase):
|
|||||||
p_vifs.execute, self.mock_lpar_wrap)
|
p_vifs.execute, self.mock_lpar_wrap)
|
||||||
|
|
||||||
# The create should have only been called once.
|
# The create should have only been called once.
|
||||||
self.assertEqual(1, mock_vm_crt.call_count)
|
self.assertEqual(1, mock_plug.call_count)
|
||||||
# Should have called save to save the new host and then changed it back
|
# Should have called save to save the new host and then changed it back
|
||||||
self.assertEqual(2, mock_inst_save.call_count)
|
self.assertEqual(2, mock_inst_save.call_count)
|
||||||
self.assertEqual('host1', inst.host)
|
self.assertEqual('host1', inst.host)
|
||||||
|
|
||||||
@mock.patch('nova_powervm.virt.powervm.vm.crt_secure_rmc_vif')
|
@mock.patch('nova_powervm.virt.powervm.vif.plug_secure_rmc_vif')
|
||||||
@mock.patch('nova_powervm.virt.powervm.vm.get_secure_rmc_vswitch')
|
@mock.patch('nova_powervm.virt.powervm.vif.get_secure_rmc_vswitch')
|
||||||
@mock.patch('nova_powervm.virt.powervm.vm.crt_vif')
|
@mock.patch('nova_powervm.virt.powervm.vif.plug')
|
||||||
@mock.patch('nova_powervm.virt.powervm.vm.get_cnas')
|
@mock.patch('nova_powervm.virt.powervm.vm.get_cnas')
|
||||||
def test_plug_mgmt_vif(self, mock_vm_get, mock_vm_crt,
|
def test_plug_mgmt_vif(self, mock_vm_get, mock_plug,
|
||||||
mock_get_rmc_vswitch, mock_crt_rmc_vif):
|
mock_get_rmc_vswitch, mock_plug_rmc_vif):
|
||||||
"""Tests that a mgmt vif can be created."""
|
"""Tests that a mgmt vif can be created."""
|
||||||
inst = objects.Instance(**powervm.TEST_INSTANCE)
|
inst = objects.Instance(**powervm.TEST_INSTANCE)
|
||||||
|
|
||||||
@ -261,7 +272,7 @@ class TestNetwork(test.TestCase):
|
|||||||
p_vifs.execute([])
|
p_vifs.execute([])
|
||||||
|
|
||||||
# The create should have only been called once.
|
# The create should have only been called once.
|
||||||
self.assertEqual(1, mock_crt_rmc_vif.call_count)
|
self.assertEqual(1, mock_plug_rmc_vif.call_count)
|
||||||
|
|
||||||
@mock.patch('nova.utils.is_neutron')
|
@mock.patch('nova.utils.is_neutron')
|
||||||
def test_get_vif_events(self, mock_is_neutron):
|
def test_get_vif_events(self, mock_is_neutron):
|
||||||
|
@ -1187,13 +1187,13 @@ class TestPowerVMDriver(test.TestCase):
|
|||||||
self.assertIsNotNone(value)
|
self.assertIsNotNone(value)
|
||||||
|
|
||||||
@mock.patch('pypowervm.wrappers.logical_partition.LPAR.can_modify_io')
|
@mock.patch('pypowervm.wrappers.logical_partition.LPAR.can_modify_io')
|
||||||
@mock.patch('nova_powervm.virt.powervm.vm.crt_secure_rmc_vif')
|
@mock.patch('nova_powervm.virt.powervm.vif.plug_secure_rmc_vif')
|
||||||
@mock.patch('nova_powervm.virt.powervm.vm.get_secure_rmc_vswitch')
|
@mock.patch('nova_powervm.virt.powervm.vif.get_secure_rmc_vswitch')
|
||||||
@mock.patch('nova_powervm.virt.powervm.vm.crt_vif')
|
@mock.patch('nova_powervm.virt.powervm.vif.plug')
|
||||||
@mock.patch('nova_powervm.virt.powervm.vm.get_cnas')
|
@mock.patch('nova_powervm.virt.powervm.vm.get_cnas')
|
||||||
def test_plug_vifs(
|
def test_plug_vifs(
|
||||||
self, mock_vm_get, mock_vm_crt, mock_get_rmc_vswitch, mock_crt_rmc_vif,
|
self, mock_vm_get, mock_plug_vif, mock_get_rmc_vswitch,
|
||||||
mock_can_modify_io):
|
mock_plug_rmc_vif, mock_can_modify_io):
|
||||||
# Mock up the CNA response
|
# Mock up the CNA response
|
||||||
cnas = [mock.MagicMock(), mock.MagicMock()]
|
cnas = [mock.MagicMock(), mock.MagicMock()]
|
||||||
cnas[0].mac = 'AABBCCDDEEFF'
|
cnas[0].mac = 'AABBCCDDEEFF'
|
||||||
@ -1220,8 +1220,8 @@ class TestPowerVMDriver(test.TestCase):
|
|||||||
|
|
||||||
# The create should have only been called once. The other was already
|
# The create should have only been called once. The other was already
|
||||||
# existing.
|
# existing.
|
||||||
self.assertEqual(1, mock_vm_crt.call_count)
|
self.assertEqual(1, mock_plug_vif.call_count)
|
||||||
self.assertEqual(0, mock_crt_rmc_vif.call_count)
|
self.assertEqual(0, mock_plug_rmc_vif.call_count)
|
||||||
|
|
||||||
@mock.patch('nova_powervm.virt.powervm.tasks.vm.Get')
|
@mock.patch('nova_powervm.virt.powervm.tasks.vm.Get')
|
||||||
def test_plug_vif_failures(self, mock_vm):
|
def test_plug_vif_failures(self, mock_vm):
|
||||||
|
144
nova_powervm/tests/virt/powervm/test_vif.py
Normal file
144
nova_powervm/tests/virt/powervm/test_vif.py
Normal file
@ -0,0 +1,144 @@
|
|||||||
|
# Copyright 2016 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 nova import exception
|
||||||
|
from nova import test
|
||||||
|
from pypowervm.tests import test_fixtures as pvm_fx
|
||||||
|
from pypowervm.wrappers import managed_system as pvm_ms
|
||||||
|
from pypowervm.wrappers import network as pvm_net
|
||||||
|
|
||||||
|
from nova_powervm.virt.powervm import vif
|
||||||
|
|
||||||
|
|
||||||
|
def cna(mac):
|
||||||
|
"""Builds a mock Client Network Adapter for unit tests."""
|
||||||
|
nic = mock.MagicMock()
|
||||||
|
nic.mac = mac
|
||||||
|
nic.vswitch_uri = 'fake_href'
|
||||||
|
return nic
|
||||||
|
|
||||||
|
|
||||||
|
class TestVifFunctions(test.TestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(TestVifFunctions, self).setUp()
|
||||||
|
|
||||||
|
self.adpt = self.useFixture(pvm_fx.AdapterFx(
|
||||||
|
traits=pvm_fx.LocalPVMTraits)).adpt
|
||||||
|
|
||||||
|
@mock.patch('pypowervm.wrappers.network.VSwitch.search')
|
||||||
|
def test_get_secure_rmc_vswitch(self, mock_search):
|
||||||
|
# Test no data coming back gets none
|
||||||
|
mock_search.return_value = []
|
||||||
|
resp = vif.get_secure_rmc_vswitch(self.adpt, 'host_uuid')
|
||||||
|
self.assertIsNone(resp)
|
||||||
|
|
||||||
|
# Mock that a couple vswitches get returned, but only the correct
|
||||||
|
# MGMT Switch gets returned
|
||||||
|
mock_vs = mock.MagicMock()
|
||||||
|
mock_vs.name = 'MGMTSWITCH'
|
||||||
|
mock_search.return_value = [mock_vs]
|
||||||
|
self.assertEqual(mock_vs,
|
||||||
|
vif.get_secure_rmc_vswitch(self.adpt, 'host_uuid'))
|
||||||
|
mock_search.assert_called_with(
|
||||||
|
self.adpt, parent_type=pvm_ms.System.schema_type,
|
||||||
|
parent_uuid='host_uuid', name=vif.SECURE_RMC_VSWITCH)
|
||||||
|
|
||||||
|
@mock.patch('pypowervm.tasks.cna.crt_cna')
|
||||||
|
@mock.patch('nova_powervm.virt.powervm.vm.get_pvm_uuid')
|
||||||
|
def test_plug_secure_rmc_vif(self, mock_pvm_uuid, mock_crt):
|
||||||
|
mock_pvm_uuid.return_value = 'lpar_uuid'
|
||||||
|
vif.plug_secure_rmc_vif(self.adpt, 'instance', 'host_uuid')
|
||||||
|
mock_crt.assert_called_once_with(
|
||||||
|
self.adpt, 'host_uuid', 'lpar_uuid', 4094, vswitch='MGMTSWITCH',
|
||||||
|
crt_vswitch=True)
|
||||||
|
|
||||||
|
def test_build_vif_driver(self):
|
||||||
|
# Test the Shared Ethernet Adapter type VIF
|
||||||
|
mock_inst = mock.MagicMock()
|
||||||
|
mock_inst.name = 'instance'
|
||||||
|
self.assertIsInstance(
|
||||||
|
vif._build_vif_driver(self.adpt, 'host_uuid', mock_inst,
|
||||||
|
{'type': 'pvm_sea'}),
|
||||||
|
vif.PvmSeaVifDriver)
|
||||||
|
|
||||||
|
# Test raises exception for no type
|
||||||
|
self.assertRaises(exception.VirtualInterfacePlugException,
|
||||||
|
vif._build_vif_driver, self.adpt, 'host_uuid',
|
||||||
|
mock_inst, {})
|
||||||
|
|
||||||
|
# Test an invalid vif type
|
||||||
|
self.assertRaises(exception.VirtualInterfacePlugException,
|
||||||
|
vif._build_vif_driver, self.adpt, 'host_uuid',
|
||||||
|
mock_inst, {'type': 'ovs'})
|
||||||
|
|
||||||
|
|
||||||
|
class TestVifSeaDriver(test.TestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(TestVifSeaDriver, self).setUp()
|
||||||
|
|
||||||
|
self.adpt = self.useFixture(pvm_fx.AdapterFx(
|
||||||
|
traits=pvm_fx.LocalPVMTraits)).adpt
|
||||||
|
self.inst = mock.MagicMock()
|
||||||
|
self.drv = vif.PvmSeaVifDriver(self.adpt, 'host_uuid', self.inst)
|
||||||
|
|
||||||
|
@mock.patch('nova_powervm.virt.powervm.vm.get_pvm_uuid')
|
||||||
|
@mock.patch('pypowervm.tasks.cna.crt_cna')
|
||||||
|
def test_plug(self, mock_crt_cna, mock_pvm_uuid):
|
||||||
|
"""Tests that a VIF can be created."""
|
||||||
|
|
||||||
|
# Set up the mocks
|
||||||
|
fake_vif = {'network': {'meta': {'vlan': 5}},
|
||||||
|
'address': 'aabbccddeeff'}
|
||||||
|
|
||||||
|
def validate_crt(adpt, host_uuid, lpar_uuid, vlan, mac_addr=None):
|
||||||
|
self.assertEqual('host_uuid', host_uuid)
|
||||||
|
self.assertEqual(5, vlan)
|
||||||
|
self.assertEqual('aabbccddeeff', mac_addr)
|
||||||
|
return pvm_net.CNA.bld(self.adpt, 5, host_uuid)
|
||||||
|
mock_crt_cna.side_effect = validate_crt
|
||||||
|
|
||||||
|
# Invoke
|
||||||
|
resp = self.drv.plug(fake_vif)
|
||||||
|
|
||||||
|
# Validate (along with validate method above)
|
||||||
|
self.assertEqual(1, mock_crt_cna.call_count)
|
||||||
|
self.assertIsNotNone(resp)
|
||||||
|
self.assertIsInstance(resp, pvm_net.CNA)
|
||||||
|
|
||||||
|
@mock.patch('nova_powervm.virt.powervm.vm.get_cnas')
|
||||||
|
def test_unplug_vifs(self, mock_vm_get):
|
||||||
|
"""Tests that a delete of the vif can be done."""
|
||||||
|
# Mock up the CNA response. Two should already exist, the other
|
||||||
|
# should not.
|
||||||
|
cnas = [cna('AABBCCDDEEFF'), cna('AABBCCDDEE11'), cna('AABBCCDDEE22')]
|
||||||
|
mock_vm_get.return_value = cnas
|
||||||
|
|
||||||
|
# Run method. The AABBCCDDEE11 wont' be unplugged (wasn't invoked
|
||||||
|
# below) and the last unplug will also just no-op because its not on
|
||||||
|
# the VM.
|
||||||
|
self.drv.unplug({'address': 'aa:bb:cc:dd:ee:ff'})
|
||||||
|
self.drv.unplug({'address': 'aa:bb:cc:dd:ee:22'})
|
||||||
|
self.drv.unplug({'address': 'aa:bb:cc:dd:ee:33'})
|
||||||
|
|
||||||
|
# The delete should have only been called once. The second CNA didn't
|
||||||
|
# have a matching mac...so it should be skipped.
|
||||||
|
self.assertEqual(1, cnas[0].delete.call_count)
|
||||||
|
self.assertEqual(0, cnas[1].delete.call_count)
|
||||||
|
self.assertEqual(1, cnas[2].delete.call_count)
|
@ -1,4 +1,4 @@
|
|||||||
# Copyright 2014, 2015, 2016 IBM Corp.
|
# Copyright 2014, 2016 IBM Corp.
|
||||||
#
|
#
|
||||||
# All Rights Reserved.
|
# All Rights Reserved.
|
||||||
#
|
#
|
||||||
@ -13,7 +13,6 @@
|
|||||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
#
|
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
import mock
|
import mock
|
||||||
@ -29,7 +28,6 @@ from pypowervm.tests import test_fixtures as pvm_fx
|
|||||||
from pypowervm.tests.test_utils import pvmhttp
|
from pypowervm.tests.test_utils import pvmhttp
|
||||||
from pypowervm.wrappers import base_partition as pvm_bp
|
from pypowervm.wrappers import base_partition as pvm_bp
|
||||||
from pypowervm.wrappers import logical_partition as pvm_lpar
|
from pypowervm.wrappers import logical_partition as pvm_lpar
|
||||||
from pypowervm.wrappers import network as pvm_net
|
|
||||||
|
|
||||||
from nova_powervm.tests.virt import powervm
|
from nova_powervm.tests.virt import powervm
|
||||||
from nova_powervm.virt.powervm import vm
|
from nova_powervm.virt.powervm import vm
|
||||||
@ -418,30 +416,6 @@ class TestVM(test.TestCase):
|
|||||||
vm.power_off, None, None, 'host_uuid',
|
vm.power_off, None, None, 'host_uuid',
|
||||||
mock.Mock(state=pvm_bp.LPARState.RUNNING))
|
mock.Mock(state=pvm_bp.LPARState.RUNNING))
|
||||||
|
|
||||||
@mock.patch('nova_powervm.virt.powervm.vm.get_pvm_uuid')
|
|
||||||
@mock.patch('pypowervm.tasks.cna.crt_cna')
|
|
||||||
def test_crt_vif(self, mock_crt_cna, mock_pvm_uuid):
|
|
||||||
"""Tests that a VIF can be created."""
|
|
||||||
|
|
||||||
# Set up the mocks
|
|
||||||
fake_vif = {'network': {'meta': {'vlan': 5}},
|
|
||||||
'address': 'aabbccddeeff'}
|
|
||||||
|
|
||||||
def validate_of_crt(*kargs, **kwargs):
|
|
||||||
self.assertEqual('fake_host', kargs[1])
|
|
||||||
self.assertEqual(5, kargs[3])
|
|
||||||
self.assertEqual('aabbccddeeff', kwargs['mac_addr'])
|
|
||||||
return pvm_net.CNA.bld(self.apt, 5, 'fake_host')
|
|
||||||
mock_crt_cna.side_effect = validate_of_crt
|
|
||||||
|
|
||||||
# Invoke
|
|
||||||
resp = vm.crt_vif(self.apt, mock.MagicMock(), 'fake_host', fake_vif)
|
|
||||||
|
|
||||||
# Validate (along with validate method above)
|
|
||||||
self.assertEqual(1, mock_crt_cna.call_count)
|
|
||||||
self.assertIsNotNone(resp)
|
|
||||||
self.assertIsInstance(resp, pvm_net.CNA)
|
|
||||||
|
|
||||||
@mock.patch('nova_powervm.virt.powervm.vm.get_pvm_uuid')
|
@mock.patch('nova_powervm.virt.powervm.vm.get_pvm_uuid')
|
||||||
@mock.patch('nova_powervm.virt.powervm.vm.get_vm_qp')
|
@mock.patch('nova_powervm.virt.powervm.vm.get_vm_qp')
|
||||||
def test_instance_exists(self, mock_getvmqp, mock_getuuid):
|
def test_instance_exists(self, mock_getvmqp, mock_getuuid):
|
||||||
|
@ -27,6 +27,7 @@ from nova_powervm.virt.powervm.i18n import _
|
|||||||
from nova_powervm.virt.powervm.i18n import _LE
|
from nova_powervm.virt.powervm.i18n import _LE
|
||||||
from nova_powervm.virt.powervm.i18n import _LI
|
from nova_powervm.virt.powervm.i18n import _LI
|
||||||
from nova_powervm.virt.powervm.i18n import _LW
|
from nova_powervm.virt.powervm.i18n import _LW
|
||||||
|
from nova_powervm.virt.powervm import vif
|
||||||
from nova_powervm.virt.powervm import vm
|
from nova_powervm.virt.powervm import vm
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
@ -43,18 +44,18 @@ class VirtualInterfaceUnplugException(exception.NovaException):
|
|||||||
class UnplugVifs(task.Task):
|
class UnplugVifs(task.Task):
|
||||||
"""The task to unplug Virtual Network Interfaces from a VM."""
|
"""The task to unplug Virtual Network Interfaces from a VM."""
|
||||||
|
|
||||||
def __init__(self, adapter, instance, network_info, host_uuid):
|
def __init__(self, adapter, instance, network_infos, host_uuid):
|
||||||
"""Create the task.
|
"""Create the task.
|
||||||
|
|
||||||
:param adapter: The pypowervm adapter.
|
:param adapter: The pypowervm adapter.
|
||||||
:param instance: The nova instance.
|
:param instance: The nova instance.
|
||||||
:param network_info: The network information containing the nova
|
:param network_infos: The network information containing the nova
|
||||||
VIFs to create.
|
VIFs to create.
|
||||||
:param host_uuid: The host system's PowerVM UUID.
|
:param host_uuid: The host system's PowerVM UUID.
|
||||||
"""
|
"""
|
||||||
self.adapter = adapter
|
self.adapter = adapter
|
||||||
self.instance = instance
|
self.instance = instance
|
||||||
self.network_info = network_info
|
self.network_infos = network_infos
|
||||||
self.host_uuid = host_uuid
|
self.host_uuid = host_uuid
|
||||||
|
|
||||||
super(UnplugVifs, self).__init__(name='unplug_vifs',
|
super(UnplugVifs, self).__init__(name='unplug_vifs',
|
||||||
@ -79,42 +80,17 @@ class UnplugVifs(task.Task):
|
|||||||
cna_w_list = vm.get_cnas(self.adapter, self.instance, self.host_uuid)
|
cna_w_list = vm.get_cnas(self.adapter, self.instance, self.host_uuid)
|
||||||
|
|
||||||
# Walk through the VIFs and delete the corresponding CNA on the VM.
|
# Walk through the VIFs and delete the corresponding CNA on the VM.
|
||||||
for vif in self.network_info:
|
for network_info in self.network_infos:
|
||||||
for cna_w in cna_w_list:
|
vif.unplug(self.adapter, self.host_uuid, self.instance,
|
||||||
# If the MAC address matched, attempt the delete.
|
network_info, cna_w_list=cna_w_list)
|
||||||
if vm.norm_mac(cna_w.mac) == vif['address']:
|
|
||||||
LOG.info(_LI('Deleting VIF with mac %(mac)s for instance '
|
|
||||||
'%(inst)s.'), {'mac': vif['address'],
|
|
||||||
'inst': self.instance.name},
|
|
||||||
instance=self.instance)
|
|
||||||
try:
|
|
||||||
cna_w.delete()
|
|
||||||
except Exception as e:
|
|
||||||
LOG.error(_LE('Unable to unplug VIF with mac %(mac)s '
|
|
||||||
'for instance %(inst)s.'),
|
|
||||||
{'mac': vif['address'],
|
|
||||||
'inst': self.instance.name},
|
|
||||||
instance=self.instance)
|
|
||||||
LOG.error(e)
|
|
||||||
raise VirtualInterfaceUnplugException()
|
|
||||||
|
|
||||||
# Break from the loop as we had a successful unplug.
|
|
||||||
# This prevents from going to 'else' loop.
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
LOG.warning(_LW('Unable to unplug VIF with mac %(mac)s for '
|
|
||||||
'instance %(inst)s. The VIF was not found on '
|
|
||||||
'the instance.'),
|
|
||||||
{'mac': vif['address'],
|
|
||||||
'inst': self.instance.name},
|
|
||||||
instance=self.instance)
|
|
||||||
return cna_w_list
|
return cna_w_list
|
||||||
|
|
||||||
|
|
||||||
class PlugVifs(task.Task):
|
class PlugVifs(task.Task):
|
||||||
"""The task to plug the Virtual Network Interfaces to a VM."""
|
"""The task to plug the Virtual Network Interfaces to a VM."""
|
||||||
|
|
||||||
def __init__(self, virt_api, adapter, instance, network_info, host_uuid):
|
def __init__(self, virt_api, adapter, instance, network_infos, host_uuid):
|
||||||
"""Create the task.
|
"""Create the task.
|
||||||
|
|
||||||
Provides the 'vm_cnas' - the Virtual Machine's Client Network Adapters.
|
Provides the 'vm_cnas' - the Virtual Machine's Client Network Adapters.
|
||||||
@ -122,14 +98,14 @@ class PlugVifs(task.Task):
|
|||||||
:param virt_api: The VirtAPI for the operation.
|
:param virt_api: The VirtAPI for the operation.
|
||||||
:param adapter: The pypowervm adapter.
|
:param adapter: The pypowervm adapter.
|
||||||
:param instance: The nova instance.
|
:param instance: The nova instance.
|
||||||
:param network_info: The network information containing the nova
|
:param network_infos: The network information containing the nova
|
||||||
VIFs to create.
|
VIFs to create.
|
||||||
:param host_uuid: The host system's PowerVM UUID.
|
:param host_uuid: The host system's PowerVM UUID.
|
||||||
"""
|
"""
|
||||||
self.virt_api = virt_api
|
self.virt_api = virt_api
|
||||||
self.adapter = adapter
|
self.adapter = adapter
|
||||||
self.instance = instance
|
self.instance = instance
|
||||||
self.network_info = network_info
|
self.network_infos = network_infos
|
||||||
self.host_uuid = host_uuid
|
self.host_uuid = host_uuid
|
||||||
|
|
||||||
super(PlugVifs, self).__init__(name='plug_vifs', provides='vm_cnas',
|
super(PlugVifs, self).__init__(name='plug_vifs', provides='vm_cnas',
|
||||||
@ -143,16 +119,16 @@ class PlugVifs(task.Task):
|
|||||||
cna_w_list = vm.get_cnas(self.adapter, self.instance, self.host_uuid)
|
cna_w_list = vm.get_cnas(self.adapter, self.instance, self.host_uuid)
|
||||||
|
|
||||||
# Trim the VIFs down to the ones that haven't yet been created.
|
# Trim the VIFs down to the ones that haven't yet been created.
|
||||||
crt_vifs = []
|
crt_network_infos = []
|
||||||
for vif in self.network_info:
|
for network_info in self.network_infos:
|
||||||
for cna_w in cna_w_list:
|
for cna_w in cna_w_list:
|
||||||
if vm.norm_mac(cna_w.mac) == vif['address']:
|
if vm.norm_mac(cna_w.mac) == network_info['address']:
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
crt_vifs.append(vif)
|
crt_network_infos.append(network_info)
|
||||||
|
|
||||||
# If there are no vifs to create, then just exit immediately.
|
# If there are no vifs to create, then just exit immediately.
|
||||||
if len(crt_vifs) == 0:
|
if len(crt_network_infos) == 0:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
# Check to see if the LPAR is OK to add VIFs to.
|
# Check to see if the LPAR is OK to add VIFs to.
|
||||||
@ -188,14 +164,14 @@ class PlugVifs(task.Task):
|
|||||||
self.instance, self._get_vif_events(),
|
self.instance, self._get_vif_events(),
|
||||||
deadline=CONF.vif_plugging_timeout,
|
deadline=CONF.vif_plugging_timeout,
|
||||||
error_callback=self._vif_callback_failed):
|
error_callback=self._vif_callback_failed):
|
||||||
for vif in crt_vifs:
|
for network_info in crt_network_infos:
|
||||||
LOG.info(_LI('Creating VIF with mac %(mac)s for instance '
|
LOG.info(_LI('Creating VIF with mac %(mac)s for instance '
|
||||||
'%(sys)s'),
|
'%(sys)s'),
|
||||||
{'mac': vif['address'],
|
{'mac': network_info['address'],
|
||||||
'sys': self.instance.name},
|
'sys': self.instance.name},
|
||||||
instance=self.instance)
|
instance=self.instance)
|
||||||
vm.crt_vif(self.adapter, self.instance, self.host_uuid,
|
vif.plug(self.adapter, self.host_uuid, self.instance,
|
||||||
vif)
|
network_info)
|
||||||
except eventlet.timeout.Timeout:
|
except eventlet.timeout.Timeout:
|
||||||
LOG.error(_LE('Error waiting for VIF to be created for instance '
|
LOG.error(_LE('Error waiting for VIF to be created for instance '
|
||||||
'%(sys)s'), {'sys': self.instance.name},
|
'%(sys)s'), {'sys': self.instance.name},
|
||||||
@ -231,9 +207,9 @@ class PlugVifs(task.Task):
|
|||||||
# more information.
|
# more information.
|
||||||
if (utils.is_neutron() and CONF.vif_plugging_is_fatal and
|
if (utils.is_neutron() and CONF.vif_plugging_is_fatal and
|
||||||
CONF.vif_plugging_timeout):
|
CONF.vif_plugging_timeout):
|
||||||
return [('network-vif-plugged', vif['id'])
|
return [('network-vif-plugged', network_info['id'])
|
||||||
for vif in self.network_info
|
for network_info in self.network_infos
|
||||||
if not vif.get('active', True)]
|
if not network_info.get('active', True)]
|
||||||
else:
|
else:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
@ -265,7 +241,7 @@ class PlugMgmtVif(task.Task):
|
|||||||
'%s'), self.instance.name, instance=self.instance)
|
'%s'), self.instance.name, instance=self.instance)
|
||||||
# Determine if we need to create the secure RMC VIF. This should only
|
# 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
|
# be needed if there is not a VIF on the secure RMC vSwitch
|
||||||
vswitch_w = vm.get_secure_rmc_vswitch(self.adapter, self.host_uuid)
|
vswitch_w = vif.get_secure_rmc_vswitch(self.adapter, self.host_uuid)
|
||||||
if vswitch_w is None:
|
if vswitch_w is None:
|
||||||
LOG.debug('No management VIF created for instance %s due to '
|
LOG.debug('No management VIF created for instance %s due to '
|
||||||
'lack of Management Virtual Switch',
|
'lack of Management Virtual Switch',
|
||||||
@ -281,5 +257,5 @@ class PlugMgmtVif(task.Task):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
# Return the created management CNA
|
# Return the created management CNA
|
||||||
return vm.crt_secure_rmc_vif(self.adapter, self.instance,
|
return vif.plug_secure_rmc_vif(self.adapter, self.instance,
|
||||||
self.host_uuid)
|
self.host_uuid)
|
||||||
|
208
nova_powervm/virt/powervm/vif.py
Normal file
208
nova_powervm/virt/powervm/vif.py
Normal file
@ -0,0 +1,208 @@
|
|||||||
|
# Copyright 2016 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 abc
|
||||||
|
import logging
|
||||||
|
import six
|
||||||
|
|
||||||
|
from nova import exception
|
||||||
|
from oslo_utils import importutils
|
||||||
|
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 nova_powervm.virt.powervm.i18n import _
|
||||||
|
from nova_powervm.virt.powervm.i18n import _LE
|
||||||
|
from nova_powervm.virt.powervm.i18n import _LI
|
||||||
|
from nova_powervm.virt.powervm.i18n import _LW
|
||||||
|
from nova_powervm.virt.powervm import vm
|
||||||
|
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
SECURE_RMC_VSWITCH = 'MGMTSWITCH'
|
||||||
|
SECURE_RMC_VLAN = 4094
|
||||||
|
|
||||||
|
VIF_MAPPING = {'pvm_sea': 'nova_powervm.virt.powervm.vif.PvmSeaVifDriver'}
|
||||||
|
|
||||||
|
|
||||||
|
class VirtualInterfaceUnplugException(exception.NovaException):
|
||||||
|
"""Indicates that a VIF unplug failed."""
|
||||||
|
# TODO(thorst) symmetrical to the exception in base Nova. Evaluate
|
||||||
|
# moving to Nova core.
|
||||||
|
msg_fmt = _("Virtual interface unplug failed")
|
||||||
|
|
||||||
|
|
||||||
|
def _build_vif_driver(adapter, host_uuid, instance, vif):
|
||||||
|
"""Returns the appropriate VIF Driver for the given VIF.
|
||||||
|
|
||||||
|
:param adapter: The pypowervm adapter API interface.
|
||||||
|
:param host_uuid: The host system UUID.
|
||||||
|
:param instance: The nova instance.
|
||||||
|
:param vif: The virtual interface to from Nova.
|
||||||
|
:return: The appropriate PvmVifDriver for the VIF.
|
||||||
|
"""
|
||||||
|
if vif.get('type') is None:
|
||||||
|
raise exception.VirtualInterfacePlugException(
|
||||||
|
_("vif_type parameter must be present "
|
||||||
|
"for this vif_driver implementation"))
|
||||||
|
|
||||||
|
# Check the type to the implementations
|
||||||
|
if VIF_MAPPING.get(vif['type']):
|
||||||
|
return importutils.import_object(
|
||||||
|
VIF_MAPPING.get(vif['type']), adapter, host_uuid, instance)
|
||||||
|
|
||||||
|
# No matching implementation, raise error.
|
||||||
|
raise exception.VirtualInterfacePlugException(
|
||||||
|
_("Unable to find appropriate PowerVM VIF Driver for VIF type "
|
||||||
|
"%(vif_type)s on instance %(instance)s") %
|
||||||
|
{'vif_type': vif['type'], 'instance': instance.name})
|
||||||
|
|
||||||
|
|
||||||
|
def plug(adapter, host_uuid, instance, vif):
|
||||||
|
"""Plugs a virtual interface (network) into a VM.
|
||||||
|
|
||||||
|
:param adapter: The pypowervm adapter.
|
||||||
|
:param host_uuid: The host UUID for the PowerVM API.
|
||||||
|
:param instance: The nova instance object.
|
||||||
|
:param vif: The virtual interface to plug into the instance.
|
||||||
|
"""
|
||||||
|
vif_drv = _build_vif_driver(adapter, host_uuid, instance, vif)
|
||||||
|
vif_drv.plug(vif)
|
||||||
|
|
||||||
|
|
||||||
|
def unplug(adapter, host_uuid, instance, vif, cna_w_list=None):
|
||||||
|
"""Unplugs a virtual interface (network) from a VM.
|
||||||
|
|
||||||
|
:param adapter: The pypowervm adapter.
|
||||||
|
:param host_uuid: The host UUID for the PowerVM API.
|
||||||
|
: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, host_uuid, instance, vif)
|
||||||
|
vif_drv.unplug(vif, cna_w_list=cna_w_list)
|
||||||
|
|
||||||
|
|
||||||
|
def get_secure_rmc_vswitch(adapter, host_uuid):
|
||||||
|
"""Returns the vSwitch that is used for secure RMC.
|
||||||
|
|
||||||
|
:param adapter: The pypowervm adapter API interface.
|
||||||
|
:param host_uuid: The host system UUID.
|
||||||
|
:return: The wrapper for the secure RMC vSwitch. If it does not exist
|
||||||
|
on the system, None is returned.
|
||||||
|
"""
|
||||||
|
vswitches = pvm_net.VSwitch.search(
|
||||||
|
adapter, parent_type=pvm_ms.System.schema_type,
|
||||||
|
parent_uuid=host_uuid, name=SECURE_RMC_VSWITCH)
|
||||||
|
if len(vswitches) == 1:
|
||||||
|
return vswitches[0]
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def plug_secure_rmc_vif(adapter, instance, host_uuid):
|
||||||
|
"""Creates the Secure RMC Network Adapter on the VM.
|
||||||
|
|
||||||
|
:param adapter: The pypowervm adapter API interface.
|
||||||
|
:param instance: The nova instance to create the VIF against.
|
||||||
|
:param host_uuid: The host system UUID.
|
||||||
|
:return: The created network adapter wrapper.
|
||||||
|
"""
|
||||||
|
lpar_uuid = vm.get_pvm_uuid(instance)
|
||||||
|
return pvm_cna.crt_cna(adapter, host_uuid, lpar_uuid, SECURE_RMC_VLAN,
|
||||||
|
vswitch=SECURE_RMC_VSWITCH, crt_vswitch=True)
|
||||||
|
|
||||||
|
|
||||||
|
@six.add_metaclass(abc.ABCMeta)
|
||||||
|
class PvmVifDriver(object):
|
||||||
|
"""Represents an abstract class for a PowerVM Vif Driver.
|
||||||
|
|
||||||
|
A VIF Driver understands a given virtual interface type (network). It
|
||||||
|
understands how to plug and unplug a given VIF for a virtual machine.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, adapter, host_uuid, instance):
|
||||||
|
"""Initializes a VIF Driver.
|
||||||
|
|
||||||
|
:param adapter: The pypowervm adapter API interface.
|
||||||
|
:param host_uuid: The host system UUID.
|
||||||
|
:param instance: The nova instance that the vif action will be run
|
||||||
|
against.
|
||||||
|
"""
|
||||||
|
self.adapter = adapter
|
||||||
|
self.host_uuid = host_uuid
|
||||||
|
self.instance = instance
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def plug(self, vif):
|
||||||
|
"""Plugs a virtual interface (network) into a VM.
|
||||||
|
|
||||||
|
:param vif: The virtual interface to plug into the instance.
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def unplug(self, vif, cna_w_list=None):
|
||||||
|
"""Unplugs a virtual interface (network) from a VM.
|
||||||
|
|
||||||
|
: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.
|
||||||
|
"""
|
||||||
|
# This is a default implementation that most implementations will
|
||||||
|
# require.
|
||||||
|
|
||||||
|
# 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,
|
||||||
|
self.host_uuid)
|
||||||
|
|
||||||
|
for cna_w in cna_w_list:
|
||||||
|
# If the MAC address matched, attempt the delete.
|
||||||
|
if vm.norm_mac(cna_w.mac) == vif['address']:
|
||||||
|
LOG.info(_LI('Deleting VIF with mac %(mac)s for instance '
|
||||||
|
'%(inst)s.'),
|
||||||
|
{'mac': vif['address'], 'inst': self.instance.name})
|
||||||
|
try:
|
||||||
|
cna_w.delete()
|
||||||
|
except Exception as e:
|
||||||
|
LOG.error(_LE('Unable to unplug VIF with mac %(mac)s '
|
||||||
|
'for instance %(inst)s.'),
|
||||||
|
{'mac': vif['address'],
|
||||||
|
'inst': self.instance.name})
|
||||||
|
LOG.exception(e)
|
||||||
|
raise VirtualInterfaceUnplugException()
|
||||||
|
|
||||||
|
# Break from the loop as we had a successful unplug.
|
||||||
|
# This prevents from going to 'else' loop.
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
LOG.warning(_LW('Unable to unplug VIF with mac %(mac)s for '
|
||||||
|
'instance %(inst)s. The VIF was not found on '
|
||||||
|
'the instance.'),
|
||||||
|
{'mac': vif['address'], 'inst': self.instance.name})
|
||||||
|
|
||||||
|
|
||||||
|
class PvmSeaVifDriver(PvmVifDriver):
|
||||||
|
"""The PowerVM Shared Ethernet Adapter VIF Driver."""
|
||||||
|
|
||||||
|
def plug(self, vif):
|
||||||
|
lpar_uuid = vm.get_pvm_uuid(self.instance)
|
||||||
|
# CNA's require a VLAN. If the network doesn't provide, default to 1
|
||||||
|
vlan = vif['network']['meta'].get('vlan', 1)
|
||||||
|
return pvm_cna.crt_cna(self.adapter, self.host_uuid, lpar_uuid, vlan,
|
||||||
|
mac_addr=vif['address'])
|
@ -26,7 +26,6 @@ from nova.virt import event
|
|||||||
from nova.virt import hardware
|
from nova.virt import hardware
|
||||||
from pypowervm import exceptions as pvm_exc
|
from pypowervm import exceptions as pvm_exc
|
||||||
from pypowervm.helpers import log_helper as pvm_log
|
from pypowervm.helpers import log_helper as pvm_log
|
||||||
from pypowervm.tasks import cna
|
|
||||||
from pypowervm.tasks import ibmi
|
from pypowervm.tasks import ibmi
|
||||||
from pypowervm.tasks import power
|
from pypowervm.tasks import power
|
||||||
from pypowervm.tasks import vterm
|
from pypowervm.tasks import vterm
|
||||||
@ -95,11 +94,6 @@ POWERVM_STOPABLE_STATE = (pvm_bp.LPARState.RUNNING, pvm_bp.LPARState.STARTING,
|
|||||||
pvm_bp.LPARState.OPEN_FIRMWARE,
|
pvm_bp.LPARState.OPEN_FIRMWARE,
|
||||||
pvm_bp.LPARState.ERROR, pvm_bp.LPARState.RESUMING)
|
pvm_bp.LPARState.ERROR, pvm_bp.LPARState.RESUMING)
|
||||||
|
|
||||||
# Attributes for secure RMC
|
|
||||||
# TODO(thorst) The name of the secure RMC vswitch will change.
|
|
||||||
SECURE_RMC_VSWITCH = 'MGMTSWITCH'
|
|
||||||
SECURE_RMC_VLAN = 4094
|
|
||||||
|
|
||||||
|
|
||||||
def translate_event(pvm_state, pwr_state):
|
def translate_event(pvm_state, pwr_state):
|
||||||
"""Translate the PowerVM state and see if it has changed.
|
"""Translate the PowerVM state and see if it has changed.
|
||||||
@ -708,52 +702,6 @@ def get_cnas(adapter, instance, host_uuid):
|
|||||||
return pvm_net.CNA.wrap(cna_resp)
|
return pvm_net.CNA.wrap(cna_resp)
|
||||||
|
|
||||||
|
|
||||||
def crt_vif(adapter, instance, host_uuid, vif):
|
|
||||||
"""Will create a Client Network Adapter on the system.
|
|
||||||
|
|
||||||
:param adapter: The pypowervm adapter API interface.
|
|
||||||
:param instance: The nova instance to create the VIF against.
|
|
||||||
:param host_uuid: The host system UUID.
|
|
||||||
:param vif: The nova VIF that describes the ethernet interface.
|
|
||||||
:return: The created network adapter wrapper.
|
|
||||||
"""
|
|
||||||
lpar_uuid = get_pvm_uuid(instance)
|
|
||||||
# CNA's require a VLAN. If the network doesn't provide, default to 1
|
|
||||||
vlan = vif['network']['meta'].get('vlan', 1)
|
|
||||||
return cna.crt_cna(adapter, host_uuid, lpar_uuid, vlan,
|
|
||||||
mac_addr=vif['address'])
|
|
||||||
|
|
||||||
|
|
||||||
def crt_secure_rmc_vif(adapter, instance, host_uuid):
|
|
||||||
"""Creates the Secure RMC Network Adapter on the VM.
|
|
||||||
|
|
||||||
:param adapter: The pypowervm adapter API interface.
|
|
||||||
:param instance: The nova instance to create the VIF against.
|
|
||||||
:param host_uuid: The host system UUID.
|
|
||||||
:return: The created network adapter wrapper.
|
|
||||||
"""
|
|
||||||
lpar_uuid = get_pvm_uuid(instance)
|
|
||||||
return cna.crt_cna(adapter, host_uuid, lpar_uuid, SECURE_RMC_VLAN,
|
|
||||||
vswitch=SECURE_RMC_VSWITCH, crt_vswitch=True)
|
|
||||||
|
|
||||||
|
|
||||||
def get_secure_rmc_vswitch(adapter, host_uuid):
|
|
||||||
"""Returns the vSwitch that is used for secure RMC.
|
|
||||||
|
|
||||||
:param adapter: The pypowervm adapter API interface.
|
|
||||||
:param host_uuid: The host system UUID.
|
|
||||||
:return: The wrapper for the secure RMC vSwitch. If it does not exist
|
|
||||||
on the system, None is returned.
|
|
||||||
"""
|
|
||||||
resp = adapter.read(pvm_ms.System.schema_type, root_id=host_uuid,
|
|
||||||
child_type=pvm_net.VSwitch.schema_type)
|
|
||||||
vswitches = pvm_net.VSwitch.wrap(resp)
|
|
||||||
for vswitch in vswitches:
|
|
||||||
if vswitch.name == SECURE_RMC_VSWITCH:
|
|
||||||
return vswitch
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def norm_mac(mac):
|
def norm_mac(mac):
|
||||||
"""Normalizes a MAC address from pypowervm format to OpenStack.
|
"""Normalizes a MAC address from pypowervm format to OpenStack.
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user