diff --git a/api-ref/source/parameters.yaml b/api-ref/source/parameters.yaml index ee9ff8be74..999d6777f4 100644 --- a/api-ref/source/parameters.yaml +++ b/api-ref/source/parameters.yaml @@ -704,6 +704,26 @@ l7policy-position-optional: in: body required: false type: integer +l7policy-redirect-http-code: + description: | + Requests matching this policy will be redirected to the specified URL or + Prefix URL with the HTTP response code. Valid if ``action`` is + ``REDIRECT_TO_URL`` or ``REDIRECT_PREFIX``. Valid options are: 301, 302, + 303, 307, or 308. Default is 302. + in: body + min_version: 2.9 + required: true + type: integer +l7policy-redirect-http-code-optional: + description: | + Requests matching this policy will be redirected to the specified URL or + Prefix URL with the HTTP response code. Valid if ``action`` is + ``REDIRECT_TO_URL`` or ``REDIRECT_PREFIX``. Valid options are: 301, 302, + 303, 307, or 308. Default is 302. + in: body + min_version: 2.9 + required: false + type: integer l7policy-redirect-pool_id: description: | Requests matching this policy will be redirected to the pool with this ID. diff --git a/api-ref/source/v2/examples/l7policies-list-response.json b/api-ref/source/v2/examples/l7policies-list-response.json index 87935227e1..0a12e2bac4 100644 --- a/api-ref/source/v2/examples/l7policies-list-response.json +++ b/api-ref/source/v2/examples/l7policies-list-response.json @@ -12,6 +12,7 @@ "created_at": "2017-06-24T23:25:14", "provisioning_status": "ACTIVE", "updated_at": "2017-06-24T23:30:05", + "redirect_http_code": 302, "redirect_pool_id": null, "redirect_prefix": null, "redirect_url": "http://www.example.com", diff --git a/api-ref/source/v2/examples/l7policy-create-curl b/api-ref/source/v2/examples/l7policy-create-curl index 4bc30d2a30..95c74fd773 100644 --- a/api-ref/source/v2/examples/l7policy-create-curl +++ b/api-ref/source/v2/examples/l7policy-create-curl @@ -1 +1 @@ -curl -X POST -H "Content-Type: application/json" -H "X-Auth-Token: " -d '{"l7policy":{"description":"Redirect requests to example.com","admin_state_up":true,"listener_id":"023f2e34-7806-443b-bfae-16c324569a3d","redirect_url":"http://www.example.com","name":"redirect-example.com","action":"REDIRECT_TO_URL","position":1,"tags":["test_tag"]}}' http://198.51.100.10:9876/v2/lbaas/l7policies +curl -X POST -H "Content-Type: application/json" -H "X-Auth-Token: " -d '{"l7policy":{"description":"Redirect requests to example.com","admin_state_up":true,"listener_id":"023f2e34-7806-443b-bfae-16c324569a3d","redirect_http_code":301,"redirect_url":"http://www.example.com","name":"redirect-example.com","action":"REDIRECT_TO_URL","position":1,"tags":["test_tag"]}}' http://198.51.100.10:9876/v2/lbaas/l7policies diff --git a/api-ref/source/v2/examples/l7policy-create-request.json b/api-ref/source/v2/examples/l7policy-create-request.json index f14619c156..0d28471d8b 100644 --- a/api-ref/source/v2/examples/l7policy-create-request.json +++ b/api-ref/source/v2/examples/l7policy-create-request.json @@ -4,6 +4,7 @@ "admin_state_up": true, "listener_id": "023f2e34-7806-443b-bfae-16c324569a3d", "redirect_url": "http://www.example.com", + "redirect_http_code": 301, "name": "redirect-example.com", "action": "REDIRECT_TO_URL", "position": 1, diff --git a/api-ref/source/v2/examples/l7policy-create-response.json b/api-ref/source/v2/examples/l7policy-create-response.json index c287dc5d87..8f3f429f44 100644 --- a/api-ref/source/v2/examples/l7policy-create-response.json +++ b/api-ref/source/v2/examples/l7policy-create-response.json @@ -12,6 +12,7 @@ "created_at": "2017-06-24T23:25:14", "provisioning_status": "PENDING_CREATE", "updated_at": "2017-06-24T23:30:05", + "redirect_http_code": 301, "redirect_pool_id": null, "redirect_prefix": null, "redirect_url": "http://www.example.com", diff --git a/api-ref/source/v2/examples/l7policy-show-response.json b/api-ref/source/v2/examples/l7policy-show-response.json index 800b2bba84..c6cebb4c42 100644 --- a/api-ref/source/v2/examples/l7policy-show-response.json +++ b/api-ref/source/v2/examples/l7policy-show-response.json @@ -12,6 +12,7 @@ "created_at": "2017-06-24T23:25:14", "provisioning_status": "ACTIVE", "updated_at": "2017-06-24T23:30:05", + "redirect_http_code": 302, "redirect_pool_id": null, "redirect_prefix": null, "redirect_url": "http://www.example.com", diff --git a/api-ref/source/v2/examples/l7policy-update-curl b/api-ref/source/v2/examples/l7policy-update-curl index 2f06eb75c6..528ef8a4d3 100644 --- a/api-ref/source/v2/examples/l7policy-update-curl +++ b/api-ref/source/v2/examples/l7policy-update-curl @@ -1 +1 @@ -curl -X PUT -H "Content-Type: application/json" -H "X-Auth-Token: " -d '{"l7policy":{"description":"Redirect requests to images.example.com","admin_state_up":true,"redirect_url":"http://images.example.com","name":"redirect-images.example.com","action":"REDIRECT_TO_URL","position":1,"tags":["updated_tag"]}}' http://198.51.100.10:9876/v2/lbaas/l7policies/8a1412f0-4c32-4257-8b07-af4770b604fd +curl -X PUT -H "Content-Type: application/json" -H "X-Auth-Token: " -d '{"l7policy":{"description":"Redirect requests to images.example.com","admin_state_up":true,"redirect_http_code":301,"redirect_url":"http://images.example.com","name":"redirect-images.example.com","action":"REDIRECT_TO_URL","position":1,"tags":["updated_tag"]}}' http://198.51.100.10:9876/v2/lbaas/l7policies/8a1412f0-4c32-4257-8b07-af4770b604fd diff --git a/api-ref/source/v2/examples/l7policy-update-request.json b/api-ref/source/v2/examples/l7policy-update-request.json index 3a571d2904..e831358372 100644 --- a/api-ref/source/v2/examples/l7policy-update-request.json +++ b/api-ref/source/v2/examples/l7policy-update-request.json @@ -2,6 +2,7 @@ "l7policy": { "description": "Redirect requests to images.example.com", "admin_state_up": true, + "redirect_http_code": 301, "redirect_url": "http://images.example.com", "name": "redirect-images.example.com", "action": "REDIRECT_TO_URL", diff --git a/api-ref/source/v2/examples/l7policy-update-response.json b/api-ref/source/v2/examples/l7policy-update-response.json index bc6c98ff1f..3451aae937 100644 --- a/api-ref/source/v2/examples/l7policy-update-response.json +++ b/api-ref/source/v2/examples/l7policy-update-response.json @@ -12,6 +12,7 @@ "created_at": "2017-06-24T23:25:14", "provisioning_status": "PENDING_UPDATE", "updated_at": "2017-06-24T23:30:05", + "redirect_http_code": 301, "redirect_pool_id": null, "redirect_prefix": null, "redirect_url": "http://www.example.com", diff --git a/api-ref/source/v2/l7policy.inc b/api-ref/source/v2/l7policy.inc index 966e56e975..ec8f024b47 100644 --- a/api-ref/source/v2/l7policy.inc +++ b/api-ref/source/v2/l7policy.inc @@ -56,6 +56,7 @@ Response Parameters - position: l7policy-position - project_id: project_id - provisioning_status: provisioning_status + - redirect_http_code: l7policy-redirect-http-code - redirect_pool_id: l7policy-redirect-pool_id - redirect_prefix: l7policy-redirect-prefix - redirect_url: l7policy-redirect-url @@ -106,8 +107,10 @@ creating multiple policies with the same action. If a new policy is created with a position that matches that of an existing policy, then the new policy is inserted at the given position. -L7 policies with ``action`` of ``REDIRECT_TO_URL`` will return a HTTP -``Found (302)`` response code with the ``redirect_url``. +L7 policies with ``action`` of ``REDIRECT_TO_URL`` will return the default HTTP +``Found (302)`` response code with the ``redirect_url``. Also, specify +``redirect_http_code`` to configure the needed HTTP response code, such as, +301, 302, 303, 307 and 308. L7 policies with ``action`` of ``REJECT`` will return a ``Forbidden (403)`` response code to the requester. @@ -140,6 +143,7 @@ Request - name: name-optional - position: l7policy-position-optional - project_id: project_id-optional + - redirect_http_code: l7policy-redirect-http-code-optional - redirect_pool_id: l7policy-redirect-pool_id-optional - redirect_prefix: l7policy-redirect-prefix-optional - redirect_url: l7policy-redirect-url-optional @@ -173,6 +177,7 @@ Response Parameters - position: l7policy-position - project_id: project_id - provisioning_status: provisioning_status + - redirect_http_code: l7policy-redirect-http-code - redirect_pool_id: l7policy-redirect-pool_id - redirect_prefix: l7policy-redirect-prefix - redirect_url: l7policy-redirect-url @@ -240,6 +245,7 @@ Response Parameters - position: l7policy-position - project_id: project_id - provisioning_status: provisioning_status + - redirect_http_code: l7policy-redirect-http-code - redirect_pool_id: l7policy-redirect-pool_id - redirect_prefix: l7policy-redirect-prefix - redirect_url: l7policy-redirect-url @@ -297,6 +303,7 @@ Request - l7policy_id: path-l7policy-id - name: name-optional - position: l7policy-position-optional + - redirect_http_code: l7policy-redirect-http-code-optional - redirect_pool_id: l7policy-redirect-pool_id-optional - redirect_prefix: l7policy-redirect-prefix-optional - redirect_url: l7policy-redirect-url-optional @@ -330,6 +337,7 @@ Response Parameters - position: l7policy-position - project_id: project_id - provisioning_status: provisioning_status + - redirect_http_code: l7policy-redirect-http-code - redirect_pool_id: l7policy-redirect-pool_id - redirect_prefix: l7policy-redirect-prefix - redirect_url: l7policy-redirect-url diff --git a/octavia/api/drivers/data_models.py b/octavia/api/drivers/data_models.py index 678b9bf097..65aabf4ea5 100644 --- a/octavia/api/drivers/data_models.py +++ b/octavia/api/drivers/data_models.py @@ -238,7 +238,7 @@ class L7Policy(BaseDataModel): def __init__(self, action=Unset, admin_state_up=Unset, description=Unset, l7policy_id=Unset, listener_id=Unset, name=Unset, position=Unset, redirect_pool_id=Unset, redirect_url=Unset, - rules=Unset, redirect_prefix=Unset): + rules=Unset, redirect_prefix=Unset, redirect_http_code=Unset): self.action = action self.admin_state_up = admin_state_up @@ -251,6 +251,7 @@ class L7Policy(BaseDataModel): self.redirect_url = redirect_url self.rules = rules self.redirect_prefix = redirect_prefix + self.redirect_http_code = redirect_http_code class L7Rule(BaseDataModel): diff --git a/octavia/api/root_controller.py b/octavia/api/root_controller.py index deaa9f985c..e0f8b8a5ec 100644 --- a/octavia/api/root_controller.py +++ b/octavia/api/root_controller.py @@ -92,6 +92,9 @@ class RootController(rest.RestController): self._add_a_version(versions, 'v2.7', 'v2', 'SUPPORTED', '2018-01-25T12:00:00Z', host_url) # TLS client authentication - self._add_a_version(versions, 'v2.8', 'v2', 'CURRENT', + self._add_a_version(versions, 'v2.8', 'v2', 'SUPPORTED', '2019-02-12T00:00:00Z', host_url) + # HTTP Redirect code + self._add_a_version(versions, 'v2.9', 'v2', 'CURRENT', + '2019-03-04T00:00:00Z', host_url) return {'versions': versions} diff --git a/octavia/api/v2/controllers/l7policy.py b/octavia/api/v2/controllers/l7policy.py index 630d9bdbf9..a7503ce5c0 100644 --- a/octavia/api/v2/controllers/l7policy.py +++ b/octavia/api/v2/controllers/l7policy.py @@ -95,6 +95,12 @@ class L7PolicyController(base.BaseController): def _validate_create_l7policy(self, lock_session, l7policy_dict): try: + # Set the default HTTP redirect code here so it's explicit + if ((l7policy_dict.get('redirect_url') or + l7policy_dict.get('redirect_prefix')) and + not l7policy_dict.get('redirect_http_code')): + l7policy_dict['redirect_http_code'] = 302 + return self.repositories.l7policy.create(lock_session, **l7policy_dict) except odb_exceptions.DBDuplicateEntry: diff --git a/octavia/api/v2/types/l7policy.py b/octavia/api/v2/types/l7policy.py index 8b13df8078..4549bd94bc 100644 --- a/octavia/api/v2/types/l7policy.py +++ b/octavia/api/v2/types/l7policy.py @@ -44,6 +44,7 @@ class L7PolicyResponse(BaseL7PolicyType): created_at = wtypes.wsattr(wtypes.datetime.datetime) updated_at = wtypes.wsattr(wtypes.datetime.datetime) tags = wtypes.wsattr(wtypes.ArrayType(wtypes.StringType())) + redirect_http_code = wtypes.wsattr(wtypes.IntegerType()) @classmethod def from_data_model(cls, data_model, children=False): @@ -96,6 +97,8 @@ class L7PolicyPOST(BaseL7PolicyType): listener_id = wtypes.wsattr(wtypes.UuidType(), mandatory=True) rules = wtypes.wsattr([l7rule.L7RuleSingleCreate]) tags = wtypes.wsattr(wtypes.ArrayType(wtypes.StringType(max_length=255))) + redirect_http_code = wtypes.wsattr( + wtypes.Enum(int, *constants.SUPPORTED_L7POLICY_REDIRECT_HTTP_CODES)) class L7PolicyRootPOST(types.BaseType): @@ -116,6 +119,8 @@ class L7PolicyPUT(BaseL7PolicyType): minimum=constants.MIN_POLICY_POSITION, maximum=constants.MAX_POLICY_POSITION)) tags = wtypes.wsattr(wtypes.ArrayType(wtypes.StringType(max_length=255))) + redirect_http_code = wtypes.wsattr( + wtypes.Enum(int, *constants.SUPPORTED_L7POLICY_REDIRECT_HTTP_CODES)) class L7PolicyRootPUT(types.BaseType): @@ -139,3 +144,5 @@ class L7PolicySingleCreate(BaseL7PolicyType): default=constants.MAX_POLICY_POSITION) rules = wtypes.wsattr([l7rule.L7RuleSingleCreate]) tags = wtypes.wsattr(wtypes.ArrayType(wtypes.StringType(max_length=255))) + redirect_http_code = wtypes.wsattr( + wtypes.Enum(int, *constants.SUPPORTED_L7POLICY_REDIRECT_HTTP_CODES)) diff --git a/octavia/common/constants.py b/octavia/common/constants.py index fd343cc767..c0cdc9ed0c 100644 --- a/octavia/common/constants.py +++ b/octavia/common/constants.py @@ -173,6 +173,9 @@ SUPPORTED_L7POLICY_ACTIONS = (L7POLICY_ACTION_REJECT, L7POLICY_ACTION_REDIRECT_TO_POOL, L7POLICY_ACTION_REDIRECT_PREFIX) +# For redirect, only codes 301, 302, 303, 307 and 308 are # supported. +SUPPORTED_L7POLICY_REDIRECT_HTTP_CODES = [301, 302, 303, 307, 308] + MIN_POLICY_POSITION = 1 # Largest a 32-bit integer can be, which is a limitation # here if you're using MySQL, as most probably are. This just needs diff --git a/octavia/common/data_models.py b/octavia/common/data_models.py index 300ec87b00..a951800946 100644 --- a/octavia/common/data_models.py +++ b/octavia/common/data_models.py @@ -620,7 +620,8 @@ class L7Policy(BaseDataModel): position=None, listener=None, redirect_pool=None, enabled=None, l7rules=None, provisioning_status=None, operating_status=None, project_id=None, created_at=None, - updated_at=None, redirect_prefix=None, tags=None): + updated_at=None, redirect_prefix=None, tags=None, + redirect_http_code=None): self.id = id self.name = name self.description = description @@ -640,6 +641,7 @@ class L7Policy(BaseDataModel): self.updated_at = updated_at self.redirect_prefix = redirect_prefix self.tags = tags + self.redirect_http_code = redirect_http_code def _conditionally_remove_pool_links(self, pool): """Removes links to the given pool from parent objects. @@ -666,6 +668,7 @@ class L7Policy(BaseDataModel): self._conditionally_remove_pool_links(self.redirect_pool) self.action = constants.L7POLICY_ACTION_REDIRECT_TO_POOL self.redirect_url = None + self.redirect_http_code = None pool = self._find_in_graph('Pool' + value) self.redirect_pool = pool if self.l7rules and (self.enabled is True or ( @@ -685,6 +688,7 @@ class L7Policy(BaseDataModel): self._conditionally_remove_pool_links(self.redirect_pool) self.redirect_pool = None self.redirect_pool_id = None + self.redirect_http_code = None elif key == 'position': self.listener.l7policies.remove(self) self.listener.l7policies.insert(value - 1, self) diff --git a/octavia/common/jinja/haproxy/jinja_cfg.py b/octavia/common/jinja/haproxy/jinja_cfg.py index 70e46e91e3..43e6e937e2 100644 --- a/octavia/common/jinja/haproxy/jinja_cfg.py +++ b/octavia/common/jinja/haproxy/jinja_cfg.py @@ -397,6 +397,12 @@ class JinjaTemplater(object): l7policy.redirect_pool, feature_compatibility, **kwargs) else: ret_value['redirect_pool'] = None + if (l7policy.action in [constants.L7POLICY_ACTION_REDIRECT_TO_URL, + constants.L7POLICY_ACTION_REDIRECT_PREFIX] and + l7policy.redirect_http_code): + ret_value['redirect_http_code'] = l7policy.redirect_http_code + else: + ret_value['redirect_http_code'] = None l7rules = [self._transform_l7rule(x, feature_compatibility) for x in l7policy.l7rules if x.enabled] ret_value['l7rules'] = l7rules diff --git a/octavia/common/jinja/haproxy/templates/macros.j2 b/octavia/common/jinja/haproxy/templates/macros.j2 index 825d99fbba..c2d422ec9a 100644 --- a/octavia/common/jinja/haproxy/templates/macros.j2 +++ b/octavia/common/jinja/haproxy/templates/macros.j2 @@ -116,16 +116,22 @@ bind {{ lb_vip_address }}:{{ listener.protocol_port }} {{ {% for l7rule in l7policy.l7rules %} {{- l7rule_macro(constants, l7rule) -}} {% endfor %} + {% if l7policy.redirect_http_code %} + {% set redirect_http_code_opt = " code %s"|format( + l7policy.redirect_http_code) %} + {% else %} + {% set redirect_http_code_opt = "" %} + {% endif %} {% if l7policy.action == constants.L7POLICY_ACTION_REJECT %} http-request deny if{{ l7rule_list_macro(l7policy) }} {% elif l7policy.action == constants.L7POLICY_ACTION_REDIRECT_TO_URL %} - redirect location {{ l7policy.redirect_url }} if{{ l7rule_list_macro( + redirect {{- redirect_http_code_opt }} location {{ l7policy.redirect_url }} if{{ l7rule_list_macro( l7policy) }} {% elif l7policy.action == constants.L7POLICY_ACTION_REDIRECT_TO_POOL and l7policy.redirect_pool.enabled %} use_backend {{ l7policy.redirect_pool.id }} if{{ l7rule_list_macro( l7policy) }} {% elif l7policy.action == constants.L7POLICY_ACTION_REDIRECT_PREFIX %} - redirect prefix {{ l7policy.redirect_prefix }} if{{ l7rule_list_macro( + redirect {{- redirect_http_code_opt }} prefix {{ l7policy.redirect_prefix }} if{{ l7rule_list_macro( l7policy) }} {% endif %} {% endmacro %} diff --git a/octavia/common/validate.py b/octavia/common/validate.py index 1d7a7388a9..16fbfe8453 100644 --- a/octavia/common/validate.py +++ b/octavia/common/validate.py @@ -274,12 +274,14 @@ def sanitize_l7policy_api_args(l7policy, create=False): l7policy.update({'redirect_url': None}) l7policy.pop('redirect_pool', None) l7policy.update({'redirect_prefix': None}) + l7policy.update({'redirect_http_code': None}) if l7policy.get('redirect_pool'): l7policy.update({ 'action': constants.L7POLICY_ACTION_REDIRECT_TO_POOL}) l7policy.update({'redirect_url': None}) l7policy.pop('redirect_pool_id', None) l7policy.update({'redirect_prefix': None}) + l7policy.update({'redirect_http_code': None}) if l7policy.get('redirect_url'): url(l7policy['redirect_url']) l7policy.update({ @@ -287,6 +289,8 @@ def sanitize_l7policy_api_args(l7policy, create=False): l7policy.update({'redirect_pool_id': None}) l7policy.update({'redirect_prefix': None}) l7policy.pop('redirect_pool', None) + if not l7policy.get('redirect_http_code'): + l7policy.update({'redirect_http_code': 302}) if l7policy.get('redirect_prefix'): url(l7policy['redirect_prefix']) l7policy.update({ @@ -294,6 +298,8 @@ def sanitize_l7policy_api_args(l7policy, create=False): l7policy.update({'redirect_pool_id': None}) l7policy.update({'redirect_url': None}) l7policy.pop('redirect_pool', None) + if not l7policy.get('redirect_http_code'): + l7policy.update({'redirect_http_code': 302}) # If we are creating, we need an action at this point if create and 'action' not in l7policy.keys(): diff --git a/octavia/db/migration/alembic_migrations/versions/6742ca1b27c2_add_l7policy_redirect_http_code.py b/octavia/db/migration/alembic_migrations/versions/6742ca1b27c2_add_l7policy_redirect_http_code.py new file mode 100644 index 0000000000..1e37ac1419 --- /dev/null +++ b/octavia/db/migration/alembic_migrations/versions/6742ca1b27c2_add_l7policy_redirect_http_code.py @@ -0,0 +1,36 @@ +# +# 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. +# + +"""Add L7policy Redirect http code + +Revision ID: 6742ca1b27c2 +Revises: a7f187cd221f +Create Date: 2018-12-13 09:35:38.780054 + +""" + +from alembic import op +import sqlalchemy as sa + +# revision identifiers, used by Alembic. +revision = '6742ca1b27c2' +down_revision = 'a7f187cd221f' + + +def upgrade(): + # Add collumn redirect_prefix + op.add_column( + u'l7policy', + sa.Column(u'redirect_http_code', sa.Integer(), nullable=True) + ) diff --git a/octavia/db/models.py b/octavia/db/models.py index 42076ce131..0c88977bb5 100644 --- a/octavia/db/models.py +++ b/octavia/db/models.py @@ -689,6 +689,7 @@ class L7Policy(base_models.BASE, base_models.IdMixin, base_models.ProjectMixin, redirect_prefix = sa.Column( sa.String(255), nullable=True) + redirect_http_code = sa.Column(sa.Integer, nullable=True) position = sa.Column(sa.Integer, nullable=False) enabled = sa.Column(sa.Boolean(), nullable=False) listener = orm.relationship("Listener", uselist=False, diff --git a/octavia/db/repositories.py b/octavia/db/repositories.py index 46dfcf3d05..28ea5e643c 100644 --- a/octavia/db/repositories.py +++ b/octavia/db/repositories.py @@ -1654,6 +1654,7 @@ class L7PolicyRepository(BaseRepository): model_kwargs.update(redirect_url=None) model_kwargs.update(redirect_pool_id=None) model_kwargs.update(redirect_prefix=None) + model_kwargs.update(redirect_http_code=None) elif (l7policy.action == consts.L7POLICY_ACTION_REDIRECT_TO_URL): model_kwargs.update(redirect_pool_id=None) @@ -1662,6 +1663,7 @@ class L7PolicyRepository(BaseRepository): consts.L7POLICY_ACTION_REDIRECT_TO_POOL): model_kwargs.update(redirect_url=None) model_kwargs.update(redirect_prefix=None) + model_kwargs.update(redirect_http_code=None) elif (l7policy.action == consts.L7POLICY_ACTION_REDIRECT_PREFIX): model_kwargs.update(redirect_url=None) diff --git a/octavia/tests/functional/api/test_root_controller.py b/octavia/tests/functional/api/test_root_controller.py index 21b7c19f2c..e8685a1478 100644 --- a/octavia/tests/functional/api/test_root_controller.py +++ b/octavia/tests/functional/api/test_root_controller.py @@ -46,7 +46,7 @@ class TestRootController(base_db_test.OctaviaDBTestBase): versions = self._get_versions_with_config( api_v1_enabled=True, api_v2_enabled=True) version_ids = tuple(v.get('id') for v in versions) - self.assertEqual(10, len(version_ids)) + self.assertEqual(11, len(version_ids)) self.assertIn('v1', version_ids) self.assertIn('v2.0', version_ids) self.assertIn('v2.1', version_ids) @@ -57,6 +57,7 @@ class TestRootController(base_db_test.OctaviaDBTestBase): self.assertIn('v2.6', version_ids) self.assertIn('v2.7', version_ids) self.assertIn('v2.8', version_ids) + self.assertIn('v2.9', version_ids) # Each version should have a 'self' 'href' to the API version URL # [{u'rel': u'self', u'href': u'http://localhost/v2'}] @@ -76,7 +77,7 @@ class TestRootController(base_db_test.OctaviaDBTestBase): def test_api_v1_disabled(self): versions = self._get_versions_with_config( api_v1_enabled=False, api_v2_enabled=True) - self.assertEqual(9, len(versions)) + self.assertEqual(10, len(versions)) self.assertEqual('v2.0', versions[0].get('id')) self.assertEqual('v2.1', versions[1].get('id')) self.assertEqual('v2.2', versions[2].get('id')) @@ -86,6 +87,7 @@ class TestRootController(base_db_test.OctaviaDBTestBase): self.assertEqual('v2.6', versions[6].get('id')) self.assertEqual('v2.7', versions[7].get('id')) self.assertEqual('v2.8', versions[8].get('id')) + self.assertEqual('v2.9', versions[9].get('id')) def test_api_v2_disabled(self): versions = self._get_versions_with_config( diff --git a/octavia/tests/functional/api/v2/test_l7policy.py b/octavia/tests/functional/api/v2/test_l7policy.py index 800db1a730..d5cd0f6859 100644 --- a/octavia/tests/functional/api/v2/test_l7policy.py +++ b/octavia/tests/functional/api/v2/test_l7policy.py @@ -703,6 +703,61 @@ class TestL7Policy(base.BaseAPITest): l7policy_prov_status=constants.PENDING_CREATE, l7policy_op_status=constants.OFFLINE) + def test_create_with_redirect_http_code(self): + action_key_values = { + constants.L7POLICY_ACTION_REDIRECT_PREFIX: { + 'redirect_prefix': 'https://example.com', + 'redirect_http_code': 302}, + constants.L7POLICY_ACTION_REDIRECT_TO_URL: { + 'redirect_url': 'http://www.example.com', + 'redirect_http_code': 301}} + count = 1 + # First, test with redirect actions + for action in [constants.L7POLICY_ACTION_REDIRECT_TO_URL, + constants.L7POLICY_ACTION_REDIRECT_PREFIX]: + api_l7policy = self.create_l7policy( + self.listener_id, action, + **action_key_values[action]).get(self.root_tag) + self.assertEqual(action, api_l7policy['action']) + self.assertEqual(count, api_l7policy['position']) + self.assertIsNone(api_l7policy.get('redirect_pool_id')) + if api_l7policy.get('redirect_url'): + self.assertEqual(action_key_values[action]['redirect_url'], + api_l7policy['redirect_url']) + elif api_l7policy.get('redirect_prefix'): + self.assertEqual(action_key_values[action]['redirect_prefix'], + api_l7policy['redirect_prefix']) + self.assertEqual(action_key_values[action]['redirect_http_code'], + api_l7policy['redirect_http_code']) + self.assert_correct_status( + lb_id=self.lb_id, listener_id=self.listener_id, + l7policy_id=api_l7policy.get('id'), + lb_prov_status=constants.PENDING_UPDATE, + listener_prov_status=constants.PENDING_UPDATE, + l7policy_prov_status=constants.PENDING_CREATE, + l7policy_op_status=constants.OFFLINE) + self.set_lb_status(self.lb_id) + count += 1 + + # test with redirect_pool action + api_l7policy = self.create_l7policy( + self.listener_id, constants.L7POLICY_ACTION_REDIRECT_TO_POOL, + redirect_pool_id=self.pool_id, + redirect_http_code=308).get(self.root_tag) + self.assertEqual(constants.L7POLICY_ACTION_REDIRECT_TO_POOL, + api_l7policy['action']) + self.assertEqual(self.pool_id, api_l7policy.get('redirect_pool_id')) + self.assertIsNone(api_l7policy.get('redirect_url')) + self.assertIsNone(api_l7policy.get('redirect_prefix')) + self.assertIsNone(api_l7policy.get('redirect_http_code')) + self.assert_correct_status( + lb_id=self.lb_id, listener_id=self.listener_id, + l7policy_id=api_l7policy.get('id'), + lb_prov_status=constants.PENDING_UPDATE, + listener_prov_status=constants.PENDING_UPDATE, + l7policy_prov_status=constants.PENDING_CREATE, + l7policy_op_status=constants.OFFLINE) + def test_bad_create(self): l7policy = {'listener_id': self.listener_id, 'name': 'test1'} @@ -736,6 +791,15 @@ class TestL7Policy(base.BaseAPITest): 'redirect_url': 'bad url'} self.post(self.L7POLICIES_PATH, self._build_body(l7policy), status=400) + def test_bad_create_with_redirect_http_code(self): + for test_code in [1, '', 'HTTPCODE']: + l7policy = {'listener_id': self.listener_id, + 'action': constants.L7POLICY_ACTION_REDIRECT_TO_URL, + 'redirect_url': 'http://www.example.com', + 'redirect_http_code': test_code} + self.post(self.L7POLICIES_PATH, self._build_body(l7policy), + status=400) + @mock.patch('octavia.api.drivers.utils.call_provider') def test_create_with_bad_provider(self, mock_provider): mock_provider.side_effect = exceptions.ProviderDriverError( @@ -958,6 +1022,61 @@ class TestL7Policy(base.BaseAPITest): self.put(self.L7POLICY_PATH.format(l7policy_id=api_l7policy.get('id')), self._build_body(new_l7policy)) + def test_update_with_redirect_http_code(self): + # test from non exist + api_l7policy = self.create_l7policy(self.listener_id, + constants.L7POLICY_ACTION_REJECT, + ).get(self.root_tag) + self.set_lb_status(self.lb_id) + new_l7policy = { + 'action': constants.L7POLICY_ACTION_REDIRECT_TO_URL, + 'redirect_url': 'http://www.example.com', + 'redirect_http_code': 308} + response = self.put(self.L7POLICY_PATH.format( + l7policy_id=api_l7policy.get('id')), + self._build_body(new_l7policy)).json.get(self.root_tag) + self.assertEqual(constants.L7POLICY_ACTION_REDIRECT_TO_URL, + response.get('action')) + self.assertEqual(308, response.get('redirect_http_code')) + self.set_lb_status(self.lb_id) + + # test from exist to new + api_l7policy = self.create_l7policy( + self.listener_id, constants.L7POLICY_ACTION_REDIRECT_TO_URL, + redirect_url='http://www.example.com', + redirect_http_code=302).get(self.root_tag) + self.set_lb_status(self.lb_id) + new_l7policy = { + 'redirect_http_code': 308} + response = self.put(self.L7POLICY_PATH.format( + l7policy_id=api_l7policy.get('id')), + self._build_body(new_l7policy)).json.get(self.root_tag) + self.assertEqual(constants.L7POLICY_ACTION_REDIRECT_TO_URL, + response.get('action')) + self.assertEqual(308, response.get('redirect_http_code')) + self.set_lb_status(self.lb_id) + + # test from exist to null + new_l7policy = { + 'redirect_http_code': None} + response = self.put(self.L7POLICY_PATH.format( + l7policy_id=api_l7policy.get('id')), + self._build_body(new_l7policy)).json.get(self.root_tag) + self.assertIsNone(response.get('redirect_http_code')) + + def test_bad_update_with_redirect_http_code(self): + api_l7policy = self.create_l7policy(self.listener_id, + constants.L7POLICY_ACTION_REJECT, + ).get(self.root_tag) + self.set_lb_status(self.lb_id) + new_l7policy = { + 'action': constants.L7POLICY_ACTION_REDIRECT_TO_URL, + 'redirect_url': 'http://www.example.com', + 'redirect_http_code': ''} + self.put(self.L7POLICY_PATH.format( + l7policy_id=api_l7policy.get('id')), + self._build_body(new_l7policy), status=400).json.get(self.root_tag) + def test_delete(self): api_l7policy = self.create_l7policy( self.listener_id, diff --git a/octavia/tests/functional/api/v2/test_load_balancer.py b/octavia/tests/functional/api/v2/test_load_balancer.py index bdd13aee81..23559d5e34 100644 --- a/octavia/tests/functional/api/v2/test_load_balancer.py +++ b/octavia/tests/functional/api/v2/test_load_balancer.py @@ -2523,12 +2523,14 @@ class TestLoadBalancerGraph(base.BaseAPITest): 'action': constants.L7POLICY_ACTION_REDIRECT_TO_URL, 'redirect_url': 'http://127.0.0.1/', 'position': 1, + 'redirect_http_code': 302, 'admin_state_up': False } create_l7policies.append(create_l7policy) expected_l7policy = { 'name': '', 'description': '', + 'redirect_http_code': None, 'redirect_url': None, 'redirect_prefix': None, 'rules': [], diff --git a/octavia/tests/unit/api/drivers/sample_data_models.py b/octavia/tests/unit/api/drivers/sample_data_models.py index 60edfd50ef..6a2375a20a 100644 --- a/octavia/tests/unit/api/drivers/sample_data_models.py +++ b/octavia/tests/unit/api/drivers/sample_data_models.py @@ -337,7 +337,8 @@ class SampleDriverDataModels(object): 'position': 1, 'listener': None, 'redirect_pool': None, - 'l7rules': self.test_l7rules} + 'l7rules': self.test_l7rules, + 'redirect_http_code': 302} self.test_l7policy1_dict.update(self._common_test_dict) @@ -367,7 +368,8 @@ class SampleDriverDataModels(object): 'redirect_pool_id': self.pool1_id, 'redirect_url': '/index.html', 'redirect_prefix': 'https://example.com/', - 'rules': self.provider_l7rules_dicts + 'rules': self.provider_l7rules_dicts, + 'redirect_http_code': 302 } self.provider_l7policy2_dict = copy.deepcopy( diff --git a/octavia/tests/unit/common/jinja/haproxy/test_jinja_cfg.py b/octavia/tests/unit/common/jinja/haproxy/test_jinja_cfg.py index 92eae4e64e..361c841e8f 100644 --- a/octavia/tests/unit/common/jinja/haproxy/test_jinja_cfg.py +++ b/octavia/tests/unit/common/jinja/haproxy/test_jinja_cfg.py @@ -612,7 +612,7 @@ class TestHaproxyCfg(base.TestCase): "This\\ string\\\\\\ with\\ stuff\n" " acl sample_l7rule_id_3 req.cook(some-cookie) -m reg " "this.*|that\n" - " redirect location http://www.example.com if " + " redirect code 302 location http://www.example.com if " "!sample_l7rule_id_2 sample_l7rule_id_3\n" " acl sample_l7rule_id_4 path_end -m str jpg\n" " acl sample_l7rule_id_5 req.hdr(host) -i -m end " @@ -623,7 +623,7 @@ class TestHaproxyCfg(base.TestCase): "This\\ string\\\\\\ with\\ stuff\n" " acl sample_l7rule_id_3 req.cook(some-cookie) -m reg " "this.*|that\n" - " redirect prefix https://example.com if " + " redirect code 302 prefix https://example.com if " "!sample_l7rule_id_2 sample_l7rule_id_3\n" " default_backend sample_pool_id_1\n" " timeout client 50000\n\n").format( @@ -901,12 +901,18 @@ class TestHaproxyCfg(base.TestCase): ret = self.jinja_cfg._transform_l7policy(in_l7policy, {}) self.assertEqual(sample_configs.RET_L7POLICY_1, ret) - def test_transform_l7policy_2(self): + def test_transform_l7policy_2_8(self): in_l7policy = sample_configs.sample_l7policy_tuple( 'sample_l7policy_id_2', sample_policy=2) ret = self.jinja_cfg._transform_l7policy(in_l7policy, {}) self.assertEqual(sample_configs.RET_L7POLICY_2, ret) + # test invalid action without redirect_http_code + in_l7policy = sample_configs.sample_l7policy_tuple( + 'sample_l7policy_id_8', sample_policy=2, redirect_http_code=None) + ret = self.jinja_cfg._transform_l7policy(in_l7policy, {}) + self.assertEqual(sample_configs.RET_L7POLICY_8, ret) + def test_transform_l7policy_disabled_rule(self): in_l7policy = sample_configs.sample_l7policy_tuple( 'sample_l7policy_id_6', sample_policy=6) @@ -1046,7 +1052,7 @@ class TestHaproxyCfg(base.TestCase): "This\\ string\\\\\\ with\\ stuff\n" " acl sample_l7rule_id_3 req.cook(some-cookie) -m reg " "this.*|that\n" - " redirect location http://www.example.com " + " redirect code 302 location http://www.example.com " "if !sample_l7rule_id_2 sample_l7rule_id_3\n" " acl sample_l7rule_id_4 path_end -m str jpg\n" " acl sample_l7rule_id_5 req.hdr(host) -i -m end " @@ -1057,7 +1063,7 @@ class TestHaproxyCfg(base.TestCase): "This\\ string\\\\\\ with\\ stuff\n" " acl sample_l7rule_id_3 req.cook(some-cookie) -m reg " "this.*|that\n" - " redirect prefix https://example.com " + " redirect code 302 prefix https://example.com " "if !sample_l7rule_id_2 sample_l7rule_id_3\n" " acl sample_l7rule_id_7 ssl_c_used\n" " acl sample_l7rule_id_8 ssl_c_verify eq 1\n" @@ -1066,7 +1072,8 @@ class TestHaproxyCfg(base.TestCase): " acl sample_l7rule_id_10 ssl_c_s_dn(OU-3) -m beg " "Orgnization\\ Bala\n" " acl sample_l7rule_id_11 path -m beg /api\n" - " redirect location http://www.ssl-type-l7rule-test.com " + " redirect code 302 location " + "http://www.ssl-type-l7rule-test.com " "if sample_l7rule_id_7 !sample_l7rule_id_8 !sample_l7rule_id_9 " "!sample_l7rule_id_10 sample_l7rule_id_11\n" " default_backend sample_pool_id_1\n" diff --git a/octavia/tests/unit/common/sample_configs/sample_configs.py b/octavia/tests/unit/common/sample_configs/sample_configs.py index 47be649c11..93bd733a29 100644 --- a/octavia/tests/unit/common/sample_configs/sample_configs.py +++ b/octavia/tests/unit/common/sample_configs/sample_configs.py @@ -208,7 +208,8 @@ RET_L7POLICY_1 = { 'redirect_url': None, 'redirect_prefix': None, 'enabled': True, - 'l7rules': [RET_L7RULE_1]} + 'l7rules': [RET_L7RULE_1], + 'redirect_http_code': None} RET_L7POLICY_2 = { 'id': 'sample_l7policy_id_2', @@ -217,7 +218,8 @@ RET_L7POLICY_2 = { 'redirect_url': 'http://www.example.com', 'redirect_prefix': None, 'enabled': True, - 'l7rules': [RET_L7RULE_2, RET_L7RULE_3]} + 'l7rules': [RET_L7RULE_2, RET_L7RULE_3], + 'redirect_http_code': 302} RET_L7POLICY_3 = { 'id': 'sample_l7policy_id_3', @@ -226,7 +228,8 @@ RET_L7POLICY_3 = { 'redirect_url': None, 'redirect_prefix': None, 'enabled': True, - 'l7rules': [RET_L7RULE_4, RET_L7RULE_5]} + 'l7rules': [RET_L7RULE_4, RET_L7RULE_5], + 'redirect_http_code': None} RET_L7POLICY_4 = { 'id': 'sample_l7policy_id_4', @@ -235,7 +238,8 @@ RET_L7POLICY_4 = { 'redirect_url': None, 'redirect_prefix': None, 'enabled': True, - 'l7rules': []} + 'l7rules': [], + 'redirect_http_code': None} RET_L7POLICY_5 = { 'id': 'sample_l7policy_id_5', @@ -244,7 +248,8 @@ RET_L7POLICY_5 = { 'redirect_url': None, 'redirect_prefix': None, 'enabled': False, - 'l7rules': [RET_L7RULE_5]} + 'l7rules': [RET_L7RULE_5], + 'redirect_http_code': None} RET_L7POLICY_6 = { 'id': 'sample_l7policy_id_6', @@ -253,7 +258,8 @@ RET_L7POLICY_6 = { 'redirect_url': None, 'redirect_prefix': None, 'enabled': True, - 'l7rules': []} + 'l7rules': [], + 'redirect_http_code': None} RET_L7POLICY_7 = { 'id': 'sample_l7policy_id_7', @@ -262,7 +268,18 @@ RET_L7POLICY_7 = { 'redirect_url': None, 'redirect_prefix': 'https://example.com', 'enabled': True, - 'l7rules': [RET_L7RULE_2, RET_L7RULE_3]} + 'l7rules': [RET_L7RULE_2, RET_L7RULE_3], + 'redirect_http_code': 302} + +RET_L7POLICY_8 = { + 'id': 'sample_l7policy_id_8', + 'action': constants.L7POLICY_ACTION_REDIRECT_TO_URL, + 'redirect_pool': None, + 'redirect_url': 'http://www.example.com', + 'redirect_prefix': None, + 'enabled': True, + 'l7rules': [RET_L7RULE_2, RET_L7RULE_3], + 'redirect_http_code': None} RET_LISTENER = { 'id': 'sample_listener_id_1', @@ -813,11 +830,13 @@ def sample_l7policy_tuple(id, action=constants.L7POLICY_ACTION_REJECT, redirect_pool=None, redirect_url=None, redirect_prefix=None, - enabled=True, sample_policy=1): + enabled=True, redirect_http_code=302, + sample_policy=1): in_l7policy = collections.namedtuple('l7policy', 'id, action, redirect_pool, ' 'redirect_url, redirect_prefix, ' - 'l7rules, enabled') + 'l7rules, enabled,' + 'redirect_http_code') l7rules = [] if sample_policy == 1: action = constants.L7POLICY_ACTION_REDIRECT_TO_POOL @@ -861,7 +880,11 @@ def sample_l7policy_tuple(id, redirect_url=redirect_url, redirect_prefix=redirect_prefix, l7rules=l7rules, - enabled=enabled) + enabled=enabled, + redirect_http_code=redirect_http_code + if (action in [constants.L7POLICY_ACTION_REDIRECT_TO_URL, + constants.L7POLICY_ACTION_REDIRECT_PREFIX] and + redirect_http_code) else None) def sample_l7rule_tuple(id, diff --git a/releasenotes/notes/support-redirect-http-code-1c2e87ef7fda12e97.yaml b/releasenotes/notes/support-redirect-http-code-1c2e87ef7fda12e97.yaml new file mode 100644 index 0000000000..ec23d53833 --- /dev/null +++ b/releasenotes/notes/support-redirect-http-code-1c2e87ef7fda12e97.yaml @@ -0,0 +1,6 @@ +--- +features: + - Now Octavia L7Policy API can accept an new option `redirect_http_code` + for L7Policy actions `REDIRECT_URL` or `REDIRECT_PREFIX`, then each HTTP + requests to the associated Listener will return the configured HTTP + response code.