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:
@@ -67,6 +67,12 @@ path-project-id:
|
|||||||
in: path
|
in: path
|
||||||
required: true
|
required: true
|
||||||
type: string
|
type: string
|
||||||
|
path-provider:
|
||||||
|
description: |
|
||||||
|
The provider to query.
|
||||||
|
in: path
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
###############################################################################
|
###############################################################################
|
||||||
# Query fields
|
# Query fields
|
||||||
###############################################################################
|
###############################################################################
|
||||||
@@ -335,6 +341,24 @@ flavor:
|
|||||||
in: body
|
in: body
|
||||||
required: true
|
required: true
|
||||||
type: object
|
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:
|
flavor-data:
|
||||||
description: |
|
description: |
|
||||||
The JSON string containing the flavor metadata.
|
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
|
.. literalinclude:: examples/provider-list-response.json
|
||||||
:language: javascript
|
: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.
|
# under the License.
|
||||||
|
|
||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
|
from oslo_log import log as logging
|
||||||
import pecan
|
import pecan
|
||||||
import six
|
import six
|
||||||
from wsme import types as wtypes
|
from wsme import types as wtypes
|
||||||
from wsmeext import pecan as wsme_pecan
|
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.controllers import base
|
||||||
from octavia.api.v2.types import provider as provider_types
|
from octavia.api.v2.types import provider as provider_types
|
||||||
from octavia.common import constants
|
from octavia.common import constants
|
||||||
|
from octavia.common import exceptions
|
||||||
|
|
||||||
CONF = cfg.CONF
|
CONF = cfg.CONF
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class ProviderController(base.BaseController):
|
class ProviderController(base.BaseController):
|
||||||
@@ -48,3 +53,46 @@ class ProviderController(base.BaseController):
|
|||||||
if fields is not None:
|
if fields is not None:
|
||||||
response_list = self._filter_fields(response_list, fields)
|
response_list = self._filter_fields(response_list, fields)
|
||||||
return provider_types.ProvidersRootResponse(providers=response_list)
|
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):
|
class ProvidersRootResponse(types.BaseType):
|
||||||
providers = wtypes.wsattr([ProviderResponse])
|
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_QUOTA = '{}:quota:'.format(LOADBALANCER_API)
|
||||||
RBAC_AMPHORA = '{}:amphora:'.format(LOADBALANCER_API)
|
RBAC_AMPHORA = '{}:amphora:'.format(LOADBALANCER_API)
|
||||||
RBAC_PROVIDER = '{}:provider:'.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 = '{}:flavor:'.format(LOADBALANCER_API)
|
||||||
RBAC_FLAVOR_PROFILE = '{}:flavor-profile:'.format(LOADBALANCER_API)
|
RBAC_FLAVOR_PROFILE = '{}:flavor-profile:'.format(LOADBALANCER_API)
|
||||||
RBAC_POST = 'post'
|
RBAC_POST = 'post'
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ from octavia.policies import loadbalancer
|
|||||||
from octavia.policies import member
|
from octavia.policies import member
|
||||||
from octavia.policies import pool
|
from octavia.policies import pool
|
||||||
from octavia.policies import provider
|
from octavia.policies import provider
|
||||||
|
from octavia.policies import provider_flavor
|
||||||
from octavia.policies import quota
|
from octavia.policies import quota
|
||||||
|
|
||||||
|
|
||||||
@@ -43,4 +44,5 @@ def list_rules():
|
|||||||
provider.list_rules(),
|
provider.list_rules(),
|
||||||
quota.list_rules(),
|
quota.list_rules(),
|
||||||
amphora.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'
|
AMPHORA_STATS_PATH = AMPHORA_PATH + '/stats'
|
||||||
|
|
||||||
PROVIDERS_PATH = '/lbaas/providers'
|
PROVIDERS_PATH = '/lbaas/providers'
|
||||||
|
FLAVOR_CAPABILITIES_PATH = (PROVIDERS_PATH +
|
||||||
|
'/{provider}/flavor_capabilities')
|
||||||
|
|
||||||
NOT_AUTHORIZED_BODY = {
|
NOT_AUTHORIZED_BODY = {
|
||||||
'debuginfo': None, 'faultcode': 'Client',
|
'debuginfo': None, 'faultcode': 'Client',
|
||||||
|
|||||||
@@ -12,6 +12,16 @@
|
|||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# 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
|
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(octavia_dict in providers_list)
|
||||||
self.assertTrue(amphora_dict in providers_list)
|
self.assertTrue(amphora_dict in providers_list)
|
||||||
self.assertTrue(noop_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)
|
||||||
|
|||||||
Reference in New Issue
Block a user