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:
parent
b8e0f5e763
commit
7f1c5011ed
@ -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
|
||||
|
132
api-ref/source/v2/amphora.inc
Normal file
132
api-ref/source/v2/amphora.inc
Normal 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
|
1
api-ref/source/v2/examples/amphora-list-curl
Normal file
1
api-ref/source/v2/examples/amphora-list-curl
Normal 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
|
38
api-ref/source/v2/examples/amphora-list-response.json
Normal file
38
api-ref/source/v2/examples/amphora-list-response.json
Normal 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
|
||||
}
|
||||
]
|
||||
}
|
1
api-ref/source/v2/examples/amphora-show-curl
Normal file
1
api-ref/source/v2/examples/amphora-show-curl
Normal 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
|
19
api-ref/source/v2/examples/amphora-show-response.json
Normal file
19
api-ref/source/v2/examples/amphora-show-response.json
Normal 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
|
||||
}
|
||||
}
|
@ -50,3 +50,8 @@ L7 Rules
|
||||
Quotas
|
||||
------
|
||||
.. include:: quota.inc
|
||||
|
||||
--------
|
||||
Amphorae
|
||||
--------
|
||||
.. include:: amphora.inc
|
||||
|
@ -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:
|
||||
|
@ -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()
|
||||
|
80
octavia/api/v2/controllers/amphora.py
Normal file
80
octavia/api/v2/controllers/amphora.py
Normal 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)
|
57
octavia/api/v2/types/amphora.py
Normal file
57
octavia/api/v2/types/amphora.py
Normal 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])
|
@ -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'
|
||||
|
@ -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
|
||||
|
@ -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"),
|
||||
|
@ -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(
|
||||
|
@ -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(),
|
||||
)
|
||||
|
36
octavia/policies/amphora.py
Normal file
36
octavia/policies/amphora.py
Normal 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
|
@ -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.'}
|
||||
|
289
octavia/tests/functional/api/v2/test_amphora.py
Normal file
289
octavia/tests/functional/api/v2/test_amphora.py
Normal 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))
|
@ -0,0 +1,5 @@
|
||||
---
|
||||
features:
|
||||
- |
|
||||
Added a new endpoint /v2.0/octavia/amphorae to expose internal details
|
||||
about amphorae. This endpoint is admin only.
|
Loading…
Reference in New Issue
Block a user