510 lines
21 KiB
Python
510 lines
21 KiB
Python
# 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 neutron_lib import constants as lib_constants
|
|
from oslo_utils import uuidutils
|
|
|
|
from neutron.agent.l3 import router_info
|
|
from neutron.agent.linux import ip_lib
|
|
from neutron.common import exceptions as n_exc
|
|
from neutron.conf.agent import common as config
|
|
from neutron.conf.agent.l3 import config as l3_config
|
|
from neutron.tests import base
|
|
|
|
|
|
_uuid = uuidutils.generate_uuid
|
|
|
|
|
|
class TestRouterInfo(base.BaseTestCase):
|
|
def setUp(self):
|
|
super(TestRouterInfo, self).setUp()
|
|
|
|
conf = config.setup_conf()
|
|
l3_config.register_l3_agent_config_opts(l3_config.OPTS, conf)
|
|
|
|
self.ip_cls_p = mock.patch('neutron.agent.linux.ip_lib.IPWrapper')
|
|
ip_cls = self.ip_cls_p.start()
|
|
self.mock_ip = mock.MagicMock()
|
|
ip_cls.return_value = self.mock_ip
|
|
self.ri_kwargs = {'agent_conf': conf,
|
|
'interface_driver': mock.sentinel.interface_driver}
|
|
|
|
def _check_agent_method_called(self, calls):
|
|
self.mock_ip.netns.execute.assert_has_calls(
|
|
[mock.call(call, check_exit_code=False) for call in calls],
|
|
any_order=True)
|
|
|
|
def test_routing_table_update(self):
|
|
ri = router_info.RouterInfo(mock.Mock(), _uuid(), {}, **self.ri_kwargs)
|
|
ri.router = {}
|
|
|
|
fake_route1 = {'destination': '135.207.0.0/16',
|
|
'nexthop': '1.2.3.4'}
|
|
fake_route2 = {'destination': '135.207.111.111/32',
|
|
'nexthop': '1.2.3.4'}
|
|
|
|
ri.update_routing_table('replace', fake_route1)
|
|
expected = [['ip', 'route', 'replace', 'to', '135.207.0.0/16',
|
|
'via', '1.2.3.4']]
|
|
self._check_agent_method_called(expected)
|
|
|
|
ri.update_routing_table('delete', fake_route1)
|
|
expected = [['ip', 'route', 'delete', 'to', '135.207.0.0/16',
|
|
'via', '1.2.3.4']]
|
|
self._check_agent_method_called(expected)
|
|
|
|
ri.update_routing_table('replace', fake_route2)
|
|
expected = [['ip', 'route', 'replace', 'to', '135.207.111.111/32',
|
|
'via', '1.2.3.4']]
|
|
self._check_agent_method_called(expected)
|
|
|
|
ri.update_routing_table('delete', fake_route2)
|
|
expected = [['ip', 'route', 'delete', 'to', '135.207.111.111/32',
|
|
'via', '1.2.3.4']]
|
|
self._check_agent_method_called(expected)
|
|
|
|
def test_update_routing_table(self):
|
|
# Just verify the correct namespace was used in the call
|
|
uuid = _uuid()
|
|
netns = 'qrouter-' + uuid
|
|
fake_route1 = {'destination': '135.207.0.0/16',
|
|
'nexthop': '1.2.3.4'}
|
|
|
|
ri = router_info.RouterInfo(mock.Mock(), uuid,
|
|
{'id': uuid}, **self.ri_kwargs)
|
|
ri._update_routing_table = mock.Mock()
|
|
|
|
ri.update_routing_table('replace', fake_route1)
|
|
ri._update_routing_table.assert_called_once_with('replace',
|
|
fake_route1,
|
|
netns)
|
|
|
|
def test_routes_updated(self):
|
|
ri = router_info.RouterInfo(mock.Mock(), _uuid(), {}, **self.ri_kwargs)
|
|
ri.router = {}
|
|
|
|
fake_old_routes = []
|
|
fake_new_routes = [{'destination': "110.100.31.0/24",
|
|
'nexthop': "10.100.10.30"},
|
|
{'destination': "110.100.30.0/24",
|
|
'nexthop': "10.100.10.30"}]
|
|
ri.routes = fake_old_routes
|
|
ri.router['routes'] = fake_new_routes
|
|
ri.routes_updated(fake_old_routes, fake_new_routes)
|
|
|
|
expected = [['ip', 'route', 'replace', 'to', '110.100.30.0/24',
|
|
'via', '10.100.10.30'],
|
|
['ip', 'route', 'replace', 'to', '110.100.31.0/24',
|
|
'via', '10.100.10.30']]
|
|
|
|
self._check_agent_method_called(expected)
|
|
ri.routes = fake_new_routes
|
|
fake_new_routes = [{'destination': "110.100.30.0/24",
|
|
'nexthop': "10.100.10.30"}]
|
|
ri.router['routes'] = fake_new_routes
|
|
ri.routes_updated(ri.routes, fake_new_routes)
|
|
expected = [['ip', 'route', 'delete', 'to', '110.100.31.0/24',
|
|
'via', '10.100.10.30']]
|
|
|
|
self._check_agent_method_called(expected)
|
|
fake_new_routes = []
|
|
ri.router['routes'] = fake_new_routes
|
|
ri.routes_updated(ri.routes, fake_new_routes)
|
|
|
|
expected = [['ip', 'route', 'delete', 'to', '110.100.30.0/24',
|
|
'via', '10.100.10.30']]
|
|
self._check_agent_method_called(expected)
|
|
|
|
def test__process_pd_iptables_rules(self):
|
|
subnet_id = _uuid()
|
|
ex_gw_port = {'id': _uuid()}
|
|
prefix = '2001:db8:cafe::/64'
|
|
|
|
ri = router_info.RouterInfo(mock.Mock(), _uuid(), {}, **self.ri_kwargs)
|
|
|
|
ipv6_mangle = ri.iptables_manager.ipv6['mangle'] = mock.MagicMock()
|
|
ri.get_ex_gw_port = mock.Mock(return_value=ex_gw_port)
|
|
ri.get_external_device_name = mock.Mock(return_value='fake_device')
|
|
ri.get_address_scope_mark_mask = mock.Mock(return_value='fake_mark')
|
|
|
|
ri._process_pd_iptables_rules(prefix, subnet_id)
|
|
|
|
mangle_rule = '-d %s ' % prefix
|
|
mangle_rule += ri.address_scope_mangle_rule('fake_device', 'fake_mark')
|
|
|
|
ipv6_mangle.add_rule.assert_called_once_with(
|
|
'scope',
|
|
mangle_rule,
|
|
tag='prefix_delegation_%s' % subnet_id)
|
|
|
|
def test_add_ports_address_scope_iptables(self):
|
|
ri = router_info.RouterInfo(mock.Mock(), _uuid(), {}, **self.ri_kwargs)
|
|
port = {
|
|
'id': _uuid(),
|
|
'fixed_ips': [{'ip_address': '172.9.9.9'}],
|
|
'address_scopes': {lib_constants.IP_VERSION_4: '1234'}
|
|
}
|
|
ipv4_mangle = ri.iptables_manager.ipv4['mangle'] = mock.MagicMock()
|
|
ri.get_address_scope_mark_mask = mock.Mock(return_value='fake_mark')
|
|
ri.get_internal_device_name = mock.Mock(return_value='fake_device')
|
|
ri.rt_tables_manager = mock.MagicMock()
|
|
ri.process_external_port_address_scope_routing = mock.Mock()
|
|
ri.process_floating_ip_address_scope_rules = mock.Mock()
|
|
ri.iptables_manager._apply = mock.Mock()
|
|
|
|
ri.router[lib_constants.INTERFACE_KEY] = [port]
|
|
ri.process_address_scope()
|
|
|
|
ipv4_mangle.add_rule.assert_called_once_with(
|
|
'scope', ri.address_scope_mangle_rule('fake_device', 'fake_mark'))
|
|
|
|
def test_address_scope_mark_ids_handling(self):
|
|
mark_ids = set(range(router_info.ADDRESS_SCOPE_MARK_ID_MIN,
|
|
router_info.ADDRESS_SCOPE_MARK_ID_MAX))
|
|
ri = router_info.RouterInfo(mock.Mock(), _uuid(), {}, **self.ri_kwargs)
|
|
# first mark id is used for the default address scope
|
|
scope_to_mark_id = {router_info.DEFAULT_ADDRESS_SCOPE: mark_ids.pop()}
|
|
self.assertEqual(scope_to_mark_id, ri._address_scope_to_mark_id)
|
|
self.assertEqual(mark_ids, ri.available_mark_ids)
|
|
|
|
# new id should be used for new address scope
|
|
ri.get_address_scope_mark_mask('new_scope')
|
|
scope_to_mark_id['new_scope'] = mark_ids.pop()
|
|
self.assertEqual(scope_to_mark_id, ri._address_scope_to_mark_id)
|
|
self.assertEqual(mark_ids, ri.available_mark_ids)
|
|
|
|
# new router should have it's own mark ids set
|
|
new_mark_ids = set(range(router_info.ADDRESS_SCOPE_MARK_ID_MIN,
|
|
router_info.ADDRESS_SCOPE_MARK_ID_MAX))
|
|
new_ri = router_info.RouterInfo(mock.Mock(), _uuid(),
|
|
{}, **self.ri_kwargs)
|
|
new_mark_ids.pop()
|
|
self.assertEqual(new_mark_ids, new_ri.available_mark_ids)
|
|
self.assertNotEqual(ri.available_mark_ids, new_ri.available_mark_ids)
|
|
|
|
def test_process_delete(self):
|
|
ri = router_info.RouterInfo(mock.Mock(), _uuid(), {}, **self.ri_kwargs)
|
|
ri.router = {'id': _uuid()}
|
|
with mock.patch.object(ri, '_process_internal_ports') as p_i_p,\
|
|
mock.patch.object(ri, '_process_external_on_delete') as p_e_o_d:
|
|
self.mock_ip.netns.exists.return_value = False
|
|
ri.process_delete()
|
|
self.assertFalse(p_i_p.called)
|
|
self.assertFalse(p_e_o_d.called)
|
|
|
|
p_i_p.reset_mock()
|
|
p_e_o_d.reset_mock()
|
|
self.mock_ip.netns.exists.return_value = True
|
|
ri.process_delete()
|
|
p_i_p.assert_called_once_with()
|
|
p_e_o_d.assert_called_once_with()
|
|
|
|
|
|
class BasicRouterTestCaseFramework(base.BaseTestCase):
|
|
def _create_router(self, router=None, **kwargs):
|
|
if not router:
|
|
router = mock.MagicMock()
|
|
self.agent_conf = mock.Mock()
|
|
self.router_id = _uuid()
|
|
return router_info.RouterInfo(mock.Mock(),
|
|
self.router_id,
|
|
router,
|
|
self.agent_conf,
|
|
mock.sentinel.interface_driver,
|
|
**kwargs)
|
|
|
|
|
|
class TestBasicRouterOperations(BasicRouterTestCaseFramework):
|
|
|
|
def test_get_floating_ips(self):
|
|
router = mock.MagicMock()
|
|
router.get.return_value = [mock.sentinel.floating_ip]
|
|
ri = self._create_router(router)
|
|
|
|
fips = ri.get_floating_ips()
|
|
|
|
self.assertEqual([mock.sentinel.floating_ip], fips)
|
|
|
|
def test_process_floating_ip_nat_rules(self):
|
|
ri = self._create_router()
|
|
fips = [{'fixed_ip_address': mock.sentinel.ip,
|
|
'floating_ip_address': mock.sentinel.fip}]
|
|
ri.get_floating_ips = mock.Mock(return_value=fips)
|
|
ri.iptables_manager = mock.MagicMock()
|
|
ipv4_nat = ri.iptables_manager.ipv4['nat']
|
|
ri.floating_forward_rules = mock.Mock(
|
|
return_value=[(mock.sentinel.chain, mock.sentinel.rule)])
|
|
|
|
ri.process_floating_ip_nat_rules()
|
|
|
|
# Be sure that the rules are cleared first and apply is called last
|
|
self.assertEqual(mock.call.clear_rules_by_tag('floating_ip'),
|
|
ipv4_nat.mock_calls[0])
|
|
self.assertEqual(mock.call.apply(), ri.iptables_manager.mock_calls[-1])
|
|
|
|
# Be sure that add_rule is called somewhere in the middle
|
|
ipv4_nat.add_rule.assert_called_once_with(mock.sentinel.chain,
|
|
mock.sentinel.rule,
|
|
tag='floating_ip')
|
|
|
|
def test_process_floating_ip_nat_rules_removed(self):
|
|
ri = self._create_router()
|
|
ri.get_floating_ips = mock.Mock(return_value=[])
|
|
ri.iptables_manager = mock.MagicMock()
|
|
ipv4_nat = ri.iptables_manager.ipv4['nat']
|
|
|
|
ri.process_floating_ip_nat_rules()
|
|
|
|
# Be sure that the rules are cleared first and apply is called last
|
|
self.assertEqual(mock.call.clear_rules_by_tag('floating_ip'),
|
|
ipv4_nat.mock_calls[0])
|
|
self.assertEqual(mock.call.apply(), ri.iptables_manager.mock_calls[-1])
|
|
|
|
# Be sure that add_rule is called somewhere in the middle
|
|
self.assertFalse(ipv4_nat.add_rule.called)
|
|
|
|
def test_process_floating_ip_address_scope_rules_diff_scopes(self):
|
|
ri = self._create_router()
|
|
fips = [{'fixed_ip_address': mock.sentinel.ip,
|
|
'floating_ip_address': mock.sentinel.fip,
|
|
'fixed_ip_address_scope': 'scope1'}]
|
|
ri.get_floating_ips = mock.Mock(return_value=fips)
|
|
ri._get_external_address_scope = mock.Mock(return_value='scope2')
|
|
ipv4_mangle = ri.iptables_manager.ipv4['mangle'] = mock.MagicMock()
|
|
ri.floating_mangle_rules = mock.Mock(
|
|
return_value=[(mock.sentinel.chain1, mock.sentinel.rule1)])
|
|
ri.get_external_device_name = mock.Mock()
|
|
|
|
ri.process_floating_ip_address_scope_rules()
|
|
|
|
# Be sure that the rules are cleared first
|
|
self.assertEqual(mock.call.clear_rules_by_tag('floating_ip'),
|
|
ipv4_mangle.mock_calls[0])
|
|
# Be sure that add_rule is called somewhere in the middle
|
|
self.assertEqual(1, ipv4_mangle.add_rule.call_count)
|
|
self.assertEqual(mock.call.add_rule(mock.sentinel.chain1,
|
|
mock.sentinel.rule1,
|
|
tag='floating_ip'),
|
|
ipv4_mangle.mock_calls[1])
|
|
|
|
def test_process_floating_ip_address_scope_rules_same_scopes(self):
|
|
ri = self._create_router()
|
|
fips = [{'fixed_ip_address': mock.sentinel.ip,
|
|
'floating_ip_address': mock.sentinel.fip,
|
|
'fixed_ip_address_scope': 'scope1'}]
|
|
ri.get_floating_ips = mock.Mock(return_value=fips)
|
|
ri._get_external_address_scope = mock.Mock(return_value='scope1')
|
|
ipv4_mangle = ri.iptables_manager.ipv4['mangle'] = mock.MagicMock()
|
|
|
|
ri.process_floating_ip_address_scope_rules()
|
|
|
|
# Be sure that the rules are cleared first
|
|
self.assertEqual(mock.call.clear_rules_by_tag('floating_ip'),
|
|
ipv4_mangle.mock_calls[0])
|
|
# Be sure that add_rule is not called somewhere in the middle
|
|
self.assertFalse(ipv4_mangle.add_rule.called)
|
|
|
|
def test_process_floating_ip_mangle_rules_removed(self):
|
|
ri = self._create_router()
|
|
ri.get_floating_ips = mock.Mock(return_value=[])
|
|
ipv4_mangle = ri.iptables_manager.ipv4['mangle'] = mock.MagicMock()
|
|
|
|
ri.process_floating_ip_address_scope_rules()
|
|
|
|
# Be sure that the rules are cleared first
|
|
self.assertEqual(mock.call.clear_rules_by_tag('floating_ip'),
|
|
ipv4_mangle.mock_calls[0])
|
|
|
|
# Be sure that add_rule is not called somewhere in the middle
|
|
self.assertFalse(ipv4_mangle.add_rule.called)
|
|
|
|
def _test_add_fip_addr_to_device_error(self, device):
|
|
ri = self._create_router()
|
|
ip = '15.1.2.3'
|
|
|
|
result = ri._add_fip_addr_to_device(
|
|
{'id': mock.sentinel.id, 'floating_ip_address': ip}, device)
|
|
|
|
device.addr.add.assert_called_with(ip + '/32')
|
|
return result
|
|
|
|
def test__add_fip_addr_to_device(self):
|
|
result = self._test_add_fip_addr_to_device_error(mock.Mock())
|
|
self.assertTrue(result)
|
|
|
|
def test__add_fip_addr_to_device_error(self):
|
|
device = mock.Mock()
|
|
device.addr.add.side_effect = RuntimeError
|
|
result = self._test_add_fip_addr_to_device_error(device)
|
|
self.assertFalse(result)
|
|
|
|
def test_process_snat_dnat_for_fip(self):
|
|
ri = self._create_router()
|
|
ri.process_floating_ip_nat_rules = mock.Mock(side_effect=Exception)
|
|
|
|
self.assertRaises(n_exc.FloatingIpSetupException,
|
|
ri.process_snat_dnat_for_fip)
|
|
|
|
ri.process_floating_ip_nat_rules.assert_called_once_with()
|
|
|
|
def test_put_fips_in_error_state(self):
|
|
ri = self._create_router()
|
|
ri.router = mock.Mock()
|
|
ri.router.get.return_value = [{'id': mock.sentinel.id1},
|
|
{'id': mock.sentinel.id2}]
|
|
|
|
statuses = ri.put_fips_in_error_state()
|
|
|
|
expected = [{mock.sentinel.id1: lib_constants.FLOATINGIP_STATUS_ERROR,
|
|
mock.sentinel.id2: lib_constants.FLOATINGIP_STATUS_ERROR}]
|
|
self.assertNotEqual(expected, statuses)
|
|
|
|
def test_configure_fip_addresses(self):
|
|
ri = self._create_router()
|
|
ri.process_floating_ip_addresses = mock.Mock(
|
|
side_effect=Exception)
|
|
|
|
self.assertRaises(n_exc.FloatingIpSetupException,
|
|
ri.configure_fip_addresses,
|
|
mock.sentinel.interface_name)
|
|
|
|
ri.process_floating_ip_addresses.assert_called_once_with(
|
|
mock.sentinel.interface_name)
|
|
|
|
def test_get_router_cidrs_returns_cidrs(self):
|
|
ri = self._create_router()
|
|
addresses = ['15.1.2.2/24', '15.1.2.3/32']
|
|
device = mock.MagicMock()
|
|
device.addr.list.return_value = [{'cidr': addresses[0]},
|
|
{'cidr': addresses[1]}]
|
|
self.assertEqual(set(addresses), ri.get_router_cidrs(device))
|
|
|
|
|
|
@mock.patch.object(ip_lib, 'IPDevice')
|
|
class TestFloatingIpWithMockDevice(BasicRouterTestCaseFramework):
|
|
|
|
def test_process_floating_ip_addresses_remap(self, IPDevice):
|
|
fip_id = _uuid()
|
|
fip = {
|
|
'id': fip_id, 'port_id': _uuid(),
|
|
'floating_ip_address': '15.1.2.3',
|
|
'fixed_ip_address': '192.168.0.2',
|
|
'status': lib_constants.FLOATINGIP_STATUS_DOWN
|
|
}
|
|
|
|
IPDevice.return_value = device = mock.Mock()
|
|
device.addr.list.return_value = [{'cidr': '15.1.2.3/32'}]
|
|
ri = self._create_router()
|
|
ri.get_floating_ips = mock.Mock(return_value=[fip])
|
|
|
|
fip_statuses = ri.process_floating_ip_addresses(
|
|
mock.sentinel.interface_name)
|
|
self.assertEqual({fip_id: lib_constants.FLOATINGIP_STATUS_ACTIVE},
|
|
fip_statuses)
|
|
|
|
self.assertFalse(device.addr.add.called)
|
|
self.assertFalse(device.addr.delete.called)
|
|
|
|
def test_process_router_with_disabled_floating_ip(self, IPDevice):
|
|
fip_id = _uuid()
|
|
fip = {
|
|
'id': fip_id, 'port_id': _uuid(),
|
|
'floating_ip_address': '15.1.2.3',
|
|
'fixed_ip_address': '192.168.0.2'
|
|
}
|
|
|
|
ri = self._create_router()
|
|
ri.floating_ips = [fip]
|
|
ri.get_floating_ips = mock.Mock(return_value=[])
|
|
|
|
fip_statuses = ri.process_floating_ip_addresses(
|
|
mock.sentinel.interface_name)
|
|
|
|
self.assertIsNone(fip_statuses.get(fip_id))
|
|
|
|
def test_process_router_floating_ip_with_device_add_error(self, IPDevice):
|
|
IPDevice.return_value = device = mock.Mock(side_effect=RuntimeError)
|
|
device.addr.list.return_value = []
|
|
fip_id = _uuid()
|
|
fip = {
|
|
'id': fip_id, 'port_id': _uuid(),
|
|
'floating_ip_address': '15.1.2.3',
|
|
'fixed_ip_address': '192.168.0.2',
|
|
'status': 'DOWN'
|
|
}
|
|
ri = self._create_router()
|
|
ri.add_floating_ip = mock.Mock(
|
|
return_value=lib_constants.FLOATINGIP_STATUS_ERROR)
|
|
ri.get_floating_ips = mock.Mock(return_value=[fip])
|
|
|
|
fip_statuses = ri.process_floating_ip_addresses(
|
|
mock.sentinel.interface_name)
|
|
|
|
self.assertEqual({fip_id: lib_constants.FLOATINGIP_STATUS_ERROR},
|
|
fip_statuses)
|
|
|
|
# TODO(mrsmith): refactor for DVR cases
|
|
def test_process_floating_ip_addresses_remove(self, IPDevice):
|
|
IPDevice.return_value = device = mock.Mock()
|
|
device.addr.list.return_value = [{'cidr': '15.1.2.3/32'}]
|
|
|
|
ri = self._create_router()
|
|
ri.remove_floating_ip = mock.Mock()
|
|
ri.router.get = mock.Mock(return_value=[])
|
|
|
|
fip_statuses = ri.process_floating_ip_addresses(
|
|
mock.sentinel.interface_name)
|
|
self.assertEqual({}, fip_statuses)
|
|
ri.remove_floating_ip.assert_called_once_with(device, '15.1.2.3/32')
|
|
|
|
def test_process_floating_ip_reassignment(self, IPDevice):
|
|
IPDevice.return_value = device = mock.Mock()
|
|
device.addr.list.return_value = [{'cidr': '15.1.2.3/32'}]
|
|
|
|
fip_id = _uuid()
|
|
fip = {
|
|
'id': fip_id, 'port_id': _uuid(),
|
|
'floating_ip_address': '15.1.2.3',
|
|
'fixed_ip_address': '192.168.0.3',
|
|
'status': 'DOWN'
|
|
}
|
|
ri = self._create_router()
|
|
ri.get_floating_ips = mock.Mock(return_value=[fip])
|
|
ri.move_floating_ip = mock.Mock()
|
|
ri.fip_map = {'15.1.2.3': '192.168.0.2'}
|
|
|
|
ri.process_floating_ip_addresses(mock.sentinel.interface_name)
|
|
ri.move_floating_ip.assert_called_once_with(fip)
|
|
|
|
def test_process_floating_ip_addresses_gw_secondary_ip_not_removed(
|
|
self, IPDevice):
|
|
IPDevice.return_value = device = mock.Mock()
|
|
device.addr.list.return_value = [{'cidr': '1.1.1.1/16'},
|
|
{'cidr': '2.2.2.2/32'},
|
|
{'cidr': '3.3.3.3/32'},
|
|
{'cidr': '4.4.4.4/32'}]
|
|
ri = self._create_router()
|
|
|
|
ri.get_floating_ips = mock.Mock(return_value=[
|
|
{'id': _uuid(),
|
|
'floating_ip_address': '3.3.3.3',
|
|
'status': 'DOWN'}])
|
|
ri.add_floating_ip = mock.Mock()
|
|
ri.get_ex_gw_port = mock.Mock(return_value={
|
|
"fixed_ips": [{"ip_address": "1.1.1.1"},
|
|
{"ip_address": "2.2.2.2"}]})
|
|
ri.remove_floating_ip = mock.Mock()
|
|
|
|
ri.process_floating_ip_addresses("qg-fake-device")
|
|
ri.remove_floating_ip.assert_called_once_with(device, '4.4.4.4/32')
|