From a139d2f97c54e88c508d9ffa85a5c6268538b84e Mon Sep 17 00:00:00 2001 From: Gregory Thiemonge Date: Mon, 22 Mar 2021 09:15:20 +0100 Subject: [PATCH] Fix using subnets with host_routes in amphorav2 driver When using subnets with host_routes in amphorav2, the host_routes attribute in the Subnet data structure was not correctly converted to a HostRoute data structure. It triggered exceptions and failed to provision the load balancer. Story 2008738 Task 42092 Change-Id: I39391070cea170a6039f901093f09fc89ba06123 (cherry picked from commit d0ec3aaf23eba0ffc8fe7bc86b51d4857e0cc507) --- .../worker/v2/tasks/amphora_driver_tasks.py | 20 ++++- .../v2/tasks/test_amphora_driver_tasks.py | 85 ++++++++++++++++++- ...ost_routes-amphorav2-3c079c5a3bfa1b3d.yaml | 5 ++ 3 files changed, 105 insertions(+), 5 deletions(-) create mode 100644 releasenotes/notes/fix-subnet-host_routes-amphorav2-3c079c5a3bfa1b3d.yaml diff --git a/octavia/controller/worker/v2/tasks/amphora_driver_tasks.py b/octavia/controller/worker/v2/tasks/amphora_driver_tasks.py index cdebfbc7ec..ec70c442b6 100644 --- a/octavia/controller/worker/v2/tasks/amphora_driver_tasks.py +++ b/octavia/controller/worker/v2/tasks/amphora_driver_tasks.py @@ -266,9 +266,17 @@ class AmphoraPostNetworkPlug(BaseAmphoraTask): for port in ports: net = data_models.Network(**port.pop(constants.NETWORK)) ips = port.pop(constants.FIXED_IPS) - fixed_ips = [data_models.FixedIP( - subnet=data_models.Subnet(**ip.pop(constants.SUBNET)), **ip) - for ip in ips] + fixed_ips = [] + for ip in ips: + subnet_arg = ip.pop(constants.SUBNET) + host_routes = subnet_arg.get('host_routes') + if host_routes: + subnet_arg['host_routes'] = [ + data_models.HostRoute(**hr) + for hr in host_routes + ] + fixed_ips.append(data_models.FixedIP( + subnet=data_models.Subnet(**subnet_arg), **ip)) self.amphora_driver.post_network_plug( db_amp, data_models.Port(network=net, fixed_ips=fixed_ips, **port)) @@ -329,6 +337,12 @@ class AmphoraPostVIPPlug(BaseAmphoraTask): vip_arg = amphorae_network_config[amphora.get( constants.ID)][constants.VIP_SUBNET] if vip_arg: + host_routes = vip_arg.get('host_routes') + if host_routes: + vip_arg['host_routes'] = [ + data_models.HostRoute(**hr) + for hr in host_routes + ] vip_subnet = data_models.Subnet(**vip_arg) else: vip_subnet = data_models.Subnet() diff --git a/octavia/tests/unit/controller/worker/v2/tasks/test_amphora_driver_tasks.py b/octavia/tests/unit/controller/worker/v2/tasks/test_amphora_driver_tasks.py index e1b6c1ac16..50ce87e92e 100644 --- a/octavia/tests/unit/controller/worker/v2/tasks/test_amphora_driver_tasks.py +++ b/octavia/tests/unit/controller/worker/v2/tasks/test_amphora_driver_tasks.py @@ -380,8 +380,9 @@ class TestAmphoraDriverTasks(base.TestCase): amphora_post_network_plug_obj = (amphora_driver_tasks. AmphoraPostNetworkPlug()) mock_amphora_repo_get.return_value = _db_amphora_mock + fixed_ips = [{constants.SUBNET: {}}] port_mock = {constants.NETWORK: mock.MagicMock(), - constants.FIXED_IPS: [mock.MagicMock()], + constants.FIXED_IPS: fixed_ips, constants.ID: uuidutils.generate_uuid()} amphora_post_network_plug_obj.execute(_amphora_mock, [port_mock]) @@ -415,6 +416,37 @@ class TestAmphoraDriverTasks(base.TestCase): failure.Failure.from_exception(Exception('boom')), _amphora_mock) repo.AmphoraRepository.update.assert_not_called() + def test_amphora_post_network_plug_with_host_routes( + self, mock_driver, mock_generate_uuid, mock_log, mock_get_session, + mock_listener_repo_get, mock_listener_repo_update, + mock_amphora_repo_get, mock_amphora_repo_update): + + amphora_post_network_plug_obj = (amphora_driver_tasks. + AmphoraPostNetworkPlug()) + mock_amphora_repo_get.return_value = _db_amphora_mock + host_routes = [{'destination': '10.0.0.0/16', + 'nexthop': '192.168.10.3'}, + {'destination': '10.2.0.0/16', + 'nexthop': '192.168.10.5'}] + fixed_ips = [{constants.SUBNET: {'host_routes': host_routes}}] + + port_mock = {constants.NETWORK: mock.MagicMock(), + constants.FIXED_IPS: fixed_ips, + constants.ID: uuidutils.generate_uuid()} + amphora_post_network_plug_obj.execute(_amphora_mock, [port_mock]) + + (mock_driver.post_network_plug. + assert_called_once_with)(_db_amphora_mock, + network_data_models.Port(**port_mock)) + + call_args = mock_driver.post_network_plug.call_args[0] + port_arg = call_args[1] + subnet_arg = port_arg.fixed_ips[0].subnet + self.assertEqual(2, len(subnet_arg.host_routes)) + for hr1, hr2 in zip(host_routes, subnet_arg.host_routes): + self.assertEqual(hr1['destination'], hr2.destination) + self.assertEqual(hr1['nexthop'], hr2.nexthop) + @mock.patch('octavia.db.repositories.LoadBalancerRepository.get') def test_amphorae_post_network_plug(self, mock_lb_get, mock_driver, @@ -495,7 +527,14 @@ class TestAmphoraDriverTasks(base.TestCase): mock_amphora_repo_get, mock_amphora_repo_update): - amphorae_net_config_mock = mock.MagicMock() + amphorae_net_config_mock = { + AMP_ID: { + constants.VIP_SUBNET: { + 'host_routes': [] + }, + constants.VRRP_PORT: mock.MagicMock(), + } + } mock_amphora_repo_get.return_value = _db_amphora_mock mock_lb_get.return_value = _db_load_balancer_mock amphora_post_vip_plug_obj = amphora_driver_tasks.AmphoraPostVIPPlug() @@ -548,6 +587,48 @@ class TestAmphoraDriverTasks(base.TestCase): None) repo.AmphoraRepository.update.assert_not_called() + @mock.patch('octavia.db.repositories.LoadBalancerRepository.update') + @mock.patch('octavia.db.repositories.LoadBalancerRepository.get') + def test_amphora_post_vip_plug_with_host_routes( + self, mock_lb_get, mock_loadbalancer_repo_update, mock_driver, + mock_generate_uuid, mock_log, mock_get_session, + mock_listener_repo_get, mock_listener_repo_update, + mock_amphora_repo_get, mock_amphora_repo_update): + + host_routes = [{'destination': '10.0.0.0/16', + 'nexthop': '192.168.10.3'}, + {'destination': '10.2.0.0/16', + 'nexthop': '192.168.10.5'}] + amphorae_net_config_mock = { + AMP_ID: { + constants.VIP_SUBNET: { + 'host_routes': host_routes + }, + constants.VRRP_PORT: mock.MagicMock(), + } + } + mock_amphora_repo_get.return_value = _db_amphora_mock + mock_lb_get.return_value = _db_load_balancer_mock + amphora_post_vip_plug_obj = amphora_driver_tasks.AmphoraPostVIPPlug() + amphora_post_vip_plug_obj.execute(_amphora_mock, + _LB_mock, + amphorae_net_config_mock) + vip_subnet = network_data_models.Subnet( + **amphorae_net_config_mock[AMP_ID]['vip_subnet']) + vrrp_port = network_data_models.Port( + **amphorae_net_config_mock[AMP_ID]['vrrp_port']) + + mock_driver.post_vip_plug.assert_called_once_with( + _db_amphora_mock, _db_load_balancer_mock, amphorae_net_config_mock, + vip_subnet=vip_subnet, vrrp_port=vrrp_port) + + call_kwargs = mock_driver.post_vip_plug.call_args[1] + vip_subnet_arg = call_kwargs.get(constants.VIP_SUBNET) + self.assertEqual(2, len(vip_subnet_arg.host_routes)) + for hr1, hr2 in zip(host_routes, vip_subnet_arg.host_routes): + self.assertEqual(hr1['destination'], hr2.destination) + self.assertEqual(hr1['nexthop'], hr2.nexthop) + @mock.patch('octavia.db.repositories.LoadBalancerRepository.update') @mock.patch('octavia.db.repositories.LoadBalancerRepository.get') def test_amphorae_post_vip_plug(self, mock_lb_get, diff --git a/releasenotes/notes/fix-subnet-host_routes-amphorav2-3c079c5a3bfa1b3d.yaml b/releasenotes/notes/fix-subnet-host_routes-amphorav2-3c079c5a3bfa1b3d.yaml new file mode 100644 index 0000000000..2e9b5b1eb7 --- /dev/null +++ b/releasenotes/notes/fix-subnet-host_routes-amphorav2-3c079c5a3bfa1b3d.yaml @@ -0,0 +1,5 @@ +--- +fixes: + - | + Fix load balancers that use customized host_routes in the VIP or the member + subnets in amphorav2.