diff --git a/octavia/amphorae/backends/agent/api_server/keepalived.py b/octavia/amphorae/backends/agent/api_server/keepalived.py index 5f7fdd8356..93ade5a9c8 100644 --- a/octavia/amphorae/backends/agent/api_server/keepalived.py +++ b/octavia/amphorae/backends/agent/api_server/keepalived.py @@ -35,74 +35,76 @@ template = j2_env.get_template(consts.KEEPALIVED_CONF) check_script_template = j2_env.get_template(consts.CHECK_SCRIPT_CONF) -def upload_keepalived_config(): - stream = listener.Wrapped(flask.request.stream) +class Keepalived(object): - if not os.path.exists(util.keepalived_dir()): - os.makedirs(util.keepalived_dir()) - os.makedirs(util.keepalived_check_scripts_dir()) + def upload_keepalived_config(self): + stream = listener.Wrapped(flask.request.stream) - conf_file = util.keepalived_cfg_path() - flags = os.O_WRONLY | os.O_CREAT | os.O_TRUNC - # mode 00644 - mode = stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | stat.S_IROTH - with os.fdopen(os.open(conf_file, flags, mode), 'w') as f: - b = stream.read(BUFFER) - while b: - f.write(b) + if not os.path.exists(util.keepalived_dir()): + os.makedirs(util.keepalived_dir()) + os.makedirs(util.keepalived_check_scripts_dir()) + + conf_file = util.keepalived_cfg_path() + flags = os.O_WRONLY | os.O_CREAT | os.O_TRUNC + # mode 00644 + mode = stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | stat.S_IROTH + with os.fdopen(os.open(conf_file, flags, mode), 'w') as f: b = stream.read(BUFFER) + while b: + f.write(b) + b = stream.read(BUFFER) - file_path = util.keepalived_init_path() - # mode 00755 - mode = (stat.S_IRWXU | stat.S_IRGRP | stat.S_IXGRP | - stat.S_IROTH | stat.S_IXOTH) - if not os.path.exists(file_path): - with os.fdopen(os.open(file_path, flags, mode), 'w') as text_file: - text = template.render( - keepalived_pid=util.keepalived_pid_path(), - keepalived_cmd=consts.KEEPALIVED_CMD, - keepalived_cfg=util.keepalived_cfg_path(), - keepalived_log=util.keepalived_log_path(), - amphora_nsname=consts.AMPHORA_NAMESPACE - ) - text_file.write(text) + file_path = util.keepalived_init_path() + # mode 00755 + mode = (stat.S_IRWXU | stat.S_IRGRP | stat.S_IXGRP | + stat.S_IROTH | stat.S_IXOTH) + if not os.path.exists(file_path): + with os.fdopen(os.open(file_path, flags, mode), 'w') as text_file: + text = template.render( + keepalived_pid=util.keepalived_pid_path(), + keepalived_cmd=consts.KEEPALIVED_CMD, + keepalived_cfg=util.keepalived_cfg_path(), + keepalived_log=util.keepalived_log_path(), + amphora_nsname=consts.AMPHORA_NAMESPACE + ) + text_file.write(text) - # Renders the Keepalived check script - keepalived_path = util.keepalived_check_script_path() - open_obj = os.open(keepalived_path, flags, mode) - with os.fdopen(open_obj, 'w') as text_file: - text = check_script_template.render( - check_scripts_dir=util.keepalived_check_scripts_dir() - ) - text_file.write(text) + # Renders the Keepalived check script + keepalived_path = util.keepalived_check_script_path() + open_obj = os.open(keepalived_path, flags, mode) + with os.fdopen(open_obj, 'w') as text_file: + text = check_script_template.render( + check_scripts_dir=util.keepalived_check_scripts_dir() + ) + text_file.write(text) - res = flask.make_response(flask.jsonify({ - 'message': 'OK'}), 200) - res.headers['ETag'] = stream.get_md5() + res = flask.make_response(flask.jsonify({ + 'message': 'OK'}), 200) + res.headers['ETag'] = stream.get_md5() - return res + return res + def manager_keepalived_service(self, action): + action = action.lower() + if action not in [consts.AMP_ACTION_START, + consts.AMP_ACTION_STOP, + consts.AMP_ACTION_RELOAD]: + return flask.make_response(flask.jsonify(dict( + message='Invalid Request', + details="Unknown action: {0}".format(action))), 400) -def manager_keepalived_service(action): - action = action.lower() - if action not in [consts.AMP_ACTION_START, - consts.AMP_ACTION_STOP, - consts.AMP_ACTION_RELOAD]: - return flask.make_response(flask.jsonify(dict( - message='Invalid Request', - details="Unknown action: {0}".format(action))), 400) + cmd = ("/usr/sbin/service octavia-keepalived {action}".format( + action=action)) - cmd = ("/usr/sbin/service octavia-keepalived {action}".format( - action=action)) + try: + subprocess.check_output(cmd.split(), stderr=subprocess.STDOUT) + except subprocess.CalledProcessError as e: + LOG.debug("Failed to {0} keepalived service: {1}".format(action, + e)) + return flask.make_response(flask.jsonify(dict( + message="Failed to {0} keepalived service".format(action), + details=e.output)), 500) - try: - subprocess.check_output(cmd.split(), stderr=subprocess.STDOUT) - except subprocess.CalledProcessError as e: - LOG.debug("Failed to {0} keepalived service: {1}".format(action, e)) - return flask.make_response(flask.jsonify(dict( - message="Failed to {0} keepalived service".format(action), - details=e.output)), 500) - - return flask.make_response(flask.jsonify( - dict(message='OK', - details='keepalived {action}ed'.format(action=action))), 202) + return flask.make_response(flask.jsonify( + dict(message='OK', + details='keepalived {action}ed'.format(action=action))), 202) diff --git a/octavia/amphorae/backends/agent/api_server/listener.py b/octavia/amphorae/backends/agent/api_server/listener.py index 1025f41e84..5f583f6543 100644 --- a/octavia/amphorae/backends/agent/api_server/listener.py +++ b/octavia/amphorae/backends/agent/api_server/listener.py @@ -69,404 +69,386 @@ class Wrapped(object): return getattr(self.stream, attr) -"""Gets the haproxy config +class Listener(object): -:param listenerid: the id of the listener -""" + def get_haproxy_config(self, listener_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: + cfg = file.read() + resp = flask.Response(cfg, mimetype='text/plain', ) + resp.headers['ETag'] = hashlib.md5(six.b(cfg)).hexdigest() # nosec + return resp -def get_haproxy_config(listener_id): - _check_listener_exists(listener_id) - with open(util.config_path(listener_id), 'r') as file: - cfg = file.read() - resp = flask.Response(cfg, mimetype='text/plain', ) - resp.headers['ETag'] = hashlib.md5(six.b(cfg)).hexdigest() # nosec - return resp + def upload_haproxy_config(self, amphora_id, listener_id): + """Upload the haproxy config + :param amphora_id: The id of the amphora to update + :param listener_id: The id of the listener + """ + stream = Wrapped(flask.request.stream) + # We have to hash here because HAProxy has a string length limitation + # in the configuration file "peer " 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)) -"""Upload the haproxy config - -:param amphora_id: The id of the amphora to update -:param listener_id: The id of the listener -""" - - -def upload_haproxy_config(amphora_id, listener_id): - stream = Wrapped(flask.request.stream) - # We have to hash here because HAProxy has a string length limitation - # in the configuration file "peer " 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)) - - name = os.path.join(util.haproxy_dir(listener_id), 'haproxy.cfg.new') - flags = os.O_WRONLY | os.O_CREAT | os.O_TRUNC - # mode 00600 - mode = stat.S_IRUSR | stat.S_IWUSR - with os.fdopen(os.open(name, flags, mode), 'w') as file: - b = stream.read(BUFFER) - while (b): - file.write(b) + name = os.path.join(util.haproxy_dir(listener_id), 'haproxy.cfg.new') + flags = os.O_WRONLY | os.O_CREAT | os.O_TRUNC + # mode 00600 + mode = stat.S_IRUSR | stat.S_IWUSR + with os.fdopen(os.open(name, flags, mode), 'w') as file: b = stream.read(BUFFER) + while (b): + file.write(b) + b = stream.read(BUFFER) - # use haproxy to check the config - cmd = "haproxy -c -L {peer} -f {config_file}".format(config_file=name, - peer=peer_name) + # use haproxy to check the config + cmd = "haproxy -c -L {peer} -f {config_file}".format(config_file=name, + peer=peer_name) - try: - subprocess.check_output(cmd.split(), stderr=subprocess.STDOUT) - except subprocess.CalledProcessError as e: - LOG.debug("Failed to verify haproxy file: %s", e) - os.remove(name) # delete file - return flask.make_response(flask.jsonify(dict( - message="Invalid request", - details=e.output)), 400) - - # file ok - move it - os.rename(name, util.config_path(listener_id)) - - use_upstart = util.CONF.haproxy_amphora.use_upstart - file = util.init_path(listener_id) - # mode 00755 - mode = (stat.S_IRWXU | stat.S_IRGRP | stat.S_IXGRP | - stat.S_IROTH | stat.S_IXOTH) - if not os.path.exists(file): - with os.fdopen(os.open(file, flags, mode), 'w') as text_file: - template = UPSTART_TEMPLATE if use_upstart else SYSVINIT_TEMPLATE - text = template.render( - peer_name=peer_name, - haproxy_pid=util.pid_path(listener_id), - haproxy_cmd=util.CONF.haproxy_amphora.haproxy_cmd, - haproxy_cfg=util.config_path(listener_id), - respawn_count=util.CONF.haproxy_amphora.respawn_count, - respawn_interval=util.CONF.haproxy_amphora.respawn_interval, - amphora_nsname=consts.AMPHORA_NAMESPACE - ) - text_file.write(text) - - if not use_upstart: - insrvcmd = ("insserv {file}".format(file=file)) - - try: - subprocess.check_output(insrvcmd.split(), stderr=subprocess.STDOUT) - except subprocess.CalledProcessError as e: - LOG.debug("Failed to make %(file)s executable: %(err)s", - {'file': file, 'err': e}) - return flask.make_response(flask.jsonify(dict( - message="Error making file {0} executable".format(file), - details=e.output)), 500) - - res = flask.make_response(flask.jsonify({ - 'message': 'OK'}), 202) - res.headers['ETag'] = stream.get_md5() - return res - - -def start_stop_listener(listener_id, action): - action = action.lower() - if action not in [consts.AMP_ACTION_START, - consts.AMP_ACTION_STOP, - consts.AMP_ACTION_RELOAD]: - return flask.make_response(flask.jsonify(dict( - message='Invalid Request', - details="Unknown action: {0}".format(action))), 400) - - _check_listener_exists(listener_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()): - 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( - listener_id=listener_id, action=action)) - - try: - subprocess.check_output(cmd.split(), stderr=subprocess.STDOUT) - except subprocess.CalledProcessError as e: - if 'Job is already running' not in e.output: - LOG.debug("Failed to %(action)s HAProxy service: %(err)s", - {'action': action, 'err': e}) - return flask.make_response(flask.jsonify(dict( - message="Error {0}ing haproxy".format(action), - details=e.output)), 500) - if action in [consts.AMP_ACTION_STOP, - consts.AMP_ACTION_RELOAD]: - return flask.make_response(flask.jsonify( - dict(message='OK', - details='Listener {listener_id} {action}ed'.format( - listener_id=listener_id, action=action))), 202) - - details = ( - 'Configuration file is valid\nhaproxy daemon for {0} '.format( - listener_id) + 'started') - - return flask.make_response(flask.jsonify( - dict(message='OK', - details=details)), 202) - - -def delete_listener(listener_id): - _check_listener_exists(listener_id) - - # 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) try: subprocess.check_output(cmd.split(), stderr=subprocess.STDOUT) except subprocess.CalledProcessError as e: - LOG.debug("Failed to stop HAProxy service: %s", e) + LOG.debug("Failed to verify haproxy file: %s", e) + os.remove(name) # delete file return flask.make_response(flask.jsonify(dict( - message="Error stopping haproxy", - details=e.output)), 500) + message="Invalid request", + details=e.output)), 400) - # parse config and delete stats socket - try: - cfg = _parse_haproxy_file(listener_id) - os.remove(cfg['stats_socket']) - except Exception: - pass + # file ok - move it + os.rename(name, util.config_path(listener_id)) - # delete the ssl files - try: - shutil.rmtree(_cert_dir(listener_id)) - except Exception: - pass + use_upstart = util.CONF.haproxy_amphora.use_upstart + file = util.init_path(listener_id) + # mode 00755 + mode = (stat.S_IRWXU | stat.S_IRGRP | stat.S_IXGRP | + stat.S_IROTH | stat.S_IXOTH) + if not os.path.exists(file): + with os.fdopen(os.open(file, flags, mode), 'w') as text_file: + template = (UPSTART_TEMPLATE if use_upstart + else SYSVINIT_TEMPLATE) + text = template.render( + peer_name=peer_name, + haproxy_pid=util.pid_path(listener_id), + haproxy_cmd=util.CONF.haproxy_amphora.haproxy_cmd, + haproxy_cfg=util.config_path(listener_id), + respawn_count=util.CONF.haproxy_amphora.respawn_count, + respawn_interval=(util.CONF.haproxy_amphora. + respawn_interval), + amphora_nsname=consts.AMPHORA_NAMESPACE + ) + text_file.write(text) - # delete the directory + init script for that listener - shutil.rmtree(util.haproxy_dir(listener_id)) - if os.path.exists(util.init_path(listener_id)): - os.remove(util.init_path(listener_id)) + if not use_upstart: + insrvcmd = ("insserv {file}".format(file=file)) - return flask.jsonify({'message': 'OK'}) + try: + subprocess.check_output(insrvcmd.split(), + stderr=subprocess.STDOUT) + except subprocess.CalledProcessError as e: + LOG.debug("Failed to make %(file)s executable: %(err)s", + {'file': file, 'err': e}) + return flask.make_response(flask.jsonify(dict( + message="Error making file {0} executable".format(file), + details=e.output)), 500) + res = flask.make_response(flask.jsonify({ + 'message': 'OK'}), 202) + res.headers['ETag'] = stream.get_md5() + return res -"""Gets the status of all listeners + def start_stop_listener(self, listener_id, action): + action = action.lower() + if action not in [consts.AMP_ACTION_START, + consts.AMP_ACTION_STOP, + consts.AMP_ACTION_RELOAD]: + return flask.make_response(flask.jsonify(dict( + message='Invalid Request', + details="Unknown action: {0}".format(action))), 400) -This method will not consult the stats socket -so a listener might show as ACTIVE but still be -in ERROR + self._check_listener_exists(listener_id) -Currently type==SSL is also not detected -""" + # 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) + # 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): + action = consts.AMP_ACTION_START -def get_all_listeners_status(): - listeners = list() + cmd = ("/usr/sbin/service haproxy-{listener_id} {action}".format( + listener_id=listener_id, action=action)) - for listener in util.get_listeners(): - status = _check_listener_status(listener) - listener_type = '' + try: + subprocess.check_output(cmd.split(), stderr=subprocess.STDOUT) + except subprocess.CalledProcessError as e: + if 'Job is already running' not in e.output: + LOG.debug("Failed to %(action)s HAProxy service: %(err)s", + {'action': action, 'err': e}) + return flask.make_response(flask.jsonify(dict( + message="Error {0}ing haproxy".format(action), + details=e.output)), 500) + if action in [consts.AMP_ACTION_STOP, + consts.AMP_ACTION_RELOAD]: + return flask.make_response(flask.jsonify( + dict(message='OK', + details='Listener {listener_id} {action}ed'.format( + listener_id=listener_id, action=action))), 202) - if status == consts.ACTIVE: - listener_type = _parse_haproxy_file(listener)['mode'] + details = ( + 'Configuration file is valid\nhaproxy daemon for {0} '.format( + listener_id) + 'started') - listeners.append({ - 'status': status, - 'uuid': listener, - 'type': listener_type, - }) + return flask.make_response(flask.jsonify( + dict(message='OK', + details=details)), 202) - # Can't use jsonify since lists are not supported - # for security reason: http://stackoverflow.com/ - # questions/12435297/how-do-i-jsonify-a-list-in-flask - return flask.Response(json.dumps(listeners), - mimetype='application/json') + def delete_listener(self, listener_id): + self._check_listener_exists(listener_id) + # 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) + try: + subprocess.check_output(cmd.split(), stderr=subprocess.STDOUT) + except subprocess.CalledProcessError as e: + LOG.debug("Failed to stop HAProxy service: %s", e) + return flask.make_response(flask.jsonify(dict( + message="Error stopping haproxy", + details=e.output)), 500) -"""Gets the status of a listener + # parse config and delete stats socket + try: + cfg = self._parse_haproxy_file(listener_id) + os.remove(cfg['stats_socket']) + except Exception: + pass -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 + # delete the ssl files + try: + shutil.rmtree(self._cert_dir(listener_id)) + except Exception: + pass -Currently type==SSL is not detected -""" + # delete the directory + init script for that listener + shutil.rmtree(util.haproxy_dir(listener_id)) + if os.path.exists(util.init_path(listener_id)): + os.remove(util.init_path(listener_id)) + return flask.jsonify({'message': 'OK'}) -def get_listener_status(listener_id): - _check_listener_exists(listener_id) + def get_all_listeners_status(self): + """Gets the status of all listeners - status = _check_listener_status(listener_id) + This method will not consult the stats socket + so a listener might show as ACTIVE but still be + in ERROR - if status != consts.ACTIVE: + Currently type==SSL is also not detected + """ + listeners = list() + + for listener in util.get_listeners(): + status = self._check_listener_status(listener) + listener_type = '' + + if status == consts.ACTIVE: + listener_type = self._parse_haproxy_file(listener)['mode'] + + listeners.append({ + 'status': status, + 'uuid': listener, + 'type': listener_type, + }) + + # Can't use jsonify since lists are not supported + # for security reason: http://stackoverflow.com/ + # questions/12435297/how-do-i-jsonify-a-list-in-flask + return flask.Response(json.dumps(listeners), + mimetype='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 flask.jsonify(stats) + + cfg = self._parse_haproxy_file(listener_id) stats = dict( status=status, uuid=listener_id, - type='' + type=cfg['mode'] ) + + # read stats socket + q = query.HAProxyQuery(cfg['stats_socket']) + servers = q.get_pool_status() + stats['pools'] = list(servers.values()) return flask.jsonify(stats) - cfg = _parse_haproxy_file(listener_id) - stats = dict( - status=status, - uuid=listener_id, - type=cfg['mode'] - ) + def upload_certificate(self, listener_id, filename): + self._check_ssl_filename_format(filename) - # read stats socket - q = query.HAProxyQuery(cfg['stats_socket']) - servers = q.get_pool_status() - stats['pools'] = list(servers.values()) - return flask.jsonify(stats) + # create directory if not already there + if not os.path.exists(self._cert_dir(listener_id)): + os.makedirs(self._cert_dir(listener_id)) - -def upload_certificate(listener_id, filename): - _check_ssl_filename_format(filename) - - # create directory if not already there - if not os.path.exists(_cert_dir(listener_id)): - os.makedirs(_cert_dir(listener_id)) - - stream = Wrapped(flask.request.stream) - file = _cert_file_path(listener_id, filename) - flags = os.O_WRONLY | os.O_CREAT - # mode 00600 - mode = stat.S_IRUSR | stat.S_IWUSR - with os.fdopen(os.open(file, flags, mode), 'w') as crt_file: - b = stream.read(BUFFER) - while (b): - crt_file.write(b) + stream = Wrapped(flask.request.stream) + file = self._cert_file_path(listener_id, filename) + flags = os.O_WRONLY | os.O_CREAT + # mode 00600 + mode = stat.S_IRUSR | stat.S_IWUSR + with os.fdopen(os.open(file, flags, mode), 'w') as crt_file: b = stream.read(BUFFER) + while (b): + crt_file.write(b) + b = stream.read(BUFFER) - resp = flask.jsonify(dict(message='OK')) - resp.headers['ETag'] = stream.get_md5() - return resp - - -def get_certificate_md5(listener_id, filename): - _check_ssl_filename_format(filename) - - cert_path = _cert_file_path(listener_id, filename) - path_exists = os.path.exists(cert_path) - if not path_exists: - return flask.make_response(flask.jsonify(dict( - message='Certificate Not Found', - details="No certificate with filename: {f}".format( - f=filename))), 404) - - with open(cert_path, 'r') as crt_file: - cert = crt_file.read() - md5 = hashlib.md5(six.b(cert)).hexdigest() # nosec - resp = flask.jsonify(dict(md5sum=md5)) - resp.headers['ETag'] = md5 + resp = flask.jsonify(dict(message='OK')) + resp.headers['ETag'] = stream.get_md5() return resp + def get_certificate_md5(self, listener_id, filename): + self._check_ssl_filename_format(filename) -def delete_certificate(listener_id, filename): - _check_ssl_filename_format(filename) - if not os.path.exists(_cert_file_path(listener_id, filename)): - return flask.make_response(flask.jsonify(dict( - message='Certificate Not Found', - details="No certificate with filename: {f}".format( - f=filename))), 404) + cert_path = self._cert_file_path(listener_id, filename) + path_exists = os.path.exists(cert_path) + if not path_exists: + return flask.make_response(flask.jsonify(dict( + message='Certificate Not Found', + details="No certificate with filename: {f}".format( + f=filename))), 404) - os.remove(_cert_file_path(listener_id, filename)) - return flask.jsonify(dict(message='OK')) + with open(cert_path, 'r') as crt_file: + cert = crt_file.read() + md5 = hashlib.md5(six.b(cert)).hexdigest() # nosec + resp = flask.jsonify(dict(md5sum=md5)) + resp.headers['ETag'] = md5 + return resp + def delete_certificate(self, listener_id, filename): + self._check_ssl_filename_format(filename) + if not os.path.exists(self._cert_file_path(listener_id, filename)): + return flask.make_response(flask.jsonify(dict( + message='Certificate Not Found', + details="No certificate with filename: {f}".format( + f=filename))), 404) -def _check_listener_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))): - # Check if the listener is disabled - with open(util.config_path(listener_id), 'r') as file: - cfg = file.read() - m = re.search('frontend {}'.format(listener_id), cfg) - if m: - return consts.ACTIVE - else: - return consts.OFFLINE - else: # pid file but no process... - return consts.ERROR - else: - return consts.OFFLINE + os.remove(self._cert_file_path(listener_id, filename)) + return flask.jsonify(dict(message='OK')) - -def _parse_haproxy_file(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(listener_id): - # check if we know about that listener - if not os.path.exists(util.config_path(listener_id)): - raise exceptions.HTTPException( - response=flask.make_response(flask.jsonify(dict( - message='Listener Not Found', - details="No listener with UUID: {0}".format( - listener_id))), 404)) - - -def _check_ssl_filename_format(filename): - # check if the format is (xxx.)*xxx.pem - if not re.search('(\w.)+pem', filename): - raise exceptions.HTTPException( - response=flask.make_response(flask.jsonify(dict( - message='Filename has wrong format')), 400)) - - -def _cert_dir(listener_id): - return os.path.join(util.CONF.haproxy_amphora.base_cert_dir, - listener_id) - - -def _cert_file_path(listener_id, filename): - return os.path.join(_cert_dir(listener_id), filename) - - -def vrrp_check_script_update(listener_id, action): - listener_ids = util.get_listeners() - if action == consts.AMP_ACTION_STOP: - listener_ids.remove(listener_id) - args = [] - for listener_id in listener_ids: - args.append(util.haproxy_sock_path(listener_id)) - - if not os.path.exists(util.keepalived_dir()): - os.makedirs(util.keepalived_dir()) - os.makedirs(util.keepalived_check_scripts_dir()) - - cmd = 'haproxy-vrrp-check {args}; exit $?'.format(args=' '.join(args)) - with open(util.haproxy_check_script_path(), 'w') as text_file: - 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... + def _check_listener_status(self, 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))): + # Check if the listener is disabled + with open(util.config_path(listener_id), 'r') as file: + cfg = file.read() + m = re.search('frontend {}'.format(listener_id), cfg) + if m: + return consts.ACTIVE + else: + return consts.OFFLINE + else: # pid file but no process... + return consts.ERROR + else: + return consts.OFFLINE + + 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)): + raise exceptions.HTTPException( + response=flask.make_response(flask.jsonify(dict( + message='Listener Not Found', + details="No listener with UUID: {0}".format( + listener_id))), 404)) + + def _check_ssl_filename_format(self, filename): + # check if the format is (xxx.)*xxx.pem + if not re.search('(\w.)+pem', filename): + raise exceptions.HTTPException( + response=flask.make_response(flask.jsonify(dict( + message='Filename has wrong format')), 400)) + + def _cert_dir(self, listener_id): + return os.path.join(util.CONF.haproxy_amphora.base_cert_dir, + listener_id) + + def _cert_file_path(self, listener_id, filename): + return os.path.join(self._cert_dir(listener_id), filename) + + def vrrp_check_script_update(self, listener_id, action): + listener_ids = util.get_listeners() + if action == consts.AMP_ACTION_STOP: + listener_ids.remove(listener_id) + args = [] + for listener_id in listener_ids: + args.append(util.haproxy_sock_path(listener_id)) + + if not os.path.exists(util.keepalived_dir()): + os.makedirs(util.keepalived_dir()) + os.makedirs(util.keepalived_check_scripts_dir()) + + cmd = 'haproxy-vrrp-check {args}; exit $?'.format(args=' '.join(args)) + 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)): + 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 - else: - return consts.OFFLINE diff --git a/octavia/amphorae/backends/agent/api_server/plug.py b/octavia/amphorae/backends/agent/api_server/plug.py index 92515dc6a0..98928d8365 100644 --- a/octavia/amphorae/backends/agent/api_server/plug.py +++ b/octavia/amphorae/backends/agent/api_server/plug.py @@ -48,306 +48,307 @@ template_port = j2_env.get_template(ETH_X_PORT_CONF) template_vip = j2_env.get_template(ETH_X_VIP_CONF) -def plug_vip(vip, subnet_cidr, gateway, - mac_address, vrrp_ip=None, host_routes=None): - # Validate vip and subnet_cidr, calculate broadcast address and netmask - try: - render_host_routes = [] - ip = ipaddress.ip_address( - vip if six.text_type == type(vip) else six.u(vip)) - network = ipaddress.ip_network( - subnet_cidr if six.text_type == type(subnet_cidr) - else six.u(subnet_cidr)) - vip = ip.exploded - broadcast = network.broadcast_address.exploded - netmask = (network.prefixlen if ip.version is 6 - else network.netmask.exploded) - vrrp_version = None - if vrrp_ip: - vrrp_ip_obj = ipaddress.ip_address( - vrrp_ip if six.text_type == type(vrrp_ip) else six.u(vrrp_ip) - ) - vrrp_version = vrrp_ip_obj.version - if host_routes: - for hr in host_routes: - network = ipaddress.ip_network( - hr['destination'] if isinstance( - hr['destination'], six.text_type) else - six.u(hr['destination'])) - render_host_routes.append({'network': network, - 'gw': hr['nexthop']}) - except ValueError: - return flask.make_response(flask.jsonify(dict( - message="Invalid VIP")), 400) +class Plug(object): - # Check if the interface is already in the network namespace - # Do not attempt to re-plug the VIP if it is already in the - # network namespace - if _netns_interface_exists(mac_address): - return flask.make_response(flask.jsonify(dict( - message="Interface already exists")), 409) - - # This is the interface prior to moving into the netns - default_netns_interface = _interface_by_mac(mac_address) - - # Always put the VIP interface as eth1 - primary_interface = consts.NETNS_PRIMARY_INTERFACE - secondary_interface = "{interface}:0".format(interface=primary_interface) - - # We need to setup the netns network directory so that the ifup - # commands used here and in the startup scripts "sees" the right - # interfaces and scripts. - interface_file_path = util.get_network_interface_file(primary_interface) - os.makedirs('/etc/netns/' + consts.AMPHORA_NAMESPACE) - shutil.copytree('/etc/network', - '/etc/netns/{}/network'.format(consts.AMPHORA_NAMESPACE), - symlinks=True, - ignore=shutil.ignore_patterns('eth0*', 'openssh*')) - name = '/etc/netns/{}/network/interfaces'.format(consts.AMPHORA_NAMESPACE) - flags = os.O_WRONLY | os.O_CREAT | os.O_TRUNC - # mode 00644 - mode = stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | stat.S_IROTH - with os.fdopen(os.open(name, flags, mode), 'w') as int_file: - int_file.write('auto lo\n') - int_file.write('iface lo inet loopback\n') - if not CONF.amphora_agent.agent_server_network_file: - int_file.write('source /etc/netns/{}/network/' - 'interfaces.d/*.cfg\n'.format( - consts.AMPHORA_NAMESPACE)) - - # write interface file - - mode = stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | stat.S_IROTH - - # If we are using a consolidated interfaces file, just append - # otherwise clear the per interface file as we are rewriting it - # TODO(johnsom): We need a way to clean out old interfaces records - if CONF.amphora_agent.agent_server_network_file: - flags = os.O_WRONLY | os.O_CREAT | os.O_APPEND - else: - flags = os.O_WRONLY | os.O_CREAT | os.O_TRUNC - - with os.fdopen(os.open(interface_file_path, flags, mode), - 'w') as text_file: - text = template_vip.render( - interface=primary_interface, - vip=vip, - vip_ipv6=ip.version is 6, - broadcast=broadcast, - netmask=netmask, - gateway=gateway, - vrrp_ip=vrrp_ip, - vrrp_ipv6=vrrp_version is 6, - host_routes=render_host_routes, - ) - text_file.write(text) - - # Update the list of interfaces to add to the namespace - # This is used in the amphora reboot case to re-establish the namespace - _update_plugged_interfaces_file(primary_interface, mac_address) - - # Create the namespace - netns = pyroute2.NetNS(consts.AMPHORA_NAMESPACE, flags=os.O_CREAT) - netns.close() - - with pyroute2.IPRoute() as ipr: - # Move the interfaces into the namespace - idx = ipr.link_lookup(ifname=default_netns_interface)[0] - ipr.link('set', index=idx, net_ns_fd=consts.AMPHORA_NAMESPACE, - IFLA_IFNAME=primary_interface) - - # bring interfaces up - _bring_if_down(primary_interface) - _bring_if_down(secondary_interface) - _bring_if_up(primary_interface, 'VIP') - _bring_if_up(secondary_interface, 'VIP') - - return flask.make_response(flask.jsonify(dict( - message="OK", - details="VIP {vip} plugged on interface {interface}".format( - vip=vip, interface=primary_interface))), 202) - - -def _generate_network_file_text(netns_interface, fixed_ips): - text = '' - if fixed_ips is None: - text = template_port.render(interface=netns_interface) - else: - for index, fixed_ip in enumerate(fixed_ips, -1): - if index == -1: - netns_ip_interface = netns_interface - else: - netns_ip_interface = "{int}:{ip}".format( - int=netns_interface, ip=index) - try: - ip_addr = fixed_ip['ip_address'] - cidr = fixed_ip['subnet_cidr'] - ip = ipaddress.ip_address( - ip_addr if six.text_type == type( - ip_addr) else six.u(ip_addr)) - network = ipaddress.ip_network( - cidr if six.text_type == type( - cidr) else six.u(cidr)) - broadcast = network.broadcast_address.exploded - netmask = (network.prefixlen if ip.version is 6 - else network.netmask.exploded) - host_routes = [] - for hr in fixed_ip.get('host_routes', []): + def plug_vip(self, vip, subnet_cidr, gateway, + mac_address, vrrp_ip=None, host_routes=None): + # Validate vip and subnet_cidr, calculate broadcast address and netmask + try: + render_host_routes = [] + ip = ipaddress.ip_address( + vip if six.text_type == type(vip) else six.u(vip)) + network = ipaddress.ip_network( + subnet_cidr if six.text_type == type(subnet_cidr) + else six.u(subnet_cidr)) + vip = ip.exploded + broadcast = network.broadcast_address.exploded + netmask = (network.prefixlen if ip.version is 6 + else network.netmask.exploded) + vrrp_version = None + if vrrp_ip: + vrrp_ip_obj = ipaddress.ip_address( + vrrp_ip if six.text_type == type(vrrp_ip) + else six.u(vrrp_ip) + ) + vrrp_version = vrrp_ip_obj.version + if host_routes: + for hr in host_routes: network = ipaddress.ip_network( hr['destination'] if isinstance( hr['destination'], six.text_type) else six.u(hr['destination'])) - host_routes.append({'network': network, - 'gw': hr['nexthop']}) - except ValueError: - return flask.make_response(flask.jsonify(dict( - message="Invalid network IP")), 400) - new_text = template_port.render(interface=netns_ip_interface, - ipv6=ip.version is 6, - ip_address=ip.exploded, - broadcast=broadcast, - netmask=netmask, - host_routes=host_routes) - text = '\n'.join([text, new_text]) - return text + render_host_routes.append({'network': network, + 'gw': hr['nexthop']}) + except ValueError: + return flask.make_response(flask.jsonify(dict( + message="Invalid VIP")), 400) + # Check if the interface is already in the network namespace + # Do not attempt to re-plug the VIP if it is already in the + # network namespace + if self._netns_interface_exists(mac_address): + return flask.make_response(flask.jsonify(dict( + message="Interface already exists")), 409) -def _check_ip_addresses(fixed_ips): - if fixed_ips: - for ip in fixed_ips: - try: - socket.inet_pton(socket.AF_INET, ip.get('ip_address')) - except socket.error: - socket.inet_pton(socket.AF_INET6, ip.get('ip_address')) + # This is the interface prior to moving into the netns + default_netns_interface = self._interface_by_mac(mac_address) + # Always put the VIP interface as eth1 + primary_interface = consts.NETNS_PRIMARY_INTERFACE + secondary_interface = "{interface}:0".format( + interface=primary_interface) -def plug_network(mac_address, fixed_ips): - - # Check if the interface is already in the network namespace - # Do not attempt to re-plug the network if it is already in the - # network namespace - if _netns_interface_exists(mac_address): - return flask.make_response(flask.jsonify(dict( - message="Interface already exists")), 409) - - # This is the interface as it was initially plugged into the - # default network namespace, this will likely always be eth1 - - try: - _check_ip_addresses(fixed_ips=fixed_ips) - except socket.error: - return flask.make_response(flask.jsonify(dict( - message="Invalid network port")), 400) - - default_netns_interface = _interface_by_mac(mac_address) - - # We need to determine the interface name when inside the namespace - # to avoid name conflicts - with pyroute2.NetNS(consts.AMPHORA_NAMESPACE, flags=os.O_CREAT) as netns: - - # 1 means just loopback, but we should already have a VIP - # This works for the add/delete/add case as we don't delete interfaces - # Note, eth0 is skipped because that is the VIP interface - netns_interface = 'eth{0}'.format(len(netns.get_links())) - - LOG.info(_LI('Plugged interface {0} will become {1} in the ' - 'namespace {2}').format(default_netns_interface, - netns_interface, - consts.AMPHORA_NAMESPACE)) - interface_file_path = util.get_network_interface_file(netns_interface) - - # write interface file - - # If we are using a consolidated interfaces file, just append - # otherwise clear the per interface file as we are rewriting it - # TODO(johnsom): We need a way to clean out old interfaces records - if CONF.amphora_agent.agent_server_network_file: - flags = os.O_WRONLY | os.O_CREAT | os.O_APPEND - else: + # We need to setup the netns network directory so that the ifup + # commands used here and in the startup scripts "sees" the right + # interfaces and scripts. + interface_file_path = util.get_network_interface_file( + primary_interface) + os.makedirs('/etc/netns/' + consts.AMPHORA_NAMESPACE) + shutil.copytree( + '/etc/network', + '/etc/netns/{}/network'.format(consts.AMPHORA_NAMESPACE), + symlinks=True, + ignore=shutil.ignore_patterns('eth0*', 'openssh*')) + name = '/etc/netns/{}/network/interfaces'.format( + consts.AMPHORA_NAMESPACE) flags = os.O_WRONLY | os.O_CREAT | os.O_TRUNC + # mode 00644 + mode = stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | stat.S_IROTH + with os.fdopen(os.open(name, flags, mode), 'w') as int_file: + int_file.write('auto lo\n') + int_file.write('iface lo inet loopback\n') + if not CONF.amphora_agent.agent_server_network_file: + int_file.write('source /etc/netns/{}/network/' + 'interfaces.d/*.cfg\n'.format( + consts.AMPHORA_NAMESPACE)) - # mode 00644 - mode = stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | stat.S_IROTH + # write interface file - with os.fdopen(os.open(interface_file_path, flags, mode), - 'w') as text_file: - text = _generate_network_file_text(netns_interface, fixed_ips) - text_file.write(text) + mode = stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | stat.S_IROTH - # Update the list of interfaces to add to the namespace - _update_plugged_interfaces_file(netns_interface, mac_address) + # If we are using a consolidated interfaces file, just append + # otherwise clear the per interface file as we are rewriting it + # TODO(johnsom): We need a way to clean out old interfaces records + if CONF.amphora_agent.agent_server_network_file: + flags = os.O_WRONLY | os.O_CREAT | os.O_APPEND + else: + flags = os.O_WRONLY | os.O_CREAT | os.O_TRUNC - with pyroute2.IPRoute() as ipr: - # Move the interfaces into the namespace - idx = ipr.link_lookup(ifname=default_netns_interface)[0] - ipr.link('set', index=idx, - net_ns_fd=consts.AMPHORA_NAMESPACE, - IFLA_IFNAME=netns_interface) + with os.fdopen(os.open(interface_file_path, flags, mode), + 'w') as text_file: + text = template_vip.render( + interface=primary_interface, + vip=vip, + vip_ipv6=ip.version is 6, + broadcast=broadcast, + netmask=netmask, + gateway=gateway, + vrrp_ip=vrrp_ip, + vrrp_ipv6=vrrp_version is 6, + host_routes=render_host_routes, + ) + text_file.write(text) - _bring_if_down(netns_interface) - _bring_if_up(netns_interface, 'network') + # Update the list of interfaces to add to the namespace + # This is used in the amphora reboot case to re-establish the namespace + self._update_plugged_interfaces_file(primary_interface, mac_address) - return flask.make_response(flask.jsonify(dict( - message="OK", - details="Plugged on interface {interface}".format( - interface=netns_interface))), 202) + # Create the namespace + netns = pyroute2.NetNS(consts.AMPHORA_NAMESPACE, flags=os.O_CREAT) + netns.close() + with pyroute2.IPRoute() as ipr: + # Move the interfaces into the namespace + idx = ipr.link_lookup(ifname=default_netns_interface)[0] + ipr.link('set', index=idx, net_ns_fd=consts.AMPHORA_NAMESPACE, + IFLA_IFNAME=primary_interface) -def _interface_by_mac(mac): - for interface in netifaces.interfaces(): - if netifaces.AF_LINK in netifaces.ifaddresses(interface): - for link in netifaces.ifaddresses(interface)[netifaces.AF_LINK]: - if link.get('addr', '').lower() == mac.lower(): - return interface - raise exceptions.HTTPException( - response=flask.make_response(flask.jsonify(dict( - details="No suitable network interface found")), 404)) + # bring interfaces up + self._bring_if_down(primary_interface) + self._bring_if_down(secondary_interface) + self._bring_if_up(primary_interface, 'VIP') + self._bring_if_up(secondary_interface, 'VIP') + return flask.make_response(flask.jsonify(dict( + message="OK", + details="VIP {vip} plugged on interface {interface}".format( + vip=vip, interface=primary_interface))), 202) -def _bring_if_up(interface, what): - # Note, we are not using pyroute2 for this as it is not /etc/netns - # aware. - cmd = ("ip netns exec {ns} ifup {params}".format( - ns=consts.AMPHORA_NAMESPACE, params=interface)) - try: - subprocess.check_output(cmd.split(), stderr=subprocess.STDOUT) - except subprocess.CalledProcessError as e: - LOG.error(_LE('Failed to if up {0} due to ' - 'error: {1}').format(interface, str(e))) + def _generate_network_file_text(self, netns_interface, fixed_ips): + text = '' + if fixed_ips is None: + text = template_port.render(interface=netns_interface) + else: + for index, fixed_ip in enumerate(fixed_ips, -1): + if index == -1: + netns_ip_interface = netns_interface + else: + netns_ip_interface = "{int}:{ip}".format( + int=netns_interface, ip=index) + try: + ip_addr = fixed_ip['ip_address'] + cidr = fixed_ip['subnet_cidr'] + ip = ipaddress.ip_address( + ip_addr if six.text_type == type( + ip_addr) else six.u(ip_addr)) + network = ipaddress.ip_network( + cidr if six.text_type == type( + cidr) else six.u(cidr)) + broadcast = network.broadcast_address.exploded + netmask = (network.prefixlen if ip.version is 6 + else network.netmask.exploded) + host_routes = [] + for hr in fixed_ip.get('host_routes', []): + network = ipaddress.ip_network( + hr['destination'] if isinstance( + hr['destination'], six.text_type) else + six.u(hr['destination'])) + host_routes.append({'network': network, + 'gw': hr['nexthop']}) + except ValueError: + return flask.make_response(flask.jsonify(dict( + message="Invalid network IP")), 400) + new_text = template_port.render(interface=netns_ip_interface, + ipv6=ip.version is 6, + ip_address=ip.exploded, + broadcast=broadcast, + netmask=netmask, + host_routes=host_routes) + text = '\n'.join([text, new_text]) + return text + + def _check_ip_addresses(self, fixed_ips): + if fixed_ips: + for ip in fixed_ips: + try: + socket.inet_pton(socket.AF_INET, ip.get('ip_address')) + except socket.error: + socket.inet_pton(socket.AF_INET6, ip.get('ip_address')) + + def plug_network(self, mac_address, fixed_ips): + # Check if the interface is already in the network namespace + # Do not attempt to re-plug the network if it is already in the + # network namespace + if self._netns_interface_exists(mac_address): + return flask.make_response(flask.jsonify(dict( + message="Interface already exists")), 409) + + # This is the interface as it was initially plugged into the + # default network namespace, this will likely always be eth1 + + try: + self._check_ip_addresses(fixed_ips=fixed_ips) + except socket.error: + return flask.make_response(flask.jsonify(dict( + message="Invalid network port")), 400) + + default_netns_interface = self._interface_by_mac(mac_address) + + # We need to determine the interface name when inside the namespace + # to avoid name conflicts + with pyroute2.NetNS(consts.AMPHORA_NAMESPACE, + flags=os.O_CREAT) as netns: + + # 1 means just loopback, but we should already have a VIP. This + # works for the add/delete/add case as we don't delete interfaces + # Note, eth0 is skipped because that is the VIP interface + netns_interface = 'eth{0}'.format(len(netns.get_links())) + + LOG.info(_LI('Plugged interface {0} will become {1} in the ' + 'namespace {2}').format(default_netns_interface, + netns_interface, + consts.AMPHORA_NAMESPACE)) + interface_file_path = util.get_network_interface_file(netns_interface) + + # write interface file + + # If we are using a consolidated interfaces file, just append + # otherwise clear the per interface file as we are rewriting it + # TODO(johnsom): We need a way to clean out old interfaces records + if CONF.amphora_agent.agent_server_network_file: + flags = os.O_WRONLY | os.O_CREAT | os.O_APPEND + else: + flags = os.O_WRONLY | os.O_CREAT | os.O_TRUNC + + # mode 00644 + mode = stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | stat.S_IROTH + + with os.fdopen(os.open(interface_file_path, flags, mode), + 'w') as text_file: + text = self._generate_network_file_text(netns_interface, fixed_ips) + text_file.write(text) + + # Update the list of interfaces to add to the namespace + self._update_plugged_interfaces_file(netns_interface, mac_address) + + with pyroute2.IPRoute() as ipr: + # Move the interfaces into the namespace + idx = ipr.link_lookup(ifname=default_netns_interface)[0] + ipr.link('set', index=idx, + net_ns_fd=consts.AMPHORA_NAMESPACE, + IFLA_IFNAME=netns_interface) + + self._bring_if_down(netns_interface) + self._bring_if_up(netns_interface, 'network') + + return flask.make_response(flask.jsonify(dict( + message="OK", + details="Plugged on interface {interface}".format( + interface=netns_interface))), 202) + + def _interface_by_mac(self, mac): + for interface in netifaces.interfaces(): + if netifaces.AF_LINK in netifaces.ifaddresses(interface): + for link in netifaces.ifaddresses( + interface)[netifaces.AF_LINK]: + if link.get('addr', '').lower() == mac.lower(): + return interface raise exceptions.HTTPException( response=flask.make_response(flask.jsonify(dict( - message='Error plugging {0}'.format(what), - details=e.output)), 500)) + details="No suitable network interface found")), 404)) + def _bring_if_up(self, interface, what): + # Note, we are not using pyroute2 for this as it is not /etc/netns + # aware. + cmd = ("ip netns exec {ns} ifup {params}".format( + ns=consts.AMPHORA_NAMESPACE, params=interface)) + try: + subprocess.check_output(cmd.split(), stderr=subprocess.STDOUT) + except subprocess.CalledProcessError as e: + LOG.error(_LE('Failed to if up {0} due to ' + 'error: {1}').format(interface, str(e))) + raise exceptions.HTTPException( + response=flask.make_response(flask.jsonify(dict( + message='Error plugging {0}'.format(what), + details=e.output)), 500)) -def _bring_if_down(interface): - # Note, we are not using pyroute2 for this as it is not /etc/netns - # aware. - cmd = ("ip netns exec {ns} ifdown {params}".format( - ns=consts.AMPHORA_NAMESPACE, params=interface)) - try: - subprocess.check_output(cmd.split(), stderr=subprocess.STDOUT) - except subprocess.CalledProcessError: - pass + def _bring_if_down(self, interface): + # Note, we are not using pyroute2 for this as it is not /etc/netns + # aware. + cmd = ("ip netns exec {ns} ifdown {params}".format( + ns=consts.AMPHORA_NAMESPACE, params=interface)) + try: + subprocess.check_output(cmd.split(), stderr=subprocess.STDOUT) + except subprocess.CalledProcessError: + pass + def _update_plugged_interfaces_file(self, interface, mac_address): + # write interfaces to plugged_interfaces file and prevent duplicates + plug_inf_file = consts.PLUGGED_INTERFACES + flags = os.O_RDWR | os.O_CREAT + # mode 0644 + mode = stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | stat.S_IROTH + with os.fdopen(os.open(plug_inf_file, flags, mode), 'r+') as text_file: + inf_list = [inf.split()[0].rstrip() for inf in text_file] + if mac_address not in inf_list: + text_file.write("{mac_address} {interface}\n".format( + mac_address=mac_address, interface=interface)) -def _update_plugged_interfaces_file(interface, mac_address): - # write interfaces to plugged_interfaces file and prevent duplicates - plug_inf_file = consts.PLUGGED_INTERFACES - flags = os.O_RDWR | os.O_CREAT - # mode 0644 - mode = stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | stat.S_IROTH - with os.fdopen(os.open(plug_inf_file, flags, mode), 'r+') as text_file: - inf_list = [inf.split()[0].rstrip() for inf in text_file] - if mac_address not in inf_list: - text_file.write("{mac_address} {interface}\n".format( - mac_address=mac_address, interface=interface)) - - -def _netns_interface_exists(mac_address): - with pyroute2.NetNS(consts.AMPHORA_NAMESPACE, flags=os.O_CREAT) as netns: - for link in netns.get_links(): - for attr in link['attrs']: - if attr[0] == 'IFLA_ADDRESS' and attr[1] == mac_address: - return True - return False + def _netns_interface_exists(self, mac_address): + with pyroute2.NetNS(consts.AMPHORA_NAMESPACE, + flags=os.O_CREAT) as netns: + for link in netns.get_links(): + for attr in link['attrs']: + if attr[0] == 'IFLA_ADDRESS' and attr[1] == mac_address: + return True + return False diff --git a/octavia/amphorae/backends/agent/api_server/server.py b/octavia/amphorae/backends/agent/api_server/server.py index 4578e52724..d867252826 100644 --- a/octavia/amphorae/backends/agent/api_server/server.py +++ b/octavia/amphorae/backends/agent/api_server/server.py @@ -24,8 +24,7 @@ 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 plug - -app = flask.Flask(__name__) +PATH_PREFIX = '/' + api_server.VERSION # make the error pages all json @@ -36,124 +35,147 @@ def make_json_error(ex): return response -for code in six.iterkeys(exceptions.default_exceptions): - app.register_error_handler(code, make_json_error) +def register_app_error_handler(app): + for code in six.iterkeys(exceptions.default_exceptions): + app.register_error_handler(code, make_json_error) -@app.route('/' + api_server.VERSION + - '/listeners///haproxy', - methods=['PUT']) -def upload_haproxy_config(amphora_id, listener_id): - return listener.upload_haproxy_config(amphora_id, listener_id) +class Server(object): + def __init__(self): + self.app = flask.Flask(__name__) + self._keepalived = keepalived.Keepalived() + self._listener = listener.Listener() + self._plug = plug.Plug() + register_app_error_handler(self.app) -@app.route('/' + api_server.VERSION + '/listeners//haproxy', - methods=['GET']) -def get_haproxy_config(listener_id): - return listener.get_haproxy_config(listener_id) + self.app.add_url_rule(rule=PATH_PREFIX + + '/listeners///haproxy', + view_func=self.upload_haproxy_config, + methods=['PUT']) + self.app.add_url_rule(rule=PATH_PREFIX + + '/listeners//haproxy', + view_func=self.get_haproxy_config, + methods=['GET']) + self.app.add_url_rule(rule=PATH_PREFIX + + '/listeners//', + view_func=self.start_stop_listener, + methods=['PUT']) + self.app.add_url_rule(rule=PATH_PREFIX + '/listeners/', + view_func=self.delete_listener, + methods=['DELETE']) + self.app.add_url_rule(rule=PATH_PREFIX + '/details', + view_func=self.get_details, + methods=['GET']) + self.app.add_url_rule(rule=PATH_PREFIX + '/info', + view_func=self.get_info, + methods=['GET']) + 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/', + view_func=self.get_listener_status, + methods=['GET']) + self.app.add_url_rule(rule=PATH_PREFIX + '/listeners/' + '/certificates/', + view_func=self.upload_certificate, + methods=['PUT']) + self.app.add_url_rule(rule=PATH_PREFIX + '/listeners/' + '/certificates/', + view_func=self.get_certificate_md5, + methods=['GET']) + self.app.add_url_rule(rule=PATH_PREFIX + '/listeners/' + '/certificates/', + view_func=self.delete_certificate, + methods=['DELETE']) + self.app.add_url_rule(rule=PATH_PREFIX + '/plug/vip/', + view_func=self.plug_vip, + methods=['POST']) + self.app.add_url_rule(rule=PATH_PREFIX + '/plug/network', + view_func=self.plug_network, + methods=['POST']) + self.app.add_url_rule(rule=PATH_PREFIX + '/certificate', + view_func=self.upload_cert, methods=['PUT']) + self.app.add_url_rule(rule=PATH_PREFIX + '/vrrp/upload', + view_func=self.upload_vrrp_config, + methods=['PUT']) + self.app.add_url_rule(rule=PATH_PREFIX + '/vrrp/', + view_func=self.manage_service_vrrp, + methods=['PUT']) + self.app.add_url_rule(rule='/' + api_server.VERSION + + '/interface/', + 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) -@app.route('/' + api_server.VERSION + - '/listeners//', - methods=['PUT']) -def start_stop_listener(listener_id, action): - return listener.start_stop_listener(listener_id, action) + def get_haproxy_config(self, listener_id): + return self._listener.get_haproxy_config(listener_id) + def start_stop_listener(self, listener_id, action): + return self._listener.start_stop_listener(listener_id, action) -@app.route('/' + api_server.VERSION + - '/listeners/', methods=['DELETE']) -def delete_listener(listener_id): - return listener.delete_listener(listener_id) + def delete_listener(self, listener_id): + return self._listener.delete_listener(listener_id) + def get_details(self): + return amphora_info.compile_amphora_details() -@app.route('/' + api_server.VERSION + '/details', - methods=['GET']) -def get_details(): - return amphora_info.compile_amphora_details() + def get_info(self): + return amphora_info.compile_amphora_info() + def get_all_listeners_status(self): + return self._listener.get_all_listeners_status() -@app.route('/' + api_server.VERSION + '/info', - methods=['GET']) -def get_info(): - return amphora_info.compile_amphora_info() + def get_listener_status(self, listener_id): + return self._listener.get_listener_status(listener_id) + def upload_certificate(self, listener_id, filename): + return self._listener.upload_certificate(listener_id, filename) -@app.route('/' + api_server.VERSION + '/listeners', - methods=['GET']) -def get_all_listeners_status(): - return listener.get_all_listeners_status() + 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) -@app.route('/' + api_server.VERSION + '/listeners/', - methods=['GET']) -def get_listener_status(listener_id): - return listener.get_listener_status(listener_id) + def plug_vip(self, vip): + # Catch any issues with the subnet info json + try: + net_info = flask.request.get_json() + assert type(net_info) is dict + assert 'subnet_cidr' in net_info + assert 'gateway' in net_info + assert 'mac_address' in net_info + except Exception: + raise exceptions.BadRequest( + description='Invalid subnet information') + return self._plug.plug_vip(vip, + net_info['subnet_cidr'], + net_info['gateway'], + net_info['mac_address'], + net_info.get('vrrp_ip'), + net_info.get('host_routes')) + def plug_network(self): + try: + port_info = flask.request.get_json() + assert type(port_info) is dict + assert 'mac_address' in port_info + except Exception: + raise exceptions.BadRequest(description='Invalid port information') + return self._plug.plug_network(port_info['mac_address'], + port_info.get('fixed_ips')) -@app.route('/' + api_server.VERSION + '/listeners//certificates' - + '/', methods=['PUT']) -def upload_certificate(listener_id, filename): - return listener.upload_certificate(listener_id, filename) + def upload_cert(self): + return certificate_update.upload_server_cert() + def upload_vrrp_config(self): + return self._keepalived.upload_keepalived_config() -@app.route('/' + api_server.VERSION + '/listeners//certificates' - + '/', methods=['GET']) -def get_certificate_md5(listener_id, filename): - return listener.get_certificate_md5(listener_id, filename) + def manage_service_vrrp(self, action): + return self._keepalived.manager_keepalived_service(action) - -@app.route('/' + api_server.VERSION + '/listeners//certificates' - + '/', methods=['DELETE']) -def delete_certificate(listener_id, filename): - return listener.delete_certificate(listener_id, filename) - - -@app.route('/' + api_server.VERSION + '/plug/vip/', methods=['POST']) -def plug_vip(vip): - # Catch any issues with the subnet info json - try: - net_info = flask.request.get_json() - assert type(net_info) is dict - assert 'subnet_cidr' in net_info - assert 'gateway' in net_info - assert 'mac_address' in net_info - except Exception: - raise exceptions.BadRequest(description='Invalid subnet information') - return plug.plug_vip(vip, - net_info['subnet_cidr'], - net_info['gateway'], - net_info['mac_address'], - net_info.get('vrrp_ip'), - net_info.get('host_routes')) - - -@app.route('/' + api_server.VERSION + '/plug/network', methods=['POST']) -def plug_network(): - try: - port_info = flask.request.get_json() - assert type(port_info) is dict - assert 'mac_address' in port_info - except Exception: - raise exceptions.BadRequest(description='Invalid port information') - return plug.plug_network(port_info['mac_address'], - port_info.get('fixed_ips')) - - -@app.route('/' + api_server.VERSION + '/certificate', methods=['PUT']) -def upload_cert(): - return certificate_update.upload_server_cert() - - -@app.route('/' + api_server.VERSION + '/vrrp/upload', methods=['PUT']) -def upload_vrrp_config(): - return keepalived.upload_keepalived_config() - - -@app.route('/' + api_server.VERSION + '/vrrp/', methods=['PUT']) -def manage_service_vrrp(action): - return keepalived.manager_keepalived_service(action) - - -@app.route('/' + api_server.VERSION + '/interface/', methods=['GET']) -def get_interface(ip_addr): - return amphora_info.get_interface(ip_addr) + def get_interface(self, ip_addr): + return amphora_info.get_interface(ip_addr) diff --git a/octavia/cmd/agent.py b/octavia/cmd/agent.py index 3193f54ff9..ded3a21f76 100644 --- a/octavia/cmd/agent.py +++ b/octavia/cmd/agent.py @@ -81,11 +81,14 @@ def main(): ctx.load_cert_chain(CONF.amphora_agent.agent_server_cert, ca=CONF.amphora_agent.agent_server_ca) + # Initiate server class + server_instance = server.Server() + # This will trigger a reload if any files change and # in particular the certificate file serving.run_simple(hostname=CONF.haproxy_amphora.bind_host, port=CONF.haproxy_amphora.bind_port, - application=server.app, + application=server_instance.app, use_debugger=CONF.debug, ssl_context=ctx, use_reloader=True, diff --git a/octavia/tests/functional/amphorae/backend/agent/api_server/test_server.py b/octavia/tests/functional/amphorae/backend/agent/api_server/test_server.py index 2321c145ab..48418c1f69 100644 --- a/octavia/tests/functional/amphorae/backend/agent/api_server/test_server.py +++ b/octavia/tests/functional/amphorae/backend/agent/api_server/test_server.py @@ -25,7 +25,6 @@ import six from octavia.amphorae.backends.agent import api_server from octavia.amphorae.backends.agent.api_server import certificate_update -from octavia.amphorae.backends.agent.api_server import listener from octavia.amphorae.backends.agent.api_server import server from octavia.amphorae.backends.agent.api_server import util from octavia.common import constants as consts @@ -42,7 +41,8 @@ class TestServerTestCase(base.TestCase): app = None def setUp(self): - self.app = server.app.test_client() + self.test_server = server.Server() + self.app = self.test_server.app.test_client() super(TestServerTestCase, self).setUp() @mock.patch('os.path.exists') @@ -140,7 +140,7 @@ class TestServerTestCase(base.TestCase): mock_remove.assert_called_once_with(file_name) @mock.patch('os.path.exists') - @mock.patch('octavia.amphorae.backends.agent.api_server.listener.' + @mock.patch('octavia.amphorae.backends.agent.api_server.listener.Listener.' 'vrrp_check_script_update') @mock.patch('subprocess.check_output') def test_start(self, mock_subprocess, mock_vrrp, mock_exists): @@ -185,9 +185,9 @@ class TestServerTestCase(base.TestCase): ['/usr/sbin/service', 'haproxy-123', 'start'], stderr=-2) @mock.patch('os.path.exists') - @mock.patch('octavia.amphorae.backends.agent.api_server.listener.' + @mock.patch('octavia.amphorae.backends.agent.api_server.listener.Listener.' 'vrrp_check_script_update') - @mock.patch('octavia.amphorae.backends.agent.api_server.listener.' + @mock.patch('octavia.amphorae.backends.agent.api_server.listener.Listener.' '_check_haproxy_status') @mock.patch('subprocess.check_output') def test_reload(self, mock_subprocess, mock_haproxy_status, @@ -320,9 +320,9 @@ class TestServerTestCase(base.TestCase): @mock.patch('octavia.amphorae.backends.agent.api_server.util.' 'get_listeners') - @mock.patch('octavia.amphorae.backends.agent.api_server.listener.' + @mock.patch('octavia.amphorae.backends.agent.api_server.listener.Listener.' '_check_listener_status') - @mock.patch('octavia.amphorae.backends.agent.api_server.listener.' + @mock.patch('octavia.amphorae.backends.agent.api_server.listener.Listener.' '_parse_haproxy_file') def test_get_all_listeners(self, mock_parse, mock_status, mock_listener): # no listeners @@ -355,9 +355,9 @@ class TestServerTestCase(base.TestCase): {'status': consts.ERROR, 'type': '', 'uuid': '456'}], json.loads(rv.data.decode('utf-8'))) - @mock.patch('octavia.amphorae.backends.agent.api_server.listener.' + @mock.patch('octavia.amphorae.backends.agent.api_server.listener.Listener.' '_check_listener_status') - @mock.patch('octavia.amphorae.backends.agent.api_server.listener.' + @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') @@ -462,7 +462,7 @@ class TestServerTestCase(base.TestCase): mock_exists.return_value = True mock_exists.side_effect = None - path = listener._cert_file_path('123', 'test.pem') + path = self.test_server._listener._cert_file_path('123', 'test.pem') self.useFixture(test_utils.OpenFixture(path, CONTENT)) rv = self.app.get('/' + api_server.VERSION + @@ -482,7 +482,7 @@ class TestServerTestCase(base.TestCase): self.assertEqual(400, rv.status_code) mock_exists.return_value = True - path = listener._cert_file_path('123', 'test.pem') + path = self.test_server._listener._cert_file_path('123', 'test.pem') m = self.useFixture(test_utils.OpenFixture(path)).mock_open with mock.patch('os.open'), mock.patch.object(os, 'fdopen', m): @@ -527,7 +527,7 @@ class TestServerTestCase(base.TestCase): @mock.patch('pyroute2.NetNS') @mock.patch('subprocess.check_output') @mock.patch('octavia.amphorae.backends.agent.api_server.' - 'plug._netns_interface_exists') + 'plug.Plug._netns_interface_exists') def test_plug_network(self, mock_int_exists, mock_check_output, mock_netns, mock_pyroute2, mock_ifaddress, mock_interfaces): port_info = {'mac_address': '123'} @@ -802,7 +802,7 @@ class TestServerTestCase(base.TestCase): 'ifup', consts.NETNS_PRIMARY_INTERFACE], stderr=-2) @mock.patch('octavia.amphorae.backends.agent.api_server.' - 'plug._netns_interface_exists') + 'plug.Plug._netns_interface_exists') @mock.patch('netifaces.interfaces') @mock.patch('netifaces.ifaddresses') @mock.patch('pyroute2.IPRoute') diff --git a/octavia/tests/functional/amphorae/backend/agent/api_server/test_server_sysvinit.py b/octavia/tests/functional/amphorae/backend/agent/api_server/test_server_sysvinit.py index 1f2f9eed91..65c1b9a60f 100644 --- a/octavia/tests/functional/amphorae/backend/agent/api_server/test_server_sysvinit.py +++ b/octavia/tests/functional/amphorae/backend/agent/api_server/test_server_sysvinit.py @@ -26,7 +26,6 @@ import six from octavia.amphorae.backends.agent import api_server from octavia.amphorae.backends.agent.api_server import certificate_update -from octavia.amphorae.backends.agent.api_server import listener from octavia.amphorae.backends.agent.api_server import server from octavia.amphorae.backends.agent.api_server import util from octavia.common import constants as consts @@ -43,7 +42,8 @@ class ServerTestCase(base.TestCase): def setUp(self): cfg.CONF.set_override('use_upstart', False, group='haproxy_amphora') - self.app = server.app.test_client() + self.test_server = server.Server() + self.app = self.test_server.app.test_client() super(ServerTestCase, self).setUp() @mock.patch('os.path.exists') @@ -147,7 +147,7 @@ class ServerTestCase(base.TestCase): mock_remove.assert_called_once_with(file_name) @mock.patch('os.path.exists') - @mock.patch('octavia.amphorae.backends.agent.api_server.listener.' + @mock.patch('octavia.amphorae.backends.agent.api_server.listener.Listener.' 'vrrp_check_script_update') @mock.patch('subprocess.check_output') def test_start(self, mock_subprocess, mock_vrrp, mock_exists): @@ -292,9 +292,9 @@ class ServerTestCase(base.TestCase): @mock.patch('octavia.amphorae.backends.agent.api_server.util.' 'get_listeners') - @mock.patch('octavia.amphorae.backends.agent.api_server.listener.' + @mock.patch('octavia.amphorae.backends.agent.api_server.listener.Listener.' '_check_listener_status') - @mock.patch('octavia.amphorae.backends.agent.api_server.listener.' + @mock.patch('octavia.amphorae.backends.agent.api_server.listener.Listener.' '_parse_haproxy_file') def test_get_all_listeners(self, mock_parse, mock_status, mock_listener): # no listeners @@ -327,9 +327,9 @@ class ServerTestCase(base.TestCase): {'status': consts.ERROR, 'type': '', 'uuid': '456'}], json.loads(rv.data.decode('utf-8'))) - @mock.patch('octavia.amphorae.backends.agent.api_server.listener.' + @mock.patch('octavia.amphorae.backends.agent.api_server.listener.Listener.' '_check_listener_status') - @mock.patch('octavia.amphorae.backends.agent.api_server.listener.' + @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') @@ -434,7 +434,7 @@ class ServerTestCase(base.TestCase): mock_exists.return_value = True mock_exists.side_effect = None - path = listener._cert_file_path('123', 'test.pem') + path = self.test_server._listener._cert_file_path('123', 'test.pem') self.useFixture(test_utils.OpenFixture(path, CONTENT)) rv = self.app.get('/' + api_server.VERSION + '/listeners/123/certificates/test.pem') @@ -452,7 +452,7 @@ class ServerTestCase(base.TestCase): self.assertEqual(400, rv.status_code) mock_exists.return_value = True - path = listener._cert_file_path('123', 'test.pem') + path = self.test_server._listener._cert_file_path('123', 'test.pem') m = self.useFixture(test_utils.OpenFixture(path)).mock_open with mock.patch('os.open'), mock.patch.object(os, 'fdopen', m): @@ -497,7 +497,7 @@ class ServerTestCase(base.TestCase): @mock.patch('pyroute2.NetNS') @mock.patch('subprocess.check_output') @mock.patch('octavia.amphorae.backends.agent.api_server.' - 'plug._netns_interface_exists') + 'plug.Plug._netns_interface_exists') def test_plug_network(self, mock_int_exists, mock_check_output, mock_netns, mock_pyroute2, mock_ifaddress, mock_interfaces): port_info = {'mac_address': '123'} diff --git a/octavia/tests/unit/amphorae/backends/agent/api_server/test_keepalived.py b/octavia/tests/unit/amphorae/backends/agent/api_server/test_keepalived.py index b65194847e..f1fc2892e4 100644 --- a/octavia/tests/unit/amphorae/backends/agent/api_server/test_keepalived.py +++ b/octavia/tests/unit/amphorae/backends/agent/api_server/test_keepalived.py @@ -28,21 +28,22 @@ class KeepalivedTestCase(base.TestCase): self.client = self.app.test_client() self._ctx = self.app.test_request_context() self._ctx.push() + self.test_keepalived = keepalived.Keepalived() @mock.patch('subprocess.check_output') def test_manager_keepalived_service(self, mock_check_output): - res = keepalived.manager_keepalived_service('start') + res = self.test_keepalived.manager_keepalived_service('start') cmd = ("/usr/sbin/service octavia-keepalived {action}".format( action='start')) mock_check_output.assert_called_once_with(cmd.split(), stderr=subprocess.STDOUT) self.assertEqual(202, res.status_code) - res = keepalived.manager_keepalived_service('restart') + res = self.test_keepalived.manager_keepalived_service('restart') self.assertEqual(400, res.status_code) mock_check_output.side_effect = subprocess.CalledProcessError(1, 'blah!') - res = keepalived.manager_keepalived_service('start') + res = self.test_keepalived.manager_keepalived_service('start') self.assertEqual(500, res.status_code) diff --git a/octavia/tests/unit/amphorae/backends/agent/api_server/test_listener.py b/octavia/tests/unit/amphorae/backends/agent/api_server/test_listener.py index db9e6a7c66..b8fbe75661 100644 --- a/octavia/tests/unit/amphorae/backends/agent/api_server/test_listener.py +++ b/octavia/tests/unit/amphorae/backends/agent/api_server/test_listener.py @@ -34,6 +34,7 @@ class ListenerTestCase(base.TestCase): self.jinja_cfg = jinja_cfg.JinjaTemplater( base_amp_path=BASE_AMP_PATH, base_crt_dir=BASE_CRT_PATH) + self.test_listener = listener.Listener() def test_parse_haproxy_config(self): # template_tls @@ -49,7 +50,7 @@ class ListenerTestCase(base.TestCase): path = agent_util.config_path(LISTENER_ID1) self.useFixture(test_utils.OpenFixture(path, rendered_obj)) - res = listener._parse_haproxy_file(LISTENER_ID1) + 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']) @@ -69,7 +70,7 @@ class ListenerTestCase(base.TestCase): self.useFixture(test_utils.OpenFixture(path, rendered_obj)) - res = listener._parse_haproxy_file(LISTENER_ID1) + 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']) @@ -84,7 +85,7 @@ class ListenerTestCase(base.TestCase): self.useFixture(test_utils.OpenFixture(path, rendered_obj)) - res = listener._parse_haproxy_file(LISTENER_ID1) + 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']) @@ -96,7 +97,7 @@ class ListenerTestCase(base.TestCase): sample_configs.sample_listener_tuple(proto='HTTPS')) self.useFixture(test_utils.OpenFixture(path, rendered_obj)) - res = listener._parse_haproxy_file(LISTENER_ID1) + 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']) @@ -105,7 +106,7 @@ class ListenerTestCase(base.TestCase): # Bogus format self.useFixture(test_utils.OpenFixture(path, 'Bogus')) try: - res = listener._parse_haproxy_file(LISTENER_ID1) + res = self.test_listener._parse_haproxy_file(LISTENER_ID1) self.fail("No Exception?") except listener.ParsingError: pass @@ -121,17 +122,17 @@ class ListenerTestCase(base.TestCase): self.useFixture(test_utils.OpenFixture(config_path, file_contents)) self.assertEqual( consts.ACTIVE, - listener._check_listener_status(LISTENER_ID1)) + self.test_listener._check_listener_status(LISTENER_ID1)) mock_exists.side_effect = [True, False] self.assertEqual( consts.ERROR, - listener._check_listener_status(LISTENER_ID1)) + self.test_listener._check_listener_status(LISTENER_ID1)) mock_exists.side_effect = [False] self.assertEqual( consts.OFFLINE, - listener._check_listener_status(LISTENER_ID1)) + self.test_listener._check_listener_status(LISTENER_ID1)) @mock.patch('os.makedirs') @mock.patch('os.path.exists') @@ -152,7 +153,7 @@ class ListenerTestCase(base.TestCase): path = agent_util.keepalived_dir() m = self.useFixture(test_utils.OpenFixture(path)).mock_open - listener.vrrp_check_script_update(LISTENER_ID1, 'stop') + self.test_listener.vrrp_check_script_update(LISTENER_ID1, 'stop') handle = m() handle.write.assert_called_once_with(cmd) @@ -162,7 +163,7 @@ class ListenerTestCase(base.TestCase): '$?') m = self.useFixture(test_utils.OpenFixture(path)).mock_open - listener.vrrp_check_script_update(LISTENER_ID1, 'start') + self.test_listener.vrrp_check_script_update(LISTENER_ID1, 'start') handle = m() handle.write.assert_called_once_with(cmd) @@ -174,14 +175,14 @@ class ListenerTestCase(base.TestCase): mock_exists.side_effect = [True, True] self.assertEqual( consts.ACTIVE, - listener._check_haproxy_status(LISTENER_ID1)) + self.test_listener._check_haproxy_status(LISTENER_ID1)) mock_exists.side_effect = [True, False] self.assertEqual( consts.OFFLINE, - listener._check_haproxy_status(LISTENER_ID1)) + self.test_listener._check_haproxy_status(LISTENER_ID1)) mock_exists.side_effect = [False] self.assertEqual( consts.OFFLINE, - listener._check_haproxy_status(LISTENER_ID1)) + self.test_listener._check_haproxy_status(LISTENER_ID1)) diff --git a/octavia/tests/unit/amphorae/backends/agent/api_server/test_plug.py b/octavia/tests/unit/amphorae/backends/agent/api_server/test_plug.py index f268e11d8b..9cbfe804a9 100644 --- a/octavia/tests/unit/amphorae/backends/agent/api_server/test_plug.py +++ b/octavia/tests/unit/amphorae/backends/agent/api_server/test_plug.py @@ -34,8 +34,8 @@ FAKE_INTERFACE = 'eth0' class TestPlug(base.TestCase): def setUp(self): super(TestPlug, self).setUp() - self.mock_netifaces = mock.patch.object(plug, "netifaces").start() + self.test_plug = plug.Plug() self.addCleanup(self.mock_netifaces.stop) # Set up our fake interface @@ -48,7 +48,7 @@ class TestPlug(base.TestCase): } def test__interface_by_mac_case_insensitive(self): - interface = plug._interface_by_mac(FAKE_MAC_ADDRESS.upper()) + interface = self.test_plug._interface_by_mac(FAKE_MAC_ADDRESS.upper()) self.assertEqual(FAKE_INTERFACE, interface) @mock.patch.object(plug, "flask") @@ -63,7 +63,7 @@ class TestPlug(base.TestCase): mock_pyroute2, mock_flask): m = mock.mock_open() with mock.patch('os.open'), mock.patch.object(os, 'fdopen', m): - plug.plug_vip( + self.test_plug.plug_vip( vip=FAKE_IP_IPV4, subnet_cidr=FAKE_CIDR_IPV4, gateway=FAKE_GATEWAY_IPV4, @@ -87,7 +87,7 @@ class TestPlug(base.TestCase): mock_pyroute2, mock_flask): m = mock.mock_open() with mock.patch('os.open'), mock.patch.object(os, 'fdopen', m): - plug.plug_vip( + self.test_plug.plug_vip( vip=FAKE_IP_IPV6, subnet_cidr=FAKE_CIDR_IPV6, gateway=FAKE_GATEWAY_IPV6, @@ -111,7 +111,7 @@ class TestPlug(base.TestCase): mock_pyroute2, mock_flask): m = mock.mock_open() with mock.patch('os.open'), mock.patch.object(os, 'fdopen', m): - plug.plug_vip( + self.test_plug.plug_vip( vip="error", subnet_cidr=FAKE_CIDR_IPV4, gateway=FAKE_GATEWAY_IPV4, @@ -128,13 +128,16 @@ class TestPlug(base.TestCase): 'attrs': [['IFLA_ADDRESS', '123']]}] # Interface is found in netns - self.assertTrue(plug._netns_interface_exists('123')) + self.assertTrue(self.test_plug._netns_interface_exists('123')) # Interface is not found in netns - self.assertFalse(plug._netns_interface_exists('321')) + self.assertFalse(self.test_plug._netns_interface_exists('321')) class TestPlugNetwork(base.TestCase): + def setUp(self): + super(TestPlugNetwork, self).setUp() + self.test_plug = plug.Plug() def test__generate_network_file_text_static_ip(self): netns_interface = 'eth1234' @@ -151,7 +154,8 @@ class TestPlugNetwork(base.TestCase): {'destination': DEST1, 'nexthop': NEXTHOP}, {'destination': DEST2, 'nexthop': NEXTHOP} ]}] - text = plug._generate_network_file_text(netns_interface, fixed_ips) + text = self.test_plug._generate_network_file_text(netns_interface, + fixed_ips) expected_text = ( '\n\n# Generated by Octavia agent\n' 'auto ' + netns_interface + '\n'