Add provider driver capabilities API
This patch adds an API that allows operators to query a provider driver for the list of supported flavor capabilities. Change-Id: Ia3d62acdc3b1af2e666f58d32a06d2238706dee6
This commit is contained in:
parent
0b1fe6a526
commit
1afeeb95d3
@ -67,6 +67,12 @@ path-project-id:
|
||||
in: path
|
||||
required: true
|
||||
type: string
|
||||
path-provider:
|
||||
description: |
|
||||
The provider to query.
|
||||
in: path
|
||||
required: true
|
||||
type: string
|
||||
###############################################################################
|
||||
# Query fields
|
||||
###############################################################################
|
||||
@ -335,6 +341,24 @@ flavor:
|
||||
in: body
|
||||
required: true
|
||||
type: object
|
||||
flavor-capabilities:
|
||||
description: |
|
||||
The provider flavor capabilities dictonary object.
|
||||
in: body
|
||||
required: true
|
||||
type: object
|
||||
flavor-capability-description:
|
||||
description: |
|
||||
The provider flavor capability description.
|
||||
in: body
|
||||
required: true
|
||||
type: string
|
||||
flavor-capability-name:
|
||||
description: |
|
||||
The provider flavor capability name.
|
||||
in: body
|
||||
required: true
|
||||
type: string
|
||||
flavor-data:
|
||||
description: |
|
||||
The JSON string containing the flavor metadata.
|
||||
|
@ -0,0 +1 @@
|
||||
curl -X GET -H "X-Auth-Token: <token>" http://198.51.100.10:9876/v2/lbaas/providers/amphora/flavor_capabilities
|
@ -0,0 +1,8 @@
|
||||
{
|
||||
"flavor_capabilities": [
|
||||
{
|
||||
"name": "loadbalancer_topology",
|
||||
"description": "The load balancer topology. One of: SINGLE - One amphora per load balancer. ACTIVE_STANDBY - Two amphora per load balancer."
|
||||
}
|
||||
]
|
||||
}
|
@ -49,3 +49,57 @@ Response Example
|
||||
|
||||
.. literalinclude:: examples/provider-list-response.json
|
||||
:language: javascript
|
||||
|
||||
Show Provider Flavor Capabilities
|
||||
=================================
|
||||
|
||||
.. rest_method:: GET /v2/lbaas/providers/{provider}/flavor_capabilities
|
||||
|
||||
Shows the provider driver flavor capabilities. These are the features of the
|
||||
provider driver that can be configured in an Octavia flavor. This API returns
|
||||
a list of dictionaries with the name and description of each flavor capability
|
||||
of the provider.
|
||||
|
||||
The list might be empty and a provider driver may not implement this feature.
|
||||
|
||||
**New in version 2.6**
|
||||
|
||||
.. rest_status_code:: success ../http-status.yaml
|
||||
|
||||
- 200
|
||||
|
||||
.. rest_status_code:: error ../http-status.yaml
|
||||
|
||||
- 400
|
||||
- 401
|
||||
- 403
|
||||
- 500
|
||||
|
||||
Request
|
||||
-------
|
||||
|
||||
.. rest_parameters:: ../parameters.yaml
|
||||
|
||||
- fields: fields
|
||||
- provider: path-provider
|
||||
|
||||
Curl Example
|
||||
------------
|
||||
|
||||
.. literalinclude:: examples/provider-flavor-capability-show-curl
|
||||
:language: bash
|
||||
|
||||
Response Parameters
|
||||
-------------------
|
||||
|
||||
.. rest_parameters:: ../parameters.yaml
|
||||
|
||||
- flavor_capabilities: flavor-capabilities
|
||||
- name: flavor-capability-name
|
||||
- description: flavor-capability-description
|
||||
|
||||
Response Example
|
||||
----------------
|
||||
|
||||
.. literalinclude:: examples/provider-flavor-capability-show-response.json
|
||||
:language: javascript
|
||||
|
@ -13,16 +13,21 @@
|
||||
# under the License.
|
||||
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log as logging
|
||||
import pecan
|
||||
import six
|
||||
from wsme import types as wtypes
|
||||
from wsmeext import pecan as wsme_pecan
|
||||
|
||||
from octavia.api.drivers import driver_factory
|
||||
from octavia.api.drivers import exceptions as driver_except
|
||||
from octavia.api.v2.controllers import base
|
||||
from octavia.api.v2.types import provider as provider_types
|
||||
from octavia.common import constants
|
||||
from octavia.common import exceptions
|
||||
|
||||
CONF = cfg.CONF
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ProviderController(base.BaseController):
|
||||
@ -48,3 +53,46 @@ class ProviderController(base.BaseController):
|
||||
if fields is not None:
|
||||
response_list = self._filter_fields(response_list, fields)
|
||||
return provider_types.ProvidersRootResponse(providers=response_list)
|
||||
|
||||
@pecan.expose()
|
||||
def _lookup(self, provider, *remainder):
|
||||
"""Overridden pecan _lookup method for custom routing.
|
||||
|
||||
Currently it checks if this was a flavor capabilities request and
|
||||
routes the request to the FlavorCapabilitiesController.
|
||||
"""
|
||||
if provider and remainder and remainder[0] == 'flavor_capabilities':
|
||||
return (FlavorCapabilitiesController(provider=provider),
|
||||
remainder[1:])
|
||||
return None
|
||||
|
||||
|
||||
class FlavorCapabilitiesController(base.BaseController):
|
||||
RBAC_TYPE = constants.RBAC_PROVIDER_FLAVOR
|
||||
|
||||
def __init__(self, provider):
|
||||
super(FlavorCapabilitiesController, self).__init__()
|
||||
self.provider = provider
|
||||
|
||||
@wsme_pecan.wsexpose(provider_types.FlavorCapabilitiesResponse,
|
||||
[wtypes.text], ignore_extra_args=True,
|
||||
status_code=200)
|
||||
def get_all(self, fields=None):
|
||||
context = pecan.request.context.get('octavia_context')
|
||||
self._auth_validate_action(context, context.project_id,
|
||||
constants.RBAC_GET_ALL)
|
||||
self.driver = driver_factory.get_driver(self.provider)
|
||||
try:
|
||||
metadata_dict = self.driver.get_supported_flavor_metadata()
|
||||
except driver_except.NotImplementedError as e:
|
||||
LOG.warning('Provider %s get_supported_flavor_metadata() '
|
||||
'reported: %s', self.provider, e.operator_fault_string)
|
||||
raise exceptions.ProviderNotImplementedError(
|
||||
prov=self.provider, user_msg=e.user_fault_string)
|
||||
response_list = [
|
||||
provider_types.ProviderResponse(name=key, description=value) for
|
||||
key, value in six.iteritems(metadata_dict)]
|
||||
if fields is not None:
|
||||
response_list = self._filter_fields(response_list, fields)
|
||||
return provider_types.FlavorCapabilitiesResponse(
|
||||
flavor_capabilities=response_list)
|
||||
|
@ -24,3 +24,7 @@ class ProviderResponse(types.BaseType):
|
||||
|
||||
class ProvidersRootResponse(types.BaseType):
|
||||
providers = wtypes.wsattr([ProviderResponse])
|
||||
|
||||
|
||||
class FlavorCapabilitiesResponse(types.BaseType):
|
||||
flavor_capabilities = wtypes.wsattr([ProviderResponse])
|
||||
|
@ -534,6 +534,7 @@ RBAC_L7RULE = '{}:l7rule:'.format(LOADBALANCER_API)
|
||||
RBAC_QUOTA = '{}:quota:'.format(LOADBALANCER_API)
|
||||
RBAC_AMPHORA = '{}:amphora:'.format(LOADBALANCER_API)
|
||||
RBAC_PROVIDER = '{}:provider:'.format(LOADBALANCER_API)
|
||||
RBAC_PROVIDER_FLAVOR = '{}:provider-flavor:'.format(LOADBALANCER_API)
|
||||
RBAC_FLAVOR = '{}:flavor:'.format(LOADBALANCER_API)
|
||||
RBAC_FLAVOR_PROFILE = '{}:flavor-profile:'.format(LOADBALANCER_API)
|
||||
RBAC_POST = 'post'
|
||||
|
@ -25,6 +25,7 @@ from octavia.policies import loadbalancer
|
||||
from octavia.policies import member
|
||||
from octavia.policies import pool
|
||||
from octavia.policies import provider
|
||||
from octavia.policies import provider_flavor
|
||||
from octavia.policies import quota
|
||||
|
||||
|
||||
@ -43,4 +44,5 @@ def list_rules():
|
||||
provider.list_rules(),
|
||||
quota.list_rules(),
|
||||
amphora.list_rules(),
|
||||
provider_flavor.list_rules(),
|
||||
)
|
||||
|
31
octavia/policies/provider_flavor.py
Normal file
31
octavia/policies/provider_flavor.py
Normal file
@ -0,0 +1,31 @@
|
||||
# Copyright 2018 Rackspace, US 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 oslo_policy import policy
|
||||
|
||||
from octavia.common import constants
|
||||
|
||||
rules = [
|
||||
policy.DocumentedRuleDefault(
|
||||
'{rbac_obj}{action}'.format(rbac_obj=constants.RBAC_PROVIDER_FLAVOR,
|
||||
action=constants.RBAC_GET_ALL),
|
||||
constants.RULE_API_ADMIN,
|
||||
"List the provider flavor capabilities.",
|
||||
[{'method': 'GET',
|
||||
'path': '/v2/lbaas/providers/{provider}/capabilities'}]
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
def list_rules():
|
||||
return rules
|
@ -79,6 +79,8 @@ class BaseAPITest(base_db_test.OctaviaDBTestBase):
|
||||
AMPHORA_STATS_PATH = AMPHORA_PATH + '/stats'
|
||||
|
||||
PROVIDERS_PATH = '/lbaas/providers'
|
||||
FLAVOR_CAPABILITIES_PATH = (PROVIDERS_PATH +
|
||||
'/{provider}/flavor_capabilities')
|
||||
|
||||
NOT_AUTHORIZED_BODY = {
|
||||
'debuginfo': None, 'faultcode': 'Client',
|
||||
|
@ -12,6 +12,16 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
|
||||
import mock
|
||||
|
||||
from oslo_config import cfg
|
||||
from oslo_config import fixture as oslo_fixture
|
||||
from oslo_utils import uuidutils
|
||||
|
||||
from octavia.api.drivers import exceptions
|
||||
from octavia.common import constants
|
||||
import octavia.common.context
|
||||
from octavia.tests.functional.api.v2 import base
|
||||
|
||||
|
||||
@ -43,3 +53,82 @@ class TestProvider(base.BaseAPITest):
|
||||
self.assertTrue(octavia_dict in providers_list)
|
||||
self.assertTrue(amphora_dict in providers_list)
|
||||
self.assertTrue(noop_dict in providers_list)
|
||||
|
||||
|
||||
class TestFlavorCapabilities(base.BaseAPITest):
|
||||
|
||||
root_tag = 'flavor_capabilities'
|
||||
|
||||
def setUp(self):
|
||||
super(TestFlavorCapabilities, self).setUp()
|
||||
|
||||
def test_nonexistent_provider(self):
|
||||
self.get(self.FLAVOR_CAPABILITIES_PATH.format(provider='bogus'),
|
||||
status=400)
|
||||
|
||||
def test_noop_provider(self):
|
||||
ref_capabilities = [{'description': 'The glance image tag to use for '
|
||||
'this load balancer.', 'name': 'amp_image_tag'}]
|
||||
|
||||
result = self.get(
|
||||
self.FLAVOR_CAPABILITIES_PATH.format(provider='noop_driver'))
|
||||
self.assertEqual(ref_capabilities, result.json.get(self.root_tag))
|
||||
|
||||
def test_amphora_driver(self):
|
||||
ref_description = ("The load balancer topology. One of: SINGLE - One "
|
||||
"amphora per load balancer. ACTIVE_STANDBY - Two "
|
||||
"amphora per load balancer.")
|
||||
result = self.get(
|
||||
self.FLAVOR_CAPABILITIES_PATH.format(provider='amphora'))
|
||||
capabilities = result.json.get(self.root_tag)
|
||||
capability_dict = [i for i in capabilities if
|
||||
i['name'] == 'loadbalancer_topology'][0]
|
||||
self.assertEqual(ref_description,
|
||||
capability_dict['description'])
|
||||
|
||||
# Some drivers might not have implemented this yet, test that case
|
||||
@mock.patch('octavia.api.drivers.noop_driver.driver.NoopProviderDriver.'
|
||||
'get_supported_flavor_metadata')
|
||||
def test_not_implemented(self, mock_get_metadata):
|
||||
mock_get_metadata.side_effect = exceptions.NotImplementedError()
|
||||
self.get(self.FLAVOR_CAPABILITIES_PATH.format(provider='noop_driver'),
|
||||
status=501)
|
||||
|
||||
def test_authorized(self):
|
||||
ref_capabilities = [{'description': 'The glance image tag to use '
|
||||
'for this load balancer.',
|
||||
'name': 'amp_image_tag'}]
|
||||
self.conf = self.useFixture(oslo_fixture.Config(cfg.CONF))
|
||||
auth_strategy = self.conf.conf.api_settings.get('auth_strategy')
|
||||
self.conf.config(group='api_settings', auth_strategy=constants.TESTING)
|
||||
project_id = uuidutils.generate_uuid()
|
||||
with mock.patch.object(octavia.common.context.Context, 'project_id',
|
||||
project_id):
|
||||
override_credentials = {
|
||||
'service_user_id': None,
|
||||
'user_domain_id': None,
|
||||
'is_admin_project': True,
|
||||
'service_project_domain_id': None,
|
||||
'service_project_id': None,
|
||||
'roles': ['load-balancer_member'],
|
||||
'user_id': None,
|
||||
'is_admin': True,
|
||||
'service_user_domain_id': None,
|
||||
'project_domain_id': None,
|
||||
'service_roles': [],
|
||||
'project_id': project_id}
|
||||
with mock.patch(
|
||||
"oslo_context.context.RequestContext.to_policy_values",
|
||||
return_value=override_credentials):
|
||||
result = self.get(self.FLAVOR_CAPABILITIES_PATH.format(
|
||||
provider='noop_driver'))
|
||||
self.conf.config(group='api_settings', auth_strategy=auth_strategy)
|
||||
self.assertEqual(ref_capabilities, result.json.get(self.root_tag))
|
||||
|
||||
def test_not_authorized(self):
|
||||
self.conf = self.useFixture(oslo_fixture.Config(cfg.CONF))
|
||||
auth_strategy = self.conf.conf.api_settings.get('auth_strategy')
|
||||
self.conf.config(group='api_settings', auth_strategy=constants.TESTING)
|
||||
self.get(self.FLAVOR_CAPABILITIES_PATH.format(provider='noop_driver'),
|
||||
status=403)
|
||||
self.conf.config(group='api_settings', auth_strategy=auth_strategy)
|
||||
|
Loading…
Reference in New Issue
Block a user