Add amphora agent configuration update admin API
This patch adds a new admin API that updates an amphora's agent configuration. Change-Id: I41ce6843fb53fa21ab84e5b1d0734e70380d716a
This commit is contained in:
@@ -215,6 +215,52 @@ Response Example
|
||||
.. literalinclude:: examples/amphora-show-stats-response.json
|
||||
:language: javascript
|
||||
|
||||
Configure Amphora
|
||||
=================
|
||||
|
||||
.. rest_method:: PUT /v2/octavia/amphorae/{amphora_id}/config
|
||||
|
||||
Update the amphora agent configuration. This will push the new configuration
|
||||
to the amphora agent and will update the configuration options that are
|
||||
mutatable.
|
||||
|
||||
If you are not an administrative user, the service returns the HTTP
|
||||
``Forbidden (403)`` response code.
|
||||
|
||||
This operation does not require a request body.
|
||||
|
||||
**New in version 2.7**
|
||||
|
||||
.. rest_status_code:: success ../http-status.yaml
|
||||
|
||||
- 202
|
||||
|
||||
.. rest_status_code:: error ../http-status.yaml
|
||||
|
||||
- 400
|
||||
- 401
|
||||
- 403
|
||||
- 404
|
||||
- 500
|
||||
|
||||
Request
|
||||
-------
|
||||
|
||||
.. rest_parameters:: ../parameters.yaml
|
||||
|
||||
- amphora_id: path-amphora-id
|
||||
|
||||
Curl Example
|
||||
------------
|
||||
|
||||
.. literalinclude:: examples/amphora-config-curl
|
||||
:language: bash
|
||||
|
||||
Response
|
||||
--------
|
||||
|
||||
There is no body content for the response of a successful PUT request.
|
||||
|
||||
Failover Amphora
|
||||
================
|
||||
|
||||
@@ -236,6 +282,7 @@ This operation does not require a request body.
|
||||
- 400
|
||||
- 401
|
||||
- 403
|
||||
- 404
|
||||
- 500
|
||||
|
||||
Request
|
||||
|
1
api-ref/source/v2/examples/amphora-config-curl
Normal file
1
api-ref/source/v2/examples/amphora-config-curl
Normal file
@@ -0,0 +1 @@
|
||||
curl -X PUT -H "X-Auth-Token: <token>" http://198.51.100.10:9876/v2/octavia/amphorae/6bd55cd3-802e-447e-a518-1e74e23bb106/config
|
@@ -86,6 +86,9 @@ class RootController(rest.RestController):
|
||||
self._add_a_version(versions, 'v2.5', 'v2', 'SUPPORTED',
|
||||
'2019-01-21T00:00:00Z', host_url)
|
||||
# Flavors
|
||||
self._add_a_version(versions, 'v2.6', 'v2', 'CURRENT',
|
||||
self._add_a_version(versions, 'v2.6', 'v2', 'SUPPORTED',
|
||||
'2019-01-25T00:00:00Z', host_url)
|
||||
# Amphora Config update
|
||||
self._add_a_version(versions, 'v2.7', 'v2', 'CURRENT',
|
||||
'2018-01-25T12:00:00Z', host_url)
|
||||
return {'versions': versions}
|
||||
|
@@ -83,6 +83,8 @@ class AmphoraController(base.BaseController):
|
||||
if amphora_id and remainder:
|
||||
controller = remainder[0]
|
||||
remainder = remainder[1:]
|
||||
if controller == 'config':
|
||||
return AmphoraUpdateController(amp_id=amphora_id), remainder
|
||||
if controller == 'failover':
|
||||
return FailoverController(amp_id=amphora_id), remainder
|
||||
if controller == 'stats':
|
||||
@@ -136,6 +138,47 @@ class FailoverController(base.BaseController):
|
||||
provisioning_status=constants.ERROR)
|
||||
|
||||
|
||||
class AmphoraUpdateController(base.BaseController):
|
||||
RBAC_TYPE = constants.RBAC_AMPHORA
|
||||
|
||||
def __init__(self, amp_id):
|
||||
super(AmphoraUpdateController, self).__init__()
|
||||
topic = cfg.CONF.oslo_messaging.topic
|
||||
self.transport = messaging.get_rpc_transport(cfg.CONF)
|
||||
self.target = messaging.Target(
|
||||
namespace=constants.RPC_NAMESPACE_CONTROLLER_AGENT,
|
||||
topic=topic, version="1.0", fanout=False)
|
||||
self.client = messaging.RPCClient(self.transport, target=self.target)
|
||||
self.amp_id = amp_id
|
||||
|
||||
@wsme_pecan.wsexpose(None, wtypes.text, status_code=202)
|
||||
def put(self):
|
||||
"""Update amphora agent configuration"""
|
||||
pcontext = pecan.request.context
|
||||
context = pcontext.get('octavia_context')
|
||||
db_amp = self._get_db_amp(context.session, self.amp_id,
|
||||
show_deleted=False)
|
||||
|
||||
# Check to see if the amphora is a spare (not associated with an LB)
|
||||
if db_amp.load_balancer:
|
||||
self._auth_validate_action(
|
||||
context, db_amp.load_balancer.project_id,
|
||||
constants.RBAC_PUT_CONFIG)
|
||||
else:
|
||||
self._auth_validate_action(
|
||||
context, context.project_id, constants.RBAC_PUT_CONFIG)
|
||||
|
||||
try:
|
||||
LOG.info("Sending amphora agent update request for amphora %s to "
|
||||
"the queue.", self.amp_id)
|
||||
payload = {constants.AMPHORA_ID: db_amp.id}
|
||||
self.client.cast({}, 'update_amphora_agent_config', **payload)
|
||||
except Exception:
|
||||
with excutils.save_and_reraise_exception(reraise=True):
|
||||
LOG.error("Unable to send amphora agent update request for "
|
||||
"amphora %s to the queue.", self.amp_id)
|
||||
|
||||
|
||||
class AmphoraStatsController(base.BaseController):
|
||||
RBAC_TYPE = constants.RBAC_AMPHORA
|
||||
|
||||
|
@@ -297,6 +297,7 @@ UPDATE_POOL_FLOW = 'octavia-update-pool-flow'
|
||||
UPDATE_L7POLICY_FLOW = 'octavia-update-l7policy-flow'
|
||||
UPDATE_L7RULE_FLOW = 'octavia-update-l7rule-flow'
|
||||
UPDATE_AMPS_SUBFLOW = 'octavia-update-amps-subflow'
|
||||
UPDATE_AMPHORA_CONFIG_FLOW = 'octavia-update-amp-config-flow'
|
||||
|
||||
POST_MAP_AMP_TO_LB_SUBFLOW = 'octavia-post-map-amp-to-lb-subflow'
|
||||
CREATE_AMP_FOR_LB_SUBFLOW = 'octavia-create-amp-for-lb-subflow'
|
||||
@@ -540,6 +541,7 @@ RBAC_FLAVOR = '{}:flavor:'.format(LOADBALANCER_API)
|
||||
RBAC_FLAVOR_PROFILE = '{}:flavor-profile:'.format(LOADBALANCER_API)
|
||||
RBAC_POST = 'post'
|
||||
RBAC_PUT = 'put'
|
||||
RBAC_PUT_CONFIG = 'put_config'
|
||||
RBAC_PUT_FAILOVER = 'put_failover'
|
||||
RBAC_DELETE = 'delete'
|
||||
RBAC_GET_ONE = 'get_one'
|
||||
|
@@ -148,3 +148,8 @@ class Endpoint(object):
|
||||
def delete_l7rule(self, context, l7rule_id):
|
||||
LOG.info('Deleting l7rule \'%s\'...', l7rule_id)
|
||||
self.worker.delete_l7rule(l7rule_id)
|
||||
|
||||
def update_amphora_agent_config(self, context, amphora_id):
|
||||
LOG.info('Updating amphora \'%s\' agent configuration...',
|
||||
amphora_id)
|
||||
self.worker.update_amphora_agent_config(amphora_id)
|
||||
|
@@ -958,3 +958,32 @@ class ControllerWorker(base_taskflow.BaseTaskFlowEngine):
|
||||
with tf_logging.DynamicLoggingListener(certrotation_amphora_tf,
|
||||
log=LOG):
|
||||
certrotation_amphora_tf.run()
|
||||
|
||||
def update_amphora_agent_config(self, amphora_id):
|
||||
"""Update the amphora agent configuration.
|
||||
|
||||
Note: This will update the amphora agent configuration file and
|
||||
update the running configuration for mutatable configuration
|
||||
items.
|
||||
|
||||
:param amphora_id: ID of the amphora to update.
|
||||
:returns: None
|
||||
"""
|
||||
LOG.info("Start amphora agent configuration update, amphora's id "
|
||||
"is: %s", amphora_id)
|
||||
amp = self._amphora_repo.get(db_apis.get_session(), id=amphora_id)
|
||||
lb = self._amphora_repo.get_lb_for_amphora(db_apis.get_session(),
|
||||
amphora_id)
|
||||
flavor = {}
|
||||
if lb.flavor_id:
|
||||
flavor = self._flavor_repo.get_flavor_metadata_dict(
|
||||
db_apis.get_session(), lb.flavor_id)
|
||||
|
||||
update_amphora_tf = self._taskflow_load(
|
||||
self._amphora_flows.update_amphora_config_flow(),
|
||||
store={constants.AMPHORA: amp,
|
||||
constants.FLAVOR: flavor})
|
||||
|
||||
with tf_logging.DynamicLoggingListener(update_amphora_tf,
|
||||
log=LOG):
|
||||
update_amphora_tf.run()
|
||||
|
@@ -533,3 +533,19 @@ class AmphoraFlows(object):
|
||||
requires=constants.AMPHORA))
|
||||
|
||||
return rotated_amphora_flow
|
||||
|
||||
def update_amphora_config_flow(self):
|
||||
"""Creates a flow to update the amphora agent configuration.
|
||||
|
||||
:returns: The flow for updating an amphora
|
||||
"""
|
||||
update_amphora_flow = linear_flow.Flow(
|
||||
constants.UPDATE_AMPHORA_CONFIG_FLOW)
|
||||
|
||||
update_amphora_flow.add(lifecycle_tasks.AmphoraToErrorOnRevertTask(
|
||||
requires=constants.AMPHORA))
|
||||
|
||||
update_amphora_flow.add(amphora_driver_tasks.AmphoraConfigUpdate(
|
||||
requires=(constants.AMPHORA, constants.FLAVOR)))
|
||||
|
||||
return update_amphora_flow
|
||||
|
@@ -30,6 +30,14 @@ rules = [
|
||||
"Show Amphora details",
|
||||
[{'method': 'GET', 'path': '/v2/octavia/amphorae/{amphora_id}'}]
|
||||
),
|
||||
policy.DocumentedRuleDefault(
|
||||
'{rbac_obj}{action}'.format(rbac_obj=constants.RBAC_AMPHORA,
|
||||
action=constants.RBAC_PUT_CONFIG),
|
||||
constants.RULE_API_ADMIN,
|
||||
"Update Amphora Agent Configuration",
|
||||
[{'method': 'PUT',
|
||||
'path': '/v2/octavia/amphorae/{amphora_id}/config'}]
|
||||
),
|
||||
policy.DocumentedRuleDefault(
|
||||
'{rbac_obj}{action}'.format(rbac_obj=constants.RBAC_AMPHORA,
|
||||
action=constants.RBAC_PUT_FAILOVER),
|
||||
|
@@ -46,7 +46,7 @@ class TestRootController(base_db_test.OctaviaDBTestBase):
|
||||
versions = self._get_versions_with_config(
|
||||
api_v1_enabled=True, api_v2_enabled=True)
|
||||
version_ids = tuple(v.get('id') for v in versions)
|
||||
self.assertEqual(8, len(version_ids))
|
||||
self.assertEqual(9, len(version_ids))
|
||||
self.assertIn('v1', version_ids)
|
||||
self.assertIn('v2.0', version_ids)
|
||||
self.assertIn('v2.1', version_ids)
|
||||
@@ -55,6 +55,7 @@ class TestRootController(base_db_test.OctaviaDBTestBase):
|
||||
self.assertIn('v2.4', version_ids)
|
||||
self.assertIn('v2.5', version_ids)
|
||||
self.assertIn('v2.6', version_ids)
|
||||
self.assertIn('v2.7', version_ids)
|
||||
|
||||
# Each version should have a 'self' 'href' to the API version URL
|
||||
# [{u'rel': u'self', u'href': u'http://localhost/v2'}]
|
||||
@@ -74,7 +75,7 @@ class TestRootController(base_db_test.OctaviaDBTestBase):
|
||||
def test_api_v1_disabled(self):
|
||||
versions = self._get_versions_with_config(
|
||||
api_v1_enabled=False, api_v2_enabled=True)
|
||||
self.assertEqual(7, len(versions))
|
||||
self.assertEqual(8, len(versions))
|
||||
self.assertEqual('v2.0', versions[0].get('id'))
|
||||
self.assertEqual('v2.1', versions[1].get('id'))
|
||||
self.assertEqual('v2.2', versions[2].get('id'))
|
||||
@@ -82,6 +83,7 @@ class TestRootController(base_db_test.OctaviaDBTestBase):
|
||||
self.assertEqual('v2.4', versions[4].get('id'))
|
||||
self.assertEqual('v2.5', versions[5].get('id'))
|
||||
self.assertEqual('v2.6', versions[6].get('id'))
|
||||
self.assertEqual('v2.7', versions[7].get('id'))
|
||||
|
||||
def test_api_v2_disabled(self):
|
||||
versions = self._get_versions_with_config(
|
||||
|
@@ -77,6 +77,7 @@ class BaseAPITest(base_db_test.OctaviaDBTestBase):
|
||||
AMPHORA_PATH = AMPHORAE_PATH + '/{amphora_id}'
|
||||
AMPHORA_FAILOVER_PATH = AMPHORA_PATH + '/failover'
|
||||
AMPHORA_STATS_PATH = AMPHORA_PATH + '/stats'
|
||||
AMPHORA_CONFIG_PATH = AMPHORA_PATH + '/config'
|
||||
|
||||
PROVIDERS_PATH = '/lbaas/providers'
|
||||
FLAVOR_CAPABILITIES_PATH = (PROVIDERS_PATH +
|
||||
|
@@ -21,6 +21,7 @@ from oslo_utils import uuidutils
|
||||
|
||||
from octavia.common import constants
|
||||
import octavia.common.context
|
||||
from octavia.common import exceptions
|
||||
from octavia.tests.functional.api.v2 import base
|
||||
|
||||
|
||||
@@ -501,3 +502,101 @@ class TestAmphora(base.BaseAPITest):
|
||||
self.amp2_id = self.amp2.id
|
||||
self.get(self.AMPHORA_STATS_PATH.format(
|
||||
amphora_id=self.amp2_id), status=404)
|
||||
|
||||
@mock.patch('oslo_messaging.RPCClient.cast')
|
||||
def test_config(self, mock_cast):
|
||||
self.put(self.AMPHORA_CONFIG_PATH.format(
|
||||
amphora_id=self.amp_id), body={}, status=202)
|
||||
payload = {constants.AMPHORA_ID: self.amp_id}
|
||||
mock_cast.assert_called_with({}, 'update_amphora_agent_config',
|
||||
**payload)
|
||||
|
||||
@mock.patch('oslo_messaging.RPCClient.cast')
|
||||
def test_config_deleted(self, mock_cast):
|
||||
new_amp = self._create_additional_amp()
|
||||
self.amphora_repo.update(self.session, new_amp.id,
|
||||
status=constants.DELETED)
|
||||
self.put(self.AMPHORA_CONFIG_PATH.format(
|
||||
amphora_id=new_amp.id), body={}, status=404)
|
||||
self.assertFalse(mock_cast.called)
|
||||
|
||||
@mock.patch('oslo_messaging.RPCClient.cast')
|
||||
def test_config_bad_amp_id(self, mock_cast):
|
||||
self.put(self.AMPHORA_CONFIG_PATH.format(
|
||||
amphora_id='bogus'), body={}, status=404)
|
||||
self.assertFalse(mock_cast.called)
|
||||
|
||||
@mock.patch('oslo_messaging.RPCClient.cast')
|
||||
def test_config_exception(self, mock_cast):
|
||||
mock_cast.side_effect = exceptions.OctaviaException('boom')
|
||||
self.put(self.AMPHORA_CONFIG_PATH.format(
|
||||
amphora_id=self.amp_id), body={}, status=500)
|
||||
|
||||
@mock.patch('oslo_messaging.RPCClient.cast')
|
||||
def test_config_spare_amp(self, mock_cast):
|
||||
amp_args = {
|
||||
'compute_id': uuidutils.generate_uuid(),
|
||||
'status': constants.AMPHORA_READY,
|
||||
'lb_network_ip': '192.168.1.2',
|
||||
'cert_expiration': datetime.datetime.now(),
|
||||
'cert_busy': False,
|
||||
'cached_zone': 'zone1',
|
||||
'created_at': datetime.datetime.now(),
|
||||
'updated_at': datetime.datetime.now(),
|
||||
'image_id': uuidutils.generate_uuid(),
|
||||
}
|
||||
amp = self.amphora_repo.create(self.session, **amp_args)
|
||||
self.put(self.AMPHORA_CONFIG_PATH.format(
|
||||
amphora_id=amp.id), body={}, status=202)
|
||||
payload = {constants.AMPHORA_ID: amp.id}
|
||||
mock_cast.assert_called_with({}, 'update_amphora_agent_config',
|
||||
**payload)
|
||||
|
||||
@mock.patch('oslo_messaging.RPCClient.cast')
|
||||
def test_config_authorized(self, mock_cast):
|
||||
self.conf = self.useFixture(oslo_fixture.Config(cfg.CONF))
|
||||
auth_strategy = self.conf.conf.api_settings.get('auth_strategy')
|
||||
self.conf.config(group='api_settings', auth_strategy=constants.TESTING)
|
||||
with mock.patch.object(octavia.common.context.Context, 'project_id',
|
||||
self.project_id):
|
||||
override_credentials = {
|
||||
'service_user_id': None,
|
||||
'user_domain_id': None,
|
||||
'is_admin_project': True,
|
||||
'service_project_domain_id': None,
|
||||
'service_project_id': None,
|
||||
'roles': ['load-balancer_member'],
|
||||
'user_id': None,
|
||||
'is_admin': True,
|
||||
'service_user_domain_id': None,
|
||||
'project_domain_id': None,
|
||||
'service_roles': [],
|
||||
'project_id': self.project_id}
|
||||
with mock.patch(
|
||||
"oslo_context.context.RequestContext.to_policy_values",
|
||||
return_value=override_credentials):
|
||||
|
||||
self.put(self.AMPHORA_CONFIG_PATH.format(
|
||||
amphora_id=self.amp_id), body={}, status=202)
|
||||
# Reset api auth setting
|
||||
self.conf.config(group='api_settings', auth_strategy=auth_strategy)
|
||||
payload = {constants.AMPHORA_ID: self.amp_id}
|
||||
mock_cast.assert_called_with({}, 'update_amphora_agent_config',
|
||||
**payload)
|
||||
|
||||
@mock.patch('oslo_messaging.RPCClient.cast')
|
||||
def test_config_not_authorized(self, mock_cast):
|
||||
self.conf = self.useFixture(oslo_fixture.Config(cfg.CONF))
|
||||
auth_strategy = self.conf.conf.api_settings.get('auth_strategy')
|
||||
self.conf.config(group='api_settings', auth_strategy=constants.TESTING)
|
||||
with mock.patch.object(octavia.common.context.Context, 'project_id',
|
||||
uuidutils.generate_uuid()):
|
||||
self.put(self.AMPHORA_CONFIG_PATH.format(
|
||||
amphora_id=self.amp_id), body={}, status=403)
|
||||
# Reset api auth setting
|
||||
self.conf.config(group='api_settings', auth_strategy=auth_strategy)
|
||||
self.assertFalse(mock_cast.called)
|
||||
|
||||
def test_bogus_path(self):
|
||||
self.put(self.AMPHORA_PATH.format(amphora_id=self.amp_id) + '/bogus',
|
||||
body={}, status=405)
|
||||
|
@@ -175,3 +175,8 @@ class TestEndpoint(base.TestCase):
|
||||
self.ep.delete_l7rule(self.context, self.resource_id)
|
||||
self.ep.worker.delete_l7rule.assert_called_once_with(
|
||||
self.resource_id)
|
||||
|
||||
def test_update_amphora_agent_config(self):
|
||||
self.ep.update_amphora_agent_config(self.context, self.resource_id)
|
||||
self.ep.worker.update_amphora_agent_config.assert_called_once_with(
|
||||
self.resource_id)
|
||||
|
@@ -408,3 +408,15 @@ class TestAmphoraFlows(base.TestCase):
|
||||
|
||||
self.assertEqual(1, len(amp_flow.provides))
|
||||
self.assertEqual(2, len(amp_flow.requires))
|
||||
|
||||
def test_update_amphora_config_flow(self, mock_get_net_driver):
|
||||
|
||||
amp_flow = self.AmpFlow.update_amphora_config_flow()
|
||||
|
||||
self.assertIsInstance(amp_flow, flow.Flow)
|
||||
|
||||
self.assertIn(constants.AMPHORA, amp_flow.requires)
|
||||
self.assertIn(constants.FLAVOR, amp_flow.requires)
|
||||
|
||||
self.assertEqual(2, len(amp_flow.requires))
|
||||
self.assertEqual(0, len(amp_flow.provides))
|
||||
|
@@ -1408,3 +1408,58 @@ class TestControllerWorker(base.TestCase):
|
||||
constants.AMPHORA_ID:
|
||||
_amphora_mock.id}))
|
||||
_flow_mock.run.assert_called_once_with()
|
||||
|
||||
@mock.patch('octavia.db.repositories.FlavorRepository.'
|
||||
'get_flavor_metadata_dict')
|
||||
@mock.patch('octavia.db.repositories.AmphoraRepository.get_lb_for_amphora')
|
||||
@mock.patch('octavia.controller.worker.flows.'
|
||||
'amphora_flows.AmphoraFlows.update_amphora_config_flow',
|
||||
return_value=_flow_mock)
|
||||
def test_update_amphora_agent_config(self,
|
||||
mock_update_flow,
|
||||
mock_get_lb_for_amp,
|
||||
mock_flavor_meta,
|
||||
mock_api_get_session,
|
||||
mock_dyn_log_listener,
|
||||
mock_taskflow_load,
|
||||
mock_pool_repo_get,
|
||||
mock_member_repo_get,
|
||||
mock_l7rule_repo_get,
|
||||
mock_l7policy_repo_get,
|
||||
mock_listener_repo_get,
|
||||
mock_lb_repo_get,
|
||||
mock_health_mon_repo_get,
|
||||
mock_amp_repo_get):
|
||||
_flow_mock.reset_mock()
|
||||
mock_lb = mock.MagicMock()
|
||||
mock_lb.flavor_id = 'vanilla'
|
||||
mock_get_lb_for_amp.return_value = mock_lb
|
||||
mock_flavor_meta.return_value = {'test': 'dict'}
|
||||
cw = controller_worker.ControllerWorker()
|
||||
cw.update_amphora_agent_config(AMP_ID)
|
||||
|
||||
mock_amp_repo_get.assert_called_once_with(_db_session, id=AMP_ID)
|
||||
mock_get_lb_for_amp.assert_called_once_with(_db_session, AMP_ID)
|
||||
mock_flavor_meta.assert_called_once_with(_db_session, 'vanilla')
|
||||
(base_taskflow.BaseTaskFlowEngine._taskflow_load.
|
||||
assert_called_once_with(_flow_mock,
|
||||
store={constants.AMPHORA: _amphora_mock,
|
||||
constants.FLAVOR: {'test': 'dict'}}))
|
||||
_flow_mock.run.assert_called_once_with()
|
||||
|
||||
# Test with no flavor
|
||||
_flow_mock.reset_mock()
|
||||
mock_amp_repo_get.reset_mock()
|
||||
mock_get_lb_for_amp.reset_mock()
|
||||
mock_flavor_meta.reset_mock()
|
||||
base_taskflow.BaseTaskFlowEngine._taskflow_load.reset_mock()
|
||||
mock_lb.flavor_id = None
|
||||
cw.update_amphora_agent_config(AMP_ID)
|
||||
mock_amp_repo_get.assert_called_once_with(_db_session, id=AMP_ID)
|
||||
mock_get_lb_for_amp.assert_called_once_with(_db_session, AMP_ID)
|
||||
mock_flavor_meta.assert_not_called()
|
||||
(base_taskflow.BaseTaskFlowEngine._taskflow_load.
|
||||
assert_called_once_with(_flow_mock,
|
||||
store={constants.AMPHORA: _amphora_mock,
|
||||
constants.FLAVOR: {}}))
|
||||
_flow_mock.run.assert_called_once_with()
|
||||
|
@@ -0,0 +1,12 @@
|
||||
---
|
||||
features:
|
||||
- |
|
||||
Octavia now has an administrative API that updates the amphora agent
|
||||
configuration on running amphora.
|
||||
upgrade:
|
||||
- |
|
||||
When the amphora agent configuration update API is called on an amphora
|
||||
running a version of the amphora agent that does not support configuration
|
||||
updates, an ERROR log message will be posted to the controller log file
|
||||
indicating that the amphora does not support agent configuration updates.
|
||||
In this case, the amphora image should be updated to a newer version.
|
Reference in New Issue
Block a user