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
This commit is contained in:
johnsom 2017-02-08 11:21:52 -08:00
parent 9cd1bab382
commit 108ab27952
19 changed files with 69 additions and 4 deletions

View File

@ -135,3 +135,9 @@ class BaseController(rest.RestController):
if db_quotas.member is None: if db_quotas.member is None:
db_quotas.member = CONF.quotas.default_member_quota db_quotas.member = CONF.quotas.default_member_quota
return db_quotas 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

View File

@ -96,6 +96,9 @@ class HealthMonitorController(base.BaseController):
"""Creates a health monitor on a pool.""" """Creates a health monitor on a pool."""
context = pecan.request.context.get('octavia_context') context = pecan.request.context.get('octavia_context')
health_monitor.project_id = self._get_lb_project_id(
context.session, self.load_balancer_id)
try: try:
db_hm = self.repositories.health_monitor.get( db_hm = self.repositories.health_monitor.get(
context.session, pool_id=self.pool_id) context.session, pool_id=self.pool_id)

View File

@ -77,6 +77,7 @@ class L7PolicyController(base.BaseController):
def post(self, l7policy): def post(self, l7policy):
"""Creates a l7policy on a listener.""" """Creates a l7policy on a listener."""
context = pecan.request.context.get('octavia_context') context = pecan.request.context.get('octavia_context')
l7policy_dict = validate.sanitize_l7policy_api_args( l7policy_dict = validate.sanitize_l7policy_api_args(
l7policy.to_dict(render_unsets=True), create=True) l7policy.to_dict(render_unsets=True), create=True)
# Make sure any pool specified by redirect_pool_id exists # Make sure any pool specified by redirect_pool_id exists

View File

@ -88,6 +88,7 @@ class L7RuleController(base.BaseController):
except Exception as e: except Exception as e:
raise exceptions.L7RuleValidation(error=e) raise exceptions.L7RuleValidation(error=e)
context = pecan.request.context.get('octavia_context') context = pecan.request.context.get('octavia_context')
self._check_l7policy_max_rules(context.session) self._check_l7policy_max_rules(context.session)
l7rule_dict = db_prepare.create_l7rule( l7rule_dict = db_prepare.create_l7rule(
l7rule.to_dict(render_unsets=True), self.l7policy_id) l7rule.to_dict(render_unsets=True), self.l7policy_id)

View File

@ -162,6 +162,9 @@ class ListenersController(base.BaseController):
"""Creates a listener on a load balancer.""" """Creates a listener on a load balancer."""
context = pecan.request.context.get('octavia_context') 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) lock_session = db_api.get_session(autocommit=False)
if self.repositories.check_quota_met( if self.repositories.check_quota_met(
context.session, context.session,

View File

@ -13,6 +13,7 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
from oslo_config import cfg
from oslo_db import exception as odb_exceptions from oslo_db import exception as odb_exceptions
from oslo_log import log as logging from oslo_log import log as logging
from oslo_utils import excutils 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.db import prepare as db_prepare
from octavia.i18n import _LI from octavia.i18n import _LI
CONF = cfg.CONF
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
@ -103,6 +104,17 @@ class LoadBalancersController(base.BaseController):
def post(self, load_balancer): def post(self, load_balancer):
"""Creates a load balancer.""" """Creates a load balancer."""
context = pecan.request.context.get('octavia_context') 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 # Validate the subnet id
if load_balancer.vip.subnet_id: if load_balancer.vip.subnet_id:
if not validate.subnet_exists(load_balancer.vip.subnet_id): if not validate.subnet_exists(load_balancer.vip.subnet_id):

View File

@ -94,6 +94,10 @@ class MembersController(base.BaseController):
def post(self, member): def post(self, member):
"""Creates a pool member on a pool.""" """Creates a pool member on a pool."""
context = pecan.request.context.get('octavia_context') context = pecan.request.context.get('octavia_context')
member.project_id = self._get_lb_project_id(context.session,
self.load_balancer_id)
# Validate member subnet # Validate member subnet
if member.subnet_id and not validate.subnet_exists(member.subnet_id): if member.subnet_id and not validate.subnet_exists(member.subnet_id):
raise exceptions.NotFound(resource='Subnet', raise exceptions.NotFound(resource='Subnet',

View File

@ -134,6 +134,9 @@ class PoolsController(base.BaseController):
# pool_dict: # pool_dict:
context = pecan.request.context.get('octavia_context') 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) lock_session = db_api.get_session(autocommit=False)
if self.repositories.check_quota_met( if self.repositories.check_quota_met(
context.session, context.session,

View File

@ -21,6 +21,8 @@ from wsmeext import pecan as wsme_pecan
from octavia.api.v1.controllers import base from octavia.api.v1.controllers import base
from octavia.api.v1.types import quotas as quota_types from octavia.api.v1.types import quotas as quota_types
from octavia.common import constants
from octavia.common import exceptions
CONF = cfg.CONF CONF = cfg.CONF
CONF.import_group('quotas', 'octavia.common.config') CONF.import_group('quotas', 'octavia.common.config')
@ -52,6 +54,17 @@ class QuotasController(base.BaseController):
def put(self, project_id, quotas): def put(self, project_id, quotas):
"""Update any or all quotas for a project.""" """Update any or all quotas for a project."""
context = pecan.request.context.get('octavia_context') 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() quotas_dict = quotas.to_dict()
self.repositories.quotas.update(context.session, project_id, self.repositories.quotas.update(context.session, project_id,
**quotas_dict) **quotas_dict)

View File

@ -48,6 +48,7 @@ class HealthMonitorPOST(base.BaseType):
expected_codes = wtypes.wsattr( expected_codes = wtypes.wsattr(
wtypes.text, default=constants.HEALTH_MONITOR_DEFAULT_EXPECTED_CODES) wtypes.text, default=constants.HEALTH_MONITOR_DEFAULT_EXPECTED_CODES)
enabled = wtypes.wsattr(bool, default=True) enabled = wtypes.wsattr(bool, default=True)
# TODO(johnsom) Remove after deprecation (R series)
project_id = wtypes.wsattr(wtypes.StringType(max_length=36)) project_id = wtypes.wsattr(wtypes.StringType(max_length=36))

View File

@ -89,6 +89,7 @@ class ListenerPOST(base.BaseType):
tls_certificate_id = wtypes.wsattr(wtypes.StringType(max_length=255)) tls_certificate_id = wtypes.wsattr(wtypes.StringType(max_length=255))
tls_termination = wtypes.wsattr(TLSTermination) tls_termination = wtypes.wsattr(TLSTermination)
sni_containers = [wtypes.StringType(max_length=255)] sni_containers = [wtypes.StringType(max_length=255)]
# TODO(johnsom) Remove after deprecation (R series)
project_id = wtypes.wsattr(wtypes.StringType(max_length=36)) project_id = wtypes.wsattr(wtypes.StringType(max_length=36))
default_pool_id = wtypes.wsattr(wtypes.UuidType()) default_pool_id = wtypes.wsattr(wtypes.UuidType())
default_pool = wtypes.wsattr(pool.PoolPOST) default_pool = wtypes.wsattr(pool.PoolPOST)

View File

@ -39,6 +39,7 @@ class MemberPOST(base.BaseType):
protocol_port = wtypes.wsattr(wtypes.IntegerType(), mandatory=True) protocol_port = wtypes.wsattr(wtypes.IntegerType(), mandatory=True)
weight = wtypes.wsattr(wtypes.IntegerType(), default=1) weight = wtypes.wsattr(wtypes.IntegerType(), default=1)
subnet_id = wtypes.wsattr(wtypes.UuidType()) subnet_id = wtypes.wsattr(wtypes.UuidType())
# TODO(johnsom) Remove after deprecation (R series)
project_id = wtypes.wsattr(wtypes.StringType(max_length=36)) project_id = wtypes.wsattr(wtypes.StringType(max_length=36))

View File

@ -97,6 +97,7 @@ class PoolPOST(base.BaseType):
wtypes.Enum(str, *constants.SUPPORTED_LB_ALGORITHMS), wtypes.Enum(str, *constants.SUPPORTED_LB_ALGORITHMS),
mandatory=True) mandatory=True)
session_persistence = wtypes.wsattr(SessionPersistencePOST) session_persistence = wtypes.wsattr(SessionPersistencePOST)
# TODO(johnsom) Remove after deprecation (R series)
project_id = wtypes.wsattr(wtypes.StringType(max_length=36)) project_id = wtypes.wsattr(wtypes.StringType(max_length=36))
health_monitor = wtypes.wsattr(health_monitor.HealthMonitorPOST) health_monitor = wtypes.wsattr(health_monitor.HealthMonitorPOST)
members = wtypes.wsattr([member.MemberPOST]) members = wtypes.wsattr([member.MemberPOST])

View File

@ -253,3 +253,8 @@ class ProjectBusyException(APIException):
class MissingProjectID(OctaviaException): class MissingProjectID(OctaviaException):
message = _('Missing project ID in request where one is required.') 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

View File

@ -117,7 +117,7 @@ class TestHealthMonitor(base.BaseAPITest):
self.pool.get('id'), self.pool.get('id'),
constants.HEALTH_MONITOR_HTTP, constants.HEALTH_MONITOR_HTTP,
1, 1, 1, 1, project_id=pid) 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): def test_create_over_quota(self):
self.check_quota_met_true_mock.start() self.check_quota_met_true_mock.start()

View File

@ -113,6 +113,7 @@ class TestListener(base.BaseAPITest):
self.assertIn(sni, sni_ex) self.assertIn(sni, sni_ex)
self.assertIsNotNone(listener_api.pop('created_at')) self.assertIsNotNone(listener_api.pop('created_at'))
self.assertIsNone(listener_api.pop('updated_at')) self.assertIsNone(listener_api.pop('updated_at'))
lb_listener['project_id'] = self.project_id
self.assertEqual(lb_listener, listener_api) self.assertEqual(lb_listener, listener_api)
self.assert_correct_lb_status(self.lb.get('id'), self.assert_correct_lb_status(self.lb.get('id'),
constants.PENDING_UPDATE, constants.PENDING_UPDATE,

View File

@ -123,7 +123,7 @@ class TestMember(base.BaseAPITest):
api_member = self.create_member(self.lb.get('id'), api_member = self.create_member(self.lb.get('id'),
self.pool.get('id'), self.pool.get('id'),
'10.0.0.1', 80, project_id=pid) '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): def test_create_with_duplicate_id(self):
member = self.create_member(self.lb.get('id'), member = self.create_member(self.lb.get('id'),

View File

@ -166,7 +166,7 @@ class TestPool(base.BaseAPITest):
constants.PROTOCOL_HTTP, constants.PROTOCOL_HTTP,
constants.LB_ALGORITHM_ROUND_ROBIN, constants.LB_ALGORITHM_ROUND_ROBIN,
project_id=pid) 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): def test_create_with_duplicate_id(self):
pool = self.create_pool(self.lb.get('id'), pool = self.create_pool(self.lb.get('id'),

View File

@ -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.