Fix admin_state_up for loadbalancer and listener

The admin-state-up=False action for loadbalancer and listener
failed to affect the appropriate change.  This patch corrects that
as well as removes an un-necessary call to the amphora-agent.

Change-Id: I698f964f584d150f162f6c8cb41c65f5c5556b52
Closes-Bug: #1619449
This commit is contained in:
Michael Johnson 2016-09-02 07:05:49 +00:00
parent f575024000
commit 83731fd9a4
8 changed files with 109 additions and 36 deletions

View File

@ -85,7 +85,9 @@ def upload_keepalived_config():
def manager_keepalived_service(action): def manager_keepalived_service(action):
action = action.lower() action = action.lower()
if action not in ['start', 'stop', 'reload']: if action not in [consts.AMP_ACTION_START,
consts.AMP_ACTION_STOP,
consts.AMP_ACTION_RELOAD]:
return flask.make_response(flask.jsonify(dict( return flask.make_response(flask.jsonify(dict(
message='Invalid Request', message='Invalid Request',
details="Unknown action: {0}".format(action))), 400) details="Unknown action: {0}".format(action))), 400)

View File

@ -164,7 +164,9 @@ def upload_haproxy_config(amphora_id, listener_id):
def start_stop_listener(listener_id, action): def start_stop_listener(listener_id, action):
action = action.lower() action = action.lower()
if action not in ['start', 'stop', 'reload']: if action not in [consts.AMP_ACTION_START,
consts.AMP_ACTION_STOP,
consts.AMP_ACTION_RELOAD]:
return flask.make_response(flask.jsonify(dict( return flask.make_response(flask.jsonify(dict(
message='Invalid Request', message='Invalid Request',
details="Unknown action: {0}".format(action))), 400) details="Unknown action: {0}".format(action))), 400)
@ -177,6 +179,12 @@ def start_stop_listener(listener_id, action):
if os.path.exists(util.keepalived_check_script_path()): if os.path.exists(util.keepalived_check_script_path()):
vrrp_check_script_update(listener_id, action) vrrp_check_script_update(listener_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 == _check_haproxy_status(listener_id):
action = consts.AMP_ACTION_START
cmd = ("/usr/sbin/service haproxy-{listener_id} {action}".format( cmd = ("/usr/sbin/service haproxy-{listener_id} {action}".format(
listener_id=listener_id, action=action)) listener_id=listener_id, action=action))
@ -189,7 +197,8 @@ def start_stop_listener(listener_id, action):
return flask.make_response(flask.jsonify(dict( return flask.make_response(flask.jsonify(dict(
message="Error {0}ing haproxy".format(action), message="Error {0}ing haproxy".format(action),
details=e.output)), 500) details=e.output)), 500)
if action in ['stop', 'reload']: if action in [consts.AMP_ACTION_STOP,
consts.AMP_ACTION_RELOAD]:
return flask.make_response(flask.jsonify( return flask.make_response(flask.jsonify(
dict(message='OK', dict(message='OK',
details='Listener {listener_id} {action}ed'.format( details='Listener {listener_id} {action}ed'.format(
@ -437,7 +446,7 @@ def _cert_file_path(listener_id, filename):
def vrrp_check_script_update(listener_id, action): def vrrp_check_script_update(listener_id, action):
listener_ids = util.get_listeners() listener_ids = util.get_listeners()
if action == 'stop': if action == consts.AMP_ACTION_STOP:
listener_ids.remove(listener_id) listener_ids.remove(listener_id)
args = [] args = []
for listener_id in listener_ids: for listener_id in listener_ids:
@ -450,3 +459,14 @@ def vrrp_check_script_update(listener_id, action):
cmd = 'haproxy-vrrp-check {args}; exit $?'.format(args=' '.join(args)) cmd = 'haproxy-vrrp-check {args}; exit $?'.format(args=' '.join(args))
with open(util.haproxy_check_script_path(), 'w') as text_file: with open(util.haproxy_check_script_path(), 'w') as text_file:
text_file.write(cmd) text_file.write(cmd)
def _check_haproxy_status(listener_id):
if os.path.exists(util.pid_path(listener_id)):
if os.path.exists(
os.path.join('/proc', util.get_haproxy_pid(listener_id))):
return consts.ACTIVE
else: # pid file but no process...
return consts.OFFLINE
else:
return consts.OFFLINE

View File

@ -28,13 +28,13 @@ from octavia.amphorae.drivers import driver_base as driver_base
from octavia.amphorae.drivers.haproxy import exceptions as exc from octavia.amphorae.drivers.haproxy import exceptions as exc
from octavia.amphorae.drivers.keepalived import vrrp_rest_driver from octavia.amphorae.drivers.keepalived import vrrp_rest_driver
from octavia.common.config import cfg from octavia.common.config import cfg
from octavia.common import constants from octavia.common import constants as consts
from octavia.common.jinja.haproxy import jinja_cfg from octavia.common.jinja.haproxy import jinja_cfg
from octavia.common.tls_utils import cert_parser from octavia.common.tls_utils import cert_parser
from octavia.i18n import _LE, _LW from octavia.i18n import _LE, _LW
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
API_VERSION = constants.API_VERSION API_VERSION = consts.API_VERSION
OCTAVIA_API_CLIENT = ( OCTAVIA_API_CLIENT = (
"Octavia HaProxy Rest Client/{version} " "Octavia HaProxy Rest Client/{version} "
"(https://wiki.openstack.org/wiki/Octavia)").format(version=API_VERSION) "(https://wiki.openstack.org/wiki/Octavia)").format(version=API_VERSION)
@ -68,21 +68,13 @@ class HaproxyAmphoraLoadBalancerDriver(
certs = self._process_tls_certificates(listener) certs = self._process_tls_certificates(listener)
for amp in listener.load_balancer.amphorae: for amp in listener.load_balancer.amphorae:
if amp.status != constants.DELETED: if amp.status != consts.DELETED:
# Generate HaProxy configuration from listener object # Generate HaProxy configuration from listener object
config = self.jinja.build_config(amp, config = self.jinja.build_config(amp,
listener, listener,
certs['tls_cert']) certs['tls_cert'])
self.client.upload_config(amp, listener.id, config) self.client.upload_config(amp, listener.id, config)
# todo (german): add a method to REST interface to reload or self.client.reload_listener(amp, listener.id)
# start without having to check
# Is that listener running?
r = self.client.get_listener_status(amp,
listener.id)
if r['status'] == 'ACTIVE':
self.client.reload_listener(amp, listener.id)
else:
self.client.start_listener(amp, listener.id)
def upload_cert_amp(self, amp, pem): def upload_cert_amp(self, amp, pem):
LOG.debug("Amphora %s updating cert in REST driver " LOG.debug("Amphora %s updating cert in REST driver "
@ -92,7 +84,7 @@ class HaproxyAmphoraLoadBalancerDriver(
def _apply(self, func, listener=None, *args): def _apply(self, func, listener=None, *args):
for amp in listener.load_balancer.amphorae: for amp in listener.load_balancer.amphorae:
if amp.status != constants.DELETED: if amp.status != consts.DELETED:
func(amp, listener.id, *args) func(amp, listener.id, *args)
def stop(self, listener, vip): def stop(self, listener, vip):
@ -114,7 +106,7 @@ class HaproxyAmphoraLoadBalancerDriver(
pass pass
def post_vip_plug(self, amphora, load_balancer, amphorae_network_config): def post_vip_plug(self, amphora, load_balancer, amphorae_network_config):
if amphora.status != constants.DELETED: if amphora.status != consts.DELETED:
subnet = amphorae_network_config.get(amphora.id).vip_subnet subnet = amphorae_network_config.get(amphora.id).vip_subnet
# NOTE(blogan): using the vrrp port here because that # NOTE(blogan): using the vrrp port here because that
# is what the allowed address pairs network driver sets # is what the allowed address pairs network driver sets
@ -224,13 +216,19 @@ class AmphoraAPIClient(object):
self.delete = functools.partial(self.request, 'delete') self.delete = functools.partial(self.request, 'delete')
self.head = functools.partial(self.request, 'head') self.head = functools.partial(self.request, 'head')
self.start_listener = functools.partial(self._action, 'start') self.start_listener = functools.partial(self._action,
self.stop_listener = functools.partial(self._action, 'stop') consts.AMP_ACTION_START)
self.reload_listener = functools.partial(self._action, 'reload') 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, 'start') self.start_vrrp = functools.partial(self._vrrp_action,
self.stop_vrrp = functools.partial(self._vrrp_action, 'stop') consts.AMP_ACTION_START)
self.reload_vrrp = functools.partial(self._vrrp_action, 'reload') 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 = requests.Session()
self.session.cert = CONF.haproxy_amphora.client_cert self.session.cert = CONF.haproxy_amphora.client_cert

View File

@ -327,3 +327,7 @@ FLOW_DOC_TITLES = {'AmphoraFlows': 'Amphora Flows',
'L7RuleFlows': 'Layer 7 Rule Flows'} 'L7RuleFlows': 'Layer 7 Rule Flows'}
NETNS_PRIMARY_INTERFACE = 'eth1' NETNS_PRIMARY_INTERFACE = 'eth1'
AMP_ACTION_START = 'start'
AMP_ACTION_STOP = 'stop'
AMP_ACTION_RELOAD = 'reload'

View File

@ -184,6 +184,40 @@ class TestServerTestCase(base.TestCase):
mock_subprocess.assert_called_with( mock_subprocess.assert_called_with(
['/usr/sbin/service', 'haproxy-123', 'start'], stderr=-2) ['/usr/sbin/service', 'haproxy-123', 'start'], stderr=-2)
@mock.patch('os.path.exists')
@mock.patch('octavia.amphorae.backends.agent.api_server.listener.'
'vrrp_check_script_update')
@mock.patch('octavia.amphorae.backends.agent.api_server.listener.'
'_check_haproxy_status')
@mock.patch('subprocess.check_output')
def test_reload(self, mock_subprocess, mock_haproxy_status,
mock_vrrp, mock_exists):
# Process running so reload
mock_exists.return_value = True
mock_haproxy_status.return_value = consts.ACTIVE
rv = self.app.put('/' + api_server.VERSION + '/listeners/123/reload')
self.assertEqual(202, rv.status_code)
self.assertEqual(
{'message': 'OK',
'details': 'Listener 123 reloaded'},
json.loads(rv.data.decode('utf-8')))
mock_subprocess.assert_called_with(
['/usr/sbin/service', 'haproxy-123', 'reload'], stderr=-2)
# Process not running so start
mock_exists.return_value = True
mock_haproxy_status.return_value = consts.OFFLINE
rv = self.app.put('/' + api_server.VERSION + '/listeners/123/reload')
self.assertEqual(202, rv.status_code)
self.assertEqual(
{'message': 'OK',
'details': 'Configuration file is valid\nhaproxy daemon for'
' 123 started'},
json.loads(rv.data.decode('utf-8')))
mock_subprocess.assert_called_with(
['/usr/sbin/service', 'haproxy-123', 'start'], stderr=-2)
@mock.patch('socket.gethostname') @mock.patch('socket.gethostname')
@mock.patch('subprocess.check_output') @mock.patch('subprocess.check_output')
def test_info(self, mock_subbprocess, mock_hostname): def test_info(self, mock_subbprocess, mock_hostname):

View File

@ -165,3 +165,23 @@ class ListenerTestCase(base.TestCase):
listener.vrrp_check_script_update(LISTENER_ID1, 'start') listener.vrrp_check_script_update(LISTENER_ID1, 'start')
handle = m() handle = m()
handle.write.assert_called_once_with(cmd) handle.write.assert_called_once_with(cmd)
@mock.patch('os.path.exists')
@mock.patch('octavia.amphorae.backends.agent.api_server'
+ '.util.get_haproxy_pid')
def test_check_haproxy_status(self, mock_pid, mock_exists):
mock_pid.return_value = '1245'
mock_exists.side_effect = [True, True]
self.assertEqual(
consts.ACTIVE,
listener._check_haproxy_status(LISTENER_ID1))
mock_exists.side_effect = [True, False]
self.assertEqual(
consts.OFFLINE,
listener._check_haproxy_status(LISTENER_ID1))
mock_exists.side_effect = [False]
self.assertEqual(
consts.OFFLINE,
listener._check_haproxy_status(LISTENER_ID1))

View File

@ -122,18 +122,6 @@ class TestHaproxyAmphoraLoadBalancerDriverTest(base.TestCase):
self.driver.client.reload_listener.assert_called_once_with( self.driver.client.reload_listener.assert_called_once_with(
self.amp, self.sl.id) self.amp, self.sl.id)
# listener down
self.driver.client.get_cert_md5sum.side_effect = [
'd41d8cd98f00b204e9800998ecf8427e'] * 3
self.driver.jinja.build_config.side_effect = ['fake_config']
self.driver.client.get_listener_status.side_effect = [
dict(status='BLAH')]
self.driver.update(self.sl, self.sv)
self.driver.client.start_listener.assert_called_once_with(
self.amp, self.sl.id)
def test_upload_cert_amp(self): def test_upload_cert_amp(self):
self.driver.upload_cert_amp(self.amp, six.b('test')) self.driver.upload_cert_amp(self.amp, six.b('test'))
self.driver.client.update_cert_for_rotation.assert_called_once_with( self.driver.client.update_cert_for_rotation.assert_called_once_with(

View File

@ -0,0 +1,7 @@
---
upgrade:
- To fix the admin-state-up bug you must upgrade your
amphora image.
fixes:
- Fixes admin-state-up=False action for loadbalancer
and listener.