Merge "New basic API tests for the default SG rules templates CRUDs"
diff --git a/neutron_tempest_plugin/api/admin/test_default_security_group_rules.py b/neutron_tempest_plugin/api/admin/test_default_security_group_rules.py
new file mode 100644
index 0000000..826e2ac
--- /dev/null
+++ b/neutron_tempest_plugin/api/admin/test_default_security_group_rules.py
@@ -0,0 +1,260 @@
+# 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.
+
+import copy
+import random
+
+from neutron_lib import constants
+from tempest.lib import decorators
+from tempest.lib import exceptions as lib_exc
+
+from neutron_tempest_plugin.api import base
+
+RULE_KEYWORDS_TO_CHECK = [
+ 'direction', 'remote_group_id', 'remote_address_group_id', 'description',
+ 'protocol', 'port_range_min', 'port_range_max', 'ethertype',
+ 'remote_ip_prefix', 'used_in_default_sg', 'used_in_non_default_sg'
+]
+
+
+class DefaultSecurityGroupRuleTest(base.BaseNetworkTest):
+ required_extensions = ['security-groups-default-rules']
+
+ credentials = ['primary', 'admin']
+
+ @classmethod
+ def setup_clients(cls):
+ super(DefaultSecurityGroupRuleTest, cls).setup_clients()
+ cls.admin_client = cls.os_admin.network_client
+
+ def _filter_not_relevant_rule_keys(self, rule):
+ new_rule = {}
+ rule_keys = list(rule.keys())
+ for k in rule_keys:
+ if k in RULE_KEYWORDS_TO_CHECK:
+ new_rule[k] = rule[k]
+ return new_rule
+
+ def _filter_not_relevant_rules_keys(self, rules):
+ return [self._filter_not_relevant_rule_keys(r) for r in rules]
+
+ def _assert_rules_exists(self, expected_rules, actual_rules):
+ actual_rules = self._filter_not_relevant_rules_keys(actual_rules)
+ for expected_rule in expected_rules:
+ self.assertIn(expected_rule, actual_rules)
+
+ @decorators.idempotent_id('2f3d3070-e9fa-4127-a33f-f1532fd89108')
+ def test_legacy_default_sg_rules_created_by_default(self):
+ expected_legacy_template_rules = [
+ {
+ 'direction': 'egress',
+ 'ethertype': 'IPv4',
+ 'remote_group_id': None,
+ 'protocol': None,
+ 'remote_ip_prefix': None,
+ 'remote_address_group_id': None,
+ 'port_range_max': None,
+ 'port_range_min': None,
+ 'used_in_default_sg': True,
+ 'used_in_non_default_sg': True,
+ 'description': 'Legacy default SG rule for egress traffic'
+ }, {
+ 'remote_group_id': 'PARENT',
+ 'direction': 'ingress',
+ 'ethertype': 'IPv6',
+ 'protocol': None,
+ 'remote_ip_prefix': None,
+ 'remote_address_group_id': None,
+ 'port_range_max': None,
+ 'port_range_min': None,
+ 'used_in_default_sg': True,
+ 'used_in_non_default_sg': False,
+ 'description': 'Legacy default SG rule for ingress traffic'
+ }, {
+ 'remote_group_id': 'PARENT',
+ 'direction': 'ingress',
+ 'ethertype': 'IPv4',
+ 'protocol': None,
+ 'remote_ip_prefix': None,
+ 'remote_address_group_id': None,
+ 'port_range_max': None,
+ 'port_range_min': None,
+ 'used_in_default_sg': True,
+ 'used_in_non_default_sg': False,
+ 'description': 'Legacy default SG rule for ingress traffic'
+ }, {
+ 'direction': 'egress',
+ 'ethertype': 'IPv6',
+ 'remote_group_id': None,
+ 'protocol': None,
+ 'remote_ip_prefix': None,
+ 'remote_address_group_id': None,
+ 'port_range_max': None,
+ 'port_range_min': None,
+ 'used_in_default_sg': True,
+ 'used_in_non_default_sg': True,
+ 'description': 'Legacy default SG rule for egress traffic'
+ }
+ ]
+ sg_rules_template = (
+ self.admin_client.list_default_security_group_rules()[
+ 'default_security_group_rules'
+ ])
+ self._assert_rules_exists(expected_legacy_template_rules,
+ sg_rules_template)
+
+ @decorators.idempotent_id('df98f969-ff2d-4597-9765-f5d4f81f775f')
+ def test_default_security_group_rule_lifecycle(self):
+ tcp_port = random.randint(constants.PORT_RANGE_MIN,
+ constants.PORT_RANGE_MAX)
+ rule_args = {
+ 'direction': 'ingress',
+ 'ethertype': 'IPv4',
+ 'protocol': 'tcp',
+ 'port_range_max': tcp_port,
+ 'port_range_min': tcp_port,
+ 'used_in_default_sg': False,
+ 'used_in_non_default_sg': True,
+ 'description': (
+ 'Allow tcp connections over IPv4 on port %s' % tcp_port)
+ }
+ expected_rule = {
+ 'remote_group_id': None,
+ 'direction': 'ingress',
+ 'ethertype': 'IPv4',
+ 'protocol': 'tcp',
+ 'port_range_min': tcp_port,
+ 'port_range_max': tcp_port,
+ 'remote_ip_prefix': None,
+ 'remote_address_group_id': None,
+ 'used_in_default_sg': False,
+ 'used_in_non_default_sg': True,
+ 'description': (
+ 'Allow tcp connections over IPv4 on port %s' % tcp_port)
+ }
+ created_rule_template = self.create_default_security_group_rule(
+ **rule_args)
+ self.assertDictEqual(
+ expected_rule,
+ self._filter_not_relevant_rule_keys(created_rule_template)
+ )
+ observed_rule_template = (
+ self.admin_client.get_default_security_group_rule(
+ created_rule_template['id'])
+ )['default_security_group_rule']
+ self.assertDictEqual(
+ expected_rule,
+ self._filter_not_relevant_rule_keys(observed_rule_template)
+ )
+
+ self.admin_client.delete_default_security_group_rule(
+ created_rule_template['id']
+ )
+ self.assertRaises(
+ lib_exc.NotFound,
+ self.admin_client.get_default_security_group_rule,
+ created_rule_template['id']
+ )
+
+ @decorators.idempotent_id('6c5a2f41-5899-47f4-9daf-4f8ddbbd3ad5')
+ def test_create_duplicate_default_security_group_rule_different_templates(
+ self):
+ tcp_port = random.randint(constants.PORT_RANGE_MIN,
+ constants.PORT_RANGE_MAX)
+ rule_args = {
+ 'direction': 'ingress',
+ 'ethertype': 'IPv4',
+ 'protocol': 'tcp',
+ 'port_range_max': tcp_port,
+ 'port_range_min': tcp_port,
+ 'used_in_default_sg': True,
+ 'used_in_non_default_sg': True}
+ self.create_default_security_group_rule(**rule_args)
+
+ # Now, even if 'used_in_non_default_sg' will be different error should
+ # be returned as 'used_in_default_sg' is the same
+ new_rule_args = copy.copy(rule_args)
+ new_rule_args['used_in_non_default_sg'] = False
+ self.assertRaises(
+ lib_exc.Conflict,
+ self.admin_client.create_default_security_group_rule,
+ **new_rule_args)
+
+ # Same in the opposite way: even if 'used_in_default_sg' will be
+ # different error should be returned as 'used_in_non_default_sg'
+ # is the same
+ new_rule_args = copy.copy(rule_args)
+ new_rule_args['used_in_default_sg'] = False
+ self.assertRaises(
+ lib_exc.Conflict,
+ self.admin_client.create_default_security_group_rule,
+ **new_rule_args)
+
+ @decorators.idempotent_id('e4696607-1a13-48eb-8912-ee1e742d9471')
+ def test_create_same_default_security_group_rule_for_different_templates(
+ self):
+ tcp_port = random.randint(constants.PORT_RANGE_MIN,
+ constants.PORT_RANGE_MAX)
+ expected_rules = [{
+ 'remote_group_id': None,
+ 'direction': 'ingress',
+ 'ethertype': 'IPv4',
+ 'protocol': 'tcp',
+ 'remote_ip_prefix': None,
+ 'remote_address_group_id': None,
+ 'port_range_max': tcp_port,
+ 'port_range_min': tcp_port,
+ 'used_in_default_sg': True,
+ 'used_in_non_default_sg': False,
+ 'description': ''
+ }, {
+ 'remote_group_id': None,
+ 'direction': 'ingress',
+ 'ethertype': 'IPv4',
+ 'protocol': 'tcp',
+ 'remote_ip_prefix': None,
+ 'remote_address_group_id': None,
+ 'port_range_max': tcp_port,
+ 'port_range_min': tcp_port,
+ 'used_in_default_sg': False,
+ 'used_in_non_default_sg': True,
+ 'description': ''
+ }]
+
+ default_sg_rule_args = {
+ 'direction': 'ingress',
+ 'ethertype': 'IPv4',
+ 'protocol': 'tcp',
+ 'port_range_max': tcp_port,
+ 'port_range_min': tcp_port,
+ 'used_in_default_sg': True,
+ 'used_in_non_default_sg': False}
+ self.create_default_security_group_rule(**default_sg_rule_args)
+
+ custom_sg_rule_args = {
+ 'direction': 'ingress',
+ 'ethertype': 'IPv4',
+ 'protocol': 'tcp',
+ 'port_range_max': tcp_port,
+ 'port_range_min': tcp_port,
+ 'used_in_default_sg': False,
+ 'used_in_non_default_sg': True}
+ self.create_default_security_group_rule(**custom_sg_rule_args)
+
+ sg_rules_template = (
+ self.admin_client.list_default_security_group_rules()[
+ 'default_security_group_rules'
+ ])
+ self._assert_rules_exists(expected_rules,
+ sg_rules_template)
diff --git a/neutron_tempest_plugin/api/base.py b/neutron_tempest_plugin/api/base.py
index b66fe0d..e3c9aad 100644
--- a/neutron_tempest_plugin/api/base.py
+++ b/neutron_tempest_plugin/api/base.py
@@ -135,6 +135,7 @@
cls.admin_subnetpools = []
cls.security_groups = []
cls.admin_security_groups = []
+ cls.sg_rule_templates = []
cls.projects = []
cls.log_objects = []
cls.reserved_subnet_cidrs = set()
@@ -243,6 +244,12 @@
security_group,
client=cls.admin_client)
+ # Clean up security group rule templates
+ for sg_rule_template in cls.sg_rule_templates:
+ cls._try_delete_resource(
+ cls.admin_client.delete_default_security_group_rule,
+ sg_rule_template['id'])
+
for subnetpool in cls.subnetpools:
cls._try_delete_resource(cls.client.delete_subnetpool,
subnetpool['id'])
@@ -971,6 +978,15 @@
client.delete_security_group(security_group['id'])
@classmethod
+ def get_security_group(cls, name='default', client=None):
+ client = client or cls.client
+ security_groups = client.list_security_groups()['security_groups']
+ for security_group in security_groups:
+ if security_group['name'] == name:
+ return security_group
+ raise ValueError("No such security group named {!r}".format(name))
+
+ @classmethod
def create_security_group_rule(cls, security_group=None, project=None,
client=None, ip_version=None, **kwargs):
if project:
@@ -1006,13 +1022,11 @@
'security_group_rule']
@classmethod
- def get_security_group(cls, name='default', client=None):
- client = client or cls.client
- security_groups = client.list_security_groups()['security_groups']
- for security_group in security_groups:
- if security_group['name'] == name:
- return security_group
- raise ValueError("No such security group named {!r}".format(name))
+ def create_default_security_group_rule(cls, **kwargs):
+ body = cls.admin_client.create_default_security_group_rule(**kwargs)
+ default_sg_rule = body['default_security_group_rule']
+ cls.sg_rule_templates.append(default_sg_rule)
+ return default_sg_rule
@classmethod
def create_keypair(cls, client=None, name=None, **kwargs):
diff --git a/neutron_tempest_plugin/services/network/json/network_client.py b/neutron_tempest_plugin/services/network/json/network_client.py
index 0666297..d5a827e 100644
--- a/neutron_tempest_plugin/services/network/json/network_client.py
+++ b/neutron_tempest_plugin/services/network/json/network_client.py
@@ -846,6 +846,38 @@
self.expected_success(204, resp.status)
return service_client.ResponseBody(resp, body)
+ def list_default_security_group_rules(self, **kwargs):
+ uri = '%s/default-security-group-rules' % self.uri_prefix
+ if kwargs:
+ uri += '?' + urlparse.urlencode(kwargs, doseq=1)
+ resp, body = self.get(uri)
+ self.expected_success(200, resp.status)
+ body = jsonutils.loads(body)
+ return service_client.ResponseBody(resp, body)
+
+ def get_default_security_group_rule(self, rule_id):
+ uri = '%s/default-security-group-rules/%s' % (self.uri_prefix,
+ rule_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 create_default_security_group_rule(self, **kwargs):
+ post_body = {'default_security_group_rule': kwargs}
+ body = jsonutils.dumps(post_body)
+ uri = '%s/default-security-group-rules' % self.uri_prefix
+ resp, body = self.post(uri, body)
+ self.expected_success(201, resp.status)
+ body = jsonutils.loads(body)
+ return service_client.ResponseBody(resp, body)
+
+ def delete_default_security_group_rule(self, rule_id):
+ uri = '%s/default-security-group-rules/%s' % (self.uri_prefix, rule_id)
+ resp, body = self.delete(uri)
+ self.expected_success(204, resp.status)
+ return service_client.ResponseBody(resp, body)
+
def list_ports(self, **kwargs):
uri = '%s/ports' % self.uri_prefix
if kwargs:
diff --git a/zuul.d/master_jobs.yaml b/zuul.d/master_jobs.yaml
index 1c47f85..938e431 100644
--- a/zuul.d/master_jobs.yaml
+++ b/zuul.d/master_jobs.yaml
@@ -114,6 +114,7 @@
- router
- router_availability_zone
- security-group
+ - security-groups-default-rules
- security-groups-remote-address-group
- segment
- service-type