Fix flavors support when using spares pool
This patch validates that a flavor is compatible with using spares pool amphora. It will also update the amphora-agent config after a spares pool amphora has been allocated. This patch enables the ability to update a running amphora's agent configuration and have the mutatable options be adopted. The following amphora agent configuration options can be updated: heartbeat_key controller_ip_port_list heartbeat_interval loadbalancer_topology This patch adds the support to the amphora-agent and the amphora driver. A follow on patch will expose this capabililty via the amphora admin API. Change-Id: I97bdf5188808193516509f20767e82c0f8d2f5a5
This commit is contained in:
parent
ddcae3e229
commit
5d7f10f6b8
doc/source/contributor/api
octavia
amphorae
backends
driver_exceptions
drivers
common
controller/worker
tests
functional/amphorae/backend/agent/api_server
unit
amphorae
backends/health_daemon
drivers
common
controller/worker
@ -29,9 +29,9 @@ communication is limited to fail-over protocols.)
|
|||||||
Versioning
|
Versioning
|
||||||
----------
|
----------
|
||||||
All Octavia APIs (including internal APIs like this one) are versioned. For the
|
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.1. (So,
|
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
|
any reference to a *:version* variable should be replaced with the literal
|
||||||
string 'v0.1'.)
|
string 'v0.5'.)
|
||||||
|
|
||||||
Response codes
|
Response codes
|
||||||
--------------
|
--------------
|
||||||
@ -408,7 +408,7 @@ Get interface
|
|||||||
::
|
::
|
||||||
|
|
||||||
GET URL:
|
GET URL:
|
||||||
https://octavia-haproxy-img-00328.local/v0.1/interface/10.0.0.1
|
https://octavia-haproxy-img-00328.local/v0.5/interface/10.0.0.1
|
||||||
|
|
||||||
JSON Response:
|
JSON Response:
|
||||||
{
|
{
|
||||||
@ -422,7 +422,7 @@ Get interface
|
|||||||
::
|
::
|
||||||
|
|
||||||
GET URL:
|
GET URL:
|
||||||
https://octavia-haproxy-img-00328.local/v0.1/interface/10.5.0.1
|
https://octavia-haproxy-img-00328.local/v0.5/interface/10.5.0.1
|
||||||
|
|
||||||
JSON Response:
|
JSON Response:
|
||||||
{
|
{
|
||||||
@ -435,7 +435,7 @@ Get interface
|
|||||||
::
|
::
|
||||||
|
|
||||||
GET URL:
|
GET URL:
|
||||||
https://octavia-haproxy-img-00328.local/v0.1/interface/10.6.0.1.1
|
https://octavia-haproxy-img-00328.local/v0.5/interface/10.6.0.1.1
|
||||||
|
|
||||||
JSON Response:
|
JSON Response:
|
||||||
{
|
{
|
||||||
@ -621,7 +621,7 @@ Start or Stop a listener
|
|||||||
::
|
::
|
||||||
|
|
||||||
PUT URL:
|
PUT URL:
|
||||||
https://octavia-haproxy-img-00328.local/v0.1/listeners/04bff5c3-5862-4a13-b9e3-9b440d0ed50a/start
|
https://octavia-haproxy-img-00328.local/v0.5/listeners/04bff5c3-5862-4a13-b9e3-9b440d0ed50a/start
|
||||||
|
|
||||||
JSON Response:
|
JSON Response:
|
||||||
{
|
{
|
||||||
@ -634,7 +634,7 @@ Start or Stop a listener
|
|||||||
::
|
::
|
||||||
|
|
||||||
PUT URL:
|
PUT URL:
|
||||||
https://octavia-haproxy-img-00328.local/v0.1/listeners/04bff5c3-5862-4a13-b9e3-9b440d0ed50a/BAD_TEST_DATA
|
https://octavia-haproxy-img-00328.local/v0.5/listeners/04bff5c3-5862-4a13-b9e3-9b440d0ed50a/BAD_TEST_DATA
|
||||||
|
|
||||||
JSON Response:
|
JSON Response:
|
||||||
{
|
{
|
||||||
@ -647,7 +647,7 @@ Start or Stop a listener
|
|||||||
::
|
::
|
||||||
|
|
||||||
PUT URL:
|
PUT URL:
|
||||||
https://octavia-haproxy-img-00328.local/v0.1/listeners/04bff5c3-5862-4a13-b9e3-9b440d0ed50a/stop
|
https://octavia-haproxy-img-00328.local/v0.5/listeners/04bff5c3-5862-4a13-b9e3-9b440d0ed50a/stop
|
||||||
|
|
||||||
JSON Response:
|
JSON Response:
|
||||||
{
|
{
|
||||||
@ -660,7 +660,7 @@ Start or Stop a listener
|
|||||||
::
|
::
|
||||||
|
|
||||||
PUT URL:
|
PUT URL:
|
||||||
https://octavia-haproxy-img-00328.local/v0.1/listeners/04bff5c3-5862-4a13-b9e3-9b440d0ed50a/stop
|
https://octavia-haproxy-img-00328.local/v0.5/listeners/04bff5c3-5862-4a13-b9e3-9b440d0ed50a/stop
|
||||||
|
|
||||||
Response:
|
Response:
|
||||||
{
|
{
|
||||||
@ -724,7 +724,7 @@ Delete a listener
|
|||||||
::
|
::
|
||||||
|
|
||||||
DELETE URL:
|
DELETE URL:
|
||||||
https://octavia-haproxy-img-00328.local/v0.1/listeners/04bff5c3-5862-4a13-b9e3-9b440d0ed50a
|
https://octavia-haproxy-img-00328.local/v0.5/listeners/04bff5c3-5862-4a13-b9e3-9b440d0ed50a
|
||||||
|
|
||||||
JSON Response:
|
JSON Response:
|
||||||
{
|
{
|
||||||
@ -736,7 +736,7 @@ Delete a listener
|
|||||||
::
|
::
|
||||||
|
|
||||||
DELETE URL:
|
DELETE URL:
|
||||||
https://octavia-haproxy-img-00328.local/v0.1/listeners/04bff5c3-5862-4a13-b9e3-9b440d0ed50a
|
https://octavia-haproxy-img-00328.local/v0.5/listeners/04bff5c3-5862-4a13-b9e3-9b440d0ed50a
|
||||||
|
|
||||||
JSON Response:
|
JSON Response:
|
||||||
{
|
{
|
||||||
@ -812,7 +812,7 @@ explicitly restarted
|
|||||||
::
|
::
|
||||||
|
|
||||||
PUT URI:
|
PUT URI:
|
||||||
https://octavia-haproxy-img-00328.local/v0.1/listeners/04bff5c3-5862-4a13-b9e3-9b440d0ed50a/certificates/www.example.com.pem
|
https://octavia-haproxy-img-00328.local/v0.5/listeners/04bff5c3-5862-4a13-b9e3-9b440d0ed50a/certificates/www.example.com.pem
|
||||||
(Put data should contain the certificate information, concatenated as
|
(Put data should contain the certificate information, concatenated as
|
||||||
described above)
|
described above)
|
||||||
|
|
||||||
@ -826,7 +826,7 @@ explicitly restarted
|
|||||||
::
|
::
|
||||||
|
|
||||||
PUT URI:
|
PUT URI:
|
||||||
https://octavia-haproxy-img-00328.local/v0.1/listeners/04bff5c3-5862-4a13-b9e3-9b440d0ed50a/certificates/www.example.com.pem
|
https://octavia-haproxy-img-00328.local/v0.5/listeners/04bff5c3-5862-4a13-b9e3-9b440d0ed50a/certificates/www.example.com.pem
|
||||||
(If PUT data does not contain a certificate)
|
(If PUT data does not contain a certificate)
|
||||||
|
|
||||||
JSON Response:
|
JSON Response:
|
||||||
@ -839,7 +839,7 @@ explicitly restarted
|
|||||||
::
|
::
|
||||||
|
|
||||||
PUT URI:
|
PUT URI:
|
||||||
https://octavia-haproxy-img-00328.local/v0.1/listeners/04bff5c3-5862-4a13-b9e3-9b440d0ed50a/certificates/www.example.com.pem
|
https://octavia-haproxy-img-00328.local/v0.5/listeners/04bff5c3-5862-4a13-b9e3-9b440d0ed50a/certificates/www.example.com.pem
|
||||||
(If PUT data does not contain an RSA key)
|
(If PUT data does not contain an RSA key)
|
||||||
|
|
||||||
JSON Response:
|
JSON Response:
|
||||||
@ -852,7 +852,7 @@ explicitly restarted
|
|||||||
::
|
::
|
||||||
|
|
||||||
PUT URI:
|
PUT URI:
|
||||||
https://octavia-haproxy-img-00328.local/v0.1/listeners/04bff5c3-5862-4a13-b9e3-9b440d0ed50a/certificates/www.example.com.pem
|
https://octavia-haproxy-img-00328.local/v0.5/listeners/04bff5c3-5862-4a13-b9e3-9b440d0ed50a/certificates/www.example.com.pem
|
||||||
(If the first certificate and the RSA key do not have the same modulus.)
|
(If the first certificate and the RSA key do not have the same modulus.)
|
||||||
|
|
||||||
JSON Response:
|
JSON Response:
|
||||||
@ -865,7 +865,7 @@ explicitly restarted
|
|||||||
::
|
::
|
||||||
|
|
||||||
PUT URI:
|
PUT URI:
|
||||||
https://octavia-haproxy-img-00328.local/v0.1/listeners/04bff5c3-5862-4a13-b9e3-9b440d0ed50a/certificates/www.example.com.pem
|
https://octavia-haproxy-img-00328.local/v0.5/listeners/04bff5c3-5862-4a13-b9e3-9b440d0ed50a/certificates/www.example.com.pem
|
||||||
|
|
||||||
JSON Response:
|
JSON Response:
|
||||||
{
|
{
|
||||||
@ -988,7 +988,7 @@ Delete SSL certificate PEM file
|
|||||||
::
|
::
|
||||||
|
|
||||||
DELETE URL:
|
DELETE URL:
|
||||||
https://octavia-haproxy-img-00328.local/v0.1/listeners/04bff5c3-5862-4a13-b9e3-9b440d0ed50a/certificates/www.example.com.pem
|
https://octavia-haproxy-img-00328.local/v0.5/listeners/04bff5c3-5862-4a13-b9e3-9b440d0ed50a/certificates/www.example.com.pem
|
||||||
|
|
||||||
JSON Response:
|
JSON Response:
|
||||||
{
|
{
|
||||||
@ -1000,7 +1000,7 @@ Delete SSL certificate PEM file
|
|||||||
::
|
::
|
||||||
|
|
||||||
DELETE URL:
|
DELETE URL:
|
||||||
https://octavia-haproxy-img-00328.local/v0.1/listeners/04bff5c3-5862-4a13-b9e3-9b440d0ed50a/certificates/www.example.com.pem
|
https://octavia-haproxy-img-00328.local/v0.5/listeners/04bff5c3-5862-4a13-b9e3-9b440d0ed50a/certificates/www.example.com.pem
|
||||||
|
|
||||||
JSON Response:
|
JSON Response:
|
||||||
{
|
{
|
||||||
@ -1071,7 +1071,7 @@ out of the haproxy daemon status interface for tracking health and stats).
|
|||||||
::
|
::
|
||||||
|
|
||||||
PUT URL:
|
PUT URL:
|
||||||
https://octavia-haproxy-img-00328.local/v0.1/listeners/d459b1c8-54b0-4030-9bec-4f449e73b1ef/04bff5c3-5862-4a13-b9e3-9b440d0ed50a/haproxy
|
https://octavia-haproxy-img-00328.local/v0.5/listeners/d459b1c8-54b0-4030-9bec-4f449e73b1ef/04bff5c3-5862-4a13-b9e3-9b440d0ed50a/haproxy
|
||||||
(Upload PUT data should be a raw haproxy.conf file.)
|
(Upload PUT data should be a raw haproxy.conf file.)
|
||||||
|
|
||||||
JSON Response:
|
JSON Response:
|
||||||
@ -1134,7 +1134,7 @@ Get listener haproxy configuration
|
|||||||
::
|
::
|
||||||
|
|
||||||
GET URL:
|
GET URL:
|
||||||
https://octavia-haproxy-img-00328.local/v0.1/listeners/7e9f91eb-b3e6-4e3b-a1a7-d6f7fdc1de7c/haproxy
|
https://octavia-haproxy-img-00328.local/v0.5/listeners/7e9f91eb-b3e6-4e3b-a1a7-d6f7fdc1de7c/haproxy
|
||||||
|
|
||||||
Response is the raw haproxy.cfg:
|
Response is the raw haproxy.cfg:
|
||||||
|
|
||||||
@ -1210,7 +1210,7 @@ Plug VIP
|
|||||||
::
|
::
|
||||||
|
|
||||||
POST URL:
|
POST URL:
|
||||||
https://octavia-haproxy-img-00328.local/v0.1/plug/vip/203.0.113.2
|
https://octavia-haproxy-img-00328.local/v0.5/plug/vip/203.0.113.2
|
||||||
|
|
||||||
JSON POST parameters:
|
JSON POST parameters:
|
||||||
{
|
{
|
||||||
@ -1292,7 +1292,7 @@ Plug Network
|
|||||||
::
|
::
|
||||||
|
|
||||||
POST URL:
|
POST URL:
|
||||||
https://octavia-haproxy-img-00328.local/v0.1/plug/network/
|
https://octavia-haproxy-img-00328.local/v0.5/plug/network/
|
||||||
|
|
||||||
JSON POST parameters:
|
JSON POST parameters:
|
||||||
{
|
{
|
||||||
@ -1362,7 +1362,7 @@ not be available for some time.
|
|||||||
::
|
::
|
||||||
|
|
||||||
PUT URI:
|
PUT URI:
|
||||||
https://octavia-haproxy-img-00328.local/v0.1/certificate
|
https://octavia-haproxy-img-00328.local/v0.5/certificate
|
||||||
(Put data should contain the certificate information, concatenated as
|
(Put data should contain the certificate information, concatenated as
|
||||||
described above)
|
described above)
|
||||||
|
|
||||||
@ -1376,7 +1376,7 @@ not be available for some time.
|
|||||||
::
|
::
|
||||||
|
|
||||||
PUT URI:
|
PUT URI:
|
||||||
https://octavia-haproxy-img-00328.local/v0.1/certificates
|
https://octavia-haproxy-img-00328.local/v0.5/certificates
|
||||||
(If PUT data does not contain a certificate)
|
(If PUT data does not contain a certificate)
|
||||||
|
|
||||||
JSON Response:
|
JSON Response:
|
||||||
@ -1389,7 +1389,7 @@ not be available for some time.
|
|||||||
::
|
::
|
||||||
|
|
||||||
PUT URI:
|
PUT URI:
|
||||||
https://octavia-haproxy-img-00328.local/v0.1/certificate
|
https://octavia-haproxy-img-00328.local/v0.5/certificate
|
||||||
(If PUT data does not contain an RSA key)
|
(If PUT data does not contain an RSA key)
|
||||||
|
|
||||||
JSON Response:
|
JSON Response:
|
||||||
@ -1402,7 +1402,7 @@ not be available for some time.
|
|||||||
::
|
::
|
||||||
|
|
||||||
PUT URI:
|
PUT URI:
|
||||||
https://octavia-haproxy-img-00328.local/v0.1/certificate
|
https://octavia-haproxy-img-00328.local/v0.5/certificate
|
||||||
(If the first certificate and the RSA key do not have the same modulus.)
|
(If the first certificate and the RSA key do not have the same modulus.)
|
||||||
|
|
||||||
JSON Response:
|
JSON Response:
|
||||||
@ -1441,7 +1441,7 @@ OK
|
|||||||
::
|
::
|
||||||
|
|
||||||
PUT URI:
|
PUT URI:
|
||||||
https://octavia-haproxy-img-00328.local/v0.1/vrrp/upload
|
https://octavia-haproxy-img-00328.local/v0.5/vrrp/upload
|
||||||
|
|
||||||
JSON Response:
|
JSON Response:
|
||||||
{
|
{
|
||||||
@ -1489,7 +1489,7 @@ Start, Stop, or Reload keepalived
|
|||||||
::
|
::
|
||||||
|
|
||||||
PUT URL:
|
PUT URL:
|
||||||
https://octavia-haproxy-img-00328.local/v0.1/vrrp/start
|
https://octavia-haproxy-img-00328.local/v0.5/vrrp/start
|
||||||
|
|
||||||
JSON Response:
|
JSON Response:
|
||||||
{
|
{
|
||||||
@ -1502,7 +1502,7 @@ Start, Stop, or Reload keepalived
|
|||||||
::
|
::
|
||||||
|
|
||||||
PUT URL:
|
PUT URL:
|
||||||
https://octavia-haproxy-img-00328.local/v0.1/vrrp/BAD_TEST_DATA
|
https://octavia-haproxy-img-00328.local/v0.5/vrrp/BAD_TEST_DATA
|
||||||
|
|
||||||
JSON Response:
|
JSON Response:
|
||||||
{
|
{
|
||||||
@ -1515,7 +1515,7 @@ Start, Stop, or Reload keepalived
|
|||||||
::
|
::
|
||||||
|
|
||||||
PUT URL:
|
PUT URL:
|
||||||
https://octavia-haproxy-img-00328.local/v0.1/vrrp/stop
|
https://octavia-haproxy-img-00328.local/v0.5/vrrp/stop
|
||||||
|
|
||||||
JSON Response:
|
JSON Response:
|
||||||
{
|
{
|
||||||
@ -1523,4 +1523,58 @@ Start, Stop, or Reload keepalived
|
|||||||
'details': 'keeepalived process with PID 3352 not found',
|
'details': 'keeepalived process with PID 3352 not found',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Update the amphora agent configuration
|
||||||
|
--------------------------------------
|
||||||
|
|
||||||
|
* **URL:** /*:version*/config
|
||||||
|
* **Method:** PUT
|
||||||
|
|
||||||
|
* **Data params:** A amphora-agent configuration file
|
||||||
|
* **Success Response:**
|
||||||
|
|
||||||
|
* Code: 202
|
||||||
|
|
||||||
|
* Content: OK
|
||||||
|
|
||||||
|
* **Error Response:**
|
||||||
|
|
||||||
|
* Code: 500
|
||||||
|
|
||||||
|
* message: Unable to update amphora-agent configuration.
|
||||||
|
* details: *(The exception details)*
|
||||||
|
|
||||||
|
* **Response:**
|
||||||
|
|
||||||
|
| OK
|
||||||
|
|
||||||
|
* **Implied actions:**
|
||||||
|
|
||||||
|
* The running amphora-agent configuration file is mutated.
|
||||||
|
|
||||||
|
**Notes:** Only options that are marked mutable in the oslo configuration
|
||||||
|
will be updated.
|
||||||
|
|
||||||
|
**Examples:**
|
||||||
|
|
||||||
|
* Success code 202:
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
PUT URL:
|
||||||
|
https://octavia-haproxy-img-00328.local/v0.5/config
|
||||||
|
(Upload PUT data should be a raw amphora-agent.conf file.)
|
||||||
|
|
||||||
|
JSON Response:
|
||||||
|
{
|
||||||
|
'message': 'OK'
|
||||||
|
}
|
||||||
|
|
||||||
|
* Error code 500:
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
JSON Response:
|
||||||
|
{
|
||||||
|
'message': 'Unable to update amphora-agent configuration.',
|
||||||
|
'details': *(The exception output)*,
|
||||||
|
}
|
||||||
|
@ -12,8 +12,12 @@
|
|||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
|
import os
|
||||||
|
import stat
|
||||||
|
|
||||||
import flask
|
import flask
|
||||||
|
from oslo_config import cfg
|
||||||
|
from oslo_log import log as logging
|
||||||
import six
|
import six
|
||||||
import webob
|
import webob
|
||||||
from werkzeug import exceptions
|
from werkzeug import exceptions
|
||||||
@ -28,7 +32,10 @@ from octavia.amphorae.backends.agent.api_server import plug
|
|||||||
from octavia.amphorae.backends.agent.api_server import udp_listener_base
|
from octavia.amphorae.backends.agent.api_server import udp_listener_base
|
||||||
from octavia.amphorae.backends.agent.api_server import util
|
from octavia.amphorae.backends.agent.api_server import util
|
||||||
|
|
||||||
|
BUFFER = 1024
|
||||||
|
CONF = cfg.CONF
|
||||||
PATH_PREFIX = '/' + api_server.VERSION
|
PATH_PREFIX = '/' + api_server.VERSION
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
# make the error pages all json
|
# make the error pages all json
|
||||||
@ -81,6 +88,9 @@ class Server(object):
|
|||||||
self.app.add_url_rule(rule=PATH_PREFIX + '/listeners/<listener_id>',
|
self.app.add_url_rule(rule=PATH_PREFIX + '/listeners/<listener_id>',
|
||||||
view_func=self.delete_listener,
|
view_func=self.delete_listener,
|
||||||
methods=['DELETE'])
|
methods=['DELETE'])
|
||||||
|
self.app.add_url_rule(rule=PATH_PREFIX + '/config',
|
||||||
|
view_func=self.upload_config,
|
||||||
|
methods=['PUT'])
|
||||||
self.app.add_url_rule(rule=PATH_PREFIX + '/details',
|
self.app.add_url_rule(rule=PATH_PREFIX + '/details',
|
||||||
view_func=self.get_details,
|
view_func=self.get_details,
|
||||||
methods=['GET'])
|
methods=['GET'])
|
||||||
@ -217,3 +227,27 @@ class Server(object):
|
|||||||
|
|
||||||
def get_interface(self, ip_addr):
|
def get_interface(self, ip_addr):
|
||||||
return self._amphora_info.get_interface(ip_addr)
|
return self._amphora_info.get_interface(ip_addr)
|
||||||
|
|
||||||
|
def upload_config(self):
|
||||||
|
try:
|
||||||
|
stream = flask.request.stream
|
||||||
|
file_path = cfg.find_config_files(project=CONF.project,
|
||||||
|
prog=CONF.prog)[0]
|
||||||
|
flags = os.O_WRONLY | os.O_CREAT | os.O_TRUNC
|
||||||
|
# mode 00600
|
||||||
|
mode = stat.S_IRUSR | stat.S_IWUSR
|
||||||
|
with os.fdopen(os.open(file_path, flags, mode), 'wb') as cfg_file:
|
||||||
|
b = stream.read(BUFFER)
|
||||||
|
while b:
|
||||||
|
cfg_file.write(b)
|
||||||
|
b = stream.read(BUFFER)
|
||||||
|
|
||||||
|
CONF.mutate_config_files()
|
||||||
|
except Exception as e:
|
||||||
|
LOG.error("Unable to update amphora-agent configuration: "
|
||||||
|
"{}".format(str(e)))
|
||||||
|
return webob.Response(json=dict(
|
||||||
|
message="Unable to update amphora-agent configuration.",
|
||||||
|
details=str(e)), status=500)
|
||||||
|
|
||||||
|
return webob.Response(json={'message': 'OK'}, status=202)
|
||||||
|
@ -33,18 +33,9 @@ def round_robin_addr(addrinfo_list):
|
|||||||
|
|
||||||
class UDPStatusSender(object):
|
class UDPStatusSender(object):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.dests = []
|
self._update_dests()
|
||||||
for ipport in CONF.health_manager.controller_ip_port_list:
|
|
||||||
try:
|
|
||||||
ip, port = ipport.rsplit(':', 1)
|
|
||||||
except ValueError:
|
|
||||||
LOG.error("Invalid ip and port '%s' in health_manager "
|
|
||||||
"controller_ip_port_list", ipport)
|
|
||||||
break
|
|
||||||
self.update(ip, port)
|
|
||||||
self.v4sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
self.v4sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||||
self.v6sock = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM)
|
self.v6sock = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM)
|
||||||
self.key = str(CONF.health_manager.heartbeat_key)
|
|
||||||
|
|
||||||
def update(self, dest, port):
|
def update(self, dest, port):
|
||||||
addrlist = socket.getaddrinfo(dest, port, 0, socket.SOCK_DGRAM)
|
addrlist = socket.getaddrinfo(dest, port, 0, socket.SOCK_DGRAM)
|
||||||
@ -55,7 +46,9 @@ class UDPStatusSender(object):
|
|||||||
break
|
break
|
||||||
|
|
||||||
def _send_msg(self, dest, msg):
|
def _send_msg(self, dest, msg):
|
||||||
envelope_str = status_message.wrap_envelope(msg, self.key)
|
# Note: heartbeat_key is mutable and must be looked up for each call
|
||||||
|
envelope_str = status_message.wrap_envelope(
|
||||||
|
msg, str(CONF.health_manager.heartbeat_key))
|
||||||
# dest = (family, socktype, proto, canonname, sockaddr)
|
# dest = (family, socktype, proto, canonname, sockaddr)
|
||||||
# e.g. 0 = sock family, 4 = sockaddr - what we actually need
|
# e.g. 0 = sock family, 4 = sockaddr - what we actually need
|
||||||
try:
|
try:
|
||||||
@ -71,7 +64,25 @@ class UDPStatusSender(object):
|
|||||||
# if the message isn't received
|
# if the message isn't received
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
# The controller_ip_port_list configuration has mutated, reload it.
|
||||||
|
def _update_dests(self):
|
||||||
|
self.dests = []
|
||||||
|
for ipport in CONF.health_manager.controller_ip_port_list:
|
||||||
|
try:
|
||||||
|
ip, port = ipport.rsplit(':', 1)
|
||||||
|
except ValueError:
|
||||||
|
LOG.error("Invalid ip and port '%s' in health_manager "
|
||||||
|
"controller_ip_port_list", ipport)
|
||||||
|
break
|
||||||
|
self.update(ip, port)
|
||||||
|
self.current_controller_ip_port_list = (
|
||||||
|
CONF.health_manager.controller_ip_port_list)
|
||||||
|
|
||||||
def dosend(self, obj):
|
def dosend(self, obj):
|
||||||
|
# Check for controller_ip_port_list mutation
|
||||||
|
if not (self.current_controller_ip_port_list ==
|
||||||
|
CONF.health_manager.controller_ip_port_list):
|
||||||
|
self._update_dests()
|
||||||
dest = round_robin_addr(self.dests)
|
dest = round_robin_addr(self.dests)
|
||||||
if dest is None:
|
if dest is None:
|
||||||
LOG.error('No controller address found. Unable to send heartbeat.')
|
LOG.error('No controller address found. Unable to send heartbeat.')
|
||||||
|
@ -116,3 +116,8 @@ class HealthMonitorProvisioningError(ProvisioningErrors):
|
|||||||
class NodeProvisioningError(ProvisioningErrors):
|
class NodeProvisioningError(ProvisioningErrors):
|
||||||
|
|
||||||
message = _('couldn\'t provision Node')
|
message = _('couldn\'t provision Node')
|
||||||
|
|
||||||
|
|
||||||
|
class AmpDriverNotImplementedError(AmphoraDriverError):
|
||||||
|
|
||||||
|
message = _('Amphora does not implement this feature.')
|
||||||
|
@ -219,6 +219,16 @@ class AmphoraLoadBalancerDriver(object):
|
|||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
def update_agent_config(self, amphora, agent_config):
|
||||||
|
"""Upload and update the amphora agent configuration.
|
||||||
|
|
||||||
|
:param amphora: amphora object, needs id and network ip(s)
|
||||||
|
:type amphora: object
|
||||||
|
:param agent_config: The new amphora agent configuration file.
|
||||||
|
:type agent_config: string
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
@six.add_metaclass(abc.ABCMeta)
|
@six.add_metaclass(abc.ABCMeta)
|
||||||
class HealthMixin(object):
|
class HealthMixin(object):
|
||||||
|
@ -289,6 +289,31 @@ class HaproxyAmphoraLoadBalancerDriver(
|
|||||||
self.client.upload_cert_pem(
|
self.client.upload_cert_pem(
|
||||||
amp, listener_id, name, pem)
|
amp, listener_id, name, pem)
|
||||||
|
|
||||||
|
def update_amphora_agent_config(self, amphora, agent_config,
|
||||||
|
timeout_dict=None):
|
||||||
|
"""Update the amphora agent configuration file.
|
||||||
|
|
||||||
|
:param amphora: The amphora to update.
|
||||||
|
:type amphora: object
|
||||||
|
:param agent_config: The new amphora agent configuration.
|
||||||
|
:type agent_config: string
|
||||||
|
: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
|
||||||
|
:returns: None
|
||||||
|
|
||||||
|
Note: This will mutate the amphora agent config and adopt the
|
||||||
|
new values.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
self.client.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))
|
||||||
|
raise driver_except.AmpDriverNotImplementedError()
|
||||||
|
|
||||||
|
|
||||||
# Check a custom hostname
|
# Check a custom hostname
|
||||||
class CustomHostNameCheckingAdapter(requests.adapters.HTTPAdapter):
|
class CustomHostNameCheckingAdapter(requests.adapters.HTTPAdapter):
|
||||||
@ -515,3 +540,7 @@ class AmphoraAPIClient(object):
|
|||||||
amphora_id=amp.id, listener_id=listener_id), timeout_dict,
|
amphora_id=amp.id, listener_id=listener_id), timeout_dict,
|
||||||
data=config)
|
data=config)
|
||||||
return exc.check_exception(r)
|
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)
|
||||||
|
@ -104,11 +104,18 @@ class NoopManager(object):
|
|||||||
load_balancer.id, amphorae_network_config, 'post_vip_plug')
|
load_balancer.id, amphorae_network_config, 'post_vip_plug')
|
||||||
|
|
||||||
def upload_cert_amp(self, amphora, pem_file):
|
def upload_cert_amp(self, amphora, pem_file):
|
||||||
LOG.debug("Amphora %s no-op, upload cert amphora %s,with pem fle %s",
|
LOG.debug("Amphora %s no-op, upload cert amphora %s,with pem file %s",
|
||||||
self.__class__.__name__, amphora.id, pem_file)
|
self.__class__.__name__, amphora.id, pem_file)
|
||||||
self.amphoraconfig[amphora.id, pem_file] = (amphora.id, pem_file,
|
self.amphoraconfig[amphora.id, pem_file] = (amphora.id, pem_file,
|
||||||
'update_amp_cert_file')
|
'update_amp_cert_file')
|
||||||
|
|
||||||
|
def update_agent_config(self, amphora, agent_config):
|
||||||
|
LOG.debug("Amphora %s no-op, update agent config amphora "
|
||||||
|
"%s, with agent config %s",
|
||||||
|
self.__class__.__name__, amphora.id, agent_config)
|
||||||
|
self.amphoraconfig[amphora.id, agent_config] = (
|
||||||
|
amphora.id, agent_config, 'update_agent_config')
|
||||||
|
|
||||||
|
|
||||||
class NoopAmphoraLoadBalancerDriver(
|
class NoopAmphoraLoadBalancerDriver(
|
||||||
driver_base.AmphoraLoadBalancerDriver,
|
driver_base.AmphoraLoadBalancerDriver,
|
||||||
@ -164,6 +171,9 @@ class NoopAmphoraLoadBalancerDriver(
|
|||||||
|
|
||||||
self.driver.upload_cert_amp(amphora, pem_file)
|
self.driver.upload_cert_amp(amphora, pem_file)
|
||||||
|
|
||||||
|
def update_agent_config(self, amphora, agent_config):
|
||||||
|
self.driver.update_agent_config(amphora, agent_config)
|
||||||
|
|
||||||
def update_vrrp_conf(self, loadbalancer):
|
def update_vrrp_conf(self, loadbalancer):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@ -174,6 +174,7 @@ healthmanager_opts = [
|
|||||||
default=None,
|
default=None,
|
||||||
help=_('Number of processes for amphora stats update.')),
|
help=_('Number of processes for amphora stats update.')),
|
||||||
cfg.StrOpt('heartbeat_key',
|
cfg.StrOpt('heartbeat_key',
|
||||||
|
mutable=True,
|
||||||
help=_('key used to validate amphora sending '
|
help=_('key used to validate amphora sending '
|
||||||
'the message'), secret=True),
|
'the message'), secret=True),
|
||||||
cfg.IntOpt('heartbeat_timeout',
|
cfg.IntOpt('heartbeat_timeout',
|
||||||
@ -191,9 +192,11 @@ healthmanager_opts = [
|
|||||||
help=_('List of controller ip and port pairs for the '
|
help=_('List of controller ip and port pairs for the '
|
||||||
'heartbeat receivers. Example 127.0.0.1:5555, '
|
'heartbeat receivers. Example 127.0.0.1:5555, '
|
||||||
'192.168.0.1:5555'),
|
'192.168.0.1:5555'),
|
||||||
|
mutable=True,
|
||||||
default=[]),
|
default=[]),
|
||||||
cfg.IntOpt('heartbeat_interval',
|
cfg.IntOpt('heartbeat_interval',
|
||||||
default=10,
|
default=10,
|
||||||
|
mutable=True,
|
||||||
help=_('Sleep time between sending heartbeats.')),
|
help=_('Sleep time between sending heartbeats.')),
|
||||||
|
|
||||||
# Used for updating health and stats
|
# Used for updating health and stats
|
||||||
@ -377,6 +380,7 @@ controller_worker_opts = [
|
|||||||
cfg.StrOpt('loadbalancer_topology',
|
cfg.StrOpt('loadbalancer_topology',
|
||||||
default=constants.TOPOLOGY_SINGLE,
|
default=constants.TOPOLOGY_SINGLE,
|
||||||
choices=constants.SUPPORTED_LB_TOPOLOGIES,
|
choices=constants.SUPPORTED_LB_TOPOLOGIES,
|
||||||
|
mutable=True,
|
||||||
help=_('Load balancer topology configuration. '
|
help=_('Load balancer topology configuration. '
|
||||||
'SINGLE - One amphora per load balancer. '
|
'SINGLE - One amphora per load balancer. '
|
||||||
'ACTIVE_STANDBY - Two amphora per load balancer.')),
|
'ACTIVE_STANDBY - Two amphora per load balancer.')),
|
||||||
|
@ -334,6 +334,7 @@ AMP_COMPUTE_CONNECTIVITY_WAIT = 'octavia-amp-compute-connectivity-wait'
|
|||||||
AMP_LISTENER_UPDATE = 'octavia-amp-listeners-update'
|
AMP_LISTENER_UPDATE = 'octavia-amp-listeners-update'
|
||||||
|
|
||||||
GENERATE_SERVER_PEM_TASK = 'GenerateServerPEMTask'
|
GENERATE_SERVER_PEM_TASK = 'GenerateServerPEMTask'
|
||||||
|
AMPHORA_CONFIG_UPDATE_TASK = 'AmphoraConfigUpdateTask'
|
||||||
|
|
||||||
# Batch Member Update constants
|
# Batch Member Update constants
|
||||||
UNORDERED_MEMBER_UPDATES_FLOW = 'octavia-unordered-member-updates-flow'
|
UNORDERED_MEMBER_UPDATES_FLOW = 'octavia-unordered-member-updates-flow'
|
||||||
|
@ -346,3 +346,11 @@ def ip_not_reserved(ip_address):
|
|||||||
if ip_address in CONF.networking.reserved_ips:
|
if ip_address in CONF.networking.reserved_ips:
|
||||||
raise exceptions.InvalidOption(value=ip_address,
|
raise exceptions.InvalidOption(value=ip_address,
|
||||||
option='member address')
|
option='member address')
|
||||||
|
|
||||||
|
|
||||||
|
def is_flavor_spares_compatible(flavor):
|
||||||
|
if flavor:
|
||||||
|
# If a compute flavor is specified, the flavor is not spares compatible
|
||||||
|
if flavor.get(constants.COMPUTE_FLAVOR, None):
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
@ -108,18 +108,22 @@ class ControllerWorker(base_taskflow.BaseTaskFlowEngine):
|
|||||||
|
|
||||||
:returns: amphora_id
|
:returns: amphora_id
|
||||||
"""
|
"""
|
||||||
create_amp_tf = self._taskflow_load(
|
try:
|
||||||
self._amphora_flows.get_create_amphora_flow(),
|
create_amp_tf = self._taskflow_load(
|
||||||
store={constants.BUILD_TYPE_PRIORITY:
|
self._amphora_flows.get_create_amphora_flow(),
|
||||||
constants.LB_CREATE_SPARES_POOL_PRIORITY}
|
store={constants.BUILD_TYPE_PRIORITY:
|
||||||
)
|
constants.LB_CREATE_SPARES_POOL_PRIORITY,
|
||||||
with tf_logging.DynamicLoggingListener(
|
constants.FLAVOR: None}
|
||||||
create_amp_tf, log=LOG,
|
)
|
||||||
hide_inputs_outputs_of=self._exclude_result_logging_tasks):
|
with tf_logging.DynamicLoggingListener(
|
||||||
|
create_amp_tf, log=LOG,
|
||||||
|
hide_inputs_outputs_of=self._exclude_result_logging_tasks):
|
||||||
|
|
||||||
create_amp_tf.run()
|
create_amp_tf.run()
|
||||||
|
|
||||||
return create_amp_tf.storage.fetch('amphora')
|
return create_amp_tf.storage.fetch('amphora')
|
||||||
|
except Exception as e:
|
||||||
|
LOG.error('Failed to create an amphora due to: {}'.format(str(e)))
|
||||||
|
|
||||||
def delete_amphora(self, amphora_id):
|
def delete_amphora(self, amphora_id):
|
||||||
"""Deletes an existing Amphora.
|
"""Deletes an existing Amphora.
|
||||||
|
@ -95,6 +95,10 @@ class AmphoraFlows(object):
|
|||||||
requires=constants.AMPHORA_ID,
|
requires=constants.AMPHORA_ID,
|
||||||
provides=constants.AMPHORA))
|
provides=constants.AMPHORA))
|
||||||
|
|
||||||
|
post_map_amp_to_lb.add(amphora_driver_tasks.AmphoraConfigUpdate(
|
||||||
|
name=sf_name + '-' + constants.AMPHORA_CONFIG_UPDATE_TASK,
|
||||||
|
requires=(constants.AMPHORA, constants.FLAVOR)))
|
||||||
|
|
||||||
if role == constants.ROLE_MASTER:
|
if role == constants.ROLE_MASTER:
|
||||||
post_map_amp_to_lb.add(database_tasks.MarkAmphoraMasterInDB(
|
post_map_amp_to_lb.add(database_tasks.MarkAmphoraMasterInDB(
|
||||||
name=sf_name + '-' + constants.MARK_AMP_MASTER_INDB,
|
name=sf_name + '-' + constants.MARK_AMP_MASTER_INDB,
|
||||||
@ -252,7 +256,7 @@ class AmphoraFlows(object):
|
|||||||
# Setup the task that maps an amphora to a load balancer
|
# Setup the task that maps an amphora to a load balancer
|
||||||
allocate_and_associate_amp = database_tasks.MapLoadbalancerToAmphora(
|
allocate_and_associate_amp = database_tasks.MapLoadbalancerToAmphora(
|
||||||
name=sf_name + '-' + constants.MAP_LOADBALANCER_TO_AMPHORA,
|
name=sf_name + '-' + constants.MAP_LOADBALANCER_TO_AMPHORA,
|
||||||
requires=constants.LOADBALANCER_ID,
|
requires=(constants.LOADBALANCER_ID, constants.FLAVOR),
|
||||||
provides=constants.AMPHORA_ID)
|
provides=constants.AMPHORA_ID)
|
||||||
|
|
||||||
# Define a subflow for if we successfully map an amphora
|
# Define a subflow for if we successfully map an amphora
|
||||||
|
@ -20,6 +20,7 @@ from stevedore import driver as stevedore_driver
|
|||||||
from taskflow import task
|
from taskflow import task
|
||||||
from taskflow.types import failure
|
from taskflow.types import failure
|
||||||
|
|
||||||
|
from octavia.amphorae.backends.agent import agent_jinja_cfg
|
||||||
from octavia.amphorae.driver_exceptions import exceptions as driver_except
|
from octavia.amphorae.driver_exceptions import exceptions as driver_except
|
||||||
from octavia.common import constants
|
from octavia.common import constants
|
||||||
from octavia.controller.worker import task_utils as task_utilities
|
from octavia.controller.worker import task_utils as task_utilities
|
||||||
@ -357,7 +358,7 @@ class AmphoraVRRPStart(BaseAmphoraTask):
|
|||||||
|
|
||||||
|
|
||||||
class AmphoraComputeConnectivityWait(BaseAmphoraTask):
|
class AmphoraComputeConnectivityWait(BaseAmphoraTask):
|
||||||
""""Task to wait for the compute instance to be up."""
|
"""Task to wait for the compute instance to be up."""
|
||||||
|
|
||||||
def execute(self, amphora):
|
def execute(self, amphora):
|
||||||
"""Execute get_info routine for an amphora until it responds."""
|
"""Execute get_info routine for an amphora until it responds."""
|
||||||
@ -373,3 +374,28 @@ class AmphoraComputeConnectivityWait(BaseAmphoraTask):
|
|||||||
self.amphora_repo.update(db_apis.get_session(), amphora.id,
|
self.amphora_repo.update(db_apis.get_session(), amphora.id,
|
||||||
status=constants.ERROR)
|
status=constants.ERROR)
|
||||||
raise
|
raise
|
||||||
|
|
||||||
|
|
||||||
|
class AmphoraConfigUpdate(BaseAmphoraTask):
|
||||||
|
"""Task to push a new amphora agent configuration to the amphroa."""
|
||||||
|
|
||||||
|
def execute(self, amphora, flavor):
|
||||||
|
# Extract any flavor based settings
|
||||||
|
if flavor:
|
||||||
|
topology = flavor.get(constants.LOADBALANCER_TOPOLOGY,
|
||||||
|
CONF.controller_worker.loadbalancer_topology)
|
||||||
|
else:
|
||||||
|
topology = CONF.controller_worker.loadbalancer_topology
|
||||||
|
|
||||||
|
# Build the amphora agent config
|
||||||
|
agent_cfg_tmpl = agent_jinja_cfg.AgentJinjaTemplater()
|
||||||
|
agent_config = agent_cfg_tmpl.build_agent_config(amphora.id, topology)
|
||||||
|
|
||||||
|
# Push the new configuration to the amphroa
|
||||||
|
try:
|
||||||
|
self.amphora_driver.update_amphora_agent_config(amphora,
|
||||||
|
agent_config)
|
||||||
|
except driver_except.AmpDriverNotImplementedError:
|
||||||
|
LOG.error('Amphora {} does not support agent configuration '
|
||||||
|
'update. Please update the amphora image for this '
|
||||||
|
'amphora. Skipping.'.format(amphora.id))
|
||||||
|
@ -27,6 +27,7 @@ from taskflow.types import failure
|
|||||||
from octavia.common import constants
|
from octavia.common import constants
|
||||||
from octavia.common import data_models
|
from octavia.common import data_models
|
||||||
import octavia.common.tls_utils.cert_parser as cert_parser
|
import octavia.common.tls_utils.cert_parser as cert_parser
|
||||||
|
from octavia.common import validate
|
||||||
from octavia.controller.worker import task_utils as task_utilities
|
from octavia.controller.worker import task_utils as task_utilities
|
||||||
from octavia.db import api as db_apis
|
from octavia.db import api as db_apis
|
||||||
from octavia.db import repositories as repo
|
from octavia.db import repositories as repo
|
||||||
@ -479,7 +480,7 @@ class AssociateFailoverAmphoraWithLBID(BaseDatabaseTask):
|
|||||||
class MapLoadbalancerToAmphora(BaseDatabaseTask):
|
class MapLoadbalancerToAmphora(BaseDatabaseTask):
|
||||||
"""Maps and assigns a load balancer to an amphora in the database."""
|
"""Maps and assigns a load balancer to an amphora in the database."""
|
||||||
|
|
||||||
def execute(self, loadbalancer_id, server_group_id=None):
|
def execute(self, loadbalancer_id, server_group_id=None, flavor=None):
|
||||||
"""Allocates an Amphora for the load balancer in the database.
|
"""Allocates an Amphora for the load balancer in the database.
|
||||||
|
|
||||||
:param loadbalancer_id: The load balancer id to map to an amphora
|
:param loadbalancer_id: The load balancer id to map to an amphora
|
||||||
@ -495,6 +496,13 @@ class MapLoadbalancerToAmphora(BaseDatabaseTask):
|
|||||||
"pool allocation.")
|
"pool allocation.")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
# Validate the flavor is spares compatible
|
||||||
|
if not validate.is_flavor_spares_compatible(flavor):
|
||||||
|
LOG.debug("Load balancer has a flavor that is not compatible with "
|
||||||
|
"using spares pool amphora. Skipping spares pool "
|
||||||
|
"allocation.")
|
||||||
|
return None
|
||||||
|
|
||||||
amp = self.amphora_repo.allocate_and_associate(
|
amp = self.amphora_repo.allocate_and_associate(
|
||||||
db_apis.get_session(),
|
db_apis.get_session(),
|
||||||
loadbalancer_id)
|
loadbalancer_id)
|
||||||
|
@ -19,6 +19,7 @@ import socket
|
|||||||
import stat
|
import stat
|
||||||
import subprocess
|
import subprocess
|
||||||
|
|
||||||
|
import fixtures
|
||||||
import mock
|
import mock
|
||||||
import netifaces
|
import netifaces
|
||||||
from oslo_config import fixture as oslo_fixture
|
from oslo_config import fixture as oslo_fixture
|
||||||
@ -36,6 +37,7 @@ from octavia.tests.common import utils as test_utils
|
|||||||
import octavia.tests.unit.base as base
|
import octavia.tests.unit.base as base
|
||||||
|
|
||||||
|
|
||||||
|
AMP_AGENT_CONF_PATH = '/etc/octavia/amphora-agent.conf'
|
||||||
RANDOM_ERROR = 'random error'
|
RANDOM_ERROR = 'random error'
|
||||||
OK = dict(message='OK')
|
OK = dict(message='OK')
|
||||||
|
|
||||||
@ -49,6 +51,11 @@ class TestServerTestCase(base.TestCase):
|
|||||||
self.conf.config(group="haproxy_amphora", base_path='/var/lib/octavia')
|
self.conf.config(group="haproxy_amphora", base_path='/var/lib/octavia')
|
||||||
self.conf.config(group="controller_worker",
|
self.conf.config(group="controller_worker",
|
||||||
loadbalancer_topology=consts.TOPOLOGY_SINGLE)
|
loadbalancer_topology=consts.TOPOLOGY_SINGLE)
|
||||||
|
self.conf.load_raw_values(project='fake_project')
|
||||||
|
self.conf.load_raw_values(prog='fake_prog')
|
||||||
|
self.useFixture(fixtures.MockPatch(
|
||||||
|
'oslo_config.cfg.find_config_files',
|
||||||
|
return_value=[AMP_AGENT_CONF_PATH]))
|
||||||
with mock.patch('distro.id',
|
with mock.patch('distro.id',
|
||||||
return_value='ubuntu'):
|
return_value='ubuntu'):
|
||||||
self.ubuntu_test_server = server.Server()
|
self.ubuntu_test_server = server.Server()
|
||||||
@ -2650,3 +2657,38 @@ class TestServerTestCase(base.TestCase):
|
|||||||
self.assertEqual(200, rv.status_code)
|
self.assertEqual(200, rv.status_code)
|
||||||
self.assertEqual(expected_dict,
|
self.assertEqual(expected_dict,
|
||||||
json.loads(rv.data.decode('utf-8')))
|
json.loads(rv.data.decode('utf-8')))
|
||||||
|
|
||||||
|
def test_ubuntu_upload_config(self):
|
||||||
|
self._test_upload_config(consts.UBUNTU)
|
||||||
|
|
||||||
|
def test_centos_upload_config(self):
|
||||||
|
self._test_upload_config(consts.CENTOS)
|
||||||
|
|
||||||
|
@mock.patch('oslo_config.cfg.CONF.mutate_config_files')
|
||||||
|
def _test_upload_config(self, distro, mock_mutate):
|
||||||
|
server.BUFFER = 5 # test the while loop
|
||||||
|
m = self.useFixture(
|
||||||
|
test_utils.OpenFixture(AMP_AGENT_CONF_PATH)).mock_open
|
||||||
|
with mock.patch('os.open'), mock.patch.object(os, 'fdopen', m):
|
||||||
|
if distro == consts.UBUNTU:
|
||||||
|
rv = self.ubuntu_app.put('/' + api_server.VERSION +
|
||||||
|
'/config', data='TestTest')
|
||||||
|
elif distro == consts.CENTOS:
|
||||||
|
rv = self.centos_app.put('/' + api_server.VERSION +
|
||||||
|
'/config', data='TestTest')
|
||||||
|
self.assertEqual(202, rv.status_code)
|
||||||
|
self.assertEqual(OK, json.loads(rv.data.decode('utf-8')))
|
||||||
|
handle = m()
|
||||||
|
handle.write.assert_any_call(six.b('TestT'))
|
||||||
|
handle.write.assert_any_call(six.b('est'))
|
||||||
|
mock_mutate.assert_called_once_with()
|
||||||
|
|
||||||
|
# Test the exception handling
|
||||||
|
mock_mutate.side_effect = Exception('boom')
|
||||||
|
if distro == consts.UBUNTU:
|
||||||
|
rv = self.ubuntu_app.put('/' + api_server.VERSION +
|
||||||
|
'/config', data='TestTest')
|
||||||
|
elif distro == consts.CENTOS:
|
||||||
|
rv = self.centos_app.put('/' + api_server.VERSION +
|
||||||
|
'/config', data='TestTest')
|
||||||
|
self.assertEqual(500, rv.status_code)
|
||||||
|
@ -127,3 +127,37 @@ class TestHealthSender(base.TestCase):
|
|||||||
|
|
||||||
# Should not raise an exception
|
# Should not raise an exception
|
||||||
sender.dosend(SAMPLE_MSG)
|
sender.dosend(SAMPLE_MSG)
|
||||||
|
|
||||||
|
# Test an controller_ip_port_list update
|
||||||
|
sendto_mock.reset_mock()
|
||||||
|
mock_getaddrinfo.reset_mock()
|
||||||
|
self.conf.config(group="health_manager",
|
||||||
|
controller_ip_port_list=['192.0.2.20:80'])
|
||||||
|
mock_getaddrinfo.return_value = [(socket.AF_INET,
|
||||||
|
socket.SOCK_DGRAM,
|
||||||
|
socket.IPPROTO_UDP,
|
||||||
|
'',
|
||||||
|
('192.0.2.20', 80))]
|
||||||
|
sender = health_sender.UDPStatusSender()
|
||||||
|
sender.dosend(SAMPLE_MSG)
|
||||||
|
sendto_mock.assert_called_once_with(SAMPLE_MSG_BIN,
|
||||||
|
('192.0.2.20', 80))
|
||||||
|
mock_getaddrinfo.assert_called_once_with('192.0.2.20', '80',
|
||||||
|
0, socket.SOCK_DGRAM)
|
||||||
|
sendto_mock.reset_mock()
|
||||||
|
mock_getaddrinfo.reset_mock()
|
||||||
|
|
||||||
|
self.conf.config(group="health_manager",
|
||||||
|
controller_ip_port_list=['192.0.2.21:81'])
|
||||||
|
mock_getaddrinfo.return_value = [(socket.AF_INET,
|
||||||
|
socket.SOCK_DGRAM,
|
||||||
|
socket.IPPROTO_UDP,
|
||||||
|
'',
|
||||||
|
('192.0.2.21', 81))]
|
||||||
|
sender.dosend(SAMPLE_MSG)
|
||||||
|
mock_getaddrinfo.assert_called_once_with('192.0.2.21', '81',
|
||||||
|
0, socket.SOCK_DGRAM)
|
||||||
|
sendto_mock.assert_called_once_with(SAMPLE_MSG_BIN,
|
||||||
|
('192.0.2.21', 81))
|
||||||
|
sendto_mock.reset_mock()
|
||||||
|
mock_getaddrinfo.reset_mock()
|
||||||
|
@ -375,6 +375,11 @@ class TestHaproxyAmphoraLoadBalancerDriverTest(base.TestCase):
|
|||||||
self.driver.client.get_info.assert_called_once_with(self.amp)
|
self.driver.client.get_info.assert_called_once_with(self.amp)
|
||||||
self.assertEqual(ref_versions, result)
|
self.assertEqual(ref_versions, result)
|
||||||
|
|
||||||
|
def test_update_amphora_agent_config(self):
|
||||||
|
self.driver.update_amphora_agent_config(self.amp, six.b('test'))
|
||||||
|
self.driver.client.update_agent_config.assert_called_once_with(
|
||||||
|
self.amp, six.b('test'), timeout_dict=None)
|
||||||
|
|
||||||
|
|
||||||
class TestAmphoraAPIClientTest(base.TestCase):
|
class TestAmphoraAPIClientTest(base.TestCase):
|
||||||
|
|
||||||
@ -1067,3 +1072,16 @@ class TestAmphoraAPIClientTest(base.TestCase):
|
|||||||
self.assertRaises(exc.InternalServerError,
|
self.assertRaises(exc.InternalServerError,
|
||||||
self.driver.get_interface,
|
self.driver.get_interface,
|
||||||
self.amp, ip_addr)
|
self.amp, ip_addr)
|
||||||
|
|
||||||
|
@requests_mock.mock()
|
||||||
|
def test_update_agent_config(self, m):
|
||||||
|
m.put("{base}/config".format(base=self.base_url))
|
||||||
|
resp_body = self.driver.update_agent_config(self.amp, "some_file")
|
||||||
|
self.assertEqual(200, resp_body.status_code)
|
||||||
|
|
||||||
|
@requests_mock.mock()
|
||||||
|
def test_update_agent_config_error(self, m):
|
||||||
|
m.put("{base}/config".format(base=self.base_url), status_code=500)
|
||||||
|
self.assertRaises(exc.InternalServerError,
|
||||||
|
self.driver.update_agent_config, self.amp,
|
||||||
|
"some_file")
|
||||||
|
@ -63,6 +63,7 @@ class TestNoopAmphoraLoadBalancerDriver(base.TestCase):
|
|||||||
vip_subnet=network_models.Subnet(id=self.FAKE_UUID_1))
|
vip_subnet=network_models.Subnet(id=self.FAKE_UUID_1))
|
||||||
}
|
}
|
||||||
self.pem_file = 'test_pem_file'
|
self.pem_file = 'test_pem_file'
|
||||||
|
self.agent_config = 'test agent config'
|
||||||
self.timeout_dict = {constants.REQ_CONN_TIMEOUT: 1,
|
self.timeout_dict = {constants.REQ_CONN_TIMEOUT: 1,
|
||||||
constants.REQ_READ_TIMEOUT: 2,
|
constants.REQ_READ_TIMEOUT: 2,
|
||||||
constants.CONN_MAX_RETRIES: 3,
|
constants.CONN_MAX_RETRIES: 3,
|
||||||
@ -147,3 +148,10 @@ class TestNoopAmphoraLoadBalancerDriver(base.TestCase):
|
|||||||
(self.amphora.id, self.pem_file, 'update_amp_cert_file'),
|
(self.amphora.id, self.pem_file, 'update_amp_cert_file'),
|
||||||
self.driver.driver.amphoraconfig[(
|
self.driver.driver.amphoraconfig[(
|
||||||
self.amphora.id, self.pem_file)])
|
self.amphora.id, self.pem_file)])
|
||||||
|
|
||||||
|
def test_update_agent_config(self):
|
||||||
|
self.driver.update_agent_config(self.amphora, self.agent_config)
|
||||||
|
self.assertEqual(
|
||||||
|
(self.amphora.id, self.agent_config, 'update_agent_config'),
|
||||||
|
self.driver.driver.amphoraconfig[(
|
||||||
|
self.amphora.id, self.agent_config)])
|
||||||
|
@ -442,3 +442,13 @@ class TestValidations(base.TestCase):
|
|||||||
self.assertRaises(exceptions.InvalidOption,
|
self.assertRaises(exceptions.InvalidOption,
|
||||||
validate.ip_not_reserved,
|
validate.ip_not_reserved,
|
||||||
'2001:0DB8::5')
|
'2001:0DB8::5')
|
||||||
|
|
||||||
|
def test_is_flavor_spares_compatible(self):
|
||||||
|
not_compat_flavor = {constants.COMPUTE_FLAVOR: 'chocolate'}
|
||||||
|
compat_flavor = {constants.LOADBALANCER_TOPOLOGY:
|
||||||
|
constants.TOPOLOGY_SINGLE}
|
||||||
|
|
||||||
|
self.assertTrue(validate.is_flavor_spares_compatible(None))
|
||||||
|
self.assertTrue(validate.is_flavor_spares_compatible(compat_flavor))
|
||||||
|
self.assertFalse(
|
||||||
|
validate.is_flavor_spares_compatible(not_compat_flavor))
|
||||||
|
@ -366,41 +366,45 @@ class TestAmphoraFlows(base.TestCase):
|
|||||||
|
|
||||||
self.assertIsInstance(amp_flow, flow.Flow)
|
self.assertIsInstance(amp_flow, flow.Flow)
|
||||||
|
|
||||||
|
self.assertIn(constants.FLAVOR, amp_flow.requires)
|
||||||
self.assertIn(constants.AMPHORA_ID, amp_flow.requires)
|
self.assertIn(constants.AMPHORA_ID, amp_flow.requires)
|
||||||
self.assertIn(constants.AMPHORA, amp_flow.provides)
|
self.assertIn(constants.AMPHORA, amp_flow.provides)
|
||||||
|
|
||||||
self.assertEqual(1, len(amp_flow.provides))
|
self.assertEqual(1, len(amp_flow.provides))
|
||||||
self.assertEqual(1, len(amp_flow.requires))
|
self.assertEqual(2, len(amp_flow.requires))
|
||||||
|
|
||||||
amp_flow = self.AmpFlow._get_post_map_lb_subflow(
|
amp_flow = self.AmpFlow._get_post_map_lb_subflow(
|
||||||
'SOMEPREFIX', constants.ROLE_BACKUP)
|
'SOMEPREFIX', constants.ROLE_BACKUP)
|
||||||
|
|
||||||
self.assertIsInstance(amp_flow, flow.Flow)
|
self.assertIsInstance(amp_flow, flow.Flow)
|
||||||
|
|
||||||
|
self.assertIn(constants.FLAVOR, amp_flow.requires)
|
||||||
self.assertIn(constants.AMPHORA_ID, amp_flow.requires)
|
self.assertIn(constants.AMPHORA_ID, amp_flow.requires)
|
||||||
self.assertIn(constants.AMPHORA, amp_flow.provides)
|
self.assertIn(constants.AMPHORA, amp_flow.provides)
|
||||||
|
|
||||||
self.assertEqual(1, len(amp_flow.provides))
|
self.assertEqual(1, len(amp_flow.provides))
|
||||||
self.assertEqual(1, len(amp_flow.requires))
|
self.assertEqual(2, len(amp_flow.requires))
|
||||||
|
|
||||||
amp_flow = self.AmpFlow._get_post_map_lb_subflow(
|
amp_flow = self.AmpFlow._get_post_map_lb_subflow(
|
||||||
'SOMEPREFIX', constants.ROLE_STANDALONE)
|
'SOMEPREFIX', constants.ROLE_STANDALONE)
|
||||||
|
|
||||||
self.assertIsInstance(amp_flow, flow.Flow)
|
self.assertIsInstance(amp_flow, flow.Flow)
|
||||||
|
|
||||||
|
self.assertIn(constants.FLAVOR, amp_flow.requires)
|
||||||
self.assertIn(constants.AMPHORA_ID, amp_flow.requires)
|
self.assertIn(constants.AMPHORA_ID, amp_flow.requires)
|
||||||
self.assertIn(constants.AMPHORA, amp_flow.provides)
|
self.assertIn(constants.AMPHORA, amp_flow.provides)
|
||||||
|
|
||||||
self.assertEqual(1, len(amp_flow.provides))
|
self.assertEqual(1, len(amp_flow.provides))
|
||||||
self.assertEqual(1, len(amp_flow.requires))
|
self.assertEqual(2, len(amp_flow.requires))
|
||||||
|
|
||||||
amp_flow = self.AmpFlow._get_post_map_lb_subflow(
|
amp_flow = self.AmpFlow._get_post_map_lb_subflow(
|
||||||
'SOMEPREFIX', 'BOGUS_ROLE')
|
'SOMEPREFIX', 'BOGUS_ROLE')
|
||||||
|
|
||||||
self.assertIsInstance(amp_flow, flow.Flow)
|
self.assertIsInstance(amp_flow, flow.Flow)
|
||||||
|
|
||||||
|
self.assertIn(constants.FLAVOR, amp_flow.requires)
|
||||||
self.assertIn(constants.AMPHORA_ID, amp_flow.requires)
|
self.assertIn(constants.AMPHORA_ID, amp_flow.requires)
|
||||||
self.assertIn(constants.AMPHORA, amp_flow.provides)
|
self.assertIn(constants.AMPHORA, amp_flow.provides)
|
||||||
|
|
||||||
self.assertEqual(1, len(amp_flow.provides))
|
self.assertEqual(1, len(amp_flow.provides))
|
||||||
self.assertEqual(1, len(amp_flow.requires))
|
self.assertEqual(2, len(amp_flow.requires))
|
||||||
|
@ -33,6 +33,7 @@ LISTENER_ID = uuidutils.generate_uuid()
|
|||||||
LB_ID = uuidutils.generate_uuid()
|
LB_ID = uuidutils.generate_uuid()
|
||||||
CONN_MAX_RETRIES = 10
|
CONN_MAX_RETRIES = 10
|
||||||
CONN_RETRY_INTERVAL = 6
|
CONN_RETRY_INTERVAL = 6
|
||||||
|
FAKE_CONFIG_FILE = 'fake config file'
|
||||||
|
|
||||||
_amphora_mock = mock.MagicMock()
|
_amphora_mock = mock.MagicMock()
|
||||||
_amphora_mock.id = AMP_ID
|
_amphora_mock.id = AMP_ID
|
||||||
@ -71,6 +72,8 @@ class TestAmphoraDriverTasks(base.TestCase):
|
|||||||
active_connection_max_retries=CONN_MAX_RETRIES)
|
active_connection_max_retries=CONN_MAX_RETRIES)
|
||||||
conf.config(group="haproxy_amphora",
|
conf.config(group="haproxy_amphora",
|
||||||
active_connection_rety_interval=CONN_RETRY_INTERVAL)
|
active_connection_rety_interval=CONN_RETRY_INTERVAL)
|
||||||
|
conf.config(group="controller_worker",
|
||||||
|
loadbalancer_topology=constants.TOPOLOGY_SINGLE)
|
||||||
super(TestAmphoraDriverTasks, self).setUp()
|
super(TestAmphoraDriverTasks, self).setUp()
|
||||||
|
|
||||||
def test_amp_listener_update(self,
|
def test_amp_listener_update(self,
|
||||||
@ -615,3 +618,50 @@ class TestAmphoraDriverTasks(base.TestCase):
|
|||||||
amp_compute_conn_wait_obj.execute, _amphora_mock)
|
amp_compute_conn_wait_obj.execute, _amphora_mock)
|
||||||
mock_amphora_repo_update.assert_called_once_with(
|
mock_amphora_repo_update.assert_called_once_with(
|
||||||
_session_mock, AMP_ID, status=constants.ERROR)
|
_session_mock, AMP_ID, status=constants.ERROR)
|
||||||
|
|
||||||
|
@mock.patch('octavia.amphorae.backends.agent.agent_jinja_cfg.'
|
||||||
|
'AgentJinjaTemplater.build_agent_config')
|
||||||
|
def test_amphora_config_update(self,
|
||||||
|
mock_build_config,
|
||||||
|
mock_driver,
|
||||||
|
mock_generate_uuid,
|
||||||
|
mock_log,
|
||||||
|
mock_get_session,
|
||||||
|
mock_listener_repo_get,
|
||||||
|
mock_listener_repo_update,
|
||||||
|
mock_amphora_repo_update):
|
||||||
|
mock_build_config.return_value = FAKE_CONFIG_FILE
|
||||||
|
amp_config_update_obj = amphora_driver_tasks.AmphoraConfigUpdate()
|
||||||
|
mock_driver.update_amphora_agent_config.side_effect = [
|
||||||
|
None, None, driver_except.AmpDriverNotImplementedError,
|
||||||
|
driver_except.TimeOutException]
|
||||||
|
# With Flavor
|
||||||
|
flavor = {constants.LOADBALANCER_TOPOLOGY:
|
||||||
|
constants.TOPOLOGY_ACTIVE_STANDBY}
|
||||||
|
amp_config_update_obj.execute(_amphora_mock, flavor)
|
||||||
|
mock_build_config.assert_called_once_with(
|
||||||
|
_amphora_mock.id, constants.TOPOLOGY_ACTIVE_STANDBY)
|
||||||
|
mock_driver.update_amphora_agent_config.assert_called_once_with(
|
||||||
|
_amphora_mock, FAKE_CONFIG_FILE)
|
||||||
|
# With no Flavor
|
||||||
|
mock_driver.reset_mock()
|
||||||
|
mock_build_config.reset_mock()
|
||||||
|
amp_config_update_obj.execute(_amphora_mock, None)
|
||||||
|
mock_build_config.assert_called_once_with(
|
||||||
|
_amphora_mock.id, constants.TOPOLOGY_SINGLE)
|
||||||
|
mock_driver.update_amphora_agent_config.assert_called_once_with(
|
||||||
|
_amphora_mock, FAKE_CONFIG_FILE)
|
||||||
|
# With amphora that does not support config update
|
||||||
|
mock_driver.reset_mock()
|
||||||
|
mock_build_config.reset_mock()
|
||||||
|
amp_config_update_obj.execute(_amphora_mock, flavor)
|
||||||
|
mock_build_config.assert_called_once_with(
|
||||||
|
_amphora_mock.id, constants.TOPOLOGY_ACTIVE_STANDBY)
|
||||||
|
mock_driver.update_amphora_agent_config.assert_called_once_with(
|
||||||
|
_amphora_mock, FAKE_CONFIG_FILE)
|
||||||
|
# With an unknown exception
|
||||||
|
mock_driver.reset_mock()
|
||||||
|
mock_build_config.reset_mock()
|
||||||
|
self.assertRaises(driver_except.TimeOutException,
|
||||||
|
amp_config_update_obj.execute,
|
||||||
|
_amphora_mock, flavor)
|
||||||
|
@ -141,7 +141,8 @@ class TestControllerWorker(base.TestCase):
|
|||||||
assert_called_once_with(
|
assert_called_once_with(
|
||||||
'TEST',
|
'TEST',
|
||||||
store={constants.BUILD_TYPE_PRIORITY:
|
store={constants.BUILD_TYPE_PRIORITY:
|
||||||
constants.LB_CREATE_SPARES_POOL_PRIORITY}))
|
constants.LB_CREATE_SPARES_POOL_PRIORITY,
|
||||||
|
constants.FLAVOR: None}))
|
||||||
|
|
||||||
_flow_mock.run.assert_called_once_with()
|
_flow_mock.run.assert_called_once_with()
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user