Remove v1 API and associated code
Includes some updates to docs and configs and related files to remove references to neutron-lbaas. Also remove handlers. Change-Id: I3082962841d3b645f3cbd1a6b41fc7fb28dcf7e6
This commit is contained in:
@@ -1,13 +1,5 @@
|
||||
{
|
||||
"versions": [{
|
||||
"status": "DEPRECATED",
|
||||
"updated": "2014-12-11T00:00:00Z",
|
||||
"id": "v1",
|
||||
"links": [{
|
||||
"href": "http://10.21.21.53/load-balancer/v1",
|
||||
"rel": "self"
|
||||
}]
|
||||
}, {
|
||||
"status": "SUPPORTED",
|
||||
"updated": "2016-12-11T00:00:00Z",
|
||||
"id": "v2.0",
|
||||
|
||||
@@ -15,15 +15,10 @@ Supported API version
|
||||
|
||||
None
|
||||
|
||||
Deprecated API version
|
||||
|
||||
:doc:`v1/octaviaapi`
|
||||
|
||||
.. toctree::
|
||||
:hidden:
|
||||
|
||||
v2/index
|
||||
v1/octaviaapi
|
||||
|
||||
Octavia API minor releases are additive to the API major revision and share
|
||||
the same URL path. Minor revision changes to the API are called out in the API
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -270,15 +270,6 @@ function octavia_configure {
|
||||
iniset $OCTAVIA_CONF oslo_messaging rpc_thread_pool_size 2
|
||||
iniset $OCTAVIA_CONF oslo_messaging topic octavia_prov
|
||||
|
||||
# TODO(nmagnezi): Remove this when neutron-lbaas gets deprecated
|
||||
# Setting neutron request_poll_timeout
|
||||
iniset $NEUTRON_CONF octavia request_poll_timeout 3000
|
||||
if [[ "$WSGI_MODE" == "uwsgi" ]]; then
|
||||
iniadd $NEUTRON_CONF octavia base_url "$OCTAVIA_PROTOCOL://$SERVICE_HOST/$OCTAVIA_SERVICE_TYPE"
|
||||
else
|
||||
iniadd $NEUTRON_CONF octavia base_url "$OCTAVIA_PROTOCOL://$SERVICE_HOST:$OCTAVIA_PORT/"
|
||||
fi
|
||||
|
||||
# Uncomment other default options
|
||||
iniuncomment $OCTAVIA_CONF haproxy_amphora base_path
|
||||
iniuncomment $OCTAVIA_CONF haproxy_amphora base_cert_dir
|
||||
|
||||
@@ -90,11 +90,7 @@ GITREPO["octavia-lib"]=${OCTAVIA_LIB_REPO:-${GIT_BASE}/openstack/octavia-lib.git
|
||||
GITBRANCH["octavia-lib"]=${OCTAVIA_LIB_BRANCH:-master}
|
||||
GITDIR["octavia-lib"]=$DEST/octavia-lib
|
||||
|
||||
NEUTRON_LBAAS_DIR=$DEST/neutron-lbaas
|
||||
NEUTRON_LBAAS_CONF=$NEUTRON_CONF_DIR/neutron_lbaas.conf
|
||||
OCTAVIA_SERVICE_PROVIDER=${OCTAVIA_SERVICE_PROVIDER:-"LOADBALANCERV2:Octavia:neutron_lbaas.drivers.octavia.driver.OctaviaDriver:default"}
|
||||
NEUTRON_ANY=${NEUTRON_ANY:-"q-svc neutron-api"}
|
||||
LBAAS_V2=${LBAAS_V2:-"neutron-lbaasv2"}
|
||||
|
||||
# HA-deployment related settings
|
||||
OCTAVIA_USE_PREGENERATED_SSH_KEY=${OCTAVIA_USE_PREGENERATED_SSH_KEY:-"False"}
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Tempest documentation build configuration file, created by
|
||||
# Octavia documentation build configuration file, created by
|
||||
# sphinx-quickstart on Tue May 21 17:43:32 2013.
|
||||
#
|
||||
# This file is execfile()d with the current directory set to its containing
|
||||
|
||||
@@ -72,8 +72,7 @@ Deployment
|
||||
2. Copy ``devstack/contrib/new-octavia-devstack.sh`` from this source
|
||||
repository onto that host.
|
||||
3. Run new-octavia-devstack.sh as root.
|
||||
4. Deploy loadbalancers, listeners, etc. as you would with any Neutron LBaaS v2
|
||||
enabled cloud.
|
||||
4. Deploy loadbalancers, listeners, etc.
|
||||
|
||||
|
||||
Running Octavia in production
|
||||
@@ -125,7 +124,7 @@ For the purposes of this guide, we will therefore assume the following core
|
||||
components have already been set up for your production OpenStack environment:
|
||||
|
||||
* Nova
|
||||
* Neutron (with Neutron LBaaS v2)
|
||||
* Neutron
|
||||
* Glance
|
||||
* Barbican (if TLS offloading functionality is enabled)
|
||||
* Keystone
|
||||
@@ -138,11 +137,8 @@ Production Deployment Walkthrough
|
||||
|
||||
Create Octavia User
|
||||
___________________
|
||||
By default Octavia will use the 'neutron' user for keystone authentication, and
|
||||
the admin user for interactions with all other services. However, it doesn't
|
||||
actually share neutron's database or otherwise access Neutron outside of
|
||||
Neutron's API, so a dedicated 'octavia' keystone user should generally be
|
||||
created for Octavia to use.
|
||||
By default Octavia will use the 'octavia' user for keystone authentication, and
|
||||
the admin user for interactions with all other services.
|
||||
|
||||
You must:
|
||||
|
||||
@@ -225,14 +221,8 @@ Running multiple instances of the individual Octavia controller components on
|
||||
separate physical hosts is recommended in order to provide scalability and
|
||||
availability of the controller software.
|
||||
|
||||
One important security note: In 0.9 of Octavia, the Octavia API is designed to
|
||||
be consumed only by the Neutron-LBaaS v2 Octavia driver. As such, there is
|
||||
presently no authentication required to use the Octavia API, and therefore the
|
||||
Octavia API should only be accessible on trusted network segments
|
||||
(specifically, the segment that runs the neutron-services daemons.)
|
||||
|
||||
The Octavia controller presently consists of several components which may be
|
||||
split across several physical machines. For the 0.9 release of Octavia, the
|
||||
split across several physical machines. For the 4.0 release of Octavia, the
|
||||
important (and potentially separable) components are the controller worker,
|
||||
housekeeper, health manager and API controller. Please see the component
|
||||
diagrams elsewhere in this repository's documentation for detailed descriptions
|
||||
@@ -253,7 +243,7 @@ components need access to outside resources:
|
||||
| housekeeper | Yes | Yes | No |
|
||||
+-------------------+------------+----------+----------------+
|
||||
|
||||
In addition to talking to each other via OSLO messaging, various controller
|
||||
In addition to talking to each other via Oslo messaging, various controller
|
||||
components must also communicate with other OpenStack components, like nova,
|
||||
neutron, barbican, etc. via their APIs.
|
||||
|
||||
@@ -438,46 +428,22 @@ You must:
|
||||
* Make sure each Octavia controller component is started appropriately.
|
||||
|
||||
|
||||
Configuring Neutron LBaaS
|
||||
_________________________
|
||||
This is fairly straightforward. Neutron LBaaS needs to be directed to use the
|
||||
Octavia service provider. There should be a line like the following in
|
||||
``/etc/neutron/neutron_lbaas.conf`` file's ``[service providers]`` section:
|
||||
|
||||
::
|
||||
|
||||
service_provider = LOADBALANCERV2:Octavia:neutron_lbaas.drivers.octavia.driver.OctaviaDriver:default
|
||||
|
||||
In addition to the above you must add the octavia API ``base_url`` to the
|
||||
``[octavia]`` section of ``/etc/neutron/neutron.conf``. For example:
|
||||
|
||||
::
|
||||
|
||||
[octavia]
|
||||
base_url=http://127.0.0.1:9876
|
||||
|
||||
You must:
|
||||
|
||||
* Update ``/etc/neutron/neutron_lbaas.conf`` as described above.
|
||||
* Add the octavia API URL to ``/etc/neutron/neutron.conf``.
|
||||
|
||||
|
||||
Install Neutron-LBaaS v2 extension in Horizon
|
||||
Install Octavia extension in Horizon
|
||||
_____________________________________________
|
||||
This isn't strictly necessary for all cloud installations, however, if yours
|
||||
makes use of the Horizon GUI interface for tenants, it is probably also a good
|
||||
idea to make sure that it is configured with the Neutron-LBaaS v2 extension.
|
||||
idea to make sure that it is configured with the Octavia extension.
|
||||
|
||||
You may:
|
||||
|
||||
* Install the neutron-lbaasv2 GUI extension in Horizon
|
||||
* Install the octavia GUI extension in Horizon
|
||||
|
||||
|
||||
Test deployment
|
||||
_______________
|
||||
If all of the above instructions have been followed, it should now be possible
|
||||
to deploy load balancing services using the python neutronclient CLI,
|
||||
communicating with the neutron-lbaas v2 API.
|
||||
to deploy load balancing services using the OpenStack CLI,
|
||||
communicating with the Octavia v2 API.
|
||||
|
||||
Example:
|
||||
|
||||
|
||||
@@ -1,672 +0,0 @@
|
||||
..
|
||||
Copyright (c) 2016 IBM
|
||||
|
||||
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.
|
||||
|
||||
===============================================================
|
||||
Basic Load Balancing Cookbook Using Neutron Client (deprecated)
|
||||
===============================================================
|
||||
|
||||
.. warning:: The neutron client used in this document is deprecated. We
|
||||
strongly encourage you to use the OpenStack Client and Octavia
|
||||
OpenStack Client plugin instead. This document is being maintained
|
||||
for deployments still using neutron-lbaas and the neutron client.
|
||||
|
||||
Introduction
|
||||
============
|
||||
This document contains several examples of using basic load balancing services
|
||||
as a tenant or "regular" cloud user.
|
||||
|
||||
For the purposes of this guide we assume that the neutron and barbican
|
||||
command-line interfaces are going to be used to configure all features of
|
||||
Neutron LBaaS with an Octavia back-end. In order to keep these examples short,
|
||||
we also assume that tasks not directly associated with deploying load balancing
|
||||
services have already been accomplished. This might include such things as
|
||||
deploying and configuring web servers, setting up Neutron networks, obtaining
|
||||
TLS certificates from a trusted provider, and so on. A description of the
|
||||
starting conditions is given in each example below.
|
||||
|
||||
Please also note that this guide assumes you are familiar with the specific
|
||||
load balancer terminology defined in the :doc:`../../reference/glossary`. For a
|
||||
description of load balancing itself and the Octavia project, please see:
|
||||
:doc:`../../reference/introduction`.
|
||||
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
Deploy a basic HTTP load balancer
|
||||
---------------------------------
|
||||
While this is technically the simplest complete load balancing solution that
|
||||
can be deployed, we recommend deploying HTTP load balancers with a health
|
||||
monitor to ensure back-end member availability. See
|
||||
:ref:`basic-lb-with-hm-neutron` below.
|
||||
|
||||
**Scenario description**:
|
||||
|
||||
* Back-end servers 192.0.2.10 and 192.0.2.11 on subnet *private-subnet* have
|
||||
been configured with an HTTP application on TCP port 80.
|
||||
* Subnet *public-subnet* is a shared external subnet created by the cloud
|
||||
operator which is reachable from the internet.
|
||||
* We want to configure a basic load balancer that is accessible from the
|
||||
internet, which distributes web requests to the back-end servers.
|
||||
|
||||
**Solution**:
|
||||
|
||||
1. Create load balancer *lb1* on subnet *public-subnet*.
|
||||
2. Create listener *listener1*.
|
||||
3. Create pool *pool1* as *listener1*'s default pool.
|
||||
4. Add members 192.0.2.10 and 192.0.2.11 on *private-subnet* to *pool1*.
|
||||
|
||||
**CLI commands**:
|
||||
|
||||
::
|
||||
|
||||
neutron lbaas-loadbalancer-create --name lb1 public-subnet
|
||||
# Re-run the following until lb1 shows ACTIVE and ONLINE statuses:
|
||||
neutron lbaas-loadbalancer-show lb1
|
||||
neutron lbaas-listener-create --name listener1 --loadbalancer lb1 --protocol HTTP --protocol-port 80
|
||||
neutron lbaas-pool-create --name pool1 --lb-algorithm ROUND_ROBIN --listener listener1 --protocol HTTP
|
||||
neutron lbaas-member-create --subnet private-subnet --address 192.0.2.10 --protocol-port 80 pool1
|
||||
neutron lbaas-member-create --subnet private-subnet --address 192.0.2.11 --protocol-port 80 pool1
|
||||
|
||||
|
||||
.. _basic-lb-with-hm-neutron:
|
||||
|
||||
Deploy a basic HTTP load balancer with a health monitor
|
||||
-------------------------------------------------------
|
||||
This is the simplest recommended load balancing solution for HTTP applications.
|
||||
This solution is appropriate for operators with provider networks that are not
|
||||
compatible with Neutron floating-ip functionality (such as IPv6 networks).
|
||||
However, if you need to retain control of the external IP through which a load
|
||||
balancer is accessible, even if the load balancer needs to be destroyed or
|
||||
recreated, it may be more appropriate to deploy your basic load balancer using
|
||||
a floating IP. See :ref:`basic-lb-with-hm-and-fip-neutron` below.
|
||||
|
||||
**Scenario description**:
|
||||
|
||||
* Back-end servers 192.0.2.10 and 192.0.2.11 on subnet *private-subnet* have
|
||||
been configured with an HTTP application on TCP port 80.
|
||||
* These back-end servers have been configured with a health check at the URL
|
||||
path "/healthcheck". See :ref:`http-heath-monitors-neutron` below.
|
||||
* Subnet *public-subnet* is a shared external subnet created by the cloud
|
||||
operator which is reachable from the internet.
|
||||
* We want to configure a basic load balancer that is accessible from the
|
||||
internet, which distributes web requests to the back-end servers, and which
|
||||
checks the "/healthcheck" path to ensure back-end member health.
|
||||
|
||||
**Solution**:
|
||||
|
||||
1. Create load balancer *lb1* on subnet *public-subnet*.
|
||||
2. Create listener *listener1*.
|
||||
3. Create pool *pool1* as *listener1*'s default pool.
|
||||
4. Create a health monitor on *pool1* which tests the "/healthcheck" path.
|
||||
5. Add members 192.0.2.10 and 192.0.2.11 on *private-subnet* to *pool1*.
|
||||
|
||||
**CLI commands**:
|
||||
|
||||
::
|
||||
|
||||
neutron lbaas-loadbalancer-create --name lb1 public-subnet
|
||||
# Re-run the following until lb1 shows ACTIVE and ONLINE statuses:
|
||||
neutron lbaas-loadbalancer-show lb1
|
||||
neutron lbaas-listener-create --name listener1 --loadbalancer lb1 --protocol HTTP --protocol-port 80
|
||||
neutron lbaas-pool-create --name pool1 --lb-algorithm ROUND_ROBIN --listener listener1 --protocol HTTP
|
||||
neutron lbaas-healthmonitor-create --delay 5 --max-retries 4 --timeout 10 --type HTTP --url_path /healthcheck --pool pool1
|
||||
neutron lbaas-member-create --subnet private-subnet --address 192.0.2.10 --protocol-port 80 pool1
|
||||
neutron lbaas-member-create --subnet private-subnet --address 192.0.2.11 --protocol-port 80 pool1
|
||||
|
||||
|
||||
.. _basic-lb-with-hm-and-fip-neutron:
|
||||
|
||||
Deploy a basic HTTP load balancer using a floating IP
|
||||
-----------------------------------------------------
|
||||
It can be beneficial to use a floating IP when setting up a load balancer's VIP
|
||||
in order to ensure you retain control of the IP that gets assigned as the
|
||||
floating IP in case the load balancer needs to be destroyed, moved, or
|
||||
recreated.
|
||||
|
||||
Note that this is not possible to do with IPv6 load balancers as floating IPs
|
||||
do not work with IPv6. Further, there is currently a bug in Neutron Distributed
|
||||
Virtual Routing (DVR) which prevents floating IPs from working correctly when
|
||||
DVR is in use. See: https://bugs.launchpad.net/neutron/+bug/1583694
|
||||
|
||||
**Scenario description**:
|
||||
|
||||
* Back-end servers 192.0.2.10 and 192.0.2.11 on subnet *private-subnet* have
|
||||
been configured with an HTTP application on TCP port 80.
|
||||
* These back-end servers have been configured with a health check at the URL
|
||||
path "/healthcheck". See :ref:`http-heath-monitors-neutron` below.
|
||||
* Neutron network *public* is a shared external network created by the cloud
|
||||
operator which is reachable from the internet.
|
||||
* We want to configure a basic load balancer that is accessible from the
|
||||
internet, which distributes web requests to the back-end servers, and which
|
||||
checks the "/healthcheck" path to ensure back-end member health. Further, we
|
||||
want to do this using a floating IP.
|
||||
|
||||
**Solution**:
|
||||
|
||||
1. Create load balancer *lb1* on subnet *private-subnet*.
|
||||
2. Create listener *listener1*.
|
||||
3. Create pool *pool1* as *listener1*'s default pool.
|
||||
4. Create a health monitor on *pool1* which tests the "/healthcheck" path.
|
||||
5. Add members 192.0.2.10 and 192.0.2.11 on *private-subnet* to *pool1*.
|
||||
6. Create a floating IP address on *public-subnet*.
|
||||
7. Associate this floating IP with the *lb1*'s VIP port.
|
||||
|
||||
**CLI commands**:
|
||||
|
||||
::
|
||||
|
||||
neutron lbaas-loadbalancer-create --name lb1 private-subnet
|
||||
# Re-run the following until lb1 shows ACTIVE and ONLINE statuses:
|
||||
neutron lbaas-loadbalancer-show lb1
|
||||
neutron lbaas-listener-create --name listener1 --loadbalancer lb1 --protocol HTTP --protocol-port 80
|
||||
neutron lbaas-pool-create --name pool1 --lb-algorithm ROUND_ROBIN --listener listener1 --protocol HTTP
|
||||
neutron lbaas-healthmonitor-create --delay 5 --max-retries 4 --timeout 10 --type HTTP --url_path /healthcheck --pool pool1
|
||||
neutron lbaas-member-create --subnet private-subnet --address 192.0.2.10 --protocol-port 80 pool1
|
||||
neutron lbaas-member-create --subnet private-subnet --address 192.0.2.11 --protocol-port 80 pool1
|
||||
neutron floatingip-create public
|
||||
# The following IDs should be visible in the output of previous commands
|
||||
neutron floatingip-associate <floating_ip_id> <load_balancer_vip_port_id>
|
||||
|
||||
|
||||
Deploy a basic HTTP load balancer with session persistence
|
||||
----------------------------------------------------------
|
||||
**Scenario description**:
|
||||
|
||||
* Back-end servers 192.0.2.10 and 192.0.2.11 on subnet *private-subnet* have
|
||||
been configured with an HTTP application on TCP port 80.
|
||||
* The application is written such that web clients should always be directed to
|
||||
the same back-end server throughout their web session, based on an
|
||||
application cookie inserted by the web application named 'PHPSESSIONID'.
|
||||
* These back-end servers have been configured with a health check at the URL
|
||||
path "/healthcheck". See :ref:`http-heath-monitors-neutron` below.
|
||||
* Subnet *public-subnet* is a shared external subnet created by the cloud
|
||||
operator which is reachable from the internet.
|
||||
* We want to configure a basic load balancer that is accessible from the
|
||||
internet, which distributes web requests to the back-end servers, persists
|
||||
sessions using the PHPSESSIONID as a key, and which checks the "/healthcheck"
|
||||
path to ensure back-end member health.
|
||||
|
||||
**Solution**:
|
||||
|
||||
1. Create load balancer *lb1* on subnet *public-subnet*.
|
||||
2. Create listener *listener1*.
|
||||
3. Create pool *pool1* as *listener1*'s default pool which defines session
|
||||
persistence on the 'PHPSESSIONID' cookie.
|
||||
4. Create a health monitor on *pool1* which tests the "/healthcheck" path.
|
||||
5. Add members 192.0.2.10 and 192.0.2.11 on *private-subnet* to *pool1*.
|
||||
|
||||
**CLI commands**:
|
||||
|
||||
::
|
||||
|
||||
neutron lbaas-loadbalancer-create --name lb1 public-subnet
|
||||
# Re-run the following until lb1 shows ACTIVE and ONLINE statuses:
|
||||
neutron lbaas-loadbalancer-show lb1
|
||||
neutron lbaas-listener-create --name listener1 --loadbalancer lb1 --protocol HTTP --protocol-port 80
|
||||
neutron lbaas-pool-create --name pool1 --lb-algorithm ROUND_ROBIN --listener listener1 --protocol HTTP --session-persistence type=APP_COOKIE,cookie_name=PHPSESSIONID
|
||||
neutron lbaas-healthmonitor-create --delay 5 --max-retries 4 --timeout 10 --type HTTP --url_path /healthcheck --pool pool1
|
||||
neutron lbaas-member-create --subnet private-subnet --address 192.0.2.10 --protocol-port 80 pool1
|
||||
neutron lbaas-member-create --subnet private-subnet --address 192.0.2.11 --protocol-port 80 pool1
|
||||
|
||||
|
||||
Deploy a TCP load balancer
|
||||
--------------------------
|
||||
This is generally suitable when load balancing a non-HTTP TCP-based service.
|
||||
|
||||
**Scenario description**:
|
||||
|
||||
* Back-end servers 192.0.2.10 and 192.0.2.11 on subnet *private-subnet* have
|
||||
been configured with an custom application on TCP port 23456
|
||||
* Subnet *public-subnet* is a shared external subnet created by the cloud
|
||||
operator which is reachable from the internet.
|
||||
* We want to configure a basic load balancer that is accessible from the
|
||||
internet, which distributes requests to the back-end servers.
|
||||
* We want to employ a TCP health check to ensure that the back-end servers are
|
||||
available.
|
||||
|
||||
**Solution**:
|
||||
|
||||
1. Create load balancer *lb1* on subnet *public-subnet*.
|
||||
2. Create listener *listener1*.
|
||||
3. Create pool *pool1* as *listener1*'s default pool.
|
||||
4. Create a health monitor on *pool1* which probes *pool1*'s members' TCP
|
||||
service port.
|
||||
5. Add members 192.0.2.10 and 192.0.2.11 on *private-subnet* to *pool1*.
|
||||
|
||||
**CLI commands**:
|
||||
|
||||
::
|
||||
|
||||
neutron lbaas-loadbalancer-create --name lb1 public-subnet
|
||||
# Re-run the following until lb1 shows ACTIVE and ONLINE statuses:
|
||||
neutron lbaas-loadbalancer-show lb1
|
||||
neutron lbaas-listener-create --name listener1 --loadbalancer lb1 --protocol TCP --protocol-port 23456
|
||||
neutron lbaas-pool-create --name pool1 --lb-algorithm ROUND_ROBIN --listener listener1 --protocol TCP
|
||||
neutron lbaas-healthmonitor-create --delay 5 --max-retries 4 --timeout 10 --type TCP --pool pool1
|
||||
neutron lbaas-member-create --subnet private-subnet --address 192.0.2.10 --protocol-port 80 pool1
|
||||
neutron lbaas-member-create --subnet private-subnet --address 192.0.2.11 --protocol-port 80 pool1
|
||||
|
||||
|
||||
Deploy a non-terminated HTTPS load balancer
|
||||
-------------------------------------------
|
||||
A non-terminated HTTPS load balancer acts effectively like a generic TCP load
|
||||
balancer: The load balancer will forward the raw TCP traffic from the web
|
||||
client to the back-end servers without decrypting it. This means that the
|
||||
back-end servers themselves must be configured to terminate the HTTPS
|
||||
connection with the web clients, and in turn, the load balancer cannot insert
|
||||
headers into the HTTP session indicating the client IP address. (That is, to
|
||||
the back-end server, all web requests will appear to originate from the load
|
||||
balancer.) Also, advanced load balancer features (like Layer 7 functionality)
|
||||
cannot be used with non-terminated HTTPS.
|
||||
|
||||
**Scenario description**:
|
||||
|
||||
* Back-end servers 192.0.2.10 and 192.0.2.11 on subnet *private-subnet* have
|
||||
been configured with a TLS-encrypted web application on TCP port 443.
|
||||
* Subnet *public-subnet* is a shared external subnet created by the cloud
|
||||
operator which is reachable from the internet.
|
||||
* We want to configure a basic load balancer that is accessible from the
|
||||
internet, which distributes requests to the back-end servers.
|
||||
* We want to employ a TCP health check to ensure that the back-end servers are
|
||||
available.
|
||||
|
||||
**Solution**:
|
||||
|
||||
1. Create load balancer *lb1* on subnet *public-subnet*.
|
||||
2. Create listener *listener1*.
|
||||
3. Create pool *pool1* as *listener1*'s default pool.
|
||||
4. Create a health monitor on *pool1* which probes *pool1*'s members' TCP
|
||||
service port.
|
||||
5. Add members 192.0.2.10 and 192.0.2.11 on *private-subnet* to *pool1*.
|
||||
|
||||
**CLI commands**:
|
||||
|
||||
::
|
||||
|
||||
neutron lbaas-loadbalancer-create --name lb1 public-subnet
|
||||
# Re-run the following until lb1 shows ACTIVE and ONLINE statuses:
|
||||
neutron lbaas-loadbalancer-show lb1
|
||||
neutron lbaas-listener-create --name listener1 --loadbalancer lb1 --protocol HTTPS --protocol-port 443
|
||||
neutron lbaas-pool-create --name pool1 --lb-algorithm ROUND_ROBIN --listener listener1 --protocol HTTPS
|
||||
neutron lbaas-healthmonitor-create --delay 5 --max-retries 4 --timeout 10 --type TCP --pool pool1
|
||||
neutron lbaas-member-create --subnet private-subnet --address 192.0.2.10 --protocol-port 443 pool1
|
||||
neutron lbaas-member-create --subnet private-subnet --address 192.0.2.11 --protocol-port 443 pool1
|
||||
|
||||
|
||||
.. _basic-tls-terminated-listener-neutron:
|
||||
|
||||
Deploy a TLS-terminated HTTPS load balancer
|
||||
-------------------------------------------
|
||||
With a TLS-terminated HTTPS load balancer, web clients communicate with the
|
||||
load balancer over TLS protocols. The load balancer terminates the TLS session
|
||||
and forwards the decrypted requests to the back-end servers. By terminating the
|
||||
TLS session on the load balancer, we offload the CPU-intensive encryption work
|
||||
to the load balancer, and enable the possibility of using advanced load
|
||||
balancer features, like Layer 7 features and header manipulation.
|
||||
|
||||
**Scenario description**:
|
||||
|
||||
* Back-end servers 192.0.2.10 and 192.0.2.11 on subnet *private-subnet* have
|
||||
been configured with regular HTTP application on TCP port 80.
|
||||
* These back-end servers have been configured with a health check at the URL
|
||||
path "/healthcheck". See :ref:`http-heath-monitors-neutron` below.
|
||||
* Subnet *public-subnet* is a shared external subnet created by the cloud
|
||||
operator which is reachable from the internet.
|
||||
* A TLS certificate, key, and intermediate certificate chain for
|
||||
www.example.com have been obtained from an external certificate authority.
|
||||
These now exist in the files server.crt, server.key, and ca-chain.p7b in the
|
||||
current directory. The key and certificate are PEM-encoded, and the
|
||||
intermediate certificate chain is PKCS7 PEM encoded. The key is not encrypted
|
||||
with a passphrase.
|
||||
* The *admin* user on this cloud installation has keystone ID *admin_id*
|
||||
* We want to configure a TLS-terminated HTTPS load balancer that is accessible
|
||||
from the internet using the key and certificate mentioned above, which
|
||||
distributes requests to the back-end servers over the non-encrypted HTTP
|
||||
protocol.
|
||||
|
||||
**Solution**:
|
||||
|
||||
1. Create barbican *secret* resources for the certificate, key, and
|
||||
intermediate certificate chain. We will call these *cert1*, *key1*, and
|
||||
*intermediates1* respectively.
|
||||
2. Create a *secret container* resource combining all of the above. We will
|
||||
call this *tls_container1*.
|
||||
3. Grant the *admin* user access to all the *secret* and *secret container*
|
||||
barbican resources above.
|
||||
4. Create load balancer *lb1* on subnet *public-subnet*.
|
||||
5. Create listener *listener1* as a TERMINATED_HTTPS listener referencing
|
||||
*tls_container1* as its default TLS container.
|
||||
6. Create pool *pool1* as *listener1*'s default pool.
|
||||
7. Add members 192.0.2.10 and 192.0.2.11 on *private-subnet* to *pool1*.
|
||||
|
||||
**CLI commands**:
|
||||
|
||||
::
|
||||
|
||||
openstack secret store --name='cert1' --payload-content-type='text/plain' --payload="$(cat server.crt)"
|
||||
openstack secret store --name='key1' --payload-content-type='text/plain' --payload="$(cat server.key)"
|
||||
openstack secret store --name='intermediates1' --payload-content-type='text/plain' --payload="$(cat ca-chain.p7b)"
|
||||
openstack secret container create --name='tls_container1' --type='certificate' --secret="certificate=$(openstack secret list | awk '/ cert1 / {print $2}')" --secret="private_key=$(openstack secret list | awk '/ key1 / {print $2}')" --secret="intermediates=$(openstack secret list | awk '/ intermediates1 / {print $2}')"
|
||||
neutron lbaas-loadbalancer-create --name lb1 public-subnet
|
||||
# Re-run the following until lb1 shows ACTIVE and ONLINE statuses:
|
||||
neutron lbaas-loadbalancer-show lb1
|
||||
neutron lbaas-listener-create --loadbalancer lb1 --protocol-port 443 --protocol TERMINATED_HTTPS --name listener1 --default-tls-container=$(openstack secret container list | awk '/ tls_container1 / {print $2}')
|
||||
neutron lbaas-pool-create --name pool1 --lb-algorithm ROUND_ROBIN --listener listener1 --protocol HTTP
|
||||
neutron lbaas-member-create --subnet private-subnet --address 192.0.2.10 --protocol-port 80 pool1
|
||||
neutron lbaas-member-create --subnet private-subnet --address 192.0.2.11 --protocol-port 80 pool1
|
||||
|
||||
|
||||
Deploy a TLS-terminated HTTPS load balancer with SNI
|
||||
----------------------------------------------------
|
||||
This example is exactly like :ref:`basic-tls-terminated-listener-neutron`,
|
||||
except that we have multiple TLS certificates that we would like to use on
|
||||
the same listener using Server Name Indication (SNI) technology.
|
||||
|
||||
**Scenario description**:
|
||||
|
||||
* Back-end servers 192.0.2.10 and 192.0.2.11 on subnet *private-subnet* have
|
||||
been configured with regular HTTP application on TCP port 80.
|
||||
* These back-end servers have been configured with a health check at the URL
|
||||
path "/healthcheck". See :ref:`http-heath-monitors-neutron` below.
|
||||
* Subnet *public-subnet* is a shared external subnet created by the cloud
|
||||
operator which is reachable from the internet.
|
||||
* TLS certificates, keys, and intermediate certificate chains for
|
||||
www.example.com and www2.example.com have been obtained from an external
|
||||
certificate authority. These now exist in the files server.crt, server.key,
|
||||
ca-chain.p7b, server2.crt, server2-encrypted.key, and ca-chain2.p7b in the
|
||||
current directory. The keys and certificates are PEM-encoded, and the
|
||||
intermediate certificate chains are PKCS7 PEM encoded.
|
||||
* The key for www.example.com is not encrypted with a passphrase.
|
||||
* The key for www2.example.com is encrypted with the passphrase "abc123".
|
||||
* The *admin* user on this cloud installation has keystone ID *admin_id*
|
||||
* We want to configure a TLS-terminated HTTPS load balancer that is accessible
|
||||
from the internet using the keys and certificates mentioned above, which
|
||||
distributes requests to the back-end servers over the non-encrypted HTTP
|
||||
protocol.
|
||||
* If a web client connects that is not SNI capable, we want the load balancer
|
||||
to respond with the certificate for www.example.com.
|
||||
|
||||
**Solution**:
|
||||
|
||||
1. Create barbican *secret* resources for the certificates, keys, and
|
||||
intermediate certificate chains. We will call these *cert1*, *key1*,
|
||||
*intermediates1*, *cert2*, *key2* and *intermediates2* respectively.
|
||||
2. Create a barbican *secret* resource *passphrase2* for the passphrase for
|
||||
*key2*
|
||||
3. Create *secret container* resources combining the above appropriately. We
|
||||
will call these *tls_container1* and *tls_container2*.
|
||||
4. Grant the *admin* user access to all the *secret* and *secret container*
|
||||
barbican resources above.
|
||||
5. Create load balancer *lb1* on subnet *public-subnet*.
|
||||
6. Create listener *listener1* as a TERMINATED_HTTPS listener referencing
|
||||
*tls_container1* as its default TLS container, and referencing both
|
||||
*tls_container1* and *tls_container2* using SNI.
|
||||
7. Create pool *pool1* as *listener1*'s default pool.
|
||||
8. Add members 192.0.2.10 and 192.0.2.11 on *private-subnet* to *pool1*.
|
||||
|
||||
**CLI commands**:
|
||||
|
||||
::
|
||||
|
||||
openstack secret store --name='cert1' --payload-content-type='text/plain' --payload="$(cat server.crt)"
|
||||
openstack secret store --name='key1' --payload-content-type='text/plain' --payload="$(cat server.key)"
|
||||
openstack secret store --name='intermediates1' --payload-content-type='text/plain' --payload="$(cat ca-chain.p7b)"
|
||||
openstack secret container create --name='tls_container1' --type='certificate' --secret="certificate=$(openstack secret list | awk '/ cert1 / {print $2}')" --secret="private_key=$(openstack secret list | awk '/ key1 / {print $2}')" --secret="intermediates=$(openstack secret list | awk '/ intermediates1 / {print $2}')"
|
||||
openstack secret store --name='cert2' --payload-content-type='text/plain' --payload="$(cat server2.crt)"
|
||||
openstack secret store --name='key2' --payload-content-type='text/plain' --payload="$(cat server2-encrypted.key)"
|
||||
openstack secret store --name='intermediates2' --payload-content-type='text/plain' --payload="$(cat ca-chain2.p7b)"
|
||||
openstack secret store --name='passphrase2' --payload-content-type='text/plain' --payload="abc123"
|
||||
openstack secret container create --name='tls_container2' --type='certificate' --secret="certificate=$(openstack secret list | awk '/ cert2 / {print $2}')" --secret="private_key=$(openstack secret list | awk '/ key2 / {print $2}')" --secret="intermediates=$(openstack secret list | awk '/ intermediates2 / {print $2}')" --secret="private_key_passphrase=$(openstack secret list | awk '/ passphrase2 / {print $2}')"
|
||||
neutron lbaas-loadbalancer-create --name lb1 public-subnet
|
||||
# Re-run the following until lb1 shows ACTIVE and ONLINE statuses:
|
||||
neutron lbaas-loadbalancer-show lb1
|
||||
neutron lbaas-listener-create --loadbalancer lb1 --protocol-port 443 --protocol TERMINATED_HTTPS --name listener1 --default-tls-container=$(openstack secret container list | awk '/ tls_container1 / {print $2}') --sni-container_refs $(openstack secret container list | awk '/ tls_container1 / {print $2}') $(openstack secret container list | awk '/ tls_container2 / {print $2}')
|
||||
neutron lbaas-pool-create --name pool1 --lb-algorithm ROUND_ROBIN --listener listener1 --protocol HTTP
|
||||
neutron lbaas-member-create --subnet private-subnet --address 192.0.2.10 --protocol-port 80 pool1
|
||||
neutron lbaas-member-create --subnet private-subnet --address 192.0.2.11 --protocol-port 80 pool1
|
||||
|
||||
|
||||
Deploy HTTP and TLS-terminated HTTPS load balancing on the same IP and backend
|
||||
------------------------------------------------------------------------------
|
||||
This example is exactly like :ref:`basic-tls-terminated-listener-neutron`,
|
||||
except that we would like to have both an HTTP and TERMINATED_HTTPS listener
|
||||
that use the same back-end pool (and therefore, probably respond with the
|
||||
exact same content regardless of whether the web client uses the HTTP or HTTPS
|
||||
protocol to connect).
|
||||
|
||||
Please note that if you wish all HTTP requests to be redirected to HTTPS (so
|
||||
that requests are only served via HTTPS, and attempts to access content over
|
||||
HTTP just get redirected to the HTTPS listener), then please see `the example
|
||||
<l7-cookbook-neutron.html#redirect-http-to-https-n>`__ in the
|
||||
:doc:`l7-cookbook-neutron`.
|
||||
|
||||
**Scenario description**:
|
||||
|
||||
* Back-end servers 192.0.2.10 and 192.0.2.11 on subnet *private-subnet* have
|
||||
been configured with regular HTTP application on TCP port 80.
|
||||
* These back-end servers have been configured with a health check at the URL
|
||||
path "/healthcheck". See :ref:`http-heath-monitors-neutron` below.
|
||||
* Subnet *public-subnet* is a shared external subnet created by the cloud
|
||||
operator which is reachable from the internet.
|
||||
* A TLS certificate, key, and intermediate certificate chain for
|
||||
www.example.com have been obtained from an external certificate authority.
|
||||
These now exist in the files server.crt, server.key, and ca-chain.p7b in the
|
||||
current directory. The key and certificate are PEM-encoded, and the
|
||||
intermediate certificate chain is PKCS7 PEM encoded. The key is not encrypted
|
||||
with a passphrase.
|
||||
* The *admin* user on this cloud installation has keystone ID *admin_id*
|
||||
* We want to configure a TLS-terminated HTTPS load balancer that is accessible
|
||||
from the internet using the key and certificate mentioned above, which
|
||||
distributes requests to the back-end servers over the non-encrypted HTTP
|
||||
protocol.
|
||||
* We also want to configure a HTTP load balancer on the same IP address as
|
||||
the above which serves the exact same content (ie. forwards to the same
|
||||
back-end pool) as the TERMINATED_HTTPS listener.
|
||||
|
||||
**Solution**:
|
||||
|
||||
1. Create barbican *secret* resources for the certificate, key, and
|
||||
intermediate certificate chain. We will call these *cert1*, *key1*, and
|
||||
*intermediates1* respectively.
|
||||
2. Create a *secret container* resource combining all of the above. We will
|
||||
call this *tls_container1*.
|
||||
3. Grant the *admin* user access to all the *secret* and *secret container*
|
||||
barbican resources above.
|
||||
4. Create load balancer *lb1* on subnet *public-subnet*.
|
||||
5. Create listener *listener1* as a TERMINATED_HTTPS listener referencing
|
||||
*tls_container1* as its default TLS container.
|
||||
6. Create pool *pool1* as *listener1*'s default pool.
|
||||
7. Add members 192.0.2.10 and 192.0.2.11 on *private-subnet* to *pool1*.
|
||||
8. Create listener *listener2* as an HTTP listener with *pool1* as its
|
||||
default pool.
|
||||
|
||||
**CLI commands**:
|
||||
|
||||
::
|
||||
|
||||
openstack secret store --name='cert1' --payload-content-type='text/plain' --payload="$(cat server.crt)"
|
||||
openstack secret store --name='key1' --payload-content-type='text/plain' --payload="$(cat server.key)"
|
||||
openstack secret store --name='intermediates1' --payload-content-type='text/plain' --payload="$(cat ca-chain.p7b)"
|
||||
openstack secret container create --name='tls_container1' --type='certificate' --secret="certificate=$(openstack secret list | awk '/ cert1 / {print $2}')" --secret="private_key=$(openstack secret list | awk '/ key1 / {print $2}')" --secret="intermediates=$(openstack secret list | awk '/ intermediates1 / {print $2}')"
|
||||
neutron lbaas-loadbalancer-create --name lb1 public-subnet
|
||||
# Re-run the following until lb1 shows ACTIVE and ONLINE statuses:
|
||||
neutron lbaas-loadbalancer-show lb1
|
||||
neutron lbaas-listener-create --loadbalancer lb1 --protocol-port 443 --protocol TERMINATED_HTTPS --name listener1 --default-tls-container=$(openstack secret container list | awk '/ tls_container1 / {print $2}')
|
||||
neutron lbaas-pool-create --name pool1 --lb-algorithm ROUND_ROBIN --listener listener1 --protocol HTTP
|
||||
neutron lbaas-member-create --subnet private-subnet --address 192.0.2.10 --protocol-port 80 pool1
|
||||
neutron lbaas-member-create --subnet private-subnet --address 192.0.2.11 --protocol-port 80 pool1
|
||||
neutron lbaas-listener-create --name listener2 --loadbalancer lb1 --protocol HTTP --protocol-port 80 --default-pool pool1
|
||||
|
||||
|
||||
.. _heath-monitor-best-practices-neutron:
|
||||
|
||||
Heath Monitor Best Practices
|
||||
============================
|
||||
While it is possible to set up a listener without a health monitor, if a
|
||||
back-end pool member goes down, Octavia will not remove the failed server from
|
||||
the pool until a considerable time has passed. This can lead to service
|
||||
disruption for web clients. Because of this, we recommend always configuring
|
||||
production load balancers to use a health monitor.
|
||||
|
||||
The health monitor itself is a process that does periodic health checks on each
|
||||
back-end server to pre-emptively detect failed servers and temporarily pull
|
||||
them out of the pool. Since effective health monitors depend as much on
|
||||
back-end application server configuration as proper load balancer
|
||||
configuration, some additional discussion of best practices is warranted here.
|
||||
|
||||
See also: `Octavia API Reference <https://developer.openstack.org/api-ref/load-balancer/>`_
|
||||
|
||||
|
||||
Heath monitor options
|
||||
---------------------
|
||||
All of the health monitors Octavia supports have the following configurable
|
||||
options:
|
||||
|
||||
* ``delay``: Number of seconds to wait between health checks.
|
||||
* ``timeout``: Number of seconds to wait for any given health check to
|
||||
complete. ``timeout`` should always be smaller than ``delay``.
|
||||
* ``max-retries``: Number of subsequent health checks a given back-end
|
||||
server must fail before it is considered *down*, or that a failed back-end
|
||||
server must pass to be considered *up* again.
|
||||
|
||||
|
||||
.. _http-heath-monitors-neutron:
|
||||
|
||||
HTTP health monitors
|
||||
--------------------
|
||||
In general, the application-side component of HTTP health checks are a part of
|
||||
the web application being load balanced. By default, Octavia will probe the "/"
|
||||
path on the application server. However, in many applications this is not
|
||||
appropriate because the "/" path ends up being a cached page, or causes the
|
||||
application server to do more work than is necessary for a basic health check.
|
||||
|
||||
In addition to the above options, HTTP health monitors also have the following
|
||||
options:
|
||||
|
||||
* ``url_path``: Path part of the URL that should be retrieved from the back-end
|
||||
server. By default this is "/".
|
||||
* ``http_method``: HTTP method that should be used to retrieve the
|
||||
``url_path``. By default this is "GET".
|
||||
* ``expected_codes``: List of HTTP status codes that indicate an OK health
|
||||
check. By default this is just "200".
|
||||
|
||||
Please keep the following best practices in mind when writing the code that
|
||||
generates the health check in your web application:
|
||||
|
||||
* The health monitor ``url_path`` should not require authentication to load.
|
||||
* By default the health monitor ``url_path`` should return a HTTP 200 OK status
|
||||
code to indicate a healthy server unless you specify alternate
|
||||
``expected_codes``.
|
||||
* The health check should do enough internal checks to ensure the application
|
||||
is healthy and no more. This may mean ensuring database or other external
|
||||
storage connections are up and running, server load is acceptable, the site
|
||||
is not in maintenance mode, and other tests specific to your application.
|
||||
* The page generated by the health check should be very light weight:
|
||||
|
||||
* It should return in a sub-second interval.
|
||||
* It should not induce significant load on the application server.
|
||||
|
||||
* The page generated by the health check should never be cached, though the
|
||||
code running the health check may reference cached data. For example, you may
|
||||
find it useful to run a more extensive health check via cron and store the
|
||||
results of this to disk. The code generating the page at the health monitor
|
||||
``url_path`` would incorporate the results of this cron job in the tests it
|
||||
performs.
|
||||
* Since Octavia only cares about the HTTP status code returned, and since
|
||||
health checks are run so frequently, it may make sense to use the "HEAD" or
|
||||
"OPTIONS" HTTP methods to cut down on unnecessary processing of a whole page.
|
||||
|
||||
|
||||
Other heath monitors
|
||||
--------------------
|
||||
Other health monitor types include ``PING``, ``TCP``, ``HTTPS``, and
|
||||
``TLS-HELLO``.
|
||||
|
||||
``PING`` health monitors send periodic ICMP PING requests to the back-end
|
||||
servers. Obviously, your back-end servers must be configured to allow PINGs in
|
||||
order for these health checks to pass.
|
||||
|
||||
``TCP`` health monitors open a TCP connection to the back-end server's protocol
|
||||
port. Your custom TCP application should be written to respond OK to the load
|
||||
balancer connecting, opening a TCP connection, and closing it again after the
|
||||
TCP handshake without sending any data.
|
||||
|
||||
``HTTPS`` health monitors operate exactly like HTTP health monitors, but with
|
||||
ssl back-end servers. Unfortunately, this causes problems if the servers are
|
||||
performing client certificate validation, as HAProxy won't have a valid cert.
|
||||
In this case, using ``TLS-HELLO`` type monitoring is an alternative.
|
||||
|
||||
``TLS-HELLO`` health monitors simply ensure the back-end server responds to
|
||||
SSLv3 client hello messages. It will not check any other health metrics, like
|
||||
status code or body contents.
|
||||
|
||||
|
||||
Intermediate certificate chains
|
||||
===============================
|
||||
Some TLS certificates require you to install an intermediate certificate chain
|
||||
in order for web client browsers to trust the certificate. This chain can take
|
||||
several forms, and is a file provided by the organization from whom you
|
||||
obtained your TLS certificate.
|
||||
|
||||
PEM-encoded chains
|
||||
------------------
|
||||
The simplest form of the intermediate chain is a PEM-encoded text file that
|
||||
either contains a sequence of individually-encoded PEM certificates, or a PEM
|
||||
encoded PKCS7 block(s). If this is the type of intermediate chain you have been
|
||||
provided, the file will contain either ``-----BEGIN PKCS7-----`` or
|
||||
``-----BEGIN CERTIFICATE-----`` near the top of the file, and one or more
|
||||
blocks of 64-character lines of ASCII text (that will look like gobbedlygook to
|
||||
a human). These files are also typically named with a ``.crt`` or ``.pem``
|
||||
extension.
|
||||
|
||||
To upload this type of intermediates chain to barbican, run a command similar
|
||||
to the following (assuming "intermediates-chain.pem" is the name of the file):
|
||||
|
||||
::
|
||||
|
||||
openstack secret store --name='intermediates1' --payload-content-type='text/plain' --payload="$(cat intermediates-chain.pem)"
|
||||
|
||||
DER-encoded chains
|
||||
------------------
|
||||
If the intermediates chain provided to you is a file that contains what appears
|
||||
to be random binary data, it is likely that it is a PKCS7 chain in DER format.
|
||||
These files also may be named with a ``.p7b`` extension. In order to use this
|
||||
intermediates chain, you can either convert it to a series of PEM-encoded
|
||||
certificates with the following command:
|
||||
|
||||
::
|
||||
|
||||
openssl pkcs7 -in intermediates-chain.p7b -inform DER -print_certs -out intermediates-chain.pem
|
||||
|
||||
...or convert it into a PEM-encoded PKCS7 bundle with the following command:
|
||||
|
||||
::
|
||||
|
||||
openssl pkcs7 -in intermediates-chain.p7b -inform DER -outform PEM -out intermediates-chain.pem
|
||||
|
||||
...or simply upload the binary DER file to barbican without conversion:
|
||||
|
||||
::
|
||||
|
||||
openstack secret store --name='intermediates1' --payload-content-type='application/octet-stream' --payload-content-encoding='base64' --payload="$(cat intermediates-chain.p7b | base64)"
|
||||
|
||||
In any case, if the file is not a PKCS7 DER bundle, then either of the above
|
||||
two openssl commands will fail.
|
||||
|
||||
Further reading
|
||||
===============
|
||||
For examples of using Layer 7 features for more advanced load balancing, please
|
||||
see: :doc:`l7-cookbook-neutron`
|
||||
@@ -1,361 +0,0 @@
|
||||
..
|
||||
Copyright (c) 2016 IBM
|
||||
|
||||
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.
|
||||
|
||||
==================================================
|
||||
Layer 7 Cookbook Using Neutron Client (deprecated)
|
||||
==================================================
|
||||
|
||||
.. warning:: The neutron client used in this document is deprecated. We
|
||||
strongly encourage you to use the OpenStack Client and Octavia
|
||||
OpenStack Client plugin instead. This document is being maintained
|
||||
for deployments still using neutron-lbaas and the neutron client.
|
||||
|
||||
Introduction
|
||||
============
|
||||
This document gives several examples of common L7 load balancer usage. For a
|
||||
description of L7 load balancing see: :doc:`l7`
|
||||
|
||||
For the purposes of this guide we assume that the neutron command-line
|
||||
interface is going to be used to configure all features of Neutron LBaaS with
|
||||
an Octavia back-end. Also, in order to keep these examples short, we assume
|
||||
that many non-L7 configuration tasks (such as deploying loadbalancers,
|
||||
listeners, pools, members, healthmonitors, etc.) have already been
|
||||
accomplished. A description of the starting conditions is given in each example
|
||||
below.
|
||||
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
.. _redirect-http-to-https-n:
|
||||
|
||||
Redirect *http://www.example.com/* to *https://www.example.com/*
|
||||
----------------------------------------------------------------
|
||||
**Scenario description**:
|
||||
|
||||
* Load balancer *lb1* has been set up with ``TERMINATED_HTTPS`` listener
|
||||
*tls_listener* on TCP port 443.
|
||||
* *tls_listener* has been populated with a default pool, members, etc.
|
||||
* *tls_listener* is available under the DNS name *https://www.example.com/*
|
||||
* We want any regular HTTP requests to TCP port 80 on *lb1* to be redirected
|
||||
to *tls_listener* on TCP port 443.
|
||||
|
||||
**Solution**:
|
||||
|
||||
1. Create listener *http_listener* as an HTTP listener on *lb1* port 80.
|
||||
2. Set up an L7 Policy *policy1* on *http_listener* with action
|
||||
``REDIRECT_TO_URL`` pointed at the URL *https://www.example.com/*
|
||||
3. Add an L7 Rule to *policy1* which matches all requests.
|
||||
|
||||
|
||||
**CLI commands**:
|
||||
|
||||
::
|
||||
|
||||
neutron lbaas-listener-create --name http_listener --loadbalancer lb1 --protocol HTTP --protocol-port 80
|
||||
neutron lbaas-l7policy-create --action REDIRECT_TO_URL --redirect-url https://www.example.com/ --listener http_listener --name policy1
|
||||
neutron lbaas-l7rule-create --type PATH --compare-type STARTS_WITH --value / policy1
|
||||
|
||||
|
||||
.. _send-requests-to-static-pool-n:
|
||||
|
||||
Send requests starting with /js or /images to *static_pool*
|
||||
-----------------------------------------------------------
|
||||
**Scenario description**:
|
||||
|
||||
* Listener *listener1* on load balancer *lb1* is set up to send all requests to
|
||||
its default_pool *pool1*.
|
||||
* We are introducing static content servers 10.0.0.10 and 10.0.0.11 on subnet
|
||||
*private-subnet*, and want any HTTP requests with a URL that starts with
|
||||
either "/js" or "/images" to be sent to those two servers instead of *pool1*.
|
||||
|
||||
**Solution**:
|
||||
|
||||
1. Create pool *static_pool* on *lb1*.
|
||||
2. Populate *static_pool* with the new back-end members.
|
||||
3. Create L7 Policy *policy1* with action ``REDIRECT_TO_POOL`` pointed at
|
||||
*static_pool*.
|
||||
4. Create an L7 Rule on *policy1* which looks for "/js" at the start of
|
||||
the request path.
|
||||
5. Create L7 Policy *policy2* with action ``REDIRECT_TO_POOL`` pointed at
|
||||
*static_pool*.
|
||||
6. Create an L7 Rule on *policy2* which looks for "/images" at the start
|
||||
of the request path.
|
||||
|
||||
**CLI commands**:
|
||||
|
||||
::
|
||||
|
||||
neutron lbaas-pool-create --name static_pool --lb-algorithm ROUND_ROBIN --loadbalancer lb1 --protocol HTTP
|
||||
neutron lbaas-member-create --subnet private-subnet --address 10.0.0.10 --protocol-port 80 static_pool
|
||||
neutron lbaas-member-create --subnet private-subnet --address 10.0.0.11 --protocol-port 80 static_pool
|
||||
neutron lbaas-l7policy-create --action REDIRECT_TO_POOL --redirect-pool static_pool --listener listener1 --name policy1
|
||||
neutron lbaas-l7rule-create --type PATH --compare-type STARTS_WITH --value /js policy1
|
||||
neutron lbaas-l7policy-create --action REDIRECT_TO_POOL --redirect-pool static_pool --listener listener1 --name policy2
|
||||
neutron lbaas-l7rule-create --type PATH --compare-type STARTS_WITH --value /images policy2
|
||||
|
||||
**Alternate solution** (using regular expressions):
|
||||
|
||||
1. Create pool *static_pool* on *lb1*.
|
||||
2. Populate *static_pool* with the new back-end members.
|
||||
3. Create L7 Policy *policy1* with action ``REDIRECT_TO_POOL`` pointed at
|
||||
*static_pool*.
|
||||
4. Create an L7 Rule on *policy1* which uses a regular expression to match
|
||||
either "/js" or "/images" at the start of the request path.
|
||||
|
||||
**CLI commands**:
|
||||
|
||||
::
|
||||
|
||||
neutron lbaas-pool-create --name static_pool --lb-algorithm ROUND_ROBIN --loadbalancer lb1 --protocol HTTP
|
||||
neutron lbaas-member-create --subnet private-subnet --address 10.0.0.10 --protocol-port 80 static_pool
|
||||
neutron lbaas-member-create --subnet private-subnet --address 10.0.0.11 --protocol-port 80 static_pool
|
||||
neutron lbaas-l7policy-create --action REDIRECT_TO_POOL --redirect-pool static_pool --listener listener1 --name policy1
|
||||
neutron lbaas-l7rule-create --type PATH --compare-type REGEX --value '^/(js|images)' policy1
|
||||
|
||||
|
||||
Send requests for *http://www2.example.com/* to *pool2*
|
||||
-------------------------------------------------------
|
||||
**Scenario description**:
|
||||
|
||||
* Listener *listener1* on load balancer *lb1* is set up to send all requests to
|
||||
its default_pool *pool1*.
|
||||
* We have set up a new pool *pool2* on *lb1* and want any requests using the
|
||||
HTTP/1.1 hostname *www2.example.com* to be sent to *pool2* instead.
|
||||
|
||||
**Solution**:
|
||||
|
||||
1. Create L7 Policy *policy1* with action ``REDIRECT_TO_POOL`` pointed at
|
||||
*pool2*.
|
||||
2. Create an L7 Rule on *policy1* which matches the hostname
|
||||
*www2.example.com*.
|
||||
|
||||
**CLI commands**:
|
||||
|
||||
::
|
||||
|
||||
neutron lbaas-l7policy-create --action REDIRECT_TO_POOL --redirect-pool pool2 --listener listener1 --name policy1
|
||||
neutron lbaas-l7rule-create --type HOST_NAME --compare-type EQUAL_TO --value www2.example.com policy1
|
||||
|
||||
|
||||
Send requests for *\*.example.com* to *pool2*
|
||||
---------------------------------------------
|
||||
**Scenario description**:
|
||||
|
||||
* Listener *listener1* on load balancer *lb1* is set up to send all requests to
|
||||
its default_pool *pool1*.
|
||||
* We have set up a new pool *pool2* on *lb1* and want any requests using any
|
||||
HTTP/1.1 hostname like *\*.example.com* to be sent to *pool2* instead.
|
||||
|
||||
**Solution**:
|
||||
|
||||
1. Create L7 Policy *policy1* with action ``REDIRECT_TO_POOL`` pointed at
|
||||
*pool2*.
|
||||
2. Create an L7 Rule on *policy1* which matches any hostname that ends with
|
||||
*example.com*.
|
||||
|
||||
**CLI commands**:
|
||||
|
||||
::
|
||||
|
||||
neutron lbaas-l7policy-create --action REDIRECT_TO_POOL --redirect-pool pool2 --listener listener1 --name policy1
|
||||
neutron lbaas-l7rule-create --type HOST_NAME --compare-type ENDS_WITH --value example.com policy1
|
||||
|
||||
|
||||
Send unauthenticated users to *login_pool* (scenario 1)
|
||||
-------------------------------------------------------
|
||||
**Scenario description**:
|
||||
|
||||
* ``TERMINATED_HTTPS`` listener *listener1* on load balancer *lb1* is set up
|
||||
to send all requests to its default_pool *pool1*.
|
||||
* The site behind *listener1* requires all web users to authenticate, after
|
||||
which a browser cookie *auth_token* will be set.
|
||||
* When web users log out, or if the *auth_token* is invalid, the application
|
||||
servers in *pool1* clear the *auth_token*.
|
||||
* We want to introduce new secure authentication server 10.0.1.10 on Neutron
|
||||
subnet *secure_subnet* (a different Neutron subnet from the default
|
||||
application servers) which handles authenticating web users and sets the
|
||||
*auth_token*.
|
||||
|
||||
*Note:* Obviously, to have a more secure authentication system that is less
|
||||
vulnerable to attacks like XSS, the new secure authentication server will need
|
||||
to set session variables to which the default_pool servers will have access
|
||||
outside the data path with the web client. There may be other security concerns
|
||||
as well. This example is not meant to address how these are to be
|
||||
accomplished--it's mainly meant to show how L7 application routing can be done
|
||||
based on a browser cookie.
|
||||
|
||||
**Solution**:
|
||||
|
||||
1. Create pool *login_pool* on *lb1*.
|
||||
2. Add member 10.0.1.10 on *secure_subnet* to *login_pool*.
|
||||
3. Create L7 Policy *policy1* with action ``REDIRECT_TO_POOL`` pointed at
|
||||
*login_pool*.
|
||||
4. Create an L7 Rule on *policy1* which looks for browser cookie *auth_token*
|
||||
(with any value) and matches if it is *NOT* present.
|
||||
|
||||
**CLI commands**:
|
||||
|
||||
::
|
||||
|
||||
neutron lbaas-pool-create --name login_pool --lb-algorithm ROUND_ROBIN --loadbalancer lb1 --protocol HTTP
|
||||
neutron lbaas-member-create --subnet secure_subnet --address 10.0.1.10 --protocol-port 80 login_pool
|
||||
neutron lbaas-l7policy-create --action REDIRECT_TO_POOL --redirect-pool login_pool --listener listener1 --name policy1
|
||||
neutron lbaas-l7rule-create --type COOKIE --key auth_token --compare-type REGEX --value '.*' --invert policy1
|
||||
|
||||
|
||||
Send unauthenticated users to *login_pool* (scenario 2)
|
||||
--------------------------------------------------------
|
||||
**Scenario description**:
|
||||
|
||||
* ``TERMINATED_HTTPS`` listener *listener1* on load balancer *lb1* is set up
|
||||
to send all requests to its default_pool *pool1*.
|
||||
* The site behind *listener1* requires all web users to authenticate, after
|
||||
which a browser cookie *auth_token* will be set.
|
||||
* When web users log out, or if the *auth_token* is invalid, the application
|
||||
servers in *pool1* set *auth_token* to the literal string "INVALID".
|
||||
* We want to introduce new secure authentication server 10.0.1.10 on Neutron
|
||||
subnet *secure_subnet* (a different Neutron subnet from the default
|
||||
application servers) which handles authenticating web users and sets the
|
||||
*auth_token*.
|
||||
|
||||
*Note:* Obviously, to have a more secure authentication system that is less
|
||||
vulnerable to attacks like XSS, the new secure authentication server will need
|
||||
to set session variables to which the default_pool servers will have access
|
||||
outside the data path with the web client. There may be other security concerns
|
||||
as well. This example is not meant to address how these are to be
|
||||
accomplished-- it's mainly meant to show how L7 application routing can be done
|
||||
based on a browser cookie.
|
||||
|
||||
**Solution**:
|
||||
|
||||
1. Create pool *login_pool* on *lb1*.
|
||||
2. Add member 10.0.1.10 on *secure_subnet* to *login_pool*.
|
||||
3. Create L7 Policy *policy1* with action ``REDIRECT_TO_POOL`` pointed at
|
||||
*login_pool*.
|
||||
4. Create an L7 Rule on *policy1* which looks for browser cookie *auth_token*
|
||||
(with any value) and matches if it is *NOT* present.
|
||||
5. Create L7 Policy *policy2* with action ``REDIRECT_TO_POOL`` pointed at
|
||||
*login_pool*.
|
||||
6. Create an L7 Rule on *policy2* which looks for browser cookie *auth_token*
|
||||
and matches if it is equal to the literal string "INVALID".
|
||||
|
||||
**CLI commands**:
|
||||
|
||||
::
|
||||
|
||||
neutron lbaas-pool-create --name login_pool --lb-algorithm ROUND_ROBIN --loadbalancer lb1 --protocol HTTP
|
||||
neutron lbaas-member-create --subnet secure_subnet --address 10.0.1.10 --protocol-port 80 login_pool
|
||||
neutron lbaas-l7policy-create --action REDIRECT_TO_POOL --redirect-pool login_pool --listener listener1 --name policy1
|
||||
neutron lbaas-l7rule-create --type COOKIE --key auth_token --compare-type REGEX --value '.*' --invert policy1
|
||||
neutron lbaas-l7policy-create --action REDIRECT_TO_POOL --redirect-pool login_pool --listener listener1 --name policy2
|
||||
neutron lbaas-l7rule-create --type COOKIE --key auth_token --compare-type EQUAL_TO --value INVALID policy2
|
||||
|
||||
|
||||
Send requests for *http://api.example.com/api* to *api_pool*
|
||||
------------------------------------------------------------
|
||||
**Scenario description**:
|
||||
|
||||
* Listener *listener1* on load balancer *lb1* is set up to send all requests
|
||||
to its default_pool *pool1*.
|
||||
* We have created pool *api_pool* on *lb1*, however, for legacy business logic
|
||||
reasons, we only want requests sent to this pool if they match the hostname
|
||||
*api.example.com* AND the request path starts with */api*.
|
||||
|
||||
**Solution**:
|
||||
|
||||
1. Create L7 Policy *policy1* with action ``REDIRECT_TO_POOL`` pointed at
|
||||
*api_pool*.
|
||||
2. Create an L7 Rule on *policy1* which matches the hostname *api.example.com*.
|
||||
3. Create an L7 Rule on *policy1* which matches */api* at the start of the
|
||||
request path. (This rule will be logically ANDed with the previous rule.)
|
||||
|
||||
**CLI commands**:
|
||||
|
||||
::
|
||||
|
||||
neutron lbaas-l7policy-create --action REDIRECT_TO_POOL --redirect-pool api_pool --listener listener1 --name policy1
|
||||
neutron lbaas-l7rule-create --type HOST_NAME --compare-type EQUAL_TO --value api.example.com policy1
|
||||
neutron lbaas-l7rule-create --type PATH --compare-type STARTS_WITH --value /api policy1
|
||||
|
||||
|
||||
Set up A/B testing on an existing production site using a cookie
|
||||
----------------------------------------------------------------
|
||||
**Scenario description**:
|
||||
|
||||
* Listener *listener1* on load balancer *lb1* is a production site set up as
|
||||
described under :ref:`send-requests-to-static-pool-n` (alternate solution)
|
||||
above. Specifically:
|
||||
|
||||
* HTTP requests with a URL that starts with either "/js" or "/images" are
|
||||
sent to pool *static_pool*.
|
||||
* All other requests are sent to *listener1's* default_pool *pool1*.
|
||||
|
||||
* We are introducing a "B" version of the production site, complete with its
|
||||
own default_pool and static_pool. We will call these *pool_B* and
|
||||
*static_pool_B* respectively.
|
||||
* The *pool_B* members should be 10.0.0.50 and 10.0.0.51, and the
|
||||
*static_pool_B* members should be 10.0.0.100 and 10.0.0.101 on subnet
|
||||
*private-subnet*.
|
||||
* Web clients which should be routed to the "B" version of the site get a
|
||||
cookie set by the member servers in *pool1*. This cookie is called
|
||||
"site_version" and should have the value "B".
|
||||
|
||||
**Solution**:
|
||||
|
||||
1. Create pool *pool_B* on *lb1*.
|
||||
2. Populate *pool_B* with its new back-end members.
|
||||
3. Create pool *static_pool_B* on *lb1*.
|
||||
4. Populate *static_pool_B* with its new back-end members.
|
||||
5. Create L7 Policy *policy2* with action ``REDIRECT_TO_POOL`` pointed at
|
||||
*static_pool_B*. This should be inserted at position 1.
|
||||
6. Create an L7 Rule on *policy2* which uses a regular expression to match
|
||||
either "/js" or "/images" at the start of the request path.
|
||||
7. Create an L7 Rule on *policy2* which matches the cookie "site_version" to
|
||||
the exact string "B".
|
||||
8. Create L7 Policy *policy3* with action ``REDIRECT_TO_POOL`` pointed at
|
||||
*pool_B*. This should be inserted at position 2.
|
||||
9. Create an L7 Rule on *policy3* which matches the cookie "site_version" to
|
||||
the exact string "B".
|
||||
|
||||
*A word about L7 Policy position*: Since L7 Policies are evaluated in order
|
||||
according to their position parameter, and since the first L7 Policy whose L7
|
||||
Rules all evaluate to True is the one whose action is followed, it is important
|
||||
that L7 Policies with the most specific rules get evaluated first.
|
||||
|
||||
For example, in this solution, if *policy3* were to appear in the listener's L7
|
||||
Policy list before *policy2* (that is, if *policy3* were to have a lower
|
||||
position number than *policy2*), then if a web client were to request the URL
|
||||
http://www.example.com/images/a.jpg with the cookie "site_version:B", then
|
||||
*policy3* would match, and the load balancer would send the request to
|
||||
*pool_B*. From the scenario description, this request clearly was meant to be
|
||||
sent to *static_pool_B*, which is why *policy2* needs to be evaluated before
|
||||
*policy3*.
|
||||
|
||||
**CLI commands**:
|
||||
|
||||
::
|
||||
|
||||
neutron lbaas-pool-create --name pool_B --lb-algorithm ROUND_ROBIN --loadbalancer lb1 --protocol HTTP
|
||||
neutron lbaas-member-create --subnet private-subnet --address 10.0.0.50 --protocol-port 80 pool_B
|
||||
neutron lbaas-member-create --subnet private-subnet --address 10.0.0.51 --protocol-port 80 pool_B
|
||||
neutron lbaas-pool-create --name static_pool_B --lb-algorithm ROUND_ROBIN --loadbalancer lb1 --protocol HTTP
|
||||
neutron lbaas-member-create --subnet private-subnet --address 10.0.0.100 --protocol-port 80 static_pool_B
|
||||
neutron lbaas-member-create --subnet private-subnet --address 10.0.0.101 --protocol-port 80 static_pool_B
|
||||
neutron lbaas-l7policy-create --action REDIRECT_TO_POOL --redirect-pool static_pool_B --listener listener1 --name policy2 --position 1
|
||||
neutron lbaas-l7rule-create --type PATH --compare-type REGEX --value '^/(js|images)' policy2
|
||||
neutron lbaas-l7rule-create --type COOKIE --key site_version --compare-type EQUAL_TO --value B policy2
|
||||
neutron lbaas-l7policy-create --action REDIRECT_TO_POOL --redirect-pool pool_B --listener listener1 --name policy3 --position 2
|
||||
neutron lbaas-l7rule-create --type COOKIE --key site_version --compare-type EQUAL_TO --value B policy3
|
||||
@@ -10,8 +10,6 @@ Cookbooks
|
||||
|
||||
guides/basic-cookbook
|
||||
guides/l7-cookbook
|
||||
guides/basic-cookbook-neutron
|
||||
guides/l7-cookbook-neutron
|
||||
|
||||
Guides
|
||||
======
|
||||
|
||||
@@ -19,7 +19,6 @@
|
||||
[api_settings]
|
||||
# bind_host = 127.0.0.1
|
||||
# bind_port = 9876
|
||||
# api_handler = queue_producer
|
||||
|
||||
# How should authentication be handled (keystone, noauth)
|
||||
# auth_strategy = keystone
|
||||
@@ -33,10 +32,6 @@
|
||||
# api_base_uri = http://localhost:9876
|
||||
# api_base_uri =
|
||||
|
||||
# Enable/disable exposing API endpoints. By default, both v1 and v2 are enabled.
|
||||
# api_v1_enabled = True
|
||||
# api_v2_enabled = True
|
||||
|
||||
# Enable/disable ability for users to create TLS Terminated listeners
|
||||
# allow_tls_terminated_listeners = True
|
||||
|
||||
@@ -95,14 +90,6 @@
|
||||
# health_update_driver = health_db
|
||||
# stats_update_driver = stats_db
|
||||
|
||||
# EventStreamer options are
|
||||
# queue_event_streamer,
|
||||
# noop_event_streamer
|
||||
# event_streamer_driver = noop_event_streamer
|
||||
|
||||
# Enable provisioning status sync with neutron db
|
||||
# sync_provisioning_status = False
|
||||
|
||||
[keystone_authtoken]
|
||||
# This group of config options are imported from keystone middleware. Thus the
|
||||
# option names should match the names declared in the middleware.
|
||||
@@ -285,17 +272,6 @@
|
||||
# Topic (i.e. Queue) Name
|
||||
# topic = octavia_prov
|
||||
|
||||
# Topic for octavia's events sent to a queue
|
||||
# event_stream_topic = neutron_lbaas_event
|
||||
|
||||
# Transport URL to use for the neutron-lbaas synchronization event stream
|
||||
# when neutron and octavia have separate queues.
|
||||
# For Single Host, specify one full transport URL:
|
||||
# event_stream_transport_url = rabbit://<user>:<pass>@127.0.0.1:5672/<vhost>
|
||||
# For HA, specify queue nodes in cluster, comma delimited:
|
||||
# event_stream_transport_url = rabbit://<user>:<pass>@server01,<user>:<pass>@server02/<vhost>
|
||||
# event_stream_transport_url =
|
||||
|
||||
[house_keeping]
|
||||
# Interval in seconds to initiate spare amphora checks
|
||||
# spare_check_interval = 30
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
# 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.
|
||||
@@ -1,67 +0,0 @@
|
||||
# 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 abc
|
||||
|
||||
import six
|
||||
|
||||
|
||||
@six.add_metaclass(abc.ABCMeta)
|
||||
class BaseObjectHandler(object):
|
||||
"""Base class for any object handler."""
|
||||
@abc.abstractmethod
|
||||
def create(self, model_id):
|
||||
"""Begins process of actually creating data_model."""
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def update(self, model_id, updated_dict):
|
||||
"""Begins process of actually updating data_model."""
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def delete(self, model_id):
|
||||
"""Begins process of actually deleting data_model."""
|
||||
pass
|
||||
|
||||
|
||||
class NotImplementedObjectHandler(BaseObjectHandler):
|
||||
"""Default Object Handler to force implementation of subclasses.
|
||||
|
||||
Helper class to make any subclass of AbstractHandler explode if it
|
||||
is missing any of the required object managers.
|
||||
"""
|
||||
@staticmethod
|
||||
def update(model_id, updated_dict):
|
||||
raise NotImplementedError()
|
||||
|
||||
@staticmethod
|
||||
def delete(model_id):
|
||||
raise NotImplementedError()
|
||||
|
||||
@staticmethod
|
||||
def create(model_id):
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
@six.add_metaclass(abc.ABCMeta)
|
||||
class BaseHandler(object):
|
||||
"""Base class for all handlers."""
|
||||
load_balancer = NotImplementedObjectHandler()
|
||||
listener = NotImplementedObjectHandler()
|
||||
pool = NotImplementedObjectHandler()
|
||||
health_monitor = NotImplementedObjectHandler()
|
||||
member = NotImplementedObjectHandler()
|
||||
l7policy = NotImplementedObjectHandler()
|
||||
l7rule = NotImplementedObjectHandler()
|
||||
@@ -1,11 +0,0 @@
|
||||
# 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.
|
||||
@@ -1,478 +0,0 @@
|
||||
# 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.
|
||||
|
||||
"""
|
||||
This is just a handler that will simulate successful operations a controller
|
||||
should perform. There is nothing useful about this other than database
|
||||
entity status management.
|
||||
"""
|
||||
|
||||
import threading
|
||||
import time
|
||||
|
||||
from oslo_log import log as logging
|
||||
|
||||
from octavia.api.handlers import abstract_handler
|
||||
from octavia.common import constants
|
||||
from octavia.common import data_models
|
||||
from octavia.db import api as db_api
|
||||
import octavia.db.repositories as repos
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
ASYNC_TIME = 1
|
||||
|
||||
|
||||
def validate_input(expected, actual):
|
||||
if not isinstance(actual, expected):
|
||||
raise InvalidHandlerInputObject(obj_type=actual.__class__)
|
||||
|
||||
|
||||
def simulate_controller(data_model, delete=False, update=False, create=False,
|
||||
batch_update=False):
|
||||
"""Simulates a successful controller operator for a data model.
|
||||
|
||||
:param data_model: data model to simulate controller operation
|
||||
:param delete: deletes from the database
|
||||
"""
|
||||
repo = repos.Repositories()
|
||||
|
||||
def member_controller(member, delete=False, update=False, create=False,
|
||||
batch_update=False):
|
||||
time.sleep(ASYNC_TIME)
|
||||
LOG.info("Simulating controller operation for member...")
|
||||
|
||||
db_mem = None
|
||||
if delete:
|
||||
db_mem = repo.member.get(db_api.get_session(), id=member.id)
|
||||
repo.member.delete(db_api.get_session(), id=member.id)
|
||||
elif update:
|
||||
db_mem = repo.member.get(db_api.get_session(), id=member.id)
|
||||
member_dict = member.to_dict()
|
||||
member_dict['operating_status'] = db_mem.operating_status
|
||||
repo.member.update(db_api.get_session(), member.id, **member_dict)
|
||||
elif create:
|
||||
repo.member.update(db_api.get_session(), member.id,
|
||||
operating_status=constants.ONLINE)
|
||||
elif batch_update:
|
||||
members = member
|
||||
for m in members:
|
||||
repo.member.update(db_api.get_session(), m.id,
|
||||
operating_status=constants.ONLINE)
|
||||
listeners = []
|
||||
if db_mem:
|
||||
for listener in db_mem.pool.listeners:
|
||||
if listener not in listeners:
|
||||
listeners.append(listener)
|
||||
if member.pool.listeners:
|
||||
for listener in member.pool.listeners:
|
||||
if listener not in listeners:
|
||||
listeners.append(listener)
|
||||
if listeners:
|
||||
for listener in listeners:
|
||||
repo.listener.update(db_api.get_session(), listener.id,
|
||||
operating_status=constants.ONLINE,
|
||||
provisioning_status=constants.ACTIVE)
|
||||
repo.load_balancer.update(db_api.get_session(),
|
||||
member.pool.load_balancer.id,
|
||||
operating_status=constants.ONLINE,
|
||||
provisioning_status=constants.ACTIVE)
|
||||
LOG.info("Simulated Controller Handler Thread Complete")
|
||||
|
||||
def l7policy_controller(l7policy, delete=False, update=False,
|
||||
create=False):
|
||||
time.sleep(ASYNC_TIME)
|
||||
LOG.info("Simulating controller operation for l7policy...")
|
||||
|
||||
db_l7policy = None
|
||||
if delete:
|
||||
db_l7policy = repo.l7policy.get(db_api.get_session(),
|
||||
id=l7policy.id)
|
||||
repo.l7policy.delete(db_api.get_session(), id=l7policy.id)
|
||||
elif update:
|
||||
db_l7policy = repo.l7policy.get(db_api.get_session(),
|
||||
id=l7policy.id)
|
||||
l7policy_dict = l7policy.to_dict()
|
||||
repo.l7policy.update(db_api.get_session(), l7policy.id,
|
||||
**l7policy_dict)
|
||||
elif create:
|
||||
db_l7policy = repo.l7policy.create(db_api.get_session(),
|
||||
**l7policy.to_dict())
|
||||
if db_l7policy.listener:
|
||||
repo.listener.update(db_api.get_session(), db_l7policy.listener.id,
|
||||
operating_status=constants.ONLINE,
|
||||
provisioning_status=constants.ACTIVE)
|
||||
repo.load_balancer.update(db_api.get_session(),
|
||||
db_l7policy.listener.load_balancer.id,
|
||||
operating_status=constants.ONLINE,
|
||||
provisioning_status=constants.ACTIVE)
|
||||
LOG.info("Simulated Controller Handler Thread Complete")
|
||||
|
||||
def l7rule_controller(l7rule, delete=False, update=False, create=False):
|
||||
time.sleep(ASYNC_TIME)
|
||||
LOG.info("Simulating controller operation for l7rule...")
|
||||
|
||||
db_l7rule = None
|
||||
if delete:
|
||||
db_l7rule = repo.l7rule.get(db_api.get_session(), id=l7rule.id)
|
||||
repo.l7rule.delete(db_api.get_session(), id=l7rule.id)
|
||||
elif update:
|
||||
db_l7rule = repo.l7rule.get(db_api.get_session(), id=l7rule.id)
|
||||
l7rule_dict = l7rule.to_dict()
|
||||
repo.l7rule.update(db_api.get_session(), l7rule.id, **l7rule_dict)
|
||||
elif create:
|
||||
l7rule_dict = l7rule.to_dict()
|
||||
db_l7rule = repo.l7rule.create(db_api.get_session(), **l7rule_dict)
|
||||
if db_l7rule.l7policy.listener:
|
||||
listener = db_l7rule.l7policy.listener
|
||||
repo.listener.update(db_api.get_session(), listener.id,
|
||||
operating_status=constants.ONLINE,
|
||||
provisioning_status=constants.ACTIVE)
|
||||
repo.load_balancer.update(db_api.get_session(),
|
||||
listener.load_balancer.id,
|
||||
operating_status=constants.ONLINE,
|
||||
provisioning_status=constants.ACTIVE)
|
||||
LOG.info("Simulated Controller Handler Thread Complete")
|
||||
|
||||
def health_monitor_controller(health_monitor, delete=False, update=False,
|
||||
create=False):
|
||||
time.sleep(ASYNC_TIME)
|
||||
LOG.info("Simulating controller operation for health monitor...")
|
||||
|
||||
db_hm = None
|
||||
if delete:
|
||||
db_hm = repo.health_monitor.get(db_api.get_session(),
|
||||
pool_id=health_monitor.pool.id)
|
||||
repo.health_monitor.delete(db_api.get_session(),
|
||||
pool_id=health_monitor.pool.id)
|
||||
elif update:
|
||||
db_hm = repo.health_monitor.get(db_api.get_session(),
|
||||
pool_id=health_monitor.pool_id)
|
||||
hm_dict = health_monitor.to_dict()
|
||||
hm_dict['operating_status'] = db_hm.operating_status()
|
||||
repo.health_monitor.update(db_api.get_session(), **hm_dict)
|
||||
elif create:
|
||||
repo.pool.update(db_api.get_session(), health_monitor.pool_id,
|
||||
operating_status=constants.ONLINE)
|
||||
listeners = []
|
||||
if db_hm:
|
||||
for listener in db_hm.pool.listeners:
|
||||
if listener not in listeners:
|
||||
listeners.append(listener)
|
||||
if health_monitor.pool.listeners:
|
||||
for listener in health_monitor.pool.listeners:
|
||||
if listener not in listeners:
|
||||
listeners.append(listener)
|
||||
if listeners:
|
||||
for listener in listeners:
|
||||
repo.test_and_set_lb_and_listener_prov_status(
|
||||
db_api.get_session(),
|
||||
health_monitor.pool.load_balancer.id,
|
||||
listener.id, constants.ACTIVE,
|
||||
constants.ACTIVE)
|
||||
repo.listener.update(db_api.get_session(),
|
||||
listener.id,
|
||||
operating_status=constants.ONLINE,
|
||||
provisioning_status=constants.ACTIVE)
|
||||
repo.load_balancer.update(
|
||||
db_api.get_session(),
|
||||
health_monitor.pool.load_balancer.id,
|
||||
operating_status=constants.ONLINE,
|
||||
provisioning_status=constants.ACTIVE)
|
||||
LOG.info("Simulated Controller Handler Thread Complete")
|
||||
|
||||
def pool_controller(pool, delete=False, update=False, create=False):
|
||||
time.sleep(ASYNC_TIME)
|
||||
LOG.info("Simulating controller operation for pool...")
|
||||
|
||||
db_pool = None
|
||||
if delete:
|
||||
db_pool = repo.pool.get(db_api.get_session(), id=pool.id)
|
||||
repo.pool.delete(db_api.get_session(), id=pool.id)
|
||||
elif update:
|
||||
db_pool = repo.pool.get(db_api.get_session(), id=pool.id)
|
||||
pool_dict = pool.to_dict()
|
||||
pool_dict['operating_status'] = db_pool.operating_status
|
||||
repo.update_pool_and_sp(db_api.get_session(), pool.id, pool_dict)
|
||||
elif create:
|
||||
repo.pool.update(db_api.get_session(), pool.id,
|
||||
operating_status=constants.ONLINE)
|
||||
listeners = []
|
||||
if db_pool:
|
||||
for listener in db_pool.listeners:
|
||||
if listener not in listeners:
|
||||
listeners.append(listener)
|
||||
if pool.listeners:
|
||||
for listener in pool.listeners:
|
||||
if listener not in listeners:
|
||||
listeners.append(listener)
|
||||
if listeners:
|
||||
for listener in listeners:
|
||||
repo.listener.update(db_api.get_session(), listener.id,
|
||||
operating_status=constants.ONLINE,
|
||||
provisioning_status=constants.ACTIVE)
|
||||
repo.load_balancer.update(db_api.get_session(),
|
||||
pool.load_balancer.id,
|
||||
operating_status=constants.ONLINE,
|
||||
provisioning_status=constants.ACTIVE)
|
||||
LOG.info("Simulated Controller Handler Thread Complete")
|
||||
|
||||
def listener_controller(listener, delete=False, update=False,
|
||||
create=False):
|
||||
time.sleep(ASYNC_TIME)
|
||||
LOG.info("Simulating controller operation for listener...")
|
||||
|
||||
if delete:
|
||||
repo.listener.update(db_api.get_session(), listener.id,
|
||||
operating_status=constants.OFFLINE,
|
||||
provisioning_status=constants.DELETED)
|
||||
elif update:
|
||||
db_listener = repo.listener.get(db_api.get_session(),
|
||||
id=listener.id)
|
||||
listener_dict = listener.to_dict()
|
||||
listener_dict['operating_status'] = db_listener.operating_status
|
||||
repo.listener.update(db_api.get_session(), listener.id,
|
||||
**listener_dict)
|
||||
elif create:
|
||||
repo.listener.update(db_api.get_session(), listener.id,
|
||||
operating_status=constants.ONLINE,
|
||||
provisioning_status=constants.ACTIVE)
|
||||
repo.load_balancer.update(db_api.get_session(),
|
||||
listener.load_balancer.id,
|
||||
operating_status=constants.ONLINE,
|
||||
provisioning_status=constants.ACTIVE)
|
||||
LOG.info("Simulated Controller Handler Thread Complete")
|
||||
|
||||
def loadbalancer_controller(loadbalancer, delete=False, update=False,
|
||||
create=False, failover=False):
|
||||
time.sleep(ASYNC_TIME)
|
||||
LOG.info("Simulating controller operation for loadbalancer...")
|
||||
|
||||
if delete:
|
||||
repo.load_balancer.update(
|
||||
db_api.get_session(), id=loadbalancer.id,
|
||||
operating_status=constants.OFFLINE,
|
||||
provisioning_status=constants.DELETED)
|
||||
elif update:
|
||||
db_lb = repo.listener.get(db_api.get_session(), id=loadbalancer.id)
|
||||
lb_dict = loadbalancer.to_dict()
|
||||
lb_dict['operating_status'] = db_lb.operating_status
|
||||
repo.load_balancer.update(db_api.get_session(), loadbalancer.id,
|
||||
**lb_dict)
|
||||
elif create:
|
||||
repo.load_balancer.update(db_api.get_session(), id=loadbalancer.id,
|
||||
operating_status=constants.ONLINE,
|
||||
provisioning_status=constants.ACTIVE)
|
||||
elif failover:
|
||||
repo.load_balancer.update(
|
||||
db_api.get_session(), id=loadbalancer.id,
|
||||
operating_status=constants.ONLINE,
|
||||
provisioning_status=constants.PENDING_UPDATE)
|
||||
LOG.info("Simulated Controller Handler Thread Complete")
|
||||
|
||||
controller = loadbalancer_controller
|
||||
if isinstance(data_model, data_models.Member):
|
||||
controller = member_controller
|
||||
elif isinstance(data_model, data_models.HealthMonitor):
|
||||
controller = health_monitor_controller
|
||||
elif isinstance(data_model, data_models.Pool):
|
||||
controller = pool_controller
|
||||
elif isinstance(data_model, data_models.Listener):
|
||||
controller = listener_controller
|
||||
|
||||
thread = threading.Thread(target=controller, args=(data_model, delete,
|
||||
update, create))
|
||||
thread.start()
|
||||
|
||||
|
||||
class InvalidHandlerInputObject(Exception):
|
||||
message = "Invalid Input Object %(obj_type)s"
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
message = self.message % kwargs
|
||||
super(InvalidHandlerInputObject, self).__init__(message=message)
|
||||
|
||||
|
||||
class LoadBalancerHandler(abstract_handler.BaseObjectHandler):
|
||||
|
||||
def create(self, load_balancer_id):
|
||||
LOG.info("%(entity)s handling the creation of load balancer %(id)s",
|
||||
{"entity": self.__class__.__name__, "id": load_balancer_id})
|
||||
simulate_controller(load_balancer_id, create=True)
|
||||
|
||||
def update(self, old_lb, load_balancer):
|
||||
validate_input(data_models.LoadBalancer, load_balancer)
|
||||
LOG.info("%(entity)s handling the update of load balancer %(id)s",
|
||||
{"entity": self.__class__.__name__, "id": old_lb.id})
|
||||
load_balancer.id = old_lb.id
|
||||
simulate_controller(load_balancer, update=True)
|
||||
|
||||
def delete(self, load_balancer_id):
|
||||
LOG.info("%(entity)s handling the deletion of load balancer %(id)s",
|
||||
{"entity": self.__class__.__name__, "id": load_balancer_id})
|
||||
simulate_controller(load_balancer_id, delete=True)
|
||||
|
||||
|
||||
class ListenerHandler(abstract_handler.BaseObjectHandler):
|
||||
|
||||
def create(self, listener_id):
|
||||
LOG.info("%(entity)s handling the creation of listener %(id)s",
|
||||
{"entity": self.__class__.__name__, "id": listener_id})
|
||||
simulate_controller(listener_id, create=True)
|
||||
|
||||
def update(self, old_listener, listener):
|
||||
validate_input(data_models.Listener, listener)
|
||||
LOG.info("%(entity)s handling the update of listener %(id)s",
|
||||
{"entity": self.__class__.__name__, "id": old_listener.id})
|
||||
listener.id = old_listener.id
|
||||
simulate_controller(listener, update=True)
|
||||
|
||||
def delete(self, listener_id):
|
||||
LOG.info("%(entity)s handling the deletion of listener %(id)s",
|
||||
{"entity": self.__class__.__name__, "id": listener_id})
|
||||
simulate_controller(listener_id, delete=True)
|
||||
|
||||
|
||||
class PoolHandler(abstract_handler.BaseObjectHandler):
|
||||
|
||||
def create(self, pool_id):
|
||||
LOG.info("%(entity)s handling the creation of pool %(id)s",
|
||||
{"entity": self.__class__.__name__, "id": pool_id})
|
||||
simulate_controller(pool_id, create=True)
|
||||
|
||||
def update(self, old_pool, pool):
|
||||
validate_input(data_models.Pool, pool)
|
||||
LOG.info("%(entity)s handling the update of pool %(id)s",
|
||||
{"entity": self.__class__.__name__, "id": old_pool.id})
|
||||
pool.id = old_pool.id
|
||||
simulate_controller(pool, update=True)
|
||||
|
||||
def delete(self, pool_id):
|
||||
LOG.info("%(entity)s handling the deletion of pool %(id)s",
|
||||
{"entity": self.__class__.__name__, "id": pool_id})
|
||||
simulate_controller(pool_id, delete=True)
|
||||
|
||||
|
||||
class HealthMonitorHandler(abstract_handler.BaseObjectHandler):
|
||||
|
||||
def create(self, pool_id):
|
||||
LOG.info("%(entity)s handling the creation of health monitor "
|
||||
"on pool %(id)s",
|
||||
{"entity": self.__class__.__name__, "id": pool_id})
|
||||
simulate_controller(pool_id, create=True)
|
||||
|
||||
def update(self, old_health_monitor, health_monitor):
|
||||
validate_input(data_models.HealthMonitor, health_monitor)
|
||||
LOG.info("%(entity)s handling the update of health monitor "
|
||||
"on pool %(id)s",
|
||||
{"entity": self.__class__.__name__,
|
||||
"id": old_health_monitor.pool_id})
|
||||
health_monitor.pool_id = old_health_monitor.pool_id
|
||||
simulate_controller(health_monitor, update=True)
|
||||
|
||||
def delete(self, pool_id):
|
||||
LOG.info("%(entity)s handling the deletion of health monitor "
|
||||
"on pool %(id)s",
|
||||
{"entity": self.__class__.__name__, "id": pool_id})
|
||||
simulate_controller(pool_id, delete=True)
|
||||
|
||||
|
||||
class MemberHandler(abstract_handler.BaseObjectHandler):
|
||||
|
||||
def create(self, member_id):
|
||||
LOG.info("%(entity)s handling the creation of member %(id)s",
|
||||
{"entity": self.__class__.__name__, "id": member_id})
|
||||
simulate_controller(member_id, create=True)
|
||||
|
||||
def update(self, old_member, member):
|
||||
validate_input(data_models.Member, member)
|
||||
LOG.info("%(entity)s handling the update of member %(id)s",
|
||||
{"entity": self.__class__.__name__, "id": old_member.id})
|
||||
member.id = old_member.id
|
||||
simulate_controller(member, update=True)
|
||||
|
||||
def batch_update(self, old_member_ids, new_member_ids, updated_members):
|
||||
for m in updated_members:
|
||||
validate_input(data_models.Member, m)
|
||||
LOG.info("%(entity)s handling the batch update of members: "
|
||||
"old=%(old)s, new=%(new)s",
|
||||
{"entity": self.__class__.__name__, "old": old_member_ids,
|
||||
"new": new_member_ids})
|
||||
|
||||
repo = repos.Repositories()
|
||||
old_members = [repo.member.get(db_api.get_session(), id=mid)
|
||||
for mid in old_member_ids]
|
||||
new_members = [repo.member.get(db_api.get_session(), id=mid)
|
||||
for mid in new_member_ids]
|
||||
all_members = []
|
||||
all_members.extend(old_members)
|
||||
all_members.extend(new_members)
|
||||
all_members.extend(updated_members)
|
||||
simulate_controller(all_members, batch_update=True)
|
||||
|
||||
def delete(self, member_id):
|
||||
LOG.info("%(entity)s handling the deletion of member %(id)s",
|
||||
{"entity": self.__class__.__name__, "id": member_id})
|
||||
simulate_controller(member_id, delete=True)
|
||||
|
||||
|
||||
class L7PolicyHandler(abstract_handler.BaseObjectHandler):
|
||||
|
||||
def create(self, l7policy_id):
|
||||
LOG.info("%(entity)s handling the creation of l7policy %(id)s",
|
||||
{"entity": self.__class__.__name__, "id": l7policy_id})
|
||||
simulate_controller(l7policy_id, create=True)
|
||||
|
||||
def update(self, old_l7policy, l7policy):
|
||||
validate_input(data_models.L7Policy, l7policy)
|
||||
LOG.info("%(entity)s handling the update of l7policy %(id)s",
|
||||
{"entity": self.__class__.__name__, "id": old_l7policy.id})
|
||||
l7policy.id = old_l7policy.id
|
||||
simulate_controller(l7policy, update=True)
|
||||
|
||||
def delete(self, l7policy_id):
|
||||
LOG.info("%(entity)s handling the deletion of l7policy %(id)s",
|
||||
{"entity": self.__class__.__name__, "id": l7policy_id})
|
||||
simulate_controller(l7policy_id, delete=True)
|
||||
|
||||
|
||||
class L7RuleHandler(abstract_handler.BaseObjectHandler):
|
||||
|
||||
def create(self, l7rule):
|
||||
LOG.info("%(entity)s handling the creation of l7rule %(id)s",
|
||||
{"entity": self.__class__.__name__, "id": l7rule.id})
|
||||
simulate_controller(l7rule, create=True)
|
||||
|
||||
def update(self, old_l7rule, l7rule):
|
||||
validate_input(data_models.L7Rule, l7rule)
|
||||
LOG.info("%(entity)s handling the update of l7rule %(id)s",
|
||||
{"entity": self.__class__.__name__, "id": old_l7rule.id})
|
||||
l7rule.id = old_l7rule.id
|
||||
simulate_controller(l7rule, update=True)
|
||||
|
||||
def delete(self, l7rule):
|
||||
LOG.info("%(entity)s handling the deletion of l7rule %(id)s",
|
||||
{"entity": self.__class__.__name__, "id": l7rule.id})
|
||||
simulate_controller(l7rule, delete=True)
|
||||
|
||||
|
||||
class SimulatedControllerHandler(abstract_handler.BaseHandler):
|
||||
"""Handler that simulates database calls of a successful controller."""
|
||||
load_balancer = LoadBalancerHandler()
|
||||
listener = ListenerHandler()
|
||||
pool = PoolHandler()
|
||||
health_monitor = HealthMonitorHandler()
|
||||
member = MemberHandler()
|
||||
l7policy = L7PolicyHandler()
|
||||
l7rule = L7RuleHandler()
|
||||
@@ -1,11 +0,0 @@
|
||||
# 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.
|
||||
@@ -1,241 +0,0 @@
|
||||
# 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.# 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 abc
|
||||
|
||||
from oslo_config import cfg
|
||||
import oslo_messaging as messaging
|
||||
import six
|
||||
|
||||
from octavia.api.handlers import abstract_handler
|
||||
from octavia.common import constants
|
||||
from octavia.common import rpc
|
||||
|
||||
|
||||
cfg.CONF.import_group('oslo_messaging', 'octavia.common.config')
|
||||
|
||||
|
||||
@six.add_metaclass(abc.ABCMeta)
|
||||
class BaseProducer(abstract_handler.BaseObjectHandler):
|
||||
"""Base queue producer class."""
|
||||
@abc.abstractproperty
|
||||
def payload_class(self):
|
||||
"""returns a string representing the container class."""
|
||||
pass
|
||||
|
||||
def __init__(self):
|
||||
topic = cfg.CONF.oslo_messaging.topic
|
||||
self.target = messaging.Target(
|
||||
namespace=constants.RPC_NAMESPACE_CONTROLLER_AGENT,
|
||||
topic=topic, version="1.0", fanout=False)
|
||||
self.client = rpc.get_client(self.target)
|
||||
|
||||
def create(self, model):
|
||||
"""Sends a create message to the controller via oslo.messaging
|
||||
|
||||
:param model:
|
||||
"""
|
||||
model_id = getattr(model, 'id', None)
|
||||
kw = {"{0}_id".format(self.payload_class): model_id}
|
||||
method_name = "create_{0}".format(self.payload_class)
|
||||
self.client.cast({}, method_name, **kw)
|
||||
|
||||
def update(self, data_model, updated_model):
|
||||
"""sends an update message to the controller via oslo.messaging
|
||||
|
||||
:param updated_model:
|
||||
:param data_model:
|
||||
"""
|
||||
model_id = getattr(data_model, 'id', None)
|
||||
kw = {"{0}_updates".format(self.payload_class):
|
||||
updated_model.to_dict(render_unsets=False),
|
||||
"{0}_id".format(self.payload_class): model_id}
|
||||
method_name = "update_{0}".format(self.payload_class)
|
||||
self.client.cast({}, method_name, **kw)
|
||||
|
||||
def delete(self, data_model):
|
||||
"""sends a delete message to the controller via oslo.messaging
|
||||
|
||||
:param data_model:
|
||||
"""
|
||||
model_id = getattr(data_model, 'id', None)
|
||||
kw = {"{0}_id".format(self.payload_class): model_id}
|
||||
method_name = "delete_{0}".format(self.payload_class)
|
||||
self.client.cast({}, method_name, **kw)
|
||||
|
||||
|
||||
class LoadBalancerProducer(BaseProducer):
|
||||
"""Sends updates,deletes and creates to the RPC end of the queue consumer
|
||||
|
||||
"""
|
||||
PAYLOAD_CLASS = "load_balancer"
|
||||
|
||||
@property
|
||||
def payload_class(self):
|
||||
return self.PAYLOAD_CLASS
|
||||
|
||||
def delete(self, data_model, cascade):
|
||||
"""sends a delete message to the controller via oslo.messaging
|
||||
|
||||
:param data_model:
|
||||
:param: cascade: delete listeners, etc. as well
|
||||
"""
|
||||
model_id = getattr(data_model, 'id', None)
|
||||
p_class = self.payload_class
|
||||
kw = {"{0}_id".format(p_class): model_id, "cascade": cascade}
|
||||
method_name = "delete_{0}".format(self.payload_class)
|
||||
self.client.cast({}, method_name, **kw)
|
||||
|
||||
def failover(self, data_model):
|
||||
"""sends a failover message to the controller via oslo.messaging
|
||||
|
||||
:param data_model:
|
||||
"""
|
||||
model_id = getattr(data_model, 'id', None)
|
||||
p_class = self.payload_class
|
||||
kw = {"{0}_id".format(p_class): model_id}
|
||||
method_name = "failover_{0}".format(self.payload_class)
|
||||
self.client.cast({}, method_name, **kw)
|
||||
|
||||
|
||||
class AmphoraProducer(BaseProducer):
|
||||
"""Sends failover messages to the RPC end of the queue consumer
|
||||
|
||||
"""
|
||||
PAYLOAD_CLASS = "amphora"
|
||||
|
||||
@property
|
||||
def payload_class(self):
|
||||
return self.PAYLOAD_CLASS
|
||||
|
||||
def failover(self, data_model):
|
||||
"""sends a failover message to the controller via oslo.messaging
|
||||
|
||||
:param data_model:
|
||||
"""
|
||||
model_id = getattr(data_model, 'id', None)
|
||||
p_class = self.payload_class
|
||||
kw = {"{0}_id".format(p_class): model_id}
|
||||
method_name = "failover_{0}".format(self.payload_class)
|
||||
self.client.cast({}, method_name, **kw)
|
||||
|
||||
|
||||
class ListenerProducer(BaseProducer):
|
||||
"""Sends updates,deletes and creates to the RPC end of the queue consumer
|
||||
|
||||
"""
|
||||
PAYLOAD_CLASS = "listener"
|
||||
|
||||
@property
|
||||
def payload_class(self):
|
||||
return self.PAYLOAD_CLASS
|
||||
|
||||
|
||||
class PoolProducer(BaseProducer):
|
||||
"""Sends updates,deletes and creates to the RPC end of the queue consumer
|
||||
|
||||
"""
|
||||
PAYLOAD_CLASS = "pool"
|
||||
|
||||
@property
|
||||
def payload_class(self):
|
||||
return self.PAYLOAD_CLASS
|
||||
|
||||
|
||||
class HealthMonitorProducer(BaseProducer):
|
||||
"""Sends updates,deletes and creates to the RPC end of the queue consumer
|
||||
|
||||
"""
|
||||
PAYLOAD_CLASS = "health_monitor"
|
||||
|
||||
@property
|
||||
def payload_class(self):
|
||||
return self.PAYLOAD_CLASS
|
||||
|
||||
|
||||
class MemberProducer(BaseProducer):
|
||||
"""Sends updates,deletes and creates to the RPC end of the queue consumer
|
||||
|
||||
"""
|
||||
PAYLOAD_CLASS = "member"
|
||||
|
||||
@property
|
||||
def payload_class(self):
|
||||
return self.PAYLOAD_CLASS
|
||||
|
||||
def batch_update(self, old_ids, new_ids, updated_models):
|
||||
"""sends an update message to the controller via oslo.messaging
|
||||
|
||||
:param old_ids: list of member ids that are being deleted
|
||||
:param new_ids: list of member ids that are being created
|
||||
:param updated_models: list of member model objects to update
|
||||
"""
|
||||
updated_dicts = [m.to_dict(render_unsets=False)
|
||||
for m in updated_models]
|
||||
kw = {"old_{0}_ids".format(self.payload_class): old_ids,
|
||||
"new_{0}_ids".format(self.payload_class): new_ids,
|
||||
"updated_{0}s".format(self.payload_class): updated_dicts}
|
||||
method_name = "batch_update_{0}s".format(self.payload_class)
|
||||
self.client.cast({}, method_name, **kw)
|
||||
|
||||
|
||||
class L7PolicyProducer(BaseProducer):
|
||||
"""Sends updates,deletes and creates to the RPC end of the queue consumer
|
||||
|
||||
"""
|
||||
PAYLOAD_CLASS = "l7policy"
|
||||
|
||||
@property
|
||||
def payload_class(self):
|
||||
return self.PAYLOAD_CLASS
|
||||
|
||||
|
||||
class L7RuleProducer(BaseProducer):
|
||||
"""Sends updates,deletes and creates to the RPC end of the queue consumer
|
||||
|
||||
"""
|
||||
PAYLOAD_CLASS = "l7rule"
|
||||
|
||||
@property
|
||||
def payload_class(self):
|
||||
return self.PAYLOAD_CLASS
|
||||
|
||||
|
||||
class ProducerHandler(abstract_handler.BaseHandler):
|
||||
"""Base class for all QueueProducers.
|
||||
|
||||
used to send messages via the Class variables load_balancer, listener,
|
||||
health_monitor, member, l7policy and l7rule.
|
||||
"""
|
||||
def __init__(self):
|
||||
self.load_balancer = LoadBalancerProducer()
|
||||
self.listener = ListenerProducer()
|
||||
self.pool = PoolProducer()
|
||||
self.health_monitor = HealthMonitorProducer()
|
||||
self.member = MemberProducer()
|
||||
self.l7policy = L7PolicyProducer()
|
||||
self.l7rule = L7RuleProducer()
|
||||
self.amphora = AmphoraProducer()
|
||||
@@ -12,18 +12,15 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log as logging
|
||||
from pecan import request as pecan_request
|
||||
from pecan import rest
|
||||
from wsme import types as wtypes
|
||||
from wsmeext import pecan as wsme_pecan
|
||||
|
||||
from octavia.api.v1 import controllers as v1_controller
|
||||
from octavia.api.v2 import controllers as v2_controller
|
||||
|
||||
|
||||
CONF = cfg.CONF
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -32,20 +29,8 @@ class RootController(rest.RestController):
|
||||
|
||||
def __init__(self):
|
||||
super(RootController, self).__init__()
|
||||
v1_enabled = CONF.api_settings.api_v1_enabled
|
||||
v2_enabled = CONF.api_settings.api_v2_enabled
|
||||
if v1_enabled:
|
||||
self.v1 = v1_controller.V1Controller()
|
||||
if v2_enabled:
|
||||
setattr(self, 'v2.0', v2_controller.V2Controller())
|
||||
setattr(self, 'v2', v2_controller.V2Controller())
|
||||
if not (v1_enabled or v2_enabled):
|
||||
LOG.warning("Both v1 and v2 API endpoints are disabled -- is "
|
||||
"this intentional?")
|
||||
elif v1_enabled and v2_enabled:
|
||||
LOG.warning("Both v1 and v2 API endpoints are enabled -- it is "
|
||||
"a security risk to expose the v1 endpoint publicly,"
|
||||
"so please make sure access to it is secured.")
|
||||
setattr(self, 'v2.0', v2_controller.V2Controller())
|
||||
setattr(self, 'v2', v2_controller.V2Controller())
|
||||
|
||||
def _add_a_version(self, versions, version, url_version, status,
|
||||
timestamp, base_url):
|
||||
@@ -67,37 +52,33 @@ class RootController(rest.RestController):
|
||||
host_url = '{}/'.format(host_url)
|
||||
|
||||
versions = []
|
||||
if CONF.api_settings.api_v1_enabled:
|
||||
self._add_a_version(versions, 'v1', 'v1', 'DEPRECATED',
|
||||
'2014-12-11T00:00:00Z', host_url)
|
||||
if CONF.api_settings.api_v2_enabled:
|
||||
self._add_a_version(versions, 'v2.0', 'v2', 'SUPPORTED',
|
||||
'2016-12-11T00:00:00Z', host_url)
|
||||
self._add_a_version(versions, 'v2.1', 'v2', 'SUPPORTED',
|
||||
'2018-04-20T00:00:00Z', host_url)
|
||||
self._add_a_version(versions, 'v2.2', 'v2', 'SUPPORTED',
|
||||
'2018-07-31T00:00:00Z', host_url)
|
||||
self._add_a_version(versions, 'v2.3', 'v2', 'SUPPORTED',
|
||||
'2018-12-18T00:00:00Z', host_url)
|
||||
# amp statistics
|
||||
self._add_a_version(versions, 'v2.4', 'v2', 'SUPPORTED',
|
||||
'2018-12-19T00:00:00Z', host_url)
|
||||
# Tags
|
||||
self._add_a_version(versions, 'v2.5', 'v2', 'SUPPORTED',
|
||||
'2019-01-21T00:00:00Z', host_url)
|
||||
# Flavors
|
||||
self._add_a_version(versions, 'v2.6', 'v2', 'SUPPORTED',
|
||||
'2019-01-25T00:00:00Z', host_url)
|
||||
# Amphora Config update
|
||||
self._add_a_version(versions, 'v2.7', 'v2', 'SUPPORTED',
|
||||
'2018-01-25T12:00:00Z', host_url)
|
||||
# TLS client authentication
|
||||
self._add_a_version(versions, 'v2.8', 'v2', 'SUPPORTED',
|
||||
'2019-02-12T00:00:00Z', host_url)
|
||||
# HTTP Redirect code
|
||||
self._add_a_version(versions, 'v2.9', 'v2', 'SUPPORTED',
|
||||
'2019-03-04T00:00:00Z', host_url)
|
||||
# Healthmonitor host header
|
||||
self._add_a_version(versions, 'v2.10', 'v2', 'CURRENT',
|
||||
'2019-03-05T00:00:00Z', host_url)
|
||||
self._add_a_version(versions, 'v2.0', 'v2', 'SUPPORTED',
|
||||
'2016-12-11T00:00:00Z', host_url)
|
||||
self._add_a_version(versions, 'v2.1', 'v2', 'SUPPORTED',
|
||||
'2018-04-20T00:00:00Z', host_url)
|
||||
self._add_a_version(versions, 'v2.2', 'v2', 'SUPPORTED',
|
||||
'2018-07-31T00:00:00Z', host_url)
|
||||
self._add_a_version(versions, 'v2.3', 'v2', 'SUPPORTED',
|
||||
'2018-12-18T00:00:00Z', host_url)
|
||||
# amp statistics
|
||||
self._add_a_version(versions, 'v2.4', 'v2', 'SUPPORTED',
|
||||
'2018-12-19T00:00:00Z', host_url)
|
||||
# Tags
|
||||
self._add_a_version(versions, 'v2.5', 'v2', 'SUPPORTED',
|
||||
'2019-01-21T00:00:00Z', host_url)
|
||||
# Flavors
|
||||
self._add_a_version(versions, 'v2.6', 'v2', 'SUPPORTED',
|
||||
'2019-01-25T00:00:00Z', host_url)
|
||||
# Amphora Config update
|
||||
self._add_a_version(versions, 'v2.7', 'v2', 'SUPPORTED',
|
||||
'2018-01-25T12:00:00Z', host_url)
|
||||
# TLS client authentication
|
||||
self._add_a_version(versions, 'v2.8', 'v2', 'SUPPORTED',
|
||||
'2019-02-12T00:00:00Z', host_url)
|
||||
# HTTP Redirect code
|
||||
self._add_a_version(versions, 'v2.9', 'v2', 'SUPPORTED',
|
||||
'2019-03-04T00:00:00Z', host_url)
|
||||
# Healthmonitor host header
|
||||
self._add_a_version(versions, 'v2.10', 'v2', 'CURRENT',
|
||||
'2019-03-05T00:00:00Z', host_url)
|
||||
return {'versions': versions}
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
# 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.
|
||||
@@ -1,36 +0,0 @@
|
||||
# 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.
|
||||
|
||||
from wsme import types as wtypes
|
||||
from wsmeext import pecan as wsme_pecan
|
||||
|
||||
from octavia.api.v1.controllers import base
|
||||
from octavia.api.v1.controllers import load_balancer
|
||||
from octavia.api.v1.controllers import quotas
|
||||
|
||||
|
||||
class V1Controller(base.BaseController):
|
||||
|
||||
loadbalancers = None
|
||||
quotas = None
|
||||
|
||||
def __init__(self):
|
||||
super(V1Controller, self).__init__()
|
||||
self.loadbalancers = load_balancer.LoadBalancersController()
|
||||
self.quotas = quotas.QuotasController()
|
||||
|
||||
@wsme_pecan.wsexpose(wtypes.text)
|
||||
def get(self):
|
||||
# TODO(blogan): decide what exactly should be here, if anything
|
||||
return "v1"
|
||||
@@ -1,141 +0,0 @@
|
||||
# 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.
|
||||
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log as logging
|
||||
from pecan import rest
|
||||
from stevedore import driver as stevedore_driver
|
||||
|
||||
from octavia.common import data_models
|
||||
from octavia.common import exceptions
|
||||
from octavia.db import repositories
|
||||
|
||||
CONF = cfg.CONF
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class BaseController(rest.RestController):
|
||||
|
||||
def __init__(self):
|
||||
super(BaseController, self).__init__()
|
||||
self.repositories = repositories.Repositories()
|
||||
self.handler = stevedore_driver.DriverManager(
|
||||
namespace='octavia.api.handlers',
|
||||
name=CONF.api_settings.api_handler,
|
||||
invoke_on_load=True
|
||||
).driver
|
||||
|
||||
@staticmethod
|
||||
def _convert_db_to_type(db_entity, to_type, children=False):
|
||||
"""Converts a data model into an Octavia WSME type
|
||||
|
||||
:param db_entity: data model to convert
|
||||
:param to_type: converts db_entity to this time
|
||||
"""
|
||||
if isinstance(to_type, list):
|
||||
to_type = to_type[0]
|
||||
|
||||
def _convert(db_obj):
|
||||
return to_type.from_data_model(db_obj, children=children)
|
||||
if isinstance(db_entity, list):
|
||||
converted = [_convert(db_obj) for db_obj in db_entity]
|
||||
else:
|
||||
converted = _convert(db_entity)
|
||||
return converted
|
||||
|
||||
@staticmethod
|
||||
def _get_db_obj(session, repo, data_model, id):
|
||||
"""Gets an object from the database and returns it."""
|
||||
db_obj = repo.get(session, id=id)
|
||||
if not db_obj:
|
||||
LOG.exception('%(name)s %(id)s not found',
|
||||
{'name': data_model._name(), 'id': id})
|
||||
raise exceptions.NotFound(
|
||||
resource=data_model._name(), id=id)
|
||||
return db_obj
|
||||
|
||||
def _get_db_lb(self, session, id):
|
||||
"""Get a load balancer from the database."""
|
||||
return self._get_db_obj(session, self.repositories.load_balancer,
|
||||
data_models.LoadBalancer, id)
|
||||
|
||||
def _get_db_listener(self, session, id):
|
||||
"""Get a listener from the database."""
|
||||
return self._get_db_obj(session, self.repositories.listener,
|
||||
data_models.Listener, id)
|
||||
|
||||
def _get_db_pool(self, session, id):
|
||||
"""Get a pool from the database."""
|
||||
return self._get_db_obj(session, self.repositories.pool,
|
||||
data_models.Pool, id)
|
||||
|
||||
def _get_db_member(self, session, id):
|
||||
"""Get a member from the database."""
|
||||
return self._get_db_obj(session, self.repositories.member,
|
||||
data_models.Member, id)
|
||||
|
||||
def _get_db_l7policy(self, session, id):
|
||||
"""Get a L7 Policy from the database."""
|
||||
return self._get_db_obj(session, self.repositories.l7policy,
|
||||
data_models.L7Policy, id)
|
||||
|
||||
def _get_db_l7rule(self, session, id):
|
||||
"""Get a L7 Rule from the database."""
|
||||
return self._get_db_obj(session, self.repositories.l7rule,
|
||||
data_models.L7Rule, id)
|
||||
|
||||
def _get_default_quotas(self, project_id):
|
||||
"""Gets the project's default quotas."""
|
||||
quotas = data_models.Quotas(
|
||||
project_id=project_id,
|
||||
load_balancer=CONF.quotas.default_load_balancer_quota,
|
||||
listener=CONF.quotas.default_listener_quota,
|
||||
pool=CONF.quotas.default_pool_quota,
|
||||
health_monitor=CONF.quotas.default_health_monitor_quota,
|
||||
member=CONF.quotas.default_member_quota)
|
||||
return quotas
|
||||
|
||||
def _get_db_quotas(self, session, project_id):
|
||||
"""Gets the project's quotas from the database, or responds with the
|
||||
|
||||
default quotas.
|
||||
"""
|
||||
# At this point project_id should not ever be None or Unset
|
||||
db_quotas = self.repositories.quotas.get(
|
||||
session, project_id=project_id)
|
||||
if not db_quotas:
|
||||
LOG.debug("No custom quotas for project %s. Returning "
|
||||
"defaults...", project_id)
|
||||
db_quotas = self._get_default_quotas(project_id=project_id)
|
||||
else:
|
||||
# Fill in any that are using the configured defaults
|
||||
if db_quotas.load_balancer is None:
|
||||
db_quotas.load_balancer = (CONF.quotas.
|
||||
default_load_balancer_quota)
|
||||
if db_quotas.listener is None:
|
||||
db_quotas.listener = CONF.quotas.default_listener_quota
|
||||
if db_quotas.pool is None:
|
||||
db_quotas.pool = CONF.quotas.default_pool_quota
|
||||
if db_quotas.health_monitor is None:
|
||||
db_quotas.health_monitor = (CONF.quotas.
|
||||
default_health_monitor_quota)
|
||||
if db_quotas.member is None:
|
||||
db_quotas.member = CONF.quotas.default_member_quota
|
||||
return db_quotas
|
||||
|
||||
def _get_lb_project_id(self, session, id):
|
||||
"""Get the project_id of the load balancer from the database."""
|
||||
lb = self._get_db_obj(session, self.repositories.load_balancer,
|
||||
data_models.LoadBalancer, id)
|
||||
return lb.project_id
|
||||
@@ -1,200 +0,0 @@
|
||||
# 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.
|
||||
|
||||
from oslo_db import exception as odb_exceptions
|
||||
from oslo_log import log as logging
|
||||
from oslo_utils import excutils
|
||||
import pecan
|
||||
from wsmeext import pecan as wsme_pecan
|
||||
|
||||
from octavia.api.v1.controllers import base
|
||||
from octavia.api.v1.types import health_monitor as hm_types
|
||||
from octavia.common import constants
|
||||
from octavia.common import data_models
|
||||
from octavia.common import exceptions
|
||||
from octavia.db import api as db_api
|
||||
from octavia.db import prepare as db_prepare
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class HealthMonitorController(base.BaseController):
|
||||
|
||||
def __init__(self, load_balancer_id, pool_id, listener_id=None):
|
||||
super(HealthMonitorController, self).__init__()
|
||||
self.load_balancer_id = load_balancer_id
|
||||
self.listener_id = listener_id
|
||||
self.pool_id = pool_id
|
||||
self.handler = self.handler.health_monitor
|
||||
|
||||
def _get_db_hm(self, session):
|
||||
"""Gets the current health monitor object from the database."""
|
||||
db_hm = self.repositories.health_monitor.get(
|
||||
session, pool_id=self.pool_id)
|
||||
if not db_hm:
|
||||
LOG.info("Health Monitor for Pool %s was not found",
|
||||
self.pool_id)
|
||||
raise exceptions.NotFound(
|
||||
resource=data_models.HealthMonitor._name(),
|
||||
id=self.pool_id)
|
||||
return db_hm
|
||||
|
||||
@wsme_pecan.wsexpose(hm_types.HealthMonitorResponse)
|
||||
def get_all(self):
|
||||
"""Gets a single health monitor's details."""
|
||||
# NOTE(blogan): since a pool can only have one health monitor
|
||||
# we are using the get_all method to only get the single health monitor
|
||||
context = pecan.request.context.get('octavia_context')
|
||||
db_hm = self._get_db_hm(context.session)
|
||||
return self._convert_db_to_type(db_hm, hm_types.HealthMonitorResponse)
|
||||
|
||||
def _get_affected_listener_ids(self, session, hm=None):
|
||||
"""Gets a list of all listeners this request potentially affects."""
|
||||
listener_ids = []
|
||||
if hm:
|
||||
listener_ids = [l.id for l in hm.pool.listeners]
|
||||
else:
|
||||
pool = self._get_db_pool(session, self.pool_id)
|
||||
listener_ids = [listener.id for listener in pool.listeners]
|
||||
if self.listener_id and self.listener_id not in listener_ids:
|
||||
listener_ids.append(self.listener_id)
|
||||
return listener_ids
|
||||
|
||||
def _test_lb_and_listener_statuses(self, session, hm=None):
|
||||
"""Verify load balancer is in a mutable state."""
|
||||
# We need to verify that any listeners referencing this pool are also
|
||||
# mutable
|
||||
if not self.repositories.test_and_set_lb_and_listeners_prov_status(
|
||||
session, self.load_balancer_id,
|
||||
constants.PENDING_UPDATE, constants.PENDING_UPDATE,
|
||||
listener_ids=self._get_affected_listener_ids(session, hm)):
|
||||
LOG.info("Health Monitor cannot be created or modified "
|
||||
"because the Load Balancer is in an immutable state")
|
||||
lb_repo = self.repositories.load_balancer
|
||||
db_lb = lb_repo.get(session, id=self.load_balancer_id)
|
||||
raise exceptions.ImmutableObject(resource=db_lb._name(),
|
||||
id=self.load_balancer_id)
|
||||
|
||||
@wsme_pecan.wsexpose(hm_types.HealthMonitorResponse,
|
||||
body=hm_types.HealthMonitorPOST, status_code=202)
|
||||
def post(self, health_monitor):
|
||||
"""Creates a health monitor on a pool."""
|
||||
context = pecan.request.context.get('octavia_context')
|
||||
|
||||
health_monitor.project_id = self._get_lb_project_id(
|
||||
context.session, self.load_balancer_id)
|
||||
|
||||
try:
|
||||
db_hm = self.repositories.health_monitor.get(
|
||||
context.session, pool_id=self.pool_id)
|
||||
if db_hm:
|
||||
raise exceptions.DuplicateHealthMonitor()
|
||||
except exceptions.NotFound:
|
||||
pass
|
||||
|
||||
lock_session = db_api.get_session(autocommit=False)
|
||||
if self.repositories.check_quota_met(
|
||||
context.session,
|
||||
lock_session,
|
||||
data_models.HealthMonitor,
|
||||
health_monitor.project_id):
|
||||
lock_session.rollback()
|
||||
raise exceptions.QuotaException(
|
||||
resource=data_models.HealthMonitor._name()
|
||||
)
|
||||
|
||||
try:
|
||||
hm_dict = db_prepare.create_health_monitor(
|
||||
health_monitor.to_dict(render_unsets=True), self.pool_id)
|
||||
self._test_lb_and_listener_statuses(lock_session)
|
||||
|
||||
db_hm = self.repositories.health_monitor.create(lock_session,
|
||||
**hm_dict)
|
||||
db_new_hm = self._get_db_hm(lock_session)
|
||||
lock_session.commit()
|
||||
except odb_exceptions.DBError:
|
||||
lock_session.rollback()
|
||||
raise exceptions.InvalidOption(value=hm_dict.get('type'),
|
||||
option='type')
|
||||
except Exception:
|
||||
with excutils.save_and_reraise_exception():
|
||||
lock_session.rollback()
|
||||
|
||||
try:
|
||||
LOG.info("Sending Creation of Health Monitor for Pool %s to "
|
||||
"handler", self.pool_id)
|
||||
self.handler.create(db_hm)
|
||||
except Exception:
|
||||
for listener_id in self._get_affected_listener_ids(
|
||||
context.session):
|
||||
with excutils.save_and_reraise_exception(reraise=False):
|
||||
self.repositories.listener.update(
|
||||
context.session, listener_id,
|
||||
operating_status=constants.ERROR)
|
||||
return self._convert_db_to_type(db_new_hm,
|
||||
hm_types.HealthMonitorResponse)
|
||||
|
||||
@wsme_pecan.wsexpose(hm_types.HealthMonitorResponse,
|
||||
body=hm_types.HealthMonitorPUT, status_code=202)
|
||||
def put(self, health_monitor):
|
||||
"""Updates a health monitor.
|
||||
|
||||
Updates a health monitor on a pool if it exists. Only one health
|
||||
monitor is allowed per pool so there is no need for a health monitor
|
||||
id.
|
||||
"""
|
||||
context = pecan.request.context.get('octavia_context')
|
||||
db_hm = self._get_db_hm(context.session)
|
||||
self._test_lb_and_listener_statuses(context.session, hm=db_hm)
|
||||
|
||||
self.repositories.health_monitor.update(
|
||||
context.session, db_hm.id,
|
||||
provisioning_status=constants.PENDING_UPDATE)
|
||||
|
||||
try:
|
||||
LOG.info("Sending Update of Health Monitor for Pool %s to handler",
|
||||
self.pool_id)
|
||||
self.handler.update(db_hm, health_monitor)
|
||||
except Exception:
|
||||
with excutils.save_and_reraise_exception(reraise=False):
|
||||
for listener_id in self._get_affected_listener_ids(
|
||||
context.session, db_hm):
|
||||
self.repositories.listener.update(
|
||||
context.session, listener_id,
|
||||
operating_status=constants.ERROR)
|
||||
db_hm = self._get_db_hm(context.session)
|
||||
return self._convert_db_to_type(db_hm, hm_types.HealthMonitorResponse)
|
||||
|
||||
@wsme_pecan.wsexpose(None, status_code=202)
|
||||
def delete(self):
|
||||
"""Deletes a health monitor."""
|
||||
context = pecan.request.context.get('octavia_context')
|
||||
db_hm = self._get_db_hm(context.session)
|
||||
self._test_lb_and_listener_statuses(context.session, hm=db_hm)
|
||||
|
||||
try:
|
||||
LOG.info("Sending Deletion of Health Monitor for Pool %s to "
|
||||
"handler", self.pool_id)
|
||||
self.handler.delete(db_hm)
|
||||
except Exception:
|
||||
with excutils.save_and_reraise_exception(reraise=False):
|
||||
for listener_id in self._get_affected_listener_ids(
|
||||
context.session, db_hm):
|
||||
self.repositories.listener.update(
|
||||
context.session, listener_id,
|
||||
operating_status=constants.ERROR)
|
||||
db_hm = self.repositories.health_monitor.get(
|
||||
context.session, pool_id=self.pool_id)
|
||||
return self._convert_db_to_type(db_hm, hm_types.HealthMonitorResponse)
|
||||
@@ -1,187 +0,0 @@
|
||||
# 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 oslo_db.exception as oslo_exc
|
||||
from oslo_log import log as logging
|
||||
from oslo_utils import excutils
|
||||
import pecan
|
||||
from wsme import types as wtypes
|
||||
from wsmeext import pecan as wsme_pecan
|
||||
|
||||
from octavia.api.v1.controllers import base
|
||||
from octavia.api.v1.controllers import l7rule
|
||||
from octavia.api.v1.types import l7policy as l7policy_types
|
||||
from octavia.common import constants
|
||||
from octavia.common import data_models
|
||||
from octavia.common import exceptions
|
||||
from octavia.common import validate
|
||||
from octavia.db import prepare as db_prepare
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class L7PolicyController(base.BaseController):
|
||||
|
||||
def __init__(self, load_balancer_id, listener_id):
|
||||
super(L7PolicyController, self).__init__()
|
||||
self.load_balancer_id = load_balancer_id
|
||||
self.listener_id = listener_id
|
||||
self.handler = self.handler.l7policy
|
||||
|
||||
@wsme_pecan.wsexpose(l7policy_types.L7PolicyResponse, wtypes.text)
|
||||
def get(self, id):
|
||||
"""Gets a single l7policy's details."""
|
||||
context = pecan.request.context.get('octavia_context')
|
||||
db_l7policy = self._get_db_l7policy(context.session, id)
|
||||
return self._convert_db_to_type(db_l7policy,
|
||||
l7policy_types.L7PolicyResponse)
|
||||
|
||||
@wsme_pecan.wsexpose([l7policy_types.L7PolicyResponse])
|
||||
def get_all(self):
|
||||
"""Lists all l7policies of a listener."""
|
||||
context = pecan.request.context.get('octavia_context')
|
||||
db_l7policies, _ = self.repositories.l7policy.get_all(
|
||||
context.session, listener_id=self.listener_id)
|
||||
return self._convert_db_to_type(db_l7policies,
|
||||
[l7policy_types.L7PolicyResponse])
|
||||
|
||||
def _test_lb_and_listener_statuses(self, session):
|
||||
"""Verify load balancer is in a mutable state."""
|
||||
if not self.repositories.test_and_set_lb_and_listeners_prov_status(
|
||||
session, self.load_balancer_id,
|
||||
constants.PENDING_UPDATE, constants.PENDING_UPDATE,
|
||||
listener_ids=[self.listener_id]):
|
||||
LOG.info("L7Policy cannot be created or modified because the "
|
||||
"Load Balancer is in an immutable state")
|
||||
lb_repo = self.repositories.load_balancer
|
||||
db_lb = lb_repo.get(session, id=self.load_balancer_id)
|
||||
raise exceptions.ImmutableObject(resource=db_lb._name(),
|
||||
id=self.load_balancer_id)
|
||||
|
||||
@wsme_pecan.wsexpose(l7policy_types.L7PolicyResponse,
|
||||
body=l7policy_types.L7PolicyPOST, status_code=202)
|
||||
def post(self, l7policy):
|
||||
"""Creates a l7policy on a listener."""
|
||||
context = pecan.request.context.get('octavia_context')
|
||||
|
||||
l7policy_dict = validate.sanitize_l7policy_api_args(
|
||||
l7policy.to_dict(render_unsets=True), create=True)
|
||||
# Make sure any pool specified by redirect_pool_id exists
|
||||
if l7policy_dict.get('redirect_pool_id'):
|
||||
self._get_db_pool(
|
||||
context.session, l7policy_dict['redirect_pool_id'])
|
||||
l7policy_dict = db_prepare.create_l7policy(l7policy_dict,
|
||||
self.load_balancer_id,
|
||||
self.listener_id)
|
||||
self._test_lb_and_listener_statuses(context.session)
|
||||
|
||||
try:
|
||||
db_l7policy = self.repositories.l7policy.create(context.session,
|
||||
**l7policy_dict)
|
||||
except oslo_exc.DBDuplicateEntry as de:
|
||||
# Setting LB and Listener back to active because this is just a
|
||||
# validation failure
|
||||
self.repositories.load_balancer.update(
|
||||
context.session, self.load_balancer_id,
|
||||
provisioning_status=constants.ACTIVE)
|
||||
self.repositories.listener.update(
|
||||
context.session, self.listener_id,
|
||||
provisioning_status=constants.ACTIVE)
|
||||
if ['id'] == de.columns:
|
||||
raise exceptions.IDAlreadyExists()
|
||||
try:
|
||||
LOG.info("Sending Creation of L7Policy %s to handler",
|
||||
db_l7policy.id)
|
||||
self.handler.create(db_l7policy)
|
||||
except Exception:
|
||||
with excutils.save_and_reraise_exception(reraise=False):
|
||||
self.repositories.listener.update(
|
||||
context.session, self.listener_id,
|
||||
operating_status=constants.ERROR)
|
||||
db_l7policy = self._get_db_l7policy(context.session, db_l7policy.id)
|
||||
return self._convert_db_to_type(db_l7policy,
|
||||
l7policy_types.L7PolicyResponse)
|
||||
|
||||
@wsme_pecan.wsexpose(l7policy_types.L7PolicyResponse,
|
||||
wtypes.text, body=l7policy_types.L7PolicyPUT,
|
||||
status_code=202)
|
||||
def put(self, id, l7policy):
|
||||
"""Updates a l7policy."""
|
||||
l7policy_dict = validate.sanitize_l7policy_api_args(
|
||||
l7policy.to_dict(render_unsets=False))
|
||||
context = pecan.request.context.get('octavia_context')
|
||||
# Make sure any specified redirect_pool_id exists
|
||||
if l7policy_dict.get('redirect_pool_id'):
|
||||
self._get_db_pool(
|
||||
context.session, l7policy_dict['redirect_pool_id'])
|
||||
db_l7policy = self._get_db_l7policy(context.session, id)
|
||||
self._test_lb_and_listener_statuses(context.session)
|
||||
|
||||
self.repositories.l7policy.update(
|
||||
context.session, id, provisioning_status=constants.PENDING_UPDATE)
|
||||
|
||||
try:
|
||||
LOG.info("Sending Update of L7Policy %s to handler", id)
|
||||
self.handler.update(
|
||||
db_l7policy, l7policy_types.L7PolicyPUT(**l7policy_dict))
|
||||
except Exception:
|
||||
with excutils.save_and_reraise_exception(reraise=False):
|
||||
self.repositories.listener.update(
|
||||
context.session, self.listener_id,
|
||||
operating_status=constants.ERROR)
|
||||
db_l7policy = self._get_db_l7policy(context.session, id)
|
||||
return self._convert_db_to_type(db_l7policy,
|
||||
l7policy_types.L7PolicyResponse)
|
||||
|
||||
@wsme_pecan.wsexpose(None, wtypes.text, status_code=202)
|
||||
def delete(self, id):
|
||||
"""Deletes a l7policy."""
|
||||
context = pecan.request.context.get('octavia_context')
|
||||
db_l7policy = self._get_db_l7policy(context.session, id)
|
||||
self._test_lb_and_listener_statuses(context.session)
|
||||
|
||||
try:
|
||||
LOG.info("Sending Deletion of L7Policy %s to handler",
|
||||
db_l7policy.id)
|
||||
self.handler.delete(db_l7policy)
|
||||
except Exception:
|
||||
with excutils.save_and_reraise_exception(reraise=False):
|
||||
self.repositories.listener.update(
|
||||
context.session, self.listener_id,
|
||||
operating_status=constants.ERROR)
|
||||
db_l7policy = self.repositories.l7policy.get(context.session, id=id)
|
||||
return self._convert_db_to_type(db_l7policy,
|
||||
l7policy_types.L7PolicyResponse)
|
||||
|
||||
@pecan.expose()
|
||||
def _lookup(self, l7policy_id, *remainder):
|
||||
"""Overridden pecan _lookup method for custom routing.
|
||||
|
||||
Verifies that the l7policy passed in the url exists, and if so decides
|
||||
which controller, if any, should control be passed.
|
||||
"""
|
||||
context = pecan.request.context.get('octavia_context')
|
||||
if l7policy_id and remainder and remainder[0] == 'l7rules':
|
||||
remainder = remainder[1:]
|
||||
db_l7policy = self.repositories.l7policy.get(
|
||||
context.session, id=l7policy_id)
|
||||
if not db_l7policy:
|
||||
LOG.info("L7Policy %s not found.", l7policy_id)
|
||||
raise exceptions.NotFound(
|
||||
resource=data_models.L7Policy._name(), id=l7policy_id)
|
||||
return l7rule.L7RuleController(
|
||||
load_balancer_id=self.load_balancer_id,
|
||||
listener_id=self.listener_id,
|
||||
l7policy_id=db_l7policy.id), remainder
|
||||
return None
|
||||
@@ -1,170 +0,0 @@
|
||||
# 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 oslo_db.exception as oslo_exc
|
||||
from oslo_log import log as logging
|
||||
from oslo_utils import excutils
|
||||
import pecan
|
||||
from wsme import types as wtypes
|
||||
from wsmeext import pecan as wsme_pecan
|
||||
|
||||
from octavia.api.v1.controllers import base
|
||||
from octavia.api.v1.types import l7rule as l7rule_types
|
||||
from octavia.common import constants
|
||||
from octavia.common import data_models
|
||||
from octavia.common import exceptions
|
||||
from octavia.common import validate
|
||||
from octavia.db import prepare as db_prepare
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class L7RuleController(base.BaseController):
|
||||
|
||||
def __init__(self, load_balancer_id, listener_id, l7policy_id):
|
||||
super(L7RuleController, self).__init__()
|
||||
self.load_balancer_id = load_balancer_id
|
||||
self.listener_id = listener_id
|
||||
self.l7policy_id = l7policy_id
|
||||
self.handler = self.handler.l7rule
|
||||
|
||||
@wsme_pecan.wsexpose(l7rule_types.L7RuleResponse, wtypes.text)
|
||||
def get(self, id):
|
||||
"""Gets a single l7rule's details."""
|
||||
context = pecan.request.context.get('octavia_context')
|
||||
db_l7rule = self._get_db_l7rule(context.session, id)
|
||||
return self._convert_db_to_type(db_l7rule,
|
||||
l7rule_types.L7RuleResponse)
|
||||
|
||||
@wsme_pecan.wsexpose([l7rule_types.L7RuleResponse])
|
||||
def get_all(self):
|
||||
"""Lists all l7rules of a l7policy."""
|
||||
context = pecan.request.context.get('octavia_context')
|
||||
db_l7rules, _ = self.repositories.l7rule.get_all(
|
||||
context.session, l7policy_id=self.l7policy_id)
|
||||
return self._convert_db_to_type(db_l7rules,
|
||||
[l7rule_types.L7RuleResponse])
|
||||
|
||||
def _test_lb_and_listener_statuses(self, session):
|
||||
"""Verify load balancer is in a mutable state."""
|
||||
if not self.repositories.test_and_set_lb_and_listeners_prov_status(
|
||||
session, self.load_balancer_id,
|
||||
constants.PENDING_UPDATE, constants.PENDING_UPDATE,
|
||||
listener_ids=[self.listener_id]):
|
||||
LOG.info("L7Rule cannot be created or modified because the "
|
||||
"Load Balancer is in an immutable state")
|
||||
lb_repo = self.repositories.load_balancer
|
||||
db_lb = lb_repo.get(session, id=self.load_balancer_id)
|
||||
raise exceptions.ImmutableObject(resource=db_lb._name(),
|
||||
id=self.load_balancer_id)
|
||||
|
||||
def _check_l7policy_max_rules(self, session):
|
||||
"""Checks to make sure the L7Policy doesn't have too many rules."""
|
||||
count = self.repositories.l7rule.count(
|
||||
session, l7policy_id=self.l7policy_id)
|
||||
if count >= constants.MAX_L7RULES_PER_L7POLICY:
|
||||
raise exceptions.TooManyL7RulesOnL7Policy(id=self.l7policy_id)
|
||||
|
||||
@wsme_pecan.wsexpose(l7rule_types.L7RuleResponse,
|
||||
body=l7rule_types.L7RulePOST, status_code=202)
|
||||
def post(self, l7rule):
|
||||
"""Creates a l7rule on an l7policy."""
|
||||
try:
|
||||
validate.l7rule_data(l7rule)
|
||||
except Exception as e:
|
||||
raise exceptions.L7RuleValidation(error=e)
|
||||
context = pecan.request.context.get('octavia_context')
|
||||
|
||||
self._check_l7policy_max_rules(context.session)
|
||||
l7rule_dict = db_prepare.create_l7rule(
|
||||
l7rule.to_dict(render_unsets=True), self.l7policy_id)
|
||||
self._test_lb_and_listener_statuses(context.session)
|
||||
|
||||
try:
|
||||
db_l7rule = self.repositories.l7rule.create(context.session,
|
||||
**l7rule_dict)
|
||||
except oslo_exc.DBDuplicateEntry as de:
|
||||
# Setting LB and Listener back to active because this is just a
|
||||
# validation failure
|
||||
self.repositories.load_balancer.update(
|
||||
context.session, self.load_balancer_id,
|
||||
provisioning_status=constants.ACTIVE)
|
||||
self.repositories.listener.update(
|
||||
context.session, self.listener_id,
|
||||
provisioning_status=constants.ACTIVE)
|
||||
if ['id'] == de.columns:
|
||||
raise exceptions.IDAlreadyExists()
|
||||
try:
|
||||
LOG.info("Sending Creation of L7Rule %s to handler",
|
||||
db_l7rule.id)
|
||||
self.handler.create(db_l7rule)
|
||||
except Exception:
|
||||
with excutils.save_and_reraise_exception(reraise=False):
|
||||
self.repositories.listener.update(
|
||||
context.session, self.listener_id,
|
||||
operating_status=constants.ERROR)
|
||||
db_l7rule = self._get_db_l7rule(context.session, db_l7rule.id)
|
||||
return self._convert_db_to_type(db_l7rule,
|
||||
l7rule_types.L7RuleResponse)
|
||||
|
||||
@wsme_pecan.wsexpose(l7rule_types.L7RuleResponse,
|
||||
wtypes.text, body=l7rule_types.L7RulePUT,
|
||||
status_code=202)
|
||||
def put(self, id, l7rule):
|
||||
"""Updates a l7rule."""
|
||||
context = pecan.request.context.get('octavia_context')
|
||||
db_l7rule = self._get_db_l7rule(context.session, id)
|
||||
new_l7rule = db_l7rule.to_dict()
|
||||
new_l7rule.update(l7rule.to_dict())
|
||||
new_l7rule = data_models.L7Rule.from_dict(new_l7rule)
|
||||
try:
|
||||
validate.l7rule_data(new_l7rule)
|
||||
except Exception as e:
|
||||
raise exceptions.L7RuleValidation(error=e)
|
||||
self._test_lb_and_listener_statuses(context.session)
|
||||
|
||||
self.repositories.l7rule.update(
|
||||
context.session, id, provisioning_status=constants.PENDING_UPDATE)
|
||||
|
||||
try:
|
||||
LOG.info("Sending Update of L7Rule %s to handler", id)
|
||||
self.handler.update(db_l7rule, l7rule)
|
||||
except Exception:
|
||||
with excutils.save_and_reraise_exception(reraise=False):
|
||||
self.repositories.listener.update(
|
||||
context.session, self.listener_id,
|
||||
operating_status=constants.ERROR)
|
||||
db_l7rule = self._get_db_l7rule(context.session, id)
|
||||
return self._convert_db_to_type(db_l7rule,
|
||||
l7rule_types.L7RuleResponse)
|
||||
|
||||
@wsme_pecan.wsexpose(None, wtypes.text, status_code=202)
|
||||
def delete(self, id):
|
||||
"""Deletes a l7rule."""
|
||||
context = pecan.request.context.get('octavia_context')
|
||||
db_l7rule = self._get_db_l7rule(context.session, id)
|
||||
self._test_lb_and_listener_statuses(context.session)
|
||||
|
||||
try:
|
||||
LOG.info("Sending Deletion of L7Rule %s to handler",
|
||||
db_l7rule.id)
|
||||
self.handler.delete(db_l7rule)
|
||||
except Exception:
|
||||
with excutils.save_and_reraise_exception(reraise=False):
|
||||
self.repositories.listener.update(
|
||||
context.session, self.listener_id,
|
||||
operating_status=constants.ERROR)
|
||||
db_l7rule = self.repositories.l7rule.get(context.session, id=id)
|
||||
return self._convert_db_to_type(db_l7rule,
|
||||
l7rule_types.L7RuleResponse)
|
||||
@@ -1,282 +0,0 @@
|
||||
# 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.
|
||||
|
||||
from oslo_db import exception as odb_exceptions
|
||||
from oslo_log import log as logging
|
||||
from oslo_utils import excutils
|
||||
import pecan
|
||||
from wsme import types as wtypes
|
||||
from wsmeext import pecan as wsme_pecan
|
||||
|
||||
from octavia.api.v1.controllers import base
|
||||
from octavia.api.v1.controllers import l7policy
|
||||
from octavia.api.v1.controllers import listener_statistics
|
||||
from octavia.api.v1.controllers import pool
|
||||
from octavia.api.v1.types import listener as listener_types
|
||||
from octavia.common import constants
|
||||
from octavia.common import data_models
|
||||
from octavia.common import exceptions
|
||||
from octavia.db import api as db_api
|
||||
from octavia.db import prepare as db_prepare
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ListenersController(base.BaseController):
|
||||
|
||||
def __init__(self, load_balancer_id):
|
||||
super(ListenersController, self).__init__()
|
||||
self.load_balancer_id = load_balancer_id
|
||||
self.handler = self.handler.listener
|
||||
|
||||
@staticmethod
|
||||
def _secure_data(listener):
|
||||
# TODO(blogan): Handle this data when certificate management code is
|
||||
# available
|
||||
listener.tls_termination = wtypes.Unset
|
||||
|
||||
def _get_db_listener(self, session, id):
|
||||
"""Gets a listener object from the database."""
|
||||
db_listener = self.repositories.listener.get(
|
||||
session, load_balancer_id=self.load_balancer_id, id=id)
|
||||
if not db_listener:
|
||||
LOG.info("Listener %s not found.", id)
|
||||
raise exceptions.NotFound(
|
||||
resource=data_models.Listener._name(), id=id)
|
||||
return db_listener
|
||||
|
||||
@wsme_pecan.wsexpose(listener_types.ListenerResponse, wtypes.text)
|
||||
def get_one(self, id):
|
||||
"""Gets a single listener's details."""
|
||||
context = pecan.request.context.get('octavia_context')
|
||||
db_listener = self._get_db_listener(context.session, id)
|
||||
return self._convert_db_to_type(db_listener,
|
||||
listener_types.ListenerResponse)
|
||||
|
||||
@wsme_pecan.wsexpose([listener_types.ListenerResponse],
|
||||
ignore_extra_args=True)
|
||||
def get_all(self):
|
||||
"""Lists all listeners on a load balancer."""
|
||||
context = pecan.request.context.get('octavia_context')
|
||||
pcontext = pecan.request.context
|
||||
db_listeners, _ = self.repositories.listener.get_all(
|
||||
context.session,
|
||||
pagination_helper=pcontext.get(constants.PAGINATION_HELPER),
|
||||
load_balancer_id=self.load_balancer_id)
|
||||
return self._convert_db_to_type(db_listeners,
|
||||
[listener_types.ListenerResponse])
|
||||
|
||||
def _test_lb_and_listener_statuses(
|
||||
self, session, id=None, listener_status=constants.PENDING_UPDATE):
|
||||
"""Verify load balancer is in a mutable state."""
|
||||
lb_repo = self.repositories.load_balancer
|
||||
if id:
|
||||
if not self.repositories.test_and_set_lb_and_listeners_prov_status(
|
||||
session, self.load_balancer_id, constants.PENDING_UPDATE,
|
||||
listener_status, listener_ids=[id]):
|
||||
LOG.info("Load Balancer %s is immutable.",
|
||||
self.load_balancer_id)
|
||||
db_lb = lb_repo.get(session, id=self.load_balancer_id)
|
||||
raise exceptions.ImmutableObject(resource=db_lb._name(),
|
||||
id=self.load_balancer_id)
|
||||
else:
|
||||
if not lb_repo.test_and_set_provisioning_status(
|
||||
session, self.load_balancer_id, constants.PENDING_UPDATE):
|
||||
db_lb = lb_repo.get(session, id=self.load_balancer_id)
|
||||
LOG.info("Load Balancer %s is immutable.", db_lb.id)
|
||||
raise exceptions.ImmutableObject(resource=db_lb._name(),
|
||||
id=self.load_balancer_id)
|
||||
|
||||
def _validate_pool(self, session, pool_id):
|
||||
"""Validate pool given exists on same load balancer as listener."""
|
||||
db_pool = self.repositories.pool.get(
|
||||
session, load_balancer_id=self.load_balancer_id, id=pool_id)
|
||||
if not db_pool:
|
||||
raise exceptions.NotFound(
|
||||
resource=data_models.Pool._name(), id=pool_id)
|
||||
|
||||
def _validate_listener(self, lock_session, listener_dict):
|
||||
"""Validate listener for wrong protocol or duplicate listeners
|
||||
|
||||
Update the load balancer db when provisioning status changes.
|
||||
"""
|
||||
if (listener_dict and
|
||||
listener_dict.get('insert_headers') and
|
||||
list(set(listener_dict['insert_headers'].keys()) -
|
||||
set(constants.SUPPORTED_HTTP_HEADERS))):
|
||||
raise exceptions.InvalidOption(
|
||||
value=listener_dict.get('insert_headers'),
|
||||
option='insert_headers')
|
||||
|
||||
try:
|
||||
sni_containers = listener_dict.pop('sni_containers', [])
|
||||
db_listener = self.repositories.listener.create(
|
||||
lock_session, **listener_dict)
|
||||
if sni_containers:
|
||||
for container in sni_containers:
|
||||
sni_dict = {'listener_id': db_listener.id,
|
||||
'tls_container_id': container.get(
|
||||
'tls_container_id')}
|
||||
self.repositories.sni.create(lock_session, **sni_dict)
|
||||
db_listener = self.repositories.listener.get(lock_session,
|
||||
id=db_listener.id)
|
||||
return db_listener
|
||||
except odb_exceptions.DBDuplicateEntry as de:
|
||||
if ['id'] == de.columns:
|
||||
raise exceptions.IDAlreadyExists()
|
||||
elif set(['load_balancer_id', 'protocol_port']) == set(de.columns):
|
||||
raise exceptions.DuplicateListenerEntry(
|
||||
port=listener_dict.get('protocol_port'))
|
||||
except odb_exceptions.DBError:
|
||||
raise exceptions.InvalidOption(value=listener_dict.get('protocol'),
|
||||
option='protocol')
|
||||
|
||||
def _send_listener_to_handler(self, session, db_listener):
|
||||
try:
|
||||
LOG.info("Sending Creation of Listener %s to handler",
|
||||
db_listener.id)
|
||||
self.handler.create(db_listener)
|
||||
except Exception:
|
||||
with excutils.save_and_reraise_exception(reraise=False):
|
||||
self.repositories.listener.update(
|
||||
session, db_listener.id,
|
||||
provisioning_status=constants.ERROR)
|
||||
db_listener = self._get_db_listener(session, db_listener.id)
|
||||
return self._convert_db_to_type(db_listener,
|
||||
listener_types.ListenerResponse)
|
||||
|
||||
@wsme_pecan.wsexpose(listener_types.ListenerResponse,
|
||||
body=listener_types.ListenerPOST, status_code=202)
|
||||
def post(self, listener):
|
||||
"""Creates a listener on a load balancer."""
|
||||
context = pecan.request.context.get('octavia_context')
|
||||
|
||||
listener.project_id = self._get_lb_project_id(context.session,
|
||||
self.load_balancer_id)
|
||||
|
||||
lock_session = db_api.get_session(autocommit=False)
|
||||
if self.repositories.check_quota_met(
|
||||
context.session,
|
||||
lock_session,
|
||||
data_models.Listener,
|
||||
listener.project_id):
|
||||
lock_session.rollback()
|
||||
raise exceptions.QuotaException(
|
||||
resource=data_models.Listener._name())
|
||||
|
||||
try:
|
||||
self._secure_data(listener)
|
||||
listener_dict = db_prepare.create_listener(
|
||||
listener.to_dict(render_unsets=True), self.load_balancer_id)
|
||||
if listener_dict['default_pool_id']:
|
||||
self._validate_pool(lock_session,
|
||||
listener_dict['default_pool_id'])
|
||||
self._test_lb_and_listener_statuses(lock_session)
|
||||
# NOTE(blogan): Throwing away because we should not store
|
||||
# secure data in the database nor should we send it to a handler.
|
||||
if 'tls_termination' in listener_dict:
|
||||
del listener_dict['tls_termination']
|
||||
# This is the extra validation layer for wrong protocol or
|
||||
# duplicate listeners on the same load balancer.
|
||||
db_listener = self._validate_listener(lock_session, listener_dict)
|
||||
lock_session.commit()
|
||||
except Exception:
|
||||
with excutils.save_and_reraise_exception():
|
||||
lock_session.rollback()
|
||||
|
||||
return self._send_listener_to_handler(context.session, db_listener)
|
||||
|
||||
@wsme_pecan.wsexpose(listener_types.ListenerResponse, wtypes.text,
|
||||
body=listener_types.ListenerPUT, status_code=202)
|
||||
def put(self, id, listener):
|
||||
"""Updates a listener on a load balancer."""
|
||||
self._secure_data(listener)
|
||||
context = pecan.request.context.get('octavia_context')
|
||||
db_listener = self._get_db_listener(context.session, id)
|
||||
listener_dict = listener.to_dict()
|
||||
if listener_dict.get('default_pool_id'):
|
||||
self._validate_pool(context.session,
|
||||
listener_dict['default_pool_id'])
|
||||
self._test_lb_and_listener_statuses(context.session, id=id)
|
||||
|
||||
try:
|
||||
LOG.info("Sending Update of Listener %s to handler", id)
|
||||
self.handler.update(db_listener, listener)
|
||||
except Exception:
|
||||
with excutils.save_and_reraise_exception(reraise=False):
|
||||
self.repositories.listener.update(
|
||||
context.session, id, provisioning_status=constants.ERROR)
|
||||
db_listener = self._get_db_listener(context.session, id)
|
||||
return self._convert_db_to_type(db_listener,
|
||||
listener_types.ListenerResponse)
|
||||
|
||||
@wsme_pecan.wsexpose(None, wtypes.text, status_code=202)
|
||||
def delete(self, id):
|
||||
"""Deletes a listener from a load balancer."""
|
||||
context = pecan.request.context.get('octavia_context')
|
||||
db_listener = self._get_db_listener(context.session, id)
|
||||
self._test_lb_and_listener_statuses(
|
||||
context.session, id=id, listener_status=constants.PENDING_DELETE)
|
||||
|
||||
try:
|
||||
LOG.info("Sending Deletion of Listener %s to handler",
|
||||
db_listener.id)
|
||||
self.handler.delete(db_listener)
|
||||
except Exception:
|
||||
with excutils.save_and_reraise_exception(reraise=False):
|
||||
self.repositories.listener.update(
|
||||
context.session, db_listener.id,
|
||||
provisioning_status=constants.ERROR)
|
||||
db_listener = self.repositories.listener.get(
|
||||
context.session, id=db_listener.id)
|
||||
return self._convert_db_to_type(db_listener,
|
||||
listener_types.ListenerResponse)
|
||||
|
||||
@pecan.expose()
|
||||
def _lookup(self, listener_id, *remainder):
|
||||
"""Overridden pecan _lookup method for custom routing.
|
||||
|
||||
Verifies that the listener passed in the url exists, and if so decides
|
||||
which controller, if any, should control be passed.
|
||||
"""
|
||||
context = pecan.request.context.get('octavia_context')
|
||||
is_children = (
|
||||
listener_id and remainder and (
|
||||
remainder[0] == 'pools' or (
|
||||
remainder[0] == 'l7policies' or remainder[0] == 'stats'
|
||||
)
|
||||
)
|
||||
)
|
||||
if is_children:
|
||||
controller = remainder[0]
|
||||
remainder = remainder[1:]
|
||||
db_listener = self.repositories.listener.get(
|
||||
context.session, id=listener_id)
|
||||
if not db_listener:
|
||||
LOG.info("Listener %s not found.", listener_id)
|
||||
raise exceptions.NotFound(
|
||||
resource=data_models.Listener._name(), id=listener_id)
|
||||
if controller == 'pools':
|
||||
return pool.PoolsController(
|
||||
load_balancer_id=self.load_balancer_id,
|
||||
listener_id=db_listener.id), remainder
|
||||
elif controller == 'l7policies':
|
||||
return l7policy.L7PolicyController(
|
||||
load_balancer_id=self.load_balancer_id,
|
||||
listener_id=db_listener.id), remainder
|
||||
elif controller == 'stats':
|
||||
return listener_statistics.ListenerStatisticsController(
|
||||
listener_id=db_listener.id), remainder
|
||||
return None
|
||||
@@ -1,42 +0,0 @@
|
||||
# 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 pecan
|
||||
from wsme import types as wtypes
|
||||
from wsmeext import pecan as wsme_pecan
|
||||
|
||||
from octavia.api.v1.controllers import base
|
||||
from octavia.api.v1.types import listener_statistics as ls_types
|
||||
from octavia.common import constants
|
||||
from octavia.common import stats
|
||||
|
||||
|
||||
class ListenerStatisticsController(base.BaseController,
|
||||
stats.StatsMixin):
|
||||
|
||||
def __init__(self, listener_id):
|
||||
super(ListenerStatisticsController, self).__init__()
|
||||
self.listener_id = listener_id
|
||||
|
||||
@wsme_pecan.wsexpose({wtypes.text: ls_types.ListenerStatisticsResponse})
|
||||
def get_all(self):
|
||||
"""Gets a single listener's statistics details."""
|
||||
# NOTE(sbalukoff): since a listener can only have one set of
|
||||
# listener statistics we are using the get_all method to only get
|
||||
# the single set of stats
|
||||
context = pecan.request.context.get('octavia_context')
|
||||
data_stats = self.get_listener_stats(
|
||||
context.session, self.listener_id)
|
||||
return {constants.LISTENER: self._convert_db_to_type(
|
||||
data_stats, ls_types.ListenerStatisticsResponse)}
|
||||
@@ -1,306 +0,0 @@
|
||||
# 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.
|
||||
|
||||
from oslo_config import cfg
|
||||
from oslo_db import exception as odb_exceptions
|
||||
from oslo_log import log as logging
|
||||
from oslo_utils import excutils
|
||||
import pecan
|
||||
from wsme import types as wtypes
|
||||
from wsmeext import pecan as wsme_pecan
|
||||
|
||||
from octavia.api.v1.controllers import base
|
||||
from octavia.api.v1.controllers import listener
|
||||
from octavia.api.v1.controllers import load_balancer_statistics as lb_stats
|
||||
from octavia.api.v1.controllers import pool
|
||||
from octavia.api.v1.types import load_balancer as lb_types
|
||||
from octavia.common import constants
|
||||
from octavia.common import data_models
|
||||
from octavia.common import exceptions
|
||||
from octavia.common import utils
|
||||
import octavia.common.validate as validate
|
||||
from octavia.db import api as db_api
|
||||
from octavia.db import prepare as db_prepare
|
||||
from octavia.i18n import _
|
||||
|
||||
CONF = cfg.CONF
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class LoadBalancersController(base.BaseController):
|
||||
|
||||
def __init__(self):
|
||||
super(LoadBalancersController, self).__init__()
|
||||
self.handler = self.handler.load_balancer
|
||||
|
||||
@wsme_pecan.wsexpose(lb_types.LoadBalancerResponse, wtypes.text)
|
||||
def get_one(self, id):
|
||||
"""Gets a single load balancer's details."""
|
||||
context = pecan.request.context.get('octavia_context')
|
||||
load_balancer = self._get_db_lb(context.session, id)
|
||||
return self._convert_db_to_type(load_balancer,
|
||||
lb_types.LoadBalancerResponse)
|
||||
|
||||
@wsme_pecan.wsexpose([lb_types.LoadBalancerResponse], wtypes.text,
|
||||
wtypes.text, ignore_extra_args=True)
|
||||
def get_all(self, tenant_id=None, project_id=None):
|
||||
"""Lists all load balancers."""
|
||||
# NOTE(blogan): tenant_id and project_id are optional query parameters
|
||||
# tenant_id and project_id are the same thing. tenant_id will be kept
|
||||
# around for a short amount of time.
|
||||
|
||||
pcontext = pecan.request.context
|
||||
context = pcontext.get('octavia_context')
|
||||
project_id = context.project_id or project_id or tenant_id
|
||||
|
||||
load_balancers, _ = self.repositories.load_balancer.get_all(
|
||||
context.session,
|
||||
pagination_helper=pcontext.get(constants.PAGINATION_HELPER),
|
||||
project_id=project_id)
|
||||
|
||||
return self._convert_db_to_type(load_balancers,
|
||||
[lb_types.LoadBalancerResponse])
|
||||
|
||||
def _test_lb_status(self, session, id, lb_status=constants.PENDING_UPDATE):
|
||||
"""Verify load balancer is in a mutable state."""
|
||||
lb_repo = self.repositories.load_balancer
|
||||
if not lb_repo.test_and_set_provisioning_status(
|
||||
session, id, lb_status):
|
||||
LOG.info("Load Balancer %s is immutable.", id)
|
||||
db_lb = lb_repo.get(session, id=id)
|
||||
raise exceptions.ImmutableObject(resource=db_lb._name(),
|
||||
id=id)
|
||||
|
||||
def _create_load_balancer_graph_db(self, session,
|
||||
lock_session, load_balancer):
|
||||
prepped_lb = db_prepare.create_load_balancer_tree(
|
||||
load_balancer.to_dict(render_unsets=True))
|
||||
try:
|
||||
db_lb = self.repositories.create_load_balancer_tree(
|
||||
session, lock_session, prepped_lb)
|
||||
except Exception:
|
||||
raise
|
||||
return db_lb
|
||||
|
||||
def _load_balancer_graph_to_handler(self, context, db_lb):
|
||||
try:
|
||||
LOG.info("Sending full load balancer configuration %s to "
|
||||
"the handler", db_lb.id)
|
||||
self.handler.create(db_lb)
|
||||
except Exception:
|
||||
with excutils.save_and_reraise_exception(reraise=False):
|
||||
self.repositories.load_balancer.update(
|
||||
context.session, db_lb.id,
|
||||
provisioning_status=constants.ERROR)
|
||||
return self._convert_db_to_type(db_lb, lb_types.LoadBalancerResponse,
|
||||
children=True)
|
||||
|
||||
@staticmethod
|
||||
def _validate_network_and_fill_or_validate_subnet(load_balancer):
|
||||
network = validate.network_exists_optionally_contains_subnet(
|
||||
network_id=load_balancer.vip.network_id,
|
||||
subnet_id=load_balancer.vip.subnet_id)
|
||||
# If subnet is not provided, pick the first subnet, preferring ipv4
|
||||
if not load_balancer.vip.subnet_id:
|
||||
network_driver = utils.get_network_driver()
|
||||
for subnet_id in network.subnets:
|
||||
# Use the first subnet, in case there are no ipv4 subnets
|
||||
if not load_balancer.vip.subnet_id:
|
||||
load_balancer.vip.subnet_id = subnet_id
|
||||
subnet = network_driver.get_subnet(subnet_id)
|
||||
if subnet.ip_version == 4:
|
||||
load_balancer.vip.subnet_id = subnet_id
|
||||
break
|
||||
if not load_balancer.vip.subnet_id:
|
||||
raise exceptions.ValidationException(detail=_(
|
||||
"Supplied network does not contain a subnet."
|
||||
))
|
||||
|
||||
@wsme_pecan.wsexpose(lb_types.LoadBalancerResponse,
|
||||
body=lb_types.LoadBalancerPOST, status_code=202)
|
||||
def post(self, load_balancer):
|
||||
"""Creates a load balancer."""
|
||||
context = pecan.request.context.get('octavia_context')
|
||||
|
||||
project_id = context.project_id
|
||||
if context.is_admin or (CONF.api_settings.auth_strategy ==
|
||||
constants.NOAUTH):
|
||||
if load_balancer.project_id:
|
||||
project_id = load_balancer.project_id
|
||||
|
||||
if not project_id:
|
||||
raise exceptions.ValidationException(detail=_(
|
||||
"Missing project ID in request where one is required."))
|
||||
|
||||
load_balancer.project_id = project_id
|
||||
|
||||
if not (load_balancer.vip.port_id or
|
||||
load_balancer.vip.network_id or
|
||||
load_balancer.vip.subnet_id):
|
||||
raise exceptions.ValidationException(detail=_(
|
||||
"VIP must contain one of: port_id, network_id, subnet_id."))
|
||||
|
||||
# Validate the port id
|
||||
if load_balancer.vip.port_id:
|
||||
port = validate.port_exists(port_id=load_balancer.vip.port_id)
|
||||
load_balancer.vip.network_id = port.network_id
|
||||
# If no port id, validate the network id (and subnet if provided)
|
||||
elif load_balancer.vip.network_id:
|
||||
self._validate_network_and_fill_or_validate_subnet(load_balancer)
|
||||
# Validate just the subnet id
|
||||
elif load_balancer.vip.subnet_id:
|
||||
subnet = validate.subnet_exists(
|
||||
subnet_id=load_balancer.vip.subnet_id)
|
||||
load_balancer.vip.network_id = subnet.network_id
|
||||
|
||||
lock_session = db_api.get_session(autocommit=False)
|
||||
if load_balancer.listeners:
|
||||
try:
|
||||
db_lb = self._create_load_balancer_graph_db(context.session,
|
||||
lock_session,
|
||||
load_balancer)
|
||||
lock_session.commit()
|
||||
except Exception:
|
||||
with excutils.save_and_reraise_exception():
|
||||
lock_session.rollback()
|
||||
|
||||
return self._load_balancer_graph_to_handler(context, db_lb)
|
||||
else:
|
||||
if self.repositories.check_quota_met(
|
||||
context.session,
|
||||
lock_session,
|
||||
data_models.LoadBalancer,
|
||||
load_balancer.project_id):
|
||||
lock_session.rollback()
|
||||
raise exceptions.QuotaException(
|
||||
resource=data_models.LoadBalancer._name())
|
||||
|
||||
try:
|
||||
lb_dict = db_prepare.create_load_balancer(load_balancer.to_dict(
|
||||
render_unsets=True
|
||||
))
|
||||
vip_dict = lb_dict.pop('vip', {})
|
||||
|
||||
db_lb = self.repositories.create_load_balancer_and_vip(
|
||||
lock_session, lb_dict, vip_dict)
|
||||
lock_session.commit()
|
||||
except odb_exceptions.DBDuplicateEntry:
|
||||
lock_session.rollback()
|
||||
raise exceptions.IDAlreadyExists()
|
||||
except Exception:
|
||||
with excutils.save_and_reraise_exception():
|
||||
lock_session.rollback()
|
||||
|
||||
# Handler will be responsible for sending to controller
|
||||
try:
|
||||
LOG.info("Sending created Load Balancer %s to the handler",
|
||||
db_lb.id)
|
||||
self.handler.create(db_lb)
|
||||
except Exception:
|
||||
with excutils.save_and_reraise_exception(reraise=False):
|
||||
self.repositories.load_balancer.update(
|
||||
context.session, db_lb.id,
|
||||
provisioning_status=constants.ERROR)
|
||||
return self._convert_db_to_type(db_lb, lb_types.LoadBalancerResponse)
|
||||
|
||||
@wsme_pecan.wsexpose(lb_types.LoadBalancerResponse,
|
||||
wtypes.text, status_code=202,
|
||||
body=lb_types.LoadBalancerPUT)
|
||||
def put(self, id, load_balancer):
|
||||
"""Updates a load balancer."""
|
||||
context = pecan.request.context.get('octavia_context')
|
||||
db_lb = self._get_db_lb(context.session, id)
|
||||
self._test_lb_status(context.session, id)
|
||||
|
||||
try:
|
||||
LOG.info("Sending updated Load Balancer %s to the handler",
|
||||
id)
|
||||
self.handler.update(db_lb, load_balancer)
|
||||
except Exception:
|
||||
with excutils.save_and_reraise_exception(reraise=False):
|
||||
self.repositories.load_balancer.update(
|
||||
context.session, id, provisioning_status=constants.ERROR)
|
||||
db_lb = self._get_db_lb(context.session, id)
|
||||
return self._convert_db_to_type(db_lb, lb_types.LoadBalancerResponse)
|
||||
|
||||
def _delete(self, id, cascade=False):
|
||||
"""Deletes a load balancer."""
|
||||
context = pecan.request.context.get('octavia_context')
|
||||
db_lb = self._get_db_lb(context.session, id)
|
||||
if (db_lb.listeners or db_lb.pools) and not cascade:
|
||||
msg = _("Cannot delete Load Balancer %s - it has children") % id
|
||||
LOG.warning(msg)
|
||||
raise exceptions.ValidationException(detail=msg)
|
||||
self._test_lb_status(context.session, id,
|
||||
lb_status=constants.PENDING_DELETE)
|
||||
|
||||
try:
|
||||
LOG.info("Sending deleted Load Balancer %s to the handler",
|
||||
db_lb.id)
|
||||
self.handler.delete(db_lb, cascade)
|
||||
except Exception:
|
||||
with excutils.save_and_reraise_exception(reraise=False):
|
||||
self.repositories.load_balancer.update(
|
||||
context.session, db_lb.id,
|
||||
provisioning_status=constants.ERROR)
|
||||
return self._convert_db_to_type(db_lb, lb_types.LoadBalancerResponse)
|
||||
|
||||
@wsme_pecan.wsexpose(None, wtypes.text, status_code=202)
|
||||
def delete(self, id):
|
||||
"""Deletes a load balancer."""
|
||||
return self._delete(id, cascade=False)
|
||||
|
||||
@pecan.expose()
|
||||
def _lookup(self, lb_id, *remainder):
|
||||
"""Overridden pecan _lookup method for custom routing.
|
||||
|
||||
Verifies that the load balancer passed in the url exists, and if so
|
||||
decides which controller, if any, should control be passed.
|
||||
"""
|
||||
context = pecan.request.context.get('octavia_context')
|
||||
|
||||
possible_remainder = ('listeners', 'pools', 'delete_cascade', 'stats')
|
||||
if lb_id and remainder and (remainder[0] in possible_remainder):
|
||||
controller = remainder[0]
|
||||
remainder = remainder[1:]
|
||||
db_lb = self.repositories.load_balancer.get(context.session,
|
||||
id=lb_id)
|
||||
if not db_lb:
|
||||
LOG.info("Load Balancer %s was not found.", lb_id)
|
||||
raise exceptions.NotFound(
|
||||
resource=data_models.LoadBalancer._name(), id=lb_id)
|
||||
if controller == 'listeners':
|
||||
return listener.ListenersController(
|
||||
load_balancer_id=db_lb.id), remainder
|
||||
elif controller == 'pools':
|
||||
return pool.PoolsController(
|
||||
load_balancer_id=db_lb.id), remainder
|
||||
elif controller == 'delete_cascade':
|
||||
return LBCascadeDeleteController(db_lb.id), ''
|
||||
elif controller == 'stats':
|
||||
return lb_stats.LoadBalancerStatisticsController(
|
||||
loadbalancer_id=db_lb.id), remainder
|
||||
return None
|
||||
|
||||
|
||||
class LBCascadeDeleteController(LoadBalancersController):
|
||||
def __init__(self, lb_id):
|
||||
super(LBCascadeDeleteController, self).__init__()
|
||||
self.lb_id = lb_id
|
||||
|
||||
@wsme_pecan.wsexpose(None, status_code=202)
|
||||
def delete(self):
|
||||
"""Deletes a load balancer."""
|
||||
return self._delete(self.lb_id, cascade=True)
|
||||
@@ -1,40 +0,0 @@
|
||||
# Copyright 2016 IBM
|
||||
#
|
||||
# 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 pecan
|
||||
from wsme import types as wtypes
|
||||
from wsmeext import pecan as wsme_pecan
|
||||
|
||||
from octavia.api.v1.controllers import base
|
||||
from octavia.api.v1.types import load_balancer_statistics as lb_types
|
||||
from octavia.common import constants
|
||||
from octavia.common import stats
|
||||
|
||||
|
||||
class LoadBalancerStatisticsController(base.BaseController,
|
||||
stats.StatsMixin):
|
||||
|
||||
def __init__(self, loadbalancer_id):
|
||||
super(LoadBalancerStatisticsController, self).__init__()
|
||||
self.loadbalancer_id = loadbalancer_id
|
||||
|
||||
@wsme_pecan.wsexpose(
|
||||
{wtypes.text: lb_types.LoadBalancerStatisticsResponse})
|
||||
def get(self):
|
||||
"""Gets a single loadbalancer's statistics details."""
|
||||
context = pecan.request.context.get('octavia_context')
|
||||
data_stats = self.get_loadbalancer_stats(
|
||||
context.session, self.loadbalancer_id)
|
||||
return {constants.LOADBALANCER: self._convert_db_to_type(
|
||||
data_stats, lb_types.LoadBalancerStatisticsResponse)}
|
||||
@@ -1,196 +0,0 @@
|
||||
# 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 oslo_db.exception as oslo_exc
|
||||
from oslo_log import log as logging
|
||||
from oslo_utils import excutils
|
||||
import pecan
|
||||
from wsme import types as wtypes
|
||||
from wsmeext import pecan as wsme_pecan
|
||||
|
||||
from octavia.api.v1.controllers import base
|
||||
from octavia.api.v1.types import member as member_types
|
||||
from octavia.common import constants
|
||||
from octavia.common import data_models
|
||||
from octavia.common import exceptions
|
||||
import octavia.common.validate as validate
|
||||
from octavia.db import api as db_api
|
||||
from octavia.db import prepare as db_prepare
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class MembersController(base.BaseController):
|
||||
|
||||
def __init__(self, load_balancer_id, pool_id, listener_id=None):
|
||||
super(MembersController, self).__init__()
|
||||
self.load_balancer_id = load_balancer_id
|
||||
self.listener_id = listener_id
|
||||
self.pool_id = pool_id
|
||||
self.handler = self.handler.member
|
||||
|
||||
@wsme_pecan.wsexpose(member_types.MemberResponse, wtypes.text)
|
||||
def get(self, id):
|
||||
"""Gets a single pool member's details."""
|
||||
context = pecan.request.context.get('octavia_context')
|
||||
db_member = self._get_db_member(context.session, id)
|
||||
return self._convert_db_to_type(db_member, member_types.MemberResponse)
|
||||
|
||||
@wsme_pecan.wsexpose([member_types.MemberResponse], ignore_extra_args=True)
|
||||
def get_all(self):
|
||||
"""Lists all pool members of a pool."""
|
||||
pcontext = pecan.request.context
|
||||
context = pcontext.get('octavia_context')
|
||||
|
||||
db_members, _ = self.repositories.member.get_all(
|
||||
context.session,
|
||||
pagination_helper=pcontext.get(constants.PAGINATION_HELPER),
|
||||
pool_id=self.pool_id)
|
||||
return self._convert_db_to_type(db_members,
|
||||
[member_types.MemberResponse])
|
||||
|
||||
def _get_affected_listener_ids(self, session, member=None):
|
||||
"""Gets a list of all listeners this request potentially affects."""
|
||||
listener_ids = []
|
||||
if member:
|
||||
listener_ids = [l.id for l in member.pool.listeners]
|
||||
else:
|
||||
pool = self._get_db_pool(session, self.pool_id)
|
||||
for listener in pool.listeners:
|
||||
if listener.id not in listener_ids:
|
||||
listener_ids.append(listener.id)
|
||||
if self.listener_id and self.listener_id not in listener_ids:
|
||||
listener_ids.append(self.listener_id)
|
||||
return listener_ids
|
||||
|
||||
def _test_lb_and_listener_statuses(self, session, member=None):
|
||||
"""Verify load balancer is in a mutable state."""
|
||||
# We need to verify that any listeners referencing this member's
|
||||
# pool are also mutable
|
||||
if not self.repositories.test_and_set_lb_and_listeners_prov_status(
|
||||
session, self.load_balancer_id,
|
||||
constants.PENDING_UPDATE, constants.PENDING_UPDATE,
|
||||
listener_ids=self._get_affected_listener_ids(session, member)):
|
||||
LOG.info("Member cannot be created or modified because the "
|
||||
"Load Balancer is in an immutable state")
|
||||
lb_repo = self.repositories.load_balancer
|
||||
db_lb = lb_repo.get(session, id=self.load_balancer_id)
|
||||
raise exceptions.ImmutableObject(resource=db_lb._name(),
|
||||
id=self.load_balancer_id)
|
||||
|
||||
@wsme_pecan.wsexpose(member_types.MemberResponse,
|
||||
body=member_types.MemberPOST, status_code=202)
|
||||
def post(self, member):
|
||||
"""Creates a pool member on a pool."""
|
||||
context = pecan.request.context.get('octavia_context')
|
||||
|
||||
member.project_id = self._get_lb_project_id(context.session,
|
||||
self.load_balancer_id)
|
||||
|
||||
# Validate member subnet
|
||||
if member.subnet_id:
|
||||
validate.subnet_exists(member.subnet_id)
|
||||
|
||||
lock_session = db_api.get_session(autocommit=False)
|
||||
if self.repositories.check_quota_met(
|
||||
context.session,
|
||||
lock_session,
|
||||
data_models.Member,
|
||||
member.project_id):
|
||||
lock_session.rollback()
|
||||
raise exceptions.QuotaException(
|
||||
resource=data_models.Member._name()
|
||||
)
|
||||
|
||||
try:
|
||||
member_dict = db_prepare.create_member(member.to_dict(
|
||||
render_unsets=True), self.pool_id)
|
||||
self._test_lb_and_listener_statuses(lock_session)
|
||||
|
||||
db_member = self.repositories.member.create(lock_session,
|
||||
**member_dict)
|
||||
db_new_member = self._get_db_member(lock_session, db_member.id)
|
||||
lock_session.commit()
|
||||
except oslo_exc.DBDuplicateEntry as de:
|
||||
lock_session.rollback()
|
||||
if ['id'] == de.columns:
|
||||
raise exceptions.IDAlreadyExists()
|
||||
|
||||
raise exceptions.DuplicateMemberEntry(
|
||||
ip_address=member_dict.get('ip_address'),
|
||||
port=member_dict.get('protocol_port'))
|
||||
except Exception:
|
||||
with excutils.save_and_reraise_exception():
|
||||
lock_session.rollback()
|
||||
|
||||
try:
|
||||
LOG.info("Sending Creation of Member %s to handler",
|
||||
db_member.id)
|
||||
self.handler.create(db_member)
|
||||
except Exception:
|
||||
for listener_id in self._get_affected_listener_ids(
|
||||
context.session):
|
||||
with excutils.save_and_reraise_exception(reraise=False):
|
||||
self.repositories.listener.update(
|
||||
context.session, listener_id,
|
||||
operating_status=constants.ERROR)
|
||||
return self._convert_db_to_type(db_new_member,
|
||||
member_types.MemberResponse)
|
||||
|
||||
@wsme_pecan.wsexpose(member_types.MemberResponse,
|
||||
wtypes.text, body=member_types.MemberPUT,
|
||||
status_code=202)
|
||||
def put(self, id, member):
|
||||
"""Updates a pool member."""
|
||||
context = pecan.request.context.get('octavia_context')
|
||||
db_member = self._get_db_member(context.session, id)
|
||||
self._test_lb_and_listener_statuses(context.session, member=db_member)
|
||||
|
||||
self.repositories.member.update(
|
||||
context.session, id, provisioning_status=constants.PENDING_UPDATE)
|
||||
|
||||
try:
|
||||
LOG.info("Sending Update of Member %s to handler", id)
|
||||
self.handler.update(db_member, member)
|
||||
except Exception:
|
||||
with excutils.save_and_reraise_exception(reraise=False):
|
||||
for listener_id in self._get_affected_listener_ids(
|
||||
context.session, db_member):
|
||||
self.repositories.listener.update(
|
||||
context.session, listener_id,
|
||||
operating_status=constants.ERROR)
|
||||
db_member = self._get_db_member(context.session, id)
|
||||
return self._convert_db_to_type(db_member, member_types.MemberResponse)
|
||||
|
||||
@wsme_pecan.wsexpose(None, wtypes.text, status_code=202)
|
||||
def delete(self, id):
|
||||
"""Deletes a pool member."""
|
||||
context = pecan.request.context.get('octavia_context')
|
||||
db_member = self._get_db_member(context.session, id)
|
||||
self._test_lb_and_listener_statuses(context.session, member=db_member)
|
||||
|
||||
try:
|
||||
LOG.info("Sending Deletion of Member %s to handler",
|
||||
db_member.id)
|
||||
self.handler.delete(db_member)
|
||||
except Exception:
|
||||
with excutils.save_and_reraise_exception(reraise=False):
|
||||
for listener_id in self._get_affected_listener_ids(
|
||||
context.session, db_member):
|
||||
self.repositories.listener.update(
|
||||
context.session, listener_id,
|
||||
operating_status=constants.ERROR)
|
||||
db_member = self.repositories.member.get(context.session, id=id)
|
||||
return self._convert_db_to_type(db_member, member_types.MemberResponse)
|
||||
@@ -1,258 +0,0 @@
|
||||
# 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.
|
||||
|
||||
from oslo_db import exception as odb_exceptions
|
||||
from oslo_log import log as logging
|
||||
from oslo_utils import excutils
|
||||
import pecan
|
||||
from wsme import types as wtypes
|
||||
from wsmeext import pecan as wsme_pecan
|
||||
|
||||
from octavia.api.v1.controllers import base
|
||||
from octavia.api.v1.controllers import health_monitor
|
||||
from octavia.api.v1.controllers import member
|
||||
from octavia.api.v1.types import pool as pool_types
|
||||
from octavia.common import constants
|
||||
from octavia.common import data_models
|
||||
from octavia.common import exceptions
|
||||
from octavia.db import api as db_api
|
||||
from octavia.db import prepare as db_prepare
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class PoolsController(base.BaseController):
|
||||
|
||||
def __init__(self, load_balancer_id, listener_id=None):
|
||||
super(PoolsController, self).__init__()
|
||||
self.load_balancer_id = load_balancer_id
|
||||
self.listener_id = listener_id
|
||||
self.handler = self.handler.pool
|
||||
|
||||
@wsme_pecan.wsexpose(pool_types.PoolResponse, wtypes.text)
|
||||
def get(self, id):
|
||||
"""Gets a pool's details."""
|
||||
context = pecan.request.context.get('octavia_context')
|
||||
db_pool = self._get_db_pool(context.session, id)
|
||||
return self._convert_db_to_type(db_pool, pool_types.PoolResponse)
|
||||
|
||||
@wsme_pecan.wsexpose([pool_types.PoolResponse], wtypes.text,
|
||||
ignore_extra_args=True)
|
||||
def get_all(self, listener_id=None):
|
||||
"""Lists all pools on a listener or loadbalancer."""
|
||||
pcontext = pecan.request.context
|
||||
context = pcontext.get('octavia_context')
|
||||
|
||||
if listener_id is not None:
|
||||
self.listener_id = listener_id
|
||||
if self.listener_id:
|
||||
pools = self._get_db_listener(context.session,
|
||||
self.listener_id).pools
|
||||
else:
|
||||
pools, _ = self.repositories.pool.get_all(
|
||||
context.session,
|
||||
pagination_helper=pcontext.get(constants.PAGINATION_HELPER),
|
||||
load_balancer_id=self.load_balancer_id)
|
||||
return self._convert_db_to_type(pools, [pool_types.PoolResponse])
|
||||
|
||||
def _get_affected_listener_ids(self, session, pool=None):
|
||||
"""Gets a list of all listeners this request potentially affects."""
|
||||
listener_ids = []
|
||||
if pool:
|
||||
listener_ids = [l.id for l in pool.listeners]
|
||||
if self.listener_id and self.listener_id not in listener_ids:
|
||||
listener_ids.append(self.listener_id)
|
||||
return listener_ids
|
||||
|
||||
def _test_lb_and_listener_statuses(self, session, pool=None):
|
||||
"""Verify load balancer is in a mutable state."""
|
||||
# We need to verify that any listeners referencing this pool are also
|
||||
# mutable
|
||||
if not self.repositories.test_and_set_lb_and_listeners_prov_status(
|
||||
session, self.load_balancer_id,
|
||||
constants.PENDING_UPDATE, constants.PENDING_UPDATE,
|
||||
listener_ids=self._get_affected_listener_ids(session, pool)):
|
||||
LOG.info("Pool cannot be created or modified because the Load "
|
||||
"Balancer is in an immutable state")
|
||||
lb_repo = self.repositories.load_balancer
|
||||
db_lb = lb_repo.get(session, id=self.load_balancer_id)
|
||||
raise exceptions.ImmutableObject(resource=db_lb._name(),
|
||||
id=self.load_balancer_id)
|
||||
|
||||
def _validate_create_pool(self, lock_session, pool_dict):
|
||||
"""Validate creating pool on load balancer.
|
||||
|
||||
Update database for load balancer and (optional) listener based on
|
||||
provisioning status.
|
||||
"""
|
||||
try:
|
||||
return self.repositories.create_pool_on_load_balancer(
|
||||
lock_session, pool_dict, listener_id=self.listener_id)
|
||||
except odb_exceptions.DBDuplicateEntry as de:
|
||||
if ['id'] == de.columns:
|
||||
raise exceptions.IDAlreadyExists()
|
||||
except odb_exceptions.DBError:
|
||||
# TODO(blogan): will have to do separate validation protocol
|
||||
# before creation or update since the exception messages
|
||||
# do not give any information as to what constraint failed
|
||||
raise exceptions.InvalidOption(value='', option='')
|
||||
|
||||
def _send_pool_to_handler(self, session, db_pool):
|
||||
try:
|
||||
LOG.info("Sending Creation of Pool %s to handler", db_pool.id)
|
||||
self.handler.create(db_pool)
|
||||
except Exception:
|
||||
for listener_id in self._get_affected_listener_ids(session):
|
||||
with excutils.save_and_reraise_exception(reraise=False):
|
||||
self.repositories.listener.update(
|
||||
session, listener_id, operating_status=constants.ERROR)
|
||||
db_pool = self._get_db_pool(session, db_pool.id)
|
||||
return self._convert_db_to_type(db_pool, pool_types.PoolResponse)
|
||||
|
||||
@wsme_pecan.wsexpose(pool_types.PoolResponse, body=pool_types.PoolPOST,
|
||||
status_code=202)
|
||||
def post(self, pool):
|
||||
"""Creates a pool on a load balancer or listener.
|
||||
|
||||
Note that this can optionally take a listener_id with which the pool
|
||||
should be associated as the listener's default_pool. If specified,
|
||||
the pool creation will fail if the listener specified already has
|
||||
a default_pool.
|
||||
"""
|
||||
# For some API requests the listener_id will be passed in the
|
||||
# pool_dict:
|
||||
context = pecan.request.context.get('octavia_context')
|
||||
|
||||
pool.project_id = self._get_lb_project_id(context.session,
|
||||
self.load_balancer_id)
|
||||
|
||||
lock_session = db_api.get_session(autocommit=False)
|
||||
if self.repositories.check_quota_met(
|
||||
context.session,
|
||||
lock_session,
|
||||
data_models.Pool,
|
||||
pool.project_id):
|
||||
lock_session.rollback()
|
||||
raise exceptions.QuotaException(
|
||||
resource=data_models.Pool._name())
|
||||
|
||||
try:
|
||||
pool_dict = db_prepare.create_pool(
|
||||
pool.to_dict(render_unsets=True))
|
||||
if 'listener_id' in pool_dict:
|
||||
if pool_dict['listener_id'] is not None:
|
||||
self.listener_id = pool_dict.pop('listener_id')
|
||||
else:
|
||||
del pool_dict['listener_id']
|
||||
listener_repo = self.repositories.listener
|
||||
if self.listener_id and listener_repo.has_default_pool(
|
||||
lock_session, self.listener_id):
|
||||
raise exceptions.DuplicatePoolEntry()
|
||||
self._test_lb_and_listener_statuses(lock_session)
|
||||
|
||||
pool_dict['operating_status'] = constants.OFFLINE
|
||||
pool_dict['load_balancer_id'] = self.load_balancer_id
|
||||
|
||||
db_pool = self._validate_create_pool(lock_session, pool_dict)
|
||||
lock_session.commit()
|
||||
except Exception:
|
||||
with excutils.save_and_reraise_exception():
|
||||
lock_session.rollback()
|
||||
|
||||
return self._send_pool_to_handler(context.session, db_pool)
|
||||
|
||||
@wsme_pecan.wsexpose(pool_types.PoolResponse, wtypes.text,
|
||||
body=pool_types.PoolPUT, status_code=202)
|
||||
def put(self, id, pool):
|
||||
"""Updates a pool on a load balancer."""
|
||||
context = pecan.request.context.get('octavia_context')
|
||||
db_pool = self._get_db_pool(context.session, id)
|
||||
self._test_lb_and_listener_statuses(context.session, pool=db_pool)
|
||||
|
||||
self.repositories.pool.update(
|
||||
context.session, id, provisioning_status=constants.PENDING_UPDATE)
|
||||
|
||||
try:
|
||||
LOG.info("Sending Update of Pool %s to handler", id)
|
||||
self.handler.update(db_pool, pool)
|
||||
except Exception:
|
||||
with excutils.save_and_reraise_exception(reraise=False):
|
||||
for listener in db_pool.listeners:
|
||||
self.repositories.listener.update(
|
||||
context.session, listener.id,
|
||||
operating_status=constants.ERROR)
|
||||
self.repositories.pool.update(
|
||||
context.session, db_pool.id,
|
||||
operating_status=constants.ERROR)
|
||||
db_pool = self._get_db_pool(context.session, id)
|
||||
return self._convert_db_to_type(db_pool, pool_types.PoolResponse)
|
||||
|
||||
@wsme_pecan.wsexpose(None, wtypes.text, status_code=202)
|
||||
def delete(self, id):
|
||||
"""Deletes a pool from a load balancer."""
|
||||
context = pecan.request.context.get('octavia_context')
|
||||
db_pool = self._get_db_pool(context.session, id)
|
||||
if db_pool.l7policies:
|
||||
raise exceptions.PoolInUseByL7Policy(
|
||||
id=db_pool.id, l7policy_id=db_pool.l7policies[0].id)
|
||||
self._test_lb_and_listener_statuses(context.session, pool=db_pool)
|
||||
|
||||
try:
|
||||
LOG.info("Sending Deletion of Pool %s to handler", db_pool.id)
|
||||
self.handler.delete(db_pool)
|
||||
except Exception:
|
||||
with excutils.save_and_reraise_exception(reraise=False):
|
||||
for listener in db_pool.listeners:
|
||||
self.repositories.listener.update(
|
||||
context.session, listener.id,
|
||||
operating_status=constants.ERROR)
|
||||
self.repositories.pool.update(
|
||||
context.session, db_pool.id,
|
||||
operating_status=constants.ERROR)
|
||||
db_pool = self.repositories.pool.get(context.session, id=db_pool.id)
|
||||
return self._convert_db_to_type(db_pool, pool_types.PoolResponse)
|
||||
|
||||
@pecan.expose()
|
||||
def _lookup(self, pool_id, *remainder):
|
||||
"""Overridden pecan _lookup method for custom routing.
|
||||
|
||||
Verifies that the pool passed in the url exists, and if so decides
|
||||
which controller, if any, should control be passed.
|
||||
"""
|
||||
context = pecan.request.context.get('octavia_context')
|
||||
is_children = (
|
||||
pool_id and remainder and (
|
||||
remainder[0] == 'members' or remainder[0] == 'healthmonitor'
|
||||
)
|
||||
)
|
||||
if is_children:
|
||||
controller = remainder[0]
|
||||
remainder = remainder[1:]
|
||||
db_pool = self.repositories.pool.get(context.session, id=pool_id)
|
||||
if not db_pool:
|
||||
LOG.info("Pool %s not found.", pool_id)
|
||||
raise exceptions.NotFound(resource=data_models.Pool._name(),
|
||||
id=pool_id)
|
||||
if controller == 'members':
|
||||
return member.MembersController(
|
||||
load_balancer_id=self.load_balancer_id,
|
||||
pool_id=db_pool.id,
|
||||
listener_id=self.listener_id), remainder
|
||||
elif controller == 'healthmonitor':
|
||||
return health_monitor.HealthMonitorController(
|
||||
load_balancer_id=self.load_balancer_id,
|
||||
pool_id=db_pool.id,
|
||||
listener_id=self.listener_id), remainder
|
||||
return None
|
||||
@@ -1,101 +0,0 @@
|
||||
# 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 oslo_config import cfg
|
||||
import pecan
|
||||
from wsme import types as wtypes
|
||||
from wsmeext import pecan as wsme_pecan
|
||||
|
||||
from octavia.api.v1.controllers import base
|
||||
from octavia.api.v1.types import quotas as quota_types
|
||||
from octavia.common import constants
|
||||
from octavia.common import exceptions
|
||||
|
||||
CONF = cfg.CONF
|
||||
CONF.import_group('quotas', 'octavia.common.config')
|
||||
|
||||
|
||||
class QuotasController(base.BaseController):
|
||||
|
||||
def __init__(self):
|
||||
super(QuotasController, self).__init__()
|
||||
|
||||
@wsme_pecan.wsexpose(quota_types.QuotaResponse, wtypes.text)
|
||||
def get(self, project_id):
|
||||
"""Get a single project's quota details."""
|
||||
context = pecan.request.context.get('octavia_context')
|
||||
db_quotas = self._get_db_quotas(context.session, project_id)
|
||||
return self._convert_db_to_type(db_quotas, quota_types.QuotaResponse)
|
||||
|
||||
@wsme_pecan.wsexpose(quota_types.QuotaAllResponse)
|
||||
def get_all(self):
|
||||
"""List all non-default quotas."""
|
||||
context = pecan.request.context.get('octavia_context')
|
||||
db_quotas, _ = self.repositories.quotas.get_all(context.session)
|
||||
quotas = quota_types.QuotaAllResponse.from_data_model(db_quotas)
|
||||
return quotas
|
||||
|
||||
@wsme_pecan.wsexpose(quota_types.QuotaResponse, wtypes.text,
|
||||
body=quota_types.QuotaPUT, status_code=202)
|
||||
def put(self, project_id, quotas):
|
||||
"""Update any or all quotas for a project."""
|
||||
context = pecan.request.context.get('octavia_context')
|
||||
|
||||
new_project_id = context.project_id
|
||||
if context.is_admin or (CONF.api_settings.auth_strategy ==
|
||||
constants.NOAUTH):
|
||||
if project_id:
|
||||
new_project_id = project_id
|
||||
|
||||
if not new_project_id:
|
||||
raise exceptions.MissingAPIProjectID()
|
||||
|
||||
project_id = new_project_id
|
||||
|
||||
quotas_dict = quotas.to_dict()
|
||||
self.repositories.quotas.update(context.session, project_id,
|
||||
**quotas_dict)
|
||||
db_quotas = self._get_db_quotas(context.session, project_id)
|
||||
return self._convert_db_to_type(db_quotas, quota_types.QuotaResponse)
|
||||
|
||||
@wsme_pecan.wsexpose(None, wtypes.text, status_code=202)
|
||||
def delete(self, project_id):
|
||||
"""Reset a project's quotas to the default values."""
|
||||
context = pecan.request.context.get('octavia_context')
|
||||
project_id = context.project_id or project_id
|
||||
self.repositories.quotas.delete(context.session, project_id)
|
||||
db_quotas = self._get_db_quotas(context.session, project_id)
|
||||
return self._convert_db_to_type(db_quotas, quota_types.QuotaResponse)
|
||||
|
||||
@pecan.expose()
|
||||
def _lookup(self, project_id, *remainder):
|
||||
"""Overridden pecan _lookup method for routing default endpoint."""
|
||||
if project_id and remainder and remainder[0] == 'default':
|
||||
return QuotasDefaultController(project_id), ''
|
||||
return None
|
||||
|
||||
|
||||
class QuotasDefaultController(base.BaseController):
|
||||
|
||||
def __init__(self, project_id):
|
||||
super(QuotasDefaultController, self).__init__()
|
||||
self.project_id = project_id
|
||||
|
||||
@wsme_pecan.wsexpose(quota_types.QuotaResponse, wtypes.text)
|
||||
def get(self):
|
||||
"""Get a project's default quota details."""
|
||||
context = pecan.request.context.get('octavia_context')
|
||||
project_id = context.project_id
|
||||
quotas = self._get_default_quotas(project_id)
|
||||
return self._convert_db_to_type(quotas, quota_types.QuotaResponse)
|
||||
@@ -1,11 +0,0 @@
|
||||
# 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.
|
||||
@@ -1,66 +0,0 @@
|
||||
# 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.
|
||||
|
||||
from wsme import types as wtypes
|
||||
|
||||
from octavia.api.common import types as base
|
||||
from octavia.common import constants
|
||||
|
||||
|
||||
class HealthMonitorResponse(base.BaseType):
|
||||
"""Defines which attributes are to be shown on any response."""
|
||||
type = wtypes.wsattr(wtypes.text)
|
||||
delay = wtypes.wsattr(wtypes.IntegerType())
|
||||
timeout = wtypes.wsattr(wtypes.IntegerType())
|
||||
fall_threshold = wtypes.wsattr(wtypes.IntegerType())
|
||||
rise_threshold = wtypes.wsattr(wtypes.IntegerType())
|
||||
http_method = wtypes.wsattr(wtypes.text)
|
||||
url_path = wtypes.wsattr(wtypes.text)
|
||||
expected_codes = wtypes.wsattr(wtypes.text)
|
||||
enabled = wtypes.wsattr(bool)
|
||||
project_id = wtypes.wsattr(wtypes.StringType())
|
||||
|
||||
|
||||
class HealthMonitorPOST(base.BaseType):
|
||||
"""Defines mandatory and optional attributes of a POST request."""
|
||||
type = wtypes.wsattr(
|
||||
wtypes.Enum(str, *constants.SUPPORTED_HEALTH_MONITOR_TYPES),
|
||||
mandatory=True)
|
||||
delay = wtypes.wsattr(wtypes.IntegerType(), mandatory=True)
|
||||
timeout = wtypes.wsattr(wtypes.IntegerType(), mandatory=True)
|
||||
fall_threshold = wtypes.wsattr(wtypes.IntegerType(), mandatory=True)
|
||||
rise_threshold = wtypes.wsattr(wtypes.IntegerType(), mandatory=True)
|
||||
http_method = wtypes.wsattr(
|
||||
wtypes.text, default=constants.HEALTH_MONITOR_HTTP_DEFAULT_METHOD)
|
||||
url_path = wtypes.wsattr(
|
||||
wtypes.text, default=constants.HEALTH_MONITOR_DEFAULT_URL_PATH)
|
||||
expected_codes = wtypes.wsattr(
|
||||
wtypes.text, default=constants.HEALTH_MONITOR_DEFAULT_EXPECTED_CODES)
|
||||
enabled = wtypes.wsattr(bool, default=True)
|
||||
# TODO(johnsom) Remove after deprecation (R series)
|
||||
project_id = wtypes.wsattr(wtypes.StringType(max_length=36))
|
||||
|
||||
|
||||
class HealthMonitorPUT(base.BaseType):
|
||||
"""Defines attributes that are acceptable of a PUT request."""
|
||||
type = wtypes.wsattr(
|
||||
wtypes.Enum(str, *constants.SUPPORTED_HEALTH_MONITOR_TYPES))
|
||||
delay = wtypes.wsattr(wtypes.IntegerType())
|
||||
timeout = wtypes.wsattr(wtypes.IntegerType())
|
||||
fall_threshold = wtypes.wsattr(wtypes.IntegerType())
|
||||
rise_threshold = wtypes.wsattr(wtypes.IntegerType())
|
||||
http_method = wtypes.wsattr(wtypes.text)
|
||||
url_path = wtypes.wsattr(wtypes.text)
|
||||
expected_codes = wtypes.wsattr(wtypes.text)
|
||||
enabled = wtypes.wsattr(bool)
|
||||
@@ -1,87 +0,0 @@
|
||||
# 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.
|
||||
|
||||
from wsme import types as wtypes
|
||||
|
||||
from octavia.api.common import types as base
|
||||
from octavia.api.v1.types import l7rule
|
||||
from octavia.api.v1.types import pool
|
||||
from octavia.common import constants
|
||||
|
||||
|
||||
class L7PolicyResponse(base.BaseType):
|
||||
"""Defines which attributes are to be shown on any response."""
|
||||
id = wtypes.wsattr(wtypes.UuidType())
|
||||
name = wtypes.wsattr(wtypes.StringType())
|
||||
description = wtypes.wsattr(wtypes.StringType())
|
||||
enabled = wtypes.wsattr(bool)
|
||||
action = wtypes.wsattr(wtypes.StringType())
|
||||
redirect_pool_id = wtypes.wsattr(wtypes.UuidType())
|
||||
redirect_url = wtypes.wsattr(wtypes.StringType())
|
||||
redirect_prefix = wtypes.wsattr(wtypes.StringType())
|
||||
position = wtypes.wsattr(wtypes.IntegerType())
|
||||
l7rules = wtypes.wsattr([l7rule.L7RuleResponse])
|
||||
redirect_pool = wtypes.wsattr(pool.PoolResponse)
|
||||
|
||||
@classmethod
|
||||
def from_data_model(cls, data_model, children=False):
|
||||
policy = super(L7PolicyResponse, cls).from_data_model(
|
||||
data_model, children=children)
|
||||
if not children:
|
||||
del policy.l7rules
|
||||
del policy.redirect_pool
|
||||
return policy
|
||||
policy.l7rules = [
|
||||
l7rule.L7RuleResponse.from_data_model(
|
||||
l7rule_dm, children=children)
|
||||
for l7rule_dm in data_model.l7rules
|
||||
]
|
||||
if policy.redirect_pool_id:
|
||||
policy.redirect_pool = pool.PoolResponse.from_data_model(
|
||||
data_model.redirect_pool, children=children)
|
||||
else:
|
||||
del policy.redirect_pool
|
||||
del policy.redirect_pool_id
|
||||
return policy
|
||||
|
||||
|
||||
class L7PolicyPOST(base.BaseType):
|
||||
"""Defines mandatory and optional attributes of a POST request."""
|
||||
id = wtypes.wsattr(wtypes.UuidType())
|
||||
name = wtypes.wsattr(wtypes.StringType(max_length=255))
|
||||
description = wtypes.wsattr(wtypes.StringType(max_length=255))
|
||||
enabled = wtypes.wsattr(bool, default=True)
|
||||
action = wtypes.wsattr(
|
||||
wtypes.Enum(str, *constants.SUPPORTED_L7POLICY_ACTIONS),
|
||||
mandatory=True)
|
||||
redirect_pool_id = wtypes.wsattr(wtypes.UuidType())
|
||||
redirect_url = wtypes.wsattr(base.URLType())
|
||||
redirect_prefix = wtypes.wsattr(base.URLType())
|
||||
position = wtypes.wsattr(wtypes.IntegerType(),
|
||||
default=constants.MAX_POLICY_POSITION)
|
||||
redirect_pool = wtypes.wsattr(pool.PoolPOST)
|
||||
l7rules = wtypes.wsattr([l7rule.L7RulePOST], default=[])
|
||||
|
||||
|
||||
class L7PolicyPUT(base.BaseType):
|
||||
"""Defines attributes that are acceptable of a PUT request."""
|
||||
name = wtypes.wsattr(wtypes.StringType(max_length=255))
|
||||
description = wtypes.wsattr(wtypes.StringType(max_length=255))
|
||||
enabled = wtypes.wsattr(bool)
|
||||
action = wtypes.wsattr(
|
||||
wtypes.Enum(str, *constants.SUPPORTED_L7POLICY_ACTIONS))
|
||||
redirect_pool_id = wtypes.wsattr(wtypes.UuidType())
|
||||
redirect_url = wtypes.wsattr(base.URLType())
|
||||
redirect_prefix = wtypes.wsattr(base.URLType())
|
||||
position = wtypes.wsattr(wtypes.IntegerType())
|
||||
@@ -1,57 +0,0 @@
|
||||
# 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.
|
||||
|
||||
from wsme import types as wtypes
|
||||
|
||||
from octavia.api.common import types as base
|
||||
from octavia.common import constants
|
||||
|
||||
|
||||
class L7RuleResponse(base.BaseType):
|
||||
"""Defines which attributes are to be shown on any response."""
|
||||
id = wtypes.wsattr(wtypes.UuidType())
|
||||
type = wtypes.wsattr(wtypes.StringType())
|
||||
compare_type = wtypes.wsattr(wtypes.StringType())
|
||||
key = wtypes.wsattr(wtypes.StringType())
|
||||
value = wtypes.wsattr(wtypes.StringType())
|
||||
invert = wtypes.wsattr(bool)
|
||||
|
||||
|
||||
class L7RulePOST(base.BaseType):
|
||||
"""Defines mandatory and optional attributes of a POST request."""
|
||||
id = wtypes.wsattr(wtypes.UuidType())
|
||||
type = wtypes.wsattr(
|
||||
wtypes.Enum(str,
|
||||
*constants.SUPPORTED_L7RULE_TYPES),
|
||||
mandatory=True)
|
||||
compare_type = wtypes.wsattr(
|
||||
wtypes.Enum(str,
|
||||
*constants.SUPPORTED_L7RULE_COMPARE_TYPES),
|
||||
mandatory=True)
|
||||
key = wtypes.wsattr(wtypes.StringType(max_length=255))
|
||||
value = wtypes.wsattr(wtypes.StringType(max_length=255), mandatory=True)
|
||||
invert = wtypes.wsattr(bool, default=False)
|
||||
|
||||
|
||||
class L7RulePUT(base.BaseType):
|
||||
"""Defines attributes that are acceptable of a PUT request."""
|
||||
type = wtypes.wsattr(
|
||||
wtypes.Enum(str,
|
||||
*constants.SUPPORTED_L7RULE_TYPES))
|
||||
compare_type = wtypes.wsattr(
|
||||
wtypes.Enum(str,
|
||||
*constants.SUPPORTED_L7RULE_COMPARE_TYPES))
|
||||
key = wtypes.wsattr(wtypes.StringType(max_length=255))
|
||||
value = wtypes.wsattr(wtypes.StringType(max_length=255))
|
||||
invert = wtypes.wsattr(bool)
|
||||
@@ -1,112 +0,0 @@
|
||||
# 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.
|
||||
|
||||
from wsme import types as wtypes
|
||||
|
||||
from octavia.api.common import types as base
|
||||
from octavia.api.v1.types import l7policy
|
||||
from octavia.api.v1.types import pool
|
||||
from octavia.common import constants
|
||||
|
||||
|
||||
class TLSTermination(base.BaseType):
|
||||
certificate = wtypes.wsattr(wtypes.StringType())
|
||||
intermediate_certificate = wtypes.wsattr(wtypes.StringType())
|
||||
private_key = wtypes.wsattr(wtypes.StringType())
|
||||
passphrase = wtypes.wsattr(wtypes.StringType())
|
||||
|
||||
|
||||
class ListenerResponse(base.BaseType):
|
||||
"""Defines which attributes are to be shown on any response."""
|
||||
id = wtypes.wsattr(wtypes.UuidType())
|
||||
name = wtypes.wsattr(wtypes.StringType())
|
||||
description = wtypes.wsattr(wtypes.StringType())
|
||||
provisioning_status = wtypes.wsattr(wtypes.StringType())
|
||||
operating_status = wtypes.wsattr(wtypes.StringType())
|
||||
enabled = wtypes.wsattr(bool)
|
||||
protocol = wtypes.wsattr(wtypes.text)
|
||||
protocol_port = wtypes.wsattr(wtypes.IntegerType())
|
||||
connection_limit = wtypes.wsattr(wtypes.IntegerType())
|
||||
tls_certificate_id = wtypes.wsattr(wtypes.StringType(max_length=255))
|
||||
sni_containers = [wtypes.StringType(max_length=255)]
|
||||
project_id = wtypes.wsattr(wtypes.StringType())
|
||||
default_pool_id = wtypes.wsattr(wtypes.UuidType())
|
||||
default_pool = wtypes.wsattr(pool.PoolResponse)
|
||||
l7policies = wtypes.wsattr([l7policy.L7PolicyResponse])
|
||||
insert_headers = wtypes.wsattr(wtypes.DictType(str, str))
|
||||
created_at = wtypes.wsattr(wtypes.datetime.datetime)
|
||||
updated_at = wtypes.wsattr(wtypes.datetime.datetime)
|
||||
|
||||
@classmethod
|
||||
def from_data_model(cls, data_model, children=False):
|
||||
listener = super(ListenerResponse, cls).from_data_model(
|
||||
data_model, children=children)
|
||||
# NOTE(blogan): we should show sni_containers for every call to show
|
||||
# a listener
|
||||
listener.sni_containers = [sni_c.tls_container_id
|
||||
for sni_c in data_model.sni_containers]
|
||||
if not children:
|
||||
# NOTE(blogan): do not show default_pool if the request does not
|
||||
# want to see children
|
||||
del listener.default_pool
|
||||
del listener.l7policies
|
||||
return listener
|
||||
if data_model.default_pool:
|
||||
listener.default_pool = pool.PoolResponse.from_data_model(
|
||||
data_model.default_pool, children=children)
|
||||
if data_model.l7policies:
|
||||
listener.l7policies = [l7policy.L7PolicyResponse.from_data_model(
|
||||
policy, children=children) for policy in data_model.l7policies]
|
||||
if not listener.default_pool:
|
||||
del listener.default_pool
|
||||
del listener.default_pool_id
|
||||
if not listener.l7policies or len(listener.l7policies) <= 0:
|
||||
del listener.l7policies
|
||||
return listener
|
||||
|
||||
|
||||
class ListenerPOST(base.BaseType):
|
||||
"""Defines mandatory and optional attributes of a POST request."""
|
||||
id = wtypes.wsattr(wtypes.UuidType())
|
||||
name = wtypes.wsattr(wtypes.StringType(max_length=255))
|
||||
description = wtypes.wsattr(wtypes.StringType(max_length=255))
|
||||
enabled = wtypes.wsattr(bool, default=True)
|
||||
protocol = wtypes.wsattr(wtypes.Enum(str, *constants.SUPPORTED_PROTOCOLS),
|
||||
mandatory=True)
|
||||
protocol_port = wtypes.wsattr(wtypes.IntegerType(), mandatory=True)
|
||||
connection_limit = wtypes.wsattr(wtypes.IntegerType())
|
||||
tls_certificate_id = wtypes.wsattr(wtypes.StringType(max_length=255))
|
||||
tls_termination = wtypes.wsattr(TLSTermination)
|
||||
sni_containers = [wtypes.StringType(max_length=255)]
|
||||
# TODO(johnsom) Remove after deprecation (R series)
|
||||
project_id = wtypes.wsattr(wtypes.StringType(max_length=36))
|
||||
default_pool_id = wtypes.wsattr(wtypes.UuidType())
|
||||
default_pool = wtypes.wsattr(pool.PoolPOST)
|
||||
l7policies = wtypes.wsattr([l7policy.L7PolicyPOST], default=[])
|
||||
insert_headers = wtypes.wsattr(wtypes.DictType(str, str))
|
||||
|
||||
|
||||
class ListenerPUT(base.BaseType):
|
||||
"""Defines attributes that are acceptable of a PUT request."""
|
||||
name = wtypes.wsattr(wtypes.StringType(max_length=255))
|
||||
description = wtypes.wsattr(wtypes.StringType(max_length=255))
|
||||
enabled = wtypes.wsattr(bool)
|
||||
protocol = wtypes.wsattr(wtypes.Enum(str, *constants.SUPPORTED_PROTOCOLS))
|
||||
protocol_port = wtypes.wsattr(wtypes.IntegerType())
|
||||
connection_limit = wtypes.wsattr(wtypes.IntegerType())
|
||||
tls_certificate_id = wtypes.wsattr(wtypes.StringType(max_length=255))
|
||||
tls_termination = wtypes.wsattr(TLSTermination)
|
||||
sni_containers = [wtypes.StringType(max_length=255)]
|
||||
default_pool_id = wtypes.wsattr(wtypes.UuidType())
|
||||
insert_headers = wtypes.wsattr(wtypes.DictType(str, str))
|
||||
@@ -1,25 +0,0 @@
|
||||
# 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.
|
||||
|
||||
from wsme import types as wtypes
|
||||
|
||||
from octavia.api.common import types as base
|
||||
|
||||
|
||||
class ListenerStatisticsResponse(base.BaseType):
|
||||
bytes_in = wtypes.wsattr(wtypes.IntegerType())
|
||||
bytes_out = wtypes.wsattr(wtypes.IntegerType())
|
||||
active_connections = wtypes.wsattr(wtypes.IntegerType())
|
||||
total_connections = wtypes.wsattr(wtypes.IntegerType())
|
||||
request_errors = wtypes.wsattr(wtypes.IntegerType())
|
||||
@@ -1,78 +0,0 @@
|
||||
# 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.
|
||||
|
||||
from wsme import types as wtypes
|
||||
|
||||
from octavia.api.common import types as base
|
||||
from octavia.api.v1.types import listener
|
||||
|
||||
|
||||
class VIP(base.BaseType):
|
||||
"""Defines the response and acceptable POST request attributes."""
|
||||
ip_address = wtypes.wsattr(base.IPAddressType())
|
||||
port_id = wtypes.wsattr(wtypes.UuidType())
|
||||
subnet_id = wtypes.wsattr(wtypes.UuidType())
|
||||
network_id = wtypes.wsattr(wtypes.UuidType())
|
||||
|
||||
|
||||
class LoadBalancerResponse(base.BaseType):
|
||||
"""Defines which attributes are to be shown on any response."""
|
||||
id = wtypes.wsattr(wtypes.UuidType())
|
||||
name = wtypes.wsattr(wtypes.StringType())
|
||||
description = wtypes.wsattr(wtypes.StringType())
|
||||
provisioning_status = wtypes.wsattr(wtypes.StringType())
|
||||
operating_status = wtypes.wsattr(wtypes.StringType())
|
||||
enabled = wtypes.wsattr(bool)
|
||||
vip = wtypes.wsattr(VIP)
|
||||
project_id = wtypes.wsattr(wtypes.StringType())
|
||||
listeners = wtypes.wsattr([listener.ListenerResponse])
|
||||
created_at = wtypes.wsattr(wtypes.datetime.datetime)
|
||||
updated_at = wtypes.wsattr(wtypes.datetime.datetime)
|
||||
|
||||
@classmethod
|
||||
def from_data_model(cls, data_model, children=False):
|
||||
lb = super(LoadBalancerResponse, cls).from_data_model(
|
||||
data_model, children=children)
|
||||
# NOTE(blogan): VIP is technically a child but its the main piece of
|
||||
# a load balancer so it makes sense to show it no matter what.
|
||||
lb.vip = VIP.from_data_model(data_model.vip)
|
||||
if not children:
|
||||
# NOTE(blogan): don't show listeners if the request does not want
|
||||
# to see children
|
||||
del lb.listeners
|
||||
return lb
|
||||
lb.listeners = [
|
||||
listener.ListenerResponse.from_data_model(
|
||||
listener_dm, children=children)
|
||||
for listener_dm in data_model.listeners
|
||||
]
|
||||
return lb
|
||||
|
||||
|
||||
class LoadBalancerPOST(base.BaseType):
|
||||
"""Defines mandatory and optional attributes of a POST request."""
|
||||
id = wtypes.wsattr(wtypes.UuidType())
|
||||
name = wtypes.wsattr(wtypes.StringType(max_length=255))
|
||||
description = wtypes.wsattr(wtypes.StringType(max_length=255))
|
||||
enabled = wtypes.wsattr(bool, default=True)
|
||||
vip = wtypes.wsattr(VIP, mandatory=True)
|
||||
project_id = wtypes.wsattr(wtypes.StringType(max_length=36))
|
||||
listeners = wtypes.wsattr([listener.ListenerPOST], default=[])
|
||||
|
||||
|
||||
class LoadBalancerPUT(base.BaseType):
|
||||
"""Defines attributes that are acceptable of a PUT request."""
|
||||
name = wtypes.wsattr(wtypes.StringType(max_length=255))
|
||||
description = wtypes.wsattr(wtypes.StringType(max_length=255))
|
||||
enabled = wtypes.wsattr(bool)
|
||||
@@ -1,50 +0,0 @@
|
||||
# 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.
|
||||
|
||||
from wsme import types as wtypes
|
||||
|
||||
from octavia.api.common import types as base
|
||||
from octavia.api.v1.types import listener_statistics
|
||||
|
||||
|
||||
class ListenerStatistics(listener_statistics.ListenerStatisticsResponse):
|
||||
id = wtypes.wsattr(wtypes.UuidType())
|
||||
|
||||
@classmethod
|
||||
def from_data_model(cls, data_model, children=False):
|
||||
ls_stats = super(ListenerStatistics, cls).from_data_model(
|
||||
data_model, children=children)
|
||||
ls_stats.id = data_model.listener_id
|
||||
return ls_stats
|
||||
|
||||
|
||||
class LoadBalancerStatisticsResponse(base.BaseType):
|
||||
bytes_in = wtypes.wsattr(wtypes.IntegerType())
|
||||
bytes_out = wtypes.wsattr(wtypes.IntegerType())
|
||||
active_connections = wtypes.wsattr(wtypes.IntegerType())
|
||||
total_connections = wtypes.wsattr(wtypes.IntegerType())
|
||||
request_errors = wtypes.wsattr(wtypes.IntegerType())
|
||||
listeners = wtypes.wsattr([ListenerStatistics])
|
||||
|
||||
@classmethod
|
||||
def from_data_model(cls, data_model, children=False):
|
||||
lb_stats = super(LoadBalancerStatisticsResponse, cls).from_data_model(
|
||||
data_model, children=children)
|
||||
|
||||
lb_stats.listeners = [
|
||||
ListenerStatistics.from_data_model(
|
||||
listener_dm, children=children)
|
||||
for listener_dm in data_model.listeners
|
||||
]
|
||||
return lb_stats
|
||||
@@ -1,60 +0,0 @@
|
||||
# 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.
|
||||
|
||||
from wsme import types as wtypes
|
||||
|
||||
from octavia.api.common import types as base
|
||||
from octavia.common import constants
|
||||
|
||||
|
||||
class MemberResponse(base.BaseType):
|
||||
"""Defines which attributes are to be shown on any response."""
|
||||
id = wtypes.wsattr(wtypes.UuidType())
|
||||
operating_status = wtypes.wsattr(wtypes.StringType())
|
||||
enabled = wtypes.wsattr(bool)
|
||||
ip_address = wtypes.wsattr(base.IPAddressType())
|
||||
protocol_port = wtypes.wsattr(wtypes.IntegerType())
|
||||
weight = wtypes.wsattr(wtypes.IntegerType())
|
||||
subnet_id = wtypes.wsattr(wtypes.UuidType())
|
||||
project_id = wtypes.wsattr(wtypes.StringType())
|
||||
created_at = wtypes.wsattr(wtypes.datetime.datetime)
|
||||
updated_at = wtypes.wsattr(wtypes.datetime.datetime)
|
||||
monitor_address = wtypes.wsattr(base.IPAddressType())
|
||||
monitor_port = wtypes.wsattr(wtypes.IntegerType())
|
||||
|
||||
|
||||
class MemberPOST(base.BaseType):
|
||||
"""Defines mandatory and optional attributes of a POST request."""
|
||||
id = wtypes.wsattr(wtypes.UuidType())
|
||||
enabled = wtypes.wsattr(bool, default=True)
|
||||
ip_address = wtypes.wsattr(base.IPAddressType(), mandatory=True)
|
||||
protocol_port = wtypes.wsattr(wtypes.IntegerType(), mandatory=True)
|
||||
weight = wtypes.wsattr(wtypes.IntegerType(), default=1)
|
||||
subnet_id = wtypes.wsattr(wtypes.UuidType())
|
||||
# TODO(johnsom) Remove after deprecation (R series)
|
||||
project_id = wtypes.wsattr(wtypes.StringType(max_length=36))
|
||||
monitor_port = wtypes.wsattr(wtypes.IntegerType(
|
||||
minimum=constants.MIN_PORT_NUMBER, maximum=constants.MAX_PORT_NUMBER),
|
||||
default=None)
|
||||
monitor_address = wtypes.wsattr(base.IPAddressType(), default=None)
|
||||
|
||||
|
||||
class MemberPUT(base.BaseType):
|
||||
"""Defines attributes that are acceptable of a PUT request."""
|
||||
protocol_port = wtypes.wsattr(wtypes.IntegerType())
|
||||
enabled = wtypes.wsattr(bool)
|
||||
weight = wtypes.wsattr(wtypes.IntegerType())
|
||||
monitor_address = wtypes.wsattr(base.IPAddressType())
|
||||
monitor_port = wtypes.wsattr(wtypes.IntegerType(
|
||||
minimum=constants.MIN_PORT_NUMBER, maximum=constants.MAX_PORT_NUMBER))
|
||||
@@ -1,114 +0,0 @@
|
||||
# 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.
|
||||
|
||||
from wsme import types as wtypes
|
||||
|
||||
from octavia.api.common import types as base
|
||||
from octavia.api.v1.types import health_monitor
|
||||
from octavia.api.v1.types import member
|
||||
from octavia.common import constants
|
||||
|
||||
|
||||
class SessionPersistenceResponse(base.BaseType):
|
||||
"""Defines which attributes are to be shown on any response."""
|
||||
type = wtypes.wsattr(wtypes.text)
|
||||
cookie_name = wtypes.wsattr(wtypes.text)
|
||||
|
||||
|
||||
class SessionPersistencePOST(base.BaseType):
|
||||
"""Defines mandatory and optional attributes of a POST request."""
|
||||
type = wtypes.wsattr(wtypes.Enum(str, *constants.SUPPORTED_SP_TYPES),
|
||||
mandatory=True)
|
||||
cookie_name = wtypes.wsattr(wtypes.text)
|
||||
|
||||
|
||||
class SessionPersistencePUT(base.BaseType):
|
||||
"""Defines attributes that are acceptable of a PUT request."""
|
||||
type = wtypes.wsattr(wtypes.Enum(str, *constants.SUPPORTED_SP_TYPES))
|
||||
cookie_name = wtypes.wsattr(wtypes.text, default=None)
|
||||
|
||||
|
||||
class PoolResponse(base.BaseType):
|
||||
"""Defines which attributes are to be shown on any response."""
|
||||
id = wtypes.wsattr(wtypes.UuidType())
|
||||
name = wtypes.wsattr(wtypes.StringType())
|
||||
description = wtypes.wsattr(wtypes.StringType())
|
||||
operating_status = wtypes.wsattr(wtypes.StringType())
|
||||
enabled = wtypes.wsattr(bool)
|
||||
protocol = wtypes.wsattr(wtypes.text)
|
||||
lb_algorithm = wtypes.wsattr(wtypes.text)
|
||||
session_persistence = wtypes.wsattr(SessionPersistenceResponse)
|
||||
project_id = wtypes.wsattr(wtypes.StringType())
|
||||
health_monitor = wtypes.wsattr(health_monitor.HealthMonitorResponse)
|
||||
members = wtypes.wsattr([member.MemberResponse])
|
||||
created_at = wtypes.wsattr(wtypes.datetime.datetime)
|
||||
updated_at = wtypes.wsattr(wtypes.datetime.datetime)
|
||||
|
||||
@classmethod
|
||||
def from_data_model(cls, data_model, children=False):
|
||||
pool = super(PoolResponse, cls).from_data_model(
|
||||
data_model, children=children)
|
||||
# NOTE(blogan): we should show session persistence on every request
|
||||
# to show a pool
|
||||
if data_model.session_persistence:
|
||||
pool.session_persistence = (
|
||||
SessionPersistenceResponse.from_data_model(
|
||||
data_model.session_persistence))
|
||||
if not children:
|
||||
# NOTE(blogan): do not show members or health_monitor if the
|
||||
# request does not want to see children
|
||||
del pool.members
|
||||
del pool.health_monitor
|
||||
return pool
|
||||
pool.members = [
|
||||
member.MemberResponse.from_data_model(member_dm, children=children)
|
||||
for member_dm in data_model.members
|
||||
]
|
||||
if data_model.health_monitor:
|
||||
pool.health_monitor = (
|
||||
health_monitor.HealthMonitorResponse.from_data_model(
|
||||
data_model.health_monitor, children=children))
|
||||
if not pool.health_monitor:
|
||||
del pool.health_monitor
|
||||
return pool
|
||||
|
||||
|
||||
class PoolPOST(base.BaseType):
|
||||
"""Defines mandatory and optional attributes of a POST request."""
|
||||
id = wtypes.wsattr(wtypes.UuidType())
|
||||
name = wtypes.wsattr(wtypes.StringType(max_length=255))
|
||||
description = wtypes.wsattr(wtypes.StringType(max_length=255))
|
||||
enabled = wtypes.wsattr(bool, default=True)
|
||||
listener_id = wtypes.wsattr(wtypes.UuidType())
|
||||
protocol = wtypes.wsattr(wtypes.Enum(str, *constants.SUPPORTED_PROTOCOLS),
|
||||
mandatory=True)
|
||||
lb_algorithm = wtypes.wsattr(
|
||||
wtypes.Enum(str, *constants.SUPPORTED_LB_ALGORITHMS),
|
||||
mandatory=True)
|
||||
session_persistence = wtypes.wsattr(SessionPersistencePOST)
|
||||
# TODO(johnsom) Remove after deprecation (R series)
|
||||
project_id = wtypes.wsattr(wtypes.StringType(max_length=36))
|
||||
health_monitor = wtypes.wsattr(health_monitor.HealthMonitorPOST)
|
||||
members = wtypes.wsattr([member.MemberPOST])
|
||||
|
||||
|
||||
class PoolPUT(base.BaseType):
|
||||
"""Defines attributes that are acceptable of a PUT request."""
|
||||
name = wtypes.wsattr(wtypes.StringType())
|
||||
description = wtypes.wsattr(wtypes.StringType())
|
||||
enabled = wtypes.wsattr(bool)
|
||||
protocol = wtypes.wsattr(wtypes.Enum(str, *constants.SUPPORTED_PROTOCOLS))
|
||||
lb_algorithm = wtypes.wsattr(
|
||||
wtypes.Enum(str, *constants.SUPPORTED_LB_ALGORITHMS))
|
||||
session_persistence = wtypes.wsattr(SessionPersistencePUT)
|
||||
@@ -1,73 +0,0 @@
|
||||
# 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 as base
|
||||
|
||||
|
||||
class QuotaBase(base.BaseType):
|
||||
"""Individual quota definitions."""
|
||||
load_balancer = wtypes.wsattr(wtypes.IntegerType())
|
||||
listener = wtypes.wsattr(wtypes.IntegerType())
|
||||
member = wtypes.wsattr(wtypes.IntegerType())
|
||||
pool = wtypes.wsattr(wtypes.IntegerType())
|
||||
health_monitor = wtypes.wsattr(wtypes.IntegerType())
|
||||
|
||||
|
||||
class QuotaResponse(base.BaseType):
|
||||
"""Wrapper object for quotas responses."""
|
||||
quota = wtypes.wsattr(QuotaBase)
|
||||
|
||||
@classmethod
|
||||
def from_data_model(cls, data_model, children=False):
|
||||
quotas = super(QuotaResponse, cls).from_data_model(
|
||||
data_model, children=children)
|
||||
quotas.quota = QuotaBase.from_data_model(data_model)
|
||||
return quotas
|
||||
|
||||
|
||||
class QuotaAllBase(base.BaseType):
|
||||
"""Wrapper object for get all quotas responses."""
|
||||
project_id = wtypes.wsattr(wtypes.StringType())
|
||||
tenant_id = wtypes.wsattr(wtypes.StringType())
|
||||
load_balancer = wtypes.wsattr(wtypes.IntegerType())
|
||||
listener = wtypes.wsattr(wtypes.IntegerType())
|
||||
member = wtypes.wsattr(wtypes.IntegerType())
|
||||
pool = wtypes.wsattr(wtypes.IntegerType())
|
||||
health_monitor = wtypes.wsattr(wtypes.IntegerType())
|
||||
|
||||
@classmethod
|
||||
def from_data_model(cls, data_model, children=False):
|
||||
quotas = super(QuotaAllBase, cls).from_data_model(
|
||||
data_model, children=children)
|
||||
quotas.tenant_id = quotas.project_id
|
||||
return quotas
|
||||
|
||||
|
||||
class QuotaAllResponse(base.BaseType):
|
||||
quotas = wtypes.wsattr([QuotaAllBase])
|
||||
|
||||
@classmethod
|
||||
def from_data_model(cls, data_model, children=False):
|
||||
quotalist = QuotaAllResponse()
|
||||
quotalist.quotas = [
|
||||
QuotaAllBase.from_data_model(obj)
|
||||
for obj in data_model]
|
||||
return quotalist
|
||||
|
||||
|
||||
class QuotaPUT(base.BaseType):
|
||||
"""Overall object for quota PUT request."""
|
||||
quota = wtypes.wsattr(QuotaBase)
|
||||
@@ -52,8 +52,6 @@ api_opts = [
|
||||
constants.KEYSTONE,
|
||||
constants.TESTING],
|
||||
help=_("The auth strategy for API requests.")),
|
||||
cfg.StrOpt('api_handler', default='queue_producer',
|
||||
help=_("The handler that the API communicates with")),
|
||||
cfg.BoolOpt('allow_pagination', default=True,
|
||||
help=_("Allow the usage of pagination")),
|
||||
cfg.BoolOpt('allow_sorting', default=True,
|
||||
@@ -71,10 +69,6 @@ api_opts = [
|
||||
help=_("Base URI for the API for use in pagination links. "
|
||||
"This will be autodetected from the request if not "
|
||||
"overridden here.")),
|
||||
cfg.BoolOpt('api_v1_enabled', default=True,
|
||||
help=_("Expose the v1 API?")),
|
||||
cfg.BoolOpt('api_v2_enabled', default=True,
|
||||
help=_("Expose the v2 API?")),
|
||||
cfg.BoolOpt('allow_tls_terminated_listeners', default=True,
|
||||
help=_("Allow users to create TLS Terminated listeners?")),
|
||||
cfg.BoolOpt('allow_ping_health_monitors', default=True,
|
||||
@@ -206,27 +200,10 @@ healthmanager_opts = [
|
||||
help=_('Driver for updating amphora health system.')),
|
||||
cfg.StrOpt('stats_update_driver', default='stats_db',
|
||||
help=_('Driver for updating amphora statistics.')),
|
||||
|
||||
# Used for synchronizing neutron-lbaas and octavia
|
||||
cfg.StrOpt('event_streamer_driver',
|
||||
help=_('Specifies which driver to use for the event_streamer '
|
||||
'for syncing the octavia and neutron_lbaas dbs. If you '
|
||||
'don\'t need to sync the database or are running '
|
||||
'octavia in stand alone mode use the '
|
||||
'noop_event_streamer'),
|
||||
default='noop_event_streamer'),
|
||||
cfg.BoolOpt('sync_provisioning_status', default=False,
|
||||
help=_("Enable provisioning status sync with neutron db"))]
|
||||
]
|
||||
|
||||
oslo_messaging_opts = [
|
||||
cfg.StrOpt('topic'),
|
||||
cfg.StrOpt('event_stream_topic',
|
||||
default='neutron_lbaas_event',
|
||||
help=_('topic name for communicating events through a queue')),
|
||||
cfg.StrOpt('event_stream_transport_url', default=None,
|
||||
help=_('Transport URL to use for the neutron-lbaas '
|
||||
'synchronization event stream when neutron and octavia '
|
||||
'have separate queues.')),
|
||||
]
|
||||
|
||||
haproxy_amphora_opts = [
|
||||
|
||||
@@ -523,8 +523,6 @@ MAX_QUOTA = 2000000000
|
||||
|
||||
API_VERSION = '0.5'
|
||||
|
||||
NOOP_EVENT_STREAMER = 'noop_event_streamer'
|
||||
|
||||
HAPROXY_BASE_PEER_PORT = 1025
|
||||
KEEPALIVED_JINJA2_UPSTART = 'keepalived.upstart.j2'
|
||||
KEEPALIVED_JINJA2_SYSTEMD = 'keepalived.systemd.j2'
|
||||
|
||||
@@ -25,7 +25,6 @@ from stevedore import driver as stevedore_driver
|
||||
from octavia.common import constants
|
||||
from octavia.common import stats
|
||||
from octavia.controller.healthmanager.health_drivers import update_base
|
||||
from octavia.controller.healthmanager import update_serializer
|
||||
from octavia.db import api as db_api
|
||||
from octavia.db import repositories as repo
|
||||
|
||||
@@ -37,10 +36,6 @@ class UpdateHealthDb(update_base.HealthUpdateBase):
|
||||
def __init__(self):
|
||||
super(UpdateHealthDb, self).__init__()
|
||||
# first setup repo for amphora, listener,member(nodes),pool repo
|
||||
self.event_streamer = stevedore_driver.DriverManager(
|
||||
namespace='octavia.controller.queues',
|
||||
name=CONF.health_manager.event_streamer_driver,
|
||||
invoke_on_load=True).driver
|
||||
self.amphora_repo = repo.AmphoraRepository()
|
||||
self.amphora_health_repo = repo.AmphoraHealthRepository()
|
||||
self.listener_repo = repo.ListenerRepository()
|
||||
@@ -48,12 +43,8 @@ class UpdateHealthDb(update_base.HealthUpdateBase):
|
||||
self.member_repo = repo.MemberRepository()
|
||||
self.pool_repo = repo.PoolRepository()
|
||||
|
||||
def emit(self, info_type, info_id, info_obj):
|
||||
cnt = update_serializer.InfoContainer(info_type, info_id, info_obj)
|
||||
self.event_streamer.emit(cnt)
|
||||
|
||||
def _update_status_and_emit_event(self, session, repo, entity_type,
|
||||
entity_id, new_op_status, old_op_status):
|
||||
def _update_status(self, session, repo, entity_type,
|
||||
entity_id, new_op_status, old_op_status):
|
||||
message = {}
|
||||
if old_op_status.lower() != new_op_status.lower():
|
||||
LOG.debug("%s %s status has changed from %s to "
|
||||
@@ -61,22 +52,10 @@ class UpdateHealthDb(update_base.HealthUpdateBase):
|
||||
entity_type, entity_id, old_op_status,
|
||||
new_op_status)
|
||||
repo.update(session, entity_id, operating_status=new_op_status)
|
||||
# Map the status for neutron-lbaas
|
||||
# Map the status for neutron-lbaas compatibility
|
||||
if new_op_status == constants.DRAINING:
|
||||
new_op_status = constants.ONLINE
|
||||
message.update({constants.OPERATING_STATUS: new_op_status})
|
||||
if (CONF.health_manager.event_streamer_driver !=
|
||||
constants.NOOP_EVENT_STREAMER):
|
||||
if CONF.health_manager.sync_provisioning_status:
|
||||
current_prov_status = repo.get(
|
||||
session, id=entity_id).provisioning_status
|
||||
LOG.debug("%s %s provisioning_status %s. "
|
||||
"Sending event.",
|
||||
entity_type, entity_id, current_prov_status)
|
||||
message.update(
|
||||
{constants.PROVISIONING_STATUS: current_prov_status})
|
||||
if message:
|
||||
self.emit(entity_type, entity_id, message)
|
||||
|
||||
def update_health(self, health, srcaddr):
|
||||
# The executor will eat any exceptions from the update_health code
|
||||
@@ -277,7 +256,7 @@ class UpdateHealthDb(update_base.HealthUpdateBase):
|
||||
try:
|
||||
if (listener_status is not None and
|
||||
listener_status != db_op_status):
|
||||
self._update_status_and_emit_event(
|
||||
self._update_status(
|
||||
session, self.listener_repo, constants.LISTENER,
|
||||
listener_id, listener_status, db_op_status)
|
||||
except sqlalchemy.orm.exc.NoResultFound:
|
||||
@@ -305,7 +284,7 @@ class UpdateHealthDb(update_base.HealthUpdateBase):
|
||||
try:
|
||||
# If the database doesn't already show the pool offline, update
|
||||
if potential_offline_pools[pool_id] != constants.OFFLINE:
|
||||
self._update_status_and_emit_event(
|
||||
self._update_status(
|
||||
session, self.pool_repo, constants.POOL,
|
||||
pool_id, constants.OFFLINE,
|
||||
potential_offline_pools[pool_id])
|
||||
@@ -315,7 +294,7 @@ class UpdateHealthDb(update_base.HealthUpdateBase):
|
||||
# Update the load balancer status last
|
||||
try:
|
||||
if lb_status != db_lb['operating_status']:
|
||||
self._update_status_and_emit_event(
|
||||
self._update_status(
|
||||
session, self.loadbalancer_repo,
|
||||
constants.LOADBALANCER, db_lb['id'], lb_status,
|
||||
db_lb[constants.OPERATING_STATUS])
|
||||
@@ -393,7 +372,7 @@ class UpdateHealthDb(update_base.HealthUpdateBase):
|
||||
try:
|
||||
if (member_status is not None and
|
||||
member_status != member_db_status):
|
||||
self._update_status_and_emit_event(
|
||||
self._update_status(
|
||||
session, self.member_repo, constants.MEMBER,
|
||||
member_id, member_status, member_db_status)
|
||||
except sqlalchemy.orm.exc.NoResultFound:
|
||||
@@ -403,7 +382,7 @@ class UpdateHealthDb(update_base.HealthUpdateBase):
|
||||
try:
|
||||
if (pool_status is not None and
|
||||
pool_status != db_pool_dict['operating_status']):
|
||||
self._update_status_and_emit_event(
|
||||
self._update_status(
|
||||
session, self.pool_repo, constants.POOL,
|
||||
pool_id, pool_status, db_pool_dict['operating_status'])
|
||||
except sqlalchemy.orm.exc.NoResultFound:
|
||||
@@ -416,16 +395,8 @@ class UpdateStatsDb(update_base.StatsUpdateBase, stats.StatsMixin):
|
||||
|
||||
def __init__(self):
|
||||
super(UpdateStatsDb, self).__init__()
|
||||
self.event_streamer = stevedore_driver.DriverManager(
|
||||
namespace='octavia.controller.queues',
|
||||
name=CONF.health_manager.event_streamer_driver,
|
||||
invoke_on_load=True).driver
|
||||
self.repo_listener = repo.ListenerRepository()
|
||||
|
||||
def emit(self, info_type, info_id, info_obj):
|
||||
cnt = update_serializer.InfoContainer(info_type, info_id, info_obj)
|
||||
self.event_streamer.emit(cnt)
|
||||
|
||||
def update_stats(self, health_message, srcaddr):
|
||||
# The executor will eat any exceptions from the update_stats code
|
||||
# so we need to wrap it and log the unhandled exception
|
||||
@@ -484,21 +455,3 @@ class UpdateStatsDb(update_base.StatsUpdateBase, stats.StatsMixin):
|
||||
listener_id, amphora_id, stats)
|
||||
self.listener_stats_repo.replace(
|
||||
session, listener_id, amphora_id, **stats)
|
||||
|
||||
if (CONF.health_manager.event_streamer_driver !=
|
||||
constants.NOOP_EVENT_STREAMER):
|
||||
listener_stats = self.get_listener_stats(session, listener_id)
|
||||
self.emit(
|
||||
'listener_stats', listener_id, listener_stats.get_stats())
|
||||
|
||||
listener_db = self.repo_listener.get(session, id=listener_id)
|
||||
if not listener_db:
|
||||
LOG.debug('Received health stats for a non-existent '
|
||||
'listener %s for amphora %s with IP '
|
||||
'%s.', listener_id, amphora_id, srcaddr)
|
||||
return
|
||||
|
||||
lb_stats = self.get_loadbalancer_stats(
|
||||
session, listener_db.load_balancer_id)
|
||||
self.emit('loadbalancer_stats',
|
||||
listener_db.load_balancer_id, lb_stats.get_stats())
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
# 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 copy
|
||||
|
||||
|
||||
class InfoContainer(object):
|
||||
@staticmethod
|
||||
def from_dict(dict_obj):
|
||||
return InfoContainer(dict_obj['info_type'],
|
||||
dict_obj['info_id'],
|
||||
dict_obj['info_payload'])
|
||||
|
||||
def __init__(self, info_type, info_id, info_payload):
|
||||
self.info_type = copy.copy(info_type)
|
||||
self.info_id = copy.copy(info_id)
|
||||
self.info_payload = copy.deepcopy(info_payload)
|
||||
|
||||
def to_dict(self):
|
||||
return {'info_type': self.info_type,
|
||||
'info_id': self.info_id,
|
||||
'info_payload': self.info_payload}
|
||||
|
||||
def __eq__(self, other):
|
||||
if not isinstance(other, InfoContainer):
|
||||
return False
|
||||
if self.info_type != other.info_type:
|
||||
return False
|
||||
if self.info_id != other.info_id:
|
||||
return False
|
||||
if self.info_payload != other.info_payload:
|
||||
return False
|
||||
return True
|
||||
|
||||
def __ne__(self, other):
|
||||
return not self == other
|
||||
@@ -1,76 +0,0 @@
|
||||
# Copyright 2015 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 abc
|
||||
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log as logging
|
||||
import oslo_messaging
|
||||
import six
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@six.add_metaclass(abc.ABCMeta)
|
||||
class EventStreamerBase(object):
|
||||
"""Base class for EventStreamer
|
||||
|
||||
A stand in abstract class that defines what methods are stevedore loaded
|
||||
implementations of event streamer is expected to provide.
|
||||
"""
|
||||
@abc.abstractmethod
|
||||
def emit(self, cnt):
|
||||
"""method to send a DB event to neutron-lbaas if it is needed.
|
||||
|
||||
:param cnt: an InfoContainer container object
|
||||
:return: None
|
||||
"""
|
||||
|
||||
|
||||
class EventStreamerNoop(EventStreamerBase):
|
||||
"""Nop class implementation of EventStreamer
|
||||
|
||||
Useful if you're running in standalone mode and don't need to send
|
||||
updates to Neutron LBaaS
|
||||
"""
|
||||
|
||||
def emit(self, cnt):
|
||||
pass
|
||||
|
||||
|
||||
class EventStreamerNeutron(EventStreamerBase):
|
||||
"""Neutron LBaaS
|
||||
|
||||
When you're using Octavia alongside neutron LBaaS this class provides
|
||||
a mechanism to send updates to neutron LBaaS database via
|
||||
oslo_messaging queues.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
topic = cfg.CONF.oslo_messaging.event_stream_topic
|
||||
if cfg.CONF.oslo_messaging.event_stream_transport_url:
|
||||
# Use custom URL
|
||||
self.transport = oslo_messaging.get_rpc_transport(
|
||||
cfg.CONF, cfg.CONF.oslo_messaging.event_stream_transport_url)
|
||||
else:
|
||||
self.transport = oslo_messaging.get_rpc_transport(cfg.CONF)
|
||||
self.target = oslo_messaging.Target(topic=topic, exchange="common",
|
||||
namespace='control', fanout=False,
|
||||
version='1.0')
|
||||
self.client = oslo_messaging.RPCClient(self.transport, self.target)
|
||||
|
||||
def emit(self, cnt):
|
||||
LOG.debug("Emitting data to event streamer %s", cnt.to_dict())
|
||||
self.client.cast({}, 'update_info', container=cnt.to_dict())
|
||||
@@ -15,7 +15,7 @@
|
||||
from oslo_config import cfg
|
||||
from oslo_utils import uuidutils
|
||||
|
||||
from octavia.api.v1.types import l7rule
|
||||
from octavia.api.v2.types import l7rule
|
||||
from octavia.common import constants
|
||||
from octavia.common import exceptions
|
||||
from octavia.common import validate
|
||||
|
||||
@@ -35,19 +35,15 @@ class TestRootController(base_db_test.OctaviaDBTestBase):
|
||||
self.conf = self.useFixture(oslo_fixture.Config(cfg.CONF))
|
||||
self.conf.config(group='api_settings', auth_strategy=constants.NOAUTH)
|
||||
|
||||
def _get_versions_with_config(self, api_v1_enabled, api_v2_enabled):
|
||||
self.conf.config(group='api_settings', api_v1_enabled=api_v1_enabled)
|
||||
self.conf.config(group='api_settings', api_v2_enabled=api_v2_enabled)
|
||||
def _get_versions_with_config(self):
|
||||
app = pecan.testing.load_test_app({'app': pconfig.app,
|
||||
'wsme': pconfig.wsme})
|
||||
return self.get(app=app, path='/').json.get('versions', None)
|
||||
|
||||
def test_api_versions(self):
|
||||
versions = self._get_versions_with_config(
|
||||
api_v1_enabled=True, api_v2_enabled=True)
|
||||
versions = self._get_versions_with_config()
|
||||
version_ids = tuple(v.get('id') for v in versions)
|
||||
self.assertEqual(12, len(version_ids))
|
||||
self.assertIn('v1', version_ids)
|
||||
self.assertEqual(11, len(version_ids))
|
||||
self.assertIn('v2.0', version_ids)
|
||||
self.assertIn('v2.1', version_ids)
|
||||
self.assertIn('v2.2', version_ids)
|
||||
@@ -63,41 +59,9 @@ class TestRootController(base_db_test.OctaviaDBTestBase):
|
||||
# Each version should have a 'self' 'href' to the API version URL
|
||||
# [{u'rel': u'self', u'href': u'http://localhost/v2'}]
|
||||
# Validate that the URL exists in the response
|
||||
version_url = 'http://localhost/v2'
|
||||
for version in versions:
|
||||
url_version = None
|
||||
if version['id'].startswith('v2.'):
|
||||
url_version = 'v2'
|
||||
else:
|
||||
url_version = version['id']
|
||||
version_url = 'http://localhost/{}'.format(url_version)
|
||||
links = version['links']
|
||||
# Note, there may be other links present, this test is for 'self'
|
||||
version_link = [link for link in links if link['rel'] == 'self']
|
||||
self.assertEqual(version_url, version_link[0]['href'])
|
||||
|
||||
def test_api_v1_disabled(self):
|
||||
versions = self._get_versions_with_config(
|
||||
api_v1_enabled=False, api_v2_enabled=True)
|
||||
self.assertEqual(11, len(versions))
|
||||
self.assertEqual('v2.0', versions[0].get('id'))
|
||||
self.assertEqual('v2.1', versions[1].get('id'))
|
||||
self.assertEqual('v2.2', versions[2].get('id'))
|
||||
self.assertEqual('v2.3', versions[3].get('id'))
|
||||
self.assertEqual('v2.4', versions[4].get('id'))
|
||||
self.assertEqual('v2.5', versions[5].get('id'))
|
||||
self.assertEqual('v2.6', versions[6].get('id'))
|
||||
self.assertEqual('v2.7', versions[7].get('id'))
|
||||
self.assertEqual('v2.8', versions[8].get('id'))
|
||||
self.assertEqual('v2.9', versions[9].get('id'))
|
||||
self.assertEqual('v2.10', versions[10].get('id'))
|
||||
|
||||
def test_api_v2_disabled(self):
|
||||
versions = self._get_versions_with_config(
|
||||
api_v1_enabled=True, api_v2_enabled=False)
|
||||
self.assertEqual(1, len(versions))
|
||||
self.assertEqual('v1', versions[0].get('id'))
|
||||
|
||||
def test_api_both_disabled(self):
|
||||
versions = self._get_versions_with_config(
|
||||
api_v1_enabled=False, api_v2_enabled=False)
|
||||
self.assertEqual(0, len(versions))
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
# 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.
|
||||
@@ -1,320 +0,0 @@
|
||||
# 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 mock
|
||||
from oslo_config import cfg
|
||||
from oslo_config import fixture as oslo_fixture
|
||||
from oslo_utils import uuidutils
|
||||
import pecan
|
||||
import pecan.testing
|
||||
|
||||
from octavia.api import config as pconfig
|
||||
# needed for tests to function when run independently:
|
||||
from octavia.common import config # noqa: F401
|
||||
from octavia.common import constants
|
||||
from octavia.db import api as db_api
|
||||
from octavia.db import repositories
|
||||
from octavia.tests.functional.db import base as base_db_test
|
||||
|
||||
|
||||
class BaseAPITest(base_db_test.OctaviaDBTestBase):
|
||||
|
||||
BASE_PATH = '/v1'
|
||||
QUOTAS_PATH = '/quotas'
|
||||
QUOTA_PATH = QUOTAS_PATH + '/{project_id}'
|
||||
QUOTA_DEFAULT_PATH = QUOTAS_PATH + '/{project_id}/default'
|
||||
LBS_PATH = '/loadbalancers'
|
||||
LB_PATH = LBS_PATH + '/{lb_id}'
|
||||
LB_DELETE_CASCADE_PATH = LB_PATH + '/delete_cascade'
|
||||
LB_STATS_PATH = LB_PATH + '/stats'
|
||||
LISTENERS_PATH = LB_PATH + '/listeners'
|
||||
LISTENER_PATH = LISTENERS_PATH + '/{listener_id}'
|
||||
LISTENER_STATS_PATH = LISTENER_PATH + '/stats'
|
||||
POOLS_PATH = LB_PATH + '/pools'
|
||||
POOL_PATH = POOLS_PATH + '/{pool_id}'
|
||||
DEPRECATED_POOLS_PATH = LISTENER_PATH + '/pools'
|
||||
DEPRECATED_POOL_PATH = DEPRECATED_POOLS_PATH + '/{pool_id}'
|
||||
MEMBERS_PATH = POOL_PATH + '/members'
|
||||
MEMBER_PATH = MEMBERS_PATH + '/{member_id}'
|
||||
DEPRECATED_MEMBERS_PATH = DEPRECATED_POOL_PATH + '/members'
|
||||
DEPRECATED_MEMBER_PATH = DEPRECATED_MEMBERS_PATH + '/{member_id}'
|
||||
HM_PATH = POOL_PATH + '/healthmonitor'
|
||||
DEPRECATED_HM_PATH = DEPRECATED_POOL_PATH + '/healthmonitor'
|
||||
L7POLICIES_PATH = LISTENER_PATH + '/l7policies'
|
||||
L7POLICY_PATH = L7POLICIES_PATH + '/{l7policy_id}'
|
||||
L7RULES_PATH = L7POLICY_PATH + '/l7rules'
|
||||
L7RULE_PATH = L7RULES_PATH + '/{l7rule_id}'
|
||||
|
||||
def setUp(self):
|
||||
super(BaseAPITest, self).setUp()
|
||||
conf = self.useFixture(oslo_fixture.Config(cfg.CONF))
|
||||
conf.config(group='api_settings', api_handler='simulated_handler')
|
||||
conf.config(group="controller_worker",
|
||||
network_driver='network_noop_driver')
|
||||
conf.config(group='api_settings', auth_strategy=constants.NOAUTH)
|
||||
self.lb_repo = repositories.LoadBalancerRepository()
|
||||
self.listener_repo = repositories.ListenerRepository()
|
||||
self.listener_stats_repo = repositories.ListenerStatisticsRepository()
|
||||
self.pool_repo = repositories.PoolRepository()
|
||||
self.member_repo = repositories.MemberRepository()
|
||||
self.amphora_repo = repositories.AmphoraRepository()
|
||||
patcher = mock.patch('octavia.api.handlers.controller_simulator.'
|
||||
'handler.SimulatedControllerHandler')
|
||||
self.handler_mock = patcher.start()
|
||||
self.check_quota_met_true_mock = mock.patch(
|
||||
'octavia.db.repositories.Repositories.check_quota_met',
|
||||
return_value=True)
|
||||
self.app = self._make_app()
|
||||
self.project_id = uuidutils.generate_uuid()
|
||||
|
||||
def reset_pecan():
|
||||
patcher.stop()
|
||||
pecan.set_config({}, overwrite=True)
|
||||
|
||||
self.addCleanup(reset_pecan)
|
||||
|
||||
def _make_app(self):
|
||||
return pecan.testing.load_test_app(
|
||||
{'app': pconfig.app, 'wsme': pconfig.wsme})
|
||||
|
||||
def _get_full_path(self, path):
|
||||
return ''.join([self.BASE_PATH, path])
|
||||
|
||||
def delete(self, path, headers=None, status=202, expect_errors=False):
|
||||
headers = headers or {}
|
||||
full_path = self._get_full_path(path)
|
||||
response = self.app.delete(full_path,
|
||||
headers=headers,
|
||||
status=status,
|
||||
expect_errors=expect_errors)
|
||||
return response
|
||||
|
||||
def post(self, path, body, headers=None, status=202, expect_errors=False):
|
||||
headers = headers or {}
|
||||
full_path = self._get_full_path(path)
|
||||
response = self.app.post_json(full_path,
|
||||
params=body,
|
||||
headers=headers,
|
||||
status=status,
|
||||
expect_errors=expect_errors)
|
||||
return response
|
||||
|
||||
def put(self, path, body, headers=None, status=202, expect_errors=False):
|
||||
headers = headers or {}
|
||||
full_path = self._get_full_path(path)
|
||||
response = self.app.put_json(full_path,
|
||||
params=body,
|
||||
headers=headers,
|
||||
status=status,
|
||||
expect_errors=expect_errors)
|
||||
return response
|
||||
|
||||
def get(self, path, params=None, headers=None, status=200,
|
||||
expect_errors=False):
|
||||
full_path = self._get_full_path(path)
|
||||
response = self.app.get(full_path,
|
||||
params=params,
|
||||
headers=headers,
|
||||
status=status,
|
||||
expect_errors=expect_errors)
|
||||
return response
|
||||
|
||||
def create_load_balancer(self, vip, **optionals):
|
||||
req_dict = {'vip': vip, 'project_id': self.project_id}
|
||||
req_dict.update(optionals)
|
||||
response = self.post(self.LBS_PATH, req_dict)
|
||||
return response.json
|
||||
|
||||
def create_listener(self, lb_id, protocol, protocol_port, **optionals):
|
||||
req_dict = {'protocol': protocol, 'protocol_port': protocol_port,
|
||||
'project_id': self.project_id}
|
||||
req_dict.update(optionals)
|
||||
path = self.LISTENERS_PATH.format(lb_id=lb_id)
|
||||
response = self.post(path, req_dict)
|
||||
return response.json
|
||||
|
||||
def create_listener_stats(self, listener_id, amphora_id):
|
||||
db_ls = self.listener_stats_repo.create(
|
||||
db_api.get_session(), listener_id=listener_id,
|
||||
amphora_id=amphora_id, bytes_in=0,
|
||||
bytes_out=0, active_connections=0, total_connections=0,
|
||||
request_errors=0)
|
||||
return db_ls.to_dict()
|
||||
|
||||
def create_amphora(self, amphora_id, loadbalancer_id, **optionals):
|
||||
# We need to default these values in the request.
|
||||
opts = {'compute_id': uuidutils.generate_uuid(),
|
||||
'status': constants.ACTIVE}
|
||||
opts.update(optionals)
|
||||
amphora = self.amphora_repo.create(
|
||||
self.session, id=amphora_id,
|
||||
load_balancer_id=loadbalancer_id,
|
||||
**opts)
|
||||
return amphora
|
||||
|
||||
def get_listener(self, lb_id, listener_id):
|
||||
path = self.LISTENER_PATH.format(lb_id=lb_id, listener_id=listener_id)
|
||||
response = self.get(path)
|
||||
return response.json
|
||||
|
||||
def create_pool_sans_listener(self, lb_id, protocol, lb_algorithm,
|
||||
**optionals):
|
||||
req_dict = {'protocol': protocol, 'lb_algorithm': lb_algorithm,
|
||||
'project_id': self.project_id}
|
||||
req_dict.update(optionals)
|
||||
path = self.POOLS_PATH.format(lb_id=lb_id)
|
||||
response = self.post(path, req_dict)
|
||||
return response.json
|
||||
|
||||
def create_pool(self, lb_id, listener_id, protocol, lb_algorithm,
|
||||
**optionals):
|
||||
req_dict = {'protocol': protocol, 'lb_algorithm': lb_algorithm,
|
||||
'project_id': self.project_id}
|
||||
req_dict.update(optionals)
|
||||
path = self.DEPRECATED_POOLS_PATH.format(lb_id=lb_id,
|
||||
listener_id=listener_id)
|
||||
response = self.post(path, req_dict)
|
||||
return response.json
|
||||
|
||||
def create_member(self, lb_id, pool_id, ip_address,
|
||||
protocol_port, expect_error=False, **optionals):
|
||||
req_dict = {'ip_address': ip_address, 'protocol_port': protocol_port,
|
||||
'project_id': self.project_id}
|
||||
req_dict.update(optionals)
|
||||
path = self.MEMBERS_PATH.format(lb_id=lb_id, pool_id=pool_id)
|
||||
response = self.post(path, req_dict, expect_errors=expect_error)
|
||||
return response.json
|
||||
|
||||
def create_member_with_listener(self, lb_id, listener_id, pool_id,
|
||||
ip_address, protocol_port, **optionals):
|
||||
req_dict = {'ip_address': ip_address, 'protocol_port': protocol_port,
|
||||
'project_id': self.project_id}
|
||||
req_dict.update(optionals)
|
||||
path = self.DEPRECATED_MEMBERS_PATH.format(
|
||||
lb_id=lb_id, listener_id=listener_id, pool_id=pool_id)
|
||||
response = self.post(path, req_dict)
|
||||
return response.json
|
||||
|
||||
def create_health_monitor(self, lb_id, pool_id, type,
|
||||
delay, timeout, fall_threshold, rise_threshold,
|
||||
**optionals):
|
||||
req_dict = {'type': type,
|
||||
'delay': delay,
|
||||
'timeout': timeout,
|
||||
'fall_threshold': fall_threshold,
|
||||
'rise_threshold': rise_threshold,
|
||||
'project_id': self.project_id}
|
||||
req_dict.update(optionals)
|
||||
path = self.HM_PATH.format(lb_id=lb_id,
|
||||
pool_id=pool_id)
|
||||
response = self.post(path, req_dict)
|
||||
return response.json
|
||||
|
||||
def create_health_monitor_with_listener(
|
||||
self, lb_id, listener_id, pool_id, type,
|
||||
delay, timeout, fall_threshold, rise_threshold, **optionals):
|
||||
req_dict = {'type': type,
|
||||
'delay': delay,
|
||||
'timeout': timeout,
|
||||
'fall_threshold': fall_threshold,
|
||||
'rise_threshold': rise_threshold,
|
||||
'project_id': self.project_id}
|
||||
req_dict.update(optionals)
|
||||
path = self.DEPRECATED_HM_PATH.format(
|
||||
lb_id=lb_id, listener_id=listener_id, pool_id=pool_id)
|
||||
response = self.post(path, req_dict)
|
||||
return response.json
|
||||
|
||||
def create_l7policy(self, lb_id, listener_id, action, **optionals):
|
||||
req_dict = {'action': action}
|
||||
req_dict.update(optionals)
|
||||
path = self.L7POLICIES_PATH.format(lb_id=lb_id,
|
||||
listener_id=listener_id)
|
||||
response = self.post(path, req_dict)
|
||||
return response.json
|
||||
|
||||
def create_l7rule(self, lb_id, listener_id, l7policy_id, type,
|
||||
compare_type, value, **optionals):
|
||||
req_dict = {'type': type, 'compare_type': compare_type, 'value': value}
|
||||
req_dict.update(optionals)
|
||||
path = self.L7RULES_PATH.format(lb_id=lb_id, listener_id=listener_id,
|
||||
l7policy_id=l7policy_id)
|
||||
response = self.post(path, req_dict)
|
||||
return response.json
|
||||
|
||||
def _set_lb_and_children_statuses(self, lb_id, prov_status, op_status):
|
||||
self.lb_repo.update(db_api.get_session(), lb_id,
|
||||
provisioning_status=prov_status,
|
||||
operating_status=op_status)
|
||||
lb_listeners, _ = self.listener_repo.get_all(
|
||||
db_api.get_session(), load_balancer_id=lb_id)
|
||||
for listener in lb_listeners:
|
||||
for pool in listener.pools:
|
||||
self.pool_repo.update(db_api.get_session(), pool.id,
|
||||
operating_status=op_status)
|
||||
for member in pool.members:
|
||||
self.member_repo.update(db_api.get_session(), member.id,
|
||||
operating_status=op_status)
|
||||
self.listener_repo.update(db_api.get_session(), listener.id,
|
||||
provisioning_status=prov_status,
|
||||
operating_status=op_status)
|
||||
|
||||
def set_lb_status(self, lb_id, status=constants.ACTIVE):
|
||||
if status == constants.DELETED:
|
||||
op_status = constants.OFFLINE
|
||||
elif status == constants.ACTIVE:
|
||||
op_status = constants.ONLINE
|
||||
else:
|
||||
db_lb = self.lb_repo.get(db_api.get_session(), id=lb_id)
|
||||
op_status = db_lb.operating_status
|
||||
self._set_lb_and_children_statuses(lb_id, status, op_status)
|
||||
return self.get(self.LB_PATH.format(lb_id=lb_id)).json
|
||||
|
||||
def assert_final_lb_statuses(self, lb_id, delete=False):
|
||||
expected_prov_status = constants.ACTIVE
|
||||
expected_op_status = constants.ONLINE
|
||||
if delete:
|
||||
expected_prov_status = constants.DELETED
|
||||
expected_op_status = constants.OFFLINE
|
||||
self.set_lb_status(lb_id, status=expected_prov_status)
|
||||
self.assert_correct_lb_status(lb_id, expected_prov_status,
|
||||
expected_op_status)
|
||||
|
||||
def assert_final_listener_statuses(self, lb_id, listener_id, delete=False):
|
||||
expected_prov_status = constants.ACTIVE
|
||||
expected_op_status = constants.ONLINE
|
||||
if delete:
|
||||
expected_prov_status = constants.DELETED
|
||||
expected_op_status = constants.OFFLINE
|
||||
self.set_lb_status(lb_id, status=expected_prov_status)
|
||||
self.assert_correct_listener_status(lb_id, listener_id,
|
||||
expected_prov_status,
|
||||
expected_op_status)
|
||||
|
||||
def assert_correct_lb_status(self, lb_id, provisioning_status,
|
||||
operating_status):
|
||||
api_lb = self.get(self.LB_PATH.format(lb_id=lb_id)).json
|
||||
self.assertEqual(provisioning_status,
|
||||
api_lb.get('provisioning_status'))
|
||||
self.assertEqual(operating_status,
|
||||
api_lb.get('operating_status'))
|
||||
|
||||
def assert_correct_listener_status(self, lb_id, listener_id,
|
||||
provisioning_status, operating_status):
|
||||
api_listener = self.get(self.LISTENER_PATH.format(
|
||||
lb_id=lb_id, listener_id=listener_id)).json
|
||||
self.assertEqual(provisioning_status,
|
||||
api_listener.get('provisioning_status'))
|
||||
self.assertEqual(operating_status,
|
||||
api_listener.get('operating_status'))
|
||||
@@ -1,225 +0,0 @@
|
||||
# Copyright 2016 Rackspace
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# 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 operator
|
||||
|
||||
from oslo_serialization import jsonutils as json
|
||||
from oslo_utils import uuidutils
|
||||
|
||||
from octavia.common import constants
|
||||
from octavia.tests.functional.api.v1 import base
|
||||
|
||||
|
||||
class TestApiSort(base.BaseAPITest):
|
||||
def setUp(self):
|
||||
super(TestApiSort, self).setUp()
|
||||
self.random_name_desc = [
|
||||
('b', 'g'), ('h', 'g'), ('b', 'a'), ('c', 'g'),
|
||||
('g', 'c'), ('h', 'h'), ('a', 'e'), ('g', 'h'),
|
||||
('g', 'd'), ('e', 'h'), ('h', 'e'), ('b', 'f'),
|
||||
('b', 'h'), ('a', 'h'), ('g', 'g'), ('h', 'f'),
|
||||
('c', 'h'), ('g', 'f'), ('f', 'f'), ('d', 'd'),
|
||||
('g', 'b'), ('a', 'c'), ('h', 'a'), ('h', 'c'),
|
||||
('e', 'd'), ('d', 'g'), ('c', 'b'), ('f', 'b'),
|
||||
('c', 'c'), ('d', 'c'), ('f', 'a'), ('h', 'd'),
|
||||
('f', 'c'), ('d', 'a'), ('d', 'e'), ('d', 'f'),
|
||||
('g', 'e'), ('a', 'a'), ('e', 'c'), ('e', 'b'),
|
||||
('f', 'g'), ('d', 'b'), ('e', 'a'), ('b', 'e'),
|
||||
('f', 'h'), ('a', 'g'), ('c', 'd'), ('b', 'd'),
|
||||
('b', 'b'), ('a', 'b'), ('f', 'd'), ('f', 'e'),
|
||||
('c', 'a'), ('b', 'c'), ('e', 'f'), ('a', 'f'),
|
||||
('e', 'e'), ('h', 'b'), ('d', 'h'), ('e', 'g'),
|
||||
('c', 'e'), ('g', 'a'), ('a', 'd'), ('c', 'f')]
|
||||
|
||||
self.headers = {'accept': constants.APPLICATION_JSON,
|
||||
'content-type': constants.APPLICATION_JSON}
|
||||
self.lbs = []
|
||||
self.lb_names = ['lb_c', 'lb_a', 'lb_b', 'lb_e', 'lb_d']
|
||||
|
||||
def _create_loadbalancers(self):
|
||||
for name in self.lb_names:
|
||||
lb = self.create_load_balancer(
|
||||
{'subnet_id': uuidutils.generate_uuid()}, name=name)
|
||||
self.lbs.append(lb)
|
||||
|
||||
def test_lb_keysort(self):
|
||||
self._create_loadbalancers()
|
||||
params = {'sort': 'name:desc',
|
||||
'project_id': self.project_id}
|
||||
resp = self.get(self.LBS_PATH, params=params,
|
||||
headers=self.headers)
|
||||
|
||||
lbs = json.loads(resp.body)
|
||||
act_names = [l['name'] for l in lbs]
|
||||
ref_names = sorted(self.lb_names[:], reverse=True)
|
||||
self.assertEqual(ref_names, act_names) # Should be in order
|
||||
|
||||
def test_loadbalancer_sorting_and_pagination(self):
|
||||
# Python's stable sort will allow us to simulate the full sorting
|
||||
# capabilities of the api during testing.
|
||||
exp_order = self.random_name_desc[:]
|
||||
exp_order.sort(key=operator.itemgetter(1), reverse=False)
|
||||
exp_order.sort(key=operator.itemgetter(0), reverse=True)
|
||||
|
||||
for (name, desc) in self.random_name_desc:
|
||||
self.create_load_balancer(
|
||||
{'subnet_id': uuidutils.generate_uuid()},
|
||||
name=name, description=desc)
|
||||
|
||||
params = {'sort': 'name:desc,description:asc',
|
||||
'project_id': self.project_id}
|
||||
|
||||
# Get all lbs
|
||||
resp = self.get(self.LBS_PATH, headers=self.headers, params=params)
|
||||
all_lbs = json.loads(resp.body)
|
||||
|
||||
# Test the first 8 which is just limit=8
|
||||
params.update({'limit': '8'})
|
||||
resp = self.get(self.LBS_PATH, headers=self.headers, params=params)
|
||||
lbs = json.loads(resp.body)
|
||||
fnd_name_descs = [(lb['name'], lb['description']) for lb in lbs]
|
||||
self.assertEqual(exp_order[0:8], fnd_name_descs)
|
||||
|
||||
# Test the slice at 8:24 which is marker=7 limit=16
|
||||
params.update({'marker': all_lbs[7].get('id'), 'limit': '16'})
|
||||
resp = self.get(self.LBS_PATH, headers=self.headers, params=params)
|
||||
lbs = json.loads(resp.body)
|
||||
fnd_name_descs = [(lb['name'], lb['description']) for lb in lbs]
|
||||
self.assertEqual(exp_order[8:24], fnd_name_descs)
|
||||
|
||||
# Test the slice at 32:56 which is marker=31 limit=24
|
||||
params.update({'marker': all_lbs[31].get('id'), 'limit': '24'})
|
||||
resp = self.get(self.LBS_PATH, headers=self.headers, params=params)
|
||||
lbs = json.loads(resp.body)
|
||||
fnd_name_descs = [(lb['name'], lb['description']) for lb in lbs]
|
||||
self.assertEqual(exp_order[32:56], fnd_name_descs)
|
||||
|
||||
# Test the last 8 entries which is slice 56:64 marker=55 limit=8
|
||||
params.update({'marker': all_lbs[55].get('id'), 'limit': '8'})
|
||||
resp = self.get(self.LBS_PATH, headers=self.headers, params=params)
|
||||
lbs = json.loads(resp.body)
|
||||
fnd_name_descs = [(lb['name'], lb['description']) for lb in lbs]
|
||||
self.assertEqual(exp_order[56:64], fnd_name_descs)
|
||||
|
||||
# Test that we don't get an overflow or some other error if
|
||||
# the number of entries is less then the limit.
|
||||
# This should only return 4 entries
|
||||
params.update({'marker': all_lbs[59].get('id'), 'limit': '8'})
|
||||
resp = self.get(self.LBS_PATH, headers=self.headers, params=params)
|
||||
lbs = json.loads(resp.body)
|
||||
fnd_name_descs = [(lb['name'], lb['description']) for lb in lbs]
|
||||
self.assertEqual(exp_order[60:64], fnd_name_descs)
|
||||
|
||||
def test_listeners_sorting_and_pagination(self):
|
||||
# Create a loadbalancer and create 2 listeners on it
|
||||
lb = self.create_load_balancer(
|
||||
{'subnet_id': uuidutils.generate_uuid()}, name="single_lb")
|
||||
lb_id = lb['id']
|
||||
self.set_lb_status(lb_id)
|
||||
exp_desc_names = self.random_name_desc[30:40]
|
||||
exp_desc_names.sort(key=operator.itemgetter(0), reverse=True)
|
||||
exp_desc_names.sort(key=operator.itemgetter(1), reverse=True)
|
||||
|
||||
port = 0
|
||||
# We did some heavy testing already and the set_lb_status function
|
||||
# is recursive and leads to n*(n-1) iterations during this test so
|
||||
# we only test 10 entries
|
||||
for (name, description) in self.random_name_desc[30:40]:
|
||||
port += 1
|
||||
opts = {"name": name, "description": description}
|
||||
self.create_listener(lb_id, constants.PROTOCOL_HTTP, port, **opts)
|
||||
# Set the lb to active but don't recurse the child objects as
|
||||
# that will create a n*(n-1) operation in this loop
|
||||
self.set_lb_status(lb_id)
|
||||
|
||||
url = self.LISTENERS_PATH.format(lb_id=lb_id)
|
||||
params = {'sort': 'description:desc,name:desc',
|
||||
'project_id': self.project_id}
|
||||
|
||||
# Get all listeners
|
||||
resp = self.get(url, headers=self.headers, params=params)
|
||||
all_listeners = json.loads(resp.body)
|
||||
|
||||
# Test the slice at 3:6
|
||||
params.update({'marker': all_listeners[2].get('id'), 'limit': '3'})
|
||||
resp = self.get(url, headers=self.headers, params=params)
|
||||
listeners = json.loads(resp.body)
|
||||
fnd_name_desc = [(l['name'], l['description']) for l in listeners]
|
||||
self.assertEqual(exp_desc_names[3:6], fnd_name_desc)
|
||||
|
||||
# Test the slice at 1:8
|
||||
params.update({'marker': all_listeners[0].get('id'), 'limit': '7'})
|
||||
resp = self.get(url, headers=self.headers, params=params)
|
||||
listeners = json.loads(resp.body)
|
||||
fnd_name_desc = [(l['name'], l['description']) for l in listeners]
|
||||
self.assertEqual(exp_desc_names[1:8], fnd_name_desc)
|
||||
|
||||
def test_members_sorting_and_pagination(self):
|
||||
lb = self.create_load_balancer(
|
||||
{'subnet_id': uuidutils.generate_uuid()}, name="single_lb")
|
||||
lb_id = lb['id']
|
||||
self.set_lb_status(lb_id)
|
||||
li = self.create_listener(lb_id, constants.PROTOCOL_HTTP, 80)
|
||||
li_id = li['id']
|
||||
self.set_lb_status(lb_id)
|
||||
p = self.create_pool(lb_id, li_id, constants.PROTOCOL_HTTP,
|
||||
constants.LB_ALGORITHM_ROUND_ROBIN)
|
||||
self.set_lb_status(lb_id)
|
||||
pool_id = p['id']
|
||||
exp_ip_weights = [('127.0.0.4', 3), ('127.0.0.5', 1), ('127.0.0.2', 5),
|
||||
('127.0.0.1', 4), ('127.0.0.3', 2)]
|
||||
|
||||
for(ip, weight) in exp_ip_weights:
|
||||
self.create_member(lb_id, pool_id, ip, 80, weight=weight)
|
||||
self.set_lb_status(lb_id)
|
||||
|
||||
exp_ip_weights.sort(key=operator.itemgetter(1))
|
||||
exp_ip_weights.sort(key=operator.itemgetter(0))
|
||||
|
||||
url = self.MEMBERS_PATH.format(lb_id=lb_id, pool_id=pool_id)
|
||||
params = {'sort': 'ip_address,weight:asc',
|
||||
'project_id': self.project_id}
|
||||
|
||||
# Get all members
|
||||
resp = self.get(url, headers=self.headers, params=params)
|
||||
all_members = json.loads(resp.body)
|
||||
|
||||
# These tests are getting exhaustive -- just test marker=0 limit=2
|
||||
params.update({'marker': all_members[0].get('id'), 'limit': '2'})
|
||||
resp = self.get(url, headers=self.headers, params=params)
|
||||
members = json.loads(resp.body)
|
||||
fnd_ip_subs = [(m['ip_address'], m['weight']) for m in members]
|
||||
self.assertEqual(exp_ip_weights[1:3], fnd_ip_subs)
|
||||
|
||||
def test_invalid_limit(self):
|
||||
params = {'project_id': self.project_id,
|
||||
'limit': 'a'}
|
||||
|
||||
self.get(self.LBS_PATH, headers=self.headers, params=params,
|
||||
status=400)
|
||||
|
||||
def test_invalid_marker(self):
|
||||
params = {'project_id': self.project_id,
|
||||
'marker': 'not_a_valid_uuid'}
|
||||
|
||||
self.get(self.LBS_PATH, headers=self.headers, params=params,
|
||||
status=400)
|
||||
|
||||
def test_invalid_sort_key(self):
|
||||
params = {'sort': 'name:desc:asc',
|
||||
'project_id': self.project_id}
|
||||
|
||||
self.get(self.LBS_PATH, headers=self.headers, params=params,
|
||||
status=400)
|
||||
@@ -1,314 +0,0 @@
|
||||
# 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.
|
||||
|
||||
from oslo_utils import uuidutils
|
||||
|
||||
from octavia.common import constants
|
||||
from octavia.tests.functional.api.v1 import base
|
||||
|
||||
|
||||
class TestHealthMonitor(base.BaseAPITest):
|
||||
|
||||
def setUp(self):
|
||||
super(TestHealthMonitor, self).setUp()
|
||||
self.lb = self.create_load_balancer(
|
||||
{'subnet_id': uuidutils.generate_uuid()})
|
||||
self.set_lb_status(self.lb.get('id'))
|
||||
self.listener = self.create_listener(self.lb.get('id'),
|
||||
constants.PROTOCOL_HTTP, 80)
|
||||
self.set_lb_status(self.lb.get('id'))
|
||||
self.pool = self.create_pool_sans_listener(
|
||||
self.lb.get('id'), constants.PROTOCOL_HTTP,
|
||||
constants.LB_ALGORITHM_ROUND_ROBIN)
|
||||
self.set_lb_status(self.lb.get('id'))
|
||||
self.pool_with_listener = self.create_pool(
|
||||
self.lb.get('id'),
|
||||
self.listener.get('id'),
|
||||
constants.PROTOCOL_HTTP,
|
||||
constants.LB_ALGORITHM_ROUND_ROBIN)
|
||||
self.set_lb_status(self.lb.get('id'))
|
||||
self.hm_path = self.HM_PATH.format(lb_id=self.lb.get('id'),
|
||||
pool_id=self.pool.get('id'))
|
||||
self.deprecated_hm_path = self.DEPRECATED_HM_PATH.format(
|
||||
lb_id=self.lb.get('id'), listener_id=self.listener.get('id'),
|
||||
pool_id=self.pool_with_listener.get('id'))
|
||||
|
||||
def test_get(self):
|
||||
api_hm = self.create_health_monitor(self.lb.get('id'),
|
||||
self.pool.get('id'),
|
||||
constants.HEALTH_MONITOR_HTTP,
|
||||
1, 1, 1, 1)
|
||||
self.set_lb_status(lb_id=self.lb.get('id'))
|
||||
response = self.get(self.hm_path)
|
||||
response_body = response.json
|
||||
self.assertEqual(api_hm, response_body)
|
||||
|
||||
def test_bad_get(self):
|
||||
self.get(self.hm_path, status=404)
|
||||
|
||||
def test_create_sans_listener(self):
|
||||
api_hm = self.create_health_monitor(self.lb.get('id'),
|
||||
self.pool.get('id'),
|
||||
constants.HEALTH_MONITOR_HTTP,
|
||||
1, 1, 1, 1)
|
||||
self.assert_correct_lb_status(self.lb.get('id'),
|
||||
constants.PENDING_UPDATE,
|
||||
constants.ONLINE)
|
||||
self.assert_correct_listener_status(self.lb.get('id'),
|
||||
self.listener.get('id'),
|
||||
constants.ACTIVE,
|
||||
constants.ONLINE)
|
||||
self.set_lb_status(self.lb.get('id'))
|
||||
self.assertEqual(constants.HEALTH_MONITOR_HTTP, api_hm.get('type'))
|
||||
self.assertEqual(1, api_hm.get('delay'))
|
||||
self.assertEqual(1, api_hm.get('timeout'))
|
||||
self.assertEqual(1, api_hm.get('fall_threshold'))
|
||||
self.assertEqual(1, api_hm.get('rise_threshold'))
|
||||
self.assert_correct_lb_status(self.lb.get('id'),
|
||||
constants.ACTIVE,
|
||||
constants.ONLINE)
|
||||
self.assert_correct_listener_status(self.lb.get('id'),
|
||||
self.listener.get('id'),
|
||||
constants.ACTIVE, constants.ONLINE)
|
||||
|
||||
def test_create_with_listener(self):
|
||||
api_hm = self.create_health_monitor_with_listener(
|
||||
self.lb.get('id'), self.listener.get('id'),
|
||||
self.pool_with_listener.get('id'),
|
||||
constants.HEALTH_MONITOR_HTTP, 1, 1, 1, 1)
|
||||
self.assert_correct_lb_status(self.lb.get('id'),
|
||||
constants.PENDING_UPDATE,
|
||||
constants.ONLINE)
|
||||
self.assert_correct_listener_status(self.lb.get('id'),
|
||||
self.listener.get('id'),
|
||||
constants.PENDING_UPDATE,
|
||||
constants.ONLINE)
|
||||
self.set_lb_status(self.lb.get('id'))
|
||||
self.assertEqual(constants.HEALTH_MONITOR_HTTP, api_hm.get('type'))
|
||||
self.assertEqual(1, api_hm.get('delay'))
|
||||
self.assertEqual(1, api_hm.get('timeout'))
|
||||
self.assertEqual(1, api_hm.get('fall_threshold'))
|
||||
self.assertEqual(1, api_hm.get('rise_threshold'))
|
||||
# Verify optional field defaults
|
||||
self.assertEqual('GET', api_hm.get('http_method'))
|
||||
self.assertEqual('/', api_hm.get('url_path'))
|
||||
self.assertEqual('200', api_hm.get('expected_codes'))
|
||||
|
||||
self.assert_correct_lb_status(self.lb.get('id'),
|
||||
constants.ACTIVE,
|
||||
constants.ONLINE)
|
||||
self.assert_correct_listener_status(self.lb.get('id'),
|
||||
self.listener.get('id'),
|
||||
constants.ACTIVE, constants.ONLINE)
|
||||
|
||||
def test_create_with_project_id(self):
|
||||
pid = uuidutils.generate_uuid()
|
||||
api_hm = self.create_health_monitor(self.lb.get('id'),
|
||||
self.pool.get('id'),
|
||||
constants.HEALTH_MONITOR_HTTP,
|
||||
1, 1, 1, 1, project_id=pid)
|
||||
self.assertEqual(self.project_id, api_hm.get('project_id'))
|
||||
|
||||
def test_create_over_quota(self):
|
||||
self.check_quota_met_true_mock.start()
|
||||
self.addCleanup(self.check_quota_met_true_mock.stop)
|
||||
self.post(self.hm_path,
|
||||
body={'type': constants.HEALTH_MONITOR_HTTP,
|
||||
'delay': 1, 'timeout': 1, 'fall_threshold': 1,
|
||||
'rise_threshold': 1, 'project_id': self.project_id},
|
||||
status=403)
|
||||
|
||||
def test_bad_create(self):
|
||||
hm_json = {'name': 'test1'}
|
||||
self.post(self.deprecated_hm_path, hm_json, status=400)
|
||||
self.assert_correct_lb_status(self.lb.get('id'),
|
||||
constants.ACTIVE,
|
||||
constants.ONLINE)
|
||||
self.assert_correct_listener_status(self.lb.get('id'),
|
||||
self.listener.get('id'),
|
||||
constants.ACTIVE, constants.ONLINE)
|
||||
|
||||
def test_create_with_bad_handler(self):
|
||||
self.handler_mock().health_monitor.create.side_effect = Exception()
|
||||
self.create_health_monitor_with_listener(
|
||||
self.lb.get('id'), self.listener.get('id'),
|
||||
self.pool_with_listener.get('id'),
|
||||
constants.HEALTH_MONITOR_HTTP, 1, 1, 1, 1)
|
||||
self.assert_correct_lb_status(self.lb.get('id'),
|
||||
constants.PENDING_UPDATE,
|
||||
constants.ONLINE)
|
||||
self.assert_correct_listener_status(self.lb.get('id'),
|
||||
self.listener.get('id'),
|
||||
constants.PENDING_UPDATE,
|
||||
constants.ERROR)
|
||||
|
||||
def test_duplicate_create(self):
|
||||
api_hm = self.create_health_monitor(self.lb.get('id'),
|
||||
self.pool.get('id'),
|
||||
constants.HEALTH_MONITOR_HTTP,
|
||||
1, 1, 1, 1)
|
||||
self.set_lb_status(lb_id=self.lb.get('id'))
|
||||
self.post(self.hm_path, api_hm, status=409)
|
||||
|
||||
def test_update(self):
|
||||
self.create_health_monitor_with_listener(
|
||||
self.lb.get('id'), self.listener.get('id'),
|
||||
self.pool_with_listener.get('id'),
|
||||
constants.HEALTH_MONITOR_HTTP, 1, 1, 1, 1)
|
||||
self.set_lb_status(lb_id=self.lb.get('id'))
|
||||
new_hm = {'type': constants.HEALTH_MONITOR_HTTPS}
|
||||
self.put(self.deprecated_hm_path, new_hm)
|
||||
self.assert_correct_lb_status(self.lb.get('id'),
|
||||
constants.PENDING_UPDATE,
|
||||
constants.ONLINE)
|
||||
self.assert_correct_listener_status(self.lb.get('id'),
|
||||
self.listener.get('id'),
|
||||
constants.PENDING_UPDATE,
|
||||
constants.ONLINE)
|
||||
self.set_lb_status(self.lb.get('id'))
|
||||
self.assert_correct_lb_status(self.lb.get('id'),
|
||||
constants.ACTIVE,
|
||||
constants.ONLINE)
|
||||
self.assert_correct_listener_status(self.lb.get('id'),
|
||||
self.listener.get('id'),
|
||||
constants.ACTIVE, constants.ONLINE)
|
||||
|
||||
def test_bad_update(self):
|
||||
self.skip("This test will need reviewed after a validation layer is "
|
||||
"built")
|
||||
self.create_health_monitor(self.lb.get('id'),
|
||||
self.pool.get('id'),
|
||||
constants.HEALTH_MONITOR_HTTP,
|
||||
1, 1, 1, 1)
|
||||
new_hm = {'type': 'bad_type', 'delay': 2}
|
||||
self.set_lb_status(self.lb.get('id'))
|
||||
self.put(self.hm_path, new_hm, status=400)
|
||||
self.assert_correct_lb_status(self.lb.get('id'),
|
||||
constants.ACTIVE,
|
||||
constants.ONLINE)
|
||||
self.assert_correct_listener_status(self.lb.get('id'),
|
||||
self.listener.get('id'),
|
||||
constants.ACTIVE, constants.ONLINE)
|
||||
|
||||
def test_update_with_bad_handler(self):
|
||||
self.create_health_monitor_with_listener(
|
||||
self.lb.get('id'), self.listener.get('id'),
|
||||
self.pool_with_listener.get('id'),
|
||||
constants.HEALTH_MONITOR_HTTP, 1, 1, 1, 1)
|
||||
self.set_lb_status(lb_id=self.lb.get('id'))
|
||||
new_hm = {'type': constants.HEALTH_MONITOR_HTTPS}
|
||||
self.handler_mock().health_monitor.update.side_effect = Exception()
|
||||
self.put(self.deprecated_hm_path, new_hm)
|
||||
self.assert_correct_lb_status(self.lb.get('id'),
|
||||
constants.PENDING_UPDATE,
|
||||
constants.ONLINE)
|
||||
self.assert_correct_listener_status(self.lb.get('id'),
|
||||
self.listener.get('id'),
|
||||
constants.PENDING_UPDATE,
|
||||
constants.ERROR)
|
||||
|
||||
def test_delete(self):
|
||||
api_hm = self.create_health_monitor_with_listener(
|
||||
self.lb.get('id'), self.listener.get('id'),
|
||||
self.pool_with_listener.get('id'),
|
||||
constants.HEALTH_MONITOR_HTTP, 1, 1, 1, 1)
|
||||
self.set_lb_status(lb_id=self.lb.get('id'))
|
||||
response = self.get(self.deprecated_hm_path)
|
||||
self.assertEqual(api_hm, response.json)
|
||||
self.delete(self.deprecated_hm_path)
|
||||
self.assert_correct_lb_status(self.lb.get('id'),
|
||||
constants.PENDING_UPDATE,
|
||||
constants.ONLINE)
|
||||
self.assert_correct_listener_status(self.lb.get('id'),
|
||||
self.listener.get('id'),
|
||||
constants.PENDING_UPDATE,
|
||||
constants.ONLINE)
|
||||
self.set_lb_status(self.lb.get('id'))
|
||||
self.assert_correct_lb_status(self.lb.get('id'),
|
||||
constants.ACTIVE,
|
||||
constants.ONLINE)
|
||||
self.assert_correct_listener_status(self.lb.get('id'),
|
||||
self.listener.get('id'),
|
||||
constants.ACTIVE, constants.ONLINE)
|
||||
|
||||
def test_bad_delete(self):
|
||||
self.delete(self.hm_path, status=404)
|
||||
|
||||
def test_delete_with_bad_handler(self):
|
||||
api_hm = self.create_health_monitor_with_listener(
|
||||
self.lb.get('id'), self.listener.get('id'),
|
||||
self.pool_with_listener.get('id'),
|
||||
constants.HEALTH_MONITOR_HTTP, 1, 1, 1, 1)
|
||||
self.set_lb_status(lb_id=self.lb.get('id'))
|
||||
response = self.get(self.deprecated_hm_path)
|
||||
self.assertEqual(api_hm, response.json)
|
||||
self.handler_mock().health_monitor.delete.side_effect = Exception()
|
||||
self.delete(self.deprecated_hm_path)
|
||||
self.assert_correct_lb_status(self.lb.get('id'),
|
||||
constants.PENDING_UPDATE,
|
||||
constants.ONLINE)
|
||||
self.assert_correct_listener_status(self.lb.get('id'),
|
||||
self.listener.get('id'),
|
||||
constants.PENDING_UPDATE,
|
||||
constants.ERROR)
|
||||
|
||||
def test_create_when_lb_pending_update(self):
|
||||
self.put(self.LB_PATH.format(lb_id=self.lb.get('id')),
|
||||
body={'name': 'test_name_change'})
|
||||
self.post(self.hm_path,
|
||||
body={'type': constants.HEALTH_MONITOR_HTTP,
|
||||
'delay': 1, 'timeout': 1, 'fall_threshold': 1,
|
||||
'rise_threshold': 1, 'project_id': self.project_id},
|
||||
status=409)
|
||||
|
||||
def test_update_when_lb_pending_update(self):
|
||||
self.create_health_monitor(self.lb.get('id'), self.pool.get('id'),
|
||||
constants.HEALTH_MONITOR_HTTP, 1, 1, 1, 1)
|
||||
self.set_lb_status(self.lb.get('id'))
|
||||
self.put(self.LB_PATH.format(lb_id=self.lb.get('id')),
|
||||
body={'name': 'test_name_change'})
|
||||
self.put(self.hm_path, body={'rise_threshold': 2}, status=409)
|
||||
|
||||
def test_delete_when_lb_pending_update(self):
|
||||
self.create_health_monitor(self.lb.get('id'), self.pool.get('id'),
|
||||
constants.HEALTH_MONITOR_HTTP, 1, 1, 1, 1)
|
||||
self.set_lb_status(self.lb.get('id'))
|
||||
self.put(self.LB_PATH.format(lb_id=self.lb.get('id')),
|
||||
body={'name': 'test_name_change'})
|
||||
self.delete(self.hm_path, status=409)
|
||||
|
||||
def test_create_when_lb_pending_delete(self):
|
||||
self.delete(self.LB_DELETE_CASCADE_PATH.format(
|
||||
lb_id=self.lb.get('id')))
|
||||
self.post(self.hm_path,
|
||||
body={'type': constants.HEALTH_MONITOR_HTTP,
|
||||
'delay': 1, 'timeout': 1, 'fall_threshold': 1,
|
||||
'rise_threshold': 1, 'project_id': self.project_id},
|
||||
status=409)
|
||||
|
||||
def test_update_when_lb_pending_delete(self):
|
||||
self.create_health_monitor(self.lb.get('id'), self.pool.get('id'),
|
||||
constants.HEALTH_MONITOR_HTTP, 1, 1, 1, 1)
|
||||
self.set_lb_status(self.lb.get('id'))
|
||||
self.delete(self.LB_DELETE_CASCADE_PATH.format(
|
||||
lb_id=self.lb.get('id')))
|
||||
self.put(self.hm_path, body={'rise_threshold': 2}, status=409)
|
||||
|
||||
def test_delete_when_lb_pending_delete(self):
|
||||
self.create_health_monitor(self.lb.get('id'), self.pool.get('id'),
|
||||
constants.HEALTH_MONITOR_HTTP, 1, 1, 1, 1)
|
||||
self.set_lb_status(self.lb.get('id'))
|
||||
self.delete(self.LB_DELETE_CASCADE_PATH.format(
|
||||
lb_id=self.lb.get('id')))
|
||||
self.delete(self.hm_path, status=409)
|
||||
@@ -1,425 +0,0 @@
|
||||
# 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.
|
||||
|
||||
from oslo_utils import uuidutils
|
||||
|
||||
from octavia.common import constants
|
||||
from octavia.tests.functional.api.v1 import base
|
||||
|
||||
|
||||
class TestL7Policy(base.BaseAPITest):
|
||||
|
||||
def setUp(self):
|
||||
super(TestL7Policy, self).setUp()
|
||||
self.lb = self.create_load_balancer(
|
||||
{'subnet_id': uuidutils.generate_uuid()})
|
||||
self.set_lb_status(self.lb.get('id'))
|
||||
self.listener = self.create_listener(self.lb.get('id'),
|
||||
constants.PROTOCOL_HTTP, 80)
|
||||
self.set_lb_status(self.lb.get('id'))
|
||||
self.pool = self.create_pool_sans_listener(
|
||||
self.lb.get('id'),
|
||||
constants.PROTOCOL_HTTP,
|
||||
constants.LB_ALGORITHM_ROUND_ROBIN)
|
||||
self.set_lb_status(self.lb.get('id'))
|
||||
self.l7policies_path = self.L7POLICIES_PATH.format(
|
||||
lb_id=self.lb.get('id'), listener_id=self.listener.get('id'))
|
||||
self.l7policy_path = self.l7policies_path + '/{l7policy_id}'
|
||||
|
||||
def test_get(self):
|
||||
api_l7policy = self.create_l7policy(
|
||||
self.lb.get('id'), self.listener.get('id'),
|
||||
constants.L7POLICY_ACTION_REJECT)
|
||||
response = self.get(self.l7policy_path.format(
|
||||
l7policy_id=api_l7policy.get('id')))
|
||||
response_body = response.json
|
||||
self.assertEqual(api_l7policy, response_body)
|
||||
|
||||
def test_bad_get(self):
|
||||
self.get(self.l7policy_path.format(
|
||||
l7policy_id=uuidutils.generate_uuid()), status=404)
|
||||
|
||||
def test_get_all(self):
|
||||
api_l7p_a = self.create_l7policy(
|
||||
self.lb.get('id'), self.listener.get('id'),
|
||||
constants.L7POLICY_ACTION_REJECT)
|
||||
self.set_lb_status(self.lb.get('id'))
|
||||
api_l7p_c = self.create_l7policy(
|
||||
self.lb.get('id'), self.listener.get('id'),
|
||||
constants.L7POLICY_ACTION_REJECT)
|
||||
self.set_lb_status(self.lb.get('id'))
|
||||
api_l7p_b = self.create_l7policy(
|
||||
self.lb.get('id'), self.listener.get('id'),
|
||||
constants.L7POLICY_ACTION_REJECT, position=2)
|
||||
self.set_lb_status(self.lb.get('id'))
|
||||
# api_l7p_b was inserted before api_l7p_c
|
||||
api_l7p_c['position'] = 3
|
||||
response = self.get(self.l7policies_path)
|
||||
response_body = response.json
|
||||
self.assertIsInstance(response_body, list)
|
||||
self.assertEqual(3, len(response_body))
|
||||
self.assertEqual(api_l7p_a, response_body[0])
|
||||
self.assertEqual(api_l7p_b, response_body[1])
|
||||
self.assertEqual(api_l7p_c, response_body[2])
|
||||
|
||||
def test_empty_get_all(self):
|
||||
response = self.get(self.l7policies_path)
|
||||
response_body = response.json
|
||||
self.assertIsInstance(response_body, list)
|
||||
self.assertEqual(0, len(response_body))
|
||||
|
||||
def test_create_reject_policy(self):
|
||||
api_l7policy = self.create_l7policy(
|
||||
self.lb.get('id'), self.listener.get('id'),
|
||||
constants.L7POLICY_ACTION_REJECT)
|
||||
self.assertEqual(constants.L7POLICY_ACTION_REJECT,
|
||||
api_l7policy.get('action'))
|
||||
self.assertEqual(1, api_l7policy.get('position'))
|
||||
self.assertIsNone(api_l7policy.get('redirect_pool_id'))
|
||||
self.assertIsNone(api_l7policy.get('redirect_url'))
|
||||
self.assertTrue(api_l7policy.get('enabled'))
|
||||
self.assert_correct_lb_status(self.lb.get('id'),
|
||||
constants.PENDING_UPDATE,
|
||||
constants.ONLINE)
|
||||
self.assert_correct_listener_status(self.lb.get('id'),
|
||||
self.listener.get('id'),
|
||||
constants.PENDING_UPDATE,
|
||||
constants.ONLINE)
|
||||
self.set_lb_status(self.lb.get('id'))
|
||||
self.assert_correct_lb_status(self.lb.get('id'),
|
||||
constants.ACTIVE,
|
||||
constants.ONLINE)
|
||||
self.assert_correct_listener_status(self.lb.get('id'),
|
||||
self.listener.get('id'),
|
||||
constants.ACTIVE, constants.ONLINE)
|
||||
|
||||
def test_create_redirect_to_pool(self):
|
||||
api_l7policy = self.create_l7policy(
|
||||
self.lb.get('id'), self.listener.get('id'),
|
||||
constants.L7POLICY_ACTION_REDIRECT_TO_POOL,
|
||||
redirect_pool_id=self.pool.get('id'))
|
||||
self.assertEqual(constants.L7POLICY_ACTION_REDIRECT_TO_POOL,
|
||||
api_l7policy.get('action'))
|
||||
self.assertEqual(1, api_l7policy.get('position'))
|
||||
self.assertEqual(self.pool.get('id'),
|
||||
api_l7policy.get('redirect_pool_id'))
|
||||
self.assertIsNone(api_l7policy.get('redirect_url'))
|
||||
self.assertTrue(api_l7policy.get('enabled'))
|
||||
self.assert_correct_lb_status(self.lb.get('id'),
|
||||
constants.PENDING_UPDATE,
|
||||
constants.ONLINE)
|
||||
self.assert_correct_listener_status(self.lb.get('id'),
|
||||
self.listener.get('id'),
|
||||
constants.PENDING_UPDATE,
|
||||
constants.ONLINE)
|
||||
self.set_lb_status(self.lb.get('id'))
|
||||
self.assert_correct_lb_status(self.lb.get('id'),
|
||||
constants.ACTIVE,
|
||||
constants.ONLINE)
|
||||
self.assert_correct_listener_status(self.lb.get('id'),
|
||||
self.listener.get('id'),
|
||||
constants.ACTIVE, constants.ONLINE)
|
||||
|
||||
def test_create_redirect_to_url(self):
|
||||
api_l7policy = self.create_l7policy(
|
||||
self.lb.get('id'), self.listener.get('id'),
|
||||
constants.L7POLICY_ACTION_REDIRECT_TO_URL,
|
||||
redirect_url='http://www.example.com')
|
||||
self.assertEqual(constants.L7POLICY_ACTION_REDIRECT_TO_URL,
|
||||
api_l7policy.get('action'))
|
||||
self.assertEqual(1, api_l7policy.get('position'))
|
||||
self.assertIsNone(api_l7policy.get('redirect_pool_id'))
|
||||
self.assertEqual('http://www.example.com',
|
||||
api_l7policy.get('redirect_url'))
|
||||
self.assertTrue(api_l7policy.get('enabled'))
|
||||
self.assert_correct_lb_status(self.lb.get('id'),
|
||||
constants.PENDING_UPDATE,
|
||||
constants.ONLINE)
|
||||
self.assert_correct_listener_status(self.lb.get('id'),
|
||||
self.listener.get('id'),
|
||||
constants.PENDING_UPDATE,
|
||||
constants.ONLINE)
|
||||
self.set_lb_status(self.lb.get('id'))
|
||||
self.assert_correct_lb_status(self.lb.get('id'),
|
||||
constants.ACTIVE,
|
||||
constants.ONLINE)
|
||||
self.assert_correct_listener_status(self.lb.get('id'),
|
||||
self.listener.get('id'),
|
||||
constants.ACTIVE, constants.ONLINE)
|
||||
|
||||
def test_create_with_id(self):
|
||||
l7p_id = uuidutils.generate_uuid()
|
||||
api_l7policy = self.create_l7policy(
|
||||
self.lb.get('id'), self.listener.get('id'),
|
||||
constants.L7POLICY_ACTION_REJECT, id=l7p_id)
|
||||
self.assertEqual(l7p_id, api_l7policy.get('id'))
|
||||
|
||||
def test_create_with_duplicate_id(self):
|
||||
l7policy = self.create_l7policy(
|
||||
self.lb.get('id'), self.listener.get('id'),
|
||||
constants.L7POLICY_ACTION_REJECT)
|
||||
self.set_lb_status(self.lb.get('id'), constants.ACTIVE)
|
||||
path = self.L7POLICIES_PATH.format(lb_id=self.lb.get('id'),
|
||||
listener_id=self.listener.get('id'))
|
||||
body = {'id': l7policy.get('id'),
|
||||
'action': constants.L7POLICY_ACTION_REJECT}
|
||||
self.post(path, body, status=409)
|
||||
|
||||
def test_bad_create(self):
|
||||
l7policy = {'name': 'test1'}
|
||||
self.post(self.l7policies_path, l7policy, status=400)
|
||||
|
||||
def test_bad_create_redirect_to_pool(self):
|
||||
l7policy = {'action': constants.L7POLICY_ACTION_REDIRECT_TO_POOL,
|
||||
'redirect_pool_id': uuidutils.generate_uuid()}
|
||||
self.post(self.l7policies_path, l7policy, status=404)
|
||||
|
||||
def test_bad_create_redirect_to_url(self):
|
||||
l7policy = {'action': constants.L7POLICY_ACTION_REDIRECT_TO_URL,
|
||||
'redirect_url': 'bad url'}
|
||||
self.post(self.l7policies_path, l7policy, status=400)
|
||||
|
||||
def test_create_with_bad_handler(self):
|
||||
self.handler_mock().l7policy.create.side_effect = Exception()
|
||||
self.create_l7policy(
|
||||
self.lb.get('id'), self.listener.get('id'),
|
||||
constants.L7POLICY_ACTION_REJECT)
|
||||
self.assert_correct_lb_status(self.lb.get('id'),
|
||||
constants.PENDING_UPDATE,
|
||||
constants.ONLINE)
|
||||
self.assert_correct_listener_status(self.lb.get('id'),
|
||||
self.listener.get('id'),
|
||||
constants.PENDING_UPDATE,
|
||||
constants.ERROR)
|
||||
|
||||
def test_update(self):
|
||||
api_l7policy = self.create_l7policy(
|
||||
self.lb.get('id'), self.listener.get('id'),
|
||||
constants.L7POLICY_ACTION_REJECT)
|
||||
self.set_lb_status(self.lb.get('id'))
|
||||
new_l7policy = {'action': constants.L7POLICY_ACTION_REDIRECT_TO_URL,
|
||||
'redirect_url': 'http://www.example.com'}
|
||||
response = self.put(self.l7policy_path.format(
|
||||
l7policy_id=api_l7policy.get('id')), new_l7policy, status=202)
|
||||
self.assert_correct_lb_status(self.lb.get('id'),
|
||||
constants.PENDING_UPDATE,
|
||||
constants.ONLINE)
|
||||
self.assert_correct_listener_status(self.lb.get('id'),
|
||||
self.listener.get('id'),
|
||||
constants.PENDING_UPDATE,
|
||||
constants.ONLINE)
|
||||
self.set_lb_status(self.lb.get('id'))
|
||||
response_body = response.json
|
||||
self.assertEqual(constants.L7POLICY_ACTION_REJECT,
|
||||
response_body.get('action'))
|
||||
self.assert_correct_lb_status(self.lb.get('id'),
|
||||
constants.ACTIVE,
|
||||
constants.ONLINE)
|
||||
self.assert_correct_listener_status(self.lb.get('id'),
|
||||
self.listener.get('id'),
|
||||
constants.ACTIVE, constants.ONLINE)
|
||||
|
||||
def test_bad_update(self):
|
||||
api_l7policy = self.create_l7policy(
|
||||
self.lb.get('id'), self.listener.get('id'),
|
||||
constants.L7POLICY_ACTION_REJECT)
|
||||
new_l7policy = {'action': 'bad action'}
|
||||
self.put(self.l7policy_path.format(l7policy_id=api_l7policy.get('id')),
|
||||
new_l7policy, status=400)
|
||||
|
||||
def test_bad_update_redirect_to_pool(self):
|
||||
api_l7policy = self.create_l7policy(
|
||||
self.lb.get('id'), self.listener.get('id'),
|
||||
constants.L7POLICY_ACTION_REJECT)
|
||||
new_l7policy = {'action': constants.L7POLICY_ACTION_REDIRECT_TO_POOL,
|
||||
'redirect_pool_id': uuidutils.generate_uuid()}
|
||||
self.put(self.l7policy_path.format(l7policy_id=api_l7policy.get('id')),
|
||||
new_l7policy, status=404)
|
||||
|
||||
def test_bad_update_redirect_to_url(self):
|
||||
api_l7policy = self.create_l7policy(
|
||||
self.lb.get('id'), self.listener.get('id'),
|
||||
constants.L7POLICY_ACTION_REJECT)
|
||||
new_l7policy = {'action': constants.L7POLICY_ACTION_REDIRECT_TO_URL,
|
||||
'redirect_url': 'bad url'}
|
||||
self.put(self.l7policy_path.format(l7policy_id=api_l7policy.get('id')),
|
||||
new_l7policy, status=400)
|
||||
|
||||
def test_update_with_bad_handler(self):
|
||||
api_l7policy = self.create_l7policy(
|
||||
self.lb.get('id'), self.listener.get('id'),
|
||||
constants.L7POLICY_ACTION_REJECT)
|
||||
self.set_lb_status(self.lb.get('id'))
|
||||
new_l7policy = {'action': constants.L7POLICY_ACTION_REDIRECT_TO_URL,
|
||||
'redirect_url': 'http://www.example.com'}
|
||||
self.handler_mock().l7policy.update.side_effect = Exception()
|
||||
self.put(self.l7policy_path.format(
|
||||
l7policy_id=api_l7policy.get('id')), new_l7policy, status=202)
|
||||
self.assert_correct_lb_status(self.lb.get('id'),
|
||||
constants.PENDING_UPDATE,
|
||||
constants.ONLINE)
|
||||
self.assert_correct_listener_status(self.lb.get('id'),
|
||||
self.listener.get('id'),
|
||||
constants.PENDING_UPDATE,
|
||||
constants.ERROR)
|
||||
|
||||
def test_update_redirect_to_pool_bad_pool_id(self):
|
||||
api_l7policy = self.create_l7policy(
|
||||
self.lb.get('id'), self.listener.get('id'),
|
||||
constants.L7POLICY_ACTION_REJECT)
|
||||
self.set_lb_status(self.lb.get('id'))
|
||||
path = self.l7policy_path.format(l7policy_id=api_l7policy.get('id'))
|
||||
new_l7policy = {'redirect_pool_id': uuidutils.generate_uuid()}
|
||||
self.put(path, new_l7policy, status=404)
|
||||
|
||||
def test_update_redirect_to_pool_minimal(self):
|
||||
api_l7policy = self.create_l7policy(
|
||||
self.lb.get('id'), self.listener.get('id'),
|
||||
constants.L7POLICY_ACTION_REJECT)
|
||||
self.set_lb_status(self.lb.get('id'))
|
||||
path = self.l7policy_path.format(l7policy_id=api_l7policy.get('id'))
|
||||
new_l7policy = {'redirect_pool_id': self.pool.get('id')}
|
||||
self.put(path, new_l7policy, status=202)
|
||||
|
||||
def test_update_redirect_to_url_bad_url(self):
|
||||
api_l7policy = self.create_l7policy(
|
||||
self.lb.get('id'), self.listener.get('id'),
|
||||
constants.L7POLICY_ACTION_REJECT)
|
||||
self.set_lb_status(self.lb.get('id'))
|
||||
path = self.l7policy_path.format(l7policy_id=api_l7policy.get('id'))
|
||||
new_l7policy = {'redirect_url': 'bad-url'}
|
||||
self.put(path, new_l7policy, status=400)
|
||||
|
||||
def test_update_redirect_to_url_minimal(self):
|
||||
api_l7policy = self.create_l7policy(
|
||||
self.lb.get('id'), self.listener.get('id'),
|
||||
constants.L7POLICY_ACTION_REJECT)
|
||||
self.set_lb_status(self.lb.get('id'))
|
||||
path = self.l7policy_path.format(l7policy_id=api_l7policy.get('id'))
|
||||
new_l7policy = {'redirect_url': 'http://www.example.com/'}
|
||||
self.put(path, new_l7policy, status=202)
|
||||
|
||||
def test_delete(self):
|
||||
api_l7policy = self.create_l7policy(
|
||||
self.lb.get('id'), self.listener.get('id'),
|
||||
constants.L7POLICY_ACTION_REJECT)
|
||||
self.set_lb_status(self.lb.get('id'))
|
||||
response = self.get(self.l7policy_path.format(
|
||||
l7policy_id=api_l7policy.get('id')))
|
||||
self.assertEqual(api_l7policy, response.json)
|
||||
self.delete(self.l7policy_path.format(
|
||||
l7policy_id=api_l7policy.get('id')))
|
||||
self.assert_correct_lb_status(self.lb.get('id'),
|
||||
constants.PENDING_UPDATE,
|
||||
constants.ONLINE)
|
||||
self.assert_correct_listener_status(self.lb.get('id'),
|
||||
self.listener.get('id'),
|
||||
constants.PENDING_UPDATE,
|
||||
constants.ONLINE)
|
||||
self.set_lb_status(self.lb.get('id'))
|
||||
self.assert_correct_lb_status(self.lb.get('id'),
|
||||
constants.ACTIVE,
|
||||
constants.ONLINE)
|
||||
self.assert_correct_listener_status(self.lb.get('id'),
|
||||
self.listener.get('id'),
|
||||
constants.ACTIVE, constants.ONLINE)
|
||||
|
||||
def test_bad_delete(self):
|
||||
self.delete(self.l7policy_path.format(
|
||||
l7policy_id=uuidutils.generate_uuid()), status=404)
|
||||
|
||||
def test_delete_with_bad_handler(self):
|
||||
api_l7policy = self.create_l7policy(
|
||||
self.lb.get('id'), self.listener.get('id'),
|
||||
constants.L7POLICY_ACTION_REJECT)
|
||||
self.set_lb_status(self.lb.get('id'))
|
||||
response = self.get(self.l7policy_path.format(
|
||||
l7policy_id=api_l7policy.get('id')))
|
||||
self.assertEqual(api_l7policy, response.json)
|
||||
self.handler_mock().l7policy.delete.side_effect = Exception()
|
||||
self.delete(self.l7policy_path.format(
|
||||
l7policy_id=api_l7policy.get('id')))
|
||||
self.assert_correct_lb_status(self.lb.get('id'),
|
||||
constants.PENDING_UPDATE,
|
||||
constants.ONLINE)
|
||||
self.assert_correct_listener_status(self.lb.get('id'),
|
||||
self.listener.get('id'),
|
||||
constants.PENDING_UPDATE,
|
||||
constants.ERROR)
|
||||
|
||||
def test_create_when_lb_pending_update(self):
|
||||
self.create_l7policy(
|
||||
self.lb.get('id'), self.listener.get('id'),
|
||||
constants.L7POLICY_ACTION_REJECT)
|
||||
self.set_lb_status(self.lb.get('id'))
|
||||
self.put(self.LB_PATH.format(lb_id=self.lb.get('id')),
|
||||
body={'name': 'test_name_change'})
|
||||
new_l7policy = {'action': constants.L7POLICY_ACTION_REDIRECT_TO_URL,
|
||||
'redirect_url': 'http://www.example.com'}
|
||||
self.post(self.l7policies_path, body=new_l7policy, status=409)
|
||||
|
||||
def test_update_when_lb_pending_update(self):
|
||||
l7policy = self.create_l7policy(
|
||||
self.lb.get('id'), self.listener.get('id'),
|
||||
constants.L7POLICY_ACTION_REJECT)
|
||||
self.set_lb_status(self.lb.get('id'))
|
||||
self.put(self.LB_PATH.format(lb_id=self.lb.get('id')),
|
||||
body={'name': 'test_name_change'})
|
||||
new_l7policy = {'action': constants.L7POLICY_ACTION_REDIRECT_TO_URL,
|
||||
'redirect_url': 'http://www.example.com'}
|
||||
self.put(self.l7policy_path.format(l7policy_id=l7policy.get('id')),
|
||||
body=new_l7policy, status=409)
|
||||
|
||||
def test_delete_when_lb_pending_update(self):
|
||||
l7policy = self.create_l7policy(
|
||||
self.lb.get('id'), self.listener.get('id'),
|
||||
constants.L7POLICY_ACTION_REJECT)
|
||||
self.set_lb_status(self.lb.get('id'))
|
||||
self.put(self.LB_PATH.format(lb_id=self.lb.get('id')),
|
||||
body={'name': 'test_name_change'})
|
||||
self.delete(self.l7policy_path.format(l7policy_id=l7policy.get('id')),
|
||||
status=409)
|
||||
|
||||
def test_create_when_lb_pending_delete(self):
|
||||
self.create_l7policy(
|
||||
self.lb.get('id'), self.listener.get('id'),
|
||||
constants.L7POLICY_ACTION_REJECT)
|
||||
self.set_lb_status(self.lb.get('id'))
|
||||
self.delete(self.LB_DELETE_CASCADE_PATH.format(
|
||||
lb_id=self.lb.get('id')))
|
||||
new_l7policy = {'action': constants.L7POLICY_ACTION_REDIRECT_TO_URL,
|
||||
'redirect_url': 'http://www.example.com'}
|
||||
self.post(self.l7policies_path, body=new_l7policy, status=409)
|
||||
|
||||
def test_update_when_lb_pending_delete(self):
|
||||
l7policy = self.create_l7policy(
|
||||
self.lb.get('id'), self.listener.get('id'),
|
||||
constants.L7POLICY_ACTION_REJECT)
|
||||
self.set_lb_status(self.lb.get('id'))
|
||||
self.delete(self.LB_DELETE_CASCADE_PATH.format(
|
||||
lb_id=self.lb.get('id')))
|
||||
new_l7policy = {'action': constants.L7POLICY_ACTION_REDIRECT_TO_URL,
|
||||
'redirect_url': 'http://www.example.com'}
|
||||
self.put(self.l7policy_path.format(l7policy_id=l7policy.get('id')),
|
||||
body=new_l7policy, status=409)
|
||||
|
||||
def test_delete_when_lb_pending_delete(self):
|
||||
l7policy = self.create_l7policy(
|
||||
self.lb.get('id'), self.listener.get('id'),
|
||||
constants.L7POLICY_ACTION_REJECT)
|
||||
self.set_lb_status(self.lb.get('id'))
|
||||
self.delete(self.LB_DELETE_CASCADE_PATH.format(
|
||||
lb_id=self.lb.get('id')))
|
||||
self.delete(self.l7policy_path.format(l7policy_id=l7policy.get('id')),
|
||||
status=409)
|
||||
@@ -1,499 +0,0 @@
|
||||
# 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.
|
||||
|
||||
from oslo_utils import uuidutils
|
||||
|
||||
from octavia.common import constants
|
||||
from octavia.tests.functional.api.v1 import base
|
||||
|
||||
|
||||
class TestL7Rule(base.BaseAPITest):
|
||||
|
||||
def setUp(self):
|
||||
super(TestL7Rule, self).setUp()
|
||||
self.lb = self.create_load_balancer(
|
||||
{'subnet_id': uuidutils.generate_uuid()})
|
||||
self.set_lb_status(self.lb.get('id'))
|
||||
self.listener = self.create_listener(self.lb.get('id'),
|
||||
constants.PROTOCOL_HTTP, 80)
|
||||
self.set_lb_status(self.lb.get('id'))
|
||||
self.l7policy = self.create_l7policy(
|
||||
self.lb.get('id'), self.listener.get('id'),
|
||||
constants.L7POLICY_ACTION_REJECT)
|
||||
self.set_lb_status(self.lb.get('id'))
|
||||
self.l7rules_path = self.L7RULES_PATH.format(
|
||||
lb_id=self.lb.get('id'), listener_id=self.listener.get('id'),
|
||||
l7policy_id=self.l7policy.get('id'))
|
||||
self.l7rule_path = self.l7rules_path + '/{l7rule_id}'
|
||||
|
||||
def test_get(self):
|
||||
l7rule = self.create_l7rule(
|
||||
self.lb.get('id'), self.listener.get('id'),
|
||||
self.l7policy.get('id'), constants.L7RULE_TYPE_PATH,
|
||||
constants.L7RULE_COMPARE_TYPE_STARTS_WITH, '/api')
|
||||
response = self.get(self.l7rule_path.format(
|
||||
l7rule_id=l7rule.get('id')))
|
||||
response_body = response.json
|
||||
self.assertEqual(l7rule, response_body)
|
||||
|
||||
def test_get_bad_parent_policy(self):
|
||||
bad_path = (self.L7RULES_PATH.format(
|
||||
lb_id=self.lb.get('id'), listener_id=self.listener.get('id'),
|
||||
l7policy_id=uuidutils.generate_uuid()) + '/' +
|
||||
uuidutils.generate_uuid())
|
||||
self.get(bad_path, status=404)
|
||||
|
||||
def test_bad_get(self):
|
||||
self.get(self.l7rule_path.format(
|
||||
l7rule_id=uuidutils.generate_uuid()), status=404)
|
||||
|
||||
def test_get_all(self):
|
||||
api_l7r_a = self.create_l7rule(
|
||||
self.lb.get('id'), self.listener.get('id'),
|
||||
self.l7policy.get('id'), constants.L7RULE_TYPE_PATH,
|
||||
constants.L7RULE_COMPARE_TYPE_STARTS_WITH, '/api')
|
||||
self.set_lb_status(self.lb.get('id'))
|
||||
api_l7r_b = self.create_l7rule(
|
||||
self.lb.get('id'), self.listener.get('id'),
|
||||
self.l7policy.get('id'), constants.L7RULE_TYPE_PATH,
|
||||
constants.L7RULE_COMPARE_TYPE_STARTS_WITH, '/images')
|
||||
self.set_lb_status(self.lb.get('id'))
|
||||
response = self.get(self.l7rules_path)
|
||||
response_body = response.json
|
||||
self.assertIsInstance(response_body, list)
|
||||
self.assertEqual(2, len(response_body))
|
||||
self.assertIn(api_l7r_a, response_body)
|
||||
self.assertIn(api_l7r_b, response_body)
|
||||
|
||||
def test_empty_get_all(self):
|
||||
response = self.get(self.l7rules_path)
|
||||
response_body = response.json
|
||||
self.assertIsInstance(response_body, list)
|
||||
self.assertEqual(0, len(response_body))
|
||||
|
||||
def test_create_host_name_rule(self):
|
||||
l7rule = self.create_l7rule(
|
||||
self.lb.get('id'), self.listener.get('id'),
|
||||
self.l7policy.get('id'), constants.L7RULE_TYPE_HOST_NAME,
|
||||
constants.L7RULE_COMPARE_TYPE_EQUAL_TO, 'www.example.com')
|
||||
self.assertEqual(constants.L7RULE_TYPE_HOST_NAME, l7rule.get('type'))
|
||||
self.assertEqual(constants.L7RULE_COMPARE_TYPE_EQUAL_TO,
|
||||
l7rule.get('compare_type'))
|
||||
self.assertEqual('www.example.com', l7rule.get('value'))
|
||||
self.assertIsNone(l7rule.get('key'))
|
||||
self.assertFalse(l7rule.get('invert'))
|
||||
self.assert_correct_lb_status(self.lb.get('id'),
|
||||
constants.PENDING_UPDATE,
|
||||
constants.ONLINE)
|
||||
self.assert_correct_listener_status(self.lb.get('id'),
|
||||
self.listener.get('id'),
|
||||
constants.PENDING_UPDATE,
|
||||
constants.ONLINE)
|
||||
self.set_lb_status(self.lb.get('id'))
|
||||
self.assert_correct_lb_status(self.lb.get('id'),
|
||||
constants.ACTIVE,
|
||||
constants.ONLINE)
|
||||
self.assert_correct_listener_status(self.lb.get('id'),
|
||||
self.listener.get('id'),
|
||||
constants.ACTIVE, constants.ONLINE)
|
||||
|
||||
def test_create_path_rule(self):
|
||||
l7rule = self.create_l7rule(
|
||||
self.lb.get('id'), self.listener.get('id'),
|
||||
self.l7policy.get('id'), constants.L7RULE_TYPE_PATH,
|
||||
constants.L7RULE_COMPARE_TYPE_STARTS_WITH, '/api',
|
||||
invert=True)
|
||||
self.assertEqual(constants.L7RULE_TYPE_PATH, l7rule.get('type'))
|
||||
self.assertEqual(constants.L7RULE_COMPARE_TYPE_STARTS_WITH,
|
||||
l7rule.get('compare_type'))
|
||||
self.assertEqual('/api', l7rule.get('value'))
|
||||
self.assertIsNone(l7rule.get('key'))
|
||||
self.assertTrue(l7rule.get('invert'))
|
||||
self.assert_correct_lb_status(self.lb.get('id'),
|
||||
constants.PENDING_UPDATE,
|
||||
constants.ONLINE)
|
||||
self.assert_correct_listener_status(self.lb.get('id'),
|
||||
self.listener.get('id'),
|
||||
constants.PENDING_UPDATE,
|
||||
constants.ONLINE)
|
||||
self.set_lb_status(self.lb.get('id'))
|
||||
self.assert_correct_lb_status(self.lb.get('id'),
|
||||
constants.ACTIVE,
|
||||
constants.ONLINE)
|
||||
self.assert_correct_listener_status(self.lb.get('id'),
|
||||
self.listener.get('id'),
|
||||
constants.ACTIVE, constants.ONLINE)
|
||||
|
||||
def test_create_file_type_rule(self):
|
||||
l7rule = self.create_l7rule(
|
||||
self.lb.get('id'), self.listener.get('id'),
|
||||
self.l7policy.get('id'), constants.L7RULE_TYPE_FILE_TYPE,
|
||||
constants.L7RULE_COMPARE_TYPE_REGEX, 'jpg|png')
|
||||
self.assertEqual(constants.L7RULE_TYPE_FILE_TYPE, l7rule.get('type'))
|
||||
self.assertEqual(constants.L7RULE_COMPARE_TYPE_REGEX,
|
||||
l7rule.get('compare_type'))
|
||||
self.assertEqual('jpg|png', l7rule.get('value'))
|
||||
self.assertIsNone(l7rule.get('key'))
|
||||
self.assertFalse(l7rule.get('invert'))
|
||||
self.assert_correct_lb_status(self.lb.get('id'),
|
||||
constants.PENDING_UPDATE,
|
||||
constants.ONLINE)
|
||||
self.assert_correct_listener_status(self.lb.get('id'),
|
||||
self.listener.get('id'),
|
||||
constants.PENDING_UPDATE,
|
||||
constants.ONLINE)
|
||||
self.set_lb_status(self.lb.get('id'))
|
||||
self.assert_correct_lb_status(self.lb.get('id'),
|
||||
constants.ACTIVE,
|
||||
constants.ONLINE)
|
||||
self.assert_correct_listener_status(self.lb.get('id'),
|
||||
self.listener.get('id'),
|
||||
constants.ACTIVE, constants.ONLINE)
|
||||
|
||||
def test_create_header_rule(self):
|
||||
l7rule = self.create_l7rule(
|
||||
self.lb.get('id'), self.listener.get('id'),
|
||||
self.l7policy.get('id'), constants.L7RULE_TYPE_HEADER,
|
||||
constants.L7RULE_COMPARE_TYPE_ENDS_WITH, '"some string"',
|
||||
key='Some-header')
|
||||
self.assertEqual(constants.L7RULE_TYPE_HEADER, l7rule.get('type'))
|
||||
self.assertEqual(constants.L7RULE_COMPARE_TYPE_ENDS_WITH,
|
||||
l7rule.get('compare_type'))
|
||||
self.assertEqual('"some string"', l7rule.get('value'))
|
||||
self.assertEqual('Some-header', l7rule.get('key'))
|
||||
self.assertFalse(l7rule.get('invert'))
|
||||
self.assert_correct_lb_status(self.lb.get('id'),
|
||||
constants.PENDING_UPDATE,
|
||||
constants.ONLINE)
|
||||
self.assert_correct_listener_status(self.lb.get('id'),
|
||||
self.listener.get('id'),
|
||||
constants.PENDING_UPDATE,
|
||||
constants.ONLINE)
|
||||
self.set_lb_status(self.lb.get('id'))
|
||||
self.assert_correct_lb_status(self.lb.get('id'),
|
||||
constants.ACTIVE,
|
||||
constants.ONLINE)
|
||||
self.assert_correct_listener_status(self.lb.get('id'),
|
||||
self.listener.get('id'),
|
||||
constants.ACTIVE, constants.ONLINE)
|
||||
|
||||
def test_create_cookie_rule(self):
|
||||
l7rule = self.create_l7rule(
|
||||
self.lb.get('id'), self.listener. | ||||