Add admin endpoint for amphora info

Administrators can now use /v2.0/octavia/amphorae to retrieve internal
information about amphora details like compute_id and lb_network_ip.

Change-Id: I5ac8d1ce189db09d52e518d42aeb3a192b8a8814
This commit is contained in:
Adam Harwell 2017-09-19 13:42:52 -07:00
parent b8e0f5e763
commit 7f1c5011ed
20 changed files with 784 additions and 11 deletions

View File

@ -1,6 +1,12 @@
###############################################################################
# Path fields
###############################################################################
path-amphora-id:
description: |
The ID of the amphora to query.
in: path
required: true
type: string
path-healthmonitor-id:
description: |
The ID of the health monitor to query.
@ -125,6 +131,25 @@ admin_state_up-optional:
in: body
required: false
type: boolean
amphora-id:
description: |
The associated amphora ID.
in: body
required: true
type: string
amphora-role:
description: |
The role of the amphora. One of ``STANDALONE``, ``MASTER``, ``BACKUP``.
in: body
required: true
type: string
amphora-status:
description: |
The status of the amphora. One of: ``BOOTING``, ``ALLOCATED``, ``READY``,
``PENDING_DELETE``, ``DELETED``, ``ERROR``.
in: body
required: true
type: string
api_links:
description: |
Links to the resources in question.
@ -160,6 +185,24 @@ bytes_out:
in: body
required: true
type: integer
cert-busy:
description: |
Whether the certificate is in the process of being replaced.
in: body
required: true
type: string
cert-expiration:
description: |
The date the certificate for the amphora expires.
in: body
required: true
type: string
compute-id:
description: |
The ID of the amphora resource in the compute system.
in: body
required: true
type: string
connection_limit:
description: |
The maximum number of connections permitted for this listener. Default
@ -563,6 +606,12 @@ lb-algorithm-optional:
in: body
required: false
type: string
lb-network-ip:
description: |
The management IP of the amphora.
in: body
required: true
type: string
listener:
description: |
A listener object.
@ -1015,6 +1064,36 @@ vip_subnet_id-optional:
in: body
required: false
type: string
vrrp-id:
description: |
The vrrp group's ID for the amphora.
in: body
required: true
type: string
vrrp-interface:
description: |
The bound interface name of the vrrp port on the amphora.
in: body
required: true
type: string
vrrp-ip:
description: |
The address of the vrrp port on the amphora.
in: body
required: true
type: string
vrrp-port-id:
description: |
The vrrp port's ID in the networking system.
in: body
required: true
type: string
vrrp-priority:
description: |
The priority of the amphora in the vrrp group.
in: body
required: true
type: string
weight:
description: |
The weight of a member determines the portion of requests or connections it

View File

@ -0,0 +1,132 @@
.. -*- rst -*-
List Amphora
============
.. rest_method:: GET /v2.0/octavia/amphorae
Lists all amphora for the project.
If you are not an administrative user, the service returns the HTTP
``Forbidden (403)`` response code.
Use the ``fields`` query parameter to control which fields are
returned in the response body. Additionally, you can filter results
by using query string parameters. For information, see :ref:`filtering`.
The list might be empty.
.. 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
Curl Example
------------
.. literalinclude:: examples/amphora-list-curl
:language: bash
Response Parameters
-------------------
.. rest_parameters:: ../parameters.yaml
- id: amphora-id
- loadbalancer_id: loadbalancer-id
- compute_id: compute-id
- lb_network_ip: lb-network-ip
- vrrp_ip: vrrp-ip
- ha_ip: vip_address
- vrrp_port_id: vrrp-port-id
- ha_port_id: vip_port_id
- cert_expiration: cert-expiration
- cert_busy: cert-busy
- role: amphora-role
- status: amphora-status
- vrrp_interface: vrrp-interface
- vrrp_id: vrrp-id
- vrrp_priority: vrrp-priority
Response Example
----------------
.. literalinclude:: examples/amphora-list-response.json
:language: javascript
Show Amphora details
===========================
.. rest_method:: GET /v2.0/octavia/amphorae/{amphora_id}
Shows the details of an amphora.
If you are not an administrative user, the service returns the HTTP
``Forbidden (403)`` response code.
This operation does not require a request body.
.. rest_status_code:: success ../http-status.yaml
- 200
.. rest_status_code:: error ../http-status.yaml
- 401
- 403
- 404
- 500
Request
-------
.. rest_parameters:: ../parameters.yaml
- fields: fields
- amphora_id: path-amphora-id
Curl Example
------------
.. literalinclude:: examples/amphora-show-curl
:language: bash
Response Parameters
-------------------
.. rest_parameters:: ../parameters.yaml
- id: amphora-id
- loadbalancer_id: loadbalancer-id
- compute_id: compute-id
- lb_network_ip: lb-network-ip
- vrrp_ip: vrrp-ip
- ha_ip: vip_address
- vrrp_port_id: vrrp-port-id
- ha_port_id: vip_port_id
- cert_expiration: cert-expiration
- cert_busy: cert-busy
- role: amphora-role
- status: amphora-status
- vrrp_interface: vrrp-interface
- vrrp_id: vrrp-id
- vrrp_priority: vrrp-priority
Response Example
----------------
.. literalinclude:: examples/amphora-show-response.json
:language: javascript

View File

@ -0,0 +1 @@
curl -X GET -H "X-Auth-Token: <token>" http://198.51.100.10:9876/v2.0/octavia/amphorae?loadbalancer_id=09eedfc6-2c55-41a8-a75c-2cd4e95212ca

View File

@ -0,0 +1,38 @@
{
"amphorae": [
{
"id": "6bd55cd3-802e-447e-a518-1e74e23bb106",
"load_balancer_id": "09eedfc6-2c55-41a8-a75c-2cd4e95212ca",
"compute_id": "f0f79f90-733d-417a-8d70-cc6be62cd54d",
"lb_network_ip": "192.168.1.2",
"vrrp_ip": "192.168.1.5",
"ha_ip": "192.168.1.10",
"vrrp_port_id": "ab2a8add-76a9-44bb-89f8-88430193cc83",
"ha_port_id": "19561fd3-5da5-46cc-bdd3-99bbdf7246e6",
"cert_expiration": "2019-09-19 00:34:51",
"cert_busy": 0,
"role": "MASTER",
"status": "ALLOCATED",
"vrrp_interface": "eth1",
"vrrp_id": 1,
"vrrp_priority": 100
},
{
"id": "89c186a3-cb16-497b-b099-c4bd40316642",
"load_balancer_id": "09eedfc6-2c55-41a8-a75c-2cd4e95212ca",
"compute_id": "24b1cb54-122d-4960-9035-083642f5c2bb",
"lb_network_ip": "192.168.1.3",
"vrrp_ip": "192.168.1.6",
"ha_ip": "192.168.1.10",
"vrrp_port_id": "cae421f6-dcf0-4866-9438-d0c682645799",
"ha_port_id": "19561fd3-5da5-46cc-bdd3-99bbdf7246e6",
"cert_expiration": "2019-09-19 00:34:51",
"cert_busy": 0,
"role": "BACKUP",
"status": "ALLOCATED",
"vrrp_interface": "eth1",
"vrrp_id": 1,
"vrrp_priority": 200
}
]
}

View File

@ -0,0 +1 @@
curl -X GET -H "X-Auth-Token: <token>" http://198.51.100.10:9876/v2.0/octavia/amphorae/6bd55cd3-802e-447e-a518-1e74e23bb106

View File

@ -0,0 +1,19 @@
{
"amphora": {
"id": "6bd55cd3-802e-447e-a518-1e74e23bb106",
"load_balancer_id": "09eedfc6-2c55-41a8-a75c-2cd4e95212ca",
"compute_id": "f0f79f90-733d-417a-8d70-cc6be62cd54d",
"lb_network_ip": "192.168.1.2",
"vrrp_ip": "192.168.1.5",
"ha_ip": "192.168.1.10",
"vrrp_port_id": "ab2a8add-76a9-44bb-89f8-88430193cc83",
"ha_port_id": "19561fd3-5da5-46cc-bdd3-99bbdf7246e6",
"cert_expiration": "2019-09-19 00:34:51",
"cert_busy": 0,
"role": "MASTER",
"status": "ALLOCATED",
"vrrp_interface": "eth1",
"vrrp_id": 1,
"vrrp_priority": 100
}
}

View File

@ -50,3 +50,8 @@ L7 Rules
Quotas
------
.. include:: quota.inc
--------
Amphorae
--------
.. include:: amphora.inc

View File

@ -205,6 +205,9 @@ class PaginationHelper(object):
if k in filter_attrs}
query = model.apply_filter(query, model, self.filters)
if model.__name__ == "Amphora" and 'project_id' in self.params:
query = query.filter(model.load_balancer.has(
project_id=self.params['project_id']))
# Add sorting
if CONF.api_settings.allow_sorting:

View File

@ -15,6 +15,7 @@
from wsme import types as wtypes
from wsmeext import pecan as wsme_pecan
from octavia.api.v2.controllers import amphora
from octavia.api.v2.controllers import base
from octavia.api.v2.controllers import health_monitor
from octavia.api.v2.controllers import l7policy
@ -46,9 +47,22 @@ class BaseV2Controller(base.BaseController):
return "v2.0"
class OctaviaV2Controller(base.BaseController):
amphorae = None
def __init__(self):
super(OctaviaV2Controller, self).__init__()
self.amphorae = amphora.AmphoraController()
@wsme_pecan.wsexpose(wtypes.text)
def get(self):
return "v2.0"
class V2Controller(BaseV2Controller):
lbaas = None
def __init__(self):
super(V2Controller, self).__init__()
self.lbaas = BaseV2Controller()
self.octavia = OctaviaV2Controller()

View File

@ -0,0 +1,80 @@
# Copyright 2014 Rackspace
# Copyright 2016 Blue Box, an IBM Company
#
# 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.
import logging
from oslo_config import cfg
import pecan
from wsme import types as wtypes
from wsmeext import pecan as wsme_pecan
from octavia.api.v2.controllers import base
from octavia.api.v2.types import amphora as amp_types
from octavia.common import constants
from octavia.common import data_models
from octavia.common import exceptions
CONF = cfg.CONF
LOG = logging.getLogger(__name__)
class AmphoraController(base.BaseController):
RBAC_TYPE = constants.RBAC_AMPHORA
def _get_db_amp(self, session, amp_id):
"""Gets the current amphora object from the database."""
db_amp = self.repositories.amphora.get(
session, id=amp_id)
if not db_amp:
LOG.info("Amphora %s was not found", amp_id)
raise exceptions.NotFound(
resource=data_models.Amphora._name(),
id=amp_id)
return db_amp
@wsme_pecan.wsexpose(amp_types.AmphoraRootResponse, wtypes.text,
wtypes.text)
def get_one(self, id):
"""Gets a single amphora's details."""
context = pecan.request.context.get('octavia_context')
db_amp = self._get_db_amp(context.session, id)
self._auth_validate_action(context, db_amp.load_balancer.project_id,
constants.RBAC_GET_ONE)
result = self._convert_db_to_type(
db_amp, amp_types.AmphoraResponse)
return amp_types.AmphoraRootResponse(amphora=result)
@wsme_pecan.wsexpose(amp_types.AmphoraeRootResponse, wtypes.text,
[wtypes.text], ignore_extra_args=True)
def get_all(self, fields=None):
"""Gets all health monitors."""
pcontext = pecan.request.context
context = pcontext.get('octavia_context')
self._auth_validate_action(context, context.project_id,
constants.RBAC_GET_ALL)
db_amp, links = self.repositories.amphora.get_all(
context.session, show_deleted=False,
pagination_helper=pcontext.get(constants.PAGINATION_HELPER))
result = self._convert_db_to_type(
db_amp, [amp_types.AmphoraResponse])
if fields is not None:
result = self._filter_fields(result, fields)
return amp_types.AmphoraeRootResponse(
amphorae=result, amphorae_links=links)

View File

@ -0,0 +1,57 @@
# Copyright 2016 Rackspace
#
# 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 wsme import types as wtypes
from octavia.api.common import types
class BaseAmphoraType(types.BaseType):
_type_to_model_map = {'loadbalancer_id': 'load_balancer_id'}
_child_map = {}
class AmphoraResponse(BaseAmphoraType):
"""Defines which attributes are to be shown on any response."""
id = wtypes.wsattr(wtypes.UuidType())
loadbalancer_id = wtypes.wsattr(wtypes.UuidType())
compute_id = wtypes.wsattr(wtypes.UuidType())
lb_network_ip = wtypes.wsattr(types.IPAddressType())
vrrp_ip = wtypes.wsattr(types.IPAddressType())
ha_ip = wtypes.wsattr(types.IPAddressType())
vrrp_port_id = wtypes.wsattr(wtypes.UuidType())
ha_port_id = wtypes.wsattr(wtypes.UuidType())
cert_expiration = wtypes.wsattr(wtypes.datetime.datetime)
cert_busy = wtypes.wsattr(bool)
role = wtypes.wsattr(wtypes.StringType())
status = wtypes.wsattr(wtypes.StringType())
vrrp_interface = wtypes.wsattr(wtypes.StringType())
vrrp_id = wtypes.wsattr(wtypes.IntegerType())
vrrp_priority = wtypes.wsattr(wtypes.IntegerType())
@classmethod
def from_data_model(cls, data_model, children=False):
amphorae = super(AmphoraResponse, cls).from_data_model(
data_model, children=children)
return amphorae
class AmphoraRootResponse(types.BaseType):
amphora = wtypes.wsattr(AmphoraResponse)
class AmphoraeRootResponse(types.BaseType):
amphorae = wtypes.wsattr([AmphoraResponse])
amphorae_links = wtypes.wsattr([types.PageType])

View File

@ -98,7 +98,7 @@ SUPPORTED_PROVISIONING_STATUSES = (ACTIVE, AMPHORA_ALLOCATED,
MUTABLE_STATUSES = (ACTIVE,)
DELETABLE_STATUSES = (ACTIVE, ERROR)
SUPPORTED_AMPHORA_STATUSES = (AMPHORA_ALLOCATED, AMPHORA_BOOTING,
SUPPORTED_AMPHORA_STATUSES = (AMPHORA_ALLOCATED, AMPHORA_BOOTING, ERROR,
AMPHORA_READY, DELETED, PENDING_DELETE)
ONLINE = 'ONLINE'
@ -450,6 +450,7 @@ RBAC_HEALTHMONITOR = '{}:healthmonitor:'.format(LOADBALANCER_API)
RBAC_L7POLICY = '{}:l7policy:'.format(LOADBALANCER_API)
RBAC_L7RULE = '{}:l7rule:'.format(LOADBALANCER_API)
RBAC_QUOTA = '{}:quota:'.format(LOADBALANCER_API)
RBAC_AMPHORA = '{}:amphora:'.format(LOADBALANCER_API)
RBAC_POST = 'post'
RBAC_PUT = 'put'
RBAC_PUT_FAILOVER = 'put_failover'

View File

@ -103,7 +103,8 @@ class Policy(oslo_policy.Enforcer):
credentials = context.to_policy_values()
# Inject is_admin into the credentials to allow override via
# config auth_strategy = constants.NOAUTH
credentials['is_admin'] = context.is_admin
credentials['is_admin'] = (
credentials.get('is_admin') or context.is_admin)
if not exc:
exc = exceptions.PolicyForbidden

View File

@ -20,6 +20,7 @@ from sqlalchemy import orm
from sqlalchemy.orm import validates
from sqlalchemy.sql import func
from octavia.api.v2.types import amphora
from octavia.api.v2.types import health_monitor
from octavia.api.v2.types import l7policy
from octavia.api.v2.types import l7rule
@ -482,6 +483,8 @@ class Amphora(base_models.BASE, base_models.IdMixin):
__tablename__ = "amphora"
__v2_wsme__ = amphora.AmphoraResponse
load_balancer_id = sa.Column(
sa.String(36), sa.ForeignKey("load_balancer.id",
name="fk_amphora_load_balancer_id"),

View File

@ -118,8 +118,12 @@ class BaseRepository(object):
query = query.options(joinedload('*'))
if not deleted:
query = query.filter(
self.model_class.provisioning_status != consts.DELETED)
if hasattr(self.model_class, 'status'):
query = query.filter(
self.model_class.status != consts.DELETED)
else:
query = query.filter(
self.model_class.provisioning_status != consts.DELETED)
if pagination_helper:
model_list, links = pagination_helper.apply(

View File

@ -13,6 +13,7 @@
import itertools
from octavia.policies import amphora
from octavia.policies import base
from octavia.policies import healthmonitor
from octavia.policies import l7policy
@ -35,4 +36,5 @@ def list_rules():
member.list_rules(),
pool.list_rules(),
quota.list_rules(),
amphora.list_rules(),
)

View File

@ -0,0 +1,36 @@
# Copyright 2017 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 octavia.common import constants
from oslo_policy import policy
rules = [
policy.DocumentedRuleDefault(
'{rbac_obj}{action}'.format(rbac_obj=constants.RBAC_AMPHORA,
action=constants.RBAC_GET_ALL),
constants.RULE_API_ADMIN,
"List Amphorae",
[{'method': 'GET', 'path': '/v2.0/octavia/amphorae'}]
),
policy.DocumentedRuleDefault(
'{rbac_obj}{action}'.format(rbac_obj=constants.RBAC_AMPHORA,
action=constants.RBAC_GET_ONE),
constants.RULE_API_ADMIN,
"Show Amphora details",
[{'method': 'GET', 'path': '/v2.0/octavia/amphorae/{amphora_id}'}]
),
]
def list_rules():
return rules

View File

@ -28,21 +28,21 @@ from octavia.tests.functional.db import base as base_db_test
class BaseAPITest(base_db_test.OctaviaDBTestBase):
BASE_PATH = '/v2.0/lbaas'
BASE_PATH = '/v2.0'
# /lbaas/loadbalancers
LBS_PATH = '/loadbalancers'
LBS_PATH = '/lbaas/loadbalancers'
LB_PATH = LBS_PATH + '/{lb_id}'
LB_STATUS_PATH = LB_PATH + '/statuses'
LB_STATS_PATH = LB_PATH + '/stats'
# /lbaas/listeners/
LISTENERS_PATH = '/listeners'
LISTENERS_PATH = '/lbaas/listeners'
LISTENER_PATH = LISTENERS_PATH + '/{listener_id}'
LISTENER_STATS_PATH = LISTENER_PATH + '/stats'
# /lbaas/pools
POOLS_PATH = '/pools'
POOLS_PATH = '/lbaas/pools'
POOL_PATH = POOLS_PATH + '/{pool_id}'
# /lbaas/pools/{pool_id}/members
@ -50,19 +50,22 @@ class BaseAPITest(base_db_test.OctaviaDBTestBase):
MEMBER_PATH = MEMBERS_PATH + '/{member_id}'
# /lbaas/healthmonitors
HMS_PATH = '/healthmonitors'
HMS_PATH = '/lbaas/healthmonitors'
HM_PATH = HMS_PATH + '/{healthmonitor_id}'
# /lbaas/l7policies
L7POLICIES_PATH = '/l7policies'
L7POLICIES_PATH = '/lbaas/l7policies'
L7POLICY_PATH = L7POLICIES_PATH + '/{l7policy_id}'
L7RULES_PATH = L7POLICY_PATH + '/rules'
L7RULE_PATH = L7RULES_PATH + '/{l7rule_id}'
QUOTAS_PATH = '/quotas'
QUOTAS_PATH = '/lbaas/quotas'
QUOTA_PATH = QUOTAS_PATH + '/{project_id}'
QUOTA_DEFAULT_PATH = QUOTAS_PATH + '/{project_id}/default'
AMPHORAE_PATH = '/octavia/amphorae'
AMPHORA_PATH = AMPHORAE_PATH + '/{amphora_id}'
NOT_AUTHORIZED_BODY = {
'debuginfo': None, 'faultcode': 'Client',
'faultstring': 'Policy does not allow this request to be performed.'}

View File

@ -0,0 +1,289 @@
# Copyright 2014 Rackspace
#
# 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.
import datetime
import mock
from oslo_config import cfg
from oslo_config import fixture as oslo_fixture
from oslo_utils import uuidutils
from octavia.common import constants
import octavia.common.context
from octavia.tests.functional.api.v2 import base
class TestAmphora(base.BaseAPITest):
root_tag = 'amphora'
root_tag_list = 'amphorae'
root_tag_links = 'amphorae_links'
def setUp(self):
super(TestAmphora, self).setUp()
self.lb = self.create_load_balancer(
uuidutils.generate_uuid()).get('loadbalancer')
self.lb_id = self.lb.get('id')
self.project_id = self.lb.get('project_id')
self.set_lb_status(self.lb_id)
self.amp_args = {
'load_balancer_id': self.lb_id,
'compute_id': uuidutils.generate_uuid(),
'lb_network_ip': '192.168.1.2',
'vrrp_ip': '192.168.1.5',
'ha_ip': '192.168.1.10',
'vrrp_port_id': uuidutils.generate_uuid(),
'ha_port_id': uuidutils.generate_uuid(),
'cert_expiration': datetime.datetime.now(),
'cert_busy': False,
'role': constants.ROLE_STANDALONE,
'status': constants.AMPHORA_ALLOCATED,
'vrrp_interface': 'eth1',
'vrrp_id': 1,
'vrrp_priority': 100,
}
self.amp = self.amphora_repo.create(self.session, **self.amp_args)
self.amp_id = self.amp.id
self.amp_args['id'] = self.amp_id
def _create_additional_amp(self):
amp_args = {
'load_balancer_id': None,
'compute_id': uuidutils.generate_uuid(),
'lb_network_ip': '192.168.1.2',
'vrrp_ip': '192.168.1.5',
'ha_ip': '192.168.1.10',
'vrrp_port_id': uuidutils.generate_uuid(),
'ha_port_id': uuidutils.generate_uuid(),
'cert_expiration': None,
'cert_busy': False,
'role': constants.ROLE_MASTER,
'status': constants.AMPHORA_READY,
'vrrp_interface': 'eth1',
'vrrp_id': 1,
'vrrp_priority': 100,
}
return self.amphora_repo.create(self.session, **amp_args)
def _assert_amp_equal(self, source, response):
self.assertEqual(source.pop('load_balancer_id'),
response.pop('loadbalancer_id'))
self.assertEqual(source.pop('cert_expiration').isoformat(),
response.pop('cert_expiration'))
self.assertEqual(source, response)
def test_get(self):
response = self.get(self.AMPHORA_PATH.format(
amphora_id=self.amp_id)).json.get(self.root_tag)
self._assert_amp_equal(self.amp_args, response)
def test_get_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)
with mock.patch.object(octavia.common.context.Context, 'project_id',
self.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': self.project_id}
with mock.patch(
"oslo_context.context.RequestContext.to_policy_values",
return_value=override_credentials):
response = self.get(self.AMPHORA_PATH.format(
amphora_id=self.amp_id)).json.get(self.root_tag)
# Reset api auth setting
self.conf.config(group='api_settings', auth_strategy=auth_strategy)
self._assert_amp_equal(self.amp_args, response)
def test_get_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)
with mock.patch.object(octavia.common.context.Context, 'project_id',
uuidutils.generate_uuid()):
response = self.get(self.AMPHORA_PATH.format(
amphora_id=self.amp_id), status=403)
# Reset api auth setting
self.conf.config(group='api_settings', auth_strategy=auth_strategy)
self.assertEqual(self.NOT_AUTHORIZED_BODY, response.json)
def test_get_hides_deleted(self):
new_amp = self._create_additional_amp()
response = self.get(self.AMPHORAE_PATH)
objects = response.json.get(self.root_tag_list)
self.assertEqual(len(objects), 2)
self.amphora_repo.update(self.session, new_amp.id,
status=constants.DELETED)
response = self.get(self.AMPHORAE_PATH)
objects = response.json.get(self.root_tag_list)
self.assertEqual(len(objects), 1)
def test_bad_get(self):
self.get(self.AMPHORA_PATH.format(
amphora_id=uuidutils.generate_uuid()), status=404)
def test_get_all(self):
amps = self.get(self.AMPHORAE_PATH).json.get(self.root_tag_list)
self.assertIsInstance(amps, list)
self.assertEqual(1, len(amps))
self.assertEqual(self.amp_id, amps[0].get('id'))
def test_get_all_authorized(self):
auth_strategy = self.conf.conf.api_settings.get('auth_strategy')
self.conf.config(group='api_settings', auth_strategy=constants.TESTING)
with mock.patch.object(octavia.common.context.Context, 'project_id',
uuidutils.generate_uuid()):
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': self.project_id}
with mock.patch(
"oslo_context.context.RequestContext.to_policy_values",
return_value=override_credentials):
amps = self.get(self.AMPHORAE_PATH).json.get(
self.root_tag_list)
self.conf.config(group='api_settings', auth_strategy=auth_strategy)
self.assertIsInstance(amps, list)
self.assertEqual(1, len(amps))
self.assertEqual(self.amp_id, amps[0].get('id'))
def test_get_all_not_authorized(self):
auth_strategy = self.conf.conf.api_settings.get('auth_strategy')
self.conf.config(group='api_settings', auth_strategy=constants.TESTING)
with mock.patch.object(octavia.common.context.Context, 'project_id',
uuidutils.generate_uuid()):
amps = self.get(self.AMPHORAE_PATH, status=403).json
self.conf.config(group='api_settings', auth_strategy=auth_strategy)
self.assertEqual(self.NOT_AUTHORIZED_BODY, amps)
def test_get_by_loadbalancer_id(self):
amps = self.get(
self.AMPHORAE_PATH,
params={'loadbalancer_id': self.lb_id}
).json.get(self.root_tag_list)
self.assertEqual(1, len(amps))
amps = self.get(
self.AMPHORAE_PATH,
params={'loadbalancer_id': uuidutils.generate_uuid()}
).json.get(self.root_tag_list)
self.assertEqual(0, len(amps))
def test_get_by_project_id(self):
amps = self.get(
self.AMPHORAE_PATH,
params={'project_id': self.project_id}
).json.get(self.root_tag_list)
self.assertEqual(1, len(amps))
amps = self.get(
self.AMPHORAE_PATH,
params={'project_id': uuidutils.generate_uuid()}
).json.get(self.root_tag_list)
self.assertEqual(0, len(amps))
def test_get_all_sorted(self):
self._create_additional_amp()
response = self.get(self.AMPHORAE_PATH, params={'sort': 'role:desc'})
amps_desc = response.json.get(self.root_tag_list)
response = self.get(self.AMPHORAE_PATH, params={'sort': 'role:asc'})
amps_asc = response.json.get(self.root_tag_list)
self.assertEqual(2, len(amps_desc))
self.assertEqual(2, len(amps_asc))
amp_id_roles_desc = [(amp.get('id'), amp.get('role'))
for amp in amps_desc]
amp_id_roles_asc = [(amp.get('id'), amp.get('role'))
for amp in amps_asc]
self.assertEqual(amp_id_roles_asc, list(reversed(amp_id_roles_desc)))
def test_get_all_limited(self):
self._create_additional_amp()
self._create_additional_amp()
# First two -- should have 'next' link
first_two = self.get(self.AMPHORAE_PATH, params={'limit': 2}).json
objs = first_two[self.root_tag_list]
links = first_two[self.root_tag_links]
self.assertEqual(2, len(objs))
self.assertEqual(1, len(links))
self.assertEqual('next', links[0]['rel'])
# Third + off the end -- should have previous link
third = self.get(self.AMPHORAE_PATH, params={
'limit': 2,
'marker': first_two[self.root_tag_list][1]['id']}).json
objs = third[self.root_tag_list]
links = third[self.root_tag_links]
self.assertEqual(1, len(objs))
self.assertEqual(1, len(links))
self.assertEqual('previous', links[0]['rel'])
# Middle -- should have both links
middle = self.get(self.AMPHORAE_PATH, params={
'limit': 1,
'marker': first_two[self.root_tag_list][0]['id']}).json
objs = middle[self.root_tag_list]
links = middle[self.root_tag_links]
self.assertEqual(1, len(objs))
self.assertEqual(2, len(links))
self.assertItemsEqual(['previous', 'next'], [l['rel'] for l in links])
def test_get_all_fields_filter(self):
amps = self.get(self.AMPHORAE_PATH, params={
'fields': ['id', 'compute_id']}).json
for amp in amps['amphorae']:
self.assertIn(u'id', amp.keys())
self.assertIn(u'compute_id', amp.keys())
self.assertNotIn(u'vrrp_ip', amp.keys())
def test_get_all_filter(self):
self._create_additional_amp()
amps = self.get(self.AMPHORAE_PATH, params={
'id': self.amp_id}).json.get(self.root_tag_list)
self.assertEqual(1, len(amps))
self.assertEqual(self.amp_id,
amps[0]['id'])
def test_empty_get_all(self):
self.amphora_repo.delete(self.session, id=self.amp_id)
response = self.get(self.AMPHORAE_PATH).json.get(self.root_tag_list)
self.assertIsInstance(response, list)
self.assertEqual(0, len(response))

View File

@ -0,0 +1,5 @@
---
features:
- |
Added a new endpoint /v2.0/octavia/amphorae to expose internal details
about amphorae. This endpoint is admin only.