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.
|
||||
#
|
||||
@ -43,13 +43,13 @@ class TestNetwork(test.TestCase):
|
||||
self.mock_lpar_wrap = mock.MagicMock()
|
||||
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')
|
||||
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."""
|
||||
inst = objects.Instance(**powervm.TEST_INSTANCE)
|
||||
|
||||
# Mock up the CNA response. One should already exist, the other
|
||||
# should not.
|
||||
# Mock up the CNA responses.
|
||||
cnas = [cna('AABBCCDDEEFF'), cna('AABBCCDDEE11'), cna('AABBCCDDEE22')]
|
||||
mock_vm_get.return_value = cnas
|
||||
|
||||
@ -60,15 +60,24 @@ class TestNetwork(test.TestCase):
|
||||
{'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
|
||||
p_vifs = tf_net.UnplugVifs(self.apt, inst, net_info, 'host_uuid')
|
||||
p_vifs.execute(self.mock_lpar_wrap)
|
||||
|
||||
# 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)
|
||||
# Make sure the unplug was invoked, so that we know that the validation
|
||||
# code was called
|
||||
self.assertEqual(3, mock_unplug.call_count)
|
||||
|
||||
def test_unplug_vifs_invalid_state(self):
|
||||
"""Tests that the delete raises an exception if bad VM state."""
|
||||
@ -82,9 +91,9 @@ class TestNetwork(test.TestCase):
|
||||
self.assertRaises(tf_net.VirtualInterfaceUnplugException,
|
||||
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')
|
||||
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."""
|
||||
inst = objects.Instance(**powervm.TEST_INSTANCE)
|
||||
|
||||
@ -105,11 +114,11 @@ class TestNetwork(test.TestCase):
|
||||
p_vifs.execute(self.mock_lpar_wrap)
|
||||
|
||||
# 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')
|
||||
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."""
|
||||
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
|
||||
# been empty.
|
||||
self.assertEqual(0, mock_vm_crt.call_count)
|
||||
self.assertEqual(0, mock_plug.call_count)
|
||||
self.assertEqual([], resp)
|
||||
|
||||
# State check shouldn't have even been invoked as no creates were
|
||||
# required
|
||||
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')
|
||||
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."""
|
||||
inst = objects.Instance(**powervm.TEST_INSTANCE)
|
||||
|
||||
@ -156,11 +165,11 @@ class TestNetwork(test.TestCase):
|
||||
p_vifs.execute, self.mock_lpar_wrap)
|
||||
|
||||
# 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')
|
||||
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."""
|
||||
inst = objects.Instance(**powervm.TEST_INSTANCE)
|
||||
|
||||
@ -171,7 +180,7 @@ class TestNetwork(test.TestCase):
|
||||
net_info = [{'address': 'aa:bb:cc:dd:ee:ff'}]
|
||||
|
||||
# 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
|
||||
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)
|
||||
|
||||
# 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')
|
||||
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."""
|
||||
inst = powervm.TEST_INST1
|
||||
|
||||
# Set this up as a different host from the inst.host
|
||||
self.flags(host='host2')
|
||||
|
||||
@ -203,20 +213,21 @@ class TestNetwork(test.TestCase):
|
||||
p_vifs.execute(self.mock_lpar_wrap)
|
||||
|
||||
# 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
|
||||
self.assertEqual(2, mock_inst_save.call_count)
|
||||
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')
|
||||
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.
|
||||
|
||||
This test ensures that if we get a timeout exception we still reset
|
||||
the inst.host value back to the original value
|
||||
"""
|
||||
inst = powervm.TEST_INST1
|
||||
|
||||
# Set this up as a different host from the inst.host
|
||||
self.flags(host='host2')
|
||||
|
||||
@ -227,7 +238,7 @@ class TestNetwork(test.TestCase):
|
||||
net_info = [{'address': 'aa:bb:cc:dd:ee:ff'}]
|
||||
|
||||
# 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
|
||||
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)
|
||||
|
||||
# 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
|
||||
self.assertEqual(2, mock_inst_save.call_count)
|
||||
self.assertEqual('host1', inst.host)
|
||||
|
||||
@mock.patch('nova_powervm.virt.powervm.vm.crt_secure_rmc_vif')
|
||||
@mock.patch('nova_powervm.virt.powervm.vm.get_secure_rmc_vswitch')
|
||||
@mock.patch('nova_powervm.virt.powervm.vm.crt_vif')
|
||||
@mock.patch('nova_powervm.virt.powervm.vif.plug_secure_rmc_vif')
|
||||
@mock.patch('nova_powervm.virt.powervm.vif.get_secure_rmc_vswitch')
|
||||
@mock.patch('nova_powervm.virt.powervm.vif.plug')
|
||||
@mock.patch('nova_powervm.virt.powervm.vm.get_cnas')
|
||||
def test_plug_mgmt_vif(self, mock_vm_get, mock_vm_crt,
|
||||
mock_get_rmc_vswitch, mock_crt_rmc_vif):
|
||||
def test_plug_mgmt_vif(self, mock_vm_get, mock_plug,
|
||||
mock_get_rmc_vswitch, mock_plug_rmc_vif):
|
||||
"""Tests that a mgmt vif can be created."""
|
||||
inst = objects.Instance(**powervm.TEST_INSTANCE)
|
||||
|
||||
@ -261,7 +272,7 @@ class TestNetwork(test.TestCase):
|
||||
p_vifs.execute([])
|
||||
|
||||
# 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')
|
||||
def test_get_vif_events(self, mock_is_neutron):
|
||||
|
@ -1187,13 +1187,13 @@ class TestPowerVMDriver(test.TestCase):
|
||||
self.assertIsNotNone(value)
|
||||
|
||||
@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.vm.get_secure_rmc_vswitch')
|
||||
@mock.patch('nova_powervm.virt.powervm.vm.crt_vif')
|
||||
@mock.patch('nova_powervm.virt.powervm.vif.plug_secure_rmc_vif')
|
||||
@mock.patch('nova_powervm.virt.powervm.vif.get_secure_rmc_vswitch')
|
||||
@mock.patch('nova_powervm.virt.powervm.vif.plug')
|
||||
@mock.patch('nova_powervm.virt.powervm.vm.get_cnas')
|
||||
def test_plug_vifs(
|
||||
self, mock_vm_get, mock_vm_crt, mock_get_rmc_vswitch, mock_crt_rmc_vif,
|
||||
mock_can_modify_io):
|
||||
self, mock_vm_get, mock_plug_vif, mock_get_rmc_vswitch,
|
||||
mock_plug_rmc_vif, mock_can_modify_io):
|
||||
# Mock up the CNA response
|
||||
cnas = [mock.MagicMock(), mock.MagicMock()]
|
||||
cnas[0].mac = 'AABBCCDDEEFF'
|
||||
@ -1220,8 +1220,8 @@ class TestPowerVMDriver(test.TestCase):
|
||||
|
||||
# The create should have only been called once. The other was already
|
||||
# existing.
|
||||
self.assertEqual(1, mock_vm_crt.call_count)
|
||||
self.assertEqual(0, mock_crt_rmc_vif.call_count)
|
||||
self.assertEqual(1, mock_plug_vif.call_count)
|
||||
self.assertEqual(0, mock_plug_rmc_vif.call_count)
|
||||
|
||||
@mock.patch('nova_powervm.virt.powervm.tasks.vm.Get')
|
||||
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.
|
||||
#
|
||||
@ -13,7 +13,6 @@
|
||||
# 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 logging
|
||||
import mock
|
||||
@ -29,7 +28,6 @@ from pypowervm.tests import test_fixtures as pvm_fx
|
||||
from pypowervm.tests.test_utils import pvmhttp
|
||||
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 nova_powervm.tests.virt import powervm
|
||||
from nova_powervm.virt.powervm import vm
|
||||
@ -418,30 +416,6 @@ class TestVM(test.TestCase):
|
||||
vm.power_off, None, None, 'host_uuid',
|
||||
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_vm_qp')
|
||||
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 _LI
|
||||
from nova_powervm.virt.powervm.i18n import _LW
|
||||
from nova_powervm.virt.powervm import vif
|
||||
from nova_powervm.virt.powervm import vm
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
@ -43,18 +44,18 @@ class VirtualInterfaceUnplugException(exception.NovaException):
|
||||
class UnplugVifs(task.Task):
|
||||
"""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.
|
||||
|
||||
:param adapter: The pypowervm adapter.
|
||||
:param instance: The nova instance.
|
||||
:param network_info: The network information containing the nova
|
||||
VIFs to create.
|
||||
:param network_infos: The network information containing the nova
|
||||
VIFs to create.
|
||||
:param host_uuid: The host system's PowerVM UUID.
|
||||
"""
|
||||
self.adapter = adapter
|
||||
self.instance = instance
|
||||
self.network_info = network_info
|
||||
self.network_infos = network_infos
|
||||
self.host_uuid = host_uuid
|
||||
|
||||
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)
|
||||
|
||||
# Walk through the VIFs and delete the corresponding CNA on the VM.
|
||||
for vif in self.network_info:
|
||||
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},
|
||||
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()
|
||||
for network_info in self.network_infos:
|
||||
vif.unplug(self.adapter, self.host_uuid, self.instance,
|
||||
network_info, cna_w_list=cna_w_list)
|
||||
|
||||
# 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
|
||||
|
||||
|
||||
class PlugVifs(task.Task):
|
||||
"""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.
|
||||
|
||||
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 adapter: The pypowervm adapter.
|
||||
:param instance: The nova instance.
|
||||
:param network_info: The network information containing the nova
|
||||
VIFs to create.
|
||||
:param network_infos: The network information containing the nova
|
||||
VIFs to create.
|
||||
:param host_uuid: The host system's PowerVM UUID.
|
||||
"""
|
||||
self.virt_api = virt_api
|
||||
self.adapter = adapter
|
||||
self.instance = instance
|
||||
self.network_info = network_info
|
||||
self.network_infos = network_infos
|
||||
self.host_uuid = host_uuid
|
||||
|
||||
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)
|
||||
|
||||
# Trim the VIFs down to the ones that haven't yet been created.
|
||||
crt_vifs = []
|
||||
for vif in self.network_info:
|
||||
crt_network_infos = []
|
||||
for network_info in self.network_infos:
|
||||
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
|
||||
else:
|
||||
crt_vifs.append(vif)
|
||||
crt_network_infos.append(network_info)
|
||||
|
||||
# If there are no vifs to create, then just exit immediately.
|
||||
if len(crt_vifs) == 0:
|
||||
if len(crt_network_infos) == 0:
|
||||
return []
|
||||
|
||||
# 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(),
|
||||
deadline=CONF.vif_plugging_timeout,
|
||||
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 '
|
||||
'%(sys)s'),
|
||||
{'mac': vif['address'],
|
||||
{'mac': network_info['address'],
|
||||
'sys': self.instance.name},
|
||||
instance=self.instance)
|
||||
vm.crt_vif(self.adapter, self.instance, self.host_uuid,
|
||||
vif)
|
||||
vif.plug(self.adapter, self.host_uuid, self.instance,
|
||||
network_info)
|
||||
except eventlet.timeout.Timeout:
|
||||
LOG.error(_LE('Error waiting for VIF to be created for instance '
|
||||
'%(sys)s'), {'sys': self.instance.name},
|
||||
@ -231,9 +207,9 @@ class PlugVifs(task.Task):
|
||||
# more information.
|
||||
if (utils.is_neutron() and CONF.vif_plugging_is_fatal and
|
||||
CONF.vif_plugging_timeout):
|
||||
return [('network-vif-plugged', vif['id'])
|
||||
for vif in self.network_info
|
||||
if not vif.get('active', True)]
|
||||
return [('network-vif-plugged', network_info['id'])
|
||||
for network_info in self.network_infos
|
||||
if not network_info.get('active', True)]
|
||||
else:
|
||||
return []
|
||||
|
||||
@ -265,7 +241,7 @@ class PlugMgmtVif(task.Task):
|
||||
'%s'), self.instance.name, 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_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:
|
||||
LOG.debug('No management VIF created for instance %s due to '
|
||||
'lack of Management Virtual Switch',
|
||||
@ -281,5 +257,5 @@ class PlugMgmtVif(task.Task):
|
||||
return None
|
||||
|
||||
# Return the created management CNA
|
||||
return vm.crt_secure_rmc_vif(self.adapter, self.instance,
|
||||
self.host_uuid)
|
||||
return vif.plug_secure_rmc_vif(self.adapter, self.instance,
|
||||
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 pypowervm import exceptions as pvm_exc
|
||||
from pypowervm.helpers import log_helper as pvm_log
|
||||
from pypowervm.tasks import cna
|
||||
from pypowervm.tasks import ibmi
|
||||
from pypowervm.tasks import power
|
||||
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.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):
|
||||
"""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)
|
||||
|
||||
|
||||
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):
|
||||
"""Normalizes a MAC address from pypowervm format to OpenStack.
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user