nova/nova/tests/unit/virt/powervm/test_vif.py

328 lines
14 KiB
Python

# Copyright 2017 IBM Corp.
#
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from pypowervm import exceptions as pvm_ex
from pypowervm.wrappers import network as pvm_net
from unittest import mock
from nova import exception
from nova.network import model
from nova import test
from nova.virt.powervm import vif
def cna(mac):
"""Builds a mock Client Network Adapter for unit tests."""
return mock.Mock(spec=pvm_net.CNA, mac=mac, vswitch_uri='fake_href')
class TestVifFunctions(test.NoDBTestCase):
def setUp(self):
super(TestVifFunctions, self).setUp()
self.adpt = mock.Mock()
@mock.patch('nova.virt.powervm.vif.PvmOvsVifDriver')
def test_build_vif_driver(self, mock_driver):
# Valid vif type
driver = vif._build_vif_driver(self.adpt, 'instance', {'type': 'ovs'})
self.assertEqual(mock_driver.return_value, driver)
mock_driver.reset_mock()
# Fail if no vif type
self.assertRaises(exception.VirtualInterfacePlugException,
vif._build_vif_driver, self.adpt, 'instance',
{'type': None})
mock_driver.assert_not_called()
# Fail if invalid vif type
self.assertRaises(exception.VirtualInterfacePlugException,
vif._build_vif_driver, self.adpt, 'instance',
{'type': 'bad_type'})
mock_driver.assert_not_called()
@mock.patch('oslo_serialization.jsonutils.dumps')
@mock.patch('pypowervm.wrappers.event.Event')
def test_push_vif_event(self, mock_event, mock_dumps):
mock_vif = mock.Mock(mac='MAC', href='HREF')
vif._push_vif_event(self.adpt, 'action', mock_vif, mock.Mock(),
'pvm_sea')
mock_dumps.assert_called_once_with(
{'provider': 'NOVA_PVM_VIF', 'action': 'action', 'mac': 'MAC',
'type': 'pvm_sea'})
mock_event.bld.assert_called_once_with(self.adpt, 'HREF',
mock_dumps.return_value)
mock_event.bld.return_value.create.assert_called_once_with()
mock_dumps.reset_mock()
mock_event.bld.reset_mock()
mock_event.bld.return_value.create.reset_mock()
# Exception reraises
mock_event.bld.return_value.create.side_effect = IndexError
self.assertRaises(IndexError, vif._push_vif_event, self.adpt, 'action',
mock_vif, mock.Mock(), 'pvm_sea')
mock_dumps.assert_called_once_with(
{'provider': 'NOVA_PVM_VIF', 'action': 'action', 'mac': 'MAC',
'type': 'pvm_sea'})
mock_event.bld.assert_called_once_with(self.adpt, 'HREF',
mock_dumps.return_value)
mock_event.bld.return_value.create.assert_called_once_with()
@mock.patch('nova.virt.powervm.vif._push_vif_event')
@mock.patch('nova.virt.powervm.vif._build_vif_driver')
def test_plug(self, mock_bld_drv, mock_event):
"""Test the top-level plug method."""
mock_vif = {'address': 'MAC', 'type': 'pvm_sea'}
# 1) With new_vif=True (default)
vnet = vif.plug(self.adpt, 'instance', mock_vif)
mock_bld_drv.assert_called_once_with(self.adpt, 'instance', mock_vif)
mock_bld_drv.return_value.plug.assert_called_once_with(mock_vif,
new_vif=True)
self.assertEqual(mock_bld_drv.return_value.plug.return_value, vnet)
mock_event.assert_called_once_with(self.adpt, 'plug', vnet, mock.ANY,
'pvm_sea')
# Clean up
mock_bld_drv.reset_mock()
mock_bld_drv.return_value.plug.reset_mock()
mock_event.reset_mock()
# 2) Plug returns None (which it should IRL whenever new_vif=False).
mock_bld_drv.return_value.plug.return_value = None
vnet = vif.plug(self.adpt, 'instance', mock_vif, new_vif=False)
mock_bld_drv.assert_called_once_with(self.adpt, 'instance', mock_vif)
mock_bld_drv.return_value.plug.assert_called_once_with(mock_vif,
new_vif=False)
self.assertIsNone(vnet)
mock_event.assert_not_called()
@mock.patch('nova.virt.powervm.vif._build_vif_driver')
def test_plug_raises(self, mock_vif_drv):
"""HttpError is converted to VirtualInterfacePlugException."""
vif_drv = mock.Mock(plug=mock.Mock(side_effect=pvm_ex.HttpError(
resp=mock.Mock())))
mock_vif_drv.return_value = vif_drv
mock_vif = {'address': 'vifaddr'}
self.assertRaises(exception.VirtualInterfacePlugException,
vif.plug, 'adap', 'inst', mock_vif,
new_vif='new_vif')
mock_vif_drv.assert_called_once_with('adap', 'inst', mock_vif)
vif_drv.plug.assert_called_once_with(mock_vif, new_vif='new_vif')
@mock.patch('nova.virt.powervm.vif._push_vif_event')
@mock.patch('nova.virt.powervm.vif._build_vif_driver')
def test_unplug(self, mock_bld_drv, mock_event):
"""Test the top-level unplug method."""
mock_vif = {'address': 'MAC', 'type': 'pvm_sea'}
# 1) With default cna_w_list
mock_bld_drv.return_value.unplug.return_value = 'vnet_w'
vif.unplug(self.adpt, 'instance', mock_vif)
mock_bld_drv.assert_called_once_with(self.adpt, 'instance', mock_vif)
mock_bld_drv.return_value.unplug.assert_called_once_with(
mock_vif, cna_w_list=None)
mock_event.assert_called_once_with(self.adpt, 'unplug', 'vnet_w',
mock.ANY, 'pvm_sea')
# Clean up
mock_bld_drv.reset_mock()
mock_bld_drv.return_value.unplug.reset_mock()
mock_event.reset_mock()
# 2) With specified cna_w_list
mock_bld_drv.return_value.unplug.return_value = None
vif.unplug(self.adpt, 'instance', mock_vif, cna_w_list='cnalist')
mock_bld_drv.assert_called_once_with(self.adpt, 'instance', mock_vif)
mock_bld_drv.return_value.unplug.assert_called_once_with(
mock_vif, cna_w_list='cnalist')
mock_event.assert_not_called()
@mock.patch('nova.virt.powervm.vif._build_vif_driver')
def test_unplug_raises(self, mock_vif_drv):
"""HttpError is converted to VirtualInterfacePlugException."""
vif_drv = mock.Mock(unplug=mock.Mock(side_effect=pvm_ex.HttpError(
resp=mock.Mock())))
mock_vif_drv.return_value = vif_drv
mock_vif = {'address': 'vifaddr'}
self.assertRaises(exception.VirtualInterfaceUnplugException,
vif.unplug, 'adap', 'inst', mock_vif,
cna_w_list='cna_w_list')
mock_vif_drv.assert_called_once_with('adap', 'inst', mock_vif)
vif_drv.unplug.assert_called_once_with(
mock_vif, cna_w_list='cna_w_list')
class TestVifOvsDriver(test.NoDBTestCase):
def setUp(self):
super(TestVifOvsDriver, self).setUp()
self.adpt = mock.Mock()
self.inst = mock.MagicMock(uuid='inst_uuid')
self.drv = vif.PvmOvsVifDriver(self.adpt, self.inst)
@mock.patch('pypowervm.tasks.cna.crt_p2p_cna', autospec=True)
@mock.patch('pypowervm.tasks.partition.get_this_partition', autospec=True)
@mock.patch('nova.virt.powervm.vm.get_pvm_uuid')
def test_plug(self, mock_pvm_uuid, mock_mgmt_lpar, mock_p2p_cna,):
# Mock the data
mock_pvm_uuid.return_value = 'lpar_uuid'
mock_mgmt_lpar.return_value = mock.Mock(uuid='mgmt_uuid')
# mock_trunk_dev_name.return_value = 'device'
cna_w, trunk_wraps = mock.MagicMock(), [mock.MagicMock()]
mock_p2p_cna.return_value = cna_w, trunk_wraps
# Run the plug
network_model = model.Model({'bridge': 'br0', 'meta': {'mtu': 1450}})
mock_vif = model.VIF(address='aa:bb:cc:dd:ee:ff', id='vif_id',
network=network_model, devname='device')
self.drv.plug(mock_vif)
# Validate the calls
ovs_ext_ids = ('iface-id=vif_id,iface-status=active,'
'attached-mac=aa:bb:cc:dd:ee:ff,vm-uuid=inst_uuid')
mock_p2p_cna.assert_called_once_with(
self.adpt, None, 'lpar_uuid', ['mgmt_uuid'],
'NovaLinkVEABridge', configured_mtu=1450, crt_vswitch=True,
mac_addr='aa:bb:cc:dd:ee:ff', dev_name='device', ovs_bridge='br0',
ovs_ext_ids=ovs_ext_ids)
@mock.patch('pypowervm.tasks.partition.get_this_partition', autospec=True)
@mock.patch('nova.virt.powervm.vm.get_pvm_uuid')
@mock.patch('nova.virt.powervm.vm.get_cnas')
@mock.patch('pypowervm.tasks.cna.find_trunks', autospec=True)
def test_plug_existing_vif(self, mock_find_trunks, mock_get_cnas,
mock_pvm_uuid, mock_mgmt_lpar):
# Mock the data
t1, t2 = mock.MagicMock(), mock.MagicMock()
mock_find_trunks.return_value = [t1, t2]
mock_cna = mock.Mock(mac='aa:bb:cc:dd:ee:ff')
mock_get_cnas.return_value = [mock_cna]
mock_pvm_uuid.return_value = 'lpar_uuid'
mock_mgmt_lpar.return_value = mock.Mock(uuid='mgmt_uuid')
self.inst = mock.MagicMock(uuid='c2e7ff9f-b9b6-46fa-8716-93bbb795b8b4')
self.drv = vif.PvmOvsVifDriver(self.adpt, self.inst)
# Run the plug
network_model = model.Model({'bridge': 'br0', 'meta': {'mtu': 1500}})
mock_vif = model.VIF(address='aa:bb:cc:dd:ee:ff', id='vif_id',
network=network_model, devname='devname')
resp = self.drv.plug(mock_vif, new_vif=False)
self.assertIsNone(resp)
# Validate if trunk.update got invoked for all trunks of CNA of vif
self.assertTrue(t1.update.called)
self.assertTrue(t2.update.called)
@mock.patch('pypowervm.tasks.cna.find_trunks')
@mock.patch('nova.virt.powervm.vm.get_cnas')
def test_unplug(self, mock_get_cnas, mock_find_trunks):
# Set up the mocks
mock_cna = mock.Mock(mac='aa:bb:cc:dd:ee:ff')
mock_get_cnas.return_value = [mock_cna]
t1, t2 = mock.MagicMock(), mock.MagicMock()
mock_find_trunks.return_value = [t1, t2]
# Call the unplug
mock_vif = {'address': 'aa:bb:cc:dd:ee:ff',
'network': {'bridge': 'br-int'}}
self.drv.unplug(mock_vif)
# The trunks and the cna should have been deleted
self.assertTrue(t1.delete.called)
self.assertTrue(t2.delete.called)
self.assertTrue(mock_cna.delete.called)
class TestVifSeaDriver(test.NoDBTestCase):
def setUp(self):
super(TestVifSeaDriver, self).setUp()
self.adpt = mock.Mock()
self.inst = mock.Mock()
self.drv = vif.PvmSeaVifDriver(self.adpt, self.inst)
@mock.patch('nova.virt.powervm.vm.get_pvm_uuid')
@mock.patch('pypowervm.tasks.cna.crt_cna')
def test_plug_from_neutron(self, mock_crt_cna, mock_pvm_uuid):
"""Tests that a VIF can be created. Mocks Neutron net"""
# Set up the mocks. Look like Neutron
fake_vif = {'details': {'vlan': 5}, 'network': {'meta': {}},
'address': 'aabbccddeeff'}
def validate_crt(adpt, host_uuid, lpar_uuid, vlan, mac_addr=None):
self.assertIsNone(host_uuid)
self.assertEqual(5, vlan)
self.assertEqual('aabbccddeeff', mac_addr)
return pvm_net.CNA.bld(self.adpt, 5, 'host_uuid',
mac_addr=mac_addr)
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)
def test_plug_existing_vif(self):
"""Tests that a VIF need not be created."""
# Set up the mocks
fake_vif = {'network': {'meta': {'vlan': 5}},
'address': 'aabbccddeeff'}
# Invoke
resp = self.drv.plug(fake_vif, new_vif=False)
self.assertIsNone(resp)
@mock.patch('nova.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 won't 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 for each applicable vif.
# 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)