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 fields
############################################################################### ###############################################################################
path-amphora-id:
description: |
The ID of the amphora to query.
in: path
required: true
type: string
path-healthmonitor-id: path-healthmonitor-id:
description: | description: |
The ID of the health monitor to query. The ID of the health monitor to query.
@ -125,6 +131,25 @@ admin_state_up-optional:
in: body in: body
required: false required: false
type: boolean 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: api_links:
description: | description: |
Links to the resources in question. Links to the resources in question.
@ -160,6 +185,24 @@ bytes_out:
in: body in: body
required: true required: true
type: integer 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: connection_limit:
description: | description: |
The maximum number of connections permitted for this listener. Default The maximum number of connections permitted for this listener. Default
@ -563,6 +606,12 @@ lb-algorithm-optional:
in: body in: body
required: false required: false
type: string type: string
lb-network-ip:
description: |
The management IP of the amphora.
in: body
required: true
type: string
listener: listener:
description: | description: |
A listener object. A listener object.
@ -1015,6 +1064,36 @@ vip_subnet_id-optional:
in: body in: body
required: false required: false
type: string 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: weight:
description: | description: |
The weight of a member determines the portion of requests or connections it 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 Quotas
------ ------
.. include:: quota.inc .. include:: quota.inc
--------
Amphorae
--------
.. include:: amphora.inc

View File

@ -205,6 +205,9 @@ class PaginationHelper(object):
if k in filter_attrs} if k in filter_attrs}
query = model.apply_filter(query, model, self.filters) 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 # Add sorting
if CONF.api_settings.allow_sorting: if CONF.api_settings.allow_sorting:

View File

@ -15,6 +15,7 @@
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.v2.controllers import amphora
from octavia.api.v2.controllers import base from octavia.api.v2.controllers import base
from octavia.api.v2.controllers import health_monitor from octavia.api.v2.controllers import health_monitor
from octavia.api.v2.controllers import l7policy from octavia.api.v2.controllers import l7policy
@ -46,9 +47,22 @@ class BaseV2Controller(base.BaseController):
return "v2.0" 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): class V2Controller(BaseV2Controller):
lbaas = None lbaas = None
def __init__(self): def __init__(self):
super(V2Controller, self).__init__() super(V2Controller, self).__init__()
self.lbaas = BaseV2Controller() 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,) MUTABLE_STATUSES = (ACTIVE,)
DELETABLE_STATUSES = (ACTIVE, ERROR) DELETABLE_STATUSES = (ACTIVE, ERROR)
SUPPORTED_AMPHORA_STATUSES = (AMPHORA_ALLOCATED, AMPHORA_BOOTING, SUPPORTED_AMPHORA_STATUSES = (AMPHORA_ALLOCATED, AMPHORA_BOOTING, ERROR,
AMPHORA_READY, DELETED, PENDING_DELETE) AMPHORA_READY, DELETED, PENDING_DELETE)
ONLINE = 'ONLINE' ONLINE = 'ONLINE'
@ -450,6 +450,7 @@ RBAC_HEALTHMONITOR = '{}:healthmonitor:'.format(LOADBALANCER_API)
RBAC_L7POLICY = '{}:l7policy:'.format(LOADBALANCER_API) RBAC_L7POLICY = '{}:l7policy:'.format(LOADBALANCER_API)
RBAC_L7RULE = '{}:l7rule:'.format(LOADBALANCER_API) 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_POST = 'post' RBAC_POST = 'post'
RBAC_PUT = 'put' RBAC_PUT = 'put'
RBAC_PUT_FAILOVER = 'put_failover' RBAC_PUT_FAILOVER = 'put_failover'

View File

@ -103,7 +103,8 @@ class Policy(oslo_policy.Enforcer):
credentials = context.to_policy_values() credentials = context.to_policy_values()
# Inject is_admin into the credentials to allow override via # Inject is_admin into the credentials to allow override via
# config auth_strategy = constants.NOAUTH # 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: if not exc:
exc = exceptions.PolicyForbidden exc = exceptions.PolicyForbidden

View File

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

View File

@ -118,6 +118,10 @@ class BaseRepository(object):
query = query.options(joinedload('*')) query = query.options(joinedload('*'))
if not deleted: if not deleted:
if hasattr(self.model_class, 'status'):
query = query.filter(
self.model_class.status != consts.DELETED)
else:
query = query.filter( query = query.filter(
self.model_class.provisioning_status != consts.DELETED) self.model_class.provisioning_status != consts.DELETED)

View File

@ -13,6 +13,7 @@
import itertools import itertools
from octavia.policies import amphora
from octavia.policies import base from octavia.policies import base
from octavia.policies import healthmonitor from octavia.policies import healthmonitor
from octavia.policies import l7policy from octavia.policies import l7policy
@ -35,4 +36,5 @@ def list_rules():
member.list_rules(), member.list_rules(),
pool.list_rules(), pool.list_rules(),
quota.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): class BaseAPITest(base_db_test.OctaviaDBTestBase):
BASE_PATH = '/v2.0/lbaas' BASE_PATH = '/v2.0'
# /lbaas/loadbalancers # /lbaas/loadbalancers
LBS_PATH = '/loadbalancers' LBS_PATH = '/lbaas/loadbalancers'
LB_PATH = LBS_PATH + '/{lb_id}' LB_PATH = LBS_PATH + '/{lb_id}'
LB_STATUS_PATH = LB_PATH + '/statuses' LB_STATUS_PATH = LB_PATH + '/statuses'
LB_STATS_PATH = LB_PATH + '/stats' LB_STATS_PATH = LB_PATH + '/stats'
# /lbaas/listeners/ # /lbaas/listeners/
LISTENERS_PATH = '/listeners' LISTENERS_PATH = '/lbaas/listeners'
LISTENER_PATH = LISTENERS_PATH + '/{listener_id}' LISTENER_PATH = LISTENERS_PATH + '/{listener_id}'
LISTENER_STATS_PATH = LISTENER_PATH + '/stats' LISTENER_STATS_PATH = LISTENER_PATH + '/stats'
# /lbaas/pools # /lbaas/pools
POOLS_PATH = '/pools' POOLS_PATH = '/lbaas/pools'
POOL_PATH = POOLS_PATH + '/{pool_id}' POOL_PATH = POOLS_PATH + '/{pool_id}'
# /lbaas/pools/{pool_id}/members # /lbaas/pools/{pool_id}/members
@ -50,19 +50,22 @@ class BaseAPITest(base_db_test.OctaviaDBTestBase):
MEMBER_PATH = MEMBERS_PATH + '/{member_id}' MEMBER_PATH = MEMBERS_PATH + '/{member_id}'
# /lbaas/healthmonitors # /lbaas/healthmonitors
HMS_PATH = '/healthmonitors' HMS_PATH = '/lbaas/healthmonitors'
HM_PATH = HMS_PATH + '/{healthmonitor_id}' HM_PATH = HMS_PATH + '/{healthmonitor_id}'
# /lbaas/l7policies # /lbaas/l7policies
L7POLICIES_PATH = '/l7policies' L7POLICIES_PATH = '/lbaas/l7policies'
L7POLICY_PATH = L7POLICIES_PATH + '/{l7policy_id}' L7POLICY_PATH = L7POLICIES_PATH + '/{l7policy_id}'
L7RULES_PATH = L7POLICY_PATH + '/rules' L7RULES_PATH = L7POLICY_PATH + '/rules'
L7RULE_PATH = L7RULES_PATH + '/{l7rule_id}' L7RULE_PATH = L7RULES_PATH + '/{l7rule_id}'
QUOTAS_PATH = '/quotas' QUOTAS_PATH = '/lbaas/quotas'
QUOTA_PATH = QUOTAS_PATH + '/{project_id}' QUOTA_PATH = QUOTAS_PATH + '/{project_id}'
QUOTA_DEFAULT_PATH = QUOTAS_PATH + '/{project_id}/default' QUOTA_DEFAULT_PATH = QUOTAS_PATH + '/{project_id}/default'
AMPHORAE_PATH = '/octavia/amphorae'
AMPHORA_PATH = AMPHORAE_PATH + '/{amphora_id}'
NOT_AUTHORIZED_BODY = { NOT_AUTHORIZED_BODY = {
'debuginfo': None, 'faultcode': 'Client', 'debuginfo': None, 'faultcode': 'Client',
'faultstring': 'Policy does not allow this request to be performed.'} '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.