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:
Drew Thorstensen 2016-02-03 16:01:45 -05:00
parent ebbb9ec623
commit 01a452be03
7 changed files with 433 additions and 172 deletions

View File

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

View File

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

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

View File

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

View File

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

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

View File

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