Fix multi-listener load balancers

Load balancers with multiple listeners, running on an amphora image
with HAProxy 1.8 or newer can experience excessive memory usage that
may lead to an ERROR provisioning_status.
This patch resolves this issue by consolidating the listeners into
a single haproxy process inside the amphora.

Conflicts:
    octavia/amphorae/backends/agent/api_server/loadbalancer.py
    octavia/amphorae/backends/agent/api_server/udp_listener_base.py
    octavia/amphorae/backends/agent/api_server/util.py
    octavia/amphorae/drivers/driver_base.py
    octavia/common/jinja/haproxy/combined_listeners/jinja_cfg.py
    octavia/common/jinja/haproxy/combined_listeners/templates/base.j2
    octavia/common/jinja/haproxy/combined_listeners/templates/macros.j2
    octavia/controller/worker/tasks/amphora_driver_tasks.py
    octavia/controller/worker/v1/flows/amphora_flows.py
    octavia/controller/worker/v1/flows/l7policy_flows.py
    octavia/controller/worker/v1/flows/listener_flows.py
    octavia/controller/worker/v1/flows/load_balancer_flows.py
    octavia/controller/worker/v1/flows/member_flows.py
    octavia/controller/worker/v1/flows/pool_flows.py
    octavia/controller/worker/v2/controller_worker.py
    octavia/controller/worker/v2/flows/health_monitor_flows.py
    octavia/controller/worker/v2/flows/l7rule_flows.py
    octavia/controller/worker/v2/tasks/amphora_driver_tasks.py
    octavia/tests/unit/common/jinja/haproxy/combined_listeners/test_jinja_cfg.py
    octavia/tests/unit/common/jinja/haproxy/split_listeners/test_jinja_cfg.py
    octavia/tests/unit/common/sample_configs/sample_configs_combined.py
    octavia/tests/unit/common/sample_configs/sample_configs_split.py
    octavia/tests/unit/controller/worker/v1/tasks/test_amphora_driver_tasks.py
    octavia/tests/unit/controller/worker/v2/flows/test_load_balancer_flows.py
    octavia/tests/unit/controller/worker/v2/test_controller_worker.py

Story: 2005412
Task: 34744
Co-Authored-By: Adam Harwell <flux.adam@gmail.com>
Change-Id: Idaccbcfa0126f1e26fbb3ad770c65c9266cfad5b
(cherry picked from commit 06ce4777c3)
This commit is contained in:
Michael Johnson 2019-06-27 16:00:22 -07:00 committed by Carlos Goncalves
parent 15358a71e4
commit 646071d852
67 changed files with 6615 additions and 2001 deletions

View File

@ -29,9 +29,7 @@ communication is limited to fail-over protocols.)
Versioning
----------
All Octavia APIs (including internal APIs like this one) are versioned. For the
purposes of this document, the initial version of this API shall be v0.5. (So,
any reference to a *:version* variable should be replaced with the literal
string 'v0.5'.)
purposes of this document, the initial version of this API shall be 1.0.
Response codes
--------------
@ -74,136 +72,6 @@ a secure way (ex. memory filesystem).
API
===
Get amphora topology
--------------------
* **URL:** /*:version*/topology
* **Method:** GET
* **URL params:** none
* **Data params:** none
* **Success Response:**
* Code: 200
* Content: JSON formatted listing of this amphora's configured topology.
* **Error Response:**
* none
JSON Response attributes:
* *hostname* - hostname of amphora
* *uuid* - uuid of amphora
* *topology* - One of: SINGLE, ACTIVE-STANDBY, ACTIVE-ACTIVE
* *role* - One of ACTIVE, STANDBY (only applicable to ACTIVE-STANDBY)
* *ha_ip* - only applicable to ACTIVE-STANDBY topology: Highly-available
routing IP address for the ACTIVE-STANDBY pair.
**Examples**
* Success code 200:
::
JSON response:
{
'hostname': 'octavia-haproxy-img-00328',
'uuid': '6e2bc8a0-2548-4fb7-a5f0-fb1ef4a696ce',
'topology': 'SINGLE',
'role': 'ACTIVE',
'ha_ip': '',
}
Set amphora topology
--------------------
* **URL:** /*:version*/topology
* **Method:** POST
* **URL params:** none
* **Data params:**
* *topology*: One of: SINGLE, ACTIVE-STANDBY, ACTIVE-ACTIVE
* *role*: One of: ACTIVE, STANDBY (only applicable to ACTIVE-STANDBY)
* *ha_ip*: (only applicable to ACTIVE-STANDBY) Highly-available IP for the
HA pair
* *secret*: (only applicable to ACTIVE-STANDBY topology) Shared secret used
for authentication with other HA pair member
* **Success Response:**
* Code: 200
* Content: OK
* Code: 202
* Content: OK
* **Error Response:**
* Code: 400
* Content: Invalid request.
* *(Response will also include information on which parameters did not*
*pass either a syntax check or other topology logic test)*
* Code: 503
* Content: Topology transition in progress
* **Response:**
| OK
**Notes:** In an ACTIVE-STANDBY configuration, the 'role' parameter might
change spontaneously due to a failure of one node. In other topologies, the
role is not used.
Also note that some topology changes can take several minutes to enact, yet
we want all API commands to return in a matter of seconds. In this case, a
topology change is initiated, and the amphora status changes from "OK" to
"TOPOLOGY-CHANGE". The controller should not try to change any resources during
this transition. (Any attempts will be met with an error.) Once the
topology change is complete, amphora status should return to "OK". (When the
UDP communication from amphorae to controller is defined, a 'transition
complete' message is probably one good candidate for this type of UDP
communication.)
**Examples**
* Success code 200:
::
JSON POST parameters:
{
'topology': 'ACTIVE-STANDBY',
'role': 'ACTIVE',
'ha_ip': ' 203.0.113.2',
'secret': 'b20e06cf1abcf29c708d3b437f4a29892a0921d0',
}
Response:
OK
* Error code 400:
::
Response:
{
'message': 'Invalid request',
'details': 'Unknown topology: BAD_TEST_DATA',
}
* Error code 503:
::
Response:
{
'message': 'Topology transition in progress',
}
Get amphora info
----------------
* **URL:** /info
@ -249,7 +117,7 @@ version string prepended to it.
Get amphora details
-------------------
* **URL:** /*:version*/details
* **URL:** /1.0/details
* **Method:** GET
* **URL params:** none
* **Data params:** none
@ -372,7 +240,7 @@ health of the amphora, currently-configured topology and role, etc.
Get interface
-------------
* **URL:** /*:version*/interface/*:ip*
* **URL:** /1.0/interface/*:ip*
* **Method:** GET
* **URL params:**
@ -408,7 +276,7 @@ Get interface
::
GET URL:
https://octavia-haproxy-img-00328.local/v0.5/interface/10.0.0.1
https://octavia-haproxy-img-00328.local/1.0/interface/10.0.0.1
JSON Response:
{
@ -422,7 +290,7 @@ Get interface
::
GET URL:
https://octavia-haproxy-img-00328.local/v0.5/interface/10.5.0.1
https://octavia-haproxy-img-00328.local/1.0/interface/10.5.0.1
JSON Response:
{
@ -435,7 +303,7 @@ Get interface
::
GET URL:
https://octavia-haproxy-img-00328.local/v0.5/interface/10.6.0.1.1
https://octavia-haproxy-img-00328.local/1.0/interface/10.6.0.1.1
JSON Response:
{
@ -446,7 +314,7 @@ Get interface
Get all listeners' statuses
---------------------------
* **URL:** /*:version*/listeners
* **URL:** /1.0/listeners
* **Method:** GET
* **URL params:** none
* **Data params:** none
@ -492,91 +360,14 @@ a valid haproxy configuration).
'type': 'TERMINATED_HTTPS',
}]
Get a listener's status
-----------------------
Start or Stop a load balancer
-----------------------------
* **URL:** /*:version*/listeners/*:listener*
* **Method:** GET
* **URL params:**
* *:listener* = Listener UUID
* **Data params:** none
* **Success Response:**
* Code: 200
* Content: JSON-formatted listener status
* **Error Response:**
* Code: 404
* Content: Not Found
JSON Response attributes:
* *status* - One of the operational status: ACTIVE, STOPPED, ERROR -
future versions might support provisioning status:
PENDING_CREATE, PENDING_UPDATE, PENDING_DELETE, DELETED
* *uuid* - Listener UUID
* *type* - One of: TCP, HTTP, TERMINATED_HTTPS
* *pools* - Map of pool UUIDs and their overall UP / DOWN / DEGRADED status
* *members* - Map of member UUIDs and their overall UP / DOWN status
**Notes:** Note that this returns a status if: the pid file exists,
the stats socket exists, or an haproxy configuration is present (not
just if there is a valid haproxy configuration).
**Examples**
* Success code 200:
::
JSON Response:
{
'status': 'ACTIVE',
'uuid': 'e2dfddc0-5b9e-11e4-8ed6-0800200c9a66',
'type': 'HTTP',
'pools':[
{
'uuid': '399bbf4b-5f6c-4370-a61e-ed2ff2fc9387',
'status': 'UP',
'members':[
{'73f6d278-ae1c-4248-ad02-0bfd50d69aab': 'UP'},
{'2edca57c-5890-4bcb-ae67-4ef75776cc67': 'DOWN'},
],
},
{
'uuid': '2250eb21-16ca-44bd-9b12-0b4eb3d18140',
'status': 'DOWN',
'members':[
{'130dff11-4aab-4ba8-a39b-8d77caa7a1ad': 'DOWN'},
],
},
],
}
* Error code 404:
::
JSON Response:
{
'message': 'Listener Not Found',
'details': 'No listener with UUID: 04bff5c3-5862-4a13-b9e3-9b440d0ed50a',
}
Start or Stop a listener
------------------------
* **URL:** /*:version*/listeners/*:listener*/*:action*
* **URL:** /1.0/loadbalancer/*:object_id*/*:action*
* **Method:** PUT
* **URL params:**
* *:listener* = Listener UUID
* *:object_id* = Object UUID
* *:action* = One of: start, stop, reload
* **Data params:** none
@ -612,7 +403,7 @@ Start or Stop a listener
| OK
| Configuration file is valid
| haproxy daemon for 7e9f91eb-b3e6-4e3b-a1a7-d6f7fdc1de7c started (pid 32428)
| haproxy daemon for 85e2111b-29c4-44be-94f3-e72045805801 started (pid 32428)
**Examples:**
@ -621,12 +412,12 @@ Start or Stop a listener
::
PUT URL:
https://octavia-haproxy-img-00328.local/v0.5/listeners/04bff5c3-5862-4a13-b9e3-9b440d0ed50a/start
https://octavia-haproxy-img-00328.local/1.0/loadbalancer/85e2111b-29c4-44be-94f3-e72045805801/start
JSON Response:
{
'message': 'OK',
'details': 'Configuration file is valid\nhaproxy daemon for 04bff5c3-5862-4a13-b9e3-9b440d0ed50a started',
'details': 'Configuration file is valid\nhaproxy daemon for 85e2111b-29c4-44be-94f3-e72045805801 started',
}
* Error code 400:
@ -634,7 +425,7 @@ Start or Stop a listener
::
PUT URL:
https://octavia-haproxy-img-00328.local/v0.5/listeners/04bff5c3-5862-4a13-b9e3-9b440d0ed50a/BAD_TEST_DATA
https://octavia-haproxy-img-00328.local/1.0/loadbalancer/85e2111b-29c4-44be-94f3-e72045805801/BAD_TEST_DATA
JSON Response:
{
@ -647,12 +438,12 @@ Start or Stop a listener
::
PUT URL:
https://octavia-haproxy-img-00328.local/v0.5/listeners/04bff5c3-5862-4a13-b9e3-9b440d0ed50a/stop
https://octavia-haproxy-img-00328.local/1.0/loadbalancer/04bff5c3-5862-4a13-b9e3-9b440d0ed50a/stop
JSON Response:
{
'message': 'Listener Not Found',
'details': 'No listener with UUID: 04bff5c3-5862-4a13-b9e3-9b440d0ed50a',
'details': 'No loadbalancer with UUID: 04bff5c3-5862-4a13-b9e3-9b440d0ed50a',
}
* Error code 500:
@ -660,7 +451,7 @@ Start or Stop a listener
::
PUT URL:
https://octavia-haproxy-img-00328.local/v0.5/listeners/04bff5c3-5862-4a13-b9e3-9b440d0ed50a/stop
https://octavia-haproxy-img-00328.local/1.0/loadbalancer/85e2111b-29c4-44be-94f3-e72045805801/stop
Response:
{
@ -680,7 +471,7 @@ Start or Stop a listener
Delete a listener
-----------------
* **URL:** /*:version*/listeners/*:listener*
* **URL:** /1.0/listeners/*:listener*
* **Method:** DELETE
* **URL params:**
@ -724,7 +515,7 @@ Delete a listener
::
DELETE URL:
https://octavia-haproxy-img-00328.local/v0.5/listeners/04bff5c3-5862-4a13-b9e3-9b440d0ed50a
https://octavia-haproxy-img-00328.local/1.0/listeners/04bff5c3-5862-4a13-b9e3-9b440d0ed50a
JSON Response:
{
@ -736,7 +527,7 @@ Delete a listener
::
DELETE URL:
https://octavia-haproxy-img-00328.local/v0.5/listeners/04bff5c3-5862-4a13-b9e3-9b440d0ed50a
https://octavia-haproxy-img-00328.local/1.0/listeners/04bff5c3-5862-4a13-b9e3-9b440d0ed50a
JSON Response:
{
@ -756,11 +547,11 @@ Delete a listener
Upload SSL certificate PEM file
-------------------------------
* **URL:** /*:version*/listeners/*:listener*/certificates/*:filename.pem*
* **URL:** /1.0/loadbalancer/*:loadbalancer_id*/certificates/*:filename.pem*
* **Method:** PUT
* **URL params:**
* *:listener* = Listener UUID
* *:loadbalancer_id* = Load balancer UUID
* *:filename* = PEM filename (see notes below for naming convention)
* **Data params:** Certificate data. (PEM file should be a concatenation of
@ -812,7 +603,7 @@ explicitly restarted
::
PUT URI:
https://octavia-haproxy-img-00328.local/v0.5/listeners/04bff5c3-5862-4a13-b9e3-9b440d0ed50a/certificates/www.example.com.pem
https://octavia-haproxy-img-00328.local/1.0/loadbalancer/85e2111b-29c4-44be-94f3-e72045805801/certificates/www.example.com.pem
(Put data should contain the certificate information, concatenated as
described above)
@ -826,7 +617,7 @@ explicitly restarted
::
PUT URI:
https://octavia-haproxy-img-00328.local/v0.5/listeners/04bff5c3-5862-4a13-b9e3-9b440d0ed50a/certificates/www.example.com.pem
https://octavia-haproxy-img-00328.local/1.0/loadbalancer/85e2111b-29c4-44be-94f3-e72045805801/certificates/www.example.com.pem
(If PUT data does not contain a certificate)
JSON Response:
@ -839,7 +630,7 @@ explicitly restarted
::
PUT URI:
https://octavia-haproxy-img-00328.local/v0.5/listeners/04bff5c3-5862-4a13-b9e3-9b440d0ed50a/certificates/www.example.com.pem
https://octavia-haproxy-img-00328.local/1.0/loadbalancer/85e2111b-29c4-44be-94f3-e72045805801/certificates/www.example.com.pem
(If PUT data does not contain an RSA key)
JSON Response:
@ -852,7 +643,7 @@ explicitly restarted
::
PUT URI:
https://octavia-haproxy-img-00328.local/v0.5/listeners/04bff5c3-5862-4a13-b9e3-9b440d0ed50a/certificates/www.example.com.pem
https://octavia-haproxy-img-00328.local/1.0/loadbalancer/85e2111b-29c4-44be-94f3-e72045805801/certificates/www.example.com.pem
(If the first certificate and the RSA key do not have the same modulus.)
JSON Response:
@ -865,15 +656,14 @@ explicitly restarted
::
PUT URI:
https://octavia-haproxy-img-00328.local/v0.5/listeners/04bff5c3-5862-4a13-b9e3-9b440d0ed50a/certificates/www.example.com.pem
https://octavia-haproxy-img-00328.local/1.0/loadbalancer/85e2111b-29c4-44be-94f3-e72045805801/certificates/www.example.com.pem
JSON Response:
{
'message': 'Listener Not Found',
'details': 'No listener with UUID: 04bff5c3-5862-4a13-b9e3-9b440d0ed50a',
'details': 'No loadbalancer with UUID: 04bff5c3-5862-4a13-b9e3-9b440d0ed50a',
}
* Error code 503:
::
@ -883,15 +673,14 @@ explicitly restarted
'message': 'Topology transition in progress',
}
Get SSL certificate md5sum
--------------------------
* **URL:** /*:version*/listeners/*:listener*/certificates/*:filename.pem*
* **URL:** /1.0/loadbalancer/*:loadbalancer_id*/certificates/*:filename.pem*
* **Method:** GET
* **URL params:**
* *:listener* = Listener UUID
* *:loadbalancer_id* = Load balancer UUID
* *:filename* = PEM filename (see notes below for naming convention)
* **Data params:** none
@ -937,7 +726,7 @@ disclosing it over the wire from the amphora is a security risk.
JSON Response:
{
'message': 'Listener Not Found',
'details': 'No listener with UUID: 04bff5c3-5862-4a13-b9e3-9b440d0ed50a',
'details': 'No loadbalancer with UUID: 04bff5c3-5862-4a13-b9e3-9b440d0ed50a',
}
* Error code 404:
@ -953,11 +742,11 @@ disclosing it over the wire from the amphora is a security risk.
Delete SSL certificate PEM file
-------------------------------
* **URL:** /*:version*/listeners/*:listener*/certificates/*:filename.pem*
* **URL:** /1.0/loadbalancer/*:loadbalancer_id*/certificates/*:filename.pem*
* **Method:** DELETE
* **URL params:**
* *:listener* = Listener UUID
* *:loadbalancer_id* = Load balancer UUID
* *:filename* = PEM filename (see notes below for naming convention)
* **Data params:** none
@ -988,7 +777,7 @@ Delete SSL certificate PEM file
::
DELETE URL:
https://octavia-haproxy-img-00328.local/v0.5/listeners/04bff5c3-5862-4a13-b9e3-9b440d0ed50a/certificates/www.example.com.pem
https://octavia-haproxy-img-00328.local/1.0/loadbalancer/85e2111b-29c4-44be-94f3-e72045805801/certificates/www.example.com.pem
JSON Response:
{
@ -1000,7 +789,7 @@ Delete SSL certificate PEM file
::
DELETE URL:
https://octavia-haproxy-img-00328.local/v0.5/listeners/04bff5c3-5862-4a13-b9e3-9b440d0ed50a/certificates/www.example.com.pem
https://octavia-haproxy-img-00328.local/1.0/loadbalancer/85e2111b-29c4-44be-94f3-e72045805801/certificates/www.example.com.pem
JSON Response:
{
@ -1017,14 +806,14 @@ Delete SSL certificate PEM file
'message': 'Topology transition in progress',
}
Upload listener haproxy configuration
-------------------------------------
Upload load balancer haproxy configuration
------------------------------------------
* **URL:** /*:version*/listeners/*:amphora_id*/*:listener*/haproxy
* **URL:** /1.0/loadbalancer/*:amphora_id*/*:loadbalancer_id*/haproxy
* **Method:** PUT
* **URL params:**
* *:listener* = Listener UUID
* *:loadbalancer_id* = Load Balancer UUID
* *:amphora_id* = Amphora UUID
* **Data params:** haproxy configuration file for the listener
@ -1071,7 +860,7 @@ out of the haproxy daemon status interface for tracking health and stats).
::
PUT URL:
https://octavia-haproxy-img-00328.local/v0.5/listeners/d459b1c8-54b0-4030-9bec-4f449e73b1ef/04bff5c3-5862-4a13-b9e3-9b440d0ed50a/haproxy
https://octavia-haproxy-img-00328.local/1.0/loadbalancer/d459b1c8-54b0-4030-9bec-4f449e73b1ef/85e2111b-29c4-44be-94f3-e72045805801/haproxy
(Upload PUT data should be a raw haproxy.conf file.)
JSON Response:
@ -1098,14 +887,14 @@ out of the haproxy daemon status interface for tracking health and stats).
'message': 'Topology transition in progress',
}
Get listener haproxy configuration
----------------------------------
Get loadbalancer haproxy configuration
--------------------------------------
* **URL:** /*:version*/listeners/*:listener*/haproxy
* **URL:** /1.0/loadbalancer/*:loadbalancer_id*/haproxy
* **Method:** GET
* **URL params:**
* *:listener* = Listener UUID
* *:loadbalancer_id* = Load balancer UUID
* **Data params:** none
* **Success Response:**
@ -1122,7 +911,7 @@ Get listener haproxy configuration
* **Response:**
| # Config file for 7e9f91eb-b3e6-4e3b-a1a7-d6f7fdc1de7c
| # Config file for 85e2111b-29c4-44be-94f3-e72045805801
| (cut for brevity)
* **Implied actions:** none
@ -1134,11 +923,11 @@ Get listener haproxy configuration
::
GET URL:
https://octavia-haproxy-img-00328.local/v0.5/listeners/7e9f91eb-b3e6-4e3b-a1a7-d6f7fdc1de7c/haproxy
https://octavia-haproxy-img-00328.local/1.0/loadbalancer/85e2111b-29c4-44be-94f3-e72045805801/haproxy
Response is the raw haproxy.cfg:
# Config file for 7e9f91eb-b3e6-4e3b-a1a7-d6f7fdc1de7c
# Config file for 85e2111b-29c4-44be-94f3-e72045805801
(cut for brevity)
* Error code 404:
@ -1147,15 +936,14 @@ Get listener haproxy configuration
JSON Response:
{
'message': 'Listener Not Found',
'details': 'No listener with UUID: 04bff5c3-5862-4a13-b9e3-9b440d0ed50a',
'message': 'Loadbalancer Not Found',
'details': 'No loadbalancer with UUID: 04bff5c3-5862-4a13-b9e3-9b440d0ed50a',
}
Plug VIP
--------
* **URL:** /*:version*/plug/vip/*:ip*
* **URL:** /1.0/plug/vip/*:ip*
* **Method:** Post
* **URL params:**
@ -1210,7 +998,7 @@ Plug VIP
::
POST URL:
https://octavia-haproxy-img-00328.local/v0.5/plug/vip/203.0.113.2
https://octavia-haproxy-img-00328.local/1.0/plug/vip/203.0.113.2
JSON POST parameters:
{
@ -1225,10 +1013,6 @@ Plug VIP
'details': 'VIP 203.0.113.2 plugged on interface eth1'
}
* Error code 400:
::
@ -1251,7 +1035,7 @@ Plug VIP
Plug Network
------------
* **URL:** /*:version*/plug/network/
* **URL:** /1.0/plug/network/
* **Method:** POST
* **URL params:** none
@ -1292,7 +1076,7 @@ Plug Network
::
POST URL:
https://octavia-haproxy-img-00328.local/v0.5/plug/network/
https://octavia-haproxy-img-00328.local/1.0/plug/network/
JSON POST parameters:
{
@ -1319,7 +1103,7 @@ Plug Network
Upload SSL server certificate PEM file for Controller Communication
-------------------------------------------------------------------
* **URL:** /*:version*/certificate
* **URL:** /1.0/certificate
* **Method:** PUT
* **Data params:** Certificate data. (PEM file should be a concatenation of
@ -1362,7 +1146,7 @@ not be available for some time.
::
PUT URI:
https://octavia-haproxy-img-00328.local/v0.5/certificate
https://octavia-haproxy-img-00328.local/1.0/certificate
(Put data should contain the certificate information, concatenated as
described above)
@ -1376,7 +1160,7 @@ not be available for some time.
::
PUT URI:
https://octavia-haproxy-img-00328.local/v0.5/certificates
https://octavia-haproxy-img-00328.local/1.0/certificates
(If PUT data does not contain a certificate)
JSON Response:
@ -1389,7 +1173,7 @@ not be available for some time.
::
PUT URI:
https://octavia-haproxy-img-00328.local/v0.5/certificate
https://octavia-haproxy-img-00328.local/1.0/certificate
(If PUT data does not contain an RSA key)
JSON Response:
@ -1402,7 +1186,7 @@ not be available for some time.
::
PUT URI:
https://octavia-haproxy-img-00328.local/v0.5/certificate
https://octavia-haproxy-img-00328.local/1.0/certificate
(If the first certificate and the RSA key do not have the same modulus.)
JSON Response:
@ -1414,7 +1198,7 @@ not be available for some time.
Upload keepalived configuration
-------------------------------
* **URL:** /*:version*/vrrp/upload
* **URL:** /1.0/vrrp/upload
* **Method:** PUT
* **URL params:** none
* **Data params:** none
@ -1441,7 +1225,7 @@ OK
::
PUT URI:
https://octavia-haproxy-img-00328.local/v0.5/vrrp/upload
https://octavia-haproxy-img-00328.local/1.0/vrrp/upload
JSON Response:
{
@ -1452,7 +1236,7 @@ OK
Start, Stop, or Reload keepalived
---------------------------------
* **URL:** /*:version*/vrrp/*:action*
* **URL:** /1.0/vrrp/*:action*
* **Method:** PUT
* **URL params:**
@ -1489,7 +1273,7 @@ Start, Stop, or Reload keepalived
::
PUT URL:
https://octavia-haproxy-img-00328.local/v0.5/vrrp/start
https://octavia-haproxy-img-00328.local/1.0/vrrp/start
JSON Response:
{
@ -1502,7 +1286,7 @@ Start, Stop, or Reload keepalived
::
PUT URL:
https://octavia-haproxy-img-00328.local/v0.5/vrrp/BAD_TEST_DATA
https://octavia-haproxy-img-00328.local/1.0/vrrp/BAD_TEST_DATA
JSON Response:
{
@ -1515,7 +1299,7 @@ Start, Stop, or Reload keepalived
::
PUT URL:
https://octavia-haproxy-img-00328.local/v0.5/vrrp/stop
https://octavia-haproxy-img-00328.local/1.0/vrrp/stop
JSON Response:
{
@ -1526,7 +1310,7 @@ Start, Stop, or Reload keepalived
Update the amphora agent configuration
--------------------------------------
* **URL:** /*:version*/config
* **URL:** /1.0/config
* **Method:** PUT
* **Data params:** A amphora-agent configuration file
@ -1561,7 +1345,7 @@ will be updated.
::
PUT URL:
https://octavia-haproxy-img-00328.local/v0.5/config
https://octavia-haproxy-img-00328.local/1.0/config
(Upload PUT data should be a raw amphora-agent.conf file.)
JSON Response:

View File

@ -12,4 +12,4 @@
# License for the specific language governing permissions and limitations
# under the License.
VERSION = '0.5'
VERSION = '1.0'

View File

@ -46,7 +46,7 @@ class AmphoraInfo(object):
return webob.Response(json=body)
def compile_amphora_details(self, extend_udp_driver=None):
haproxy_listener_list = util.get_listeners()
haproxy_listener_list = sorted(util.get_listeners())
extend_body = {}
udp_listener_list = []
if extend_udp_driver:
@ -87,8 +87,8 @@ class AmphoraInfo(object):
'load': self._load(),
'topology': consts.TOPOLOGY_SINGLE,
'topology_status': consts.TOPOLOGY_STATUS_OK,
'listeners': list(
set(haproxy_listener_list + udp_listener_list))
'listeners': sorted(list(
set(haproxy_listener_list + udp_listener_list)))
if udp_listener_list else haproxy_listener_list,
'packages': {}}
if extend_body:
@ -101,10 +101,10 @@ class AmphoraInfo(object):
version = subprocess.check_output(cmd.split())
return version
def _count_haproxy_processes(self, listener_list):
def _count_haproxy_processes(self, lb_list):
num = 0
for listener_id in listener_list:
if util.is_listener_running(listener_id):
for lb_id in lb_list:
if util.is_lb_running(lb_id):
# optional check if it's still running
num += 1
return num

View File

@ -21,7 +21,7 @@ import jinja2
from oslo_log import log as logging
import webob
from octavia.amphorae.backends.agent.api_server import listener
from octavia.amphorae.backends.agent.api_server import loadbalancer
from octavia.amphorae.backends.agent.api_server import util
from octavia.common import constants as consts
@ -41,7 +41,7 @@ check_script_template = j2_env.get_template(consts.CHECK_SCRIPT_CONF)
class Keepalived(object):
def upload_keepalived_config(self):
stream = listener.Wrapped(flask.request.stream)
stream = loadbalancer.Wrapped(flask.request.stream)
if not os.path.exists(util.keepalived_dir()):
os.makedirs(util.keepalived_dir())

View File

@ -24,10 +24,9 @@ from oslo_log import log as logging
import webob
from werkzeug import exceptions
from octavia.amphorae.backends.agent.api_server import listener
from octavia.amphorae.backends.agent.api_server import loadbalancer
from octavia.amphorae.backends.agent.api_server import udp_listener_base
from octavia.amphorae.backends.agent.api_server import util
from octavia.amphorae.backends.utils import keepalivedlvs_query
from octavia.common import constants as consts
BUFFER = 100
@ -49,7 +48,7 @@ class KeepalivedLvs(udp_listener_base.UdpListenerApiServerBase):
_SUBSCRIBED_AMP_COMPILE = ['keepalived', 'ipvsadm']
def upload_udp_listener_config(self, listener_id):
stream = listener.Wrapped(flask.request.stream)
stream = loadbalancer.Wrapped(flask.request.stream)
NEED_CHECK = True
if not os.path.exists(util.keepalived_lvs_dir()):
@ -252,45 +251,6 @@ class KeepalivedLvs(udp_listener_base.UdpListenerApiServerBase):
})
return listeners
def get_udp_listener_status(self, listener_id):
"""Gets the status of a UDP listener
This method will consult the stats socket
so calling this method will interfere with
the health daemon with the risk of the amphora
shut down
:param listener_id: The id of the listener
"""
self._check_udp_listener_exists(listener_id)
status = self._check_udp_listener_status(listener_id)
if status != consts.ACTIVE:
stats = dict(
status=status,
uuid=listener_id,
type='UDP'
)
return webob.Response(json=stats)
stats = dict(
status=status,
uuid=listener_id,
type='UDP'
)
try:
pool = keepalivedlvs_query.get_udp_listener_pool_status(
listener_id)
except subprocess.CalledProcessError as e:
return webob.Response(json=dict(
message="Error getting kernel lvs status for udp listener "
"{}".format(listener_id),
details=e.output), status=500)
stats['pools'] = [pool]
return webob.Response(json=stats)
def delete_udp_listener(self, listener_id):
try:
self._check_udp_listener_exists(listener_id)

View File

@ -31,7 +31,6 @@ from werkzeug import exceptions
from octavia.amphorae.backends.agent.api_server import haproxy_compatibility
from octavia.amphorae.backends.agent.api_server import osutils
from octavia.amphorae.backends.agent.api_server import util
from octavia.amphorae.backends.utils import haproxy_query as query
from octavia.common import constants as consts
from octavia.common import utils as octavia_utils
@ -54,10 +53,6 @@ SYSVINIT_TEMPLATE = JINJA_ENV.get_template(SYSVINIT_CONF)
SYSTEMD_TEMPLATE = JINJA_ENV.get_template(SYSTEMD_CONF)
class ParsingError(Exception):
pass
# Wrap a stream so we can compute the md5 while reading
class Wrapped(object):
def __init__(self, stream_):
@ -77,37 +72,37 @@ class Wrapped(object):
return getattr(self.stream, attr)
class Listener(object):
class Loadbalancer(object):
def __init__(self):
self._osutils = osutils.BaseOS.get_os_util()
def get_haproxy_config(self, listener_id):
def get_haproxy_config(self, lb_id):
"""Gets the haproxy config
:param listener_id: the id of the listener
"""
self._check_listener_exists(listener_id)
with open(util.config_path(listener_id), 'r') as file:
self._check_lb_exists(lb_id)
with open(util.config_path(lb_id), 'r') as file:
cfg = file.read()
resp = webob.Response(cfg, content_type='text/plain')
resp.headers['ETag'] = hashlib.md5(six.b(cfg)).hexdigest() # nosec
return resp
def upload_haproxy_config(self, amphora_id, listener_id):
def upload_haproxy_config(self, amphora_id, lb_id):
"""Upload the haproxy config
:param amphora_id: The id of the amphora to update
:param listener_id: The id of the listener
:param lb_id: The id of the loadbalancer
"""
stream = Wrapped(flask.request.stream)
# We have to hash here because HAProxy has a string length limitation
# in the configuration file "peer <peername>" lines
peer_name = octavia_utils.base64_sha1_string(amphora_id).rstrip('=')
if not os.path.exists(util.haproxy_dir(listener_id)):
os.makedirs(util.haproxy_dir(listener_id))
if not os.path.exists(util.haproxy_dir(lb_id)):
os.makedirs(util.haproxy_dir(lb_id))
name = os.path.join(util.haproxy_dir(listener_id), 'haproxy.cfg.new')
name = os.path.join(util.haproxy_dir(lb_id), 'haproxy.cfg.new')
flags = os.O_WRONLY | os.O_CREAT | os.O_TRUNC
# mode 00600
mode = stat.S_IRUSR | stat.S_IWUSR
@ -148,7 +143,7 @@ class Listener(object):
status=400)
# file ok - move it
os.rename(name, util.config_path(listener_id))
os.rename(name, util.config_path(lb_id))
try:
@ -156,7 +151,7 @@ class Listener(object):
LOG.debug('Found init system: %s', init_system)
init_path = util.init_path(listener_id, init_system)
init_path = util.init_path(lb_id, init_system)
if init_system == consts.INIT_SYSTEMD:
template = SYSTEMD_TEMPLATE
@ -194,9 +189,9 @@ class Listener(object):
text = template.render(
peer_name=peer_name,
haproxy_pid=util.pid_path(listener_id),
haproxy_pid=util.pid_path(lb_id),
haproxy_cmd=util.CONF.haproxy_amphora.haproxy_cmd,
haproxy_cfg=util.config_path(listener_id),
haproxy_cfg=util.config_path(lb_id),
haproxy_user_group_cfg=consts.HAPROXY_USER_GROUP_CFG,
respawn_count=util.CONF.haproxy_amphora.respawn_count,
respawn_interval=(util.CONF.haproxy_amphora.
@ -212,25 +207,25 @@ class Listener(object):
# Make sure the new service is enabled on boot
if init_system == consts.INIT_SYSTEMD:
util.run_systemctl_command(
consts.ENABLE, "haproxy-{list}".format(list=listener_id))
consts.ENABLE, "haproxy-{lb_id}".format(lb_id=lb_id))
elif init_system == consts.INIT_SYSVINIT:
try:
subprocess.check_output(init_enable_cmd.split(),
stderr=subprocess.STDOUT)
except subprocess.CalledProcessError as e:
LOG.error("Failed to enable haproxy-%(list)s service: "
"%(err)s %(out)s", {'list': listener_id, 'err': e,
LOG.error("Failed to enable haproxy-%(lb_id)s service: "
"%(err)s %(out)s", {'lb_id': lb_id, 'err': e,
'out': e.output})
return webob.Response(json=dict(
message="Error enabling haproxy-{0} service".format(
listener_id), details=e.output), status=500)
lb_id), details=e.output), status=500)
res = webob.Response(json={'message': 'OK'}, status=202)
res.headers['ETag'] = stream.get_md5()
return res
def start_stop_listener(self, listener_id, action):
def start_stop_lb(self, lb_id, action):
action = action.lower()
if action not in [consts.AMP_ACTION_START,
consts.AMP_ACTION_STOP,
@ -239,30 +234,30 @@ class Listener(object):
message='Invalid Request',
details="Unknown action: {0}".format(action)), status=400)
self._check_listener_exists(listener_id)
self._check_lb_exists(lb_id)
# Since this script should be created at LB create time
# we can check for this path to see if VRRP is enabled
# on this amphora and not write the file if VRRP is not in use
if os.path.exists(util.keepalived_check_script_path()):
self.vrrp_check_script_update(listener_id, action)
self.vrrp_check_script_update(lb_id, action)
# HAProxy does not start the process when given a reload
# so start it if haproxy is not already running
if action == consts.AMP_ACTION_RELOAD:
if consts.OFFLINE == self._check_haproxy_status(listener_id):
if consts.OFFLINE == self._check_haproxy_status(lb_id):
action = consts.AMP_ACTION_START
cmd = ("/usr/sbin/service haproxy-{listener_id} {action}".format(
listener_id=listener_id, action=action))
cmd = ("/usr/sbin/service haproxy-{lb_id} {action}".format(
lb_id=lb_id, action=action))
try:
subprocess.check_output(cmd.split(), stderr=subprocess.STDOUT)
except subprocess.CalledProcessError as e:
if b'Job is already running' not in e.output:
LOG.debug(
"Failed to %(action)s haproxy-%(list)s service: %(err)s "
"%(out)s", {'action': action, 'list': listener_id,
"Failed to %(action)s haproxy-%(lb_id)s service: %(err)s "
"%(out)s", {'action': action, 'lb_id': lb_id,
'err': e, 'out': e.output})
return webob.Response(json=dict(
message="Error {0}ing haproxy".format(action),
@ -271,40 +266,40 @@ class Listener(object):
consts.AMP_ACTION_RELOAD]:
return webob.Response(json=dict(
message='OK',
details='Listener {listener_id} {action}ed'.format(
listener_id=listener_id, action=action)), status=202)
details='Listener {lb_id} {action}ed'.format(
lb_id=lb_id, action=action)), status=202)
details = (
'Configuration file is valid\n'
'haproxy daemon for {0} started'.format(listener_id)
'haproxy daemon for {0} started'.format(lb_id)
)
return webob.Response(json=dict(message='OK', details=details),
status=202)
def delete_listener(self, listener_id):
def delete_lb(self, lb_id):
try:
self._check_listener_exists(listener_id)
self._check_lb_exists(lb_id)
except exceptions.HTTPException:
return webob.Response(json={'message': 'OK'})
# check if that haproxy is still running and if stop it
if os.path.exists(util.pid_path(listener_id)) and os.path.exists(
os.path.join('/proc', util.get_haproxy_pid(listener_id))):
cmd = "/usr/sbin/service haproxy-{0} stop".format(listener_id)
if os.path.exists(util.pid_path(lb_id)) and os.path.exists(
os.path.join('/proc', util.get_haproxy_pid(lb_id))):
cmd = "/usr/sbin/service haproxy-{0} stop".format(lb_id)
try:
subprocess.check_output(cmd.split(), stderr=subprocess.STDOUT)
except subprocess.CalledProcessError as e:
LOG.error("Failed to stop haproxy-%s service: %s %s",
listener_id, e, e.output)
lb_id, e, e.output)
return webob.Response(json=dict(
message="Error stopping haproxy",
details=e.output), status=500)
# parse config and delete stats socket
try:
cfg = self._parse_haproxy_file(listener_id)
os.remove(cfg['stats_socket'])
stats_socket = util.parse_haproxy_file(lb_id)[0]
os.remove(stats_socket)
except Exception:
pass
@ -313,22 +308,22 @@ class Listener(object):
# on this amphora and not write the file if VRRP is not in use
if os.path.exists(util.keepalived_check_script_path()):
self.vrrp_check_script_update(
listener_id, action=consts.AMP_ACTION_STOP)
lb_id, action=consts.AMP_ACTION_STOP)
# delete the ssl files
try:
shutil.rmtree(self._cert_dir(listener_id))
shutil.rmtree(self._cert_dir(lb_id))
except Exception:
pass
# disable the service
init_system = util.get_os_init_system()
init_path = util.init_path(listener_id, init_system)
init_path = util.init_path(lb_id, init_system)
if init_system == consts.INIT_SYSTEMD:
util.run_systemctl_command(
consts.DISABLE, "haproxy-{list}".format(
list=listener_id))
consts.DISABLE, "haproxy-{lb_id}".format(
lb_id=lb_id))
elif init_system == consts.INIT_SYSVINIT:
init_disable_cmd = "insserv -r {file}".format(file=init_path)
elif init_system != consts.INIT_UPSTART:
@ -339,15 +334,15 @@ class Listener(object):
subprocess.check_output(init_disable_cmd.split(),
stderr=subprocess.STDOUT)
except subprocess.CalledProcessError as e:
LOG.error("Failed to disable haproxy-%(list)s service: "
"%(err)s %(out)s", {'list': listener_id, 'err': e,
LOG.error("Failed to disable haproxy-%(lb_id)s service: "
"%(err)s %(out)s", {'lb_id': lb_id, 'err': e,
'out': e.output})
return webob.Response(json=dict(
message="Error disabling haproxy-{0} service".format(
listener_id), details=e.output), status=500)
lb_id), details=e.output), status=500)
# delete the directory + init script for that listener
shutil.rmtree(util.haproxy_dir(listener_id))
shutil.rmtree(util.haproxy_dir(lb_id))
if os.path.exists(init_path):
os.remove(init_path)
@ -364,68 +359,29 @@ class Listener(object):
"""
listeners = list()
for listener in util.get_listeners():
status = self._check_listener_status(listener)
listener_type = ''
for lb in util.get_loadbalancers():
stats_socket, listeners_on_lb = util.parse_haproxy_file(lb)
if status == consts.ACTIVE:
listener_type = self._parse_haproxy_file(listener)['mode']
listeners.append({
'status': status,
'uuid': listener,
'type': listener_type,
})
for listener_id, listener in listeners_on_lb.items():
listeners.append({
'status': consts.ACTIVE,
'uuid': listener_id,
'type': listener['mode'],
})
if other_listeners:
listeners = listeners + other_listeners
return webob.Response(json=listeners, content_type='application/json')
def get_listener_status(self, listener_id):
"""Gets the status of a listener
This method will consult the stats socket
so calling this method will interfere with
the health daemon with the risk of the amphora
shut down
Currently type==SSL is not detected
:param listener_id: The id of the listener
"""
self._check_listener_exists(listener_id)
status = self._check_listener_status(listener_id)
if status != consts.ACTIVE:
stats = dict(
status=status,
uuid=listener_id,
type=''
)
return webob.Response(json=stats)
cfg = self._parse_haproxy_file(listener_id)
stats = dict(
status=status,
uuid=listener_id,
type=cfg['mode']
)
# read stats socket
q = query.HAProxyQuery(cfg['stats_socket'])
servers = q.get_pool_status()
stats['pools'] = list(servers.values())
return webob.Response(json=stats)
def upload_certificate(self, listener_id, filename):
def upload_certificate(self, lb_id, filename):
self._check_ssl_filename_format(filename)
# create directory if not already there
if not os.path.exists(self._cert_dir(listener_id)):
os.makedirs(self._cert_dir(listener_id))
if not os.path.exists(self._cert_dir(lb_id)):
os.makedirs(self._cert_dir(lb_id))
stream = Wrapped(flask.request.stream)
file = self._cert_file_path(listener_id, filename)
file = self._cert_file_path(lb_id, filename)
flags = os.O_WRONLY | os.O_CREAT
# mode 00600
mode = stat.S_IRUSR | stat.S_IWUSR
@ -439,10 +395,10 @@ class Listener(object):
resp.headers['ETag'] = stream.get_md5()
return resp
def get_certificate_md5(self, listener_id, filename):
def get_certificate_md5(self, lb_id, filename):
self._check_ssl_filename_format(filename)
cert_path = self._cert_file_path(listener_id, filename)
cert_path = self._cert_file_path(lb_id, filename)
path_exists = os.path.exists(cert_path)
if not path_exists:
return webob.Response(json=dict(
@ -457,60 +413,34 @@ class Listener(object):
resp.headers['ETag'] = md5
return resp
def delete_certificate(self, listener_id, filename):
def delete_certificate(self, lb_id, filename):
self._check_ssl_filename_format(filename)
if os.path.exists(self._cert_file_path(listener_id, filename)):
os.remove(self._cert_file_path(listener_id, filename))
if os.path.exists(self._cert_file_path(lb_id, filename)):
os.remove(self._cert_file_path(lb_id, filename))
return webob.Response(json=dict(message='OK'))
def _check_listener_status(self, listener_id):
if os.path.exists(util.pid_path(listener_id)):
def _get_listeners_on_lb(self, lb_id):
if os.path.exists(util.pid_path(lb_id)):
if os.path.exists(
os.path.join('/proc', util.get_haproxy_pid(listener_id))):
os.path.join('/proc', util.get_haproxy_pid(lb_id))):
# Check if the listener is disabled
with open(util.config_path(listener_id), 'r') as file:
with open(util.config_path(lb_id), 'r') as file:
cfg = file.read()
m = re.search('frontend {}'.format(listener_id), cfg)
if m:
return consts.ACTIVE
return consts.OFFLINE
m = re.findall('^frontend (.*)$', cfg, re.MULTILINE)
return m or []
else: # pid file but no process...
return consts.ERROR
return []
else:
return consts.OFFLINE
return []
def _parse_haproxy_file(self, listener_id):
with open(util.config_path(listener_id), 'r') as file:
cfg = file.read()
m = re.search('mode\s+(http|tcp)', cfg)
if not m:
raise ParsingError()
mode = m.group(1).upper()
m = re.search('stats socket\s+(\S+)', cfg)
if not m:
raise ParsingError()
stats_socket = m.group(1)
m = re.search('ssl crt\s+(\S+)', cfg)
ssl_crt = None
if m:
ssl_crt = m.group(1)
mode = 'TERMINATED_HTTPS'
return dict(mode=mode,
stats_socket=stats_socket,
ssl_crt=ssl_crt)
def _check_listener_exists(self, listener_id):
# check if we know about that listener
if not os.path.exists(util.config_path(listener_id)):
def _check_lb_exists(self, lb_id):
# check if we know about that lb
if lb_id not in util.get_loadbalancers():
raise exceptions.HTTPException(
response=webob.Response(json=dict(
message='Listener Not Found',
details="No listener with UUID: {0}".format(
listener_id)), status=404))
message='Loadbalancer Not Found',
details="No loadbalancer with UUID: {0}".format(
lb_id)), status=404))
def _check_ssl_filename_format(self, filename):
# check if the format is (xxx.)*xxx.pem
@ -519,20 +449,19 @@ class Listener(object):
response=webob.Response(json=dict(
message='Filename has wrong format'), status=400))
def _cert_dir(self, listener_id):
return os.path.join(util.CONF.haproxy_amphora.base_cert_dir,
listener_id)
def _cert_dir(self, lb_id):
return os.path.join(util.CONF.haproxy_amphora.base_cert_dir, lb_id)
def _cert_file_path(self, listener_id, filename):
return os.path.join(self._cert_dir(listener_id), filename)
def _cert_file_path(self, lb_id, filename):
return os.path.join(self._cert_dir(lb_id), filename)
def vrrp_check_script_update(self, listener_id, action):
listener_ids = util.get_listeners()
def vrrp_check_script_update(self, lb_id, action):
lb_ids = util.get_loadbalancers()
if action == consts.AMP_ACTION_STOP:
listener_ids.remove(listener_id)
lb_ids.remove(lb_id)
args = []
for lid in listener_ids:
args.append(util.haproxy_sock_path(lid))
for lbid in lb_ids:
args.append(util.haproxy_sock_path(lbid))
if not os.path.exists(util.keepalived_dir()):
os.makedirs(util.keepalived_dir())
@ -542,9 +471,9 @@ class Listener(object):
with open(util.haproxy_check_script_path(), 'w') as text_file:
text_file.write(cmd)
def _check_haproxy_status(self, listener_id):
if os.path.exists(util.pid_path(listener_id)):
def _check_haproxy_status(self, lb_id):
if os.path.exists(util.pid_path(lb_id)):
if os.path.exists(
os.path.join('/proc', util.get_haproxy_pid(listener_id))):
os.path.join('/proc', util.get_haproxy_pid(lb_id))):
return consts.ACTIVE
return consts.OFFLINE

View File

@ -26,7 +26,7 @@ from octavia.amphorae.backends.agent import api_server
from octavia.amphorae.backends.agent.api_server import amphora_info
from octavia.amphorae.backends.agent.api_server import certificate_update
from octavia.amphorae.backends.agent.api_server import keepalived
from octavia.amphorae.backends.agent.api_server import listener
from octavia.amphorae.backends.agent.api_server import loadbalancer
from octavia.amphorae.backends.agent.api_server import osutils
from octavia.amphorae.backends.agent.api_server import plug
from octavia.amphorae.backends.agent.api_server import udp_listener_base
@ -56,7 +56,7 @@ class Server(object):
self.app = flask.Flask(__name__)
self._osutils = osutils.BaseOS.get_os_util()
self._keepalived = keepalived.Keepalived()
self._listener = listener.Listener()
self._loadbalancer = loadbalancer.Loadbalancer()
self._udp_listener = (udp_listener_base.UdpListenerApiServerBase.
get_server_driver())
self._plug = plug.Plug(self._osutils)
@ -64,8 +64,10 @@ class Server(object):
register_app_error_handler(self.app)
self.app.add_url_rule(rule='/', view_func=self.version_discovery,
methods=['GET'])
self.app.add_url_rule(rule=PATH_PREFIX +
'/listeners/<amphora_id>/<listener_id>/haproxy',
'/loadbalancer/<amphora_id>/<lb_id>/haproxy',
view_func=self.upload_haproxy_config,
methods=['PUT'])
self.app.add_url_rule(rule=PATH_PREFIX +
@ -74,7 +76,7 @@ class Server(object):
view_func=self.upload_udp_listener_config,
methods=['PUT'])
self.app.add_url_rule(rule=PATH_PREFIX +
'/listeners/<listener_id>/haproxy',
'/loadbalancer/<lb_id>/haproxy',
view_func=self.get_haproxy_config,
methods=['GET'])
self.app.add_url_rule(rule=PATH_PREFIX +
@ -82,11 +84,11 @@ class Server(object):
view_func=self.get_udp_listener_config,
methods=['GET'])
self.app.add_url_rule(rule=PATH_PREFIX +
'/listeners/<listener_id>/<action>',
view_func=self.start_stop_listener,
'/loadbalancer/<object_id>/<action>',
view_func=self.start_stop_lb_object,
methods=['PUT'])
self.app.add_url_rule(rule=PATH_PREFIX + '/listeners/<listener_id>',
view_func=self.delete_listener,
self.app.add_url_rule(rule=PATH_PREFIX + '/listeners/<object_id>',
view_func=self.delete_lb_object,
methods=['DELETE'])
self.app.add_url_rule(rule=PATH_PREFIX + '/config',
view_func=self.upload_config,
@ -100,18 +102,15 @@ class Server(object):
self.app.add_url_rule(rule=PATH_PREFIX + '/listeners',
view_func=self.get_all_listeners_status,
methods=['GET'])
self.app.add_url_rule(rule=PATH_PREFIX + '/listeners/<listener_id>',
view_func=self.get_listener_status,
methods=['GET'])
self.app.add_url_rule(rule=PATH_PREFIX + '/listeners/<listener_id>'
self.app.add_url_rule(rule=PATH_PREFIX + '/loadbalancer/<lb_id>'
'/certificates/<filename>',
view_func=self.upload_certificate,
methods=['PUT'])
self.app.add_url_rule(rule=PATH_PREFIX + '/listeners/<listener_id>'
self.app.add_url_rule(rule=PATH_PREFIX + '/loadbalancer/<lb_id>'
'/certificates/<filename>',
view_func=self.get_certificate_md5,
methods=['GET'])
self.app.add_url_rule(rule=PATH_PREFIX + '/listeners/<listener_id>'
self.app.add_url_rule(rule=PATH_PREFIX + '/loadbalancer/<lb_id>'
'/certificates/<filename>',
view_func=self.delete_certificate,
methods=['DELETE'])
@ -133,30 +132,30 @@ class Server(object):
view_func=self.get_interface,
methods=['GET'])
def upload_haproxy_config(self, amphora_id, listener_id):
return self._listener.upload_haproxy_config(amphora_id, listener_id)
def upload_haproxy_config(self, amphora_id, lb_id):
return self._loadbalancer.upload_haproxy_config(amphora_id, lb_id)
def upload_udp_listener_config(self, amphora_id, listener_id):
return self._udp_listener.upload_udp_listener_config(listener_id)
def get_haproxy_config(self, listener_id):
return self._listener.get_haproxy_config(listener_id)
def get_haproxy_config(self, lb_id):
return self._loadbalancer.get_haproxy_config(lb_id)
def get_udp_listener_config(self, listener_id):
return self._udp_listener.get_udp_listener_config(listener_id)
def start_stop_listener(self, listener_id, action):
protocol = util.get_listener_protocol(listener_id)
def start_stop_lb_object(self, object_id, action):
protocol = util.get_protocol_for_lb_object(object_id)
if protocol == 'UDP':
return self._udp_listener.manage_udp_listener(
listener_id, action)
return self._listener.start_stop_listener(listener_id, action)
listener_id=object_id, action=action)
return self._loadbalancer.start_stop_lb(lb_id=object_id, action=action)
def delete_listener(self, listener_id):
protocol = util.get_listener_protocol(listener_id)
def delete_lb_object(self, object_id):
protocol = util.get_protocol_for_lb_object(object_id)
if protocol == 'UDP':
return self._udp_listener.delete_udp_listener(listener_id)
return self._listener.delete_listener(listener_id)
return self._udp_listener.delete_udp_listener(object_id)
return self._loadbalancer.delete_lb(object_id)
def get_details(self):
return self._amphora_info.compile_amphora_details(
@ -168,23 +167,17 @@ class Server(object):
def get_all_listeners_status(self):
udp_listeners = self._udp_listener.get_all_udp_listeners_status()
return self._listener.get_all_listeners_status(
return self._loadbalancer.get_all_listeners_status(
other_listeners=udp_listeners)
def get_listener_status(self, listener_id):
protocol = util.get_listener_protocol(listener_id)
if protocol == 'UDP':
return self._udp_listener.get_udp_listener_status(listener_id)
return self._listener.get_listener_status(listener_id)
def upload_certificate(self, lb_id, filename):
return self._loadbalancer.upload_certificate(lb_id, filename)
def upload_certificate(self, listener_id, filename):
return self._listener.upload_certificate(listener_id, filename)
def get_certificate_md5(self, lb_id, filename):
return self._loadbalancer.get_certificate_md5(lb_id, filename)
def get_certificate_md5(self, listener_id, filename):
return self._listener.get_certificate_md5(listener_id, filename)
def delete_certificate(self, listener_id, filename):
return self._listener.delete_certificate(listener_id, filename)
def delete_certificate(self, lb_id, filename):
return self._loadbalancer.delete_certificate(lb_id, filename)
def plug_vip(self, vip):
# Catch any issues with the subnet info json
@ -251,3 +244,6 @@ class Server(object):
details=str(e)), status=500)
return webob.Response(json={'message': 'OK'}, status=202)
def version_discovery(self):
return webob.Response(json={'api_version': api_server.VERSION})

View File

@ -98,18 +98,6 @@ class UdpListenerApiServerBase(object):
"""
pass
@abc.abstractmethod
def get_udp_listener_status(self, listener_id):
"""Gets the status of a UDP listener
:param listener_id: The id of the listener
:returns: HTTP response with status code.
:raises Exception: If the listener is failed to find.
"""
pass
@abc.abstractmethod
def delete_udp_listener(self, listener_id):
"""Delete a UDP Listener from a amphora

View File

@ -28,23 +28,32 @@ from octavia.common import constants as consts
CONF = cfg.CONF
LOG = logging.getLogger(__name__)
FRONTEND_BACKEND_PATTERN = re.compile(r'\n(frontend|backend)\s+(\S+)\n')
LISTENER_MODE_PATTERN = re.compile(r'^\s+mode\s+(.*)$', re.MULTILINE)
TLS_CERT_PATTERN = re.compile(r'^\s+bind\s+\S+\s+ssl crt\s+(.*)$',
re.MULTILINE)
STATS_SOCKET_PATTERN = re.compile(r'stats socket\s+(\S+)')
class ParsingError(Exception):
pass
class UnknownInitError(Exception):
pass
def init_path(listener_id, init_system):
def init_path(lb_id, init_system):
if init_system == consts.INIT_SYSTEMD:
return os.path.join(consts.SYSTEMD_DIR,
'haproxy-{0}.service'.format(listener_id))
elif init_system == consts.INIT_UPSTART:
'haproxy-{0}.service'.format(lb_id))
if init_system == consts.INIT_UPSTART:
return os.path.join(consts.UPSTART_DIR,
'haproxy-{0}.conf'.format(listener_id))
elif init_system == consts.INIT_SYSVINIT:
'haproxy-{0}.conf'.format(lb_id))
if init_system == consts.INIT_SYSVINIT:
return os.path.join(consts.SYSVINIT_DIR,
'haproxy-{0}'.format(listener_id))
else:
raise UnknownInitError()
'haproxy-{0}'.format(lb_id))
raise UnknownInitError()
def keepalived_lvs_dir():
@ -93,20 +102,20 @@ def keepalived_lvs_cfg_path(listener_id):
str(listener_id))
def haproxy_dir(listener_id):
return os.path.join(CONF.haproxy_amphora.base_path, listener_id)
def haproxy_dir(lb_id):
return os.path.join(CONF.haproxy_amphora.base_path, lb_id)
def pid_path(listener_id):
return os.path.join(haproxy_dir(listener_id), listener_id + '.pid')
def pid_path(lb_id):
return os.path.join(haproxy_dir(lb_id), lb_id + '.pid')
def config_path(listener_id):
return os.path.join(haproxy_dir(listener_id), 'haproxy.cfg')
def config_path(lb_id):
return os.path.join(haproxy_dir(lb_id), 'haproxy.cfg')
def get_haproxy_pid(listener_id):
with open(pid_path(listener_id), 'r') as f:
def get_haproxy_pid(lb_id):
with open(pid_path(lb_id), 'r') as f:
return f.readline().rstrip()
@ -116,8 +125,8 @@ def get_keepalivedlvs_pid(listener_id):
return f.readline().rstrip()
def haproxy_sock_path(listener_id):
return os.path.join(CONF.haproxy_amphora.base_path, listener_id + '.sock')
def haproxy_sock_path(lb_id):
return os.path.join(CONF.haproxy_amphora.base_path, lb_id + '.sock')
def haproxy_check_script_path():
@ -171,16 +180,28 @@ def get_listeners():
:returns: An array with the ids of all listeners, e.g. ['123', '456', ...]
or [] if no listeners exist
"""
listeners = []
for lb_id in get_loadbalancers():
listeners_on_lb = parse_haproxy_file(lb_id)[1]
listeners.extend(list(listeners_on_lb.keys()))
return listeners
def get_loadbalancers():
"""Get Load balancers
:returns: An array with the ids of all load balancers,
e.g. ['123', '456', ...] or [] if no loadbalancers exist
"""
if os.path.exists(CONF.haproxy_amphora.base_path):
return [f for f in os.listdir(CONF.haproxy_amphora.base_path)
if os.path.exists(config_path(f))]
return []
def is_listener_running(listener_id):
return os.path.exists(pid_path(listener_id)) and os.path.exists(
os.path.join('/proc', get_haproxy_pid(listener_id)))
def is_lb_running(lb_id):
return os.path.exists(pid_path(lb_id)) and os.path.exists(
os.path.join('/proc', get_haproxy_pid(lb_id)))
def get_udp_listeners():
@ -254,7 +275,7 @@ def run_systemctl_command(command, service):
'err': e, 'out': e.output})
def get_listener_protocol(listener_id):
def get_protocol_for_lb_object(object_id):
"""Returns the L4 protocol for a listener.
If the listener is a TCP based listener (haproxy) return TCP.
@ -264,8 +285,52 @@ def get_listener_protocol(listener_id):
:param listener_id: The ID of the listener to identify.
:returns: TCP, UDP, or None
"""
if os.path.exists(config_path(listener_id)):
if os.path.exists(config_path(object_id)):
return consts.PROTOCOL_TCP
elif os.path.exists(keepalived_lvs_cfg_path(listener_id)):
if os.path.exists(keepalived_lvs_cfg_path(object_id)):
return consts.PROTOCOL_UDP
return None
def parse_haproxy_file(lb_id):
with open(config_path(lb_id), 'r') as file:
cfg = file.read()
listeners = {}
m = FRONTEND_BACKEND_PATTERN.split(cfg)
last_token = None
last_id = None
for section in m:
if last_token is None:
# We aren't in a section yet, see if this line starts one
if section == 'frontend':
last_token = section
elif last_token == 'frontend':
# We're in a frontend section, save the id for later
last_token = last_token + "_id"
last_id = section
elif last_token == 'frontend_id':
# We're in a frontend section and already have the id
# Look for the mode
mode_matcher = LISTENER_MODE_PATTERN.search(section)
if not mode_matcher:
raise ParsingError()
listeners[last_id] = {
'mode': mode_matcher.group(1).upper(),
}
# Now see if this is a TLS frontend
tls_matcher = TLS_CERT_PATTERN.search(section)
if tls_matcher:
# TODO(rm_work): Can't we have terminated tcp?
listeners[last_id]['mode'] = 'TERMINATED_HTTPS'
listeners[last_id]['ssl_crt'] = tls_matcher.group(1)
# Clear out the token and id and start over
last_token = last_id = None
m = STATS_SOCKET_PATTERN.search(cfg)
if not m:
raise ParsingError()
stats_socket = m.group(1)
return stats_socket, listeners

View File

@ -43,18 +43,19 @@ SEQ = 0
# incompatible changes.
#
# ver 1 - Adds UDP listener status when no pool or members are present
# ver 2 - Switch to all listeners in a single combined haproxy config
#
MSG_VER = 1
MSG_VER = 2
def list_sock_stat_files(hadir=None):
stat_sock_files = {}
if hadir is None:
hadir = CONF.haproxy_amphora.base_path
listener_ids = util.get_listeners()
for listener_id in listener_ids:
sock_file = listener_id + ".sock"
stat_sock_files[listener_id] = os.path.join(hadir, sock_file)
lb_ids = util.get_loadbalancers()
for lb_id in lb_ids:
sock_file = lb_id + ".sock"
stat_sock_files[lb_id] = os.path.join(hadir, sock_file)
return stat_sock_files
@ -121,39 +122,55 @@ def get_stats(stat_sock_file):
def build_stats_message():
# Example version 2 message without UDP:
# {
# "id": "<amphora_id>",
# "seq": 67,
# "listeners": {
# "<listener_id>": {
# "status": "OPEN",
# "stats": {
# "tx": 0,
# "rx": 0,
# "conns": 0,
# "totconns": 0,
# "ereq": 0
# }
# }
# },
# "pools": {
# "<pool_id>:<listener_id>": {
# "status": "UP",
# "members": {
# "<member_id>": "no check"
# }
# }
# },
# "ver": 2
# }
global SEQ
msg = {'id': CONF.amphora_agent.amphora_id,
'seq': SEQ, "listeners": {},
'seq': SEQ, 'listeners': {}, 'pools': {},
'ver': MSG_VER}
SEQ += 1
stat_sock_files = list_sock_stat_files()
for listener_id, stat_sock_file in stat_sock_files.items():
listener_dict = {'pools': {},
'status': 'DOWN',
'stats': {
'tx': 0,
'rx': 0,
'conns': 0,
'totconns': 0,
'ereq': 0}}
msg['listeners'][listener_id] = listener_dict
if util.is_listener_running(listener_id):
# TODO(rm_work) There should only be one of these in the new config system
for lb_id, stat_sock_file in stat_sock_files.items():
if util.is_lb_running(lb_id):
(stats, pool_status) = get_stats(stat_sock_file)
listener_dict = msg['listeners'][listener_id]
for row in stats:
if row['svname'] == 'FRONTEND':
listener_dict['stats']['tx'] = int(row['bout'])
listener_dict['stats']['rx'] = int(row['bin'])
listener_dict['stats']['conns'] = int(row['scur'])
listener_dict['stats']['totconns'] = int(row['stot'])
listener_dict['stats']['ereq'] = int(row['ereq'])
listener_dict['status'] = row['status']
for oid, pool in pool_status.items():
if oid != listener_id:
pool_id = oid
pools = listener_dict['pools']
pools[pool_id] = {"status": pool['status'],
"members": pool['members']}
listener_id = row['pxname']
msg['listeners'][listener_id] = {
'status': row['status'],
'stats': {'tx': int(row['bout']),
'rx': int(row['bin']),
'conns': int(row['scur']),
'totconns': int(row['stot']),
'ereq': int(row['ereq'])}}
for pool_id, pool in pool_status.items():
msg['pools'][pool_id] = {"status": pool['status'],
"members": pool['members']}
# UDP listener part
udp_listener_ids = util.get_udp_listeners()

View File

@ -124,7 +124,9 @@ class HAProxyQuery(object):
final_results[line['pxname']] = dict(members={})
if line['svname'] == 'BACKEND':
final_results[line['pxname']]['uuid'] = line['pxname']
pool_id, listener_id = line['pxname'].split(':')
final_results[line['pxname']]['pool_uuid'] = pool_id
final_results[line['pxname']]['listener_uuid'] = listener_id
final_results[line['pxname']]['status'] = line['status']
else:
final_results[line['pxname']]['members'][line['svname']] = (

View File

@ -22,17 +22,19 @@ import six
class AmphoraLoadBalancerDriver(object):
@abc.abstractmethod
def update_amphora_listeners(self, listeners, amphora_id, timeout_dict):
def update_amphora_listeners(self, loadbalancer, amphora,
timeout_dict):
"""Update the amphora with a new configuration.
:param listeners: List of listeners to update.
:type listener: list
:param amphora_id: The ID of the amphora to update
:type amphora_id: string
:param loadbalancer: List of listeners to update.
:type loadbalancer: list(octavia.db.models.Listener)
:param amphora: The index of the specific amphora to update
:type amphora: octavia.db.models.Amphora
:param timeout_dict: Dictionary of timeout values for calls to the
amphora. May contain: req_conn_timeout,
req_read_timeout, conn_max_retries,
conn_retry_interval
:type timeout_dict: dict
:returns: None
Builds a new configuration, pushes it to the amphora, and reloads
@ -41,14 +43,12 @@ class AmphoraLoadBalancerDriver(object):
pass
@abc.abstractmethod
def update(self, listener, vip):
def update(self, loadbalancer):
"""Update the amphora with a new configuration.
:param listener: listener object,
need to use its protocol_port property
:type listener: object
:param vip: vip object, need to use its ip_address property
:type vip: object
:param loadbalancer: loadbalancer object, need to use its
vip.ip_address property
:type loadbalancer: octavia.db.models.LoadBalancer
:returns: None
At this moment, we just build the basic structure for testing, will
@ -57,32 +57,13 @@ class AmphoraLoadBalancerDriver(object):
pass
@abc.abstractmethod
def stop(self, listener, vip):
"""Stop the listener on the vip.
def start(self, loadbalancer, amphora):
"""Start the listeners on the amphora.
:param listener: listener object,
need to use its protocol_port property
:type listener: object
:param vip: vip object, need to use its ip_address property
:type vip: object
:returns: return a value list (listener, vip, status flag--suspend)
At this moment, we just build the basic structure for testing, will
add more function along with the development.
"""
pass
@abc.abstractmethod
def start(self, listener, vip, amphora):
"""Start the listener on the vip.
:param listener: listener object,
need to use its protocol_port property
:type listener: object
:param vip: vip object, need to use its ip_address property
:type vip: object
:param loadbalancer: loadbalancer object to start listeners
:type loadbalancer: octavia.db.models.LoadBalancer
:param amphora: Amphora to start. If None, start on all amphora
:type amphora: object
:type amphora: octavia.db.models.Amphora
:returns: return a value list (listener, vip, status flag--enable)
At this moment, we just build the basic structure for testing, will
@ -91,14 +72,12 @@ class AmphoraLoadBalancerDriver(object):
pass
@abc.abstractmethod
def delete(self, listener, vip):
def delete(self, listener):
"""Delete the listener on the vip.
:param listener: listener object,
need to use its protocol_port property
:type listener: object
:param vip: vip object, need to use its ip_address property
:type vip: object
:type listener: octavia.db.models.Listener
:returns: return a value list (listener, vip, status flag--delete)
At this moment, we just build the basic structure for testing, will
@ -111,7 +90,7 @@ class AmphoraLoadBalancerDriver(object):
"""Returns information about the amphora.
:param amphora: amphora object, need to use its id property
:type amphora: object
:type amphora: octavia.db.models.Amphora
:returns: return a value list (amphora.id, status flag--'info')
At this moment, we just build the basic structure for testing, will
@ -128,7 +107,7 @@ class AmphoraLoadBalancerDriver(object):
"""Return ceilometer ready diagnostic data.
:param amphora: amphora object, need to use its id property
:type amphora: object
:type amphora: octavia.db.models.Amphora
:returns: return a value list (amphora.id, status flag--'ge
t_diagnostics')
@ -145,7 +124,7 @@ class AmphoraLoadBalancerDriver(object):
"""Finalize the amphora before any listeners are configured.
:param amphora: amphora object, need to use its id property
:type amphora: object
:type amphora: octavia.db.models.Amphora
:returns: None
At this moment, we just build the basic structure for testing, will
@ -159,6 +138,8 @@ class AmphoraLoadBalancerDriver(object):
def post_vip_plug(self, amphora, load_balancer, amphorae_network_config):
"""Called after network driver has allocated and plugged the VIP
:param amphora:
:type amphora: octavia.db.models.Amphora
:param load_balancer: A load balancer that just had its vip allocated
and plugged in the network driver.
:type load_balancer: octavia.common.data_models.LoadBalancer
@ -177,7 +158,7 @@ class AmphoraLoadBalancerDriver(object):
"""Called after amphora added to network
:param amphora: amphora object, needs id and network ip(s)
:type amphora: object
:type amphora: octavia.db.models.Amphora
:param port: contains information of the plugged port
:type port: octavia.network.data_models.Port
@ -192,7 +173,7 @@ class AmphoraLoadBalancerDriver(object):
"""Start health checks.
:param health_mixin: health mixin object
:type amphora: object
:type health_mixin: HealthMixin
Starts listener process and calls HealthMixin to update
databases information.
@ -211,7 +192,7 @@ class AmphoraLoadBalancerDriver(object):
"""Upload cert info to the amphora.
:param amphora: amphora object, needs id and network ip(s)
:type amphora: object
:type amphora: octavia.db.models.Amphora
:param pem_file: a certificate file
:type pem_file: file object
@ -223,7 +204,7 @@ class AmphoraLoadBalancerDriver(object):
"""Upload and update the amphora agent configuration.
:param amphora: amphora object, needs id and network ip(s)
:type amphora: object
:type amphora: octavia.db.models.Amphora
:param agent_config: The new amphora agent configuration file.
:type agent_config: string
"""

View File

@ -31,7 +31,8 @@ from octavia.amphorae.drivers.haproxy import exceptions as exc
from octavia.amphorae.drivers.keepalived import vrrp_rest_driver
from octavia.common.config import cfg
from octavia.common import constants as consts
from octavia.common.jinja.haproxy import jinja_cfg
import octavia.common.jinja.haproxy.combined_listeners.jinja_cfg as jinja_combo
import octavia.common.jinja.haproxy.split_listeners.jinja_cfg as jinja_split
from octavia.common.jinja.lvs import jinja_cfg as jinja_udp_cfg
from octavia.common.tls_utils import cert_parser
from octavia.common import utils
@ -50,14 +51,23 @@ class HaproxyAmphoraLoadBalancerDriver(
def __init__(self):
super(HaproxyAmphoraLoadBalancerDriver, self).__init__()
self.client = AmphoraAPIClient()
self.clients = {
'base': AmphoraAPIClientBase(),
'0.5': AmphoraAPIClient0_5(),
'1.0': AmphoraAPIClient1_0(),
}
self.cert_manager = stevedore_driver.DriverManager(
namespace='octavia.cert_manager',
name=CONF.certificates.cert_manager,
invoke_on_load=True,
).driver
self.jinja = jinja_cfg.JinjaTemplater(
self.jinja_combo = jinja_combo.JinjaTemplater(
base_amp_path=CONF.haproxy_amphora.base_path,
base_crt_dir=CONF.haproxy_amphora.base_cert_dir,
haproxy_template=CONF.haproxy_amphora.haproxy_template,
connection_logging=CONF.haproxy_amphora.connection_logging)
self.jinja_split = jinja_split.JinjaTemplater(
base_amp_path=CONF.haproxy_amphora.base_path,
base_crt_dir=CONF.haproxy_amphora.base_cert_dir,
haproxy_template=CONF.haproxy_amphora.haproxy_template,
@ -71,21 +81,39 @@ class HaproxyAmphoraLoadBalancerDriver(
:returns version_list: A list with the major and minor numbers
"""
self._populate_amphora_api_version(amphora)
amp_info = self.clients[amphora.api_version].get_info(amphora)
haproxy_version_string = amp_info['haproxy_version']
version_string = self.client.get_info(amphora)['haproxy_version']
return haproxy_version_string.split('.')[:2]
return version_string.split('.')[:2]
def _populate_amphora_api_version(self, amphora):
"""Populate the amphora object with the api_version
def update_amphora_listeners(self, listeners, amphora_index,
amphorae, timeout_dict=None):
This will query the amphora for version discovery and populate
the api_version string attribute on the amphora object.
:returns: None
"""
if not getattr(amphora, 'api_version', None):
try:
amphora.api_version = self.clients['base'].get_api_version(
amphora)['api_version']
except exc.NotFound:
# Amphora is too old for version discovery, default to 0.5
amphora.api_version = '0.5'
LOG.debug('Amphora %s has API version %s',
amphora.id, amphora.api_version)
return list(map(int, amphora.api_version.split('.')))
def update_amphora_listeners(self, loadbalancer, amphora,
timeout_dict=None):
"""Update the amphora with a new configuration.
:param listeners: List of listeners to update.
:type listener: list
:param amphora_index: The index of the amphora to update
:type amphora_index: integer
:param amphorae: List of amphorae
:type amphorae: list
:param loadbalancer: The load balancer to update
:type loadbalancer: object
:param amphora: The amphora to update
:type amphora: object
:param timeout_dict: Dictionary of timeout values for calls to the
amphora. May contain: req_conn_timeout,
req_read_timeout, conn_max_retries,
@ -95,46 +123,82 @@ class HaproxyAmphoraLoadBalancerDriver(
Updates the configuration of the listeners on a single amphora.
"""
# if the amphora does not yet have listeners, no need to update them.
if not listeners:
if not loadbalancer.listeners:
LOG.debug('No listeners found to update.')
return
amp = amphorae[amphora_index]
if amp is None or amp.status == consts.DELETED:
if amphora is None or amphora.status == consts.DELETED:
return
haproxy_versions = self._get_haproxy_versions(amp)
# Check which HAProxy version is on the amp
haproxy_versions = self._get_haproxy_versions(amphora)
# Check which config style to use
api_version = self._populate_amphora_api_version(amphora)
if api_version[0] == 0 and api_version[1] <= 5: # 0.5 or earlier
split_config = True
LOG.warning(
'Amphora %s for loadbalancer %s needs upgrade to single '
'process mode.', amphora.id, loadbalancer.id)
else:
split_config = False
LOG.debug('Amphora %s for loadbalancer %s is already in single '
'process mode.', amphora.id, loadbalancer.id)
# TODO(johnsom) remove when we don't have a process per listener
for listener in listeners:
has_tcp = False
for listener in loadbalancer.listeners:
LOG.debug("%s updating listener %s on amphora %s",
self.__class__.__name__, listener.id, amp.id)
self.__class__.__name__, listener.id, amphora.id)
if listener.protocol == 'UDP':
# Generate Keepalived LVS configuration from listener object
config = self.udp_jinja.build_config(listener=listener)
self.client.upload_udp_config(amp, listener.id, config,
timeout_dict=timeout_dict)
self.client.reload_listener(amp, listener.id,
timeout_dict=timeout_dict)
self.clients[amphora.api_version].upload_udp_config(
amphora, listener.id, config, timeout_dict=timeout_dict)
self.clients[amphora.api_version].reload_listener(
amphora, listener.id, timeout_dict=timeout_dict)
else:
certs = self._process_tls_certificates(listener)
client_ca_filename = self._process_secret(
listener, listener.client_ca_tls_certificate_id)
crl_filename = self._process_secret(
listener, listener.client_crl_container_id)
pool_tls_certs = self._process_listener_pool_certs(listener)
has_tcp = True
if split_config:
obj_id = listener.id
else:
obj_id = loadbalancer.id
# Generate HaProxy configuration from listener object
config = self.jinja.build_config(
host_amphora=amp, listener=listener,
tls_cert=certs['tls_cert'],
haproxy_versions=haproxy_versions,
client_ca_filename=client_ca_filename,
client_crl=crl_filename,
pool_tls_certs=pool_tls_certs)
self.client.upload_config(amp, listener.id, config,
timeout_dict=timeout_dict)
self.client.reload_listener(amp, listener.id,
timeout_dict=timeout_dict)
certs = self._process_tls_certificates(
listener, amphora, obj_id)
client_ca_filename = self._process_secret(
listener, listener.client_ca_tls_certificate_id,
amphora, obj_id)
crl_filename = self._process_secret(
listener, listener.client_crl_container_id,
amphora, obj_id)
pool_tls_certs = self._process_listener_pool_certs(
listener, amphora, obj_id)
if split_config:
config = self.jinja_split.build_config(
host_amphora=amphora, listener=listener,
tls_cert=certs['tls_cert'],
haproxy_versions=haproxy_versions,
client_ca_filename=client_ca_filename,
client_crl=crl_filename,
pool_tls_certs=pool_tls_certs)
self.clients[amphora.api_version].upload_config(
amphora, listener.id, config,
timeout_dict=timeout_dict)
self.clients[amphora.api_version].reload_listener(
amphora, listener.id, timeout_dict=timeout_dict)
if has_tcp and not split_config:
# Generate HaProxy configuration from listener object
config = self.jinja_combo.build_config(
host_amphora=amphora, listeners=loadbalancer.listeners,
tls_cert=certs['tls_cert'],
haproxy_versions=haproxy_versions,
client_ca_filename=client_ca_filename,
client_crl=crl_filename,
pool_tls_certs=pool_tls_certs)
self.clients[amphora.api_version].upload_config(
amphora, loadbalancer.id, config, timeout_dict=timeout_dict)
self.clients[amphora.api_version].reload_listener(
amphora, loadbalancer.id, timeout_dict=timeout_dict)
def _udp_update(self, listener, vip):
LOG.debug("Amphora %s keepalivedlvs, updating "
@ -145,69 +209,132 @@ class HaproxyAmphoraLoadBalancerDriver(
for amp in listener.load_balancer.amphorae:
if amp.status != consts.DELETED:
# Generate Keepalived LVS configuration from listener object
self._populate_amphora_api_version(amp)
config = self.udp_jinja.build_config(listener=listener)
self.client.upload_udp_config(amp, listener.id, config)
self.client.reload_listener(amp, listener.id)
self.clients[amp.api_version].upload_udp_config(
amp, listener.id, config)
self.clients[amp.api_version].reload_listener(
amp, listener.id)
def update(self, listener, vip):
if listener.protocol == 'UDP':
self._udp_update(listener, vip)
else:
LOG.debug("Amphora %s haproxy, updating listener %s, "
"vip %s", self.__class__.__name__,
listener.protocol_port,
vip.ip_address)
# Process listener certificate info
certs = self._process_tls_certificates(listener)
client_ca_filename = self._process_secret(
listener, listener.client_ca_tls_certificate_id)
crl_filename = self._process_secret(
listener, listener.client_crl_container_id)
pool_tls_certs = self._process_listener_pool_certs(listener)
for amp in listener.load_balancer.amphorae:
if amp.status != consts.DELETED:
haproxy_versions = self._get_haproxy_versions(amp)
# Generate HaProxy configuration from listener object
config = self.jinja.build_config(
host_amphora=amp, listener=listener,
tls_cert=certs['tls_cert'],
haproxy_versions=haproxy_versions,
client_ca_filename=client_ca_filename,
client_crl=crl_filename,
pool_tls_certs=pool_tls_certs)
self.client.upload_config(amp, listener.id, config)
self.client.reload_listener(amp, listener.id)
def update(self, loadbalancer):
for amphora in loadbalancer.amphorae:
if amphora.status != consts.DELETED:
self.update_amphora_listeners(loadbalancer, amphora)
def upload_cert_amp(self, amp, pem):
LOG.debug("Amphora %s updating cert in REST driver "
"with amphora id %s,",
self.__class__.__name__, amp.id)
self.client.update_cert_for_rotation(amp, pem)
self._populate_amphora_api_version(amp)
self.clients[amp.api_version].update_cert_for_rotation(amp, pem)
def _apply(self, func, listener=None, amphora=None, *args):
def _apply(self, func_name, loadbalancer, amphora=None, *args):
if amphora is None:
for amp in listener.load_balancer.amphorae:
if amp.status != consts.DELETED:
func(amp, listener.id, *args)
amphorae = loadbalancer.amphorae
else:
if amphora.status != consts.DELETED:
func(amphora, listener.id, *args)
amphorae = [amphora]
def stop(self, listener, vip):
self._apply(self.client.stop_listener, listener)
for amp in amphorae:
if amp.status != consts.DELETED:
api_version = self._populate_amphora_api_version(amp)
# Check which config style to use
if api_version[0] == 0 and api_version[1] <= 5:
# 0.5 or earlier
LOG.warning(
'Amphora %s for loadbalancer %s needs upgrade to '
'single process mode.', amp.id, loadbalancer.id)
for listener in loadbalancer.listeners:
getattr(self.clients[amp.api_version], func_name)(
amp, listener.id, *args)
else:
LOG.debug(
'Amphora %s for loadbalancer %s is already in single '
'process mode.', amp.id, loadbalancer.id)
has_tcp = False
for listener in loadbalancer.listeners:
if listener.protocol == consts.PROTOCOL_UDP:
getattr(self.clients[amp.api_version], func_name)(
amp, listener.id, *args)
else:
has_tcp = True
if has_tcp:
getattr(self.clients[amp.api_version], func_name)(
amp, loadbalancer.id, *args)
def start(self, listener, vip, amphora=None):
self._apply(self.client.start_listener, listener, amphora)
def start(self, loadbalancer, amphora=None):
self._apply('start_listener', loadbalancer, amphora)
def delete(self, listener, vip):
self._apply(self.client.delete_listener, listener)
def delete(self, listener):
# Delete any UDP listeners the old way (we didn't update the way they
# are configured)
loadbalancer = listener.load_balancer
if listener.protocol == consts.PROTOCOL_UDP:
for amp in loadbalancer.amphorae:
if amp.status != consts.DELETED:
self._populate_amphora_api_version(amp)
self.clients[amp.api_version].delete_listener(
amp, listener.id)
return
# In case the listener is not UDP, things get more complicated.
# We need to do this individually for each amphora in case some are
# using split config and others are using combined config.
for amp in loadbalancer.amphorae:
if amp.status != consts.DELETED:
api_version = self._populate_amphora_api_version(amp)
# Check which config style to use
if api_version[0] == 0 and api_version[1] <= 5:
# 0.5 or earlier
LOG.warning(
'Amphora %s for loadbalancer %s needs upgrade to '
'single process mode.', amp.id, loadbalancer.id)
self.clients[amp.api_version].delete_listener(
amp, listener.id)
else:
LOG.debug(
'Amphora %s for loadbalancer %s is already in single '
'process mode.', amp.id, loadbalancer.id)
self._combined_config_delete(amp, listener)
def _combined_config_delete(self, amphora, listener):
# Remove the listener from the listener list on the LB before
# passing the whole thing over to update (so it'll actually delete)
listener.load_balancer.listeners.remove(listener)
# Check if there's any certs that we need to delete
certs = self._process_tls_certificates(listener)
certs_to_delete = set()
if certs['tls_cert']:
certs_to_delete.add(certs['tls_cert'].id)
for sni_cert in certs['sni_certs']:
certs_to_delete.add(sni_cert.id)
# Delete them (they'll be recreated before the reload if they are
# needed for other listeners anyway)
self._populate_amphora_api_version(amphora)
for cert_id in certs_to_delete:
self.clients[amphora.api_version].delete_cert_pem(
amphora, listener.load_balancer.id,
'{id}.pem'.format(id=cert_id))
# See how many non-UDP listeners we have left
non_udp_listener_count = len([
1 for l in listener.load_balancer.listeners
if l.protocol != consts.PROTOCOL_UDP])
if non_udp_listener_count > 0:
# We have other listeners, so just update is fine.
# TODO(rm_work): This is a little inefficient since this duplicates
# a lot of the detection logic that has already been done, but it
# is probably safer to re-use the existing code-path.
self.update_amphora_listeners(listener.load_balancer, amphora)
else:
# Deleting the last listener, so really do the delete
self.clients[amphora.api_version].delete_listener(
amphora, listener.load_balancer.id)
def get_info(self, amphora):
return self.client.get_info(amphora)
self._populate_amphora_api_version(amphora)
return self.clients[amphora.api_version].get_info(amphora)
def get_diagnostics(self, amphora):
pass
@ -217,6 +344,7 @@ class HaproxyAmphoraLoadBalancerDriver(
def post_vip_plug(self, amphora, load_balancer, amphorae_network_config):
if amphora.status != consts.DELETED:
self._populate_amphora_api_version(amphora)
subnet = amphorae_network_config.get(amphora.id).vip_subnet
# NOTE(blogan): using the vrrp port here because that
# is what the allowed address pairs network driver sets
@ -241,9 +369,8 @@ class HaproxyAmphoraLoadBalancerDriver(
'mtu': port.network.mtu,
'host_routes': host_routes}
try:
self.client.plug_vip(amphora,
load_balancer.vip.ip_address,
net_info)
self.clients[amphora.api_version].plug_vip(
amphora, load_balancer.vip.ip_address, net_info)
except exc.Conflict:
LOG.warning('VIP with MAC %(mac)s already exists on amphora, '
'skipping post_vip_plug',
@ -263,13 +390,14 @@ class HaproxyAmphoraLoadBalancerDriver(
'fixed_ips': fixed_ips,
'mtu': port.network.mtu}
try:
self.client.plug_network(amphora, port_info)
self._populate_amphora_api_version(amphora)
self.clients[amphora.api_version].plug_network(amphora, port_info)
except exc.Conflict:
LOG.warning('Network with MAC %(mac)s already exists on amphora, '
'skipping post_network_plug',
{'mac': port.mac_address})
def _process_tls_certificates(self, listener):
def _process_tls_certificates(self, listener, amphora=None, obj_id=None):
"""Processes TLS data from the listener.
Converts and uploads PEM data to the Amphora API
@ -289,15 +417,15 @@ class HaproxyAmphoraLoadBalancerDriver(
sni_certs = data['sni_certs']
certs.extend(sni_certs)
for cert in certs:
pem = cert_parser.build_pem(cert)
md5 = hashlib.md5(pem).hexdigest() # nosec
name = '{id}.pem'.format(id=cert.id)
self._apply(self._upload_cert, listener, None, pem, md5, name)
if amphora and obj_id:
for cert in certs:
pem = cert_parser.build_pem(cert)
md5 = hashlib.md5(pem).hexdigest() # nosec
name = '{id}.pem'.format(id=cert.id)
self._upload_cert(amphora, obj_id, pem, md5, name)
return {'tls_cert': tls_cert, 'sni_certs': sni_certs}
def _process_secret(self, listener, secret_ref):
def _process_secret(self, listener, secret_ref, amphora=None, obj_id=None):
"""Get the secret from the cert manager and upload it to the amp.
:returns: The filename of the secret in the amp.
@ -313,10 +441,14 @@ class HaproxyAmphoraLoadBalancerDriver(
md5 = hashlib.md5(secret).hexdigest() # nosec
id = hashlib.sha1(secret).hexdigest() # nosec
name = '{id}.pem'.format(id=id)
self._apply(self._upload_cert, listener, None, secret, md5, name)
if amphora and obj_id:
self._upload_cert(
amphora, obj_id, pem=secret, md5=md5, name=name)
return name
def _process_listener_pool_certs(self, listener):
def _process_listener_pool_certs(self, listener, amphora=None,
obj_id=None):
# {'POOL-ID': {
# 'client_cert': client_full_filename,
# 'ca_cert': ca_cert_full_filename,
@ -324,19 +456,20 @@ class HaproxyAmphoraLoadBalancerDriver(
pool_certs_dict = dict()
for pool in listener.pools:
if pool.id not in pool_certs_dict:
pool_certs_dict[pool.id] = self._process_pool_certs(listener,
pool)
pool_certs_dict[pool.id] = self._process_pool_certs(
listener, pool, amphora, obj_id)
for l7policy in listener.l7policies:
if (l7policy.redirect_pool and
l7policy.redirect_pool.id not in pool_certs_dict):
pool_certs_dict[l7policy.redirect_pool.id] = (
self._process_pool_certs(listener, l7policy.redirect_pool))
self._process_pool_certs(listener, l7policy.redirect_pool,
amphora, obj_id))
return pool_certs_dict
def _process_pool_certs(self, listener, pool):
def _process_pool_certs(self, listener, pool, amphora=None, obj_id=None):
pool_cert_dict = dict()
# Handle the cleint cert(s) and key
# Handle the client cert(s) and key
if pool.tls_certificate_id:
data = cert_parser.load_certificates_data(self.cert_manager, pool)
pem = cert_parser.build_pem(data)
@ -346,15 +479,18 @@ class HaproxyAmphoraLoadBalancerDriver(
pass
md5 = hashlib.md5(pem).hexdigest() # nosec
name = '{id}.pem'.format(id=data.id)
self._apply(self._upload_cert, listener, None, pem, md5, name)
if amphora and obj_id:
self._upload_cert(amphora, obj_id, pem=pem, md5=md5, name=name)
pool_cert_dict['client_cert'] = os.path.join(
CONF.haproxy_amphora.base_cert_dir, listener.id, name)
if pool.ca_tls_certificate_id:
name = self._process_secret(listener, pool.ca_tls_certificate_id)
name = self._process_secret(listener, pool.ca_tls_certificate_id,
amphora, obj_id)
pool_cert_dict['ca_cert'] = os.path.join(
CONF.haproxy_amphora.base_cert_dir, listener.id, name)
if pool.crl_container_id:
name = self._process_secret(listener, pool.crl_container_id)
name = self._process_secret(listener, pool.crl_container_id,
amphora, obj_id)
pool_cert_dict['crl'] = os.path.join(
CONF.haproxy_amphora.base_cert_dir, listener.id, name)
@ -362,13 +498,14 @@ class HaproxyAmphoraLoadBalancerDriver(
def _upload_cert(self, amp, listener_id, pem, md5, name):
try:
if self.client.get_cert_md5sum(
if self.clients[amp.api_version].get_cert_md5sum(
amp, listener_id, name, ignore=(404,)) == md5:
return
except exc.NotFound:
pass
self.client.upload_cert_pem(amp, listener_id, name, pem)
self.clients[amp.api_version].upload_cert_pem(
amp, listener_id, name, pem)
def update_amphora_agent_config(self, amphora, agent_config,
timeout_dict=None):
@ -388,8 +525,9 @@ class HaproxyAmphoraLoadBalancerDriver(
new values.
"""
try:
self.client.update_agent_config(amphora, agent_config,
timeout_dict=timeout_dict)
self._populate_amphora_api_version(amphora)
self.clients[amphora.api_version].update_agent_config(
amphora, agent_config, timeout_dict=timeout_dict)
except exc.NotFound:
LOG.debug('Amphora {} does not support the update_agent_config '
'API.'.format(amphora.id))
@ -404,10 +542,9 @@ class CustomHostNameCheckingAdapter(requests.adapters.HTTPAdapter):
self).cert_verify(conn, url, verify, cert)
class AmphoraAPIClient(object):
class AmphoraAPIClientBase(object):
def __init__(self):
super(AmphoraAPIClient, self).__init__()
self.secure = False
super(AmphoraAPIClientBase, self).__init__()
self.get = functools.partial(self.request, 'get')
self.post = functools.partial(self.request, 'post')
@ -415,38 +552,29 @@ class AmphoraAPIClient(object):
self.delete = functools.partial(self.request, 'delete')
self.head = functools.partial(self.request, 'head')
self.start_listener = functools.partial(self._action,
consts.AMP_ACTION_START)
self.stop_listener = functools.partial(self._action,
consts.AMP_ACTION_STOP)
self.reload_listener = functools.partial(self._action,
consts.AMP_ACTION_RELOAD)
self.start_vrrp = functools.partial(self._vrrp_action,
consts.AMP_ACTION_START)
self.stop_vrrp = functools.partial(self._vrrp_action,
consts.AMP_ACTION_STOP)
self.reload_vrrp = functools.partial(self._vrrp_action,
consts.AMP_ACTION_RELOAD)
self.session = requests.Session()
self.session.cert = CONF.haproxy_amphora.client_cert
self.ssl_adapter = CustomHostNameCheckingAdapter()
self.session.mount('https://', self.ssl_adapter)
def _base_url(self, ip):
def _base_url(self, ip, api_version=None):
if utils.is_ipv6_lla(ip):
ip = '[{ip}%{interface}]'.format(
ip=ip,
interface=CONF.haproxy_amphora.lb_network_interface)
elif utils.is_ipv6(ip):
ip = '[{ip}]'.format(ip=ip)
return "https://{ip}:{port}/{version}/".format(
if api_version:
return "https://{ip}:{port}/{version}/".format(
ip=ip,
port=CONF.haproxy_amphora.bind_port,
version=api_version)
return "https://{ip}:{port}/".format(
ip=ip,
port=CONF.haproxy_amphora.bind_port,
version=API_VERSION)
port=CONF.haproxy_amphora.bind_port)
def request(self, method, amp, path='/', timeout_dict=None, **kwargs):
def request(self, method, amp, path='/', timeout_dict=None,
retry_404=True, **kwargs):
cfg_ha_amp = CONF.haproxy_amphora
if timeout_dict is None:
timeout_dict = {}
@ -461,7 +589,7 @@ class AmphoraAPIClient(object):
LOG.debug("request url %s", path)
_request = getattr(self.session, method.lower())
_url = self._base_url(amp.lb_network_ip) + path
_url = self._base_url(amp.lb_network_ip, amp.api_version) + path
LOG.debug("request url %s", _url)
reqargs = {
'verify': CONF.haproxy_amphora.server_ca,
@ -474,7 +602,7 @@ class AmphoraAPIClient(object):
self.ssl_adapter.uuid = amp.id
exception = None
# Keep retrying
for a in six.moves.xrange(conn_max_retries):
for dummy in six.moves.xrange(conn_max_retries):
try:
with warnings.catch_warnings():
warnings.filterwarnings(
@ -490,6 +618,8 @@ class AmphoraAPIClient(object):
# amphora is not yet up, in which case retry.
# Otherwise return the response quickly.
if r.status_code == 404:
if not retry_404:
raise exc.NotFound()
LOG.debug('Got a 404 (content-type: %(content_type)s) -- '
'connection data: %(content)s',
{'content_type': content_type,
@ -517,6 +647,32 @@ class AmphoraAPIClient(object):
'exception': exception})
raise driver_except.TimeOutException()
def get_api_version(self, amp):
amp.api_version = None
r = self.get(amp, retry_404=False)
# Handle 404 special as we don't want to log an ERROR on 404
exc.check_exception(r, (404,))
if r.status_code == 404:
raise exc.NotFound()
return r.json()
class AmphoraAPIClient0_5(AmphoraAPIClientBase):
def __init__(self):
super(AmphoraAPIClient0_5, self).__init__()
self.start_listener = functools.partial(self._action,
consts.AMP_ACTION_START)
self.reload_listener = functools.partial(self._action,
consts.AMP_ACTION_RELOAD)
self.start_vrrp = functools.partial(self._vrrp_action,
consts.AMP_ACTION_START)
self.stop_vrrp = functools.partial(self._vrrp_action,
consts.AMP_ACTION_STOP)
self.reload_vrrp = functools.partial(self._vrrp_action,
consts.AMP_ACTION_RELOAD)
def upload_config(self, amp, listener_id, config, timeout_dict=None):
r = self.put(
amp,
@ -525,14 +681,6 @@ class AmphoraAPIClient(object):
data=config)
return exc.check_exception(r)
def get_listener_status(self, amp, listener_id):
r = self.get(
amp,
'listeners/{listener_id}'.format(listener_id=listener_id))
if exc.check_exception(r):
return r.json()
return None
def _action(self, action, amp, listener_id, timeout_dict=None):
r = self.put(amp, 'listeners/{listener_id}/{action}'.format(
listener_id=listener_id, action=action), timeout_dict=timeout_dict)
@ -623,3 +771,133 @@ class AmphoraAPIClient(object):
def update_agent_config(self, amp, agent_config, timeout_dict=None):
r = self.put(amp, 'config', timeout_dict, data=agent_config)
return exc.check_exception(r)
class AmphoraAPIClient1_0(AmphoraAPIClientBase):
def __init__(self):
super(AmphoraAPIClient1_0, self).__init__()
self.start_listener = functools.partial(self._action,
consts.AMP_ACTION_START)
self.reload_listener = functools.partial(self._action,
consts.AMP_ACTION_RELOAD)
self.start_vrrp = functools.partial(self._vrrp_action,
consts.AMP_ACTION_START)
self.stop_vrrp = functools.partial(self._vrrp_action,
consts.AMP_ACTION_STOP)
self.reload_vrrp = functools.partial(self._vrrp_action,
consts.AMP_ACTION_RELOAD)
def upload_config(self, amp, loadbalancer_id, config, timeout_dict=None):
r = self.put(
amp,
'loadbalancer/{amphora_id}/{loadbalancer_id}/haproxy'.format(
amphora_id=amp.id, loadbalancer_id=loadbalancer_id),
timeout_dict, data=config)
return exc.check_exception(r)
def get_listener_status(self, amp, listener_id):
r = self.get(
amp,
'listeners/{listener_id}'.format(listener_id=listener_id))
if exc.check_exception(r):
return r.json()
return None
def _action(self, action, amp, object_id, timeout_dict=None):
r = self.put(
amp, 'loadbalancer/{object_id}/{action}'.format(
object_id=object_id, action=action),
timeout_dict=timeout_dict)
return exc.check_exception(r)
def upload_cert_pem(self, amp, loadbalancer_id, pem_filename, pem_file):
r = self.put(
amp,
'loadbalancer/{loadbalancer_id}/certificates/{filename}'.format(
loadbalancer_id=loadbalancer_id, filename=pem_filename),
data=pem_file)
return exc.check_exception(r)
def get_cert_md5sum(self, amp, loadbalancer_id, pem_filename,
ignore=tuple()):
r = self.get(
amp,
'loadbalancer/{loadbalancer_id}/certificates/{filename}'.format(
loadbalancer_id=loadbalancer_id, filename=pem_filename))
if exc.check_exception(r, ignore):
return r.json().get("md5sum")
return None
def delete_cert_pem(self, amp, loadbalancer_id, pem_filename):
r = self.delete(
amp,
'loadbalancer/{loadbalancer_id}/certificates/{filename}'.format(
loadbalancer_id=loadbalancer_id, filename=pem_filename))
return exc.check_exception(r, (404,))
def update_cert_for_rotation(self, amp, pem_file):
r = self.put(amp, 'certificate', data=pem_file)
return exc.check_exception(r)
def delete_listener(self, amp, object_id):
r = self.delete(
amp, 'listeners/{object_id}'.format(object_id=object_id))
return exc.check_exception(r, (404,))
def get_info(self, amp):
r = self.get(amp, "info")
if exc.check_exception(r):
return r.json()
return None
def get_details(self, amp):
r = self.get(amp, "details")
if exc.check_exception(r):
return r.json()
return None
def get_all_listeners(self, amp):
r = self.get(amp, "listeners")
if exc.check_exception(r):
return r.json()
return None
def plug_network(self, amp, port):
r = self.post(amp, 'plug/network',
json=port)
return exc.check_exception(r)
def plug_vip(self, amp, vip, net_info):
r = self.post(amp,
'plug/vip/{vip}'.format(vip=vip),
json=net_info)
return exc.check_exception(r)
def upload_vrrp_config(self, amp, config):
r = self.put(amp, 'vrrp/upload', data=config)
return exc.check_exception(r)
def _vrrp_action(self, action, amp):
r = self.put(amp, 'vrrp/{action}'.format(action=action))
return exc.check_exception(r)
def get_interface(self, amp, ip_addr, timeout_dict=None):
r = self.get(amp, 'interface/{ip_addr}'.format(ip_addr=ip_addr),
timeout_dict=timeout_dict)
if exc.check_exception(r):
return r.json()
return None
def upload_udp_config(self, amp, listener_id, config, timeout_dict=None):
r = self.put(
amp,
'listeners/{amphora_id}/{listener_id}/udp_listener'.format(
amphora_id=amp.id, listener_id=listener_id), timeout_dict,
data=config)
return exc.check_exception(r)
def update_agent_config(self, amp, agent_config, timeout_dict=None):
r = self.put(amp, 'config', timeout_dict, data=agent_config)
return exc.check_exception(r)

View File

@ -45,13 +45,14 @@ class KeepalivedAmphoraDriverMixin(driver_base.VRRPDriverMixin):
lambda amp: amp.status == constants.AMPHORA_ALLOCATED,
loadbalancer.amphorae):
self._populate_amphora_api_version(amp)
# Get the VIP subnet prefix for the amphora
vip_cidr = amphorae_network_config[amp.id].vip_subnet.cidr
# Generate Keepalived configuration from loadbalancer object
config = templater.build_keepalived_config(
loadbalancer, amp, vip_cidr)
self.client.upload_vrrp_config(amp, config)
self.clients[amp.api_version].upload_vrrp_config(amp, config)
def stop_vrrp_service(self, loadbalancer):
"""Stop the vrrp services running on the loadbalancer's amphorae
@ -65,7 +66,8 @@ class KeepalivedAmphoraDriverMixin(driver_base.VRRPDriverMixin):
lambda amp: amp.status == constants.AMPHORA_ALLOCATED,
loadbalancer.amphorae):
self.client.stop_vrrp(amp)
self._populate_amphora_api_version(amp)
self.clients[amp.api_version].stop_vrrp(amp)
def start_vrrp_service(self, loadbalancer):
"""Start the VRRP services of all amphorae of the loadbalancer
@ -80,7 +82,8 @@ class KeepalivedAmphoraDriverMixin(driver_base.VRRPDriverMixin):
loadbalancer.amphorae):
LOG.debug("Start VRRP Service on amphora %s .", amp.lb_network_ip)
self.client.start_vrrp(amp)
self._populate_amphora_api_version(amp)
self.clients[amp.api_version].start_vrrp(amp)
def reload_vrrp_service(self, loadbalancer):
"""Reload the VRRP services of all amphorae of the loadbalancer
@ -94,8 +97,10 @@ class KeepalivedAmphoraDriverMixin(driver_base.VRRPDriverMixin):
lambda amp: amp.status == constants.AMPHORA_ALLOCATED,
loadbalancer.amphorae):
self.client.reload_vrrp(amp)
self._populate_amphora_api_version(amp)
self.clients[amp.api_version].reload_vrrp(amp)
def get_vrrp_interface(self, amphora, timeout_dict=None):
return self.client.get_interface(
self._populate_amphora_api_version(amphora)
return self.clients[amphora.api_version].get_interface(
amphora, amphora.vrrp_ip, timeout_dict=timeout_dict)['interface']

View File

@ -37,44 +37,41 @@ class NoopManager(object):
super(NoopManager, self).__init__()
self.amphoraconfig = {}
def update_amphora_listeners(self, listeners, amphora_index,
amphorae, timeout_dict):
amphora_id = amphorae[amphora_index].id
for listener in listeners:
def update_amphora_listeners(self, loadbalancer, amphora, timeout_dict):
amphora_id = amphora.id
for listener in loadbalancer.listeners:
LOG.debug("Amphora noop driver update_amphora_listeners, "
"listener %s, amphora %s, timeouts %s", listener.id,
amphora_id, timeout_dict)
self.amphoraconfig[(listener.id, amphora_id)] = (
listener, amphora_id, timeout_dict, "update_amp")
def update(self, listener, vip):
def update(self, loadbalancer):
LOG.debug("Amphora %s no-op, update listener %s, vip %s",
self.__class__.__name__, listener.protocol_port,
vip.ip_address)
self.amphoraconfig[(listener.protocol_port,
vip.ip_address)] = (listener, vip, 'active')
def stop(self, listener, vip):
LOG.debug("Amphora %s no-op, stop listener %s, vip %s",
self.__class__.__name__,
listener.protocol_port, vip.ip_address)
self.amphoraconfig[(listener.protocol_port,
vip.ip_address)] = (listener, vip, 'stop')
tuple(l.protocol_port for l in loadbalancer.listeners),
loadbalancer.vip.ip_address)
self.amphoraconfig[
(tuple(l.protocol_port for l in loadbalancer.listeners),
loadbalancer.vip.ip_address)] = (loadbalancer.listeners,
loadbalancer.vip,
'active')
def start(self, listener, vip, amphora=None):
LOG.debug("Amphora %s no-op, start listener %s, vip %s, amp %s",
self.__class__.__name__,
listener.protocol_port, vip.ip_address, amphora)
self.amphoraconfig[(listener.protocol_port,
vip.ip_address, amphora)] = (listener, vip,
amphora, 'start')
def start(self, loadbalancer, amphora=None):
LOG.debug("Amphora %s no-op, start listeners, lb %s, amp %s",
self.__class__.__name__, loadbalancer.id, amphora)
self.amphoraconfig[
(loadbalancer.id, amphora.id)] = (loadbalancer, amphora,
'start')
def delete(self, listener, vip):
def delete(self, listener):
LOG.debug("Amphora %s no-op, delete listener %s, vip %s",
self.__class__.__name__,
listener.protocol_port, vip.ip_address)
listener.protocol_port,
listener.load_balancer.vip.ip_address)
self.amphoraconfig[(listener.protocol_port,
vip.ip_address)] = (listener, vip, 'delete')
listener.load_balancer.vip.ip_address)] = (
listener, listener.load_balancer.vip, 'delete')
def get_info(self, amphora):
LOG.debug("Amphora %s no-op, info amphora %s",
@ -124,27 +121,22 @@ class NoopAmphoraLoadBalancerDriver(
super(NoopAmphoraLoadBalancerDriver, self).__init__()
self.driver = NoopManager()
def update_amphora_listeners(self, listeners, amphora_index,
amphorae, timeout_dict):
def update_amphora_listeners(self, loadbalancer, amphora, timeout_dict):
self.driver.update_amphora_listeners(listeners, amphora_index,
amphorae, timeout_dict)
self.driver.update_amphora_listeners(loadbalancer, amphora,
timeout_dict)
def update(self, listener, vip):
def update(self, loadbalancer):
self.driver.update(listener, vip)
self.driver.update(loadbalancer)
def stop(self, listener, vip):
def start(self, loadbalancer, amphora=None):
self.driver.stop(listener, vip)
self.driver.start(loadbalancer, amphora)
def start(self, listener, vip, amphora=None):
def delete(self, listener):
self.driver.start(listener, vip, amphora)
def delete(self, listener, vip):
self.driver.delete(listener, vip)
self.driver.delete(listener)
def get_info(self, amphora):

View File

@ -122,3 +122,6 @@ def main():
process.join()
except KeyboardInterrupt:
process_cleanup()
if __name__ == "__main__":
main()

View File

@ -36,3 +36,6 @@ def main():
args=(CONF,))
oslo_config_glue.setup(sm, CONF, reload_method="mutate")
sm.run()
if __name__ == "__main__":
main()

View File

@ -0,0 +1,475 @@
# Copyright (c) 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 os
import re
import jinja2
import six
from octavia.common.config import cfg
from octavia.common import constants
from octavia.common import utils as octavia_utils
PROTOCOL_MAP = {
constants.PROTOCOL_TCP: 'tcp',
constants.PROTOCOL_HTTP: 'http',
constants.PROTOCOL_HTTPS: 'tcp',
constants.PROTOCOL_PROXY: 'proxy',
constants.PROTOCOL_TERMINATED_HTTPS: 'http'
}
BALANCE_MAP = {
constants.LB_ALGORITHM_ROUND_ROBIN: 'roundrobin',
constants.LB_ALGORITHM_LEAST_CONNECTIONS: 'leastconn',
constants.LB_ALGORITHM_SOURCE_IP: 'source'
}
CLIENT_AUTH_MAP = {constants.CLIENT_AUTH_NONE: 'none',
constants.CLIENT_AUTH_OPTIONAL: 'optional',
constants.CLIENT_AUTH_MANDATORY: 'required'}
ACTIVE_PENDING_STATUSES = constants.SUPPORTED_PROVISIONING_STATUSES + (
constants.DEGRADED,)
BASE_PATH = '/var/lib/octavia'
BASE_CRT_DIR = BASE_PATH + '/certs'
HAPROXY_TEMPLATE = os.path.abspath(
os.path.join(os.path.dirname(__file__),
'templates/haproxy.cfg.j2'))
CONF = cfg.CONF
JINJA_ENV = None
class JinjaTemplater(object):
def __init__(self,
base_amp_path=None,
base_crt_dir=None,
haproxy_template=None,
log_http=None,
log_server=None,
connection_logging=True):
"""HaProxy configuration generation
:param base_amp_path: Base path for amphora data
:param base_crt_dir: Base directory for certificate storage
:param haproxy_template: Absolute path to Jinja template
:param log_http: Haproxy HTTP logging path
:param log_server: Haproxy Server logging path
:param connection_logging: enable logging connections in haproxy
"""
self.base_amp_path = base_amp_path or BASE_PATH
self.base_crt_dir = base_crt_dir or BASE_CRT_DIR
self.haproxy_template = haproxy_template or HAPROXY_TEMPLATE
self.log_http = log_http
self.log_server = log_server
self.connection_logging = connection_logging
def build_config(self, host_amphora, listeners, tls_cert,
haproxy_versions, socket_path=None,
client_ca_filename=None, client_crl=None,
pool_tls_certs=None):
"""Convert a logical configuration to the HAProxy version
:param host_amphora: The Amphora this configuration is hosted on
:param listener: The listener configuration
:param tls_cert: The TLS certificates for the listener
:param socket_path: The socket path for Haproxy process
:return: Rendered configuration
"""
# Check for any backward compatibility items we need to check
# This is done here for upgrade scenarios where one amp in a
# pair might be running an older amphora version.
feature_compatibility = {}
# Is it newer than haproxy 1.5?
if not (int(haproxy_versions[0]) < 2 and int(haproxy_versions[1]) < 6):
feature_compatibility[constants.HTTP_REUSE] = True
return self.render_loadbalancer_obj(
host_amphora, listeners, tls_cert=tls_cert,
socket_path=socket_path,
feature_compatibility=feature_compatibility,
client_ca_filename=client_ca_filename, client_crl=client_crl,
pool_tls_certs=pool_tls_certs)
def _get_template(self):
"""Returns the specified Jinja configuration template."""
global JINJA_ENV
if not JINJA_ENV:
template_loader = jinja2.FileSystemLoader(
searchpath=os.path.dirname(self.haproxy_template))
JINJA_ENV = jinja2.Environment(
autoescape=True,
loader=template_loader,
trim_blocks=True,
lstrip_blocks=True)
JINJA_ENV.filters['hash_amp_id'] = octavia_utils.base64_sha1_string
return JINJA_ENV.get_template(os.path.basename(self.haproxy_template))
def render_loadbalancer_obj(self, host_amphora, listeners,
tls_cert=None, socket_path=None,
feature_compatibility=None,
client_ca_filename=None, client_crl=None,
pool_tls_certs=None):
"""Renders a templated configuration from a load balancer object
:param host_amphora: The Amphora this configuration is hosted on
:param listener: The listener configuration
:param tls_cert: The TLS certificates for the listener
:param client_ca_filename: The CA certificate for client authorization
:param socket_path: The socket path for Haproxy process
:return: Rendered configuration
"""
feature_compatibility = feature_compatibility or {}
loadbalancer = self._transform_loadbalancer(
host_amphora,
listeners[0].load_balancer,
listeners,
tls_cert,
feature_compatibility,
client_ca_filename=client_ca_filename,
client_crl=client_crl,
pool_tls_certs=pool_tls_certs)
if not socket_path:
socket_path = '%s/%s.sock' % (self.base_amp_path,
listeners[0].load_balancer.id)
return self._get_template().render(
{'loadbalancer': loadbalancer,
'stats_sock': socket_path,
'log_http': self.log_http,
'log_server': self.log_server,
'connection_logging': self.connection_logging},
constants=constants)
def _transform_loadbalancer(self, host_amphora, loadbalancer, listeners,
tls_cert, feature_compatibility,
client_ca_filename=None, client_crl=None,
pool_tls_certs=None):
"""Transforms a load balancer into an object that will
be processed by the templating system
"""
listener_transforms = []
for listener in listeners:
if listener.protocol == constants.PROTOCOL_UDP:
continue
listener_transforms.append(self._transform_listener(
listener, tls_cert, feature_compatibility, loadbalancer,
client_ca_filename=client_ca_filename, client_crl=client_crl,
pool_tls_certs=pool_tls_certs))
ret_value = {
'id': loadbalancer.id,
'vip_address': loadbalancer.vip.ip_address,
'listeners': listener_transforms,
'topology': loadbalancer.topology,
'enabled': loadbalancer.enabled,
'peer_port': listeners[0].peer_port,
'host_amphora': self._transform_amphora(
host_amphora, feature_compatibility),
'amphorae': loadbalancer.amphorae
}
# NOTE(sbalukoff): Global connection limit should be a sum of all
# listeners' connection limits.
connection_limit_sum = 0
for listener in listeners:
if listener.protocol == constants.PROTOCOL_UDP:
continue
if listener.connection_limit and listener.connection_limit > -1:
connection_limit_sum += listener.connection_limit
else:
# If *any* listener has no connection limit, global = MAX
connection_limit_sum = constants.HAPROXY_MAX_MAXCONN
# If there's a limit between 0 and MAX, set it, otherwise just set MAX
if 0 < connection_limit_sum < constants.HAPROXY_MAX_MAXCONN:
ret_value['global_connection_limit'] = connection_limit_sum
else:
ret_value['global_connection_limit'] = (
constants.HAPROXY_MAX_MAXCONN)
return ret_value
def _transform_amphora(self, amphora, feature_compatibility):
"""Transform an amphora into an object that will
be processed by the templating system.
"""
return {
'id': amphora.id,
'lb_network_ip': amphora.lb_network_ip,
'vrrp_ip': amphora.vrrp_ip,
'ha_ip': amphora.ha_ip,
'vrrp_port_id': amphora.vrrp_port_id,
'ha_port_id': amphora.ha_port_id,
'role': amphora.role,
'status': amphora.status,
'vrrp_interface': amphora.vrrp_interface,
'vrrp_priority': amphora.vrrp_priority
}
def _transform_listener(self, listener, tls_cert, feature_compatibility,
loadbalancer, client_ca_filename=None,
client_crl=None, pool_tls_certs=None):
"""Transforms a listener into an object that will
be processed by the templating system
"""
ret_value = {
'id': listener.id,
'protocol_port': listener.protocol_port,
'protocol_mode': PROTOCOL_MAP[listener.protocol],
'protocol': listener.protocol,
'insert_headers': listener.insert_headers,
'enabled': listener.enabled,
'timeout_client_data': (
listener.timeout_client_data or
CONF.haproxy_amphora.timeout_client_data),
'timeout_member_connect': (
listener.timeout_member_connect or
CONF.haproxy_amphora.timeout_member_connect),
'timeout_member_data': (
listener.timeout_member_data or
CONF.haproxy_amphora.timeout_member_data),
'timeout_tcp_inspect': (listener.timeout_tcp_inspect or
CONF.haproxy_amphora.timeout_tcp_inspect),
}
if listener.connection_limit and listener.connection_limit > -1:
ret_value['connection_limit'] = listener.connection_limit
else:
ret_value['connection_limit'] = constants.HAPROXY_MAX_MAXCONN
if listener.tls_certificate_id:
ret_value['default_tls_path'] = '%s.pem' % (
os.path.join(self.base_crt_dir,
loadbalancer.id,
tls_cert.id))
if listener.sni_containers:
ret_value['crt_dir'] = os.path.join(
self.base_crt_dir, loadbalancer.id)
if listener.client_ca_tls_certificate_id:
ret_value['client_ca_tls_path'] = '%s' % (
os.path.join(self.base_crt_dir, loadbalancer.id,
client_ca_filename))
ret_value['client_auth'] = CLIENT_AUTH_MAP.get(
listener.client_authentication)
if listener.client_crl_container_id:
ret_value['client_crl_path'] = '%s' % (
os.path.join(self.base_crt_dir, loadbalancer.id, client_crl))
pools = []
for x in listener.pools:
kwargs = {}
if pool_tls_certs and pool_tls_certs.get(x.id):
kwargs = {'pool_tls_certs': pool_tls_certs.get(x.id)}
pools.append(self._transform_pool(
x, feature_compatibility, **kwargs))
ret_value['pools'] = pools
if listener.default_pool:
for pool in pools:
if pool['id'] == listener.default_pool.id:
ret_value['default_pool'] = pool
break
l7policies = [self._transform_l7policy(
x, feature_compatibility, pool_tls_certs)
for x in listener.l7policies]
ret_value['l7policies'] = l7policies
return ret_value
def _transform_pool(self, pool, feature_compatibility,
pool_tls_certs=None):
"""Transforms a pool into an object that will
be processed by the templating system
"""
ret_value = {
'id': pool.id,
'protocol': PROTOCOL_MAP[pool.protocol],
'proxy_protocol': pool.protocol == constants.PROTOCOL_PROXY,
'lb_algorithm': BALANCE_MAP.get(pool.lb_algorithm, 'roundrobin'),
'members': [],
'health_monitor': '',
'session_persistence': '',
'enabled': pool.enabled,
'operating_status': pool.operating_status,
'stick_size': CONF.haproxy_amphora.haproxy_stick_size,
constants.HTTP_REUSE: feature_compatibility.get(
constants.HTTP_REUSE, False),
'ca_tls_path': '',
'crl_path': '',
'tls_enabled': pool.tls_enabled
}
members = [self._transform_member(x, feature_compatibility)
for x in pool.members]
ret_value['members'] = members
if pool.health_monitor:
ret_value['health_monitor'] = self._transform_health_monitor(
pool.health_monitor, feature_compatibility)
if pool.session_persistence:
ret_value[
'session_persistence'] = self._transform_session_persistence(
pool.session_persistence, feature_compatibility)
if (pool.tls_certificate_id and pool_tls_certs and
pool_tls_certs.get('client_cert')):
ret_value['client_cert'] = pool_tls_certs.get('client_cert')
if (pool.ca_tls_certificate_id and pool_tls_certs and
pool_tls_certs.get('ca_cert')):
ret_value['ca_cert'] = pool_tls_certs.get('ca_cert')
if (pool.crl_container_id and pool_tls_certs and
pool_tls_certs.get('crl')):
ret_value['crl'] = pool_tls_certs.get('crl')
return ret_value
@staticmethod
def _transform_session_persistence(persistence, feature_compatibility):
"""Transforms session persistence into an object that will
be processed by the templating system
"""
return {
'type': persistence.type,
'cookie_name': persistence.cookie_name
}
@staticmethod
def _transform_member(member, feature_compatibility):
"""Transforms a member into an object that will
be processed by the templating system
"""
return {
'id': member.id,
'address': member.ip_address,
'protocol_port': member.protocol_port,
'weight': member.weight,
'enabled': member.enabled,
'subnet_id': member.subnet_id,
'operating_status': member.operating_status,
'monitor_address': member.monitor_address,
'monitor_port': member.monitor_port,
'backup': member.backup
}
def _transform_health_monitor(self, monitor, feature_compatibility):
"""Transforms a health monitor into an object that will
be processed by the templating system
"""
codes = None
if monitor.expected_codes:
codes = '|'.join(self._expand_expected_codes(
monitor.expected_codes))
return {
'id': monitor.id,
'type': monitor.type,
'delay': monitor.delay,
'timeout': monitor.timeout,
'fall_threshold': monitor.fall_threshold,
'rise_threshold': monitor.rise_threshold,
'http_method': monitor.http_method,
'url_path': monitor.url_path,
'expected_codes': codes,
'enabled': monitor.enabled,
'http_version': monitor.http_version,
'domain_name': monitor.domain_name,
}
def _transform_l7policy(self, l7policy, feature_compatibility,
pool_tls_certs=None):
"""Transforms an L7 policy into an object that will
be processed by the templating system
"""
ret_value = {
'id': l7policy.id,
'action': l7policy.action,
'redirect_url': l7policy.redirect_url,
'redirect_prefix': l7policy.redirect_prefix,
'enabled': l7policy.enabled
}
if l7policy.redirect_pool:
kwargs = {}
if pool_tls_certs and pool_tls_certs.get(
l7policy.redirect_pool.id):
kwargs = {'pool_tls_certs':
pool_tls_certs.get(l7policy.redirect_pool.id)}
ret_value['redirect_pool'] = self._transform_pool(
l7policy.redirect_pool, feature_compatibility, **kwargs)
else:
ret_value['redirect_pool'] = None
if (l7policy.action in [constants.L7POLICY_ACTION_REDIRECT_TO_URL,
constants.L7POLICY_ACTION_REDIRECT_PREFIX] and
l7policy.redirect_http_code):
ret_value['redirect_http_code'] = l7policy.redirect_http_code
else:
ret_value['redirect_http_code'] = None
l7rules = [self._transform_l7rule(x, feature_compatibility)
for x in l7policy.l7rules if x.enabled]
ret_value['l7rules'] = l7rules
return ret_value
def _transform_l7rule(self, l7rule, feature_compatibility):
"""Transforms an L7 rule into an object that will
be processed by the templating system
"""
return {
'id': l7rule.id,
'type': l7rule.type,
'compare_type': l7rule.compare_type,
'key': l7rule.key,
'value': self._escape_haproxy_config_string(l7rule.value),
'invert': l7rule.invert,
'enabled': l7rule.enabled
}
@staticmethod
def _escape_haproxy_config_string(value):
"""Escapes certain characters in a given string such that
haproxy will parse the string as a single value
"""
# Escape backslashes first
value = re.sub(r'\\', r'\\\\', value)
# Spaces next
value = re.sub(' ', '\\ ', value)
return value
@staticmethod
def _expand_expected_codes(codes):
"""Expand the expected code string in set of codes.
200-204 -> 200, 201, 202, 204
200, 203 -> 200, 203
"""
retval = set()
for code in codes.replace(',', ' ').split(' '):
code = code.strip()
if not code:
continue
elif '-' in code:
low, hi = code.split('-')[:2]
retval.update(
str(i) for i in six.moves.xrange(int(low), int(hi) + 1))
else:
retval.add(code)
return sorted(retval)

View File

@ -0,0 +1,52 @@
{# Copyright (c) 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.
#
#}
# Configuration for loadbalancer {{ loadbalancer_id }}
global
daemon
user nobody
log {{ log_http | default('/dev/log', true)}} local0
log {{ log_server | default('/dev/log', true)}} local1 notice
stats socket {{ sock_path }} mode 0666 level user
{% if loadbalancer.global_connection_limit is defined %}
maxconn {{ loadbalancer.global_connection_limit }}
{% endif %}
{% set found_ns = namespace(found=false) %}
{% for listener in loadbalancer.listeners if listener.enabled %}
{% for pool in listener.pools if pool.enabled %}
{% if pool.health_monitor and pool.health_monitor.enabled and
pool.health_monitor.type == constants.HEALTH_MONITOR_PING and
found_ns.found == false %}
{% set found_ns.found = true %}
external-check
{% endif %}
{% endfor %}
{% endfor %}
defaults
{% if connection_logging %}
log global
{% else %}
no log
{% endif %}
retries 3
option redispatch
option splice-request
option splice-response
option http-keep-alive
{% block peers %}{% endblock peers %}
{% block proxies %}{% endblock proxies %}

View File

@ -0,0 +1,40 @@
{# Copyright (c) 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.
#
#}
{% extends 'base.j2' %}
{% from 'macros.j2' import frontend_macro, backend_macro %}
{% from 'macros.j2' import peers_macro %}
{% set loadbalancer_id = loadbalancer.id %}
{% set sock_path = stats_sock %}
{% block peers %}
{{ peers_macro(constants, loadbalancer) }}
{% endblock peers %}
{% block proxies %}
{% if loadbalancer.enabled %}
{% for listener in loadbalancer.listeners if listener.enabled %}
{{- frontend_macro(constants, listener, loadbalancer.vip_address) }}
{% for pool in listener.pools if pool.enabled %}
{{- backend_macro(constants, listener, pool, loadbalancer) }}
{% endfor %}
{% endfor %}
{% endif %}
{% endblock proxies %}

View File

@ -0,0 +1,377 @@
{# Copyright (c) 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.
#
#}
{% macro peers_macro(constants, loadbalancer) %}
{% if loadbalancer.topology == constants.TOPOLOGY_ACTIVE_STANDBY %}
peers {{ "%s_peers"|format(loadbalancer.id.replace("-", ""))|trim() }}
{% for amp in loadbalancer.amphorae if (
amp.status == constants.AMPHORA_ALLOCATED) %}
{# HAProxy has peer name limitations, thus the hash filter #}
peer {{ amp.id|hash_amp_id|replace('=', '') }} {{
amp.vrrp_ip }}:{{ constants.HAPROXY_BASE_PEER_PORT }}
{% endfor %}
{% endif %}
{% endmacro %}
{% macro bind_macro(constants, listener, lb_vip_address) %}
{% if listener.default_tls_path %}
{% set def_crt_opt = ("ssl crt %s"|format(
listener.default_tls_path)|trim()) %}
{% else %}
{% set def_crt_opt = "" %}
{% endif %}
{% if listener.crt_dir %}
{% set crt_dir_opt = "crt %s"|format(listener.crt_dir)|trim() %}
{% else %}
{% set crt_dir_opt = "" %}
{% endif %}
{% if listener.client_ca_tls_path and listener.client_auth %}
{% set client_ca_opt = "ca-file %s verify %s"|format(listener.client_ca_tls_path, listener.client_auth)|trim() %}
{% else %}
{% set client_ca_opt = "" %}
{% endif %}
{% if listener.client_crl_path and listener.client_ca_tls_path %}
{% set ca_crl_opt = "crl-file %s"|format(listener.client_crl_path)|trim() %}
{% else %}
{% set ca_crl_opt = "" %}
{% endif %}
bind {{ lb_vip_address }}:{{ listener.protocol_port }} {{
"%s %s %s %s"|format(def_crt_opt, crt_dir_opt, client_ca_opt, ca_crl_opt)|trim() }}
{% endmacro %}
{% macro l7rule_compare_type_macro(constants, ctype) %}
{% if ctype == constants.L7RULE_COMPARE_TYPE_REGEX %}
{{- "-m reg" -}}
{% elif ctype == constants.L7RULE_COMPARE_TYPE_STARTS_WITH %}
{{- "-m beg" -}}
{% elif ctype == constants.L7RULE_COMPARE_TYPE_ENDS_WITH %}
{{- "-m end" -}}
{% elif ctype == constants.L7RULE_COMPARE_TYPE_CONTAINS %}
{{- "-m sub" -}}
{% elif ctype == constants.L7RULE_COMPARE_TYPE_EQUAL_TO %}
{{- "-m str" -}}
{% endif %}
{% endmacro %}
{% macro l7rule_macro(constants, l7rule) %}
{% if l7rule.type == constants.L7RULE_TYPE_HOST_NAME %}
acl {{ l7rule.id }} req.hdr(host) -i {{ l7rule_compare_type_macro(
constants, l7rule.compare_type) }} {{ l7rule.value }}
{% elif l7rule.type == constants.L7RULE_TYPE_PATH %}
acl {{ l7rule.id }} path {{ l7rule_compare_type_macro(
constants, l7rule.compare_type) }} {{ l7rule.value }}
{% elif l7rule.type == constants.L7RULE_TYPE_FILE_TYPE %}
acl {{ l7rule.id }} path_end {{ l7rule_compare_type_macro(
constants, l7rule.compare_type) }} {{ l7rule.value }}
{% elif l7rule.type == constants.L7RULE_TYPE_HEADER %}
acl {{ l7rule.id }} req.hdr({{ l7rule.key }}) {{
l7rule_compare_type_macro(
constants, l7rule.compare_type) }} {{ l7rule.value }}
{% elif l7rule.type == constants.L7RULE_TYPE_COOKIE %}
acl {{ l7rule.id }} req.cook({{ l7rule.key }}) {{
l7rule_compare_type_macro(
constants, l7rule.compare_type) }} {{ l7rule.value }}
{% elif l7rule.type == constants.L7RULE_TYPE_SSL_CONN_HAS_CERT %}
acl {{ l7rule.id }} ssl_c_used
{% elif l7rule.type == constants.L7RULE_TYPE_SSL_VERIFY_RESULT %}
acl {{ l7rule.id }} ssl_c_verify eq {{ l7rule.value }}
{% elif l7rule.type == constants.L7RULE_TYPE_SSL_DN_FIELD %}
acl {{ l7rule.id }} ssl_c_s_dn({{ l7rule.key }}) {{
l7rule_compare_type_macro(
constants, l7rule.compare_type) }} {{ l7rule.value }}
{% endif %}
{% endmacro %}
{% macro l7rule_invert_macro(invert) %}
{% if invert %}
{{- "!" -}}
{% endif %}
{% endmacro %}
{% macro l7rule_list_macro(l7policy) %}
{% for l7rule in l7policy.l7rules %}
{{- " " -}}{{- l7rule_invert_macro(l7rule.invert) -}}{{- l7rule.id -}}
{% endfor %}
{% endmacro %}
{% macro l7policy_macro(constants, l7policy, listener) %}
{% for l7rule in l7policy.l7rules %}
{{- l7rule_macro(constants, l7rule) -}}
{% endfor %}
{% if l7policy.redirect_http_code %}
{% set redirect_http_code_opt = " code %s"|format(
l7policy.redirect_http_code) %}
{% else %}
{% set redirect_http_code_opt = "" %}
{% endif %}
{% if l7policy.action == constants.L7POLICY_ACTION_REJECT %}
http-request deny if{{ l7rule_list_macro(l7policy) }}
{% elif l7policy.action == constants.L7POLICY_ACTION_REDIRECT_TO_URL %}
redirect {{- redirect_http_code_opt }} location {{ l7policy.redirect_url }} if{{ l7rule_list_macro(l7policy) }}
{% elif l7policy.action == constants.L7POLICY_ACTION_REDIRECT_TO_POOL and l7policy.redirect_pool.enabled %}
use_backend {{ l7policy.redirect_pool.id }}:{{ listener.id }} if{{ l7rule_list_macro(l7policy) }}
{% elif l7policy.action == constants.L7POLICY_ACTION_REDIRECT_PREFIX %}
redirect {{- redirect_http_code_opt }} prefix {{ l7policy.redirect_prefix }} if{{ l7rule_list_macro(l7policy) }}
{% endif %}
{% endmacro %}
{% macro frontend_macro(constants, listener, lb_vip_address) %}
frontend {{ listener.id }}
{% if (listener.protocol.lower() ==
constants.PROTOCOL_TERMINATED_HTTPS.lower() or
listener.protocol.lower() ==
constants.PROTOCOL_HTTP.lower()) %}
option httplog
{% else %}
option tcplog
{% endif %}
{% if listener.connection_limit is defined %}
maxconn {{ listener.connection_limit }}
{% endif %}
{% if (listener.protocol.lower() ==
constants.PROTOCOL_TERMINATED_HTTPS.lower()) %}
redirect scheme https if !{ ssl_fc }
{% endif %}
{{ bind_macro(constants, listener, lb_vip_address)|trim() }}
mode {{ listener.protocol_mode }}
{% for l7policy in listener.l7policies if (l7policy.enabled and
l7policy.l7rules|length > 0) %}
{{- l7policy_macro(constants, l7policy, listener) -}}
{% endfor %}
{% if listener.default_pool and listener.default_pool.enabled %}
default_backend {{ listener.default_pool.id }}:{{ listener.id }}
{% endif %}
timeout client {{ listener.timeout_client_data }}
{% if listener.timeout_tcp_inspect %}
tcp-request inspect-delay {{ listener.timeout_tcp_inspect }}
{% endif %}
{% endmacro %}
{% macro member_macro(constants, pool, member) %}
{% if pool.health_monitor and pool.health_monitor.enabled %}
{% if member.monitor_address %}
{% set monitor_addr_opt = " addr %s"|format(member.monitor_address) %}
{% else %}
{% set monitor_addr_opt = "" %}
{% endif %}
{% if member.monitor_port %}
{% set monitor_port_opt = " port %s"|format(member.monitor_port) %}
{% else %}
{% set monitor_port_opt = "" %}
{% endif %}
{% if pool.health_monitor.type == constants.HEALTH_MONITOR_HTTPS %}
{% set monitor_ssl_opt = " check-ssl verify none" %}
{% else %}
{% set monitor_ssl_opt = "" %}
{% endif %}
{% set hm_opt = " check%s inter %ds fall %d rise %d%s%s"|format(
monitor_ssl_opt, pool.health_monitor.delay,
pool.health_monitor.fall_threshold,
pool.health_monitor.rise_threshold, monitor_addr_opt,
monitor_port_opt) %}
{% else %}
{% set hm_opt = "" %}
{% endif %}
{% if (pool.session_persistence.type ==
constants.SESSION_PERSISTENCE_HTTP_COOKIE) %}
{% set persistence_opt = " cookie %s"|format(member.id) %}
{% else %}
{% set persistence_opt = "" %}
{% endif %}
{% if pool.proxy_protocol %}
{% set proxy_protocol_opt = " send-proxy" %}
{% else %}
{% set proxy_protocol_opt = "" %}
{% endif %}
{% if member.backup %}
{% set member_backup_opt = " backup" %}
{% else %}
{% set member_backup_opt = "" %}
{% endif %}
{% if member.enabled %}
{% set member_enabled_opt = "" %}
{% else %}
{% set member_enabled_opt = " disabled" %}
{% endif %}
{% if pool.tls_enabled %}
{% set def_opt_prefix = " ssl" %}
{% set def_sni_opt = " sni ssl_fc_sni" %}
{% else %}
{% set def_opt_prefix = "" %}
{% set def_sni_opt = "" %}
{% endif %}
{% if pool.client_cert and pool.tls_enabled %}
{% set def_crt_opt = " crt %s"|format(pool.client_cert) %}
{% else %}
{% set def_crt_opt = "" %}
{% endif %}
{% if pool.ca_cert and pool.tls_enabled %}
{% set ca_opt = " ca-file %s"|format(pool.ca_cert) %}
{% set def_verify_opt = " verify required" %}
{% if pool.crl %}
{% set crl_opt = " crl-file %s"|format(pool.crl) %}
{% else %}
{% set def_verify_opt = "" %}
{% endif %}
{% elif pool.tls_enabled %}
{% set def_verify_opt = " verify none" %}
{% endif %}
{{ "server %s %s:%d weight %s%s%s%s%s%s%s%s%s%s%s%s"|e|format(
member.id, member.address, member.protocol_port, member.weight,
hm_opt, persistence_opt, proxy_protocol_opt, member_backup_opt,
member_enabled_opt, def_opt_prefix, def_crt_opt, ca_opt, crl_opt,
def_verify_opt, def_sni_opt)|trim() }}
{% endmacro %}
{% macro backend_macro(constants, listener, pool, loadbalancer) %}
backend {{ pool.id }}:{{ listener.id }}
{% if pool.protocol.lower() == constants.PROTOCOL_PROXY.lower() %}
mode {{ listener.protocol_mode }}
{% else %}
mode {{ pool.protocol }}
{% endif %}
{% if pool.get(constants.HTTP_REUSE, False) and (
pool.protocol.lower() == constants.PROTOCOL_HTTP.lower() or
(pool.protocol.lower() == constants.PROTOCOL_PROXY.lower() and
listener.protocol_mode.lower() ==
constants.PROTOCOL_HTTP.lower()))%}
http-reuse safe
{% endif %}
balance {{ pool.lb_algorithm }}
{% if pool.session_persistence %}
{% if (pool.session_persistence.type ==
constants.SESSION_PERSISTENCE_SOURCE_IP) %}
{% if loadbalancer.topology == constants.TOPOLOGY_ACTIVE_STANDBY %}
stick-table type ip size {{ pool.stick_size }} peers {{
"%s_peers"|format(loadbalancer.id.replace("-", ""))|trim() }}
{% else %}
stick-table type ip size {{ pool.stick_size }}
{% endif %}
stick on src
{% elif (pool.session_persistence.type ==
constants.SESSION_PERSISTENCE_APP_COOKIE) %}
{% if loadbalancer.topology == constants.TOPOLOGY_ACTIVE_STANDBY %}
stick-table type string len 64 size {{
pool.stick_size }} peers {{
"%s_peers"|format(loadbalancer.id.replace("-", ""))|trim() }}
{% else %}
stick-table type string len 64 size {{ pool.stick_size }}
{% endif %}
stick store-response res.cook({{ pool.session_persistence.cookie_name }})
stick match req.cook({{ pool.session_persistence.cookie_name }})
{% elif (pool.session_persistence.type ==
constants.SESSION_PERSISTENCE_HTTP_COOKIE) %}
cookie SRV insert indirect nocache
{% endif %}
{% endif %}
{% if pool.health_monitor and pool.health_monitor.enabled %}
timeout check {{ pool.health_monitor.timeout }}s
{% if (pool.health_monitor.type ==
constants.HEALTH_MONITOR_HTTP or pool.health_monitor.type ==
constants.HEALTH_MONITOR_HTTPS) %}
{% if (pool.health_monitor.http_version and
pool.health_monitor.http_version == 1.1 and
pool.health_monitor.domain_name) %}
option httpchk {{ pool.health_monitor.http_method }} {{ pool.health_monitor.url_path }} HTTP/
{{- pool.health_monitor.http_version -}}{{- "\\r\\n" | safe -}}
Host:\ {{ pool.health_monitor.domain_name }}
{% elif pool.health_monitor.http_version %}
option httpchk {{ pool.health_monitor.http_method }} {{ pool.health_monitor.url_path }} HTTP/
{{- pool.health_monitor.http_version -}}{{- "\\r\\n" | safe }}
{% else %}
option httpchk {{ pool.health_monitor.http_method }} {{ pool.health_monitor.url_path }}
{% endif %}
http-check expect rstatus {{ pool.health_monitor.expected_codes }}
{% endif %}
{% if pool.health_monitor.type == constants.HEALTH_MONITOR_TLS_HELLO %}
option ssl-hello-chk
{% endif %}
{% if pool.health_monitor.type == constants.HEALTH_MONITOR_PING %}
option external-check
external-check command /var/lib/octavia/ping-wrapper.sh
{% endif %}
{% endif %}
{% if pool.protocol.lower() == constants.PROTOCOL_HTTP.lower() %}
{% if listener.insert_headers.get('X-Forwarded-For',
'False').lower() == 'true' %}
option forwardfor
{% endif %}
{% if listener.insert_headers.get('X-Forwarded-Port',
'False').lower() == 'true' %}
http-request set-header X-Forwarded-Port %[dst_port]
{% endif %}
{% endif %}
{% if listener.insert_headers.get('X-Forwarded-Proto',
'False').lower() == 'true' %}
{% if listener.protocol.lower() == constants.PROTOCOL_HTTP.lower() %}
http-request set-header X-Forwarded-Proto http
{% elif listener.protocol.lower() ==
constants.PROTOCOL_TERMINATED_HTTPS.lower() %}
http-request set-header X-Forwarded-Proto https
{% endif %}
{% endif %}
{% if listener.protocol.lower() == constants.PROTOCOL_TERMINATED_HTTPS.lower() %}
{% if listener.insert_headers.get('X-SSL-Client-Verify',
'False').lower() == 'true' %}
http-request set-header X-SSL-Client-Verify %[ssl_c_verify]
{% endif %}
{% if listener.insert_headers.get('X-SSL-Client-Has-Cert',
'False').lower() == 'true' %}
http-request set-header X-SSL-Client-Has-Cert %[ssl_c_used]
{% endif %}
{% if listener.insert_headers.get('X-SSL-Client-DN',
'False').lower() == 'true' %}
http-request set-header X-SSL-Client-DN %{+Q}[ssl_c_s_dn]
{% endif %}
{% if listener.insert_headers.get('X-SSL-Client-CN',
'False').lower() == 'true' %}
http-request set-header X-SSL-Client-CN %{+Q}[ssl_c_s_dn(cn)]
{% endif %}
{% if listener.insert_headers.get('X-SSL-Issuer',
'False').lower() == 'true' %}
http-request set-header X-SSL-Issuer %{+Q}[ssl_c_i_dn]
{% endif %}
{% if listener.insert_headers.get('X-SSL-Client-SHA1',
'False').lower() == 'true' %}
http-request set-header X-SSL-Client-SHA1 %{+Q}[ssl_c_sha1,hex]
{% endif %}
{% if listener.insert_headers.get('X-SSL-Client-Not-Before',
'False').lower() == 'true' %}
http-request set-header X-SSL-Client-Not-Before %{+Q}[ssl_c_notbefore]
{% endif %}
{% if listener.insert_headers.get('X-SSL-Client-Not-After',
'False').lower() == 'true' %}
http-request set-header X-SSL-Client-Not-After %{+Q}[ssl_c_notafter]
{% endif %}
{% endif %}
{% if listener.connection_limit is defined %}
fullconn {{ listener.connection_limit }}
{% endif %}
option allbackups
timeout connect {{ listener.timeout_member_connect }}
timeout server {{ listener.timeout_member_data }}
{% for member in pool.members %}
{{- member_macro(constants, pool, member) -}}
{% endfor %}
{% endmacro %}

View File

@ -131,7 +131,7 @@ class UpdateHealthDb(update_base.HealthUpdateBase):
:type map: string
:returns: null
The input health data structure is shown as below::
The input v1 health data structure is shown as below::
health = {
"id": self.FAKE_UUID_1,
@ -146,6 +146,33 @@ class UpdateHealthDb(update_base.HealthUpdateBase):
}
}
Example V2 message::
{"id": "<amphora_id>",
"seq": 67,
"listeners": {
"<listener_id>": {
"status": "OPEN",
"stats": {
"tx": 0,
"rx": 0,
"conns": 0,
"totconns": 0,
"ereq": 0
}
}
},
"pools": {
"<pool_id>:<listener_id>": {
"status": "UP",
"members": {
"<member_id>": "no check"
}
}
},
"ver": 2
}
"""
session = db_api.get_session()
@ -155,10 +182,15 @@ class UpdateHealthDb(update_base.HealthUpdateBase):
ignore_listener_count = False
if db_lb:
expected_listener_count = len(db_lb.get('listeners', {}))
expected_listener_count = 0
if 'PENDING' in db_lb['provisioning_status']:
ignore_listener_count = True
else:
for key, listener in db_lb.get('listeners', {}).items():
# disabled listeners don't report from the amphora
if listener['enabled']:
expected_listener_count += 1
# If this is a heartbeat older than versioning, handle
# UDP special for backward compatibility.
if 'ver' not in health:
@ -252,6 +284,8 @@ class UpdateHealthDb(update_base.HealthUpdateBase):
else:
lb_status = constants.ONLINE
health_msg_version = health.get('ver', 0)
for listener_id in db_lb.get('listeners', {}):
db_op_status = db_lb['listeners'][listener_id]['operating_status']
listener_status = None
@ -288,11 +322,36 @@ class UpdateHealthDb(update_base.HealthUpdateBase):
if not listener:
continue
pools = listener['pools']
if health_msg_version < 2:
raw_pools = listener['pools']
# normalize the pool IDs. Single process listener pools
# have the listener id appended with an ':' seperator.
# Old multi-process listener pools only have a pool ID.
# This makes sure the keys are only pool IDs.
pools = {(k + ' ')[:k.rfind(':')]: v for k, v in
raw_pools.items()}
for db_pool_id in db_lb.get('pools', {}):
# If we saw this pool already on another listener, skip it.
if db_pool_id in processed_pools:
continue
db_pool_dict = db_lb['pools'][db_pool_id]
lb_status = self._process_pool_status(
session, db_pool_id, db_pool_dict, pools,
lb_status, processed_pools, potential_offline_pools)
if health_msg_version >= 2:
raw_pools = health['pools']
# normalize the pool IDs. Single process listener pools
# have the listener id appended with an ':' seperator.
# Old multi-process listener pools only have a pool ID.
# This makes sure the keys are only pool IDs.
pools = {(k + ' ')[:k.rfind(':')]: v for k, v in raw_pools.items()}
for db_pool_id in db_lb.get('pools', {}):
# If we saw this pool already on another listener
# skip it.
# If we saw this pool already, skip it.
if db_pool_id in processed_pools:
continue
db_pool_dict = db_lb['pools'][db_pool_id]
@ -445,7 +504,7 @@ class UpdateStatsDb(update_base.StatsUpdateBase, stats.StatsMixin):
:type map: string
:returns: null
Example::
Example V1 message::
health = {
"id": self.FAKE_UUID_1,
@ -469,6 +528,33 @@ class UpdateStatsDb(update_base.StatsUpdateBase, stats.StatsMixin):
}
}
Example V2 message::
{"id": "<amphora_id>",
"seq": 67,
"listeners": {
"<listener_id>": {
"status": "OPEN",
"stats": {
"tx": 0,
"rx": 0,
"conns": 0,
"totconns": 0,
"ereq": 0
}
}
},
"pools": {
"<pool_id>:<listener_id>": {
"status": "UP",
"members": {
"<member_id>": "no check"
}
}
},
"ver": 2
}
"""
session = db_api.get_session()

View File

@ -235,13 +235,14 @@ class ControllerWorker(base_taskflow.BaseTaskFlowEngine):
raise db_exceptions.NoResultFound
load_balancer = listener.load_balancer
listeners = load_balancer.listeners
create_listener_tf = self._taskflow_load(self._listener_flows.
get_create_listener_flow(),
store={constants.LOADBALANCER:
load_balancer,
constants.LISTENERS:
[listener]})
listeners})
with tf_logging.DynamicLoggingListener(create_listener_tf,
log=LOG):
create_listener_tf.run()

View File

@ -470,7 +470,7 @@ class AmphoraFlows(object):
update_amps_subflow.add(
amphora_driver_tasks.AmpListenersUpdate(
name=constants.AMP_LISTENER_UPDATE + '-' + str(amp_index),
requires=(constants.LISTENERS, constants.AMPHORAE),
requires=(constants.LOADBALANCER, constants.AMPHORAE),
inject={constants.AMPHORA_INDEX: amp_index,
constants.TIMEOUT_DICT: timeout_dict}))
amp_index += 1
@ -514,8 +514,7 @@ class AmphoraFlows(object):
requires=constants.AMPHORA))
failover_amphora_flow.add(amphora_driver_tasks.ListenersStart(
requires=(constants.LOADBALANCER, constants.LISTENERS,
constants.AMPHORA)))
requires=(constants.LOADBALANCER, constants.AMPHORA)))
failover_amphora_flow.add(
database_tasks.DisableAmphoraHealthMonitoring(
rebind={constants.AMPHORA: constants.FAILED_AMPHORA},

View File

@ -37,7 +37,7 @@ class HealthMonitorFlows(object):
create_hm_flow.add(database_tasks.MarkHealthMonitorPendingCreateInDB(
requires=constants.HEALTH_MON))
create_hm_flow.add(amphora_driver_tasks.ListenersUpdate(
requires=[constants.LOADBALANCER, constants.LISTENERS]))
requires=constants.LOADBALANCER))
create_hm_flow.add(database_tasks.MarkHealthMonitorActiveInDB(
requires=constants.HEALTH_MON))
create_hm_flow.add(database_tasks.MarkPoolActiveInDB(
@ -63,7 +63,7 @@ class HealthMonitorFlows(object):
DeleteModelObject(rebind={constants.OBJECT:
constants.HEALTH_MON}))
delete_hm_flow.add(amphora_driver_tasks.ListenersUpdate(
requires=[constants.LOADBALANCER, constants.LISTENERS]))
requires=constants.LOADBALANCER))
delete_hm_flow.add(database_tasks.DeleteHealthMonitorInDB(
requires=constants.HEALTH_MON))
delete_hm_flow.add(database_tasks.DecrementHealthMonitorQuota(
@ -92,7 +92,7 @@ class HealthMonitorFlows(object):
update_hm_flow.add(database_tasks.MarkHealthMonitorPendingUpdateInDB(
requires=constants.HEALTH_MON))
update_hm_flow.add(amphora_driver_tasks.ListenersUpdate(
requires=[constants.LOADBALANCER, constants.LISTENERS]))
requires=constants.LOADBALANCER))
update_hm_flow.add(database_tasks.UpdateHealthMonInDB(
requires=[constants.HEALTH_MON, constants.UPDATE_DICT]))
update_hm_flow.add(database_tasks.MarkHealthMonitorActiveInDB(

View File

@ -37,7 +37,7 @@ class L7PolicyFlows(object):
create_l7policy_flow.add(database_tasks.MarkL7PolicyPendingCreateInDB(
requires=constants.L7POLICY))
create_l7policy_flow.add(amphora_driver_tasks.ListenersUpdate(
requires=[constants.LOADBALANCER, constants.LISTENERS]))
requires=constants.LOADBALANCER))
create_l7policy_flow.add(database_tasks.MarkL7PolicyActiveInDB(
requires=constants.L7POLICY))
create_l7policy_flow.add(database_tasks.MarkLBAndListenersActiveInDB(
@ -60,7 +60,7 @@ class L7PolicyFlows(object):
delete_l7policy_flow.add(model_tasks.DeleteModelObject(
rebind={constants.OBJECT: constants.L7POLICY}))
delete_l7policy_flow.add(amphora_driver_tasks.ListenersUpdate(
requires=[constants.LOADBALANCER, constants.LISTENERS]))
requires=constants.LOADBALANCER))
delete_l7policy_flow.add(database_tasks.DeleteL7PolicyInDB(
requires=constants.L7POLICY))
delete_l7policy_flow.add(database_tasks.MarkLBAndListenersActiveInDB(
@ -81,7 +81,7 @@ class L7PolicyFlows(object):
update_l7policy_flow.add(database_tasks.MarkL7PolicyPendingUpdateInDB(
requires=constants.L7POLICY))
update_l7policy_flow.add(amphora_driver_tasks.ListenersUpdate(
requires=[constants.LOADBALANCER, constants.LISTENERS]))
requires=constants.LOADBALANCER))
update_l7policy_flow.add(database_tasks.UpdateL7PolicyInDB(
requires=[constants.L7POLICY, constants.UPDATE_DICT]))
update_l7policy_flow.add(database_tasks.MarkL7PolicyActiveInDB(

View File

@ -37,7 +37,7 @@ class L7RuleFlows(object):
create_l7rule_flow.add(database_tasks.MarkL7RulePendingCreateInDB(
requires=constants.L7RULE))
create_l7rule_flow.add(amphora_driver_tasks.ListenersUpdate(
requires=[constants.LOADBALANCER, constants.LISTENERS]))
requires=constants.LOADBALANCER))
create_l7rule_flow.add(database_tasks.MarkL7RuleActiveInDB(
requires=constants.L7RULE))
create_l7rule_flow.add(database_tasks.MarkL7PolicyActiveInDB(
@ -62,7 +62,7 @@ class L7RuleFlows(object):
delete_l7rule_flow.add(model_tasks.DeleteModelObject(
rebind={constants.OBJECT: constants.L7RULE}))
delete_l7rule_flow.add(amphora_driver_tasks.ListenersUpdate(
requires=[constants.LOADBALANCER, constants.LISTENERS]))
requires=constants.LOADBALANCER))
delete_l7rule_flow.add(database_tasks.DeleteL7RuleInDB(
requires=constants.L7RULE))
delete_l7rule_flow.add(database_tasks.MarkL7PolicyActiveInDB(
@ -85,7 +85,7 @@ class L7RuleFlows(object):
update_l7rule_flow.add(database_tasks.MarkL7RulePendingUpdateInDB(
requires=constants.L7RULE))
update_l7rule_flow.add(amphora_driver_tasks.ListenersUpdate(
requires=[constants.LOADBALANCER, constants.LISTENERS]))
requires=constants.LOADBALANCER))
update_l7rule_flow.add(database_tasks.UpdateL7RuleInDB(
requires=[constants.L7RULE, constants.UPDATE_DICT]))
update_l7rule_flow.add(database_tasks.MarkL7RuleActiveInDB(

View File

@ -33,7 +33,7 @@ class ListenerFlows(object):
create_listener_flow.add(lifecycle_tasks.ListenersToErrorOnRevertTask(
requires=[constants.LOADBALANCER, constants.LISTENERS]))
create_listener_flow.add(amphora_driver_tasks.ListenersUpdate(
requires=[constants.LOADBALANCER, constants.LISTENERS]))
requires=constants.LOADBALANCER))
create_listener_flow.add(network_tasks.UpdateVIP(
requires=constants.LOADBALANCER))
create_listener_flow.add(database_tasks.
@ -57,7 +57,7 @@ class ListenerFlows(object):
requires=constants.LOADBALANCER_ID,
provides=constants.LOADBALANCER))
create_all_listeners_flow.add(amphora_driver_tasks.ListenersUpdate(
requires=[constants.LOADBALANCER, constants.LISTENERS]))
requires=constants.LOADBALANCER))
create_all_listeners_flow.add(network_tasks.UpdateVIP(
requires=constants.LOADBALANCER))
return create_all_listeners_flow
@ -71,7 +71,7 @@ class ListenerFlows(object):
delete_listener_flow.add(lifecycle_tasks.ListenerToErrorOnRevertTask(
requires=constants.LISTENER))
delete_listener_flow.add(amphora_driver_tasks.ListenerDelete(
requires=[constants.LOADBALANCER, constants.LISTENER]))
requires=constants.LISTENER))
delete_listener_flow.add(network_tasks.UpdateVIPForDelete(
requires=constants.LOADBALANCER))
delete_listener_flow.add(database_tasks.DeleteListenerInDB(
@ -115,7 +115,7 @@ class ListenerFlows(object):
update_listener_flow.add(lifecycle_tasks.ListenersToErrorOnRevertTask(
requires=[constants.LOADBALANCER, constants.LISTENERS]))
update_listener_flow.add(amphora_driver_tasks.ListenersUpdate(
requires=[constants.LOADBALANCER, constants.LISTENERS]))
requires=constants.LOADBALANCER))
update_listener_flow.add(database_tasks.UpdateListenerInDB(
requires=[constants.LISTENER, constants.UPDATE_DICT]))
update_listener_flow.add(database_tasks.

View File

@ -332,7 +332,7 @@ class LoadBalancerFlows(object):
update_LB_flow.add(network_tasks.ApplyQos(
requires=(constants.LOADBALANCER, constants.UPDATE_DICT)))
update_LB_flow.add(amphora_driver_tasks.ListenersUpdate(
requires=[constants.LOADBALANCER, constants.LISTENERS]))
requires=constants.LOADBALANCER))
update_LB_flow.add(database_tasks.UpdateLoadbalancerInDB(
requires=[constants.LOADBALANCER, constants.UPDATE_DICT]))
update_LB_flow.add(database_tasks.MarkLBActiveInDB(

View File

@ -48,7 +48,7 @@ class MemberFlows(object):
requires=(constants.LOADBALANCER, constants.ADDED_PORTS)
))
create_member_flow.add(amphora_driver_tasks.ListenersUpdate(
requires=(constants.LOADBALANCER, constants.LISTENERS)))
requires=constants.LOADBALANCER))
create_member_flow.add(database_tasks.MarkMemberActiveInDB(
requires=constants.MEMBER))
create_member_flow.add(database_tasks.MarkPoolActiveInDB(
@ -79,7 +79,7 @@ class MemberFlows(object):
delete_member_flow.add(database_tasks.DeleteMemberInDB(
requires=constants.MEMBER))
delete_member_flow.add(amphora_driver_tasks.ListenersUpdate(
requires=[constants.LOADBALANCER, constants.LISTENERS]))
requires=constants.LOADBALANCER))
delete_member_flow.add(database_tasks.DecrementMemberQuota(
requires=constants.MEMBER))
delete_member_flow.add(database_tasks.MarkPoolActiveInDB(
@ -105,7 +105,7 @@ class MemberFlows(object):
update_member_flow.add(database_tasks.MarkMemberPendingUpdateInDB(
requires=constants.MEMBER))
update_member_flow.add(amphora_driver_tasks.ListenersUpdate(
requires=[constants.LOADBALANCER, constants.LISTENERS]))
requires=constants.LOADBALANCER))
update_member_flow.add(database_tasks.UpdateMemberInDB(
requires=[constants.MEMBER, constants.UPDATE_DICT]))
update_member_flow.add(database_tasks.MarkMemberActiveInDB(
@ -195,7 +195,7 @@ class MemberFlows(object):
# Update the Listener (this makes the changes active on the Amp)
batch_update_members_flow.add(amphora_driver_tasks.ListenersUpdate(
requires=(constants.LOADBALANCER, constants.LISTENERS)))
requires=constants.LOADBALANCER))
# Mark all the members ACTIVE here, then pool then LB/Listeners
batch_update_members_flow.add(unordered_members_active_flow)

View File

@ -37,7 +37,7 @@ class PoolFlows(object):
create_pool_flow.add(database_tasks.MarkPoolPendingCreateInDB(
requires=constants.POOL))
create_pool_flow.add(amphora_driver_tasks.ListenersUpdate(
requires=[constants.LOADBALANCER, constants.LISTENERS]))
requires=constants.LOADBALANCER))
create_pool_flow.add(database_tasks.MarkPoolActiveInDB(
requires=constants.POOL))
create_pool_flow.add(database_tasks.MarkLBAndListenersActiveInDB(
@ -62,7 +62,7 @@ class PoolFlows(object):
delete_pool_flow.add(model_tasks.DeleteModelObject(
rebind={constants.OBJECT: constants.POOL}))
delete_pool_flow.add(amphora_driver_tasks.ListenersUpdate(
requires=[constants.LOADBALANCER, constants.LISTENERS]))
requires=constants.LOADBALANCER))
delete_pool_flow.add(database_tasks.DeletePoolInDB(
requires=constants.POOL))
delete_pool_flow.add(database_tasks.DecrementPoolQuota(
@ -116,7 +116,7 @@ class PoolFlows(object):
update_pool_flow.add(database_tasks.MarkPoolPendingUpdateInDB(
requires=constants.POOL))
update_pool_flow.add(amphora_driver_tasks.ListenersUpdate(
requires=[constants.LOADBALANCER, constants.LISTENERS]))
requires=constants.LOADBALANCER))
update_pool_flow.add(database_tasks.UpdatePoolInDB(
requires=[constants.POOL, constants.UPDATE_DICT]))
update_pool_flow.add(database_tasks.MarkPoolActiveInDB(

View File

@ -52,13 +52,13 @@ class BaseAmphoraTask(task.Task):
class AmpListenersUpdate(BaseAmphoraTask):
"""Task to update the listeners on one amphora."""
def execute(self, listeners, amphora_index, amphorae, timeout_dict=()):
def execute(self, loadbalancer, amphora_index, amphorae, timeout_dict=()):
# Note, we don't want this to cause a revert as it may be used
# in a failover flow with both amps failing. Skip it and let
# health manager fix it.
try:
self.amphora_driver.update_amphora_listeners(
listeners, amphora_index, amphorae, timeout_dict)
loadbalancer, amphorae[amphora_index], timeout_dict)
except Exception as e:
amphora_id = amphorae[amphora_index].id
LOG.error('Failed to update listeners on amphora %s. Skipping '
@ -71,11 +71,9 @@ class AmpListenersUpdate(BaseAmphoraTask):
class ListenersUpdate(BaseAmphoraTask):
"""Task to update amphora with all specified listeners' configurations."""
def execute(self, loadbalancer, listeners):
def execute(self, loadbalancer):
"""Execute updates per listener for an amphora."""
for listener in listeners:
listener.load_balancer = loadbalancer
self.amphora_driver.update(listener, loadbalancer.vip)
self.amphora_driver.update(loadbalancer)
def revert(self, loadbalancer, *args, **kwargs):
"""Handle failed listeners updates."""
@ -88,56 +86,20 @@ class ListenersUpdate(BaseAmphoraTask):
return None
class ListenerStop(BaseAmphoraTask):
"""Task to stop the listener on the vip."""
def execute(self, loadbalancer, listener):
"""Execute listener stop routines for an amphora."""
self.amphora_driver.stop(listener, loadbalancer.vip)
LOG.debug("Stopped the listener on the vip")
def revert(self, listener, *args, **kwargs):
"""Handle a failed listener stop."""
LOG.warning("Reverting listener stop.")
self.task_utils.mark_listener_prov_status_error(listener.id)
return None
class ListenerStart(BaseAmphoraTask):
"""Task to start the listener on the vip."""
def execute(self, loadbalancer, listener):
"""Execute listener start routines for an amphora."""
self.amphora_driver.start(listener, loadbalancer.vip)
LOG.debug("Started the listener on the vip")
def revert(self, listener, *args, **kwargs):
"""Handle a failed listener start."""
LOG.warning("Reverting listener start.")
self.task_utils.mark_listener_prov_status_error(listener.id)
return None
class ListenersStart(BaseAmphoraTask):
"""Task to start all listeners on the vip."""
def execute(self, loadbalancer, listeners, amphora=None):
def execute(self, loadbalancer, amphora=None):
"""Execute listener start routines for listeners on an amphora."""
for listener in listeners:
self.amphora_driver.start(listener, loadbalancer.vip, amphora)
LOG.debug("Started the listeners on the vip")
if loadbalancer.listeners:
self.amphora_driver.start(loadbalancer, amphora)
LOG.debug("Started the listeners on the vip")
def revert(self, listeners, *args, **kwargs):
def revert(self, loadbalancer, *args, **kwargs):
"""Handle failed listeners starts."""
LOG.warning("Reverting listeners starts.")
for listener in listeners:
for listener in loadbalancer.listeners:
self.task_utils.mark_listener_prov_status_error(listener.id)
return None
@ -146,9 +108,10 @@ class ListenersStart(BaseAmphoraTask):
class ListenerDelete(BaseAmphoraTask):
"""Task to delete the listener on the vip."""
def execute(self, loadbalancer, listener):
def execute(self, listener):
"""Execute listener delete routines for an amphora."""
self.amphora_driver.delete(listener, loadbalancer.vip)
# TODO(rm_work): This is only relevant because of UDP listeners now.
self.amphora_driver.delete(listener)
LOG.debug("Deleted the listener on the vip")
def revert(self, listener, *args, **kwargs):

View File

@ -1253,6 +1253,7 @@ class AmphoraRepository(BaseRepository):
"load_balancer.operating_status AS lb_op_status, "
"listener.id AS list_id, "
"listener.operating_status AS list_op_status, "
"listener.enabled AS list_enabled, "
"listener.protocol AS list_protocol, "
"pool.id AS pool_id, "
"pool.operating_status AS pool_op_status, "
@ -1278,7 +1279,8 @@ class AmphoraRepository(BaseRepository):
lb['operating_status'] = row['lb_op_status']
if row['list_id'] and row['list_id'] not in listeners:
listener = {'operating_status': row['list_op_status'],
'protocol': row['list_protocol']}
'protocol': row['list_protocol'],
'enabled': row['list_enabled']}
listeners[row['list_id']] = listener
if row['pool_id']:
if row['pool_id'] in pools and row['member_id']:

View File

@ -18,7 +18,6 @@ import subprocess
import flask
import mock
from werkzeug import exceptions
from oslo_utils import uuidutils
@ -315,67 +314,6 @@ class KeepalivedLvsTestCase(base.TestCase):
'start')
self.assertEqual(500, res.status_code)
@mock.patch('octavia.amphorae.backends.utils.keepalivedlvs_query.'
'get_listener_realserver_mapping')
@mock.patch('subprocess.check_output', return_value=PROC_CONTENT)
@mock.patch('os.path.exists')
def test_get_udp_listener_status(self, m_exist, m_check_output,
mget_mapping):
mget_mapping.return_value = (
True, {'10.0.0.99:82': {'status': 'UP',
'Weight': '13',
'InActConn': '0',
'ActiveConn': '0'},
'10.0.0.98:82': {'status': 'UP',
'Weight': '13',
'InActConn': '0',
'ActiveConn': '0'}})
pid_path = ('/var/lib/octavia/lvs/octavia-'
'keepalivedlvs-%s.pid' % self.FAKE_ID)
self.useFixture(test_utils.OpenFixture(pid_path,
self.NORMAL_PID_CONTENT))
cfg_path = ('/var/lib/octavia/lvs/octavia-'
'keepalivedlvs-%s.conf' % self.FAKE_ID)
self.useFixture(test_utils.OpenFixture(cfg_path,
self.NORMAL_CFG_CONTENT))
m_exist.return_value = True
expected = {'status': 'ACTIVE',
'pools': [{'lvs': {
'members': {self.MEMBER_ID1: 'UP',
self.MEMBER_ID2: 'UP'},
'status': 'UP',
'uuid': self.POOL_ID}}],
'type': 'UDP', 'uuid': self.FAKE_ID}
res = self.test_keepalivedlvs.get_udp_listener_status(self.FAKE_ID)
self.assertEqual(200, res.status_code)
self.assertEqual(expected, res.json)
@mock.patch('os.path.exists')
def test_get_udp_listener_status_no_exists(self, m_exist):
m_exist.return_value = False
self.assertRaises(exceptions.HTTPException,
self.test_keepalivedlvs.get_udp_listener_status,
self.FAKE_ID)
@mock.patch('os.path.exists')
def test_get_udp_listener_status_offline_status(self, m_exist):
m_exist.return_value = True
pid_path = ('/var/lib/octavia/lvs/octavia-'
'keepalivedlvs-%s.pid' % self.FAKE_ID)
self.useFixture(test_utils.OpenFixture(pid_path,
self.NORMAL_PID_CONTENT))
cfg_path = ('/var/lib/octavia/lvs/octavia-'
'keepalivedlvs-%s.conf' % self.FAKE_ID)
self.useFixture(test_utils.OpenFixture(cfg_path, 'NO VS CONFIG'))
expected = {'status': 'OFFLINE',
'type': 'UDP',
'uuid': self.FAKE_ID}
res = self.test_keepalivedlvs.get_udp_listener_status(self.FAKE_ID)
self.assertEqual(200, res.status_code)
self.assertEqual(expected, res.json)
@mock.patch('octavia.amphorae.backends.agent.api_server.util.'
'get_udp_listeners', return_value=[LISTENER_ID])
@mock.patch('octavia.amphorae.backends.agent.api_server.util.'

View File

@ -118,11 +118,11 @@ class TestServerTestCase(base.TestCase):
mock_distro_id.return_value = distro
if distro == consts.UBUNTU:
rv = self.ubuntu_app.put('/' + api_server.VERSION +
'/listeners/amp_123/123/haproxy',
'/loadbalancer/amp_123/123/haproxy',
data='test')
elif distro == consts.CENTOS:
rv = self.centos_app.put('/' + api_server.VERSION +
'/listeners/amp_123/123/haproxy',
'/loadbalancer/amp_123/123/haproxy',
data='test')
mode = stat.S_IRUSR | stat.S_IWUSR
mock_open.assert_called_with(file_name, flags, mode)
@ -159,11 +159,11 @@ class TestServerTestCase(base.TestCase):
mock_distro_id.return_value = distro
if distro == consts.UBUNTU:
rv = self.ubuntu_app.put('/' + api_server.VERSION +
'/listeners/amp_123/123/haproxy',
'/loadbalancer/amp_123/123/haproxy',
data='test')
elif distro == consts.CENTOS:
rv = self.centos_app.put('/' + api_server.VERSION +
'/listeners/amp_123/123/haproxy',
'/loadbalancer/amp_123/123/haproxy',
data='test')
self.assertEqual(500, rv.status_code)
@ -188,11 +188,11 @@ class TestServerTestCase(base.TestCase):
if distro == consts.UBUNTU:
rv = self.ubuntu_app.put('/' + api_server.VERSION +
'/listeners/amp_123/123/haproxy',
'/loadbalancer/amp_123/123/haproxy',
data='test')
elif distro == consts.CENTOS:
rv = self.centos_app.put('/' + api_server.VERSION +
'/listeners/amp_123/123/haproxy',
'/loadbalancer/amp_123/123/haproxy',
data='test')
self.assertEqual(202, rv.status_code)
@ -220,11 +220,11 @@ class TestServerTestCase(base.TestCase):
mock_distro_id.return_value = distro
if distro == consts.UBUNTU:
rv = self.ubuntu_app.put('/' + api_server.VERSION +
'/listeners/amp_123/123/haproxy',
'/loadbalancer/amp_123/123/haproxy',
data='test')
elif distro == consts.CENTOS:
rv = self.centos_app.put('/' + api_server.VERSION +
'/listeners/amp_123/123/haproxy',
'/loadbalancer/amp_123/123/haproxy',
data='test')
self.assertEqual(400, rv.status_code)
self.assertEqual(
@ -255,11 +255,11 @@ class TestServerTestCase(base.TestCase):
mock_distro_id.return_value = distro
if distro == consts.UBUNTU:
rv = self.ubuntu_app.put('/' + api_server.VERSION +
'/listeners/amp_123/123/haproxy',
'/loadbalancer/amp_123/123/haproxy',
data='test')
elif distro == consts.CENTOS:
rv = self.ubuntu_app.put('/' + api_server.VERSION +
'/listeners/amp_123/123/haproxy',
'/loadbalancer/amp_123/123/haproxy',
data='test')
self.assertEqual(500, rv.status_code)
@ -269,45 +269,49 @@ class TestServerTestCase(base.TestCase):
def test_centos_start(self):
self._test_start(consts.CENTOS)
@mock.patch('os.listdir')
@mock.patch('os.path.exists')
@mock.patch('octavia.amphorae.backends.agent.api_server.listener.Listener.'
'vrrp_check_script_update')
@mock.patch('octavia.amphorae.backends.agent.api_server.loadbalancer.'
'Loadbalancer.vrrp_check_script_update')
@mock.patch('subprocess.check_output')
def _test_start(self, distro, mock_subprocess, mock_vrrp, mock_exists):
def _test_start(self, distro, mock_subprocess, mock_vrrp, mock_exists,
mock_listdir):
self.assertIn(distro, [consts.UBUNTU, consts.CENTOS])
if distro == consts.UBUNTU:
rv = self.ubuntu_app.put('/' + api_server.VERSION +
'/listeners/123/error')
'/loadbalancer/123/error')
elif distro == consts.CENTOS:
rv = self.centos_app.put('/' + api_server.VERSION +
'/listeners/123/error')
'/loadbalancer/123/error')
self.assertEqual(400, rv.status_code)
self.assertEqual(
{'message': 'Invalid Request',
'details': 'Unknown action: error', },
jsonutils.loads(rv.data.decode('utf-8')))
mock_exists.reset_mock()
mock_exists.return_value = False
if distro == consts.UBUNTU:
rv = self.ubuntu_app.put('/' + api_server.VERSION +
'/listeners/123/start')
'/loadbalancer/123/start')
elif distro == consts.CENTOS:
rv = self.centos_app.put('/' + api_server.VERSION +
'/listeners/123/start')
'/loadbalancer/123/start')
self.assertEqual(404, rv.status_code)
self.assertEqual(
{'message': 'Listener Not Found',
'details': 'No listener with UUID: 123'},
{'message': 'Loadbalancer Not Found',
'details': 'No loadbalancer with UUID: 123'},
jsonutils.loads(rv.data.decode('utf-8')))
mock_exists.assert_called_with('/var/lib/octavia/123/haproxy.cfg')
mock_exists.assert_called_with('/var/lib/octavia')
mock_exists.return_value = True
mock_listdir.return_value = ['123']
if distro == consts.UBUNTU:
rv = self.ubuntu_app.put('/' + api_server.VERSION +
'/listeners/123/start')
'/loadbalancer/123/start')
elif distro == consts.CENTOS:
rv = self.centos_app.put('/' + api_server.VERSION +
'/listeners/123/start')
'/loadbalancer/123/start')
self.assertEqual(202, rv.status_code)
self.assertEqual(
{'message': 'OK',
@ -322,10 +326,10 @@ class TestServerTestCase(base.TestCase):
7, 'test', RANDOM_ERROR)
if distro == consts.UBUNTU:
rv = self.ubuntu_app.put('/' + api_server.VERSION +
'/listeners/123/start')
'/loadbalancer/123/start')
elif distro == consts.CENTOS:
rv = self.centos_app.put('/' + api_server.VERSION +
'/listeners/123/start')
'/loadbalancer/123/start')
self.assertEqual(500, rv.status_code)
self.assertEqual(
{
@ -341,26 +345,28 @@ class TestServerTestCase(base.TestCase):
def test_centos_reload(self):
self._test_reload(consts.CENTOS)
@mock.patch('os.listdir')
@mock.patch('os.path.exists')
@mock.patch('octavia.amphorae.backends.agent.api_server.listener.Listener.'
'vrrp_check_script_update')
@mock.patch('octavia.amphorae.backends.agent.api_server.listener.Listener.'
'_check_haproxy_status')
@mock.patch('octavia.amphorae.backends.agent.api_server.loadbalancer.'
'Loadbalancer.vrrp_check_script_update')
@mock.patch('octavia.amphorae.backends.agent.api_server.loadbalancer.'
'Loadbalancer._check_haproxy_status')
@mock.patch('subprocess.check_output')
def _test_reload(self, distro, mock_subprocess, mock_haproxy_status,
mock_vrrp, mock_exists):
mock_vrrp, mock_exists, mock_listdir):
self.assertIn(distro, [consts.UBUNTU, consts.CENTOS])
# Process running so reload
mock_exists.return_value = True
mock_listdir.return_value = ['123']
mock_haproxy_status.return_value = consts.ACTIVE
if distro == consts.UBUNTU:
rv = self.ubuntu_app.put('/' + api_server.VERSION +
'/listeners/123/reload')
'/loadbalancer/123/reload')
elif distro == consts.CENTOS:
rv = self.centos_app.put('/' + api_server.VERSION +
'/listeners/123/reload')
'/loadbalancer/123/reload')
self.assertEqual(202, rv.status_code)
self.assertEqual(
{'message': 'OK',
@ -374,10 +380,10 @@ class TestServerTestCase(base.TestCase):
mock_haproxy_status.return_value = consts.OFFLINE
if distro == consts.UBUNTU:
rv = self.ubuntu_app.put('/' + api_server.VERSION +
'/listeners/123/reload')
'/loadbalancer/123/reload')
elif distro == consts.CENTOS:
rv = self.centos_app.put('/' + api_server.VERSION +
'/listeners/123/reload')
'/loadbalancer/123/reload')
self.assertEqual(202, rv.status_code)
self.assertEqual(
{'message': 'OK',
@ -411,13 +417,13 @@ class TestServerTestCase(base.TestCase):
self.assertEqual(200, rv.status_code)
self.assertEqual(dict(
api_version='0.5',
api_version='1.0',
haproxy_version='9.9.99-9',
hostname='test-host'),
jsonutils.loads(rv.data.decode('utf-8')))
@mock.patch('octavia.amphorae.backends.agent.api_server.util.'
'get_listener_protocol', return_value='TCP')
'get_protocol_for_lb_object', return_value='TCP')
@mock.patch('octavia.amphorae.backends.agent.api_server.util.'
'get_os_init_system', return_value=consts.INIT_SYSTEMD)
def test_delete_ubuntu_listener_systemd(self, mock_init_system,
@ -426,7 +432,7 @@ class TestServerTestCase(base.TestCase):
mock_init_system)
@mock.patch('octavia.amphorae.backends.agent.api_server.util.'
'get_listener_protocol', return_value='TCP')
'get_protocol_for_lb_object', return_value='TCP')
@mock.patch('octavia.amphorae.backends.agent.api_server.util.'
'get_os_init_system', return_value=consts.INIT_SYSTEMD)
def test_delete_centos_listener_systemd(self, mock_init_system,
@ -435,7 +441,7 @@ class TestServerTestCase(base.TestCase):
mock_init_system)
@mock.patch('octavia.amphorae.backends.agent.api_server.util.'
'get_listener_protocol', return_value='TCP')
'get_protocol_for_lb_object', return_value='TCP')
@mock.patch('octavia.amphorae.backends.agent.api_server.util.'
'get_os_init_system', return_value=consts.INIT_SYSVINIT)
def test_delete_ubuntu_listener_sysvinit(self, mock_init_system,
@ -444,7 +450,7 @@ class TestServerTestCase(base.TestCase):
mock_init_system)
@mock.patch('octavia.amphorae.backends.agent.api_server.util.'
'get_listener_protocol', return_value='TCP')
'get_protocol_for_lb_object', return_value='TCP')
@mock.patch('octavia.amphorae.backends.agent.api_server.util.'
'get_os_init_system', return_value=consts.INIT_UPSTART)
def test_delete_ubuntu_listener_upstart(self, mock_init_system,
@ -452,20 +458,22 @@ class TestServerTestCase(base.TestCase):
self._test_delete_listener(consts.INIT_UPSTART, consts.UBUNTU,
mock_init_system)
@mock.patch('os.listdir')
@mock.patch('os.path.exists')
@mock.patch('subprocess.check_output')
@mock.patch('octavia.amphorae.backends.agent.api_server.listener.Listener.'
'vrrp_check_script_update')
@mock.patch('octavia.amphorae.backends.agent.api_server.loadbalancer.'
'Loadbalancer.vrrp_check_script_update')
@mock.patch('octavia.amphorae.backends.agent.api_server.util.' +
'get_haproxy_pid')
@mock.patch('shutil.rmtree')
@mock.patch('os.remove')
def _test_delete_listener(self, init_system, distro, mock_init_system,
mock_remove, mock_rmtree, mock_pid, mock_vrrp,
mock_check_output, mock_exists):
mock_check_output, mock_exists, mock_listdir):
self.assertIn(distro, [consts.UBUNTU, consts.CENTOS])
# no listener
mock_exists.return_value = False
mock_listdir.return_value = ['123']
if distro == consts.UBUNTU:
rv = self.ubuntu_app.delete('/' + api_server.VERSION +
'/listeners/123')
@ -474,10 +482,10 @@ class TestServerTestCase(base.TestCase):
'/listeners/123')
self.assertEqual(200, rv.status_code)
self.assertEqual(OK, jsonutils.loads(rv.data.decode('utf-8')))
mock_exists.assert_called_with('/var/lib/octavia/123/haproxy.cfg')
mock_exists.assert_called_once_with('/var/lib/octavia')
# service is stopped + no upstart script + no vrrp
mock_exists.side_effect = [True, False, False, False]
mock_exists.side_effect = [True, True, False, False, False]
if distro == consts.UBUNTU:
rv = self.ubuntu_app.delete('/' + api_server.VERSION +
'/listeners/123')
@ -504,7 +512,7 @@ class TestServerTestCase(base.TestCase):
mock_exists.assert_any_call('/var/lib/octavia/123/123.pid')
# service is stopped + no upstart script + vrrp
mock_exists.side_effect = [True, False, True, False]
mock_exists.side_effect = [True, True, False, True, False]
if distro == consts.UBUNTU:
rv = self.ubuntu_app.delete('/' + api_server.VERSION +
'/listeners/123')
@ -531,7 +539,7 @@ class TestServerTestCase(base.TestCase):
mock_exists.assert_any_call('/var/lib/octavia/123/123.pid')
# service is stopped + upstart script + no vrrp
mock_exists.side_effect = [True, False, False, True]
mock_exists.side_effect = [True, True, False, False, True]
if distro == consts.UBUNTU:
rv = self.ubuntu_app.delete('/' + api_server.VERSION +
'/listeners/123')
@ -555,7 +563,7 @@ class TestServerTestCase(base.TestCase):
self.assertIn(init_system, consts.VALID_INIT_SYSTEMS)
# service is stopped + upstart script + vrrp
mock_exists.side_effect = [True, False, True, True]
mock_exists.side_effect = [True, True, False, True, True]
if distro == consts.UBUNTU:
rv = self.ubuntu_app.delete('/' + api_server.VERSION +
'/listeners/123')
@ -579,7 +587,7 @@ class TestServerTestCase(base.TestCase):
self.assertIn(init_system, consts.VALID_INIT_SYSTEMS)
# service is running + upstart script + no vrrp
mock_exists.side_effect = [True, True, True, False, True]
mock_exists.side_effect = [True, True, True, True, False, True]
mock_pid.return_value = '456'
if distro == consts.UBUNTU:
rv = self.ubuntu_app.delete('/' + api_server.VERSION +
@ -609,7 +617,7 @@ class TestServerTestCase(base.TestCase):
self.assertIn(init_system, consts.VALID_INIT_SYSTEMS)
# service is running + upstart script + vrrp
mock_exists.side_effect = [True, True, True, True, True]
mock_exists.side_effect = [True, True, True, True, True, True]
mock_pid.return_value = '456'
if distro == consts.UBUNTU:
rv = self.ubuntu_app.delete('/' + api_server.VERSION +
@ -639,7 +647,7 @@ class TestServerTestCase(base.TestCase):
self.assertIn(init_system, consts.VALID_INIT_SYSTEMS)
# service is running + stopping fails
mock_exists.side_effect = [True, True, True]
mock_exists.side_effect = [True, True, True, True]
mock_check_output.side_effect = subprocess.CalledProcessError(
7, 'test', RANDOM_ERROR)
if distro == consts.UBUNTU:
@ -661,8 +669,9 @@ class TestServerTestCase(base.TestCase):
def test_centos_get_haproxy(self):
self._test_get_haproxy(consts.CENTOS)
@mock.patch('os.listdir')
@mock.patch('os.path.exists')
def _test_get_haproxy(self, distro, mock_exists):
def _test_get_haproxy(self, distro, mock_exists, mock_listdir):
self.assertIn(distro, [consts.UBUNTU, consts.CENTOS])
@ -670,23 +679,24 @@ class TestServerTestCase(base.TestCase):
mock_exists.side_effect = [False]
if distro == consts.UBUNTU:
rv = self.ubuntu_app.get('/' + api_server.VERSION +
'/listeners/123/haproxy')
'/loadbalancer/123/haproxy')
elif distro == consts.CENTOS:
rv = self.centos_app.get('/' + api_server.VERSION +
'/listeners/123/haproxy')
'/loadbalancer/123/haproxy')
self.assertEqual(404, rv.status_code)
mock_exists.side_effect = [True]
mock_exists.side_effect = [True, True]
path = util.config_path('123')
self.useFixture(test_utils.OpenFixture(path, CONTENT))
mock_listdir.return_value = ['123']
if distro == consts.UBUNTU:
rv = self.ubuntu_app.get('/' + api_server.VERSION +
'/listeners/123/haproxy')
'/loadbalancer/123/haproxy')
elif distro == consts.CENTOS:
rv = self.centos_app.get('/' + api_server.VERSION +
'/listeners/123/haproxy')
'/loadbalancer/123/haproxy')
self.assertEqual(200, rv.status_code)
self.assertEqual(six.b(CONTENT), rv.data)
self.assertEqual('text/plain; charset=utf-8',
@ -699,17 +709,17 @@ class TestServerTestCase(base.TestCase):
self._test_get_all_listeners(consts.CENTOS)
@mock.patch('octavia.amphorae.backends.agent.api_server.util.'
'get_listeners')
@mock.patch('octavia.amphorae.backends.agent.api_server.listener.Listener.'
'_check_listener_status')
@mock.patch('octavia.amphorae.backends.agent.api_server.listener.Listener.'
'_parse_haproxy_file')
'get_loadbalancers')
@mock.patch('octavia.amphorae.backends.agent.api_server.loadbalancer.'
'Loadbalancer._check_haproxy_status')
@mock.patch('octavia.amphorae.backends.agent.api_server.util.'
'parse_haproxy_file')
def _test_get_all_listeners(self, distro, mock_parse, mock_status,
mock_listener):
mock_lbs):
self.assertIn(distro, [consts.UBUNTU, consts.CENTOS])
# no listeners
mock_listener.side_effect = [[]]
mock_lbs.side_effect = [[]]
if distro == consts.UBUNTU:
rv = self.ubuntu_app.get('/' + api_server.VERSION + '/listeners')
elif distro == consts.CENTOS:
@ -719,8 +729,8 @@ class TestServerTestCase(base.TestCase):
self.assertFalse(jsonutils.loads(rv.data.decode('utf-8')))
# one listener ACTIVE
mock_listener.side_effect = [['123']]
mock_parse.side_effect = [{'mode': 'test'}]
mock_lbs.side_effect = [['123']]
mock_parse.side_effect = [['fake_socket', {'123': {'mode': 'test'}}]]
mock_status.side_effect = [consts.ACTIVE]
if distro == consts.UBUNTU:
rv = self.ubuntu_app.get('/' + api_server.VERSION + '/listeners')
@ -732,10 +742,11 @@ class TestServerTestCase(base.TestCase):
[{'status': consts.ACTIVE, 'type': 'test', 'uuid': '123'}],
jsonutils.loads(rv.data.decode('utf-8')))
# two listener one ACTIVE, one ERROR
mock_listener.side_effect = [['123', '456']]
mock_parse.side_effect = [{'mode': 'test'}, {'mode': 'http'}]
mock_status.side_effect = [consts.ACTIVE, consts.ERROR]
# two listeners, two modes
mock_lbs.side_effect = [['123', '456']]
mock_parse.side_effect = [['fake_socket', {'123': {'mode': 'test'}}],
['fake_socket', {'456': {'mode': 'http'}}]]
mock_status.return_value = consts.ACTIVE
if distro == consts.UBUNTU:
rv = self.ubuntu_app.get('/' + api_server.VERSION + '/listeners')
elif distro == consts.CENTOS:
@ -744,86 +755,7 @@ class TestServerTestCase(base.TestCase):
self.assertEqual(200, rv.status_code)
self.assertEqual(
[{'status': consts.ACTIVE, 'type': 'test', 'uuid': '123'},
{'status': consts.ERROR, 'type': '', 'uuid': '456'}],
jsonutils.loads(rv.data.decode('utf-8')))
def test_ubuntu_get_listener(self):
self._test_get_listener(consts.UBUNTU)
def test_centos_get_listener(self):
self._test_get_listener(consts.CENTOS)
@mock.patch('octavia.amphorae.backends.agent.api_server.util.'
'get_listener_protocol', return_value='TCP')
@mock.patch('octavia.amphorae.backends.agent.api_server.listener.Listener.'
'_check_listener_status')
@mock.patch('octavia.amphorae.backends.agent.api_server.listener.Listener.'
'_parse_haproxy_file')
@mock.patch('octavia.amphorae.backends.utils.haproxy_query.HAProxyQuery')
@mock.patch('os.path.exists')
def _test_get_listener(self, distro, mock_exists, mock_query, mock_parse,
mock_status, mock_get_proto):
self.assertIn(distro, [consts.UBUNTU, consts.CENTOS])
# Listener not found
mock_exists.side_effect = [False]
if distro == consts.UBUNTU:
rv = self.ubuntu_app.get('/' + api_server.VERSION +
'/listeners/123')
elif distro == consts.CENTOS:
rv = self.centos_app.get('/' + api_server.VERSION +
'/listeners/123')
self.assertEqual(404, rv.status_code)
self.assertEqual(
{'message': 'Listener Not Found',
'details': 'No listener with UUID: 123'},
jsonutils.loads(rv.data.decode('utf-8')))
# Listener not ACTIVE
mock_parse.side_effect = [dict(mode='test')]
mock_status.side_effect = [consts.ERROR]
mock_exists.side_effect = [True]
if distro == consts.UBUNTU:
rv = self.ubuntu_app.get('/' + api_server.VERSION +
'/listeners/123')
elif distro == consts.CENTOS:
rv = self.centos_app.get('/' + api_server.VERSION +
'/listeners/123')
self.assertEqual(200, rv.status_code)
self.assertEqual(dict(
status=consts.ERROR,
type='',
uuid='123'), jsonutils.loads(rv.data.decode('utf-8')))
# Listener ACTIVE
mock_parse.side_effect = [dict(mode='test', stats_socket='blah')]
mock_status.side_effect = [consts.ACTIVE]
mock_exists.side_effect = [True]
mock_pool = mock.Mock()
mock_query.side_effect = [mock_pool]
mock_pool.get_pool_status.side_effect = [
{'tcp-servers': {
'status': 'DOWN',
'uuid': 'tcp-servers',
'members': [
{'id-34833': 'DOWN'},
{'id-34836': 'DOWN'}]}}]
if distro == consts.UBUNTU:
rv = self.ubuntu_app.get('/' + api_server.VERSION +
'/listeners/123')
elif distro == consts.CENTOS:
rv = self.centos_app.get('/' + api_server.VERSION +
'/listeners/123')
self.assertEqual(200, rv.status_code)
self.assertEqual(dict(
status=consts.ACTIVE,
type='test',
uuid='123',
pools=[dict(
status=consts.DOWN,
uuid='tcp-servers',
members=[
{u'id-34833': u'DOWN'},
{u'id-34836': u'DOWN'}])]),
{'status': consts.ACTIVE, 'type': 'http', 'uuid': '456'}],
jsonutils.loads(rv.data.decode('utf-8')))
def test_ubuntu_delete_cert(self):
@ -838,11 +770,13 @@ class TestServerTestCase(base.TestCase):
self.assertIn(distro, [consts.UBUNTU, consts.CENTOS])
mock_exists.side_effect = [False]
if distro == consts.UBUNTU:
rv = self.ubuntu_app.delete('/' + api_server.VERSION +
'/listeners/123/certificates/test.pem')
rv = self.ubuntu_app.delete(
'/' + api_server.VERSION +
'/loadbalancer/123/certificates/test.pem')
elif distro == consts.CENTOS:
rv = self.centos_app.delete('/' + api_server.VERSION +
'/listeners/123/certificates/test.pem')
rv = self.centos_app.delete(
'/' + api_server.VERSION +
'/loadbalancer/123/certificates/test.pem')
self.assertEqual(200, rv.status_code)
self.assertEqual(OK, jsonutils.loads(rv.data.decode('utf-8')))
mock_exists.assert_called_once_with(
@ -851,20 +785,24 @@ class TestServerTestCase(base.TestCase):
# wrong file name
mock_exists.side_effect = [True]
if distro == consts.UBUNTU:
rv = self.ubuntu_app.delete('/' + api_server.VERSION +
'/listeners/123/certificates/test.bla')
rv = self.ubuntu_app.delete(
'/' + api_server.VERSION +
'/loadbalancer/123/certificates/test.bla')
elif distro == consts.CENTOS:
rv = self.centos_app.delete('/' + api_server.VERSION +
'/listeners/123/certificates/test.bla')
rv = self.centos_app.delete(
'/' + api_server.VERSION +
'/loadbalancer/123/certificates/test.bla')
self.assertEqual(400, rv.status_code)
mock_exists.side_effect = [True]
if distro == consts.UBUNTU:
rv = self.ubuntu_app.delete('/' + api_server.VERSION +
'/listeners/123/certificates/test.pem')
rv = self.ubuntu_app.delete(
'/' + api_server.VERSION +
'/loadbalancer/123/certificates/test.pem')
elif distro == consts.CENTOS:
rv = self.centos_app.delete('/' + api_server.VERSION +
'/listeners/123/certificates/test.pem')
rv = self.centos_app.delete(
'/' + api_server.VERSION +
'/loadbalancer/123/certificates/test.pem')
self.assertEqual(200, rv.status_code)
self.assertEqual(OK, jsonutils.loads(rv.data.decode('utf-8')))
mock_remove.assert_called_once_with(
@ -886,10 +824,10 @@ class TestServerTestCase(base.TestCase):
if distro == consts.UBUNTU:
rv = self.ubuntu_app.get('/' + api_server.VERSION +
'/listeners/123/certificates/test.pem')
'/loadbalancer/123/certificates/test.pem')
elif distro == consts.CENTOS:
rv = self.centos_app.get('/' + api_server.VERSION +
'/listeners/123/certificates/test.pem')
'/loadbalancer/123/certificates/test.pem')
self.assertEqual(404, rv.status_code)
self.assertEqual(dict(
details='No certificate with filename: test.pem',
@ -901,29 +839,29 @@ class TestServerTestCase(base.TestCase):
mock_exists.side_effect = [True]
if distro == consts.UBUNTU:
rv = self.ubuntu_app.put('/' + api_server.VERSION +
'/listeners/123/certificates/test.bla',
'/loadbalancer/123/certificates/test.bla',
data='TestTest')
elif distro == consts.CENTOS:
rv = self.centos_app.put('/' + api_server.VERSION +
'/listeners/123/certificates/test.bla',
'/loadbalancer/123/certificates/test.bla',
data='TestTest')
self.assertEqual(400, rv.status_code)
mock_exists.return_value = True
mock_exists.side_effect = None
if distro == consts.UBUNTU:
path = self.ubuntu_test_server._listener._cert_file_path(
path = self.ubuntu_test_server._loadbalancer._cert_file_path(
'123', 'test.pem')
elif distro == consts.CENTOS:
path = self.centos_test_server._listener._cert_file_path(
path = self.centos_test_server._loadbalancer._cert_file_path(
'123', 'test.pem')
self.useFixture(test_utils.OpenFixture(path, CONTENT))
if distro == consts.UBUNTU:
rv = self.ubuntu_app.get('/' + api_server.VERSION +
'/listeners/123/certificates/test.pem')
'/loadbalancer/123/certificates/test.pem')
elif distro == consts.CENTOS:
rv = self.centos_app.get('/' + api_server.VERSION +
'/listeners/123/certificates/test.pem')
'/loadbalancer/123/certificates/test.pem')
self.assertEqual(200, rv.status_code)
self.assertEqual(dict(md5sum=hashlib.md5(six.b(CONTENT)).hexdigest()),
jsonutils.loads(rv.data.decode('utf-8')))
@ -942,20 +880,20 @@ class TestServerTestCase(base.TestCase):
# wrong file name
if distro == consts.UBUNTU:
rv = self.ubuntu_app.put('/' + api_server.VERSION +
'/listeners/123/certificates/test.bla',
'/loadbalancer/123/certificates/test.bla',
data='TestTest')
elif distro == consts.CENTOS:
rv = self.centos_app.put('/' + api_server.VERSION +
'/listeners/123/certificates/test.bla',
'/loadbalancer/123/certificates/test.bla',
data='TestTest')
self.assertEqual(400, rv.status_code)
mock_exists.return_value = True
if distro == consts.UBUNTU:
path = self.ubuntu_test_server._listener._cert_file_path(
path = self.ubuntu_test_server._loadbalancer._cert_file_path(
'123', 'test.pem')
elif distro == consts.CENTOS:
path = self.centos_test_server._listener._cert_file_path(
path = self.centos_test_server._loadbalancer._cert_file_path(
'123', 'test.pem')
m = self.useFixture(test_utils.OpenFixture(path)).mock_open
@ -963,11 +901,11 @@ class TestServerTestCase(base.TestCase):
if distro == consts.UBUNTU:
rv = self.ubuntu_app.put('/' + api_server.VERSION +
'/listeners/123/certificates/'
'/loadbalancer/123/certificates/'
'test.pem', data='TestTest')
elif distro == consts.CENTOS:
rv = self.centos_app.put('/' + api_server.VERSION +
'/listeners/123/certificates/'
'/loadbalancer/123/certificates/'
'test.pem', data='TestTest')
self.assertEqual(200, rv.status_code)
self.assertEqual(OK, jsonutils.loads(rv.data.decode('utf-8')))
@ -980,11 +918,11 @@ class TestServerTestCase(base.TestCase):
with mock.patch('os.open'), mock.patch.object(os, 'fdopen', m):
if distro == consts.UBUNTU:
rv = self.ubuntu_app.put('/' + api_server.VERSION +
'/listeners/123/certificates/'
'/loadbalancer/123/certificates/'
'test.pem', data='TestTest')
elif distro == consts.CENTOS:
rv = self.centos_app.put('/' + api_server.VERSION +
'/listeners/123/certificates/'
'/loadbalancer/123/certificates/'
'test.pem', data='TestTest')
self.assertEqual(200, rv.status_code)
self.assertEqual(OK, jsonutils.loads(rv.data.decode('utf-8')))
@ -2626,7 +2564,7 @@ class TestServerTestCase(base.TestCase):
haproxy_count = random.randrange(0, 100)
mock_count_haproxy.return_value = haproxy_count
expected_dict = {'active': True, 'api_version': '0.5',
expected_dict = {'active': True, 'api_version': '1.0',
'cpu': {'soft_irq': cpu_softirq, 'system': cpu_system,
'total': cpu_total, 'user': cpu_user},
'disk': {'available': disk_available,
@ -2699,3 +2637,11 @@ class TestServerTestCase(base.TestCase):
rv = self.centos_app.put('/' + api_server.VERSION +
'/config', data='TestTest')
self.assertEqual(500, rv.status_code)
def test_version_discovery(self):
self.test_client = server.Server().app.test_client()
expected_dict = {'api_version': api_server.VERSION}
rv = self.test_client.get('/')
self.assertEqual(200, rv.status_code)
self.assertEqual(expected_dict,
jsonutils.loads(rv.data.decode('utf-8')))

View File

@ -3274,7 +3274,8 @@ class AmphoraRepositoryTest(BaseRepositoryTest):
enabled=True, peer_port=1025, default_pool_id=pool.id)
listener_ref = {listener.id: {'operating_status': constants.ONLINE,
'protocol': constants.PROTOCOL_HTTP}}
'protocol': constants.PROTOCOL_HTTP,
'enabled': 1}}
lb_ref['listeners'] = listener_ref
# Test with an LB, pool, and listener (no members)

View File

@ -19,13 +19,18 @@ from oslo_utils import uuidutils
from octavia.amphorae.backends.agent import api_server
from octavia.amphorae.backends.agent.api_server import amphora_info
from octavia.amphorae.backends.agent.api_server import util
from octavia.common.jinja.haproxy.combined_listeners import jinja_cfg
from octavia.tests.common import utils as test_utils
import octavia.tests.unit.base as base
from octavia.tests.unit.common.sample_configs import sample_configs_combined
class TestAmphoraInfo(base.TestCase):
API_VERSION = random.randrange(0, 10000)
BASE_AMP_PATH = '/var/lib/octavia'
BASE_CRT_PATH = BASE_AMP_PATH + '/certs'
HAPROXY_VERSION = random.randrange(0, 10000)
KEEPALIVED_VERSION = random.randrange(0, 10000)
IPVSADM_VERSION = random.randrange(0, 10000)
@ -33,6 +38,7 @@ class TestAmphoraInfo(base.TestCase):
FAKE_LISTENER_ID_2 = uuidutils.generate_uuid()
FAKE_LISTENER_ID_3 = uuidutils.generate_uuid()
FAKE_LISTENER_ID_4 = uuidutils.generate_uuid()
LB_ID_1 = uuidutils.generate_uuid()
def setUp(self):
super(TestAmphoraInfo, self).setUp()
@ -40,6 +46,23 @@ class TestAmphoraInfo(base.TestCase):
self.amp_info = amphora_info.AmphoraInfo(self.osutils_mock)
self.udp_driver = mock.MagicMock()
# setup a fake haproxy config file
templater = jinja_cfg.JinjaTemplater(
base_amp_path=self.BASE_AMP_PATH,
base_crt_dir=self.BASE_CRT_PATH)
tls_tupel = sample_configs_combined.sample_tls_container_tuple(
id='tls_container_id',
certificate='imaCert1', private_key='imaPrivateKey1',
primary_cn='FakeCN')
self.rendered_haproxy_cfg = templater.render_loadbalancer_obj(
sample_configs_combined.sample_amphora_tuple(),
[sample_configs_combined.sample_listener_tuple(
proto='TERMINATED_HTTPS', tls=True, sni=True)],
tls_tupel)
path = util.config_path(self.LB_ID_1)
self.useFixture(test_utils.OpenFixture(path,
self.rendered_haproxy_cfg))
def _return_version(self, package_name):
if package_name == 'ipvsadm':
return self.IPVSADM_VERSION
@ -138,8 +161,8 @@ class TestAmphoraInfo(base.TestCase):
u'haproxy_count': 5,
u'haproxy_version': self.HAPROXY_VERSION,
u'hostname': u'FAKE_HOST',
u'listeners': [self.FAKE_LISTENER_ID_1,
self.FAKE_LISTENER_ID_2],
u'listeners': sorted([self.FAKE_LISTENER_ID_1,
self.FAKE_LISTENER_ID_2]),
u'load': [u'0.09', u'0.11', u'0.10'],
u'memory': {u'buffers': 344792,
u'cached': 4271856,
@ -163,8 +186,7 @@ class TestAmphoraInfo(base.TestCase):
'get_udp_listeners',
return_value=[FAKE_LISTENER_ID_3, FAKE_LISTENER_ID_4])
@mock.patch('octavia.amphorae.backends.agent.api_server.util.'
'get_listeners', return_value=[FAKE_LISTENER_ID_1,
FAKE_LISTENER_ID_2])
'get_loadbalancers')
@mock.patch('octavia.amphorae.backends.agent.api_server.'
'amphora_info.AmphoraInfo._get_meminfo')
@mock.patch('octavia.amphorae.backends.agent.api_server.'
@ -182,7 +204,7 @@ class TestAmphoraInfo(base.TestCase):
def test_compile_amphora_details_for_udp(self, mhostname, m_count,
m_pkg_version, m_load, m_get_nets,
m_os, m_cpu, mget_mem,
mget_listener, mget_udp_listener):
mock_get_lb, mget_udp_listener):
mget_mem.return_value = {'SwapCached': 0, 'Buffers': 344792,
'MemTotal': 21692784, 'Cached': 4271856,
'Slab': 534384, 'MemFree': 12685624,
@ -205,6 +227,7 @@ class TestAmphoraInfo(base.TestCase):
self.udp_driver.get_subscribed_amp_compile_info.return_value = [
'keepalived', 'ipvsadm']
self.udp_driver.is_listener_running.side_effect = [True, False]
mock_get_lb.return_value = [self.LB_ID_1]
original_version = api_server.VERSION
api_server.VERSION = self.API_VERSION
expected_dict = {u'active': True,
@ -221,10 +244,10 @@ class TestAmphoraInfo(base.TestCase):
u'ipvsadm_version': self.IPVSADM_VERSION,
u'udp_listener_process_count': 1,
u'hostname': u'FAKE_HOST',
u'listeners': list(set([self.FAKE_LISTENER_ID_1,
self.FAKE_LISTENER_ID_2,
self.FAKE_LISTENER_ID_3,
self.FAKE_LISTENER_ID_4])),
u'listeners': sorted(list(set(
[self.FAKE_LISTENER_ID_3,
self.FAKE_LISTENER_ID_4,
'sample_listener_id_1']))),
u'load': [u'0.09', u'0.11', u'0.10'],
u'memory': {u'buffers': 344792,
u'cached': 4271856,
@ -245,7 +268,7 @@ class TestAmphoraInfo(base.TestCase):
api_server.VERSION = original_version
@mock.patch('octavia.amphorae.backends.agent.api_server.util.'
'is_listener_running')
'is_lb_running')
def test__count_haproxy_process(self, mock_is_running):
# Test no listeners passed in

View File

@ -17,7 +17,7 @@ import mock
from octavia.amphorae.backends.agent.api_server import haproxy_compatibility
from octavia.common import constants
import octavia.tests.unit.base as base
from octavia.tests.unit.common.sample_configs import sample_configs
from octavia.tests.unit.common.sample_configs import sample_configs_combined
class HAProxyCompatTestCase(base.TestCase):
@ -30,7 +30,7 @@ class HAProxyCompatTestCase(base.TestCase):
" user nobody\n"
" log /dev/log local0\n"
" log /dev/log local1 notice\n"
" stats socket /var/lib/octavia/sample_listener_id_1.sock"
" stats socket /var/lib/octavia/sample_loadbalancer_id_1.sock"
" mode 0666 level user\n"
" maxconn {maxconn}\n\n"
"defaults\n"
@ -45,11 +45,11 @@ class HAProxyCompatTestCase(base.TestCase):
" maxconn {maxconn}\n"
" bind 10.0.0.2:80\n"
" mode http\n"
" default_backend sample_pool_id_1\n"
" default_backend sample_pool_id_1:sample_listener_id_1\n"
" timeout client 50000\n\n").format(
maxconn=constants.HAPROXY_MAX_MAXCONN)
self.backend_without_external = (
"backend sample_pool_id_1\n"
"backend sample_pool_id_1:sample_listener_id_1\n"
" mode http\n"
" balance roundrobin\n"
" cookie SRV insert indirect nocache\n"
@ -67,7 +67,7 @@ class HAProxyCompatTestCase(base.TestCase):
"sample_member_id_2\n").format(
maxconn=constants.HAPROXY_MAX_MAXCONN)
self.backend_with_external = (
"backend sample_pool_id_1\n"
"backend sample_pool_id_1:sample_listener_id_1\n"
" mode http\n"
" balance roundrobin\n"
" cookie SRV insert indirect nocache\n"
@ -101,7 +101,7 @@ class HAProxyCompatTestCase(base.TestCase):
def test_process_cfg_for_version_compat(self, mock_get_version):
# Test 1.6 version path, no change to config expected
mock_get_version.return_value = [1, 6]
test_config = sample_configs.sample_base_expected_config(
test_config = sample_configs_combined.sample_base_expected_config(
backend=self.backend_with_external)
result_config = haproxy_compatibility.process_cfg_for_version_compat(
test_config)
@ -109,7 +109,7 @@ class HAProxyCompatTestCase(base.TestCase):
# Test 1.5 version path, external-check should be removed
mock_get_version.return_value = [1, 5]
test_config = sample_configs.sample_base_expected_config(
test_config = sample_configs_combined.sample_base_expected_config(
backend=self.backend_with_external)
result_config = haproxy_compatibility.process_cfg_for_version_compat(
test_config)

View File

@ -13,7 +13,6 @@
# under the License.
import mock
from werkzeug import exceptions
from oslo_utils import uuidutils
@ -29,13 +28,6 @@ class KeepalivedLvsTestCase(base.TestCase):
super(KeepalivedLvsTestCase, self).setUp()
self.test_keepalivedlvs = keepalivedlvs.KeepalivedLvs()
@mock.patch('os.path.exists')
def test_get_udp_listener_status_no_exists(self, m_exist):
m_exist.return_value = False
self.assertRaises(exceptions.HTTPException,
self.test_keepalivedlvs.get_udp_listener_status,
self.FAKE_ID)
@mock.patch.object(keepalivedlvs, "webob")
@mock.patch('os.path.exists')
def test_delete_udp_listener_not_exist(self, m_exist, m_webob):

View File

@ -17,141 +17,35 @@ import subprocess
import mock
from oslo_utils import uuidutils
from octavia.amphorae.backends.agent.api_server import listener
from octavia.amphorae.backends.agent.api_server import loadbalancer
from octavia.amphorae.backends.agent.api_server import util as agent_util
from octavia.common import constants as consts
from octavia.common.jinja.haproxy import jinja_cfg
from octavia.tests.common import utils as test_utils
import octavia.tests.unit.base as base
from octavia.tests.unit.common.sample_configs import sample_configs
BASE_AMP_PATH = '/var/lib/octavia'
BASE_CRT_PATH = BASE_AMP_PATH + '/certs'
LISTENER_ID1 = uuidutils.generate_uuid()
LB_ID1 = uuidutils.generate_uuid()
class ListenerTestCase(base.TestCase):
def setUp(self):
super(ListenerTestCase, self).setUp()
self.jinja_cfg = jinja_cfg.JinjaTemplater(
base_amp_path=BASE_AMP_PATH,
base_crt_dir=BASE_CRT_PATH)
self.mock_platform = mock.patch("distro.id").start()
self.mock_platform.return_value = "ubuntu"
self.test_listener = listener.Listener()
def test_parse_haproxy_config(self):
# template_tls
tls_tupe = sample_configs.sample_tls_container_tuple(
id='tls_container_id',
certificate='imaCert1', private_key='imaPrivateKey1',
primary_cn='FakeCN')
rendered_obj = self.jinja_cfg.render_loadbalancer_obj(
sample_configs.sample_amphora_tuple(),
sample_configs.sample_listener_tuple(proto='TERMINATED_HTTPS',
tls=True, sni=True),
tls_tupe)
path = agent_util.config_path(LISTENER_ID1)
self.useFixture(test_utils.OpenFixture(path, rendered_obj))
res = self.test_listener._parse_haproxy_file(LISTENER_ID1)
self.assertEqual('TERMINATED_HTTPS', res['mode'])
self.assertEqual('/var/lib/octavia/sample_listener_id_1.sock',
res['stats_socket'])
self.assertEqual(
'/var/lib/octavia/certs/sample_listener_id_1/tls_container_id.pem',
res['ssl_crt'])
# render_template_tls_no_sni
rendered_obj = self.jinja_cfg.render_loadbalancer_obj(
sample_configs.sample_amphora_tuple(),
sample_configs.sample_listener_tuple(
proto='TERMINATED_HTTPS', tls=True),
tls_cert=sample_configs.sample_tls_container_tuple(
id='tls_container_id',
certificate='ImAalsdkfjCert',
private_key='ImAsdlfksdjPrivateKey',
primary_cn="FakeCN"))
self.useFixture(test_utils.OpenFixture(path, rendered_obj))
res = self.test_listener._parse_haproxy_file(LISTENER_ID1)
self.assertEqual('TERMINATED_HTTPS', res['mode'])
self.assertEqual(BASE_AMP_PATH + '/sample_listener_id_1.sock',
res['stats_socket'])
self.assertEqual(
BASE_CRT_PATH + '/sample_listener_id_1/tls_container_id.pem',
res['ssl_crt'])
# render_template_http
rendered_obj = self.jinja_cfg.render_loadbalancer_obj(
sample_configs.sample_amphora_tuple(),
sample_configs.sample_listener_tuple())
self.useFixture(test_utils.OpenFixture(path, rendered_obj))
res = self.test_listener._parse_haproxy_file(LISTENER_ID1)
self.assertEqual('HTTP', res['mode'])
self.assertEqual(BASE_AMP_PATH + '/sample_listener_id_1.sock',
res['stats_socket'])
self.assertIsNone(res['ssl_crt'])
# template_https
rendered_obj = self.jinja_cfg.render_loadbalancer_obj(
sample_configs.sample_amphora_tuple(),
sample_configs.sample_listener_tuple(proto='HTTPS'))
self.useFixture(test_utils.OpenFixture(path, rendered_obj))
res = self.test_listener._parse_haproxy_file(LISTENER_ID1)
self.assertEqual('TCP', res['mode'])
self.assertEqual(BASE_AMP_PATH + '/sample_listener_id_1.sock',
res['stats_socket'])
self.assertIsNone(res['ssl_crt'])
# Bogus format
self.useFixture(test_utils.OpenFixture(path, 'Bogus'))
try:
res = self.test_listener._parse_haproxy_file(LISTENER_ID1)
self.fail("No Exception?")
except listener.ParsingError:
pass
@mock.patch('os.path.exists')
@mock.patch('octavia.amphorae.backends.agent.api_server' +
'.util.get_haproxy_pid')
def test_check_listener_status(self, mock_pid, mock_exists):
mock_pid.return_value = '1245'
mock_exists.side_effect = [True, True]
config_path = agent_util.config_path(LISTENER_ID1)
file_contents = 'frontend {}'.format(LISTENER_ID1)
self.useFixture(test_utils.OpenFixture(config_path, file_contents))
self.assertEqual(
consts.ACTIVE,
self.test_listener._check_listener_status(LISTENER_ID1))
mock_exists.side_effect = [True, False]
self.assertEqual(
consts.ERROR,
self.test_listener._check_listener_status(LISTENER_ID1))
mock_exists.side_effect = [False]
self.assertEqual(
consts.OFFLINE,
self.test_listener._check_listener_status(LISTENER_ID1))
self.test_loadbalancer = loadbalancer.Loadbalancer()
@mock.patch('os.makedirs')
@mock.patch('os.path.exists')
@mock.patch('os.listdir')
@mock.patch('os.path.join')
@mock.patch('octavia.amphorae.backends.agent.api_server.util.'
'get_listeners')
'get_loadbalancers')
@mock.patch('octavia.amphorae.backends.agent.api_server.util'
'.haproxy_sock_path')
def test_vrrp_check_script_update(self, mock_sock_path, mock_get_listeners,
def test_vrrp_check_script_update(self, mock_sock_path, mock_get_lbs,
mock_join, mock_listdir, mock_exists,
mock_makedirs):
mock_get_listeners.return_value = ['abc', LISTENER_ID1]
mock_get_lbs.return_value = ['abc', LB_ID1]
mock_sock_path.return_value = 'listener.sock'
mock_exists.return_value = False
cmd = 'haproxy-vrrp-check ' + ' '.join(['listener.sock']) + '; exit $?'
@ -159,17 +53,17 @@ class ListenerTestCase(base.TestCase):
path = agent_util.keepalived_dir()
m = self.useFixture(test_utils.OpenFixture(path)).mock_open
self.test_listener.vrrp_check_script_update(LISTENER_ID1, 'stop')
self.test_loadbalancer.vrrp_check_script_update(LB_ID1, 'stop')
handle = m()
handle.write.assert_called_once_with(cmd)
mock_get_listeners.return_value = ['abc', LISTENER_ID1]
mock_get_lbs.return_value = ['abc', LB_ID1]
cmd = ('haproxy-vrrp-check ' + ' '.join(['listener.sock',
'listener.sock']) + '; exit '
'$?')
m = self.useFixture(test_utils.OpenFixture(path)).mock_open
self.test_listener.vrrp_check_script_update(LISTENER_ID1, 'start')
self.test_loadbalancer.vrrp_check_script_update(LB_ID1, 'start')
handle = m()
handle.write.assert_called_once_with(cmd)
@ -181,29 +75,29 @@ class ListenerTestCase(base.TestCase):
mock_exists.side_effect = [True, True]
self.assertEqual(
consts.ACTIVE,
self.test_listener._check_haproxy_status(LISTENER_ID1))
self.test_loadbalancer._check_haproxy_status(LISTENER_ID1))
mock_exists.side_effect = [True, False]
self.assertEqual(
consts.OFFLINE,
self.test_listener._check_haproxy_status(LISTENER_ID1))
self.test_loadbalancer._check_haproxy_status(LISTENER_ID1))
mock_exists.side_effect = [False]
self.assertEqual(
consts.OFFLINE,
self.test_listener._check_haproxy_status(LISTENER_ID1))
self.test_loadbalancer._check_haproxy_status(LISTENER_ID1))
@mock.patch('octavia.amphorae.backends.agent.api_server.listener.Listener.'
'_check_haproxy_status')
@mock.patch('octavia.amphorae.backends.agent.api_server.listener.Listener.'
'vrrp_check_script_update')
@mock.patch('octavia.amphorae.backends.agent.api_server.loadbalancer.'
'Loadbalancer._check_haproxy_status')
@mock.patch('octavia.amphorae.backends.agent.api_server.loadbalancer.'
'Loadbalancer.vrrp_check_script_update')
@mock.patch('os.path.exists')
@mock.patch('octavia.amphorae.backends.agent.api_server.listener.Listener.'
'_check_listener_exists')
@mock.patch('octavia.amphorae.backends.agent.api_server.loadbalancer.'
'Loadbalancer._check_lb_exists')
@mock.patch('subprocess.check_output')
def test_start_stop_listener(self, mock_check_output, mock_list_exists,
mock_path_exists, mock_vrrp_update,
mock_check_status):
def test_start_stop_lb(self, mock_check_output, mock_lb_exists,
mock_path_exists, mock_vrrp_update,
mock_check_status):
listener_id = uuidutils.generate_uuid()
mock_path_exists.side_effect = [False, True, True, False, False]
@ -214,12 +108,12 @@ class ListenerTestCase(base.TestCase):
ref_command_split.append('haproxy-{}'.format(listener_id))
ref_command_split.append(consts.AMP_ACTION_START)
result = self.test_listener.start_stop_listener(
result = self.test_loadbalancer.start_stop_lb(
listener_id, consts.AMP_ACTION_START)
mock_check_output.assert_called_once_with(ref_command_split,
stderr=subprocess.STDOUT)
mock_list_exists.assert_called_once_with(listener_id)
mock_lb_exists.assert_called_once_with(listener_id)
mock_vrrp_update.assert_not_called()
self.assertEqual(202, result.status_code)
self.assertEqual('OK', result.json['message'])
@ -228,7 +122,7 @@ class ListenerTestCase(base.TestCase):
self.assertEqual(ref_details, result.json['details'])
# Happy path - VRRP - RELOAD
mock_list_exists.reset_mock()
mock_lb_exists.reset_mock()
mock_vrrp_update.reset_mock()
mock_check_output.reset_mock()
@ -236,12 +130,12 @@ class ListenerTestCase(base.TestCase):
ref_command_split.append('haproxy-{}'.format(listener_id))
ref_command_split.append(consts.AMP_ACTION_RELOAD)
result = self.test_listener.start_stop_listener(
result = self.test_loadbalancer.start_stop_lb(
listener_id, consts.AMP_ACTION_RELOAD)
mock_check_output.assert_called_once_with(ref_command_split,
stderr=subprocess.STDOUT)
mock_list_exists.assert_called_once_with(listener_id)
mock_lb_exists.assert_called_once_with(listener_id)
mock_vrrp_update.assert_called_once_with(listener_id,
consts.AMP_ACTION_RELOAD)
self.assertEqual(202, result.status_code)
@ -251,7 +145,7 @@ class ListenerTestCase(base.TestCase):
self.assertEqual(ref_details, result.json['details'])
# Happy path - VRRP - RELOAD - OFFLINE
mock_list_exists.reset_mock()
mock_lb_exists.reset_mock()
mock_vrrp_update.reset_mock()
mock_check_output.reset_mock()
@ -259,12 +153,12 @@ class ListenerTestCase(base.TestCase):
ref_command_split.append('haproxy-{}'.format(listener_id))
ref_command_split.append(consts.AMP_ACTION_START)
result = self.test_listener.start_stop_listener(
result = self.test_loadbalancer.start_stop_lb(
listener_id, consts.AMP_ACTION_RELOAD)
mock_check_output.assert_called_once_with(ref_command_split,
stderr=subprocess.STDOUT)
mock_list_exists.assert_called_once_with(listener_id)
mock_lb_exists.assert_called_once_with(listener_id)
mock_vrrp_update.assert_called_once_with(listener_id,
consts.AMP_ACTION_RELOAD)
self.assertEqual(202, result.status_code)
@ -274,7 +168,7 @@ class ListenerTestCase(base.TestCase):
self.assertEqual(ref_details, result.json['details'])
# Unhappy path - Not already running
mock_list_exists.reset_mock()
mock_lb_exists.reset_mock()
mock_vrrp_update.reset_mock()
mock_check_output.reset_mock()
@ -285,12 +179,12 @@ class ListenerTestCase(base.TestCase):
mock_check_output.side_effect = subprocess.CalledProcessError(
output=b'bogus', returncode=-2, cmd='sit')
result = self.test_listener.start_stop_listener(
result = self.test_loadbalancer.start_stop_lb(
listener_id, consts.AMP_ACTION_START)
mock_check_output.assert_called_once_with(ref_command_split,
stderr=subprocess.STDOUT)
mock_list_exists.assert_called_once_with(listener_id)
mock_lb_exists.assert_called_once_with(listener_id)
mock_vrrp_update.assert_not_called()
self.assertEqual(500, result.status_code)
self.assertEqual('Error {}ing haproxy'.format(consts.AMP_ACTION_START),
@ -298,7 +192,7 @@ class ListenerTestCase(base.TestCase):
self.assertEqual('bogus', result.json['details'])
# Unhappy path - Already running
mock_list_exists.reset_mock()
mock_lb_exists.reset_mock()
mock_vrrp_update.reset_mock()
mock_check_output.reset_mock()
@ -309,12 +203,12 @@ class ListenerTestCase(base.TestCase):
mock_check_output.side_effect = subprocess.CalledProcessError(
output=b'Job is already running', returncode=-2, cmd='sit')
result = self.test_listener.start_stop_listener(
result = self.test_loadbalancer.start_stop_lb(
listener_id, consts.AMP_ACTION_START)
mock_check_output.assert_called_once_with(ref_command_split,
stderr=subprocess.STDOUT)
mock_list_exists.assert_called_once_with(listener_id)
mock_lb_exists.assert_called_once_with(listener_id)
mock_vrrp_update.assert_not_called()
self.assertEqual(202, result.status_code)
self.assertEqual('OK', result.json['message'])
@ -324,14 +218,62 @@ class ListenerTestCase(base.TestCase):
# Invalid action
mock_check_output.reset_mock()
mock_list_exists.reset_mock()
mock_lb_exists.reset_mock()
mock_path_exists.reset_mock()
mock_vrrp_update.reset_mock()
result = self.test_listener.start_stop_listener(listener_id, 'bogus')
result = self.test_loadbalancer.start_stop_lb(listener_id, 'bogus')
self.assertEqual(400, result.status_code)
self.assertEqual('Invalid Request', result.json['message'])
self.assertEqual('Unknown action: bogus', result.json['details'])
mock_list_exists.assert_not_called()
mock_lb_exists.assert_not_called()
mock_path_exists.assert_not_called()
mock_vrrp_update.assert_not_called()
mock_check_output.assert_not_called()
@mock.patch('octavia.amphorae.backends.agent.api_server.util.'
'config_path')
@mock.patch('octavia.amphorae.backends.agent.api_server.util.'
'get_haproxy_pid')
@mock.patch('os.path.exists')
def test_get_listeners_on_lb(self, mock_exists, mock_get_haproxy_pid,
mock_config_path):
fake_cfg_path = '/some/fake/cfg/file.cfg'
mock_config_path.return_value = fake_cfg_path
mock_get_haproxy_pid.return_value = 'fake_pid'
# Finds two listeners
mock_exists.side_effect = [True, True]
fake_cfg_data = 'frontend list1\nbackend foo\nfrontend list2'
self.useFixture(
test_utils.OpenFixture(fake_cfg_path, fake_cfg_data)).mock_open
result = self.test_loadbalancer._get_listeners_on_lb(LB_ID1)
self.assertEqual(['list1', 'list2'], result)
mock_exists.assert_has_calls([mock.call(agent_util.pid_path(LB_ID1)),
mock.call('/proc/fake_pid')])
# No PID file, no listeners
mock_exists.reset_mock()
mock_exists.side_effect = [False]
result = self.test_loadbalancer._get_listeners_on_lb(LB_ID1)
self.assertEqual([], result)
mock_exists.assert_called_once_with(agent_util.pid_path(LB_ID1))
# PID file, no running process, no listeners
mock_exists.reset_mock()
mock_exists.side_effect = [True, False]
result = self.test_loadbalancer._get_listeners_on_lb(LB_ID1)
self.assertEqual([], result)
mock_exists.assert_has_calls([mock.call(agent_util.pid_path(LB_ID1)),
mock.call('/proc/fake_pid')])
# PID file, running process, no listeners
mock_exists.reset_mock()
mock_exists.side_effect = [True, True]
fake_cfg_data = 'backend only'
self.useFixture(
test_utils.OpenFixture(fake_cfg_path, fake_cfg_data)).mock_open
result = self.test_loadbalancer._get_listeners_on_lb(LB_ID1)
self.assertEqual([], result)
mock_exists.assert_has_calls([mock.call(agent_util.pid_path(LB_ID1)),
mock.call('/proc/fake_pid')])

View File

@ -22,11 +22,15 @@ from oslo_utils import uuidutils
from octavia.amphorae.backends.agent.api_server import util
from octavia.common import constants as consts
from octavia.common.jinja.haproxy.combined_listeners import jinja_cfg
from octavia.tests.common import utils as test_utils
import octavia.tests.unit.base as base
from octavia.tests.unit.common.sample_configs import sample_configs_combined
BASE_AMP_PATH = '/var/lib/octavia'
BASE_CRT_PATH = BASE_AMP_PATH + '/certs'
CONF = cfg.CONF
LISTENER_ID1 = uuidutils.generate_uuid()
class TestUtil(base.TestCase):
@ -34,6 +38,9 @@ class TestUtil(base.TestCase):
super(TestUtil, self).setUp()
self.CONF = self.useFixture(oslo_fixture.Config(cfg.CONF))
self.listener_id = uuidutils.generate_uuid()
self.jinja_cfg = jinja_cfg.JinjaTemplater(
base_amp_path=BASE_AMP_PATH,
base_crt_dir=BASE_CRT_PATH)
def test_keepalived_lvs_dir(self):
fake_path = '/fake/path'
@ -171,7 +178,7 @@ class TestUtil(base.TestCase):
mock_cfg_path.return_value = '/there'
mock_path_exists.side_effect = [True, False, True, False, False]
result = util.get_listener_protocol('1')
result = util.get_protocol_for_lb_object('1')
mock_cfg_path.assert_called_once_with('1')
mock_path_exists.assert_called_once_with('/there')
@ -180,7 +187,7 @@ class TestUtil(base.TestCase):
mock_cfg_path.reset_mock()
result = util.get_listener_protocol('2')
result = util.get_protocol_for_lb_object('2')
mock_cfg_path.assert_called_once_with('2')
mock_lvs_path.assert_called_once_with('2')
@ -189,8 +196,97 @@ class TestUtil(base.TestCase):
mock_cfg_path.reset_mock()
mock_lvs_path.reset_mock()
result = util.get_listener_protocol('3')
result = util.get_protocol_for_lb_object('3')
mock_cfg_path.assert_called_once_with('3')
mock_lvs_path.assert_called_once_with('3')
self.assertIsNone(result)
def test_parse_haproxy_config(self):
# template_tls
tls_tupe = sample_configs_combined.sample_tls_container_tuple(
id='tls_container_id',
certificate='imaCert1', private_key='imaPrivateKey1',
primary_cn='FakeCN')
rendered_obj = self.jinja_cfg.render_loadbalancer_obj(
sample_configs_combined.sample_amphora_tuple(),
[sample_configs_combined.sample_listener_tuple(
proto='TERMINATED_HTTPS', tls=True, sni=True)],
tls_tupe)
path = util.config_path(LISTENER_ID1)
self.useFixture(test_utils.OpenFixture(path, rendered_obj))
res = util.parse_haproxy_file(LISTENER_ID1)
listener_dict = res[1]['sample_listener_id_1']
self.assertEqual('TERMINATED_HTTPS', listener_dict['mode'])
self.assertEqual('/var/lib/octavia/sample_loadbalancer_id_1.sock',
res[0])
self.assertEqual(
'/var/lib/octavia/certs/sample_loadbalancer_id_1/'
'tls_container_id.pem crt /var/lib/octavia/certs/'
'sample_loadbalancer_id_1',
listener_dict['ssl_crt'])
# render_template_tls_no_sni
rendered_obj = self.jinja_cfg.render_loadbalancer_obj(
sample_configs_combined.sample_amphora_tuple(),
[sample_configs_combined.sample_listener_tuple(
proto='TERMINATED_HTTPS', tls=True)],
tls_cert=sample_configs_combined.sample_tls_container_tuple(
id='tls_container_id',
certificate='ImAalsdkfjCert',
private_key='ImAsdlfksdjPrivateKey',
primary_cn="FakeCN"))
self.useFixture(test_utils.OpenFixture(path, rendered_obj))
res = util.parse_haproxy_file(LISTENER_ID1)
listener_dict = res[1]['sample_listener_id_1']
self.assertEqual('TERMINATED_HTTPS', listener_dict['mode'])
self.assertEqual(BASE_AMP_PATH + '/sample_loadbalancer_id_1.sock',
res[0])
self.assertEqual(
BASE_CRT_PATH + '/sample_loadbalancer_id_1/tls_container_id.pem',
listener_dict['ssl_crt'])
# render_template_http
rendered_obj = self.jinja_cfg.render_loadbalancer_obj(
sample_configs_combined.sample_amphora_tuple(),
[sample_configs_combined.sample_listener_tuple()])
self.useFixture(test_utils.OpenFixture(path, rendered_obj))
res = util.parse_haproxy_file(LISTENER_ID1)
listener_dict = res[1]['sample_listener_id_1']
self.assertEqual('HTTP', listener_dict['mode'])
self.assertEqual(BASE_AMP_PATH + '/sample_loadbalancer_id_1.sock',
res[0])
self.assertIsNone(listener_dict.get('ssl_crt', None))
# template_https
rendered_obj = self.jinja_cfg.render_loadbalancer_obj(
sample_configs_combined.sample_amphora_tuple(),
[sample_configs_combined.sample_listener_tuple(proto='HTTPS')])
self.useFixture(test_utils.OpenFixture(path, rendered_obj))
res = util.parse_haproxy_file(LISTENER_ID1)
listener_dict = res[1]['sample_listener_id_1']
self.assertEqual('TCP', listener_dict['mode'])
self.assertEqual(BASE_AMP_PATH + '/sample_loadbalancer_id_1.sock',
res[0])
self.assertIsNone(listener_dict.get('ssl_crt', None))
# Bogus format
self.useFixture(test_utils.OpenFixture(path, 'Bogus'))
try:
res = util.parse_haproxy_file(LISTENER_ID1)
self.fail("No Exception?")
except util.ParsingError:
pass
# Bad listener mode
fake_cfg = 'stats socket foo\nfrontend {}\nmode\n'.format(LISTENER_ID1)
self.useFixture(test_utils.OpenFixture(path, fake_cfg))
self.assertRaises(util.ParsingError, util.parse_haproxy_file,
LISTENER_ID1)

View File

@ -52,7 +52,7 @@ SAMPLE_STATS = ({'': '', 'status': 'OPEN', 'lastchg': '',
'rate': '0', 'req_rate': '0', 'check_status': '',
'econ': '', 'comp_out': '0', 'wredis': '', 'dresp': '0',
'ereq': '0', 'tracked': '', 'comp_in': '0',
'pxname': '490b6ae7-21aa-43f1-b82a-68ddcd2ca2fb',
'pxname': LISTENER_ID1,
'dreq': '0', 'hrsp_5xx': '0', 'last_chk': '',
'check_code': '', 'sid': '0', 'bout': '0', 'hrsp_1xx': '0',
'qlimit': '', 'hrsp_other': '0', 'bin': '0', 'rtime': '',
@ -107,25 +107,16 @@ SAMPLE_STATS = ({'': '', 'status': 'OPEN', 'lastchg': '',
SAMPLE_STATS_MSG = {
'listeners': {
LISTENER_ID1: {
'pools': {
'432fc8b3-d446-48d4-bb64-13beb90e22bc': {
'members': {
'302e33d9-dee1-4de9-98d5-36329a06fb58': 'DOWN'},
'status': 'UP'}},
'stats': {
'totconns': 0, 'conns': 0,
'tx': 0, 'rx': 0, 'ereq': 0},
'status': 'OPEN'},
LISTENER_ID2: {
'pools': {
'432fc8b3-d446-48d4-bb64-13beb90e22bc': {
'members': {
'302e33d9-dee1-4de9-98d5-36329a06fb58': 'DOWN'},
'status': 'UP'}},
'stats': {
'totconns': 0, 'conns': 0,
'tx': 0, 'rx': 0, 'ereq': 0},
'status': 'OPEN'}
},
'pools': {'432fc8b3-d446-48d4-bb64-13beb90e22bc': {
'members': {'302e33d9-dee1-4de9-98d5-36329a06fb58': 'DOWN'},
'status': 'UP'}, '432fc8b3-d446-48d4-bb64-13beb90e22bc': {
'members': {'302e33d9-dee1-4de9-98d5-36329a06fb58': 'DOWN'},
'status': 'UP'},
},
'id': None,
'seq': 0,
@ -141,7 +132,7 @@ class TestHealthDaemon(base.TestCase):
conf.config(group="haproxy_amphora", base_path=BASE_PATH)
@mock.patch('octavia.amphorae.backends.agent.'
'api_server.util.get_listeners')
'api_server.util.get_loadbalancers')
def test_list_sock_stat_files(self, mock_get_listener):
mock_get_listener.return_value = LISTENER_IDS
@ -297,7 +288,7 @@ class TestHealthDaemon(base.TestCase):
stats_query_mock.get_pool_status.assert_called_once_with()
@mock.patch('octavia.amphorae.backends.agent.api_server.'
'util.is_listener_running')
'util.is_lb_running')
@mock.patch('octavia.amphorae.backends.health_daemon.'
'health_daemon.get_stats')
@mock.patch('octavia.amphorae.backends.health_daemon.'
@ -318,7 +309,7 @@ class TestHealthDaemon(base.TestCase):
mock_get_stats.assert_any_call('TEST2')
@mock.patch('octavia.amphorae.backends.agent.api_server.'
'util.is_listener_running')
'util.is_lb_running')
@mock.patch('octavia.amphorae.backends.health_daemon.'
'health_daemon.get_stats')
@mock.patch('octavia.amphorae.backends.health_daemon.'
@ -336,25 +327,6 @@ class TestHealthDaemon(base.TestCase):
self.assertEqual(1, mock_get_stats.call_count)
@mock.patch('octavia.amphorae.backends.agent.api_server.'
'util.is_listener_running')
@mock.patch('octavia.amphorae.backends.health_daemon.'
'health_daemon.get_stats')
@mock.patch('octavia.amphorae.backends.health_daemon.'
'health_daemon.list_sock_stat_files')
def test_build_stats_message_mismatch_pool(self, mock_list_files,
mock_get_stats,
mock_is_running):
mock_list_files.return_value = {LISTENER_ID1: 'TEST',
LISTENER_ID2: 'TEST2'}
mock_is_running.return_value = True
mock_get_stats.return_value = SAMPLE_STATS, SAMPLE_BOGUS_POOL_STATUS
msg = health_daemon.build_stats_message()
self.assertEqual({}, msg['listeners'][LISTENER_ID1]['pools'])
@mock.patch("octavia.amphorae.backends.utils.keepalivedlvs_query."
"get_udp_listener_pool_status")
@mock.patch("octavia.amphorae.backends.utils.keepalivedlvs_query."
@ -411,7 +383,7 @@ class TestHealthDaemon(base.TestCase):
'status': constants.DOWN,
'pools': {},
'stats': {'conns': 0, 'totconns': 0, 'ereq': 0,
'rx': 0, 'tx': 0}}}, 'id': None,
'rx': 0, 'tx': 0}}}, 'pools': {}, 'id': None,
'seq': mock.ANY, 'ver': health_daemon.MSG_VER}
msg = health_daemon.build_stats_message()
self.assertEqual(expected, msg)

View File

@ -29,22 +29,29 @@ STATS_SOCKET_SAMPLE = (
"_3xx,hrsp_4xx,hrsp_5xx,hrsp_other,hanafail,req_rate,req_rate_max,req_tot"
",cli_abrt,srv_abrt,comp_in,comp_out,comp_byp,comp_rsp,lastsess,last_chk,"
"last_agt,qtime,ctime,rtime,ttime,\n"
"http-servers,id-34821,0,0,0,0,,0,0,0,,0,,0,0,0,0,DOWN,1,1,0,1,1,575,575"
",,1,3,1,,0,,2,0,,0,L4TOUT,,30001,0,0,0,0,0,0,0,,,,0,0,,,,,-1,,,0,0,0,0,\n"
"http-servers,id-34824,0,0,0,0,,0,0,0,,0,,0,0,0,0,DOWN,1,1,0,1,1,567,567,"
",1,3,2,,0,,2,0,,0,L4TOUT,,30001,0,0,0,0,0,0,0,,,,0,0,,,,,-1,,,0,0,0,0,\n"
"http-servers,BACKEND,0,0,0,0,200,0,0,0,0,0,,0,0,0,0,DOWN,0,0,0,,1,567,567"
",,1,3,0,,0,,1,0,,0,,,,0,0,0,0,0,0,,,,,0,0,0,0,0,0,-1,,,0,0,0,0,\n"
"tcp-servers,id-34833,0,0,0,0,,0,0,0,,0,,0,0,0,0,UP,1,1,0,1,1,560,560,,"
"1,5,1,,0,,2,0,,0,L4TOUT,,30000,,,,,,,0,,,,0,0,,,,,-1,,,0,0,0,0,\n"
"tcp-servers,id-34836,0,0,0,0,,0,0,0,,0,,0,0,0,0,UP,1,1,0,1,1,552,552,,"
"1,5,2,,0,,2,0,,0,L4TOUT,,30001,,,,,,,0,,,,0,0,,,,,-1,,,0,0,0,0,\n"
"tcp-servers,id-34839,0,0,0,0,,0,0,0,,0,,0,0,0,0,DRAIN,0,1,0,0,0,552,0,,"
"1,5,2,,0,,2,0,,0,L7OK,,30001,,,,,,,0,,,,0,0,,,,,-1,,,0,0,0,0,\n"
"tcp-servers,id-34842,0,0,0,0,,0,0,0,,0,,0,0,0,0,MAINT,0,1,0,0,0,552,0,,"
"1,5,2,,0,,2,0,,0,L7OK,,30001,,,,,,,0,,,,0,0,,,,,-1,,,0,0,0,0,\n"
"tcp-servers,BACKEND,0,0,0,0,200,0,0,0,0,0,,0,0,0,0,UP,0,0,0,,1,552,552"
",,1,5,0,,0,,1,0,,0,,,,,,,,,,,,,,0,0,0,0,0,0,-1,,,0,0,0,0,"
"http-servers:listener-id,id-34821,0,0,0,0,,0,0,0,,0,,0,0,0,0,DOWN,1,1,0,"
"1,1,575,575,,1,3,1,,0,,2,0,,0,L4TOUT,,30001,0,0,0,0,0,0,0,,,,0,0,,,,,-1,,"
",0,0,0,0,\n"
"http-servers:listener-id,id-34824,0,0,0,0,,0,0,0,,0,,0,0,0,0,DOWN,1,1,0,"
"1,1,567,567,,1,3,2,,0,,2,0,,0,L4TOUT,,30001,0,0,0,0,0,0,0,,,,0,0,,,,,-1,,"
",0,0,0,0,\n"
"http-servers:listener-id,BACKEND,0,0,0,0,200,0,0,0,0,0,,0,0,0,0,DOWN,0,0,"
"0,,1,567,567,,1,3,0,,0,,1,0,,0,,,,0,0,0,0,0,0,,,,,0,0,0,0,0,0,-1,,,0,0,0,"
"0,\n"
"tcp-servers:listener-id,id-34833,0,0,0,0,,0,0,0,,0,,0,0,0,0,UP,1,1,0,1,1,"
"560,560,,1,5,1,,0,,2,0,,0,L4TOUT,,30000,,,,,,,0,,,,0,0,,,,,-1,,,0,0,0,0,"
"\n"
"tcp-servers:listener-id,id-34836,0,0,0,0,,0,0,0,,0,,0,0,0,0,UP,1,1,0,1,1,"
"552,552,,1,5,2,,0,,2,0,,0,L4TOUT,,30001,,,,,,,0,,,,0,0,,,,,-1,,,0,0,0,0,"
"\n"
"tcp-servers:listener-id,id-34839,0,0,0,0,,0,0,0,,0,,0,0,0,0,DRAIN,0,1,0,"
"0,0,552,0,,1,5,2,,0,,2,0,,0,L7OK,,30001,,,,,,,0,,,,0,0,,,,,-1,,,0,0,0,0,"
"\n"
"tcp-servers:listener-id,id-34842,0,0,0,0,,0,0,0,,0,,0,0,0,0,MAINT,0,1,0,"
"0,0,552,0,,1,5,2,,0,,2,0,,0,L7OK,,30001,,,,,,,0,,,,0,0,,,,,-1,,,0,0,0,0,"
"\n"
"tcp-servers:listener-id,BACKEND,0,0,0,0,200,0,0,0,0,0,,0,0,0,0,UP,0,0,0,,"
"1,552,552,,1,5,0,,0,,1,0,,0,,,,,,,,,,,,,,0,0,0,0,0,0,-1,,,0,0,0,0,"
)
INFO_SOCKET_SAMPLE = (
@ -94,17 +101,19 @@ class QueryTestCase(base.TestCase):
self.q._query = query_mock
query_mock.return_value = STATS_SOCKET_SAMPLE
self.assertEqual(
{'tcp-servers': {
{'tcp-servers:listener-id': {
'status': constants.UP,
'uuid': 'tcp-servers',
'listener_uuid': 'listener-id',
'pool_uuid': 'tcp-servers',
'members':
{'id-34833': constants.UP,
'id-34836': constants.UP,
'id-34839': constants.DRAIN,
'id-34842': constants.MAINT}},
'http-servers': {
'http-servers:listener-id': {
'status': constants.DOWN,
'uuid': 'http-servers',
'listener_uuid': 'listener-id',
'pool_uuid': 'http-servers',
'members':
{'id-34821': constants.DOWN,
'id-34824': constants.DOWN}}},

File diff suppressed because it is too large Load Diff

View File

@ -20,18 +20,25 @@ from octavia.amphorae.drivers.keepalived import vrrp_rest_driver
from octavia.common import constants
import octavia.tests.unit.base as base
# Version 1.0 is functionally identical to all versions before it
API_VERSION = '1.0'
class TestVRRPRestDriver(base.TestCase):
def setUp(self):
self.keepalived_mixin = vrrp_rest_driver.KeepalivedAmphoraDriverMixin()
self.keepalived_mixin.client = mock.MagicMock()
self.client = self.keepalived_mixin.client
self.keepalived_mixin.clients = {
'base': mock.MagicMock(),
API_VERSION: mock.MagicMock()}
self.keepalived_mixin._populate_amphora_api_version = mock.MagicMock()
self.clients = self.keepalived_mixin.clients
self.FAKE_CONFIG = 'FAKE CONFIG'
self.lb_mock = mock.MagicMock()
self.amphora_mock = mock.MagicMock()
self.amphora_mock.id = uuidutils.generate_uuid()
self.amphora_mock.status = constants.AMPHORA_ALLOCATED
self.amphora_mock.api_version = API_VERSION
self.lb_mock.amphorae = [self.amphora_mock]
self.amphorae_network_config = {}
vip_subnet = mock.MagicMock()
@ -49,7 +56,7 @@ class TestVRRPRestDriver(base.TestCase):
self.keepalived_mixin.update_vrrp_conf(self.lb_mock,
self.amphorae_network_config)
self.client.upload_vrrp_config.assert_called_once_with(
self.clients[API_VERSION].upload_vrrp_config.assert_called_once_with(
self.amphora_mock,
self.FAKE_CONFIG)
@ -57,16 +64,19 @@ class TestVRRPRestDriver(base.TestCase):
self.keepalived_mixin.stop_vrrp_service(self.lb_mock)
self.client.stop_vrrp.assert_called_once_with(self.amphora_mock)
self.clients[API_VERSION].stop_vrrp.assert_called_once_with(
self.amphora_mock)
def test_start_vrrp_service(self):
self.keepalived_mixin.start_vrrp_service(self.lb_mock)
self.client.start_vrrp.assert_called_once_with(self.amphora_mock)
self.clients[API_VERSION].start_vrrp.assert_called_once_with(
self.amphora_mock)
def test_reload_vrrp_service(self):
self.keepalived_mixin.reload_vrrp_service(self.lb_mock)
self.client.reload_vrrp.assert_called_once_with(self.amphora_mock)
self.clients[API_VERSION].reload_vrrp.assert_called_once_with(
self.amphora_mock)

View File

@ -12,6 +12,7 @@
# License for the specific language governing permissions and limitations
# under the License.
import mock
from oslo_utils import uuidutils
from octavia.amphorae.drivers.noop_driver import driver
@ -54,6 +55,7 @@ class TestNoopAmphoraLoadBalancerDriver(base.TestCase):
self.load_balancer = data_models.LoadBalancer(
id=FAKE_UUID_1, amphorae=[self.amphora], vip=self.vip,
listeners=[self.listener])
self.listener.load_balancer = self.load_balancer
self.network = network_models.Network(id=self.FAKE_UUID_1)
self.port = network_models.Port(id=uuidutils.generate_uuid())
self.amphorae_net_configs = {
@ -70,8 +72,7 @@ class TestNoopAmphoraLoadBalancerDriver(base.TestCase):
constants.CONN_RETRY_INTERVAL: 4}
def test_update_amphora_listeners(self):
amphorae = [self.amphora]
self.driver.update_amphora_listeners([self.listener], 0, amphorae,
self.driver.update_amphora_listeners(self.load_balancer, self.amphora,
self.timeout_dict)
self.assertEqual((self.listener, self.amphora.id, self.timeout_dict,
'update_amp'),
@ -80,28 +81,22 @@ class TestNoopAmphoraLoadBalancerDriver(base.TestCase):
self.amphora.id)])
def test_update(self):
self.driver.update(self.listener, self.vip)
self.assertEqual((self.listener, self.vip, 'active'),
self.driver.update(self.load_balancer)
self.assertEqual(([self.listener], self.vip, 'active'),
self.driver.driver.amphoraconfig[(
self.listener.protocol_port,
self.vip.ip_address)])
def test_stop(self):
self.driver.stop(self.listener, self.vip)
self.assertEqual((self.listener, self.vip, 'stop'),
self.driver.driver.amphoraconfig[(
self.listener.protocol_port,
(self.listener.protocol_port,),
self.vip.ip_address)])
def test_start(self):
self.driver.start(self.listener, self.vip, amphora='amp1')
self.assertEqual((self.listener, self.vip, 'amp1', 'start'),
mock_amphora = mock.MagicMock()
mock_amphora.id = '321'
self.driver.start(self.load_balancer, amphora=mock_amphora)
self.assertEqual((self.load_balancer, mock_amphora, 'start'),
self.driver.driver.amphoraconfig[(
self.listener.protocol_port,
self.vip.ip_address, 'amp1')])
self.load_balancer.id, '321')])
def test_delete(self):
self.driver.delete(self.listener, self.vip)
self.driver.delete(self.listener)
self.assertEqual((self.listener, self.vip, 'delete'),
self.driver.driver.amphoraconfig[(
self.listener.protocol_port,

View File

@ -134,7 +134,8 @@ class TestBarbicanManager(base.TestCase):
self.assertIsInstance(data, cert.Cert)
self.assertEqual(sample.X509_CERT_KEY, data.get_private_key())
self.assertEqual(sample.X509_CERT, data.get_certificate())
self.assertItemsEqual(sample.X509_IMDS_LIST, data.get_intermediates())
self.assertEqual(sorted(sample.X509_IMDS_LIST),
sorted(data.get_intermediates()))
self.assertIsNone(data.get_private_key_passphrase())
def test_delete_cert_legacy(self):

File diff suppressed because it is too large Load Diff

View File

@ -17,9 +17,9 @@ import copy
import os
from octavia.common import constants
from octavia.common.jinja.haproxy import jinja_cfg
from octavia.common.jinja.haproxy.split_listeners import jinja_cfg
from octavia.tests.unit import base
from octavia.tests.unit.common.sample_configs import sample_configs
from octavia.tests.unit.common.sample_configs import sample_configs_split
class TestHaproxyCfg(base.TestCase):
@ -67,20 +67,19 @@ class TestHaproxyCfg(base.TestCase):
"weight 13 check inter 30s fall 3 rise 2 cookie "
"sample_member_id_2\n\n").format(
maxconn=constants.HAPROXY_MAX_MAXCONN)
tls_tupe = sample_configs.sample_tls_container_tuple(
tls_tupe = sample_configs_split.sample_tls_container_tuple(
id='tls_container_id',
certificate='imaCert1', private_key='imaPrivateKey1',
primary_cn='FakeCN')
rendered_obj = self.jinja_cfg.render_loadbalancer_obj(
sample_configs.sample_amphora_tuple(),
sample_configs.sample_listener_tuple(proto='TERMINATED_HTTPS',
tls=True, sni=True,
client_ca_cert=True,
client_crl_cert=True),
sample_configs_split.sample_amphora_tuple(),
sample_configs_split.sample_listener_tuple(
proto='TERMINATED_HTTPS', tls=True, sni=True,
client_ca_cert=True, client_crl_cert=True),
tls_tupe, client_ca_filename='client_ca.pem',
client_crl='SHA_ID.pem')
self.assertEqual(
sample_configs.sample_base_expected_config(
sample_configs_split.sample_base_expected_config(
frontend=fe, backend=be),
rendered_obj)
@ -115,16 +114,16 @@ class TestHaproxyCfg(base.TestCase):
"cookie sample_member_id_2\n\n").format(
maxconn=constants.HAPROXY_MAX_MAXCONN)
rendered_obj = self.jinja_cfg.render_loadbalancer_obj(
sample_configs.sample_amphora_tuple(),
sample_configs.sample_listener_tuple(
sample_configs_split.sample_amphora_tuple(),
sample_configs_split.sample_listener_tuple(
proto='TERMINATED_HTTPS', tls=True),
tls_cert=sample_configs.sample_tls_container_tuple(
tls_cert=sample_configs_split.sample_tls_container_tuple(
id='tls_container_id',
certificate='ImAalsdkfjCert',
private_key='ImAsdlfksdjPrivateKey',
primary_cn="FakeCN"))
self.assertEqual(
sample_configs.sample_base_expected_config(
sample_configs_split.sample_base_expected_config(
frontend=fe, backend=be),
rendered_obj)
@ -148,10 +147,10 @@ class TestHaproxyCfg(base.TestCase):
"cookie sample_member_id_2\n\n").format(
maxconn=constants.HAPROXY_MAX_MAXCONN)
rendered_obj = self.jinja_cfg.render_loadbalancer_obj(
sample_configs.sample_amphora_tuple(),
sample_configs.sample_listener_tuple())
sample_configs_split.sample_amphora_tuple(),
sample_configs_split.sample_listener_tuple())
self.assertEqual(
sample_configs.sample_base_expected_config(backend=be),
sample_configs_split.sample_base_expected_config(backend=be),
rendered_obj)
def test_render_template_member_backup(self):
@ -176,11 +175,11 @@ class TestHaproxyCfg(base.TestCase):
"cookie sample_member_id_2 backup\n\n").format(
maxconn=constants.HAPROXY_MAX_MAXCONN)
rendered_obj = self.jinja_cfg.render_loadbalancer_obj(
sample_configs.sample_amphora_tuple(),
sample_configs.sample_listener_tuple(monitor_ip_port=True,
backup_member=True))
sample_configs_split.sample_amphora_tuple(),
sample_configs_split.sample_listener_tuple(
monitor_ip_port=True, backup_member=True))
self.assertEqual(
sample_configs.sample_base_expected_config(backend=be),
sample_configs_split.sample_base_expected_config(backend=be),
rendered_obj)
def test_render_template_custom_timeouts(self):
@ -210,13 +209,13 @@ class TestHaproxyCfg(base.TestCase):
"sample_member_id_2\n\n").format(
maxconn=constants.HAPROXY_MAX_MAXCONN)
rendered_obj = self.jinja_cfg.render_loadbalancer_obj(
sample_configs.sample_amphora_tuple(),
sample_configs.sample_listener_tuple(timeout_member_connect=1,
timeout_client_data=2,
timeout_member_data=3))
sample_configs_split.sample_amphora_tuple(),
sample_configs_split.sample_listener_tuple(
timeout_member_connect=1, timeout_client_data=2,
timeout_member_data=3))
self.assertEqual(
sample_configs.sample_base_expected_config(frontend=fe,
backend=be),
sample_configs_split.sample_base_expected_config(
frontend=fe, backend=be),
rendered_obj)
def test_render_template_null_timeouts(self):
@ -246,13 +245,13 @@ class TestHaproxyCfg(base.TestCase):
"sample_member_id_2\n\n").format(
maxconn=constants.HAPROXY_MAX_MAXCONN)
rendered_obj = self.jinja_cfg.render_loadbalancer_obj(
sample_configs.sample_amphora_tuple(),
sample_configs.sample_listener_tuple(timeout_member_connect=None,
timeout_client_data=None,
timeout_member_data=None))
sample_configs_split.sample_amphora_tuple(),
sample_configs_split.sample_listener_tuple(
timeout_member_connect=None, timeout_client_data=None,
timeout_member_data=None))
self.assertEqual(
sample_configs.sample_base_expected_config(frontend=fe,
backend=be),
sample_configs_split.sample_base_expected_config(
frontend=fe, backend=be),
rendered_obj)
def test_render_template_member_monitor_addr_port(self):
@ -277,10 +276,10 @@ class TestHaproxyCfg(base.TestCase):
"cookie sample_member_id_2\n\n").format(
maxconn=constants.HAPROXY_MAX_MAXCONN)
rendered_obj = self.jinja_cfg.render_loadbalancer_obj(
sample_configs.sample_amphora_tuple(),
sample_configs.sample_listener_tuple(monitor_ip_port=True))
sample_configs_split.sample_amphora_tuple(),
sample_configs_split.sample_listener_tuple(monitor_ip_port=True))
self.assertEqual(
sample_configs.sample_base_expected_config(backend=be),
sample_configs_split.sample_base_expected_config(backend=be),
rendered_obj)
def test_render_template_https_real_monitor(self):
@ -311,9 +310,9 @@ class TestHaproxyCfg(base.TestCase):
"cookie sample_member_id_2\n\n").format(
maxconn=constants.HAPROXY_MAX_MAXCONN)
rendered_obj = self.jinja_cfg.render_loadbalancer_obj(
sample_configs.sample_amphora_tuple(),
sample_configs.sample_listener_tuple(proto='HTTPS'))
self.assertEqual(sample_configs.sample_base_expected_config(
sample_configs_split.sample_amphora_tuple(),
sample_configs_split.sample_listener_tuple(proto='HTTPS'))
self.assertEqual(sample_configs_split.sample_base_expected_config(
frontend=fe, backend=be), rendered_obj)
def test_render_template_https_hello_monitor(self):
@ -343,10 +342,10 @@ class TestHaproxyCfg(base.TestCase):
"cookie sample_member_id_2\n\n").format(
maxconn=constants.HAPROXY_MAX_MAXCONN)
rendered_obj = self.jinja_cfg.render_loadbalancer_obj(
sample_configs.sample_amphora_tuple(),
sample_configs.sample_listener_tuple(
sample_configs_split.sample_amphora_tuple(),
sample_configs_split.sample_listener_tuple(
proto='HTTPS', monitor_proto='TLS-HELLO'))
self.assertEqual(sample_configs.sample_base_expected_config(
self.assertEqual(sample_configs_split.sample_base_expected_config(
frontend=fe, backend=be), rendered_obj)
def test_render_template_no_monitor_http(self):
@ -364,9 +363,10 @@ class TestHaproxyCfg(base.TestCase):
"cookie sample_member_id_2\n\n").format(
maxconn=constants.HAPROXY_MAX_MAXCONN)
rendered_obj = self.jinja_cfg.render_loadbalancer_obj(
sample_configs.sample_amphora_tuple(),
sample_configs.sample_listener_tuple(proto='HTTP', monitor=False))
self.assertEqual(sample_configs.sample_base_expected_config(
sample_configs_split.sample_amphora_tuple(),
sample_configs_split.sample_listener_tuple(
proto='HTTP', monitor=False))
self.assertEqual(sample_configs_split.sample_base_expected_config(
backend=be), rendered_obj)
def test_render_template_disabled_member(self):
@ -384,10 +384,10 @@ class TestHaproxyCfg(base.TestCase):
"cookie sample_member_id_2 disabled\n\n").format(
maxconn=constants.HAPROXY_MAX_MAXCONN)
rendered_obj = self.jinja_cfg.render_loadbalancer_obj(
sample_configs.sample_amphora_tuple(),
sample_configs.sample_listener_tuple(proto='HTTP', monitor=False,
disabled_member=True))
self.assertEqual(sample_configs.sample_base_expected_config(
sample_configs_split.sample_amphora_tuple(),
sample_configs_split.sample_listener_tuple(
proto='HTTP', monitor=False, disabled_member=True))
self.assertEqual(sample_configs_split.sample_base_expected_config(
backend=be), rendered_obj)
def test_render_template_ping_monitor_http(self):
@ -412,10 +412,10 @@ class TestHaproxyCfg(base.TestCase):
go = " maxconn {maxconn}\n external-check\n\n".format(
maxconn=constants.HAPROXY_MAX_MAXCONN)
rendered_obj = self.jinja_cfg.render_loadbalancer_obj(
sample_configs.sample_amphora_tuple(),
sample_configs.sample_listener_tuple(proto='HTTP',
monitor_proto='PING'))
self.assertEqual(sample_configs.sample_base_expected_config(
sample_configs_split.sample_amphora_tuple(),
sample_configs_split.sample_listener_tuple(
proto='HTTP', monitor_proto='PING'))
self.assertEqual(sample_configs_split.sample_base_expected_config(
backend=be, global_opts=go), rendered_obj)
def test_render_template_no_monitor_https(self):
@ -441,9 +441,10 @@ class TestHaproxyCfg(base.TestCase):
"cookie sample_member_id_2\n\n").format(
maxconn=constants.HAPROXY_MAX_MAXCONN)
rendered_obj = self.jinja_cfg.render_loadbalancer_obj(
sample_configs.sample_amphora_tuple(),
sample_configs.sample_listener_tuple(proto='HTTPS', monitor=False))
self.assertEqual(sample_configs.sample_base_expected_config(
sample_configs_split.sample_amphora_tuple(),
sample_configs_split.sample_listener_tuple(
proto='HTTPS', monitor=False))
self.assertEqual(sample_configs_split.sample_base_expected_config(
frontend=fe, backend=be), rendered_obj)
def test_render_template_health_monitor_http_check(self):
@ -467,11 +468,10 @@ class TestHaproxyCfg(base.TestCase):
"cookie sample_member_id_2\n\n").format(
maxconn=constants.HAPROXY_MAX_MAXCONN)
rendered_obj = self.jinja_cfg.render_loadbalancer_obj(
sample_configs.sample_amphora_tuple(),
sample_configs.sample_listener_tuple(proto='HTTP',
monitor_proto='HTTP',
hm_host_http_check=True))
self.assertEqual(sample_configs.sample_base_expected_config(
sample_configs_split.sample_amphora_tuple(),
sample_configs_split.sample_listener_tuple(
proto='HTTP', monitor_proto='HTTP', hm_host_http_check=True))
self.assertEqual(sample_configs_split.sample_base_expected_config(
backend=be), rendered_obj)
def test_render_template_no_persistence_https(self):
@ -494,10 +494,10 @@ class TestHaproxyCfg(base.TestCase):
" server sample_member_id_2 10.0.0.98:82 "
"weight 13\n\n").format(maxconn=constants.HAPROXY_MAX_MAXCONN)
rendered_obj = self.jinja_cfg.render_loadbalancer_obj(
sample_configs.sample_amphora_tuple(),
sample_configs.sample_listener_tuple(proto='HTTPS', monitor=False,
persistence=False))
self.assertEqual(sample_configs.sample_base_expected_config(
sample_configs_split.sample_amphora_tuple(),
sample_configs_split.sample_listener_tuple(
proto='HTTPS', monitor=False, persistence=False))
self.assertEqual(sample_configs_split.sample_base_expected_config(
frontend=fe, backend=be), rendered_obj)
def test_render_template_no_persistence_http(self):
@ -512,10 +512,10 @@ class TestHaproxyCfg(base.TestCase):
" server sample_member_id_2 10.0.0.98:82 "
"weight 13\n\n").format(maxconn=constants.HAPROXY_MAX_MAXCONN)
rendered_obj = self.jinja_cfg.render_loadbalancer_obj(
sample_configs.sample_amphora_tuple(),
sample_configs.sample_listener_tuple(proto='HTTP', monitor=False,
persistence=False))
self.assertEqual(sample_configs.sample_base_expected_config(
sample_configs_split.sample_amphora_tuple(),
sample_configs_split.sample_listener_tuple(
proto='HTTP', monitor=False, persistence=False))
self.assertEqual(sample_configs_split.sample_base_expected_config(
backend=be), rendered_obj)
def test_render_template_sourceip_persistence(self):
@ -537,11 +537,11 @@ class TestHaproxyCfg(base.TestCase):
"weight 13 check inter 30s fall 3 rise 2\n\n").format(
maxconn=constants.HAPROXY_MAX_MAXCONN)
rendered_obj = self.jinja_cfg.render_loadbalancer_obj(
sample_configs.sample_amphora_tuple(),
sample_configs.sample_listener_tuple(
sample_configs_split.sample_amphora_tuple(),
sample_configs_split.sample_listener_tuple(
persistence_type='SOURCE_IP'))
self.assertEqual(
sample_configs.sample_base_expected_config(backend=be),
sample_configs_split.sample_base_expected_config(backend=be),
rendered_obj)
def test_render_template_appcookie_persistence(self):
@ -564,12 +564,12 @@ class TestHaproxyCfg(base.TestCase):
"weight 13 check inter 30s fall 3 rise 2\n\n").format(
maxconn=constants.HAPROXY_MAX_MAXCONN)
rendered_obj = self.jinja_cfg.render_loadbalancer_obj(
sample_configs.sample_amphora_tuple(),
sample_configs.sample_listener_tuple(
sample_configs_split.sample_amphora_tuple(),
sample_configs_split.sample_listener_tuple(
persistence_type='APP_COOKIE',
persistence_cookie='JSESSIONID'))
self.assertEqual(
sample_configs.sample_base_expected_config(backend=be),
sample_configs_split.sample_base_expected_config(backend=be),
rendered_obj)
def test_render_template_unlimited_connections(self):
@ -595,9 +595,10 @@ class TestHaproxyCfg(base.TestCase):
"cookie sample_member_id_2\n\n").format(
maxconn=constants.HAPROXY_MAX_MAXCONN)
rendered_obj = self.jinja_cfg.render_loadbalancer_obj(
sample_configs.sample_amphora_tuple(),
sample_configs.sample_listener_tuple(proto='HTTPS', monitor=False))
self.assertEqual(sample_configs.sample_base_expected_config(
sample_configs_split.sample_amphora_tuple(),
sample_configs_split.sample_listener_tuple(
proto='HTTPS', monitor=False))
self.assertEqual(sample_configs_split.sample_base_expected_config(
frontend=fe, backend=be), rendered_obj)
def test_render_template_limited_connections(self):
@ -622,10 +623,10 @@ class TestHaproxyCfg(base.TestCase):
"cookie sample_member_id_2\n\n")
g_opts = " maxconn 2014\n\n"
rendered_obj = self.jinja_cfg.render_loadbalancer_obj(
sample_configs.sample_amphora_tuple(),
sample_configs.sample_listener_tuple(proto='HTTPS', monitor=False,
connection_limit=2014))
self.assertEqual(sample_configs.sample_base_expected_config(
sample_configs_split.sample_amphora_tuple(),
sample_configs_split.sample_listener_tuple(
proto='HTTPS', monitor=False, connection_limit=2014))
self.assertEqual(sample_configs_split.sample_base_expected_config(
frontend=fe, backend=be, global_opts=g_opts), rendered_obj)
def test_render_template_l7policies(self):
@ -687,9 +688,9 @@ class TestHaproxyCfg(base.TestCase):
"inter 30s fall 3 rise 2 cookie sample_member_id_3\n\n").format(
maxconn=constants.HAPROXY_MAX_MAXCONN)
rendered_obj = self.jinja_cfg.render_loadbalancer_obj(
sample_configs.sample_amphora_tuple(),
sample_configs.sample_listener_tuple(l7=True))
self.assertEqual(sample_configs.sample_base_expected_config(
sample_configs_split.sample_amphora_tuple(),
sample_configs_split.sample_listener_tuple(l7=True))
self.assertEqual(sample_configs_split.sample_base_expected_config(
frontend=fe, backend=be), rendered_obj)
def test_render_template_http_xff(self):
@ -713,11 +714,11 @@ class TestHaproxyCfg(base.TestCase):
"cookie sample_member_id_2\n\n").format(
maxconn=constants.HAPROXY_MAX_MAXCONN)
rendered_obj = self.jinja_cfg.render_loadbalancer_obj(
sample_configs.sample_amphora_tuple(),
sample_configs.sample_listener_tuple(
sample_configs_split.sample_amphora_tuple(),
sample_configs_split.sample_listener_tuple(
insert_headers={'X-Forwarded-For': 'true'}))
self.assertEqual(
sample_configs.sample_base_expected_config(backend=be),
sample_configs_split.sample_base_expected_config(backend=be),
rendered_obj)
def test_render_template_http_xff_xfport(self):
@ -742,12 +743,12 @@ class TestHaproxyCfg(base.TestCase):
"cookie sample_member_id_2\n\n").format(
maxconn=constants.HAPROXY_MAX_MAXCONN)
rendered_obj = self.jinja_cfg.render_loadbalancer_obj(
sample_configs.sample_amphora_tuple(),
sample_configs.sample_listener_tuple(
sample_configs_split.sample_amphora_tuple(),
sample_configs_split.sample_listener_tuple(
insert_headers={'X-Forwarded-For': 'true',
'X-Forwarded-Port': 'true'}))
self.assertEqual(
sample_configs.sample_base_expected_config(backend=be),
sample_configs_split.sample_base_expected_config(backend=be),
rendered_obj)
def test_render_template_pool_proxy_protocol(self):
@ -768,11 +769,11 @@ class TestHaproxyCfg(base.TestCase):
"cookie sample_member_id_2 send-proxy\n\n").format(
maxconn=constants.HAPROXY_MAX_MAXCONN)
rendered_obj = self.jinja_cfg.render_loadbalancer_obj(
sample_configs.sample_amphora_tuple(),
sample_configs.sample_listener_tuple(
sample_configs_split.sample_amphora_tuple(),
sample_configs_split.sample_listener_tuple(
be_proto='PROXY'))
self.assertEqual(
sample_configs.sample_base_expected_config(backend=be),
sample_configs_split.sample_base_expected_config(backend=be),
rendered_obj)
def test_render_template_pool_cert(self):
@ -798,15 +799,15 @@ class TestHaproxyCfg(base.TestCase):
maxconn=constants.HAPROXY_MAX_MAXCONN,
opts="ssl crt %s verify none sni ssl_fc_sni" % cert_file_path)
rendered_obj = self.jinja_cfg.render_loadbalancer_obj(
sample_configs.sample_amphora_tuple(),
sample_configs.sample_listener_tuple(
sample_configs_split.sample_amphora_tuple(),
sample_configs_split.sample_listener_tuple(
pool_cert=True, tls_enabled=True),
pool_tls_certs={
'sample_pool_id_1':
{'client_cert': cert_file_path,
'ca_cert': None, 'crl': None}})
self.assertEqual(
sample_configs.sample_base_expected_config(backend=be),
sample_configs_split.sample_base_expected_config(backend=be),
rendered_obj)
def test_render_template_with_full_pool_cert(self):
@ -837,8 +838,8 @@ class TestHaproxyCfg(base.TestCase):
"crl-file %s" % pool_crl,
"verify required sni ssl_fc_sni"))
rendered_obj = self.jinja_cfg.render_loadbalancer_obj(
sample_configs.sample_amphora_tuple(),
sample_configs.sample_listener_tuple(
sample_configs_split.sample_amphora_tuple(),
sample_configs_split.sample_listener_tuple(
pool_cert=True, pool_ca_cert=True, pool_crl=True,
tls_enabled=True),
pool_tls_certs={
@ -847,105 +848,107 @@ class TestHaproxyCfg(base.TestCase):
'ca_cert': pool_ca_cert,
'crl': pool_crl}})
self.assertEqual(
sample_configs.sample_base_expected_config(backend=be),
sample_configs_split.sample_base_expected_config(backend=be),
rendered_obj)
def test_transform_session_persistence(self):
in_persistence = sample_configs.sample_session_persistence_tuple()
ret = self.jinja_cfg._transform_session_persistence(in_persistence, {})
self.assertEqual(sample_configs.RET_PERSISTENCE, ret)
in_persistence = (
sample_configs_split.sample_session_persistence_tuple())
ret = self.jinja_cfg._transform_session_persistence(
in_persistence, {})
self.assertEqual(sample_configs_split.RET_PERSISTENCE, ret)
def test_transform_health_monitor(self):
in_persistence = sample_configs.sample_health_monitor_tuple()
in_persistence = sample_configs_split.sample_health_monitor_tuple()
ret = self.jinja_cfg._transform_health_monitor(in_persistence, {})
self.assertEqual(sample_configs.RET_MONITOR_1, ret)
self.assertEqual(sample_configs_split.RET_MONITOR_1, ret)
def test_transform_member(self):
in_member = sample_configs.sample_member_tuple('sample_member_id_1',
'10.0.0.99')
in_member = sample_configs_split.sample_member_tuple(
'sample_member_id_1', '10.0.0.99')
ret = self.jinja_cfg._transform_member(in_member, {})
self.assertEqual(sample_configs.RET_MEMBER_1, ret)
self.assertEqual(sample_configs_split.RET_MEMBER_1, ret)
def test_transform_pool(self):
in_pool = sample_configs.sample_pool_tuple()
in_pool = sample_configs_split.sample_pool_tuple()
ret = self.jinja_cfg._transform_pool(in_pool, {})
self.assertEqual(sample_configs.RET_POOL_1, ret)
self.assertEqual(sample_configs_split.RET_POOL_1, ret)
def test_transform_pool_2(self):
in_pool = sample_configs.sample_pool_tuple(sample_pool=2)
in_pool = sample_configs_split.sample_pool_tuple(sample_pool=2)
ret = self.jinja_cfg._transform_pool(in_pool, {})
self.assertEqual(sample_configs.RET_POOL_2, ret)
self.assertEqual(sample_configs_split.RET_POOL_2, ret)
def test_transform_pool_http_reuse(self):
in_pool = sample_configs.sample_pool_tuple(sample_pool=2)
in_pool = sample_configs_split.sample_pool_tuple(sample_pool=2)
ret = self.jinja_cfg._transform_pool(
in_pool, {constants.HTTP_REUSE: True})
expected_config = copy.copy(sample_configs.RET_POOL_2)
expected_config = copy.copy(sample_configs_split.RET_POOL_2)
expected_config[constants.HTTP_REUSE] = True
self.assertEqual(expected_config, ret)
def test_transform_pool_cert(self):
in_pool = sample_configs.sample_pool_tuple(pool_cert=True)
in_pool = sample_configs_split.sample_pool_tuple(pool_cert=True)
cert_path = os.path.join(self.jinja_cfg.base_crt_dir,
'test_listener_id', 'pool_cert.pem')
ret = self.jinja_cfg._transform_pool(
in_pool, {}, pool_tls_certs={'client_cert': cert_path})
expected_config = copy.copy(sample_configs.RET_POOL_1)
expected_config = copy.copy(sample_configs_split.RET_POOL_1)
expected_config['client_cert'] = cert_path
self.assertEqual(expected_config, ret)
def test_transform_listener(self):
in_listener = sample_configs.sample_listener_tuple()
in_listener = sample_configs_split.sample_listener_tuple()
ret = self.jinja_cfg._transform_listener(in_listener, None, {})
self.assertEqual(sample_configs.RET_LISTENER, ret)
self.assertEqual(sample_configs_split.RET_LISTENER, ret)
def test_transform_listener_with_l7(self):
in_listener = sample_configs.sample_listener_tuple(l7=True)
in_listener = sample_configs_split.sample_listener_tuple(l7=True)
ret = self.jinja_cfg._transform_listener(in_listener, None, {})
self.assertEqual(sample_configs.RET_LISTENER_L7, ret)
self.assertEqual(sample_configs_split.RET_LISTENER_L7, ret)
def test_transform_loadbalancer(self):
in_amphora = sample_configs.sample_amphora_tuple()
in_listener = sample_configs.sample_listener_tuple()
in_amphora = sample_configs_split.sample_amphora_tuple()
in_listener = sample_configs_split.sample_listener_tuple()
ret = self.jinja_cfg._transform_loadbalancer(
in_amphora, in_listener.load_balancer, in_listener, None, {})
self.assertEqual(sample_configs.RET_LB, ret)
self.assertEqual(sample_configs_split.RET_LB, ret)
def test_transform_amphora(self):
in_amphora = sample_configs.sample_amphora_tuple()
in_amphora = sample_configs_split.sample_amphora_tuple()
ret = self.jinja_cfg._transform_amphora(in_amphora, {})
self.assertEqual(sample_configs.RET_AMPHORA, ret)
self.assertEqual(sample_configs_split.RET_AMPHORA, ret)
def test_transform_loadbalancer_with_l7(self):
in_amphora = sample_configs.sample_amphora_tuple()
in_listener = sample_configs.sample_listener_tuple(l7=True)
in_amphora = sample_configs_split.sample_amphora_tuple()
in_listener = sample_configs_split.sample_listener_tuple(l7=True)
ret = self.jinja_cfg._transform_loadbalancer(
in_amphora, in_listener.load_balancer, in_listener, None, {})
self.assertEqual(sample_configs.RET_LB_L7, ret)
self.assertEqual(sample_configs_split.RET_LB_L7, ret)
def test_transform_l7policy(self):
in_l7policy = sample_configs.sample_l7policy_tuple(
in_l7policy = sample_configs_split.sample_l7policy_tuple(
'sample_l7policy_id_1')
ret = self.jinja_cfg._transform_l7policy(in_l7policy, {})
self.assertEqual(sample_configs.RET_L7POLICY_1, ret)
self.assertEqual(sample_configs_split.RET_L7POLICY_1, ret)
def test_transform_l7policy_2_8(self):
in_l7policy = sample_configs.sample_l7policy_tuple(
in_l7policy = sample_configs_split.sample_l7policy_tuple(
'sample_l7policy_id_2', sample_policy=2)
ret = self.jinja_cfg._transform_l7policy(in_l7policy, {})
self.assertEqual(sample_configs.RET_L7POLICY_2, ret)
self.assertEqual(sample_configs_split.RET_L7POLICY_2, ret)
# test invalid action without redirect_http_code
in_l7policy = sample_configs.sample_l7policy_tuple(
in_l7policy = sample_configs_split.sample_l7policy_tuple(
'sample_l7policy_id_8', sample_policy=2, redirect_http_code=None)
ret = self.jinja_cfg._transform_l7policy(in_l7policy, {})
self.assertEqual(sample_configs.RET_L7POLICY_8, ret)
self.assertEqual(sample_configs_split.RET_L7POLICY_8, ret)
def test_transform_l7policy_disabled_rule(self):
in_l7policy = sample_configs.sample_l7policy_tuple(
in_l7policy = sample_configs_split.sample_l7policy_tuple(
'sample_l7policy_id_6', sample_policy=6)
ret = self.jinja_cfg._transform_l7policy(in_l7policy, {})
self.assertEqual(sample_configs.RET_L7POLICY_6, ret)
self.assertEqual(sample_configs_split.RET_L7POLICY_6, ret)
def test_escape_haproxy_config_string(self):
self.assertEqual(self.jinja_cfg._escape_haproxy_config_string(
@ -999,11 +1002,12 @@ class TestHaproxyCfg(base.TestCase):
" option splice-response\n"
" option http-keep-alive\n\n")
rendered_obj = j_cfg.render_loadbalancer_obj(
sample_configs.sample_amphora_tuple(),
sample_configs.sample_listener_tuple()
sample_configs_split.sample_amphora_tuple(),
sample_configs_split.sample_listener_tuple()
)
self.assertEqual(
sample_configs.sample_base_expected_config(defaults=defaults),
sample_configs_split.sample_base_expected_config(
defaults=defaults),
rendered_obj)
def test_http_reuse(self):
@ -1030,12 +1034,12 @@ class TestHaproxyCfg(base.TestCase):
"cookie sample_member_id_2 send-proxy\n\n").format(
maxconn=constants.HAPROXY_MAX_MAXCONN)
rendered_obj = j_cfg.build_config(
sample_configs.sample_amphora_tuple(),
sample_configs.sample_listener_tuple(be_proto='PROXY'),
sample_configs_split.sample_amphora_tuple(),
sample_configs_split.sample_listener_tuple(be_proto='PROXY'),
tls_cert=None,
haproxy_versions=("1", "8", "1"))
self.assertEqual(
sample_configs.sample_base_expected_config(backend=be),
sample_configs_split.sample_base_expected_config(backend=be),
rendered_obj)
# Without http-reuse
@ -1056,12 +1060,12 @@ class TestHaproxyCfg(base.TestCase):
"cookie sample_member_id_2 send-proxy\n\n").format(
maxconn=constants.HAPROXY_MAX_MAXCONN)
rendered_obj = j_cfg.build_config(
sample_configs.sample_amphora_tuple(),
sample_configs.sample_listener_tuple(be_proto='PROXY'),
sample_configs_split.sample_amphora_tuple(),
sample_configs_split.sample_listener_tuple(be_proto='PROXY'),
tls_cert=None,
haproxy_versions=("1", "5", "18"))
self.assertEqual(
sample_configs.sample_base_expected_config(backend=be),
sample_configs_split.sample_base_expected_config(backend=be),
rendered_obj)
def test_ssl_types_l7rules(self):
@ -1134,15 +1138,15 @@ class TestHaproxyCfg(base.TestCase):
" timeout server 50000\n"
" server sample_member_id_3 10.0.0.97:82 weight 13 check "
"inter 30s fall 3 rise 2 cookie sample_member_id_3\n\n")
sample_listener = sample_configs.sample_listener_tuple(
sample_listener = sample_configs_split.sample_listener_tuple(
proto=constants.PROTOCOL_TERMINATED_HTTPS, l7=True,
ssl_type_l7=True)
rendered_obj = j_cfg.build_config(
sample_configs.sample_amphora_tuple(),
sample_configs_split.sample_amphora_tuple(),
sample_listener,
tls_cert=None,
haproxy_versions=("1", "5", "18"))
self.assertEqual(
sample_configs.sample_base_expected_config(
sample_configs_split.sample_base_expected_config(
frontend=fe, backend=be),
rendered_obj)

View File

@ -16,7 +16,7 @@
from octavia.common import constants
from octavia.common.jinja.lvs import jinja_cfg
from octavia.tests.unit import base
from octavia.tests.unit.common.sample_configs import sample_configs
from octavia.tests.unit.common.sample_configs import sample_configs_combined
from oslo_config import cfg
from oslo_config import fixture as oslo_fixture
@ -76,7 +76,7 @@ class TestLvsCfg(base.TestCase):
" }\n\n"
"}\n\n")
rendered_obj = self.udp_jinja_cfg.render_loadbalancer_obj(
sample_configs.sample_listener_tuple(
sample_configs_combined.sample_listener_tuple(
proto=constants.PROTOCOL_UDP,
persistence_type=constants.SESSION_PERSISTENCE_SOURCE_IP,
persistence_timeout=33,
@ -124,7 +124,7 @@ class TestLvsCfg(base.TestCase):
" }\n\n"
"}\n\n")
listener = sample_configs.sample_listener_tuple(
listener = sample_configs_combined.sample_listener_tuple(
proto=constants.PROTOCOL_UDP,
monitor_proto=constants.HEALTH_MONITOR_UDP_CONNECT,
connection_limit=98,
@ -172,7 +172,7 @@ class TestLvsCfg(base.TestCase):
"}\n\n")
rendered_obj = self.udp_jinja_cfg.render_loadbalancer_obj(
sample_configs.sample_listener_tuple(
sample_configs_combined.sample_listener_tuple(
proto=constants.PROTOCOL_UDP,
monitor_proto=constants.HEALTH_MONITOR_UDP_CONNECT,
persistence=False,
@ -185,56 +185,57 @@ class TestLvsCfg(base.TestCase):
"net_namespace amphora-haproxy\n\n\n")
rendered_obj = self.udp_jinja_cfg.render_loadbalancer_obj(
sample_configs.sample_listener_tuple(
sample_configs_combined.sample_listener_tuple(
proto=constants.PROTOCOL_UDP, monitor=False,
persistence=False, alloc_default_pool=False))
self.assertEqual(exp, rendered_obj)
def test_udp_transform_session_persistence(self):
persistence_src_ip = sample_configs.sample_session_persistence_tuple(
persistence_type=constants.SESSION_PERSISTENCE_SOURCE_IP,
persistence_cookie=None,
persistence_timeout=33,
persistence_granularity='255.0.0.0'
)
exp = sample_configs.UDP_SOURCE_IP_BODY
persistence_src_ip = (
sample_configs_combined.sample_session_persistence_tuple(
persistence_type=constants.SESSION_PERSISTENCE_SOURCE_IP,
persistence_cookie=None,
persistence_timeout=33,
persistence_granularity='255.0.0.0'
))
exp = sample_configs_combined.UDP_SOURCE_IP_BODY
ret = self.udp_jinja_cfg._transform_session_persistence(
persistence_src_ip)
self.assertEqual(exp, ret)
def test_udp_transform_health_monitor(self):
in_hm = sample_configs.sample_health_monitor_tuple(
in_hm = sample_configs_combined.sample_health_monitor_tuple(
proto=constants.HEALTH_MONITOR_UDP_CONNECT
)
ret = self.udp_jinja_cfg._transform_health_monitor(in_hm)
self.assertEqual(sample_configs.RET_UDP_HEALTH_MONITOR, ret)
self.assertEqual(sample_configs_combined.RET_UDP_HEALTH_MONITOR, ret)
def test_udp_transform_member(self):
in_member = sample_configs.sample_member_tuple('member_id_1',
'192.0.2.10')
in_member = sample_configs_combined.sample_member_tuple(
'member_id_1', '192.0.2.10')
ret = self.udp_jinja_cfg._transform_member(in_member)
self.assertEqual(sample_configs.RET_UDP_MEMBER, ret)
self.assertEqual(sample_configs_combined.RET_UDP_MEMBER, ret)
def test_udp_transform_pool(self):
in_pool = sample_configs.sample_pool_tuple(
in_pool = sample_configs_combined.sample_pool_tuple(
proto=constants.PROTOCOL_UDP,
persistence_type=constants.SESSION_PERSISTENCE_SOURCE_IP,
persistence_timeout=33, persistence_granularity='255.0.0.0',
)
ret = self.udp_jinja_cfg._transform_pool(in_pool)
self.assertEqual(sample_configs.RET_UDP_POOL, ret)
self.assertEqual(sample_configs_combined.RET_UDP_POOL, ret)
in_pool = sample_configs.sample_pool_tuple(
in_pool = sample_configs_combined.sample_pool_tuple(
proto=constants.PROTOCOL_UDP,
persistence_type=constants.SESSION_PERSISTENCE_SOURCE_IP,
persistence_timeout=33, persistence_granularity='255.0.0.0',
monitor=False)
sample_configs.RET_UDP_POOL['health_monitor'] = ''
sample_configs_combined.RET_UDP_POOL['health_monitor'] = ''
ret = self.udp_jinja_cfg._transform_pool(in_pool)
self.assertEqual(sample_configs.RET_UDP_POOL, ret)
self.assertEqual(sample_configs_combined.RET_UDP_POOL, ret)
def test_udp_transform_listener(self):
in_listener = sample_configs.sample_listener_tuple(
in_listener = sample_configs_combined.sample_listener_tuple(
proto=constants.PROTOCOL_UDP,
persistence_type=constants.SESSION_PERSISTENCE_SOURCE_IP,
persistence_timeout=33,
@ -243,9 +244,9 @@ class TestLvsCfg(base.TestCase):
connection_limit=98
)
ret = self.udp_jinja_cfg._transform_listener(in_listener)
self.assertEqual(sample_configs.RET_UDP_LISTENER, ret)
self.assertEqual(sample_configs_combined.RET_UDP_LISTENER, ret)
in_listener = sample_configs.sample_listener_tuple(
in_listener = sample_configs_combined.sample_listener_tuple(
proto=constants.PROTOCOL_UDP,
persistence_type=constants.SESSION_PERSISTENCE_SOURCE_IP,
persistence_timeout=33,
@ -254,5 +255,5 @@ class TestLvsCfg(base.TestCase):
connection_limit=-1)
ret = self.udp_jinja_cfg._transform_listener(in_listener)
sample_configs.RET_UDP_LISTENER.pop('connection_limit')
self.assertEqual(sample_configs.RET_UDP_LISTENER, ret)
sample_configs_combined.RET_UDP_LISTENER.pop('connection_limit')
self.assertEqual(sample_configs_combined.RET_UDP_LISTENER, ret)

File diff suppressed because it is too large Load Diff

View File

@ -27,11 +27,11 @@ def sample_amphora_tuple(id='sample_amphora_id_1', lb_network_ip='10.0.1.1',
vrrp_ip='10.1.1.1', ha_ip='192.168.10.1',
vrrp_port_id='1234', ha_port_id='1234', role=None,
status='ACTIVE', vrrp_interface=None,
vrrp_priority=None):
vrrp_priority=None, api_version='0.5'):
in_amphora = collections.namedtuple(
'amphora', 'id, lb_network_ip, vrrp_ip, ha_ip, vrrp_port_id, '
'ha_port_id, role, status, vrrp_interface,'
'vrrp_priority')
'vrrp_priority, api_version')
return in_amphora(
id=id,
lb_network_ip=lb_network_ip,
@ -42,7 +42,8 @@ def sample_amphora_tuple(id='sample_amphora_id_1', lb_network_ip='10.0.1.1',
role=role,
status=status,
vrrp_interface=vrrp_interface,
vrrp_priority=vrrp_priority)
vrrp_priority=vrrp_priority,
api_version=api_version)
RET_PERSISTENCE = {
@ -504,7 +505,7 @@ def sample_listener_loadbalancer_tuple(proto=None, topology=None,
topology = constants.TOPOLOGY_SINGLE
in_lb = collections.namedtuple(
'load_balancer', 'id, name, protocol, vip, amphorae, topology, '
'enabled')
'listeners, enabled, project_id')
return in_lb(
id='sample_loadbalancer_id_1',
name='test-lb',
@ -518,7 +519,47 @@ def sample_listener_loadbalancer_tuple(proto=None, topology=None,
role=constants.ROLE_BACKUP)]
if more_amp else [sample_amphora_tuple()],
topology=topology,
enabled=enabled
listeners=[],
enabled=enabled,
project_id='12345'
)
def sample_lb_with_udp_listener_tuple(
proto=None, topology=None, enabled=True, pools=None):
proto = 'HTTP' if proto is None else proto
if topology and topology in ['ACTIVE_STANDBY', 'ACTIVE_ACTIVE']:
more_amp = True
else:
more_amp = False
topology = constants.TOPOLOGY_SINGLE
listeners = [sample_listener_tuple(
proto=constants.PROTOCOL_UDP,
persistence_type=constants.SESSION_PERSISTENCE_SOURCE_IP,
persistence_timeout=33,
persistence_granularity='255.255.0.0',
monitor_proto=constants.HEALTH_MONITOR_UDP_CONNECT)]
in_lb = collections.namedtuple(
'load_balancer', 'id, name, protocol, vip, amphorae, topology, '
'pools, enabled, project_id, listeners')
return in_lb(
id='sample_loadbalancer_id_1',
name='test-lb',
protocol=proto,
vip=sample_vip_tuple(),
amphorae=[sample_amphora_tuple(role=constants.ROLE_MASTER),
sample_amphora_tuple(
id='sample_amphora_id_2',
lb_network_ip='10.0.1.2',
vrrp_ip='10.1.1.2',
role=constants.ROLE_BACKUP)]
if more_amp else [sample_amphora_tuple()],
topology=topology,
listeners=listeners,
pools=pools or [],
enabled=enabled,
project_id='12345'
)
@ -558,7 +599,8 @@ def sample_listener_tuple(proto=None, monitor=True, alloc_default_pool=True,
client_ca_cert=False, client_crl_cert=False,
ssl_type_l7=False, pool_cert=False,
pool_ca_cert=False, pool_crl=False,
tls_enabled=False, hm_host_http_check=False):
tls_enabled=False, hm_host_http_check=False,
id='sample_listener_id_1', recursive_nest=False):
proto = 'HTTP' if proto is None else proto
if be_proto is None:
be_proto = 'HTTP' if proto is 'TERMINATED_HTTPS' else proto
@ -617,8 +659,8 @@ def sample_listener_tuple(proto=None, monitor=True, alloc_default_pool=True,
pool_crl=pool_crl, tls_enabled=tls_enabled,
hm_host_http_check=hm_host_http_check)]
l7policies = []
return in_listener(
id='sample_listener_id_1',
listener = in_listener(
id=id,
project_id='12345',
protocol_port=port,
protocol=proto,
@ -682,6 +724,9 @@ def sample_listener_tuple(proto=None, monitor=True, alloc_default_pool=True,
constants.CLIENT_AUTH_NONE),
client_crl_container_id='cont_id_crl' if client_crl_cert else '',
)
if recursive_nest:
listener.load_balancer.listeners.append(listener)
return listener
def sample_tls_sni_container_tuple(tls_container_id=None, tls_container=None):

View File

@ -22,7 +22,7 @@ import octavia.common.exceptions as exceptions
import octavia.common.tls_utils.cert_parser as cert_parser
from octavia.tests.unit import base
from octavia.tests.unit.common.sample_configs import sample_certs
from octavia.tests.unit.common.sample_configs import sample_configs
from octavia.tests.unit.common.sample_configs import sample_configs_combined
class TestTLSParseUtils(base.TestCase):
@ -144,8 +144,8 @@ class TestTLSParseUtils(base.TestCase):
@mock.patch('oslo_context.context.RequestContext')
def test_load_certificates(self, mock_oslo):
listener = sample_configs.sample_listener_tuple(tls=True, sni=True,
client_ca_cert=True)
listener = sample_configs_combined.sample_listener_tuple(
tls=True, sni=True, client_ca_cert=True)
client = mock.MagicMock()
context = mock.Mock()
context.project_id = '12345'
@ -165,8 +165,8 @@ class TestTLSParseUtils(base.TestCase):
client.assert_has_calls(calls_cert_mngr)
# Test asking for nothing
listener = sample_configs.sample_listener_tuple(tls=False, sni=False,
client_ca_cert=False)
listener = sample_configs_combined.sample_listener_tuple(
tls=False, sni=False, client_ca_cert=False)
client = mock.MagicMock()
with mock.patch.object(cert_parser,
'_map_cert_tls_container') as mock_map:
@ -211,7 +211,7 @@ class TestTLSParseUtils(base.TestCase):
def test_build_pem(self):
expected = b'imacert\nimakey\nimainter\nimainter2\n'
tls_tuple = sample_configs.sample_tls_container_tuple(
tls_tuple = sample_configs_combined.sample_tls_container_tuple(
certificate=b'imacert', private_key=b'imakey',
intermediates=[b'imainter', b'imainter2'])
self.assertEqual(expected, cert_parser.build_pem(tls_tuple))

View File

@ -143,7 +143,8 @@ class TestUpdateHealthDb(base.TestCase):
if listener:
listener_ref = {'listener-id-1': {
constants.OPERATING_STATUS: 'bogus',
'protocol': listener_protocol}}
'protocol': listener_protocol,
'enabled': True}}
lb_ref['listeners'] = listener_ref
return lb_ref
@ -184,6 +185,7 @@ class TestUpdateHealthDb(base.TestCase):
health = {
"id": self.FAKE_UUID_1,
"ver": 1,
"listeners": {},
"recv_time": time.time()
}
@ -200,6 +202,7 @@ class TestUpdateHealthDb(base.TestCase):
health = {
"id": self.FAKE_UUID_1,
"ver": 1,
"listeners": {},
"recv_time": time.time()
}
@ -217,6 +220,7 @@ class TestUpdateHealthDb(base.TestCase):
health = {
"id": self.FAKE_UUID_1,
"ver": 1,
"listeners": {},
"recv_time": time.time()
}
@ -233,6 +237,7 @@ class TestUpdateHealthDb(base.TestCase):
hb_interval = cfg.CONF.health_manager.heartbeat_interval
health = {
"id": self.FAKE_UUID_1,
"ver": 1,
"listeners": {},
"recv_time": time.time() - hb_interval - 1 # extra -1 for buffer
}
@ -250,6 +255,7 @@ class TestUpdateHealthDb(base.TestCase):
health = {
"id": self.FAKE_UUID_1,
"ver": 1,
"listeners": {
"listener-id-1": {"status": constants.OPEN, "pools": {
"pool-id-1": {"status": constants.UP,
@ -272,9 +278,9 @@ class TestUpdateHealthDb(base.TestCase):
self.session_mock.rollback.assert_called_once()
def test_update_health_online(self):
health = {
"id": self.FAKE_UUID_1,
"ver": 1,
"listeners": {
"listener-id-1": {"status": constants.OPEN, "pools": {
"pool-id-1": {"status": constants.UP,
@ -320,10 +326,53 @@ class TestUpdateHealthDb(base.TestCase):
self.hm.update_health(health, '192.0.2.1')
self.assertTrue(not self.amphora_health_repo.replace.called)
def test_update_health_listener_disabled(self):
health = {
"id": self.FAKE_UUID_1,
"ver": 1,
"listeners": {
"listener-id-1": {"status": constants.OPEN, "pools": {
"pool-id-1": {"status": constants.UP,
"members": {"member-id-1": constants.UP}
}
}
}
},
"recv_time": time.time()
}
lb_ref = self._make_fake_lb_health_dict()
lb_ref['listeners']['listener-id-2'] = {
'enabled': False, constants.OPERATING_STATUS: constants.OFFLINE}
self.amphora_repo.get_lb_for_health_update.return_value = lb_ref
self.hm.update_health(health, '192.0.2.1')
self.assertTrue(self.amphora_health_repo.replace.called)
# test listener, member
for listener_id, listener in six.iteritems(
health.get('listeners', {})):
self.listener_repo.update.assert_any_call(
self.session_mock, listener_id,
operating_status=constants.ONLINE)
for pool_id, pool in six.iteritems(listener.get('pools', {})):
self.hm.pool_repo.update.assert_any_call(
self.session_mock, pool_id,
operating_status=constants.ONLINE)
for member_id, member in six.iteritems(
pool.get('members', {})):
self.member_repo.update.assert_any_call(
self.session_mock, member_id,
operating_status=constants.ONLINE)
def test_update_lb_pool_health_offline(self):
health = {
"id": self.FAKE_UUID_1,
"ver": 1,
"listeners": {
"listener-id-1": {"status": constants.OPEN, "pools": {}}
},
@ -351,6 +400,7 @@ class TestUpdateHealthDb(base.TestCase):
def test_update_lb_multiple_listeners_one_error_pool(self):
health = {
"id": self.FAKE_UUID_1,
"ver": 1,
"listeners": {
"listener-id-1": {"status": constants.OPEN, "pools": {
"pool-id-1": {"status": constants.DOWN,
@ -372,7 +422,8 @@ class TestUpdateHealthDb(base.TestCase):
lb_ref['listeners']['listener-id-2'] = {
constants.OPERATING_STATUS: 'bogus',
'protocol': constants.PROTOCOL_TCP}
'protocol': constants.PROTOCOL_TCP,
'enabled': True}
self.amphora_repo.get_lb_for_health_update.return_value = lb_ref
self.hm.update_health(health, '192.0.2.1')
@ -398,6 +449,7 @@ class TestUpdateHealthDb(base.TestCase):
health = {
"id": self.FAKE_UUID_1,
"ver": 1,
"listeners": {
"listener-id-1": {"status": constants.OPEN, "pools": {
"pool-id-1": {"status": constants.UP,
@ -435,10 +487,54 @@ class TestUpdateHealthDb(base.TestCase):
self.session_mock, member_id,
operating_status=constants.ONLINE)
def test_update_v2_lb_and_list_pool_health_online(self):
health = {
"id": self.FAKE_UUID_1,
"ver": 2,
"listeners": {
"listener-id-1": {"status": constants.OPEN}
},
"pools": {
"pool-id-1:listener-id-1": {
"status": constants.UP,
"members": {"member-id-1": constants.UP}},
"pool-id-1:listener-id-1": {
"status": constants.UP,
"members": {"member-id-1": constants.UP}}},
"recv_time": time.time()
}
lb_ref = self._make_fake_lb_health_dict()
self.amphora_repo.get_lb_for_health_update.return_value = lb_ref
self.hm.update_health(health, '192.0.2.1')
self.assertTrue(self.amphora_health_repo.replace.called)
# test listener, member
for listener_id, listener in six.iteritems(
health.get('listeners', {})):
self.listener_repo.update.assert_any_call(
self.session_mock, listener_id,
operating_status=constants.ONLINE)
for pool_id, pool in six.iteritems(health.get('pools', {})):
# We should not double process a shared pool
self.hm.pool_repo.update.assert_called_once_with(
self.session_mock, 'pool-id-1',
operating_status=constants.ONLINE)
for member_id, member in six.iteritems(
pool.get('members', {})):
self.member_repo.update.assert_any_call(
self.session_mock, member_id,
operating_status=constants.ONLINE)
def test_update_pool_offline(self):
health = {
"id": self.FAKE_UUID_1,
"ver": 1,
"listeners": {
"listener-id-1": {"status": constants.OPEN, "pools": {
"pool-id-5": {"status": constants.UP,
@ -475,6 +571,7 @@ class TestUpdateHealthDb(base.TestCase):
health = {
"id": self.FAKE_UUID_1,
"ver": 1,
"listeners": {
"listener-id-1": {
"status": constants.OPEN,
@ -514,6 +611,7 @@ class TestUpdateHealthDb(base.TestCase):
health = {
"id": self.FAKE_UUID_1,
"ver": 1,
"listeners": {
"listener-id-1": {
"status": constants.OPEN,
@ -553,6 +651,7 @@ class TestUpdateHealthDb(base.TestCase):
health = {
"id": self.FAKE_UUID_1,
"ver": 1,
"listeners": {
"listener-id-1": {
"status": constants.OPEN,
@ -586,6 +685,7 @@ class TestUpdateHealthDb(base.TestCase):
health = {
"id": self.FAKE_UUID_1,
"ver": 1,
"listeners": {
"listener-id-1": {"status": constants.OPEN, "pools": {
"pool-id-1": {"status": constants.UP,
@ -628,6 +728,7 @@ class TestUpdateHealthDb(base.TestCase):
health = {
"id": self.FAKE_UUID_1,
"ver": 1,
"listeners": {
"listener-id-1": {"status": constants.OPEN, "pools": {
"pool-id-1": {"status": constants.UP,
@ -665,6 +766,7 @@ class TestUpdateHealthDb(base.TestCase):
health = {
"id": self.FAKE_UUID_1,
"ver": 1,
"listeners": {
"listener-id-1": {"status": constants.OPEN, "pools": {
"pool-id-1": {"status": constants.UP,
@ -707,6 +809,7 @@ class TestUpdateHealthDb(base.TestCase):
health = {
"id": self.FAKE_UUID_1,
"ver": 1,
"listeners": {
"listener-id-1": {"status": constants.OPEN, "pools": {
"pool-id-1": {"status": constants.UP,
@ -749,6 +852,7 @@ class TestUpdateHealthDb(base.TestCase):
health = {
"id": self.FAKE_UUID_1,
"ver": 1,
"listeners": {
"listener-id-1": {
"status": constants.OPEN,
@ -794,6 +898,7 @@ class TestUpdateHealthDb(base.TestCase):
health = {
"id": self.FAKE_UUID_1,
"ver": 1,
"listeners": {
"listener-id-1": {"status": constants.FULL, "pools": {
"pool-id-1": {"status": constants.UP,
@ -843,6 +948,7 @@ class TestUpdateHealthDb(base.TestCase):
health = {
"id": self.FAKE_UUID_1,
"ver": 1,
"listeners": {
"listener-id-1": {"status": constants.OPEN, "pools": {
"pool-id-1": {"status": constants.DOWN,
@ -886,6 +992,7 @@ class TestUpdateHealthDb(base.TestCase):
health = {
"id": self.FAKE_UUID_1,
"ver": 1,
"listeners": {
"listener-id-1": {"status": constants.FULL, "pools": {
"pool-id-1": {"status": constants.DOWN,
@ -934,7 +1041,8 @@ class TestUpdateHealthDb(base.TestCase):
lb_ref['listeners']['listener-id-%s' % i] = {
constants.OPERATING_STATUS: 'bogus',
'protocol': constants.PROTOCOL_TCP}
'protocol': constants.PROTOCOL_TCP,
'enabled': True}
if i == 3:
members_dict = {'member-id-3': {
@ -975,6 +1083,7 @@ class TestUpdateHealthDb(base.TestCase):
health = {
"id": self.FAKE_UUID_1,
"ver": 1,
"listeners": {
"listener-id-1": {"status": constants.OPEN, "pools": {
"pool-id-1": {"status": constants.UP,
@ -1039,6 +1148,7 @@ class TestUpdateHealthDb(base.TestCase):
def test_update_health_no_status_change(self):
health = {
"id": self.FAKE_UUID_1,
"ver": 1,
"listeners": {
"listener-id-1": {
"status": constants.OPEN, "pools": {
@ -1076,6 +1186,7 @@ class TestUpdateHealthDb(base.TestCase):
def test_update_health_lb_admin_down(self):
health = {
"id": self.FAKE_UUID_1,
"ver": 1,
"listeners": {},
"recv_time": time.time()}
@ -1158,10 +1269,12 @@ class TestUpdateHealthDb(base.TestCase):
listener_protocol=constants.PROTOCOL_UDP)
lb_ref['listeners']['listener-id-2'] = {
constants.OPERATING_STATUS: 'bogus',
'protocol': constants.PROTOCOL_UDP}
'protocol': constants.PROTOCOL_UDP,
'enabled': True}
lb_ref['listeners']['listener-id-3'] = {
constants.OPERATING_STATUS: 'bogus',
'protocol': constants.PROTOCOL_UDP}
'protocol': constants.PROTOCOL_UDP,
'enabled': True}
self.amphora_repo.get_lb_for_health_update.return_value = lb_ref
self.hm.update_health(health, '192.0.2.1')
@ -1172,6 +1285,7 @@ class TestUpdateHealthDb(base.TestCase):
def test_update_health_no_db_lb(self):
health = {
"id": self.FAKE_UUID_1,
"ver": 1,
"listeners": {},
"recv_time": time.time()
}
@ -1356,6 +1470,7 @@ class TestUpdateStatsDb(base.TestCase):
health = {
"id": self.amphora_id,
"ver": 1,
"listeners": {
self.listener_id: {
"status": constants.OPEN,

View File

@ -128,9 +128,10 @@ class TestLoadBalancerFlows(base.TestCase):
self.assertIsInstance(lb_flow, flow.Flow)
self.assertIn(constants.LOADBALANCER, lb_flow.requires)
self.assertIn(constants.UPDATE_DICT, lb_flow.requires)
self.assertEqual(0, len(lb_flow.provides))
self.assertEqual(3, len(lb_flow.requires))
self.assertEqual(2, len(lb_flow.requires))
def test_get_post_lb_amp_association_flow(self, mock_get_net_driver):
amp_flow = self.LBFlow.get_post_lb_amp_association_flow(

View File

@ -93,15 +93,15 @@ class TestAmphoraDriverTasks(base.TestCase):
constants.CONN_RETRY_INTERVAL: 4}
amp_list_update_obj = amphora_driver_tasks.AmpListenersUpdate()
amp_list_update_obj.execute([_listener_mock], 0,
amp_list_update_obj.execute(_load_balancer_mock, 0,
[_amphora_mock], timeout_dict)
mock_driver.update_amphora_listeners.assert_called_once_with(
[_listener_mock], 0, [_amphora_mock], timeout_dict)
_load_balancer_mock, _amphora_mock, timeout_dict)
mock_driver.update_amphora_listeners.side_effect = Exception('boom')
amp_list_update_obj.execute([_listener_mock], 0,
amp_list_update_obj.execute(_load_balancer_mock, 0,
[_amphora_mock], timeout_dict)
mock_amphora_repo_update.assert_called_once_with(
@ -117,9 +117,9 @@ class TestAmphoraDriverTasks(base.TestCase):
mock_amphora_repo_update):
listener_update_obj = amphora_driver_tasks.ListenersUpdate()
listener_update_obj.execute(_load_balancer_mock, [_listener_mock])
listener_update_obj.execute(_load_balancer_mock)
mock_driver.update.assert_called_once_with(_listener_mock, _vip_mock)
mock_driver.update.assert_called_once_with(_load_balancer_mock)
# Test the revert
amp = listener_update_obj.revert(_load_balancer_mock)
@ -152,12 +152,9 @@ class TestAmphoraDriverTasks(base.TestCase):
data_models.Listener(id='listener2')]
vip = data_models.Vip(ip_address='10.0.0.1')
lb = data_models.LoadBalancer(id='lb1', listeners=listeners, vip=vip)
listeners_update_obj.execute(lb, listeners)
mock_driver.update.assert_has_calls([mock.call(listeners[0], vip),
mock.call(listeners[1], vip)])
self.assertEqual(2, mock_driver.update.call_count)
self.assertIsNotNone(listeners[0].load_balancer)
self.assertIsNotNone(listeners[1].load_balancer)
listeners_update_obj.execute(lb)
mock_driver.update.assert_called_once_with(lb)
self.assertEqual(1, mock_driver.update.call_count)
# Test the revert
amp = listeners_update_obj.revert(lb)
@ -171,69 +168,37 @@ class TestAmphoraDriverTasks(base.TestCase):
self.assertEqual(2, repo.ListenerRepository.update.call_count)
self.assertIsNone(amp)
def test_listener_stop(self,
mock_driver,
mock_generate_uuid,
mock_log,
mock_get_session,
mock_listener_repo_get,
mock_listener_repo_update,
mock_amphora_repo_update):
@mock.patch('octavia.controller.worker.task_utils.TaskUtils.'
'mark_listener_prov_status_error')
def test_listeners_start(self,
mock_prov_status_error,
mock_driver,
mock_generate_uuid,
mock_log,
mock_get_session,
mock_listener_repo_get,
mock_listener_repo_update,
mock_amphora_repo_update):
listeners_start_obj = amphora_driver_tasks.ListenersStart()
mock_lb = mock.MagicMock()
mock_listener = mock.MagicMock()
mock_listener.id = '12345'
listener_stop_obj = amphora_driver_tasks.ListenerStop()
listener_stop_obj.execute(_load_balancer_mock, _listener_mock)
# Test no listeners
mock_lb.listeners = None
listeners_start_obj.execute(mock_lb)
mock_driver.start.assert_not_called()
mock_driver.stop.assert_called_once_with(_listener_mock, _vip_mock)
# Test with listeners
mock_driver.start.reset_mock()
mock_lb.listeners = [mock_listener]
listeners_start_obj.execute(mock_lb)
mock_driver.start.assert_called_once_with(mock_lb, None)
# Test the revert
amp = listener_stop_obj.revert(_listener_mock)
repo.ListenerRepository.update.assert_called_once_with(
_session_mock,
id=LISTENER_ID,
provisioning_status=constants.ERROR)
self.assertIsNone(amp)
# Test the revert with exception
repo.ListenerRepository.update.reset_mock()
mock_listener_repo_update.side_effect = Exception('fail')
amp = listener_stop_obj.revert(_listener_mock)
repo.ListenerRepository.update.assert_called_once_with(
_session_mock,
id=LISTENER_ID,
provisioning_status=constants.ERROR)
self.assertIsNone(amp)
def test_listener_start(self,
mock_driver,
mock_generate_uuid,
mock_log,
mock_get_session,
mock_listener_repo_get,
mock_listener_repo_update,
mock_amphora_repo_update):
listener_start_obj = amphora_driver_tasks.ListenerStart()
listener_start_obj.execute(_load_balancer_mock, _listener_mock)
mock_driver.start.assert_called_once_with(_listener_mock, _vip_mock)
# Test the revert
amp = listener_start_obj.revert(_listener_mock)
repo.ListenerRepository.update.assert_called_once_with(
_session_mock,
id=LISTENER_ID,
provisioning_status=constants.ERROR)
self.assertIsNone(amp)
# Test the revert with exception
repo.ListenerRepository.update.reset_mock()
mock_listener_repo_update.side_effect = Exception('fail')
amp = listener_start_obj.revert(_listener_mock)
repo.ListenerRepository.update.assert_called_once_with(
_session_mock,
id=LISTENER_ID,
provisioning_status=constants.ERROR)
self.assertIsNone(amp)
# Test revert
mock_lb.listeners = [mock_listener]
listeners_start_obj.revert(mock_lb)
mock_prov_status_error.assert_called_once_with('12345')
def test_listener_delete(self,
mock_driver,
@ -245,9 +210,9 @@ class TestAmphoraDriverTasks(base.TestCase):
mock_amphora_repo_update):
listener_delete_obj = amphora_driver_tasks.ListenerDelete()
listener_delete_obj.execute(_load_balancer_mock, _listener_mock)
listener_delete_obj.execute(_listener_mock)
mock_driver.delete.assert_called_once_with(_listener_mock, _vip_mock)
mock_driver.delete.assert_called_once_with(_listener_mock)
# Test the revert
amp = listener_delete_obj.revert(_listener_mock)

View File

@ -49,6 +49,7 @@ _health_mon_mock = mock.MagicMock()
_vip_mock = mock.MagicMock()
_listener_mock = mock.MagicMock()
_load_balancer_mock = mock.MagicMock()
_load_balancer_mock.listeners = [_listener_mock]
_member_mock = mock.MagicMock()
_pool_mock = mock.MagicMock()
_l7policy_mock = mock.MagicMock()
@ -324,7 +325,7 @@ class TestControllerWorker(base.TestCase):
store={constants.LOADBALANCER:
_load_balancer_mock,
constants.LISTENERS:
[_listener_mock]}))
_load_balancer_mock.listeners}))
_flow_mock.run.assert_called_once_with()
self.assertEqual(2, mock_listener_repo_get.call_count)

View File

@ -0,0 +1,11 @@
---
upgrade:
- |
A new amphora image is required to resolve the amphora memory issues when
a load balancer has multiple listeners and the amphora image uses
haproxy 1.8 or newer.
fixes:
- |
Fixed an issue with load balancers that have multiple listeners when using
an amphora image that contains HAProxy 1.8 or newer. An updated amphora
image is required to apply this fix.