It was suggested that we replace TestCase with NoDBTestCase [1]. This does two things. First it prevents the test case setup from running the database schema migrations unnecessarily which should improve performance. Second it will cause the test to fail if the code does touch the DB when the developer is claiming that it shouldn't. [1] https://review.openstack.org/#/c/409404/46/nova/tests/unit/virt/powervm/test_media.py Change-Id: I8bcc9cbdcadd9fe89a4450ca405387899baa9fc3
403 lines
17 KiB
Python
403 lines
17 KiB
Python
# Copyright 2015, 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 copy
|
|
import eventlet
|
|
import mock
|
|
|
|
from nova import exception
|
|
from nova import objects
|
|
from nova import test
|
|
from pypowervm.tests import test_fixtures as pvm_fx
|
|
from pypowervm.wrappers import iocard as pvm_card
|
|
from pypowervm.wrappers import network as pvm_net
|
|
|
|
from nova_powervm.tests.virt import powervm
|
|
from nova_powervm.virt.powervm.tasks import network as tf_net
|
|
|
|
|
|
def cna(mac):
|
|
"""Builds a mock Client Network Adapter (or VNIC) for unit tests."""
|
|
nic = mock.MagicMock()
|
|
nic.mac = mac
|
|
nic.vswitch_uri = 'fake_href'
|
|
return nic
|
|
|
|
|
|
class TestNetwork(test.NoDBTestCase):
|
|
def setUp(self):
|
|
super(TestNetwork, self).setUp()
|
|
self.flags(host='host1')
|
|
self.apt = self.useFixture(pvm_fx.AdapterFx()).adpt
|
|
|
|
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, mock_unplug):
|
|
"""Tests that a delete of the vif can be done."""
|
|
inst = objects.Instance(**powervm.TEST_INSTANCE)
|
|
|
|
# Mock up the CNA responses.
|
|
cnas = [cna('AABBCCDDEEFF'), cna('AABBCCDDEE11'), cna('AABBCCDDEE22')]
|
|
mock_vm_get.return_value = cnas
|
|
|
|
# Mock up the network info. This also validates that they will be
|
|
# sanitized to upper case.
|
|
net_info = [
|
|
{'address': 'aa:bb:cc:dd:ee:ff'}, {'address': 'aa:bb:cc:dd:ee:22'},
|
|
{'address': 'aa:bb:cc:dd:ee:33'}
|
|
]
|
|
|
|
# Mock out the vif driver
|
|
def validate_unplug(adapter, host_uuid, instance, vif,
|
|
slot_mgr, 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('slot_mgr', slot_mgr)
|
|
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',
|
|
'slot_mgr')
|
|
p_vifs.execute(self.mock_lpar_wrap)
|
|
|
|
# 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."""
|
|
inst = objects.Instance(**powervm.TEST_INSTANCE)
|
|
|
|
# Mock that the state is incorrect
|
|
self.mock_lpar_wrap.can_modify_io.return_value = False, 'bad'
|
|
|
|
# Run method
|
|
p_vifs = tf_net.UnplugVifs(self.apt, inst, mock.Mock(), 'host_uuid',
|
|
'slot_mgr')
|
|
self.assertRaises(exception.VirtualInterfaceUnplugException,
|
|
p_vifs.execute, self.mock_lpar_wrap)
|
|
|
|
@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_vnics')
|
|
def test_plug_vifs_rmc(self, mock_vnic_get, mock_cna_get, mock_plug):
|
|
"""Tests that a crt vif can be done with secure RMC."""
|
|
inst = objects.Instance(**powervm.TEST_INSTANCE)
|
|
|
|
# Mock up the CNA response. One should already exist, the other
|
|
# should not.
|
|
pre_cnas = [cna('AABBCCDDEEFF'), cna('AABBCCDDEE11')]
|
|
mock_cna_get.return_value = copy.deepcopy(pre_cnas)
|
|
# Ditto VNIC response.
|
|
mock_vnic_get.return_value = [cna('AABBCCDDEE33'), cna('AABBCCDDEE44')]
|
|
|
|
# Mock up the network info. This also validates that they will be
|
|
# sanitized to upper case.
|
|
net_info = [
|
|
{'address': 'aa:bb:cc:dd:ee:ff', 'vnic_type': 'normal'},
|
|
{'address': 'aa:bb:cc:dd:ee:22', 'vnic_type': 'normal'},
|
|
{'address': 'aa:bb:cc:dd:ee:33', 'vnic_type': 'direct'},
|
|
{'address': 'aa:bb:cc:dd:ee:55', 'vnic_type': 'direct'}
|
|
]
|
|
|
|
# Both updates run first (one CNA, one VNIC); then the CNA create, then
|
|
# the VNIC create.
|
|
mock_new_cna = mock.Mock(spec=pvm_net.CNA)
|
|
mock_new_vnic = mock.Mock(spec=pvm_card.VNIC)
|
|
mock_plug.side_effect = ['upd_cna', 'upd_vnic',
|
|
mock_new_cna, mock_new_vnic]
|
|
|
|
# Run method
|
|
p_vifs = tf_net.PlugVifs(mock.MagicMock(), self.apt, inst, net_info,
|
|
'host_uuid', 'slot_mgr')
|
|
|
|
all_cnas = p_vifs.execute(self.mock_lpar_wrap)
|
|
|
|
# new vif should be created twice.
|
|
mock_plug.assert_any_call(self.apt, 'host_uuid', inst, net_info[0],
|
|
'slot_mgr', new_vif=False)
|
|
mock_plug.assert_any_call(self.apt, 'host_uuid', inst, net_info[1],
|
|
'slot_mgr', new_vif=True)
|
|
mock_plug.assert_any_call(self.apt, 'host_uuid', inst, net_info[2],
|
|
'slot_mgr', new_vif=False)
|
|
mock_plug.assert_any_call(self.apt, 'host_uuid', inst, net_info[3],
|
|
'slot_mgr', new_vif=True)
|
|
|
|
# The Task provides the list of original CNAs plus only CNAs that were
|
|
# created.
|
|
self.assertEqual(pre_cnas + [mock_new_cna], all_cnas)
|
|
|
|
@mock.patch('nova_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_plug):
|
|
"""Verifies if no creates are needed, none are done."""
|
|
inst = objects.Instance(**powervm.TEST_INSTANCE)
|
|
|
|
# Mock up the CNA response. Both should already exist.
|
|
mock_vm_get.return_value = [cna('AABBCCDDEEFF'), cna('AABBCCDDEE11')]
|
|
|
|
# Mock up the network info. This also validates that they will be
|
|
# sanitized to upper case. This also validates that we don't call
|
|
# get_vnics if no nets have vnic_type 'direct'.
|
|
net_info = [
|
|
{'address': 'aa:bb:cc:dd:ee:ff', 'vnic_type': 'normal'},
|
|
{'address': 'aa:bb:cc:dd:ee:11', 'vnic_type': 'normal'}
|
|
]
|
|
|
|
# Run method
|
|
p_vifs = tf_net.PlugVifs(mock.MagicMock(), self.apt, inst, net_info,
|
|
'host_uuid', 'slot_mgr')
|
|
p_vifs.execute(self.mock_lpar_wrap)
|
|
|
|
# The create should have been called with new_vif as False.
|
|
mock_plug.assert_called_with(
|
|
self.apt, 'host_uuid', inst, net_info[1],
|
|
'slot_mgr', new_vif=False)
|
|
|
|
@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_plug):
|
|
"""Tests that a crt_vif fails when the LPAR state is bad."""
|
|
inst = objects.Instance(**powervm.TEST_INSTANCE)
|
|
|
|
# Mock up the CNA response. Only doing one for simplicity
|
|
mock_vm_get.return_value = []
|
|
net_info = [{'address': 'aa:bb:cc:dd:ee:ff', 'vnic_type': 'normal'}]
|
|
|
|
# Mock that the state is incorrect
|
|
self.mock_lpar_wrap.can_modify_io.return_value = False, 'bad'
|
|
|
|
# Run method
|
|
p_vifs = tf_net.PlugVifs(mock.MagicMock(), self.apt, inst, net_info,
|
|
'host_uuid', 'slot_mgr')
|
|
self.assertRaises(exception.VirtualInterfaceCreateException,
|
|
p_vifs.execute, self.mock_lpar_wrap)
|
|
|
|
# The create should not have been invoked
|
|
self.assertEqual(0, mock_plug.call_count)
|
|
|
|
@mock.patch('nova_powervm.virt.powervm.vif.plug')
|
|
@mock.patch('nova_powervm.virt.powervm.vm.get_cnas')
|
|
def test_plug_vifs_timeout(self, mock_vm_get, mock_plug):
|
|
"""Tests that crt vif failure via loss of neutron callback."""
|
|
inst = objects.Instance(**powervm.TEST_INSTANCE)
|
|
|
|
# Mock up the CNA response. Only doing one for simplicity
|
|
mock_vm_get.return_value = [cna('AABBCCDDEE11')]
|
|
|
|
# Mock up the network info.
|
|
net_info = [{'address': 'aa:bb:cc:dd:ee:ff', 'vnic_type': 'normal'}]
|
|
|
|
# Ensure that an exception is raised by a timeout.
|
|
mock_plug.side_effect = eventlet.timeout.Timeout()
|
|
|
|
# Run method
|
|
p_vifs = tf_net.PlugVifs(mock.MagicMock(), self.apt, inst, net_info,
|
|
'host_uuid', 'slot_mgr')
|
|
self.assertRaises(exception.VirtualInterfaceCreateException,
|
|
p_vifs.execute, self.mock_lpar_wrap)
|
|
|
|
# The create should have only been called once.
|
|
self.assertEqual(1, mock_plug.call_count)
|
|
|
|
@mock.patch('nova_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_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')
|
|
|
|
# Mock up the CNA response. Only doing one for simplicity
|
|
mock_vm_get.return_value = [cna('AABBCCDDEE11')]
|
|
|
|
# Mock up the network info.
|
|
net_info = [{'address': 'aa:bb:cc:dd:ee:ff', 'vnic_type': 'normal'}]
|
|
|
|
# Run method
|
|
p_vifs = tf_net.PlugVifs(mock.MagicMock(), self.apt, inst, net_info,
|
|
'host_uuid', 'slot_mgr')
|
|
with mock.patch.object(inst, 'save') as mock_inst_save:
|
|
p_vifs.execute(self.mock_lpar_wrap)
|
|
|
|
# The create should have only been called once.
|
|
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.vif.plug')
|
|
@mock.patch('nova_powervm.virt.powervm.vm.get_cnas')
|
|
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')
|
|
|
|
# Mock up the CNA response. Only doing one for simplicity
|
|
mock_vm_get.return_value = [cna('AABBCCDDEE11')]
|
|
|
|
# Mock up the network info.
|
|
net_info = [{'address': 'aa:bb:cc:dd:ee:ff', 'vnic_type': 'normal'}]
|
|
|
|
# Ensure that an exception is raised by a timeout.
|
|
mock_plug.side_effect = eventlet.timeout.Timeout()
|
|
|
|
# Run method
|
|
p_vifs = tf_net.PlugVifs(mock.MagicMock(), self.apt, inst, net_info,
|
|
'host_uuid', 'slot_mgr')
|
|
with mock.patch.object(inst, 'save') as mock_inst_save:
|
|
self.assertRaises(exception.VirtualInterfaceCreateException,
|
|
p_vifs.execute, self.mock_lpar_wrap)
|
|
|
|
# The create should have only been called once.
|
|
self.assertEqual(1, mock_plug.call_count)
|
|
# 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.vif.unplug')
|
|
@mock.patch('nova_powervm.virt.powervm.vif.plug')
|
|
@mock.patch('nova_powervm.virt.powervm.vm.get_cnas')
|
|
def test_plug_vifs_revert(self, mock_vm_get, mock_plug, mock_unplug):
|
|
"""Tests that the revert flow works properly."""
|
|
inst = objects.Instance(**powervm.TEST_INSTANCE)
|
|
|
|
# Fake CNA list. The one pre-existing VIF should *not* get reverted.
|
|
cna_list = [cna('AABBCCDDEEFF'), cna('FFEEDDCCBBAA')]
|
|
mock_vm_get.return_value = cna_list
|
|
|
|
# Mock up the network info. Three roll backs.
|
|
net_info = [
|
|
{'address': 'aa:bb:cc:dd:ee:ff', 'vnic_type': 'normal'},
|
|
{'address': 'aa:bb:cc:dd:ee:22', 'vnic_type': 'normal'},
|
|
{'address': 'aa:bb:cc:dd:ee:33', 'vnic_type': 'normal'}
|
|
]
|
|
|
|
# Make sure we test raising an exception
|
|
mock_unplug.side_effect = [exception.NovaException(), None]
|
|
|
|
# Run method
|
|
p_vifs = tf_net.PlugVifs(mock.MagicMock(), self.apt, inst, net_info,
|
|
'host_uuid', 'slot_mgr')
|
|
p_vifs.execute(self.mock_lpar_wrap)
|
|
p_vifs.revert(self.mock_lpar_wrap, mock.Mock(), mock.Mock())
|
|
|
|
# The unplug should be called twice. The exception shouldn't stop the
|
|
# second call.
|
|
self.assertEqual(2, mock_unplug.call_count)
|
|
|
|
# Make sure each call is invoked correctly. The first plug was not a
|
|
# new vif, so it should not be reverted.
|
|
c2 = mock.call(self.apt, 'host_uuid', inst, net_info[1],
|
|
'slot_mgr', cna_w_list=cna_list)
|
|
c3 = mock.call(self.apt, 'host_uuid', inst, net_info[2],
|
|
'slot_mgr', cna_w_list=cna_list)
|
|
mock_unplug.assert_has_calls([c2, c3])
|
|
|
|
@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_plug,
|
|
mock_get_rmc_vswitch, mock_plug_rmc_vif):
|
|
"""Tests that a mgmt vif can be created."""
|
|
inst = objects.Instance(**powervm.TEST_INSTANCE)
|
|
|
|
# Mock up the rmc vswitch
|
|
vswitch_w = mock.MagicMock()
|
|
vswitch_w.href = 'fake_mgmt_uri'
|
|
mock_get_rmc_vswitch.return_value = vswitch_w
|
|
|
|
# Run method such that it triggers a fresh CNA search
|
|
p_vifs = tf_net.PlugMgmtVif(self.apt, inst, 'host_uuid', 'slot_mgr')
|
|
p_vifs.execute(None)
|
|
|
|
# With the default get_cnas mock (which returns a Mock()), we think we
|
|
# found an existing management CNA.
|
|
mock_plug_rmc_vif.assert_not_called()
|
|
mock_vm_get.assert_called_once_with(
|
|
self.apt, inst, vswitch_uri='fake_mgmt_uri')
|
|
|
|
# Now mock get_cnas to return no hits
|
|
mock_vm_get.reset_mock()
|
|
mock_vm_get.return_value = []
|
|
p_vifs.execute(None)
|
|
|
|
# Get was called; and since it didn't have the mgmt CNA, so was plug.
|
|
self.assertEqual(1, mock_plug_rmc_vif.call_count)
|
|
mock_vm_get.assert_called_once_with(
|
|
self.apt, inst, vswitch_uri='fake_mgmt_uri')
|
|
|
|
# Now pass CNAs, but not the mgmt vif, "from PlugVifs"
|
|
cnas = [mock.Mock(vswitch_uri='uri1'), mock.Mock(vswitch_uri='uri2')]
|
|
mock_plug_rmc_vif.reset_mock()
|
|
mock_vm_get.reset_mock()
|
|
p_vifs.execute(cnas)
|
|
|
|
# Get wasn't called, since the CNAs were passed "from PlugVifs"; but
|
|
# since the mgmt vif wasn't included, plug was called.
|
|
mock_vm_get.assert_not_called()
|
|
mock_plug_rmc_vif.assert_called()
|
|
|
|
# Finally, pass CNAs including the mgmt.
|
|
cnas.append(mock.Mock(vswitch_uri='fake_mgmt_uri'))
|
|
mock_plug_rmc_vif.reset_mock()
|
|
p_vifs.execute(cnas)
|
|
|
|
# Neither get nor plug was called.
|
|
mock_vm_get.assert_not_called()
|
|
mock_plug_rmc_vif.assert_not_called()
|
|
|
|
@mock.patch('nova.utils.is_neutron')
|
|
def test_get_vif_events(self, mock_is_neutron):
|
|
# Set up common mocks.
|
|
inst = objects.Instance(**powervm.TEST_INSTANCE)
|
|
net_info = [mock.MagicMock(), mock.MagicMock()]
|
|
net_info[0]['id'] = 'a'
|
|
net_info[0].get.return_value = False
|
|
net_info[1]['id'] = 'b'
|
|
net_info[1].get.return_value = True
|
|
|
|
# Set up the runner.
|
|
p_vifs = tf_net.PlugVifs(mock.MagicMock(), self.apt, inst, net_info,
|
|
'host_uuid', 'slot_mgr')
|
|
p_vifs.crt_network_infos = net_info
|
|
|
|
# Mock that neutron is off.
|
|
mock_is_neutron.return_value = False
|
|
self.assertEqual([], p_vifs._get_vif_events())
|
|
|
|
# Turn neutron on.
|
|
mock_is_neutron.return_value = True
|
|
resp = p_vifs._get_vif_events()
|
|
|
|
# Only one should be returned since only one was active.
|
|
self.assertEqual(1, len(resp))
|