diff --git a/.zuul.yaml b/.zuul.yaml index 060443d00..44de1bfb1 100644 --- a/.zuul.yaml +++ b/.zuul.yaml @@ -31,6 +31,7 @@ - external-net - extra_dhcp_opt - extraroute + - extraroute-atomic - filter-validation - fip-port-details - flavors diff --git a/neutron_tempest_plugin/api/base.py b/neutron_tempest_plugin/api/base.py index 79ac4a686..71a0e5eee 100644 --- a/neutron_tempest_plugin/api/base.py +++ b/neutron_tempest_plugin/api/base.py @@ -723,6 +723,14 @@ class BaseNetworkTest(test.BaseTestCase): router_id, subnet_id) return interface + @classmethod + def add_extra_routes_atomic(cls, *args, **kwargs): + return cls.client.add_extra_routes_atomic(*args, **kwargs) + + @classmethod + def remove_extra_routes_atomic(cls, *args, **kwargs): + return cls.client.remove_extra_routes_atomic(*args, **kwargs) + @classmethod def get_supported_qos_rule_types(cls): body = cls.client.list_qos_rule_types() diff --git a/neutron_tempest_plugin/api/test_routers.py b/neutron_tempest_plugin/api/test_routers.py index 3b9867b0d..d866dbccc 100644 --- a/neutron_tempest_plugin/api/test_routers.py +++ b/neutron_tempest_plugin/api/test_routers.py @@ -200,6 +200,71 @@ class RoutersTest(base_routers.BaseRouterTest): def _delete_extra_routes(self, router_id): self.client.delete_extra_routes(router_id) + @decorators.idempotent_id('b29d1698-d603-11e9-9c66-079cc4aec539') + @tutils.requires_ext(extension='extraroute-atomic', service='network') + def test_extra_routes_atomic(self): + self.network = self.create_network() + self.subnet = self.create_subnet(self.network) + self.router = self._create_router( + data_utils.rand_name('router-'), True) + self.create_router_interface(self.router['id'], self.subnet['id']) + self.addCleanup( + self._delete_extra_routes, + self.router['id']) + + if self._ip_version == 6: + dst = '2001:db8:%s::/64' + else: + dst = '10.0.%s.0/24' + + cidr = netaddr.IPNetwork(self.subnet['cidr']) + + routes = [ + {'destination': dst % 2, 'nexthop': cidr[2]}, + ] + resp = self.client.add_extra_routes_atomic( + self.router['id'], routes) + self.assertEqual(1, len(resp['router']['routes'])) + + routes = [ + {'destination': dst % 2, 'nexthop': cidr[2]}, + {'destination': dst % 3, 'nexthop': cidr[3]}, + ] + resp = self.client.add_extra_routes_atomic( + self.router['id'], routes) + self.assertEqual(2, len(resp['router']['routes'])) + + routes = [ + {'destination': dst % 3, 'nexthop': cidr[3]}, + {'destination': dst % 4, 'nexthop': cidr[4]}, + ] + resp = self.client.remove_extra_routes_atomic( + self.router['id'], routes) + self.assertEqual(1, len(resp['router']['routes'])) + + routes = [ + {'destination': dst % 2, 'nexthop': cidr[5]}, + ] + resp = self.client.add_extra_routes_atomic( + self.router['id'], routes) + self.assertEqual(2, len(resp['router']['routes'])) + + routes = [ + {'destination': dst % 2, 'nexthop': cidr[5]}, + ] + resp = self.client.remove_extra_routes_atomic( + self.router['id'], routes) + self.assertEqual(1, len(resp['router']['routes'])) + + routes = [ + {'destination': dst % 2, 'nexthop': cidr[2]}, + {'destination': dst % 3, 'nexthop': cidr[3]}, + {'destination': dst % 2, 'nexthop': cidr[5]}, + ] + resp = self.client.remove_extra_routes_atomic( + self.router['id'], routes) + self.assertEqual(0, len(resp['router']['routes'])) + @decorators.idempotent_id('01f185d1-d1a6-4cf9-abf7-e0e1384c169c') def test_network_attached_with_two_routers(self): network = self.create_network(data_utils.rand_name('network1')) diff --git a/neutron_tempest_plugin/services/network/json/network_client.py b/neutron_tempest_plugin/services/network/json/network_client.py index 11ba8ef5e..dabf3afe9 100644 --- a/neutron_tempest_plugin/services/network/json/network_client.py +++ b/neutron_tempest_plugin/services/network/json/network_client.py @@ -564,6 +564,22 @@ class NetworkClientJSON(service_client.RestClient): body = jsonutils.loads(body) return service_client.ResponseBody(resp, body) + def add_extra_routes_atomic(self, router_id, routes): + uri = '%s/routers/%s/add_extraroutes' % (self.uri_prefix, router_id) + request_body = {'router': {'routes': routes}} + resp, response_body = self.put(uri, jsonutils.dumps(request_body)) + self.expected_success(200, resp.status) + return service_client.ResponseBody( + resp, jsonutils.loads(response_body)) + + def remove_extra_routes_atomic(self, router_id, routes): + uri = '%s/routers/%s/remove_extraroutes' % (self.uri_prefix, router_id) + request_body = {'router': {'routes': routes}} + resp, response_body = self.put(uri, jsonutils.dumps(request_body)) + self.expected_success(200, resp.status) + return service_client.ResponseBody( + resp, jsonutils.loads(response_body)) + def add_dhcp_agent_to_network(self, agent_id, network_id): post_body = {'network_id': network_id} body = jsonutils.dumps(post_body)