From 108ab279524d323552d825387e34d5ef0520292b Mon Sep 17 00:00:00 2001 From: johnsom Date: Wed, 8 Feb 2017 11:21:52 -0800 Subject: [PATCH] Fix Octavia v1 API project_id for POST This patch corrects the project_id handling for POST calls to the Octavia v1 API. For load balancer create calls we use the specified project_id if the user is an admin or noauth is specified. If no project_id is specified in the request we use the project_id from the context. If no project_id can be found we raise an exception. For the other object POST methods we use the project_id from the parent load balancer. Change-Id: Ibf59541b8811e3bbe36cfec039f91e20036102e4 Closes-Bug: #1624145 --- octavia/api/v1/controllers/base.py | 6 ++++++ octavia/api/v1/controllers/health_monitor.py | 3 +++ octavia/api/v1/controllers/l7policy.py | 1 + octavia/api/v1/controllers/l7rule.py | 1 + octavia/api/v1/controllers/listener.py | 3 +++ octavia/api/v1/controllers/load_balancer.py | 14 +++++++++++++- octavia/api/v1/controllers/member.py | 4 ++++ octavia/api/v1/controllers/pool.py | 3 +++ octavia/api/v1/controllers/quotas.py | 13 +++++++++++++ octavia/api/v1/types/health_monitor.py | 1 + octavia/api/v1/types/listener.py | 1 + octavia/api/v1/types/member.py | 1 + octavia/api/v1/types/pool.py | 1 + octavia/common/exceptions.py | 5 +++++ .../tests/functional/api/v1/test_health_monitor.py | 2 +- octavia/tests/functional/api/v1/test_listener.py | 1 + octavia/tests/functional/api/v1/test_member.py | 2 +- octavia/tests/functional/api/v1/test_pool.py | 2 +- .../api-create-project-id-4bb984b24d56de2e.yaml | 9 +++++++++ 19 files changed, 69 insertions(+), 4 deletions(-) create mode 100644 releasenotes/notes/api-create-project-id-4bb984b24d56de2e.yaml diff --git a/octavia/api/v1/controllers/base.py b/octavia/api/v1/controllers/base.py index 6d76883e1c..cf91687ba4 100644 --- a/octavia/api/v1/controllers/base.py +++ b/octavia/api/v1/controllers/base.py @@ -135,3 +135,9 @@ class BaseController(rest.RestController): if db_quotas.member is None: db_quotas.member = CONF.quotas.default_member_quota return db_quotas + + def _get_lb_project_id(self, session, id): + """Get the project_id of the load balancer from the database.""" + lb = self._get_db_obj(session, self.repositories.load_balancer, + data_models.LoadBalancer, id) + return lb.project_id diff --git a/octavia/api/v1/controllers/health_monitor.py b/octavia/api/v1/controllers/health_monitor.py index f73ca771d9..e26d5607c6 100644 --- a/octavia/api/v1/controllers/health_monitor.py +++ b/octavia/api/v1/controllers/health_monitor.py @@ -96,6 +96,9 @@ class HealthMonitorController(base.BaseController): """Creates a health monitor on a pool.""" context = pecan.request.context.get('octavia_context') + health_monitor.project_id = self._get_lb_project_id( + context.session, self.load_balancer_id) + try: db_hm = self.repositories.health_monitor.get( context.session, pool_id=self.pool_id) diff --git a/octavia/api/v1/controllers/l7policy.py b/octavia/api/v1/controllers/l7policy.py index b6804679fe..dfd84d3d31 100644 --- a/octavia/api/v1/controllers/l7policy.py +++ b/octavia/api/v1/controllers/l7policy.py @@ -77,6 +77,7 @@ class L7PolicyController(base.BaseController): def post(self, l7policy): """Creates a l7policy on a listener.""" context = pecan.request.context.get('octavia_context') + l7policy_dict = validate.sanitize_l7policy_api_args( l7policy.to_dict(render_unsets=True), create=True) # Make sure any pool specified by redirect_pool_id exists diff --git a/octavia/api/v1/controllers/l7rule.py b/octavia/api/v1/controllers/l7rule.py index 601fd547df..44716da68a 100644 --- a/octavia/api/v1/controllers/l7rule.py +++ b/octavia/api/v1/controllers/l7rule.py @@ -88,6 +88,7 @@ class L7RuleController(base.BaseController): except Exception as e: raise exceptions.L7RuleValidation(error=e) context = pecan.request.context.get('octavia_context') + self._check_l7policy_max_rules(context.session) l7rule_dict = db_prepare.create_l7rule( l7rule.to_dict(render_unsets=True), self.l7policy_id) diff --git a/octavia/api/v1/controllers/listener.py b/octavia/api/v1/controllers/listener.py index e9f050000a..911cc05c90 100644 --- a/octavia/api/v1/controllers/listener.py +++ b/octavia/api/v1/controllers/listener.py @@ -162,6 +162,9 @@ class ListenersController(base.BaseController): """Creates a listener on a load balancer.""" context = pecan.request.context.get('octavia_context') + listener.project_id = self._get_lb_project_id(context.session, + self.load_balancer_id) + lock_session = db_api.get_session(autocommit=False) if self.repositories.check_quota_met( context.session, diff --git a/octavia/api/v1/controllers/load_balancer.py b/octavia/api/v1/controllers/load_balancer.py index 70ec4026e5..7c429b77bd 100644 --- a/octavia/api/v1/controllers/load_balancer.py +++ b/octavia/api/v1/controllers/load_balancer.py @@ -13,6 +13,7 @@ # License for the specific language governing permissions and limitations # under the License. +from oslo_config import cfg from oslo_db import exception as odb_exceptions from oslo_log import log as logging from oslo_utils import excutils @@ -33,7 +34,7 @@ from octavia.db import api as db_api from octavia.db import prepare as db_prepare from octavia.i18n import _LI - +CONF = cfg.CONF LOG = logging.getLogger(__name__) @@ -103,6 +104,17 @@ class LoadBalancersController(base.BaseController): def post(self, load_balancer): """Creates a load balancer.""" context = pecan.request.context.get('octavia_context') + + project_id = context.project_id + if context.is_admin or CONF.auth_strategy == constants.NOAUTH: + if load_balancer.project_id: + project_id = load_balancer.project_id + + if not project_id: + raise exceptions.MissingAPIProjectID() + + load_balancer.project_id = project_id + # Validate the subnet id if load_balancer.vip.subnet_id: if not validate.subnet_exists(load_balancer.vip.subnet_id): diff --git a/octavia/api/v1/controllers/member.py b/octavia/api/v1/controllers/member.py index caf6206ad5..fed401874a 100644 --- a/octavia/api/v1/controllers/member.py +++ b/octavia/api/v1/controllers/member.py @@ -94,6 +94,10 @@ class MembersController(base.BaseController): def post(self, member): """Creates a pool member on a pool.""" context = pecan.request.context.get('octavia_context') + + member.project_id = self._get_lb_project_id(context.session, + self.load_balancer_id) + # Validate member subnet if member.subnet_id and not validate.subnet_exists(member.subnet_id): raise exceptions.NotFound(resource='Subnet', diff --git a/octavia/api/v1/controllers/pool.py b/octavia/api/v1/controllers/pool.py index 5152b0bef4..319e6a4275 100644 --- a/octavia/api/v1/controllers/pool.py +++ b/octavia/api/v1/controllers/pool.py @@ -134,6 +134,9 @@ class PoolsController(base.BaseController): # pool_dict: context = pecan.request.context.get('octavia_context') + pool.project_id = self._get_lb_project_id(context.session, + self.load_balancer_id) + lock_session = db_api.get_session(autocommit=False) if self.repositories.check_quota_met( context.session, diff --git a/octavia/api/v1/controllers/quotas.py b/octavia/api/v1/controllers/quotas.py index c60c2edfc4..a1923f213c 100644 --- a/octavia/api/v1/controllers/quotas.py +++ b/octavia/api/v1/controllers/quotas.py @@ -21,6 +21,8 @@ from wsmeext import pecan as wsme_pecan from octavia.api.v1.controllers import base from octavia.api.v1.types import quotas as quota_types +from octavia.common import constants +from octavia.common import exceptions CONF = cfg.CONF CONF.import_group('quotas', 'octavia.common.config') @@ -52,6 +54,17 @@ class QuotasController(base.BaseController): def put(self, project_id, quotas): """Update any or all quotas for a project.""" context = pecan.request.context.get('octavia_context') + + new_project_id = context.project_id + if context.is_admin or CONF.auth_strategy == constants.NOAUTH: + if project_id: + new_project_id = project_id + + if not new_project_id: + raise exceptions.MissingAPIProjectID() + + project_id = new_project_id + quotas_dict = quotas.to_dict() self.repositories.quotas.update(context.session, project_id, **quotas_dict) diff --git a/octavia/api/v1/types/health_monitor.py b/octavia/api/v1/types/health_monitor.py index 6a0ede51b6..7979ef397d 100644 --- a/octavia/api/v1/types/health_monitor.py +++ b/octavia/api/v1/types/health_monitor.py @@ -48,6 +48,7 @@ class HealthMonitorPOST(base.BaseType): expected_codes = wtypes.wsattr( wtypes.text, default=constants.HEALTH_MONITOR_DEFAULT_EXPECTED_CODES) enabled = wtypes.wsattr(bool, default=True) + # TODO(johnsom) Remove after deprecation (R series) project_id = wtypes.wsattr(wtypes.StringType(max_length=36)) diff --git a/octavia/api/v1/types/listener.py b/octavia/api/v1/types/listener.py index 7ed92e85e9..55e8d783a9 100644 --- a/octavia/api/v1/types/listener.py +++ b/octavia/api/v1/types/listener.py @@ -89,6 +89,7 @@ class ListenerPOST(base.BaseType): tls_certificate_id = wtypes.wsattr(wtypes.StringType(max_length=255)) tls_termination = wtypes.wsattr(TLSTermination) sni_containers = [wtypes.StringType(max_length=255)] + # TODO(johnsom) Remove after deprecation (R series) project_id = wtypes.wsattr(wtypes.StringType(max_length=36)) default_pool_id = wtypes.wsattr(wtypes.UuidType()) default_pool = wtypes.wsattr(pool.PoolPOST) diff --git a/octavia/api/v1/types/member.py b/octavia/api/v1/types/member.py index 38aa6f69a8..889bdb84b6 100644 --- a/octavia/api/v1/types/member.py +++ b/octavia/api/v1/types/member.py @@ -39,6 +39,7 @@ class MemberPOST(base.BaseType): protocol_port = wtypes.wsattr(wtypes.IntegerType(), mandatory=True) weight = wtypes.wsattr(wtypes.IntegerType(), default=1) subnet_id = wtypes.wsattr(wtypes.UuidType()) + # TODO(johnsom) Remove after deprecation (R series) project_id = wtypes.wsattr(wtypes.StringType(max_length=36)) diff --git a/octavia/api/v1/types/pool.py b/octavia/api/v1/types/pool.py index f101af21c0..762c51fcdb 100644 --- a/octavia/api/v1/types/pool.py +++ b/octavia/api/v1/types/pool.py @@ -97,6 +97,7 @@ class PoolPOST(base.BaseType): wtypes.Enum(str, *constants.SUPPORTED_LB_ALGORITHMS), mandatory=True) session_persistence = wtypes.wsattr(SessionPersistencePOST) + # TODO(johnsom) Remove after deprecation (R series) project_id = wtypes.wsattr(wtypes.StringType(max_length=36)) health_monitor = wtypes.wsattr(health_monitor.HealthMonitorPOST) members = wtypes.wsattr([member.MemberPOST]) diff --git a/octavia/common/exceptions.py b/octavia/common/exceptions.py index e4d660e93e..41ed9d1f4f 100644 --- a/octavia/common/exceptions.py +++ b/octavia/common/exceptions.py @@ -253,3 +253,8 @@ class ProjectBusyException(APIException): class MissingProjectID(OctaviaException): message = _('Missing project ID in request where one is required.') + + +class MissingAPIProjectID(APIException): + msg = _('Missing project ID in request where one is required.') + code = 400 diff --git a/octavia/tests/functional/api/v1/test_health_monitor.py b/octavia/tests/functional/api/v1/test_health_monitor.py index d6945b53f1..45fbab6714 100644 --- a/octavia/tests/functional/api/v1/test_health_monitor.py +++ b/octavia/tests/functional/api/v1/test_health_monitor.py @@ -117,7 +117,7 @@ class TestHealthMonitor(base.BaseAPITest): self.pool.get('id'), constants.HEALTH_MONITOR_HTTP, 1, 1, 1, 1, project_id=pid) - self.assertEqual(pid, api_hm.get('project_id')) + self.assertEqual(self.project_id, api_hm.get('project_id')) def test_create_over_quota(self): self.check_quota_met_true_mock.start() diff --git a/octavia/tests/functional/api/v1/test_listener.py b/octavia/tests/functional/api/v1/test_listener.py index 65cf897f82..cd2f1a683a 100644 --- a/octavia/tests/functional/api/v1/test_listener.py +++ b/octavia/tests/functional/api/v1/test_listener.py @@ -113,6 +113,7 @@ class TestListener(base.BaseAPITest): self.assertIn(sni, sni_ex) self.assertIsNotNone(listener_api.pop('created_at')) self.assertIsNone(listener_api.pop('updated_at')) + lb_listener['project_id'] = self.project_id self.assertEqual(lb_listener, listener_api) self.assert_correct_lb_status(self.lb.get('id'), constants.PENDING_UPDATE, diff --git a/octavia/tests/functional/api/v1/test_member.py b/octavia/tests/functional/api/v1/test_member.py index 5a41463717..6e864263db 100644 --- a/octavia/tests/functional/api/v1/test_member.py +++ b/octavia/tests/functional/api/v1/test_member.py @@ -123,7 +123,7 @@ class TestMember(base.BaseAPITest): api_member = self.create_member(self.lb.get('id'), self.pool.get('id'), '10.0.0.1', 80, project_id=pid) - self.assertEqual(pid, api_member.get('project_id')) + self.assertEqual(self.project_id, api_member.get('project_id')) def test_create_with_duplicate_id(self): member = self.create_member(self.lb.get('id'), diff --git a/octavia/tests/functional/api/v1/test_pool.py b/octavia/tests/functional/api/v1/test_pool.py index 2b06167008..a754ba86c0 100644 --- a/octavia/tests/functional/api/v1/test_pool.py +++ b/octavia/tests/functional/api/v1/test_pool.py @@ -166,7 +166,7 @@ class TestPool(base.BaseAPITest): constants.PROTOCOL_HTTP, constants.LB_ALGORITHM_ROUND_ROBIN, project_id=pid) - self.assertEqual(pid, api_pool.get('project_id')) + self.assertEqual(self.project_id, api_pool.get('project_id')) def test_create_with_duplicate_id(self): pool = self.create_pool(self.lb.get('id'), diff --git a/releasenotes/notes/api-create-project-id-4bb984b24d56de2e.yaml b/releasenotes/notes/api-create-project-id-4bb984b24d56de2e.yaml new file mode 100644 index 0000000000..fa4c9b85ce --- /dev/null +++ b/releasenotes/notes/api-create-project-id-4bb984b24d56de2e.yaml @@ -0,0 +1,9 @@ +--- +deprecations: + - | + The project_id attribute of the POST method on the following objects + is now deprecated\: listener, pool, health monitor, and member. + These objects will use the parent load balancer's project_id. + Values passed into the project_id on those objects will be ignored + until the deprecation cycle has expired, at which point they will + cause an error.