From e1ec15c9a860bc431174d8db63ce50e2d143b79c Mon Sep 17 00:00:00 2001 From: Adam Harwell Date: Wed, 8 Feb 2017 13:07:21 -0800 Subject: [PATCH] Allow to create vip in lb-mgmt-net Change-Id: Ie2c916bd557190e5dfead12c2635955da92c52ff Closes-Bug: #1659488 --- .../drivers/neutron/allowed_address_pairs.py | 20 ++-- octavia/tests/common/constants.py | 91 ++++++++++++++++++- octavia/tests/common/data_model_helpers.py | 11 ++- .../neutron/test_allowed_address_pairs.py | 86 ++++++++++++++---- ...llow-vip-on-mgmt-net-d6c65d4ccb2a8f2c.yaml | 4 + 5 files changed, 180 insertions(+), 32 deletions(-) create mode 100644 releasenotes/notes/allow-vip-on-mgmt-net-d6c65d4ccb2a8f2c.yaml diff --git a/octavia/network/drivers/neutron/allowed_address_pairs.py b/octavia/network/drivers/neutron/allowed_address_pairs.py index a83e35a8f4..3de317ae11 100644 --- a/octavia/network/drivers/neutron/allowed_address_pairs.py +++ b/octavia/network/drivers/neutron/allowed_address_pairs.py @@ -74,10 +74,14 @@ class AllowedAddressPairsDriver(neutron_base.BaseNeutronDriver): ret.append(interface) return ret - def _get_plugged_interface(self, compute_id, network_id): + def _get_plugged_interface(self, compute_id, network_id, lb_network_ip): interfaces = self.get_plugged_networks(compute_id) for interface in interfaces: - if interface.network_id == network_id: + is_correct_interface = interface.network_id == network_id + for ip in interface.fixed_ips: + if ip.ip_address == lb_network_ip: + is_correct_interface = False + if is_correct_interface: return interface def _plug_amphora_vip(self, amphora, subnet): @@ -299,8 +303,8 @@ class AllowedAddressPairsDriver(neutron_base.BaseNeutronDriver): lambda amp: amp.status == constants.AMPHORA_ALLOCATED, load_balancer.amphorae): - interface = self._get_plugged_interface(amphora.compute_id, - subnet.network_id) + interface = self._get_plugged_interface( + amphora.compute_id, subnet.network_id, amphora.lb_network_ip) if not interface: interface = self._plug_amphora_vip(amphora, subnet) @@ -310,7 +314,9 @@ class AllowedAddressPairsDriver(neutron_base.BaseNeutronDriver): interface.port_id) vrrp_ip = None for fixed_ip in interface.fixed_ips: - if fixed_ip.subnet_id == subnet.id: + is_correct_subnet = fixed_ip.subnet_id == subnet.id + is_management_ip = fixed_ip.ip_address == amphora.lb_network_ip + if is_correct_subnet and not is_management_ip: vrrp_ip = fixed_ip.ip_address break plugged_amphorae.append(data_models.Amphora( @@ -358,8 +364,8 @@ class AllowedAddressPairsDriver(neutron_base.BaseNeutronDriver): lambda amp: amp.status == constants.AMPHORA_ALLOCATED, load_balancer.amphorae): - interface = self._get_plugged_interface(amphora.compute_id, - subnet.network_id) + interface = self._get_plugged_interface( + amphora.compute_id, subnet.network_id, amphora.lb_network_ip) if not interface: # Thought about raising PluggedVIPNotFound exception but # then that wouldn't evaluate all amphorae, so just continue diff --git a/octavia/tests/common/constants.py b/octavia/tests/common/constants.py index c20000402a..41e56df39f 100644 --- a/octavia/tests/common/constants.py +++ b/octavia/tests/common/constants.py @@ -18,14 +18,18 @@ class MockNovaInterface(object): port_id = None fixed_ips = [] -MOCK_NETWORK_ID = '1' +MOCK_NETWORK_ID = 'mock-network-1' +MOCK_NETWORK_ID2 = 'mock-network-2' MOCK_NETWORK_NAME = 'TestNet1' -MOCK_SUBNET_ID = '2' +MOCK_SUBNET_ID = 'mock-subnet-1' +MOCK_SUBNET_ID2 = 'mock-subnet-2' MOCK_SUBNET_NAME = 'TestSubnet1' -MOCK_PORT_ID = '3' +MOCK_PORT_ID = 'mock-port-1' +MOCK_PORT_ID2 = 'mock-port-2' MOCK_PORT_NAME = 'TestPort1' -MOCK_COMPUTE_ID = '4' +MOCK_COMPUTE_ID = 'mock-compute-1' MOCK_IP_ADDRESS = '10.0.0.1' +MOCK_IP_ADDRESS2 = '10.0.0.2' MOCK_CIDR = '10.0.0.0/24' MOCK_MAC_ADDR = 'fe:16:3e:00:95:5c' MOCK_NOVA_INTERFACE = MockNovaInterface() @@ -33,6 +37,12 @@ MOCK_SUBNET = {'subnet': {'id': MOCK_SUBNET_ID, 'network_id': MOCK_NETWORK_ID}} MOCK_NOVA_INTERFACE.net_id = MOCK_NETWORK_ID MOCK_NOVA_INTERFACE.port_id = MOCK_PORT_ID MOCK_NOVA_INTERFACE.fixed_ips = [{'ip_address': MOCK_IP_ADDRESS}] +MOCK_NOVA_INTERFACE2 = MockNovaInterface() +MOCK_SUBNET2 = {'subnet': {'id': MOCK_SUBNET_ID2, + 'network_id': MOCK_NETWORK_ID2}} +MOCK_NOVA_INTERFACE2.net_id = MOCK_NETWORK_ID2 +MOCK_NOVA_INTERFACE2.port_id = MOCK_PORT_ID2 +MOCK_NOVA_INTERFACE2.fixed_ips = [{'ip_address': MOCK_IP_ADDRESS2}] MOCK_DEVICE_OWNER = 'Moctavia' MOCK_DEVICE_ID = 'Moctavia123' @@ -42,3 +52,76 @@ MOCK_NEUTRON_PORT = {'port': {'network_id': MOCK_NETWORK_ID, 'id': MOCK_PORT_ID, 'fixed_ips': [{'ip_address': MOCK_IP_ADDRESS, 'subnet_id': MOCK_SUBNET_ID}]}} + +MOCK_AMP_ID1 = 'amp1-id' +MOCK_AMP_ID2 = 'amp2-id' +MOCK_AMP_COMPUTE_ID1 = 'amp1-compute-id' +MOCK_AMP_COMPUTE_ID2 = 'amp2-compute-id' + +MOCK_MANAGEMENT_SUBNET_ID = 'mgmt-subnet-1' +MOCK_MANAGEMENT_NET_ID = 'mgmt-net-1' +MOCK_MANAGEMENT_PORT_ID1 = 'mgmt-port-1' +MOCK_MANAGEMENT_PORT_ID2 = 'mgmt-port-2' +# These IPs become lb_network_ip +MOCK_MANAGEMENT_IP1 = '99.99.99.1' +MOCK_MANAGEMENT_IP2 = '99.99.99.2' + +MOCK_MANAGEMENT_FIXED_IPS1 = [{'ip_address': MOCK_MANAGEMENT_IP1, + 'subnet_id': MOCK_MANAGEMENT_SUBNET_ID}] +MOCK_MANAGEMENT_FIXED_IPS2 = [{'ip_address': MOCK_MANAGEMENT_IP2, + 'subnet_id': MOCK_MANAGEMENT_SUBNET_ID}] + +MOCK_MANAGEMENT_INTERFACE1 = MockNovaInterface() +MOCK_MANAGEMENT_INTERFACE1.net_id = MOCK_MANAGEMENT_NET_ID +MOCK_MANAGEMENT_INTERFACE1.port_id = MOCK_MANAGEMENT_PORT_ID1 +MOCK_MANAGEMENT_INTERFACE1.fixed_ips = MOCK_MANAGEMENT_FIXED_IPS1 +MOCK_MANAGEMENT_INTERFACE2 = MockNovaInterface() +MOCK_MANAGEMENT_INTERFACE2.net_id = MOCK_MANAGEMENT_NET_ID +MOCK_MANAGEMENT_INTERFACE2.port_id = MOCK_MANAGEMENT_PORT_ID2 +MOCK_MANAGEMENT_INTERFACE2.fixed_ips = MOCK_MANAGEMENT_FIXED_IPS2 + +MOCK_MANAGEMENT_PORT1 = {'port': {'network_id': MOCK_MANAGEMENT_NET_ID, + 'device_id': MOCK_AMP_COMPUTE_ID1, + 'device_owner': MOCK_DEVICE_OWNER, + 'id': MOCK_MANAGEMENT_PORT_ID1, + 'fixed_ips': MOCK_MANAGEMENT_FIXED_IPS1}} + +MOCK_MANAGEMENT_PORT2 = {'port': {'network_id': MOCK_MANAGEMENT_NET_ID, + 'device_id': MOCK_AMP_COMPUTE_ID2, + 'device_owner': MOCK_DEVICE_OWNER, + 'id': MOCK_MANAGEMENT_PORT_ID2, + 'fixed_ips': MOCK_MANAGEMENT_FIXED_IPS2}} + +MOCK_VIP_SUBNET_ID = 'vip-subnet-1' +MOCK_VIP_NET_ID = 'vip-net-1' +MOCK_VRRP_PORT_ID1 = 'vrrp-port-1' +MOCK_VRRP_PORT_ID2 = 'vrrp-port-2' +# These IPs become vrrp_ip +MOCK_VRRP_IP1 = '55.55.55.1' +MOCK_VRRP_IP2 = '55.55.55.2' + +MOCK_VRRP_FIXED_IPS1 = [{'ip_address': MOCK_VRRP_IP1, + 'subnet_id': MOCK_VIP_SUBNET_ID}] +MOCK_VRRP_FIXED_IPS2 = [{'ip_address': MOCK_VRRP_IP2, + 'subnet_id': MOCK_VIP_SUBNET_ID}] + +MOCK_VRRP_INTERFACE1 = MockNovaInterface() +MOCK_VRRP_INTERFACE1.net_id = MOCK_VIP_NET_ID +MOCK_VRRP_INTERFACE1.port_id = MOCK_VRRP_PORT_ID1 +MOCK_VRRP_INTERFACE1.fixed_ips = MOCK_VRRP_FIXED_IPS1 +MOCK_VRRP_INTERFACE2 = MockNovaInterface() +MOCK_VRRP_INTERFACE2.net_id = MOCK_VIP_NET_ID +MOCK_VRRP_INTERFACE2.port_id = MOCK_VRRP_PORT_ID2 +MOCK_VRRP_INTERFACE2.fixed_ips = MOCK_VRRP_FIXED_IPS2 + +MOCK_VRRP_PORT1 = {'port': {'network_id': MOCK_VIP_NET_ID, + 'device_id': MOCK_AMP_COMPUTE_ID1, + 'device_owner': MOCK_DEVICE_OWNER, + 'id': MOCK_VRRP_PORT_ID1, + 'fixed_ips': MOCK_VRRP_FIXED_IPS1}} + +MOCK_VRRP_PORT2 = {'port': {'network_id': MOCK_VIP_NET_ID, + 'device_id': MOCK_AMP_COMPUTE_ID2, + 'device_owner': MOCK_DEVICE_OWNER, + 'id': MOCK_VRRP_PORT_ID2, + 'fixed_ips': MOCK_VRRP_FIXED_IPS2}} diff --git a/octavia/tests/common/data_model_helpers.py b/octavia/tests/common/data_model_helpers.py index 16f62e4bc3..11f32f2261 100644 --- a/octavia/tests/common/data_model_helpers.py +++ b/octavia/tests/common/data_model_helpers.py @@ -14,6 +14,7 @@ from octavia.common import constants from octavia.common import data_models +from octavia.tests.common import constants as ut_constants def generate_load_balancer_tree(): @@ -54,8 +55,8 @@ def generate_vip(load_balancer=None): global VIP_SEED VIP_SEED += 1 vip = data_models.Vip(ip_address='10.0.0.{0}'.format(VIP_SEED), - subnet_id='subnet{0}-id'.format(VIP_SEED), - port_id='port{0}-id'.format(VIP_SEED), + subnet_id=ut_constants.MOCK_VIP_SUBNET_ID, + port_id='vrrp-port-{0}'.format(VIP_SEED), load_balancer=load_balancer) if load_balancer: vip.load_balancer_id = load_balancer.id @@ -69,10 +70,10 @@ def generate_amphora(load_balancer=None): global AMP_SEED AMP_SEED += 1 amp = data_models.Amphora(id='amp{0}-id'.format(AMP_SEED), - compute_id='compute{0}-id'.format(AMP_SEED), + compute_id='amp{0}-compute-id'.format(AMP_SEED), status='ACTIVE', - lb_network_ip='11.0.0.{0}'.format(AMP_SEED), - vrrp_ip='12.0.0.{0}'.format(AMP_SEED), + lb_network_ip='99.99.99.{0}'.format(AMP_SEED), + vrrp_ip='55.55.55.{0}'.format(AMP_SEED), load_balancer=load_balancer) if load_balancer: amp.load_balancer_id = load_balancer.id diff --git a/octavia/tests/unit/network/drivers/neutron/test_allowed_address_pairs.py b/octavia/tests/unit/network/drivers/neutron/test_allowed_address_pairs.py index 0b50267c08..a0f83f395c 100644 --- a/octavia/tests/unit/network/drivers/neutron/test_allowed_address_pairs.py +++ b/octavia/tests/unit/network/drivers/neutron/test_allowed_address_pairs.py @@ -252,11 +252,17 @@ class TestAllowedAddressPairsDriver(base.TestCase): show_subnet = self.driver.neutron_client.show_subnet show_subnet.return_value = { 'subnet': { - 'id': lb.vip.subnet_id + 'id': t_constants.MOCK_VIP_SUBNET_ID, + 'network_id': t_constants.MOCK_VIP_NET_ID } } + list_ports = self.driver.neutron_client.list_ports + port1 = t_constants.MOCK_MANAGEMENT_PORT1['port'] + port2 = t_constants.MOCK_MANAGEMENT_PORT2['port'] + list_ports.side_effect = [{'ports': [port1]}, {'ports': [port2]}] interface_attach = self.driver.nova_client.servers.interface_attach - interface_attach.return_value = t_constants.MOCK_NOVA_INTERFACE + interface_attach.side_effect = [t_constants.MOCK_VRRP_INTERFACE1, + t_constants.MOCK_VRRP_INTERFACE2] list_security_groups = self.driver.neutron_client.list_security_groups list_security_groups.return_value = { 'security_groups': [ @@ -266,22 +272,69 @@ class TestAllowedAddressPairsDriver(base.TestCase): update_port = self.driver.neutron_client.update_port expected_aap = {'port': {'allowed_address_pairs': [{'ip_address': lb.vip.ip_address}]}} - interface_list = self.driver.nova_client.servers.interface_list - if1 = t_constants.MOCK_NOVA_INTERFACE - if2 = t_constants.MockNovaInterface() - if2.net_id = '3' - if2.port_id = '4' - if2.fixed_ips = [{'ip_address': '10.0.0.2'}] - if1.fixed_ips = [{'ip_address': t_constants.MOCK_IP_ADDRESS, - 'subnet_id': lb.vip.subnet_id}] - interface_list.return_value = [if1, if2] amps = self.driver.plug_vip(lb, lb.vip) self.assertEqual(5, update_port.call_count) - update_port.assert_any_call(if1.port_id, expected_aap) for amp in amps: - self.assertEqual(t_constants.MOCK_IP_ADDRESS, amp.vrrp_ip) + update_port.assert_any_call(amp.vrrp_port_id, expected_aap) + self.assertIn(amp.vrrp_ip, [t_constants.MOCK_VRRP_IP1, + t_constants.MOCK_VRRP_IP2]) + self.assertEqual(lb.vip.ip_address, amp.ha_ip) + + def _set_safely(self, obj, name, value): + if isinstance(obj, dict): + current = obj.get(name) + self.addCleanup(obj.update, {name: current}) + obj.update({name: value}) + else: + current = getattr(obj, name) + self.addCleanup(setattr, obj, name, current) + setattr(obj, name, value) + + def test_plug_vip_on_mgmt_net(self): + lb = dmh.generate_load_balancer_tree() + lb.vip.subnet_id = t_constants.MOCK_MANAGEMENT_SUBNET_ID + show_subnet = self.driver.neutron_client.show_subnet + show_subnet.return_value = { + 'subnet': { + 'id': t_constants.MOCK_MANAGEMENT_SUBNET_ID, + 'network_id': t_constants.MOCK_MANAGEMENT_NET_ID + } + } + list_ports = self.driver.neutron_client.list_ports + port1 = t_constants.MOCK_MANAGEMENT_PORT1['port'] + port2 = t_constants.MOCK_MANAGEMENT_PORT2['port'] + self._set_safely(t_constants.MOCK_MANAGEMENT_FIXED_IPS1[0], + 'ip_address', lb.amphorae[0].lb_network_ip) + self._set_safely(t_constants.MOCK_MANAGEMENT_FIXED_IPS2[0], + 'ip_address', lb.amphorae[1].lb_network_ip) + list_ports.side_effect = [{'ports': [port1]}, {'ports': [port2]}] + interface_attach = self.driver.nova_client.servers.interface_attach + self._set_safely(t_constants.MOCK_VRRP_INTERFACE1, + 'net_id', t_constants.MOCK_MANAGEMENT_NET_ID) + self._set_safely(t_constants.MOCK_VRRP_FIXED_IPS1[0], + 'subnet_id', t_constants.MOCK_MANAGEMENT_SUBNET_ID) + self._set_safely(t_constants.MOCK_VRRP_INTERFACE2, + 'net_id', t_constants.MOCK_MANAGEMENT_NET_ID) + self._set_safely(t_constants.MOCK_VRRP_FIXED_IPS2[0], + 'subnet_id', t_constants.MOCK_MANAGEMENT_SUBNET_ID) + interface_attach.side_effect = [t_constants.MOCK_VRRP_INTERFACE1, + t_constants.MOCK_VRRP_INTERFACE2] + list_security_groups = self.driver.neutron_client.list_security_groups + list_security_groups.return_value = { + 'security_groups': [ + {'id': 'lb-sec-grp1'} + ] + } + update_port = self.driver.neutron_client.update_port + expected_aap = {'port': {'allowed_address_pairs': + [{'ip_address': lb.vip.ip_address}]}} + amps = self.driver.plug_vip(lb, lb.vip) + self.assertEqual(5, update_port.call_count) + for amp in amps: + update_port.assert_any_call(amp.vrrp_port_id, expected_aap) + self.assertIn(amp.vrrp_ip, [t_constants.MOCK_VRRP_IP1, + t_constants.MOCK_VRRP_IP2]) self.assertEqual(lb.vip.ip_address, amp.ha_ip) - self.assertIn(amp.id, [lb.amphorae[0].id, lb.amphorae[1].id]) def test_allocate_vip_when_port_already_provided(self): show_port = self.driver.neutron_client.show_port @@ -306,7 +359,7 @@ class TestAllowedAddressPairsDriver(base.TestCase): self.assertRaises(network_base.AllocateVIPException, self.driver.allocate_vip, fake_lb) - def test_allocate_vip_when_only_subnet_provided(self): + def test_allocate_vip_when_no_port_provided(self): port_create_dict = copy.copy(t_constants.MOCK_NEUTRON_PORT) port_create_dict['port']['device_owner'] = ( allowed_address_pairs.OCTAVIA_OWNER) @@ -433,7 +486,8 @@ class TestAllowedAddressPairsDriver(base.TestCase): actual_ips = [fixed_ip.ip_address for fixed_ip in oct_interface.fixed_ips] self.assertEqual(exp_ips, actual_ips) - self.assertEqual(t_constants.MOCK_COMPUTE_ID, oct_interface.compute_id) + self.assertEqual(t_constants.MOCK_COMPUTE_ID, + oct_interface.compute_id) self.assertEqual(net_id, oct_interface.network_id) def test_unplug_network_when_compute_port_cant_be_found(self): diff --git a/releasenotes/notes/allow-vip-on-mgmt-net-d6c65d4ccb2a8f2c.yaml b/releasenotes/notes/allow-vip-on-mgmt-net-d6c65d4ccb2a8f2c.yaml new file mode 100644 index 0000000000..86f35f6304 --- /dev/null +++ b/releasenotes/notes/allow-vip-on-mgmt-net-d6c65d4ccb2a8f2c.yaml @@ -0,0 +1,4 @@ +--- +fixes: + - Allow the loadbalancer's VIP to be created on the same + network as the management interface.