Merge "Move neutron-dynamic-routing BGP tests from stadium"
diff --git a/.zuul.yaml b/.zuul.yaml
index 70809b3..e8f2584 100644
--- a/.zuul.yaml
+++ b/.zuul.yaml
@@ -27,16 +27,19 @@
- dns-integration
- empty-string-filtering
- expose-port-forwarding-in-fip
+ - expose-l3-conntrack-helper
- ext-gw-mode
- external-net
- extra_dhcp_opt
- extraroute
+ - extraroute-atomic
- filter-validation
- fip-port-details
- flavors
- floating-ip-port-forwarding
- floatingip-pools
- ip-substring-filtering
+ - l3-conntrack-helper
- l3-flavors
- l3-ha
- l3_agent_scheduler
@@ -99,6 +102,7 @@
neutron-uplink-status-propagation: true
neutron-network-segment-range: true
neutron-port-forwarding: true
+ neutron-conntrack-helper: true
devstack_local_conf:
post-config:
$NEUTRON_CONF:
@@ -845,8 +849,6 @@
- sfc
devstack_localrc:
NETWORK_API_EXTENSIONS: "{{ (network_api_extensions_common + network_api_extensions_sfc) | join(',') }}"
- files:
- - ^neutron_tempest_plugin/sfc/.*$
- job:
name: neutron-tempest-plugin-bgpvpn-bagpipe
@@ -891,8 +893,6 @@
- fwaas_v2
devstack_localrc:
NETWORK_API_EXTENSIONS: "{{ (network_api_extensions_common + network_api_extensions_fwaas) | join(',') }}"
- files:
- - ^neutron_tempest_plugin/fwaas/.*$
- job:
name: neutron-tempest-plugin-dynamic-routing
diff --git a/neutron_tempest_plugin/api/base.py b/neutron_tempest_plugin/api/base.py
index 79ac4a6..4441dd1 100644
--- a/neutron_tempest_plugin/api/base.py
+++ b/neutron_tempest_plugin/api/base.py
@@ -137,6 +137,7 @@
cls.keypairs = []
cls.trunks = []
cls.network_segment_ranges = []
+ cls.conntrack_helpers = []
@classmethod
def resource_cleanup(cls):
@@ -153,6 +154,10 @@
for floating_ip in cls.floating_ips:
cls._try_delete_resource(cls.delete_floatingip, floating_ip)
+ # Clean up conntrack helpers
+ for cth in cls.conntrack_helpers:
+ cls._try_delete_resource(cls.delete_conntrack_helper, cth)
+
# Clean up routers
for router in cls.routers:
cls._try_delete_resource(cls.delete_router,
@@ -724,6 +729,14 @@
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()
return [rule_type['type'] for rule_type in body['rule_types']]
@@ -952,6 +965,55 @@
client.delete_trunk(trunk['id'])
+ @classmethod
+ def create_conntrack_helper(cls, router_id, helper, protocol, port,
+ client=None):
+ """Create a conntrack helper
+
+ Create a conntrack helper and schedule it for later deletion. If a
+ client is passed, then it is used for deleteing the CTH too.
+
+ :param router_id: The ID of the Neutron router associated to the
+ conntrack helper.
+
+ :param helper: The conntrack helper module alias
+
+ :param protocol: The conntrack helper IP protocol used in the conntrack
+ helper.
+
+ :param port: The conntrack helper IP protocol port number for the
+ conntrack helper.
+
+ :param client: network client to be used for creating and cleaning up
+ the conntrack helper.
+ """
+
+ client = client or cls.client
+
+ cth = client.create_conntrack_helper(router_id, helper, protocol,
+ port)['conntrack_helper']
+
+ # save ID of router associated with conntrack helper for final cleanup
+ cth['router_id'] = router_id
+
+ # save client to be used later in cls.delete_conntrack_helper for final
+ # cleanup
+ cth['client'] = client
+ cls.conntrack_helpers.append(cth)
+ return cth
+
+ @classmethod
+ def delete_conntrack_helper(cls, cth, client=None):
+ """Delete conntrack helper
+
+ :param client: Client to be used
+ If client is not given it will use the client used to create the
+ conntrack helper, or cls.client if unknown.
+ """
+
+ client = client or cth.get('client') or cls.client
+ client.delete_conntrack_helper(cth['router_id'], cth['id'])
+
class BaseAdminNetworkTest(BaseNetworkTest):
diff --git a/neutron_tempest_plugin/api/test_conntrack_helper.py b/neutron_tempest_plugin/api/test_conntrack_helper.py
new file mode 100644
index 0000000..900851a
--- /dev/null
+++ b/neutron_tempest_plugin/api/test_conntrack_helper.py
@@ -0,0 +1,135 @@
+# Copyright (c) 2019 Red Hat, Inc.
+# 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.
+
+# from tempest.common import utils
+from tempest.lib.common.utils import data_utils
+from tempest.lib import decorators
+from tempest.lib import exceptions
+
+from neutron_tempest_plugin.api import base
+from neutron_tempest_plugin import config
+
+CONF = config.CONF
+
+
+class ConntrackHelperTestJSON(base.BaseNetworkTest):
+
+ required_extensions = ['router', 'l3-conntrack-helper',
+ 'expose-l3-conntrack-helper']
+
+ @classmethod
+ def resource_setup(cls):
+ super(ConntrackHelperTestJSON, cls).resource_setup()
+ cls.ext_net_id = CONF.network.public_network_id
+
+ # Create network, subnet
+ cls.network = cls.create_network()
+ cls.subnet = cls.create_subnet(cls.network)
+
+ @decorators.idempotent_id('6361c80e-902d-4c2a-88b4-ea8066507eee')
+ def test_create_list_update_show_delete_conntrack_helper(self):
+ # Create a router
+ router = self.create_router(data_utils.rand_name('router'),
+ external_network_id=self.ext_net_id)
+
+ # Create conntrack helper
+ created_cth = self.create_conntrack_helper(router['id'], helper='tftp',
+ protocol='udp', port=69)
+ self.assertEqual('tftp', created_cth['helper'])
+ self.assertEqual('udp', created_cth['protocol'])
+ self.assertEqual(69, created_cth['port'])
+
+ # List conntrack helpers
+ conntrack_helpers = self.client.list_conntrack_helpers(
+ router['id'])['conntrack_helpers']
+ self.assertIn(created_cth['id'],
+ {cth['id'] for cth in conntrack_helpers})
+
+ # Update conntrack helper
+ updated_conntrack_helper = self.client.update_conntrack_helper(
+ router['id'], created_cth['id'], port=6969)['conntrack_helper']
+ self.assertEqual(updated_conntrack_helper['port'], 6969)
+
+ # Show conntrack helper
+ conntrack_helper = self.client.get_conntrack_helper(
+ router['id'], created_cth['id'])['conntrack_helper']
+ self.assertEqual('tftp', conntrack_helper['helper'])
+ self.assertEqual('udp', conntrack_helper['protocol'])
+ self.assertEqual(6969, conntrack_helper['port'])
+
+ # Delete conntrack helper
+ self.client.delete_conntrack_helper(router['id'], created_cth['id'])
+ self.assertRaises(
+ exceptions.NotFound,
+ self.client.get_conntrack_helper, router['id'], created_cth['id'])
+
+ @decorators.idempotent_id('0a6ae20c-3f66-423e-93c6-cfedd1c93b8d')
+ def test_conntrack_helper_info_in_router_details(self):
+ # Create a router
+ router = self.create_router(data_utils.rand_name('router'),
+ external_network_id=self.ext_net_id)
+
+ # Ensure routerd does not have information about any conntrack helper
+ router = self.client.show_router(router['id'])['router']
+ self.assertEqual(0, len(router['conntrack_helpers']))
+
+ # Now create conntrack helper and ensure it's visible in Router details
+ cth = self.create_conntrack_helper(router['id'], helper='ftp',
+ protocol='tcp', port=21)
+ router = self.client.show_router(router['id'])['router']
+ self.assertEqual(1, len(router['conntrack_helpers']))
+ self.assertEqual('ftp', router['conntrack_helpers'][0]['helper'])
+ self.assertEqual('tcp', router['conntrack_helpers'][0]['protocol'])
+ self.assertEqual(21, router['conntrack_helpers'][0]['port'])
+
+ # Delete conntrack_helper and ensure it's no longer in Router details
+ self.client.delete_conntrack_helper(router['id'], cth['id'])
+ router = self.client.show_router(router['id'])['router']
+ self.assertEqual(0, len(router['conntrack_helpers']))
+
+ @decorators.idempotent_id('134469d9-fb25-4165-adc8-f4747f07caf1')
+ def test_2_conntrack_helpers_to_same_router(self):
+ # Create a router
+ router = self.create_router(data_utils.rand_name('router'),
+ external_network_id=self.ext_net_id)
+
+ cth_data = [{'helper': 'tftp', 'protocol': 'udp', 'port': 60},
+ {'helper': 'ftp', 'protocol': 'tcp', 'port': 21}]
+ created_cths = []
+ for cth in cth_data:
+ created_cth = self.create_conntrack_helper(
+ router_id=router['id'],
+ helper=cth['helper'],
+ protocol=cth['protocol'],
+ port=cth['port'])
+ self.assertEqual(cth['helper'], created_cth['helper'])
+ self.assertEqual(cth['protocol'], created_cth['protocol'])
+ self.assertEqual(cth['port'], created_cth['port'])
+ created_cths.append(created_cth)
+
+ # Check that conntrack helpers are in Router details
+ router = self.client.show_router(router['id'])['router']
+ self.assertEqual(len(cth_data), len(router['conntrack_helpers']))
+ for cth in created_cths:
+ expected_cth = cth.copy()
+ expected_cth.pop('id')
+ expected_cth.pop('client')
+ expected_cth.pop('router_id')
+ self.assertIn(expected_cth, router['conntrack_helpers'])
+
+ # Test list of conntrack helpers
+ conntrack_helpers = self.client.list_conntrack_helpers(
+ router['id'])['conntrack_helpers']
+ self.assertEqual(len(cth_data), len(conntrack_helpers))
diff --git a/neutron_tempest_plugin/api/test_routers.py b/neutron_tempest_plugin/api/test_routers.py
index 3b9867b..d866dbc 100644
--- a/neutron_tempest_plugin/api/test_routers.py
+++ b/neutron_tempest_plugin/api/test_routers.py
@@ -200,6 +200,71 @@
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 11ba8ef..05095a7 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 @@
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)
@@ -1004,6 +1020,49 @@
self.expected_success(204, resp.status)
service_client.ResponseBody(resp, body)
+ def create_conntrack_helper(self, router_id, helper, protocol, port):
+ post_body = {'conntrack_helper': {
+ 'helper': helper,
+ 'protocol': protocol,
+ 'port': port}}
+ body = jsonutils.dumps(post_body)
+ uri = '%s/routers/%s/conntrack_helpers' % (self.uri_prefix, router_id)
+ resp, body = self.post(uri, body)
+ self.expected_success(201, resp.status)
+ body = jsonutils.loads(body)
+ return service_client.ResponseBody(resp, body)
+
+ def get_conntrack_helper(self, router_id, cth_id):
+ uri = '%s/routers/%s/conntrack_helpers/%s' % (self.uri_prefix,
+ router_id, cth_id)
+ get_resp, get_resp_body = self.get(uri)
+ self.expected_success(200, get_resp.status)
+ body = jsonutils.loads(get_resp_body)
+ return service_client.ResponseBody(get_resp, body)
+
+ def list_conntrack_helpers(self, router_id):
+ uri = '%s/routers/%s/conntrack_helpers' % (self.uri_prefix, router_id)
+ resp, body = self.get(uri)
+ self.expected_success(200, resp.status)
+ body = jsonutils.loads(body)
+ return service_client.ResponseBody(resp, body)
+
+ def update_conntrack_helper(self, router_id, cth_id, **kwargs):
+ uri = '%s/routers/%s/conntrack_helpers/%s' % (self.uri_prefix,
+ router_id, cth_id)
+ put_body = jsonutils.dumps({'conntrack_helper': kwargs})
+ put_resp, resp_body = self.put(uri, put_body)
+ self.expected_success(200, put_resp.status)
+ body = jsonutils.loads(resp_body)
+ return service_client.ResponseBody(put_resp, body)
+
+ def delete_conntrack_helper(self, router_id, cth_id):
+ uri = '%s/routers/%s/conntrack_helpers/%s' % (self.uri_prefix,
+ router_id, cth_id)
+ resp, body = self.delete(uri)
+ self.expected_success(204, resp.status)
+ service_client.ResponseBody(resp, body)
+
def create_network_keystone_v3(self, name, project_id, tenant_id=None):
uri = '%s/networks' % self.uri_prefix
post_data = {