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:
Michael Johnson 2018-12-10 12:24:48 -08:00
parent 0b1fe6a526
commit 1afeeb95d3
11 changed files with 264 additions and 0 deletions

View File

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

View File

@ -0,0 +1 @@
curl -X GET -H "X-Auth-Token: <token>" http://198.51.100.10:9876/v2/lbaas/providers/amphora/flavor_capabilities

View File

@ -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."
}
]
}

View File

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

View File

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

View File

@ -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])

View File

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

View File

@ -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(),
)

View 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

View File

@ -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',

View File

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