From 59aaf1dff97aa25a71d317300b8255f4c59391a9 Mon Sep 17 00:00:00 2001 From: Morgan Fainberg Date: Wed, 6 Feb 2013 15:39:54 -0800 Subject: [PATCH] Default SG rules for the Security Group "Default" Added in the API os-security-group-default-rules This allows create, delete, list, and get (of individual rules) for rules that will be pre-populated into the Security Group "default" that is populated in all projects on creation. These rules will not be applied retroactively, as it is designed to allow the creation of a "reasonable" base-line set of sg rules. The new rules live in a separate table that mirrors the relevant structures of the security_group_rules table. Added unit tests/API samples for the new API calls Related to bp default-rules-for-default-security-group DocImpact Change-Id: I7ab51e68aff562bb869538197a0eca158fc3220c --- .../all_extensions/extensions-get-resp.json | 8 + .../all_extensions/extensions-get-resp.xml | 3 + ...curity-group-default-rules-create-req.json | 8 + ...ecurity-group-default-rules-create-req.xml | 7 + ...urity-group-default-rules-create-resp.json | 11 + ...curity-group-default-rules-create-resp.xml | 9 + ...ecurity-group-default-rules-list-resp.json | 13 + ...security-group-default-rules-list-resp.xml | 11 + ...ecurity-group-default-rules-show-resp.json | 11 + ...security-group-default-rules-show-resp.xml | 9 + etc/nova/policy.json | 1 + .../contrib/security_group_default_rules.py | 210 ++++++++ nova/compute/api.py | 40 ++ nova/db/api.py | 22 + nova/db/sqlalchemy/api.py | 60 +++ .../157_add_security_group_default_rules.py | 61 +++ nova/db/sqlalchemy/models.py | 9 + nova/exception.py | 4 + .../test_security_group_default_rules.py | 467 ++++++++++++++++++ .../api/openstack/compute/test_extensions.py | 1 + nova/tests/fake_policy.py | 1 + .../extensions-get-resp.json.tpl | 8 + .../extensions-get-resp.xml.tpl | 3 + ...ty-group-default-rules-create-req.json.tpl | 8 + ...ity-group-default-rules-create-req.xml.tpl | 7 + ...y-group-default-rules-create-resp.json.tpl | 11 + ...ty-group-default-rules-create-resp.xml.tpl | 9 + ...ity-group-default-rules-list-resp.json.tpl | 13 + ...rity-group-default-rules-list-resp.xml.tpl | 11 + ...ity-group-default-rules-show-resp.json.tpl | 11 + ...rity-group-default-rules-show-resp.xml.tpl | 9 + nova/tests/integrated/test_api_samples.py | 34 +- 32 files changed, 1089 insertions(+), 1 deletion(-) create mode 100644 doc/api_samples/os-security-group-default-rules/security-group-default-rules-create-req.json create mode 100644 doc/api_samples/os-security-group-default-rules/security-group-default-rules-create-req.xml create mode 100644 doc/api_samples/os-security-group-default-rules/security-group-default-rules-create-resp.json create mode 100644 doc/api_samples/os-security-group-default-rules/security-group-default-rules-create-resp.xml create mode 100644 doc/api_samples/os-security-group-default-rules/security-group-default-rules-list-resp.json create mode 100644 doc/api_samples/os-security-group-default-rules/security-group-default-rules-list-resp.xml create mode 100644 doc/api_samples/os-security-group-default-rules/security-group-default-rules-show-resp.json create mode 100644 doc/api_samples/os-security-group-default-rules/security-group-default-rules-show-resp.xml create mode 100644 nova/api/openstack/compute/contrib/security_group_default_rules.py create mode 100644 nova/db/sqlalchemy/migrate_repo/versions/157_add_security_group_default_rules.py create mode 100644 nova/tests/api/openstack/compute/contrib/test_security_group_default_rules.py create mode 100644 nova/tests/integrated/api_samples/os-security-group-default-rules/security-group-default-rules-create-req.json.tpl create mode 100644 nova/tests/integrated/api_samples/os-security-group-default-rules/security-group-default-rules-create-req.xml.tpl create mode 100644 nova/tests/integrated/api_samples/os-security-group-default-rules/security-group-default-rules-create-resp.json.tpl create mode 100644 nova/tests/integrated/api_samples/os-security-group-default-rules/security-group-default-rules-create-resp.xml.tpl create mode 100644 nova/tests/integrated/api_samples/os-security-group-default-rules/security-group-default-rules-list-resp.json.tpl create mode 100644 nova/tests/integrated/api_samples/os-security-group-default-rules/security-group-default-rules-list-resp.xml.tpl create mode 100644 nova/tests/integrated/api_samples/os-security-group-default-rules/security-group-default-rules-show-resp.json.tpl create mode 100644 nova/tests/integrated/api_samples/os-security-group-default-rules/security-group-default-rules-show-resp.xml.tpl diff --git a/doc/api_samples/all_extensions/extensions-get-resp.json b/doc/api_samples/all_extensions/extensions-get-resp.json index ba5e410eb880..14e9062ca37b 100644 --- a/doc/api_samples/all_extensions/extensions-get-resp.json +++ b/doc/api_samples/all_extensions/extensions-get-resp.json @@ -384,6 +384,14 @@ "namespace": "http://docs.openstack.org/compute/ext/rescue/api/v1.1", "updated": "2011-08-18T00:00:00+00:00" }, + { + "alias": "os-security-group-default-rules", + "description": "Default rules for security group support.", + "links": [], + "name": "SecurityGroupDefaultRules", + "namespace": "http://docs.openstack.org/compute/ext/securitygroupdefaultrules/api/v1.1", + "updated": "2013-02-05T00:00:00+00:00" + }, { "alias": "os-security-groups", "description": "Security group support.", diff --git a/doc/api_samples/all_extensions/extensions-get-resp.xml b/doc/api_samples/all_extensions/extensions-get-resp.xml index a18e52437045..133b0570ad0a 100644 --- a/doc/api_samples/all_extensions/extensions-get-resp.xml +++ b/doc/api_samples/all_extensions/extensions-get-resp.xml @@ -162,6 +162,9 @@ Instance rescue mode. + + Default rules for security group support. + Security group support. diff --git a/doc/api_samples/os-security-group-default-rules/security-group-default-rules-create-req.json b/doc/api_samples/os-security-group-default-rules/security-group-default-rules-create-req.json new file mode 100644 index 000000000000..8b0a901c7c3a --- /dev/null +++ b/doc/api_samples/os-security-group-default-rules/security-group-default-rules-create-req.json @@ -0,0 +1,8 @@ +{ + "security_group_default_rule": { + "ip_protocol": "TCP", + "from_port": "80", + "to_port": "80", + "cidr": "10.10.12.0/24" + } +} \ No newline at end of file diff --git a/doc/api_samples/os-security-group-default-rules/security-group-default-rules-create-req.xml b/doc/api_samples/os-security-group-default-rules/security-group-default-rules-create-req.xml new file mode 100644 index 000000000000..7fa3af7d9dc7 --- /dev/null +++ b/doc/api_samples/os-security-group-default-rules/security-group-default-rules-create-req.xml @@ -0,0 +1,7 @@ + + + TCP + 80 + 80 + 10.10.12.0/24 + \ No newline at end of file diff --git a/doc/api_samples/os-security-group-default-rules/security-group-default-rules-create-resp.json b/doc/api_samples/os-security-group-default-rules/security-group-default-rules-create-resp.json new file mode 100644 index 000000000000..ae6c62bfd670 --- /dev/null +++ b/doc/api_samples/os-security-group-default-rules/security-group-default-rules-create-resp.json @@ -0,0 +1,11 @@ +{ + "security_group_default_rule": { + "from_port": 80, + "id": 1, + "ip_protocol": "TCP", + "ip_range":{ + "cidr": "10.10.10.0/24" + }, + "to_port": 80 + } +} \ No newline at end of file diff --git a/doc/api_samples/os-security-group-default-rules/security-group-default-rules-create-resp.xml b/doc/api_samples/os-security-group-default-rules/security-group-default-rules-create-resp.xml new file mode 100644 index 000000000000..9e700969ff1f --- /dev/null +++ b/doc/api_samples/os-security-group-default-rules/security-group-default-rules-create-resp.xml @@ -0,0 +1,9 @@ + + + TCP + 80 + 80 + + 10.10.10.0/24 + + \ No newline at end of file diff --git a/doc/api_samples/os-security-group-default-rules/security-group-default-rules-list-resp.json b/doc/api_samples/os-security-group-default-rules/security-group-default-rules-list-resp.json new file mode 100644 index 000000000000..c083640c3e70 --- /dev/null +++ b/doc/api_samples/os-security-group-default-rules/security-group-default-rules-list-resp.json @@ -0,0 +1,13 @@ +{ + "security_group_default_rules": [ + { + "from_port": 80, + "id": 1, + "ip_protocol": "TCP", + "ip_range": { + "cidr": "10.10.10.0/24" + }, + "to_port": 80 + } + ] +} \ No newline at end of file diff --git a/doc/api_samples/os-security-group-default-rules/security-group-default-rules-list-resp.xml b/doc/api_samples/os-security-group-default-rules/security-group-default-rules-list-resp.xml new file mode 100644 index 000000000000..f009bf80f1fc --- /dev/null +++ b/doc/api_samples/os-security-group-default-rules/security-group-default-rules-list-resp.xml @@ -0,0 +1,11 @@ + + + + TCP + 80 + 80 + + 10.10.10.0/24 + + + \ No newline at end of file diff --git a/doc/api_samples/os-security-group-default-rules/security-group-default-rules-show-resp.json b/doc/api_samples/os-security-group-default-rules/security-group-default-rules-show-resp.json new file mode 100644 index 000000000000..97b5259a181b --- /dev/null +++ b/doc/api_samples/os-security-group-default-rules/security-group-default-rules-show-resp.json @@ -0,0 +1,11 @@ +{ + "security_group_default_rule": { + "id": 1, + "from_port": 80, + "to_port": 80, + "ip_protocol": "TCP", + "ip_range": { + "cidr": "10.10.10.0/24" + } + } +} \ No newline at end of file diff --git a/doc/api_samples/os-security-group-default-rules/security-group-default-rules-show-resp.xml b/doc/api_samples/os-security-group-default-rules/security-group-default-rules-show-resp.xml new file mode 100644 index 000000000000..9181abd3871d --- /dev/null +++ b/doc/api_samples/os-security-group-default-rules/security-group-default-rules-show-resp.xml @@ -0,0 +1,9 @@ + + + 80 + 80 + TCP + + 10.10.10.0/24 + + \ No newline at end of file diff --git a/etc/nova/policy.json b/etc/nova/policy.json index 2d3c4ed062ad..a8bc6f1414fd 100644 --- a/etc/nova/policy.json +++ b/etc/nova/policy.json @@ -78,6 +78,7 @@ "compute_extension:quotas:update": "rule:admin_api", "compute_extension:quota_classes": "", "compute_extension:rescue": "", + "compute_extension:security_group_default_rules": "rule:admin_api", "compute_extension:security_groups": "", "compute_extension:server_diagnostics": "rule:admin_api", "compute_extension:server_password": "", diff --git a/nova/api/openstack/compute/contrib/security_group_default_rules.py b/nova/api/openstack/compute/contrib/security_group_default_rules.py new file mode 100644 index 000000000000..fed1468a81c0 --- /dev/null +++ b/nova/api/openstack/compute/contrib/security_group_default_rules.py @@ -0,0 +1,210 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2013 Metacloud Inc. +# +# 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 xml.dom import minidom + +import webob +from webob import exc + +from nova.api.openstack.compute.contrib import security_groups as sg +from nova.api.openstack import extensions +from nova.api.openstack import wsgi +from nova.api.openstack import xmlutil +from nova import exception +from nova.openstack.common import log as logging + + +LOG = logging.getLogger(__name__) +authorize = extensions.extension_authorizer('compute', + 'security_group_default_rules') + +sg_nsmap = {None: wsgi.XMLNS_V11} + + +def make_default_rule(elem): + elem.set('id') + + proto = xmlutil.SubTemplateElement(elem, 'ip_protocol') + proto.text = 'ip_protocol' + + from_port = xmlutil.SubTemplateElement(elem, 'from_port') + from_port.text = 'from_port' + + to_port = xmlutil.SubTemplateElement(elem, 'to_port') + to_port.text = 'to_port' + + ip_range = xmlutil.SubTemplateElement(elem, 'ip_range', + selector='ip_range') + cidr = xmlutil.SubTemplateElement(ip_range, 'cidr') + cidr.text = 'cidr' + + +class SecurityGroupDefaultRulesTemplate(xmlutil.TemplateBuilder): + def construct(self): + root = xmlutil.TemplateElement('security_group_default_rules') + elem = xmlutil.SubTemplateElement(root, 'security_group_default_rule', + selector='security_group_default_rules') + + make_default_rule(elem) + return xmlutil.MasterTemplate(root, 1, nsmap=sg_nsmap) + + +class SecurityGroupDefaultRuleTemplate(xmlutil.TemplateBuilder): + def construct(self): + root = xmlutil.TemplateElement('security_group_default_rule', + selector='security_group_default_rule') + make_default_rule(root) + return xmlutil.MasterTemplate(root, 1, nsmap=sg_nsmap) + + +class SecurityGroupDefaultRulesXMLDeserializer(wsgi.MetadataXMLDeserializer): + def default(self, string): + dom = minidom.parseString(string) + security_group_rule = self._extract_security_group_default_rule(dom) + return {'body': {'security_group_default_rule': security_group_rule}} + + def _extract_security_group_default_rule(self, node): + sg_rule = {} + sg_rule_node = self.find_first_child_named(node, + 'security_group_default_rule') + if sg_rule_node is not None: + ip_protocol_node = self.find_first_child_named(sg_rule_node, + "ip_protocol") + if ip_protocol_node is not None: + sg_rule['ip_protocol'] = self.extract_text(ip_protocol_node) + + from_port_node = self.find_first_child_named(sg_rule_node, + "from_port") + if from_port_node is not None: + sg_rule['from_port'] = self.extract_text(from_port_node) + + to_port_node = self.find_first_child_named(sg_rule_node, "to_port") + if to_port_node is not None: + sg_rule['to_port'] = self.extract_text(to_port_node) + + cidr_node = self.find_first_child_named(sg_rule_node, "cidr") + if cidr_node is not None: + sg_rule['cidr'] = self.extract_text(cidr_node) + + return sg_rule + + +class SecurityGroupDefaultRulesController(sg.SecurityGroupControllerBase): + + @wsgi.serializers(xml=SecurityGroupDefaultRuleTemplate) + @wsgi.deserializers(xml=SecurityGroupDefaultRulesXMLDeserializer) + def create(self, req, body): + context = self._authorize_context(req) + authorize(context) + + sg_rule = self._from_body(body, 'security_group_default_rule') + + try: + values = self._rule_args_to_dict(to_port=sg_rule.get('to_port'), + from_port=sg_rule.get('from_port'), + ip_protocol=sg_rule.get('ip_protocol'), + cidr=sg_rule.get('cidr')) + except Exception as exp: + raise exc.HTTPBadRequest(explanation=unicode(exp)) + + if values is None: + msg = _('Not enough parameters to build a valid rule.') + raise exc.HTTPBadRequest(explanation=msg) + + if self.security_group_api.default_rule_exists(context, values): + msg = _('This default rule already exists.') + raise exc.HTTPBadRequest(explanation=msg) + security_group_rule = self.security_group_api.add_default_rules( + context, [values])[0] + fmt_rule = self._format_security_group_default_rule( + security_group_rule) + return {'security_group_default_rule': fmt_rule} + + def _rule_args_to_dict(self, to_port=None, from_port=None, + ip_protocol=None, cidr=None): + cidr = self.security_group_api.parse_cidr(cidr) + return self.security_group_api.new_cidr_ingress_rule( + cidr, ip_protocol, from_port, to_port) + + @wsgi.serializers(xml=SecurityGroupDefaultRuleTemplate) + def show(self, req, id): + context = self._authorize_context(req) + authorize(context) + + id = self._validate_id(id) + LOG.debug(_("Showing security_group_default_rule with id %s") % id) + try: + rule = self.security_group_api.get_default_rule(context, id) + except exception.SecurityGroupDefaultRuleNotFound: + raise exc.HTTPNotFound(_("security group default rule not found")) + + fmt_rule = self._format_security_group_default_rule(rule) + return {"security_group_default_rule": fmt_rule} + + def delete(self, req, id): + context = self._authorize_context(req) + authorize(context) + + id = self._validate_id(id) + + rule = self.security_group_api.get_default_rule(context, id) + + self.security_group_api.remove_default_rules(context, [rule['id']]) + + return webob.Response(status_int=204) + + @wsgi.serializers(xml=SecurityGroupDefaultRulesTemplate) + def index(self, req): + + context = self._authorize_context(req) + authorize(context) + + ret = {'security_group_default_rules': []} + for rule in self.security_group_api.get_all_default_rules(context): + rule_fmt = self._format_security_group_default_rule(rule) + ret['security_group_default_rules'].append(rule_fmt) + + return ret + + def _format_security_group_default_rule(self, rule): + sg_rule = {} + sg_rule['id'] = rule['id'] + sg_rule['ip_protocol'] = rule['protocol'] + sg_rule['from_port'] = rule['from_port'] + sg_rule['to_port'] = rule['to_port'] + sg_rule['ip_range'] = {} + sg_rule['ip_range'] = {'cidr': rule['cidr']} + return sg_rule + + +class Security_group_default_rules(extensions.ExtensionDescriptor): + """Default rules for security group support.""" + name = "SecurityGroupDefaultRules" + alias = "os-security-group-default-rules" + namespace = ("http://docs.openstack.org/compute/ext/" + "securitygroupdefaultrules/api/v1.1") + updated = "2013-02-05T00:00:00+00:00" + + def get_resources(self): + resources = [ + extensions.ResourceExtension('os-security-group-default-rules', + SecurityGroupDefaultRulesController(), + collection_actions={'create': 'POST', + 'delete': 'DELETE', + 'index': 'GET'}, + member_actions={'show': 'GET'})] + + return resources diff --git a/nova/compute/api.py b/nova/compute/api.py index 5e160d2ef440..39d5f7050030 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -3128,6 +3128,46 @@ class SecurityGroupAPI(base.Base): self.trigger_rules_refresh(context, id=security_group['id']) self.trigger_handler('security_group_rule_destroy', context, rule_ids) + def remove_default_rules(self, context, rule_ids): + for rule_id in rule_ids: + self.db.security_group_default_rule_destroy(context, rule_id) + + def add_default_rules(self, context, vals): + rules = [self.db.security_group_default_rule_create(context, v) + for v in vals] + return rules + + def default_rule_exists(self, context, values): + """Indicates whether the specified rule values are already + defined in the default security group rules. + """ + for rule in self.db.security_group_default_rule_list(context): + is_duplicate = True + keys = ('cidr', 'from_port', 'to_port', 'protocol') + for key in keys: + if rule.get(key) != values.get(key): + is_duplicate = False + break + if is_duplicate: + return rule.get('id') or True + return False + + def get_all_default_rules(self, context): + try: + rules = self.db.security_group_default_rule_list(context) + except Exception: + msg = 'cannot get default security group rules' + raise exception.SecurityGroupDefaultRuleNotFound(msg) + + return rules + + def get_default_rule(self, context, id): + try: + return self.db.security_group_default_rule_get(context, id) + except exception.NotFound: + msg = _("Rule (%s) not found") % id + self.raise_not_found(msg) + @staticmethod def raise_invalid_property(msg): raise NotImplementedError() diff --git a/nova/db/api.py b/nova/db/api.py index 6ec0b3a95be8..969cf494aa72 100644 --- a/nova/db/api.py +++ b/nova/db/api.py @@ -1206,6 +1206,28 @@ def security_group_rule_count_by_group(context, security_group_id): ################### +def security_group_default_rule_get(context, security_group_rule_default_id): + return IMPL.security_group_default_rule_get(context, + security_group_rule_default_id) + + +def security_group_default_rule_destroy(context, + security_group_rule_default_id): + return IMPL.security_group_default_rule_destroy( + context, security_group_rule_default_id) + + +def security_group_default_rule_create(context, values): + return IMPL.security_group_default_rule_create(context, values) + + +def security_group_default_rule_list(context): + return IMPL.security_group_default_rule_list(context) + + +################### + + def provider_fw_rule_create(context, rule): """Add a firewall rule at the provider level (all hosts & instances).""" return IMPL.provider_fw_rule_create(context, rule) diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 375a3884b421..66fc2435580a 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -3180,6 +3180,16 @@ def security_group_ensure_default(context, session=None): 'project_id': context.project_id} default_group = security_group_create(context, values, session=session) + for default_rule in security_group_default_rule_list(context): + # This is suboptimal, it should be programmatic to know + # the values of the default_rule + rule_values = {'protocol': default_rule.protocol, + 'from_port': default_rule.from_port, + 'to_port': default_rule.to_port, + 'cidr': default_rule.cidr, + 'parent_group_id': default_group.id, + } + security_group_rule_create(context, rule_values) return (False, default_group) @@ -3280,6 +3290,56 @@ def security_group_rule_count_by_group(context, security_group_id): ################### +def _security_group_rule_get_default_query(context, session=None): + return model_query(context, models.SecurityGroupIngressDefaultRule, + session=session) + + +@require_context +def security_group_default_rule_get(context, security_group_rule_default_id, + session=None): + result = _security_group_rule_get_default_query(context, session=session).\ + filter_by(id=security_group_rule_default_id).\ + first() + + if not result: + raise exception.SecurityGroupDefaultRuleNotFound( + rule_id=security_group_rule_default_id) + + return result + + +@require_admin_context +def security_group_default_rule_destroy(context, + security_group_rule_default_id): + session = get_session() + with session.begin(): + count = _security_group_rule_get_default_query(context, + session=session).\ + filter_by(id=security_group_rule_default_id).\ + soft_delete() + if count == 0: + raise exception.SecurityGroupDefaultRuleNotFound( + rule_id=security_group_rule_default_id) + + +@require_admin_context +def security_group_default_rule_create(context, values): + security_group_default_rule_ref = models.SecurityGroupIngressDefaultRule() + security_group_default_rule_ref.update(values) + security_group_default_rule_ref.save() + return security_group_default_rule_ref + + +@require_context +def security_group_default_rule_list(context, session=None): + return _security_group_rule_get_default_query(context, session=session).\ + all() + + +################### + + @require_admin_context def provider_fw_rule_create(context, rule): fw_rule_ref = models.ProviderFirewallRule() diff --git a/nova/db/sqlalchemy/migrate_repo/versions/157_add_security_group_default_rules.py b/nova/db/sqlalchemy/migrate_repo/versions/157_add_security_group_default_rules.py new file mode 100644 index 000000000000..5dcfdbb90de7 --- /dev/null +++ b/nova/db/sqlalchemy/migrate_repo/versions/157_add_security_group_default_rules.py @@ -0,0 +1,61 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# +# 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 sqlalchemy import Column, DateTime, Integer, MetaData, String, Table +from nova.db.sqlalchemy import types + +from nova.openstack.common import log as logging + +LOG = logging.getLogger(__name__) + + +def upgrade(migrate_engine): + meta = MetaData() + meta.bind = migrate_engine + + security_group_default_rules = Table('security_group_default_rules', meta, + Column('created_at', DateTime), + Column('updated_at', DateTime), + Column('deleted_at', DateTime), + Column('deleted', Integer, default=0), + Column('id', Integer, primary_key=True, nullable=False), + Column('protocol', String(length=5)), + Column('from_port', Integer), + Column('to_port', Integer), + Column('cidr', types.CIDR()), + mysql_engine='InnoDB', + mysql_charset='utf8', + ) + + try: + security_group_default_rules.create() + except Exception: + msg = "Exception while creating table 'security_group_default_rules" + LOG.exception(msg) + raise + + +def downgrade(migrate_engine): + meta = MetaData() + meta.bind = migrate_engine + security_group_default_rules = Table('security_group_default_rules', + meta, + autoload=True) + try: + security_group_default_rules.drop() + except Exception: + msg = "Exception while droppping table 'security_group_default_rules'" + LOG.exception(msg) + raise diff --git a/nova/db/sqlalchemy/models.py b/nova/db/sqlalchemy/models.py index 28d8f0882514..f0dcd33079c3 100644 --- a/nova/db/sqlalchemy/models.py +++ b/nova/db/sqlalchemy/models.py @@ -541,6 +541,15 @@ class SecurityGroupIngressRule(BASE, NovaBase): 'SecurityGroupIngressRule.deleted == 0)') +class SecurityGroupIngressDefaultRule(BASE, NovaBase): + __tablename__ = 'security_group_default_rules' + id = Column(Integer, primary_key=True) + protocol = Column(String(5)) # "tcp", "udp" or "icmp" + from_port = Column(Integer) + to_port = Column(Integer) + cidr = Column(types.CIDR()) + + class ProviderFirewallRule(BASE, NovaBase): """Represents a rule in a security group.""" __tablename__ = 'provider_fw_rules' diff --git a/nova/exception.py b/nova/exception.py index 9e9e5182b497..09b01b342d10 100644 --- a/nova/exception.py +++ b/nova/exception.py @@ -732,6 +732,10 @@ class SecurityGroupNotExistsForInstance(Invalid): " the instance %(instance_id)s") +class SecurityGroupDefaultRuleNotFound(Invalid): + message = _("Security group default rule (%rule_id)s not found.") + + class MigrationNotFound(NotFound): message = _("Migration %(migration_id)s could not be found.") diff --git a/nova/tests/api/openstack/compute/contrib/test_security_group_default_rules.py b/nova/tests/api/openstack/compute/contrib/test_security_group_default_rules.py new file mode 100644 index 000000000000..88dee2edfb4c --- /dev/null +++ b/nova/tests/api/openstack/compute/contrib/test_security_group_default_rules.py @@ -0,0 +1,467 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2013 Metacloud, Inc +# +# 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 lxml import etree +import webob + +from nova.api.openstack.compute.contrib import security_group_default_rules +from nova.api.openstack import wsgi +from nova import context +import nova.db +from nova.openstack.common import cfg +from nova import test +from nova.tests.api.openstack import fakes + + +CONF = cfg.CONF + + +class AttrDict(dict): + def __getattr__(self, k): + return self[k] + + +def security_group_default_rule_template(**kwargs): + rule = kwargs.copy() + rule.setdefault('ip_protocol', 'TCP') + rule.setdefault('from_port', 22) + rule.setdefault('to_port', 22) + rule.setdefault('cidr', '10.10.10.0/24') + return rule + + +def security_group_default_rule_db(security_group_default_rule, id=None): + attrs = security_group_default_rule.copy() + if id is not None: + attrs['id'] = id + return AttrDict(attrs) + + +class TestSecurityGroupDefaultRules(test.TestCase): + def setUp(self): + super(TestSecurityGroupDefaultRules, self).setUp() + self.controller = \ + security_group_default_rules.SecurityGroupDefaultRulesController() + + def test_create_security_group_default_rule(self): + sgr = security_group_default_rule_template() + + req = fakes.HTTPRequest.blank( + '/v2/fake/os-security-group-default-rules', use_admin_context=True) + sgr_dict = dict(security_group_default_rule=sgr) + res_dict = self.controller.create(req, sgr_dict) + security_group_default_rule = res_dict['security_group_default_rule'] + self.assertEqual(security_group_default_rule['ip_protocol'], + sgr['ip_protocol']) + self.assertEqual(security_group_default_rule['from_port'], + sgr['from_port']) + self.assertEqual(security_group_default_rule['to_port'], + sgr['to_port']) + self.assertEqual(security_group_default_rule['ip_range']['cidr'], + sgr['cidr']) + + def test_create_security_group_default_rule_with_no_to_port(self): + sgr = security_group_default_rule_template() + del sgr['to_port'] + + req = fakes.HTTPRequest.blank( + '/v2/fake/os-security-group-default-rules', use_admin_context=True) + self.assertRaises(webob.exc.HTTPBadRequest, self.controller.create, + req, {'security_group_default_rule': sgr}) + + def test_create_security_group_default_rule_with_no_from_port(self): + sgr = security_group_default_rule_template() + del sgr['from_port'] + + req = fakes.HTTPRequest.blank( + '/v2/fake/os-security-group-default-rules', use_admin_context=True) + self.assertRaises(webob.exc.HTTPBadRequest, self.controller.create, + req, {'security_group_default_rule': sgr}) + + def test_create_security_group_default_rule_with_no_ip_protocol(self): + sgr = security_group_default_rule_template() + del sgr['ip_protocol'] + + req = fakes.HTTPRequest.blank( + '/v2/fake/os-security-group-default-rules', use_admin_context=True) + self.assertRaises(webob.exc.HTTPBadRequest, self.controller.create, + req, {'security_group_default_rule': sgr}) + + def test_create_security_group_default_rule_with_no_cidr(self): + sgr = security_group_default_rule_template() + del sgr['cidr'] + + req = fakes.HTTPRequest.blank( + '/v2/fake/os-security-group-default-rules', use_admin_context=True) + res_dict = self.controller.create(req, + {'security_group_default_rule': sgr}) + security_group_default_rule = res_dict['security_group_default_rule'] + self.assertNotEquals(security_group_default_rule['id'], 0) + self.assertEquals(security_group_default_rule['ip_range']['cidr'], + '0.0.0.0/0') + + def test_create_security_group_default_rule_with_blank_to_port(self): + sgr = security_group_default_rule_template(to_port='') + + req = fakes.HTTPRequest.blank( + '/v2/fake/os-security-group-default-rules', use_admin_context=True) + self.assertRaises(webob.exc.HTTPBadRequest, self.controller.create, + req, {'security_group_default_rule': sgr}) + + def test_create_security_group_default_rule_with_blank_from_port(self): + sgr = security_group_default_rule_template(from_port='') + + req = fakes.HTTPRequest.blank( + '/v2/fake/os-security-group-default-rules', use_admin_context=True) + self.assertRaises(webob.exc.HTTPBadRequest, self.controller.create, + req, {'security_group_default_rule': sgr}) + + def test_create_security_group_default_rule_with_blank_ip_protocol(self): + sgr = security_group_default_rule_template(ip_protocol='') + + req = fakes.HTTPRequest.blank( + '/v2/fake/os-security-group-default-rules', use_admin_context=True) + self.assertRaises(webob.exc.HTTPBadRequest, self.controller.create, + req, {'security_group_default_rule': sgr}) + + def test_create_security_group_default_rule_with_blank_cidr(self): + sgr = security_group_default_rule_template(cidr='') + + req = fakes.HTTPRequest.blank( + '/v2/fake/os-security-group-default-rules', use_admin_context=True) + res_dict = self.controller.create(req, + {'security_group_default_rule': sgr}) + security_group_default_rule = res_dict['security_group_default_rule'] + self.assertNotEquals(security_group_default_rule['id'], 0) + self.assertEquals(security_group_default_rule['ip_range']['cidr'], + '0.0.0.0/0') + + def test_create_security_group_default_rule_non_numerical_to_port(self): + sgr = security_group_default_rule_template(to_port='invalid') + + req = fakes.HTTPRequest.blank( + '/v2/fake/os-security-group-default-rules', use_admin_context=True) + self.assertRaises(webob.exc.HTTPBadRequest, self.controller.create, + req, {'security_group_default_rule': sgr}) + + def test_create_security_group_default_rule_non_numerical_from_port(self): + sgr = security_group_default_rule_template(from_port='invalid') + + req = fakes.HTTPRequest.blank( + '/v2/fake/os-security-group-default-rules', use_admin_context=True) + self.assertRaises(webob.exc.HTTPBadRequest, self.controller.create, + req, {'security_group_default_rule': sgr}) + + def test_create_security_group_default_rule_invalid_ip_protocol(self): + sgr = security_group_default_rule_template(ip_protocol='invalid') + + req = fakes.HTTPRequest.blank( + '/v2/fake/os-security-group-default-rules', use_admin_context=True) + self.assertRaises(webob.exc.HTTPBadRequest, self.controller.create, + req, {'security_group_default_rule': sgr}) + + def test_create_security_group_default_rule_invalid_cidr(self): + sgr = security_group_default_rule_template(cidr='10.10.2222.0/24') + + req = fakes.HTTPRequest.blank( + '/v2/fake/os-security-group-default-rules', use_admin_context=True) + self.assertRaises(webob.exc.HTTPBadRequest, self.controller.create, + req, {'security_group_default_rule': sgr}) + + def test_create_security_group_default_rule_invalid_to_port(self): + sgr = security_group_default_rule_template(to_port='666666') + + req = fakes.HTTPRequest.blank( + '/v2/fake/os-security-group-default-rules', use_admin_context=True) + self.assertRaises(webob.exc.HTTPBadRequest, self.controller.create, + req, {'security_group_default_rule': sgr}) + + def test_create_security_group_default_rule_invalid_from_port(self): + sgr = security_group_default_rule_template(from_port='666666') + + req = fakes.HTTPRequest.blank( + '/v2/fake/os-security-group-default-rules', use_admin_context=True) + self.assertRaises(webob.exc.HTTPBadRequest, self.controller.create, + req, {'security_group_default_rule': sgr}) + + def test_create_security_group_default_rule_with_no_body(self): + req = fakes.HTTPRequest.blank( + '/v2/fake/os-security-group-default-rules', use_admin_context=True) + self.assertRaises(webob.exc.HTTPUnprocessableEntity, + self.controller.create, req, None) + + def test_create_duplicate_security_group_default_rule(self): + sgr = security_group_default_rule_template() + + req = fakes.HTTPRequest.blank( + '/v2/fake/os-security-group-default-rules', use_admin_context=True) + self.controller.create(req, {'security_group_default_rule': sgr}) + + req = fakes.HTTPRequest.blank( + '/v2/fake/os-security-group-default-rules', use_admin_context=True) + self.assertRaises(webob.exc.HTTPBadRequest, self.controller.create, + req, {'security_group_default_rule': sgr}) + + def test_security_group_default_rules_list(self): + self.test_create_security_group_default_rule() + rules = [dict(id=1, + ip_protocol='TCP', + from_port=22, + to_port=22, + ip_range=dict(cidr='10.10.10.0/24'))] + expected = {'security_group_default_rules': rules} + + req = fakes.HTTPRequest.blank( + '/v2/fake/os-security-group-default-rules', use_admin_context=True) + res_dict = self.controller.index(req) + self.assertEqual(res_dict, expected) + + def test_default_security_group_default_rule_show(self): + sgr = security_group_default_rule_template(id=1) + + self.test_create_security_group_default_rule() + + req = fakes.HTTPRequest.blank( + '/v2/fake/os-security-group-default-rules', use_admin_context=True) + res_dict = self.controller.show(req, '1') + + security_group_default_rule = res_dict['security_group_default_rule'] + + self.assertEqual(security_group_default_rule['ip_protocol'], + sgr['ip_protocol']) + self.assertEqual(security_group_default_rule['to_port'], + sgr['to_port']) + self.assertEqual(security_group_default_rule['from_port'], + sgr['from_port']) + self.assertEqual(security_group_default_rule['ip_range']['cidr'], + sgr['cidr']) + + def test_delete_security_group_default_rule(self): + sgr = security_group_default_rule_template(id=1) + + self.test_create_security_group_default_rule() + + self.called = False + + def security_group_default_rule_destroy(context, id): + self.called = True + + def return_security_group_default_rule(context, id): + self.assertEquals(sgr['id'], id) + return security_group_default_rule_db(sgr) + + self.stubs.Set(nova.db, 'security_group_default_rule_destroy', + security_group_default_rule_destroy) + self.stubs.Set(nova.db, 'security_group_default_rule_get', + return_security_group_default_rule) + + req = fakes.HTTPRequest.blank( + '/v2/fake/os-security-group-default-rules', use_admin_context=True) + self.controller.delete(req, '1') + + self.assertTrue(self.called) + + def test_security_group_ensure_default(self): + sgr = security_group_default_rule_template(id=1) + self.test_create_security_group_default_rule() + + ctxt = context.get_admin_context() + + setattr(ctxt, 'project_id', 'new_project_id') + + _, sg = nova.db.security_group_ensure_default(ctxt) + rules = nova.db.security_group_rule_get_by_security_group(ctxt, sg.id) + security_group_rule = rules[0] + self.assertEqual(sgr['id'], security_group_rule.id) + self.assertEqual(sgr['ip_protocol'], security_group_rule.protocol) + self.assertEqual(sgr['from_port'], security_group_rule.from_port) + self.assertEqual(sgr['to_port'], security_group_rule.to_port) + self.assertEqual(sgr['cidr'], security_group_rule.cidr) + + +class TestSecurityGroupDefaultRulesXMLDeserializer(test.TestCase): + def setUp(self): + super(TestSecurityGroupDefaultRulesXMLDeserializer, self).setUp() + deserializer = security_group_default_rules.\ + SecurityGroupDefaultRulesXMLDeserializer() + self.deserializer = deserializer + + def test_create_request(self): + serial_request = """ + + 22 + 22 + TCP + 10.10.10.0/24 +""" + request = self.deserializer.deserialize(serial_request) + expected = { + "security_group_default_rule": { + "from_port": "22", + "to_port": "22", + "ip_protocol": "TCP", + "cidr": "10.10.10.0/24" + }, + } + self.assertEqual(request['body'], expected) + + def test_create_no_to_port_request(self): + serial_request = """ + + 22 + TCP + 10.10.10.0/24 +""" + request = self.deserializer.deserialize(serial_request) + expected = { + "security_group_default_rule": { + "from_port": "22", + "ip_protocol": "TCP", + "cidr": "10.10.10.0/24" + }, + } + self.assertEqual(request['body'], expected) + + def test_create_no_from_port_request(self): + serial_request = """ + + 22 + TCP + 10.10.10.0/24 +""" + request = self.deserializer.deserialize(serial_request) + expected = { + "security_group_default_rule": { + "to_port": "22", + "ip_protocol": "TCP", + "cidr": "10.10.10.0/24" + }, + } + self.assertEqual(request['body'], expected) + + def test_create_no_ip_protocol_request(self): + serial_request = """ + + 22 + 22 + 10.10.10.0/24 +""" + request = self.deserializer.deserialize(serial_request) + expected = { + "security_group_default_rule": { + "from_port": "22", + "to_port": "22", + "cidr": "10.10.10.0/24" + }, + } + self.assertEqual(request['body'], expected) + + def test_create_no_cidr_request(self): + serial_request = """ + + 22 + 22 + TCP +""" + request = self.deserializer.deserialize(serial_request) + expected = { + "security_group_default_rule": { + "from_port": "22", + "to_port": "22", + "ip_protocol": "TCP", + }, + } + self.assertEqual(request['body'], expected) + + +class TestSecurityGroupDefaultRuleXMLSerializer(test.TestCase): + def setUp(self): + super(TestSecurityGroupDefaultRuleXMLSerializer, self).setUp() + self.namespace = wsgi.XMLNS_V11 + self.rule_serializer =\ + security_group_default_rules.SecurityGroupDefaultRuleTemplate() + self.index_serializer =\ + security_group_default_rules.SecurityGroupDefaultRulesTemplate() + + def _tag(self, elem): + tagname = elem.tag + self.assertEqual(tagname[0], '{') + tmp = tagname.partition('}') + namespace = tmp[0][1:] + self.assertEqual(namespace, self.namespace) + return tmp[2] + + def _verify_security_group_default_rule(self, raw_rule, tree): + self.assertEqual(raw_rule['id'], tree.get('id')) + + seen = set() + expected = set(['ip_protocol', 'from_port', 'to_port', 'ip_range', + 'ip_range/cidr']) + + for child in tree: + child_tag = self._tag(child) + seen.add(child_tag) + if child_tag == 'ip_range': + for gr_child in child: + gr_child_tag = self._tag(gr_child) + self.assertTrue(gr_child_tag in raw_rule[child_tag]) + seen.add('%s/%s' % (child_tag, gr_child_tag)) + self.assertEqual(gr_child.text, + raw_rule[child_tag][gr_child_tag]) + else: + self.assertEqual(child.text, raw_rule[child_tag]) + self.assertEqual(seen, expected) + + def test_rule_serializer(self): + raw_rule = dict(id='123', + ip_protocol='TCP', + from_port='22', + to_port='22', + ip_range=dict(cidr='10.10.10.0/24')) + rule = dict(security_group_default_rule=raw_rule) + text = self.rule_serializer.serialize(rule) + + tree = etree.fromstring(text) + + self.assertEqual('security_group_default_rule', self._tag(tree)) + self._verify_security_group_default_rule(raw_rule, tree) + + def test_index_serializer(self): + rules = [dict(id='123', + ip_protocol='TCP', + from_port='22', + to_port='22', + ip_range=dict(cidr='10.10.10.0/24')), + dict(id='234', + ip_protocol='UDP', + from_port='23456', + to_port='234567', + ip_range=dict(cidr='10.12.0.0/18')), + dict(id='345', + ip_protocol='tcp', + from_port='3456', + to_port='4567', + ip_range=dict(cidr='192.168.1.0/32'))] + + rules_dict = dict(security_group_default_rules=rules) + + text = self.index_serializer.serialize(rules_dict) + + tree = etree.fromstring(text) + self.assertEqual('security_group_default_rules', self._tag(tree)) + self.assertEqual(len(rules), len(tree)) + for idx, child in enumerate(tree): + self._verify_security_group_default_rule(rules[idx], child) diff --git a/nova/tests/api/openstack/compute/test_extensions.py b/nova/tests/api/openstack/compute/test_extensions.py index 9c45edc08bd8..63a6b3c9a2ab 100644 --- a/nova/tests/api/openstack/compute/test_extensions.py +++ b/nova/tests/api/openstack/compute/test_extensions.py @@ -193,6 +193,7 @@ class ExtensionControllerTest(ExtensionTestCase): "Quotas", "Rescue", "SchedulerHints", + "SecurityGroupDefaultRules", "SecurityGroups", "ServerDiagnostics", "ServerPassword", diff --git a/nova/tests/fake_policy.py b/nova/tests/fake_policy.py index 3878df531d99..382667904856 100644 --- a/nova/tests/fake_policy.py +++ b/nova/tests/fake_policy.py @@ -156,6 +156,7 @@ policy_data = """ "compute_extension:quotas:update": "", "compute_extension:quota_classes": "", "compute_extension:rescue": "", + "compute_extension:security_group_default_rules": "", "compute_extension:security_groups": "", "compute_extension:server_diagnostics": "", "compute_extension:server_password": "", diff --git a/nova/tests/integrated/api_samples/all_extensions/extensions-get-resp.json.tpl b/nova/tests/integrated/api_samples/all_extensions/extensions-get-resp.json.tpl index 17914de426d4..6aeedbe56a31 100644 --- a/nova/tests/integrated/api_samples/all_extensions/extensions-get-resp.json.tpl +++ b/nova/tests/integrated/api_samples/all_extensions/extensions-get-resp.json.tpl @@ -392,6 +392,14 @@ "namespace": "http://docs.openstack.org/compute/ext/rescue/api/v1.1", "updated": "%(timestamp)s" }, + { + "alias": "os-security-group-default-rules", + "description": "%(text)s", + "links": [], + "name": "SecurityGroupDefaultRules", + "namespace": "http://docs.openstack.org/compute/ext/securitygroupdefaultrules/api/v1.1", + "updated": "%(timestamp)s" + }, { "alias": "os-security-groups", "description": "%(text)s", diff --git a/nova/tests/integrated/api_samples/all_extensions/extensions-get-resp.xml.tpl b/nova/tests/integrated/api_samples/all_extensions/extensions-get-resp.xml.tpl index 4492ed3aaaae..421cc2233819 100644 --- a/nova/tests/integrated/api_samples/all_extensions/extensions-get-resp.xml.tpl +++ b/nova/tests/integrated/api_samples/all_extensions/extensions-get-resp.xml.tpl @@ -147,6 +147,9 @@ %(text)s + + %(text)s + %(text)s diff --git a/nova/tests/integrated/api_samples/os-security-group-default-rules/security-group-default-rules-create-req.json.tpl b/nova/tests/integrated/api_samples/os-security-group-default-rules/security-group-default-rules-create-req.json.tpl new file mode 100644 index 000000000000..8836d0eeccfd --- /dev/null +++ b/nova/tests/integrated/api_samples/os-security-group-default-rules/security-group-default-rules-create-req.json.tpl @@ -0,0 +1,8 @@ +{ + "security_group_default_rule": { + "ip_protocol": "TCP", + "from_port": "80", + "to_port": "80", + "cidr": "10.10.10.0/24" + } +} \ No newline at end of file diff --git a/nova/tests/integrated/api_samples/os-security-group-default-rules/security-group-default-rules-create-req.xml.tpl b/nova/tests/integrated/api_samples/os-security-group-default-rules/security-group-default-rules-create-req.xml.tpl new file mode 100644 index 000000000000..daee122905ef --- /dev/null +++ b/nova/tests/integrated/api_samples/os-security-group-default-rules/security-group-default-rules-create-req.xml.tpl @@ -0,0 +1,7 @@ + + + TCP + 80 + 80 + 10.10.10.0/24 + \ No newline at end of file diff --git a/nova/tests/integrated/api_samples/os-security-group-default-rules/security-group-default-rules-create-resp.json.tpl b/nova/tests/integrated/api_samples/os-security-group-default-rules/security-group-default-rules-create-resp.json.tpl new file mode 100644 index 000000000000..ae6c62bfd670 --- /dev/null +++ b/nova/tests/integrated/api_samples/os-security-group-default-rules/security-group-default-rules-create-resp.json.tpl @@ -0,0 +1,11 @@ +{ + "security_group_default_rule": { + "from_port": 80, + "id": 1, + "ip_protocol": "TCP", + "ip_range":{ + "cidr": "10.10.10.0/24" + }, + "to_port": 80 + } +} \ No newline at end of file diff --git a/nova/tests/integrated/api_samples/os-security-group-default-rules/security-group-default-rules-create-resp.xml.tpl b/nova/tests/integrated/api_samples/os-security-group-default-rules/security-group-default-rules-create-resp.xml.tpl new file mode 100644 index 000000000000..9e700969ff1f --- /dev/null +++ b/nova/tests/integrated/api_samples/os-security-group-default-rules/security-group-default-rules-create-resp.xml.tpl @@ -0,0 +1,9 @@ + + + TCP + 80 + 80 + + 10.10.10.0/24 + + \ No newline at end of file diff --git a/nova/tests/integrated/api_samples/os-security-group-default-rules/security-group-default-rules-list-resp.json.tpl b/nova/tests/integrated/api_samples/os-security-group-default-rules/security-group-default-rules-list-resp.json.tpl new file mode 100644 index 000000000000..c083640c3e70 --- /dev/null +++ b/nova/tests/integrated/api_samples/os-security-group-default-rules/security-group-default-rules-list-resp.json.tpl @@ -0,0 +1,13 @@ +{ + "security_group_default_rules": [ + { + "from_port": 80, + "id": 1, + "ip_protocol": "TCP", + "ip_range": { + "cidr": "10.10.10.0/24" + }, + "to_port": 80 + } + ] +} \ No newline at end of file diff --git a/nova/tests/integrated/api_samples/os-security-group-default-rules/security-group-default-rules-list-resp.xml.tpl b/nova/tests/integrated/api_samples/os-security-group-default-rules/security-group-default-rules-list-resp.xml.tpl new file mode 100644 index 000000000000..f009bf80f1fc --- /dev/null +++ b/nova/tests/integrated/api_samples/os-security-group-default-rules/security-group-default-rules-list-resp.xml.tpl @@ -0,0 +1,11 @@ + + + + TCP + 80 + 80 + + 10.10.10.0/24 + + + \ No newline at end of file diff --git a/nova/tests/integrated/api_samples/os-security-group-default-rules/security-group-default-rules-show-resp.json.tpl b/nova/tests/integrated/api_samples/os-security-group-default-rules/security-group-default-rules-show-resp.json.tpl new file mode 100644 index 000000000000..97b5259a181b --- /dev/null +++ b/nova/tests/integrated/api_samples/os-security-group-default-rules/security-group-default-rules-show-resp.json.tpl @@ -0,0 +1,11 @@ +{ + "security_group_default_rule": { + "id": 1, + "from_port": 80, + "to_port": 80, + "ip_protocol": "TCP", + "ip_range": { + "cidr": "10.10.10.0/24" + } + } +} \ No newline at end of file diff --git a/nova/tests/integrated/api_samples/os-security-group-default-rules/security-group-default-rules-show-resp.xml.tpl b/nova/tests/integrated/api_samples/os-security-group-default-rules/security-group-default-rules-show-resp.xml.tpl new file mode 100644 index 000000000000..9181abd3871d --- /dev/null +++ b/nova/tests/integrated/api_samples/os-security-group-default-rules/security-group-default-rules-show-resp.xml.tpl @@ -0,0 +1,9 @@ + + + 80 + 80 + TCP + + 10.10.10.0/24 + + \ No newline at end of file diff --git a/nova/tests/integrated/test_api_samples.py b/nova/tests/integrated/test_api_samples.py index ae34765d9fd1..6344ae45aac8 100644 --- a/nova/tests/integrated/test_api_samples.py +++ b/nova/tests/integrated/test_api_samples.py @@ -1127,7 +1127,39 @@ class SecurityGroupsSampleJsonTest(ServersSampleBase): subs, response) -class SecurityGroupsSampleXmlTest(SecurityGroupsSampleJsonTest): +class SecurityGroupsSampleXmlTest(ApiSampleTestBase): + ctype = 'xml' + + +class SecurityGroupDefaultRulesSampleJsonTest(ServersSampleBase): + extension_name = ('nova.api.openstack.compute.contrib' + '.security_group_default_rules' + '.Security_group_default_rules') + + def test_security_group_default_rules_create(self): + response = self._do_post('os-security-group-default-rules', + 'security-group-default-rules-create-req', + {}) + self.assertEqual(response.status, 200) + return self._verify_response( + 'security-group-default-rules-create-resp', {}, response) + + def test_security_group_default_rules_list(self): + self.test_security_group_default_rules_create() + response = self._do_get('os-security-group-default-rules') + return self._verify_response('security-group-default-rules-list-resp', + {}, response) + + def test_security_group_default_rules_show(self): + self.test_security_group_default_rules_create() + rule_id = '1' + response = self._do_get('os-security-group-default-rules/%s' % rule_id) + return self._verify_response('security-group-default-rules-show-resp', + {}, response) + + +class SecurityGroupDefaultRulesSampleXmlTest( + SecurityGroupDefaultRulesSampleJsonTest): ctype = 'xml'