diff --git a/doc/v3/api_samples/os-security-group-default-rules/security-group-default-rules-create-req.json b/doc/v3/api_samples/os-security-group-default-rules/security-group-default-rules-create-req.json new file mode 100644 index 000000000000..8836d0eeccfd --- /dev/null +++ b/doc/v3/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.10.0/24" + } +} \ No newline at end of file diff --git a/doc/v3/api_samples/os-security-group-default-rules/security-group-default-rules-create-resp.json b/doc/v3/api_samples/os-security-group-default-rules/security-group-default-rules-create-resp.json new file mode 100644 index 000000000000..f757b727a480 --- /dev/null +++ b/doc/v3/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/v3/api_samples/os-security-group-default-rules/security-group-default-rules-list-resp.json b/doc/v3/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/v3/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/v3/api_samples/os-security-group-default-rules/security-group-default-rules-show-resp.json b/doc/v3/api_samples/os-security-group-default-rules/security-group-default-rules-show-resp.json new file mode 100644 index 000000000000..f757b727a480 --- /dev/null +++ b/doc/v3/api_samples/os-security-group-default-rules/security-group-default-rules-show-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/etc/nova/policy.json b/etc/nova/policy.json index 9fbc3ef0021f..2a8b6e8bcff8 100644 --- a/etc/nova/policy.json +++ b/etc/nova/policy.json @@ -242,6 +242,8 @@ "compute_extension:v3:os-rescue:discoverable": "", "compute_extension:v3:os-scheduler-hints:discoverable": "", "compute_extension:security_group_default_rules": "rule:admin_api", + "compute_extension:v3:os-security-group-default-rules:discoverable": "", + "compute_extension:v3:os-security-group-default-rules": "rule:admin_api", "compute_extension:security_groups": "", "compute_extension:v3:os-security-groups": "", "compute_extension:v3:os-security-groups:discoverable": "", diff --git a/nova/api/openstack/compute/plugins/v3/security_group_default_rules.py b/nova/api/openstack/compute/plugins/v3/security_group_default_rules.py new file mode 100644 index 000000000000..12f25504d441 --- /dev/null +++ b/nova/api/openstack/compute/plugins/v3/security_group_default_rules.py @@ -0,0 +1,158 @@ +# 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 webob import exc + +from nova.api.openstack.compute.plugins.v3 import security_groups as sg +from nova.api.openstack import extensions +from nova.api.openstack import wsgi +from nova import exception +from nova.i18n import _ +from nova.network.security_group import openstack_driver + + +ALIAS = "os-security-group-default-rules" +authorize = extensions.extension_authorizer('compute', 'v3:' + ALIAS) + + +def _authorize_context(req): + context = req.environ['nova.context'] + authorize(context) + return context + + +class SecurityGroupDefaultRulesController(sg.SecurityGroupControllerBase): + + def __init__(self): + self.security_group_api = ( + openstack_driver.get_openstack_security_group_driver()) + + @extensions.expected_errors((400, 501)) + def create(self, req, body): + context = sg._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.InvalidCidr, + exception.InvalidInput, + exception.InvalidIpProtocol, + exception.InvalidPortRange) as ex: + raise exc.HTTPBadRequest(explanation=ex.format_message()) + + 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) + + @extensions.expected_errors((400, 404, 501)) + def show(self, req, id): + context = sg._authorize_context(req) + authorize(context) + + try: + id = self.security_group_api.validate_id(id) + except exception.Invalid as ex: + raise exc.HTTPBadRequest(explanation=ex.format_message()) + + try: + rule = self.security_group_api.get_default_rule(context, id) + except exception.SecurityGroupNotFound as ex: + raise exc.HTTPNotFound(explanation=ex.format_message()) + + fmt_rule = self._format_security_group_default_rule(rule) + return {"security_group_default_rule": fmt_rule} + + @extensions.expected_errors((400, 404, 501)) + @wsgi.response(204) + def delete(self, req, id): + context = sg._authorize_context(req) + authorize(context) + + try: + id = self.security_group_api.validate_id(id) + except exception.Invalid as ex: + raise exc.HTTPBadRequest(explanation=ex.format_message()) + + try: + rule = self.security_group_api.get_default_rule(context, id) + except exception.SecurityGroupNotFound as ex: + raise exc.HTTPNotFound(explanation=ex.format_message()) + + try: + self.security_group_api.remove_default_rules(context, [rule['id']]) + except exception.SecurityGroupDefaultRuleNotFound as ex: + raise exc.HTTPNotFound(explanation=ex.format_message()) + + @extensions.expected_errors((404, 501)) + def index(self, req): + + context = sg._authorize_context(req) + authorize(context) + + ret = {'security_group_default_rules': []} + try: + 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) + except exception.SecurityGroupDefaultRuleNotFound as ex: + raise exc.HTTPNotFound(explanation=ex.format_message()) + + 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 SecurityGroupDefaultRules(extensions.V3APIExtensionBase): + """Default rules for security group support.""" + name = "SecurityGroupDefaultRules" + alias = ALIAS + version = 1 + + def get_resources(self): + resources = [ + extensions.ResourceExtension(ALIAS, + SecurityGroupDefaultRulesController())] + + return resources + + def get_controller_extensions(self): + return [] 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 index af543dede060..abe0724f78a8 100644 --- 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 @@ -16,7 +16,10 @@ from lxml import etree from oslo.config import cfg import webob -from nova.api.openstack.compute.contrib import security_group_default_rules +from nova.api.openstack.compute.contrib import \ + security_group_default_rules as security_group_default_rules_v2 +from nova.api.openstack.compute.plugins.v3 import \ + security_group_default_rules as security_group_default_rules_v21 from nova.api.openstack import wsgi from nova import context import nova.db @@ -48,12 +51,14 @@ def security_group_default_rule_db(security_group_default_rule, id=None): return AttrDict(attrs) -class TestSecurityGroupDefaultRulesNeutron(test.TestCase): +class TestSecurityGroupDefaultRulesNeutronV21(test.TestCase): + controller_cls = (security_group_default_rules_v21. + SecurityGroupDefaultRulesController) + def setUp(self): self.flags(security_group_api='neutron') - super(TestSecurityGroupDefaultRulesNeutron, self).setUp() - self.controller = \ - security_group_default_rules.SecurityGroupDefaultRulesController() + super(TestSecurityGroupDefaultRulesNeutronV21, self).setUp() + self.controller = self.controller_cls() def test_create_security_group_default_rule_not_implemented_neutron(self): sgr = security_group_default_rule_template() @@ -81,11 +86,18 @@ class TestSecurityGroupDefaultRulesNeutron(test.TestCase): req, '602ed77c-a076-4f9b-a617-f93b847b62c5') -class TestSecurityGroupDefaultRules(test.TestCase): +class TestSecurityGroupDefaultRulesNeutronV2(test.TestCase): + controller_cls = (security_group_default_rules_v2. + SecurityGroupDefaultRulesController) + + +class TestSecurityGroupDefaultRulesV21(test.TestCase): + controller_cls = (security_group_default_rules_v21. + SecurityGroupDefaultRulesController) + def setUp(self): - super(TestSecurityGroupDefaultRules, self).setUp() - self.controller = \ - security_group_default_rules.SecurityGroupDefaultRulesController() + super(TestSecurityGroupDefaultRulesV21, self).setUp() + self.controller = self.controller_cls() def test_create_security_group_default_rule(self): sgr = security_group_default_rule_template() @@ -323,10 +335,15 @@ class TestSecurityGroupDefaultRules(test.TestCase): self.assertEqual(sgr['cidr'], security_group_rule.cidr) +class TestSecurityGroupDefaultRulesV2(test.TestCase): + controller_cls = (security_group_default_rules_v2. + SecurityGroupDefaultRulesController) + + class TestSecurityGroupDefaultRulesXMLDeserializer(test.TestCase): def setUp(self): super(TestSecurityGroupDefaultRulesXMLDeserializer, self).setUp() - deserializer = security_group_default_rules.\ + deserializer = security_group_default_rules_v2.\ SecurityGroupDefaultRulesXMLDeserializer() self.deserializer = deserializer @@ -423,9 +440,9 @@ class TestSecurityGroupDefaultRuleXMLSerializer(test.TestCase): super(TestSecurityGroupDefaultRuleXMLSerializer, self).setUp() self.namespace = wsgi.XMLNS_V11 self.rule_serializer =\ - security_group_default_rules.SecurityGroupDefaultRuleTemplate() + security_group_default_rules_v2.SecurityGroupDefaultRuleTemplate() self.index_serializer =\ - security_group_default_rules.SecurityGroupDefaultRulesTemplate() + security_group_default_rules_v2.SecurityGroupDefaultRulesTemplate() def _tag(self, elem): tagname = elem.tag diff --git a/nova/tests/fake_policy.py b/nova/tests/fake_policy.py index f9c89f3c7275..b4c11f8a4e0d 100644 --- a/nova/tests/fake_policy.py +++ b/nova/tests/fake_policy.py @@ -279,6 +279,7 @@ policy_data = """ "compute_extension:rescue": "", "compute_extension:v3:os-rescue": "", "compute_extension:security_group_default_rules": "", + "compute_extension:v3:os-security-group-default-rules": "", "compute_extension:security_groups": "", "compute_extension:v3:os-security-groups": "", "compute_extension:server_diagnostics": "", diff --git a/nova/tests/integrated/v3/api_samples/os-security-group-default-rules/security-group-default-rules-create-req.json.tpl b/nova/tests/integrated/v3/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/v3/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/v3/api_samples/os-security-group-default-rules/security-group-default-rules-create-resp.json.tpl b/nova/tests/integrated/v3/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/v3/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/v3/api_samples/os-security-group-default-rules/security-group-default-rules-list-resp.json.tpl b/nova/tests/integrated/v3/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/v3/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/v3/api_samples/os-security-group-default-rules/security-group-default-rules-show-resp.json.tpl b/nova/tests/integrated/v3/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/v3/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/v3/test_security_group_default_rules.py b/nova/tests/integrated/v3/test_security_group_default_rules.py new file mode 100644 index 000000000000..99882ce865d6 --- /dev/null +++ b/nova/tests/integrated/v3/test_security_group_default_rules.py @@ -0,0 +1,40 @@ +# Copyright 2014 IBM Corp. +# +# 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 nova.tests.integrated.v3 import api_sample_base + + +class SecurityGroupDefaultRulesSampleJsonTest( + api_sample_base.ApiSampleTestBaseV3): + extension_name = 'os-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._verify_response('security-group-default-rules-create-resp', + {}, response, 200) + + def test_security_group_default_rules_list(self): + self.test_security_group_default_rules_create() + response = self._do_get('os-security-group-default-rules') + self._verify_response('security-group-default-rules-list-resp', + {}, response, 200) + + 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) + self._verify_response('security-group-default-rules-show-resp', + {}, response, 200) diff --git a/setup.cfg b/setup.cfg index 65da97491097..ccc350c3dc0f 100644 --- a/setup.cfg +++ b/setup.cfg @@ -114,6 +114,7 @@ nova.api.v3.extensions = remote_consoles = nova.api.openstack.compute.plugins.v3.remote_consoles:RemoteConsoles rescue = nova.api.openstack.compute.plugins.v3.rescue:Rescue scheduler_hints = nova.api.openstack.compute.plugins.v3.scheduler_hints:SchedulerHints + security_group_default_rules = nova.api.openstack.compute.plugins.v3.security_group_default_rules:SecurityGroupDefaultRules security_groups = nova.api.openstack.compute.plugins.v3.security_groups:SecurityGroups server_diagnostics = nova.api.openstack.compute.plugins.v3.server_diagnostics:ServerDiagnostics server_external_events = nova.api.openstack.compute.plugins.v3.server_external_events:ServerExternalEvents