From dacb0b123a86e03ab85232f3f61ef8b0ba9584d7 Mon Sep 17 00:00:00 2001 From: jxie Date: Thu, 22 Aug 2019 15:17:42 +0800 Subject: [PATCH] patch for sdn-base For differences between sdn-base and 9.8-stable branch(commit 46e2320). Note: version in __init__.py is modified to be 5.0.0 here. So it is actually not the exact differences between the two branches. Meanwhile, it only includes the files from f5_openstack_agent directory, and it excludes 3 diff test files from this directory: f5_openstack_agent/lbaasv2/drivers/bigip/test/test_esd_filehandler.py f5_openstack_agent/lbaasv2/drivers/bigip/test/test_lbaas_builder.py f5_openstack_agent/lbaasv2/drivers/bigip/test/test_service_adapter.py Other directories are ignored here for now. NOTE: This should never be merged into sdn-base branch. The sdn-base branch should never be touched/merged. sdn-base is at 14d8557. This should only server as the PR to download the patch file. Issues: Fixes # Problem: Analysis: Tests: --- f5_openstack_agent/__init__.py | 1 + .../lbaasv2/drivers/bigip/agent.py | 17 ++- .../lbaasv2/drivers/bigip/agent_manager.py | 20 +++ .../lbaasv2/drivers/bigip/esd_filehandler.py | 63 ++++---- .../lbaasv2/drivers/bigip/icontrol_driver.py | 53 +++++-- .../lbaasv2/drivers/bigip/l7policy_adapter.py | 142 +++++++++++++++++- .../lbaasv2/drivers/bigip/l7policy_service.py | 76 +++++++++- .../lbaasv2/drivers/bigip/lbaas_builder.py | 26 ++++ .../lbaasv2/drivers/bigip/refersh_esd.py | 31 ++++ .../lbaasv2/drivers/bigip/service_adapter.py | 20 ++- .../lbaasv2/drivers/bigip/service_launcher.py | 43 ++++++ 11 files changed, 429 insertions(+), 63 deletions(-) create mode 100755 f5_openstack_agent/lbaasv2/drivers/bigip/refersh_esd.py create mode 100644 f5_openstack_agent/lbaasv2/drivers/bigip/service_launcher.py diff --git a/f5_openstack_agent/__init__.py b/f5_openstack_agent/__init__.py index ba7be38e4..0db984c4e 100644 --- a/f5_openstack_agent/__init__.py +++ b/f5_openstack_agent/__init__.py @@ -1 +1,2 @@ +# actually patched to 46e2320 of 9.8-stable althougn it is 5.0.0 here __version__ = "5.0.0" diff --git a/f5_openstack_agent/lbaasv2/drivers/bigip/agent.py b/f5_openstack_agent/lbaasv2/drivers/bigip/agent.py index cd72e1338..f99e6e34a 100755 --- a/f5_openstack_agent/lbaasv2/drivers/bigip/agent.py +++ b/f5_openstack_agent/lbaasv2/drivers/bigip/agent.py @@ -16,6 +16,7 @@ import errno import inspect +import service_launcher import sys import f5_openstack_agent.lbaasv2.drivers.bigip.exceptions as exceptions @@ -23,7 +24,6 @@ try: from oslo_config import cfg from oslo_log import log as oslo_logging - from oslo_service import service except ImportError as CriticalError: frame = inspect.getframeinfo(inspect.currentframe()) CriticalError = \ @@ -77,14 +77,12 @@ def main(): """F5 LBaaS agent for OpenStack.""" cfg.CONF.register_opts(OPTS) cfg.CONF.register_opts(manager.OPTS) - # pzhang(NOTE): may not be used anywhere, needs to check cfg.CONF.register_opts(INTERFACE_OPTS) config.register_agent_state_opts_helper(cfg.CONF) config.register_root_helper(cfg.CONF) common_config.init(sys.argv[1:]) - # alias for common_config.setup_logging()... config.setup_logging() mgr = manager.LbaasAgentManager(cfg.CONF) @@ -94,7 +92,18 @@ def main(): topic=f5constants.TOPIC_LOADBALANCER_AGENT_V2, manager=mgr ) - service.launch(cfg.CONF, svc).wait() + + def handler(*args): + LOG.info("receive signal to refresh ESD files!") + if mgr.lbdriver.esd_processor: + mgr.lbdriver.init_esd() + LOG.info("ESD has been refreshed") + + service_launch = service_launcher.F5ServiceLauncher(cfg.CONF) + + service_launch.signal_handler.add_handler('SIGUSR1', handler) + service_launch.launch_service(svc) + service_launch.wait() if __name__ == '__main__': diff --git a/f5_openstack_agent/lbaasv2/drivers/bigip/agent_manager.py b/f5_openstack_agent/lbaasv2/drivers/bigip/agent_manager.py index 3e9630dbf..636453a24 100644 --- a/f5_openstack_agent/lbaasv2/drivers/bigip/agent_manager.py +++ b/f5_openstack_agent/lbaasv2/drivers/bigip/agent_manager.py @@ -129,6 +129,16 @@ default=60, help=( 'Amount of time to wait for a errored service to become active') + ), + cfg.BoolOpt( + 'password_cipher_mode', + default=False, + help='The flag indicating the password is plain text or not.' + ), + cfg.BoolOpt( + 'esd_auto_refresh', + default=True, + help='Enable ESD file periodic refresh' ) ] @@ -324,6 +334,9 @@ def __init__(self, conf): self._report_state) heartbeat.start(interval=report_interval) + if self.lbdriver: + self.lbdriver.connect() + def _load_driver(self, conf): self.lbdriver = None @@ -489,6 +502,13 @@ def connect_driver(self, context): if self.lbdriver: self.lbdriver.connect() + @periodic_task.periodic_task(spacing=PERIODIC_TASK_INTERVAL) + def refresh_esd(self, context): + """Refresh ESD files.""" + if self.lbdriver.esd_processor and self.conf.esd_auto_refresh: + LOG.info("refresh ESD files automatically") + self.lbdriver.init_esd() + @periodic_task.periodic_task(spacing=PERIODIC_TASK_INTERVAL) def recover_errored_devices(self, context): """Try to reconnect to errored devices.""" diff --git a/f5_openstack_agent/lbaasv2/drivers/bigip/esd_filehandler.py b/f5_openstack_agent/lbaasv2/drivers/bigip/esd_filehandler.py index d37ce65b2..d62a835eb 100644 --- a/f5_openstack_agent/lbaasv2/drivers/bigip/esd_filehandler.py +++ b/f5_openstack_agent/lbaasv2/drivers/bigip/esd_filehandler.py @@ -14,6 +14,7 @@ # limitations under the License. # +import copy import glob import json import os @@ -31,56 +32,50 @@ LOG = logging.getLogger(__name__) -class EsdJSONValidation(object): - """Class reads the json file(s) +class EsdTagProcessor(object): + """Class processes json dictionary - It checks and parses the content of json file(s) to a dictionary + It checks compares the tags from esdjson dictionary to list of valid tags """ - def __init__(self, esddir): - self.esdJSONFileList = glob.glob(os.path.join(esddir, '*.json')) - self.esdJSONDict = {} - - def read_json(self): - for fileList in self.esdJSONFileList: + _instance = None + esd_dict = {} + + def __new__(cls, *args, **kwargs): + if not isinstance(cls._instance, cls): + cls._instance = object.__new__(cls, *args, **kwargs) + return cls._instance + + def read_json(self, esddir): + esdJSONFileList = glob.glob(os.path.join(esddir, '*.json')) + esdJSONDict = {} + for fileList in esdJSONFileList: try: with open(fileList) as json_file: # Reading each file to a dictionary fileJSONDict = json.load(json_file) # Combine all dictionaries to one - self.esdJSONDict.update(fileJSONDict) + esdJSONDict.update(fileJSONDict) except ValueError as err: LOG.error('ESD JSON File is invalid: %s', err) raise f5_ex.esdJSONFileInvalidException() - return self.esdJSONDict - - -class EsdTagProcessor(EsdJSONValidation): - """Class processes json dictionary - - It checks compares the tags from esdjson dictionary to list of valid tags - """ - def __init__(self, esddir): - super(EsdTagProcessor, self).__init__(esddir) - - # this function will return intersection of known valid esd tags - # and the ones that user provided - def valid_tag_key_subset(self): - self.validtags = list(set(self.esdJSONDict.keys()) & - set(self.valid_esd_tags.keys())) - if not self.validtags: - LOG.error("Intersect of valid esd tags and user esd tags is empty") - - if set(self.validtags) != set(self.esdJSONDict.keys()): - LOG.error("invalid tags in the user esd tags") + return esdJSONDict - def process_esd(self, bigips): + def process_esd(self, bigips, esddir): + esd_json_backup = {} + esd_json_backup = copy.deepcopy(self.esd_dict) try: - dict = self.read_json() + dict = self.read_json(esddir) self.esd_dict = self.verify_esd_dict(bigips, dict) except f5_ex.esdJSONFileInvalidException: - self.esd_dict = {} + # if error happens, we set backup esd. + self.esd_dict = copy.deepcopy(esd_json_backup) + LOG.warning( + "ESD JSON File is invalid, " + "ESD JSON Dict is restored to %s", + esd_json_backup + ) raise def get_esd(self, name): diff --git a/f5_openstack_agent/lbaasv2/drivers/bigip/icontrol_driver.py b/f5_openstack_agent/lbaasv2/drivers/bigip/icontrol_driver.py index ee6f10471..1bda46a52 100644 --- a/f5_openstack_agent/lbaasv2/drivers/bigip/icontrol_driver.py +++ b/f5_openstack_agent/lbaasv2/drivers/bigip/icontrol_driver.py @@ -14,6 +14,7 @@ # limitations under the License. # +import base64 import datetime import hashlib import json @@ -334,7 +335,7 @@ class iControlDriver(LBaaSBaseDriver): def __init__(self, conf, registerOpts=True): # The registerOpts parameter allows a test to # turn off config option handling so that it can - # set the options manually instead. """ + # set the options manually instead. super(iControlDriver, self).__init__(conf) self.conf = conf if registerOpts: @@ -368,6 +369,7 @@ def __init__(self, conf, registerOpts=True): # to store the verified esd names self.esd_names = [] + self.esd_processor = None # service component managers self.tenant_manager = None @@ -389,6 +391,12 @@ def __init__(self, conf, registerOpts=True): self.pool_manager = resource_helper.BigIPResourceHelper( resource_helper.ResourceType.pool) + if self.conf.password_cipher_mode: + self.conf.icontrol_password = \ + base64.b64decode(self.conf.icontrol_password) + if self.conf.os_password: + self.conf.os_password = base64.b64decode(self.conf.os_password) + try: # debug logging of service requests recieved by driver @@ -493,6 +501,11 @@ def _init_bigip_managers(self): self.system_helper = SystemHelper() self.lbaas_builder = LBaaSBuilder(self.conf, self) + # Set esd_processor object as soon as ServiceModelAdapter and + # LBaaSBuilder class instantiated, otherwise manager RPC exception + # will break setting esd_porcessor procedure. + self.init_esd() + if self.conf.f5_global_routed_mode: self.network_builder = None else: @@ -668,7 +681,7 @@ def _init_errored_bigips(self): raise def _open_bigip(self, hostname): - # Open bigip connection """ + # Open bigip connection try: bigip = self.__bigips[hostname] if bigip.status not in ['creating', 'error']: @@ -824,27 +837,37 @@ def _post_init(self): if self.network_builder: self.network_builder.post_init() - # read enhanced services definitions + self._set_agent_status(False) + + def init_esd(self): + # read all esd file from esd dir + # init esd object in lbaas_builder + # init esd object in service_adapter esd_dir = os.path.join(self.get_config_dir(), 'esd') - esd = EsdTagProcessor(esd_dir) + # EsdTagProcessor is a singleton, so nothing new + self.esd_processor = EsdTagProcessor() try: - esd.process_esd(self.get_all_bigips()) - self.lbaas_builder.init_esd(esd) - self.service_adapter.init_esd(esd) - - LOG.debug('esd details here after process_esd(): ') - LOG.debug(esd) - self.esd_names = esd.esd_dict.keys() or [] - LOG.debug('##### self.esd_names obtainded here:') - LOG.debug(self.esd_names) + self.esd_processor.process_esd(self.get_all_bigips(), esd_dir) + self.lbaas_builder.init_esd(self.esd_processor) + self.service_adapter.init_esd(self.esd_processor) + except f5ex.esdJSONFileInvalidException as err: LOG.error("unable to initialize ESD. Error: %s.", err.message) - self._set_agent_status(False) + except IOError as ioe: + LOG.error("unable to process ESD file. Error: %s.", ioe.message) + except Exception as exc: + LOG.error("unknown Error happens. Error: %s.", exc.message) + + LOG.debug('ESD details here after process_esd(): ') + LOG.debug(self.esd_processor) + self.esd_names = self.esd_processor.esd_dict.keys() or [] + LOG.debug('self.esd_names obtainded here:') + LOG.debug(self.esd_names) def _validate_ha(self, bigip): # if there was only one address supplied and # this is not a standalone device, get the - # devices trusted by this device. """ + # devices trusted by this device. device_group_name = None if self.conf.f5_ha_type == 'standalone': if len(self.hostnames) != 1: diff --git a/f5_openstack_agent/lbaasv2/drivers/bigip/l7policy_adapter.py b/f5_openstack_agent/lbaasv2/drivers/bigip/l7policy_adapter.py index 63ccd257d..96cfa7f62 100644 --- a/f5_openstack_agent/lbaasv2/drivers/bigip/l7policy_adapter.py +++ b/f5_openstack_agent/lbaasv2/drivers/bigip/l7policy_adapter.py @@ -74,7 +74,140 @@ def __init__(self, condition, cond_name): if condition['invert']: setattr(self, 'not', condition['invert']) self.__dict__.update(cond_type_map[condition['type']]) - setattr(self, self.cond_comp_type_map[condition['compare_type']], True) + if condition['compare_type'] in self.cond_comp_type_map: + setattr(self, + self.cond_comp_type_map[condition['compare_type']], + True) + setattr(self, "not_regex", True) + + +class iRule(object): + """Describes a single iRule for REGEX l7policy""" + + action_mapping = { + "REDIRECT_TO_POOL": '''when HTTP_REQUEST { + if {[HTTP::%s] matches_regex "%s"} { + pool %s + } + }''', + "REDIRECT_TO_URL": '''when HTTP_REQUEST { + if {[HTTP::%s] matches_regex "%s"} { + HTTP::redirect "%s" + } + }''', + "REJECT": '''when HTTP_REQUEST { + if {[HTTP::%s] matches_regex "%s"} { + drop + } + } ''' + } + + file_action_mapping = { + "REDIRECT_TO_POOL": '''when HTTP_REQUEST { + if {[HTTP::%s] ends_with "%s"} { + pool %s + } + }''', + "REDIRECT_TO_URL": '''when HTTP_REQUEST { + if {[HTTP::%s] ends_with "%s"} { + HTTP::redirect "%s" + } + }''', + "REJECT": '''when HTTP_REQUEST { + if {[HTTP::%s] ends_with "%s"} { + drop + } + } ''' + } + + type_mapping = { + "HOST_NAME": "host", + "PATH": "path", + "FILE_TYPE": "uri", + "HEADER": "header %s", + "COOKIE": "cookie %s" + } + + def __init__(self, policy): + self.listener = policy["listener_id"] + self.action = policy["action"] + self.action_pool = policy.get("redirect_pool_id") + self.action_uri = policy.get("redirect_url") + self.value = None + self.type = None + self.apiAnonymous = None + self.key = None + self.template = None + + def adapt_regex_to_irule( + self, policy, service, partition, env_prefix): + """Adapt OpenStack rules into conditions and actions.""" + irule_list = [] + for os_rule_dict in policy['rules']: + rule_id = os_rule_dict['id'] + os_rule = self._get_l7rule(rule_id, service) + if os_rule['provisioning_status'] != 'PENDING_DELETE' and \ + os_rule['admin_state_up']: + if os_rule['compare_type'] == "REGEX": + self.value = os_rule["value"] + self.key = os_rule.get("key") + + if os_rule["type"] in ("HEADER", "COOKIE"): + self.type = self.type_mapping[ + os_rule["type"]] % self.key + else: + self.type = self.type_mapping[os_rule["type"]] + + if os_rule["type"] == "FILE_TYPE": + self.template = self.file_action_mapping[self.action] + else: + self.template = self.action_mapping[self.action] + + irule_object = self._l7rule_to_irule( + partition, + env_prefix, + rule_id) + + irule_list.append(irule_object) + return irule_list + + def _l7rule_to_irule(self, partition, env_prefix, rule_id): + irule_object = {} + + if self.action == "REDIRECT_TO_POOL": + pool_name = self._get_pool_name( + partition, env_prefix, self.action_pool) + self.apiAnonymous = self.template % (self.type, + self.value, + pool_name) + if self.action == "REDIRECT_TO_URL": + self.apiAnonymous = self.template % (self.type, + self.value, + self.action_uri) + if self.action == "REJECT": + self.apiAnonymous = self.template % (self.type, + self.value) + irule_object["listener"] = self.listener + irule_object["apiAnonymous"] = self.apiAnonymous + # irule_object["action_pool"] = self.action_pool + # irule_object["action_uri"] = self.action_uri + irule_object["partition"] = partition + irule_object["name"] = "irule_" + rule_id + irule_object["fullPath"] = self._get_fullPath(partition, rule_id) + return irule_object + + def _get_l7rule(self, rule_id, service): + """Get rule dict from service list.""" + for rule in service['l7rules']: + if rule['id'] == rule_id: + return rule + + def _get_pool_name(self, partition, env_prefix, action_value): + """Construct pool name from partition and OpenStack pool name.""" + return '/{0}/{1}{2}'.format(partition, env_prefix, action_value) + + def _get_fullPath(self, partition, name): + return '/{0}/{1}'.format(partition, name) class Rule(object): @@ -96,7 +229,8 @@ def _adapt_rule_to_conditions_and_actions( if os_rule['provisioning_status'] != 'PENDING_DELETE' and \ os_rule['admin_state_up']: cond = Condition(os_rule, str(idx)) - self.conditions.append(cond.__dict__) + if cond.__dict__.get("not_regex", False): + self.conditions.append(cond.__dict__) act_type, act_val = self._get_action_and_value(policy['id'], service) action = Action(act_type, '0', partition, env_prefix, act_val) self.actions.append(action.__dict__) @@ -137,12 +271,16 @@ class L7PolicyServiceAdapter(ServiceModelAdapter): """Map OpenStack policies and rules to policy and rules on device.""" def _adapt_policies_to_rules(self): """OS Policies are translated into Rules on the device.""" + self.iRules = [] for policy in self.service['l7policies']: if policy['provisioning_status'] != 'PENDING_DELETE' and \ policy['admin_state_up']: bigip_rule = Rule( policy, self.service, self.folder, self.prefix) self.policy_dict['rules'].append(bigip_rule.__dict__) + irule = iRule(policy) + self.iRules += irule.adapt_regex_to_irule( + policy, self.service, self.folder, self.prefix) if not self.policy_dict['rules']: msg = 'All policies were in a PENDING_DELETE or admin_state ' \ 'DOWN. Deleting wrapper_policy.' diff --git a/f5_openstack_agent/lbaasv2/drivers/bigip/l7policy_service.py b/f5_openstack_agent/lbaasv2/drivers/bigip/l7policy_service.py index f57f51b20..2603bc1c4 100644 --- a/f5_openstack_agent/lbaasv2/drivers/bigip/l7policy_service.py +++ b/f5_openstack_agent/lbaasv2/drivers/bigip/l7policy_service.py @@ -35,6 +35,7 @@ class L7PolicyService(object): def __init__(self, conf): self.conf = conf self.policy_helper = BigIPResourceHelper(ResourceType.l7policy) + self.rule_helper = BigIPResourceHelper(ResourceType.rule) def create_l7policy(self, f5_l7policy, bigips): LOG.debug("L7PolicyService: create_l7policy") @@ -102,7 +103,12 @@ def build_policy(self, l7policy, lbaas_service): l7policy_adapter = L7PolicyServiceAdapter(self.conf) - os_policies = {'l7rules': [], 'l7policies': [], 'f5_policy': {}} + os_policies = { + 'l7rules': [], + 'l7policies': [], + 'f5_policy': {}, + 'iRules': [] + } # get all policies and rules for listener referenced by this policy listener = lbaas_service.get_listener(l7policy['listener_id']) @@ -117,6 +123,74 @@ def build_policy(self, l7policy, lbaas_service): if os_policies['l7policies']: os_policies['f5_policy'] = l7policy_adapter.translate(os_policies) + if getattr(l7policy_adapter, 'iRules', None): + os_policies['iRules'] = l7policy_adapter.iRules LOG.debug(pprint.pformat(os_policies, indent=2)) return os_policies + + def create_irule(self, irules, bigips): + LOG.debug("L7PolicyService: create_iRule") + + error = None + for bigip in bigips: + for rule in irules: + try: + self.rule_helper.create(bigip, rule) + except HTTPError as err: + status_code = err.response.status_code + if status_code == 409: + LOG.debug( + "L7 rule (REGEX irule) already exists...updating") + try: + self.rule_helper.update(bigip, rule) + except Exception as err: + error = f5_ex.L7PolicyUpdateException(err.message) + else: + error = f5_ex.L7PolicyCreationException(err.message) + except Exception as err: + error = f5_ex.L7PolicyCreationException(err.message) + + if error: + LOG.error("L7 rule (REGEX irule) creation error: %s" % + error.message) + + return error + + def delete_irule(self, delete_irules, bigips): + LOG.debug("L7PolicyService:delete_iRule") + + error = False + for bigip in bigips: + for rule in delete_irules: + try: + self.rule_helper.delete(bigip, + name=rule.get('name'), + partition=rule.get('partition')) + except HTTPError as err: + status_code = err.response.status_code + if status_code == 404: + LOG.warn( + "Deleting L7 policy (iRule) " + + "failed...not found: %s", + err.message + ) + elif status_code == 400: + LOG.debug( + "Deleting L7 policy (iRule) failed...unknown " + "client error: %s", err.message + ) + error = f5_ex.L7PolicyDeleteException(err.message) + else: + error = f5_ex.L7PolicyDeleteException(err.message) + except Exception as err: + LOG.exception(err) + error = f5_ex.L7PolicyDeleteException(err.message) + + if error: + LOG.error( + "L7 Policy (iRule) deletion error: %s", + error.message + ) + + return error diff --git a/f5_openstack_agent/lbaasv2/drivers/bigip/lbaas_builder.py b/f5_openstack_agent/lbaasv2/drivers/bigip/lbaas_builder.py index 8f41bfdf2..1a21845c0 100644 --- a/f5_openstack_agent/lbaasv2/drivers/bigip/lbaas_builder.py +++ b/f5_openstack_agent/lbaasv2/drivers/bigip/lbaas_builder.py @@ -176,6 +176,7 @@ def _assure_listeners_created(self, service): pools = service.get("pools", list()) l7policies = service.get("l7policies", list()) l7rules = service.get("l7policy_rules", list()) + irules = service.get("irules", dict()) bigips = self.driver.get_config_bigips() for listener in listeners: @@ -187,6 +188,7 @@ def _assure_listeners_created(self, service): "pools": pools, "l7policies": l7policies, "l7policy_rules": l7rules, + "irules": irules.get(listener["id"], list()), "networks": networks} # create_listener() will do an update if VS exists @@ -432,6 +434,7 @@ def _assure_l7policies_created(self, service): listener_policy_map = dict() bigips = self.driver.get_config_bigips() lbaas_service = LbaasServiceObject(service) + service['irules'] = dict() l7policies = service['l7policies'] LOG.debug("L7 debug: processing policies: %s", l7policies) @@ -454,6 +457,10 @@ def _assure_l7policies_created(self, service): error = self.l7service.create_l7policy( policy['f5_policy'], bigips) + if policy.get('iRules'): + error = self.l7service.create_irule( + policy['iRules'], bigips) + for p in service['l7policies']: if self._is_not_pending_delete(p): if not error: @@ -466,6 +473,7 @@ def _assure_l7policies_created(self, service): listener = lbaas_service.get_listener(listener_id) if listener: listener['f5_policy'] = policy['f5_policy'] + service['irules'][listener_id] = policy.get('iRules') else: loadbalancer['provisioning_status'] = \ constants_v2.F5_ERROR @@ -511,6 +519,24 @@ def _assure_l7policies_deleted(self, service): error = self.l7service.delete_l7policy( policy['f5_policy'], bigips) + delete_irules = [] + for rule in (policy['l7rules']): + if rule.get('provisioning_status') == 'PENDING_DELETE': + tenant_id = rule.get('tenant_id') + partition = self.service_adapter.get_folder_name( + tenant_id + ) + name = 'irule_' + rule.get('id') + delete_rule = { + "name": name, + "partition": partition + } + delete_irules.append(delete_rule) + + if delete_irules: + error = self.l7service.delete_irule( + delete_irules, bigips) + for p in policy['l7policies']: if self._is_not_pending_delete(p): if not error: diff --git a/f5_openstack_agent/lbaasv2/drivers/bigip/refersh_esd.py b/f5_openstack_agent/lbaasv2/drivers/bigip/refersh_esd.py new file mode 100755 index 000000000..d25de28c4 --- /dev/null +++ b/f5_openstack_agent/lbaasv2/drivers/bigip/refersh_esd.py @@ -0,0 +1,31 @@ +#!/usr/bin/python + +# Copyright (c) 2014-2018, F5 Networks, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +import os +import signal +import subprocess + + +def refresh_esd(): + + cmd = ['pgrep', '-f', 'f5-oslbaasv2-ag'] + child_process = subprocess.Popen(cmd, stdout=subprocess.PIPE) + resp = child_process.communicate()[0].split() + + for pid in resp: + os.kill(int(pid), signal.SIGUSR1) + print("Refreshed ESD for f5-oslbaasv2-agent (PID): %s." % pid) diff --git a/f5_openstack_agent/lbaasv2/drivers/bigip/service_adapter.py b/f5_openstack_agent/lbaasv2/drivers/bigip/service_adapter.py index b27415993..f141faf56 100644 --- a/f5_openstack_agent/lbaasv2/drivers/bigip/service_adapter.py +++ b/f5_openstack_agent/lbaasv2/drivers/bigip/service_adapter.py @@ -123,12 +123,17 @@ def get_virtual(self, service): listener["session_persistence"] = pool["session_persistence"] listener_policies = self.get_listener_policies(service) + listener_irules = self.get_listener_irules(service) vip = self._map_virtual(loadbalancer, listener, pool=pool, - policies=listener_policies) + policies=listener_policies, + irules=list(listener_irules)) return vip + def get_listener_irules(self, service): + return [r["name"] for r in service.get("irules", [])] + def get_listener_policies(self, service): """Return a map of listener L7 policy ids to a list of L7 rules.""" lbaas_service = LbaasServiceObject(service) @@ -424,7 +429,8 @@ def _get_lb_method(self, method): else: return 'round-robin' - def _map_virtual(self, loadbalancer, listener, pool=None, policies=None): + def _map_virtual(self, loadbalancer, listener, + pool=None, policies=None, irules=[]): if policies: LOG.debug("L7_debug: policies: %s", policies) vip = self._init_virtual_name(loadbalancer, listener) @@ -473,7 +479,9 @@ def _map_virtual(self, loadbalancer, listener, pool=None, policies=None): self._add_vlan_and_snat(listener, vip) self._add_profiles_session_persistence(listener, pool, vip) - vip['rules'] = list() + existed_irules = vip.get('rules', []) + irules += existed_irules + vip['rules'] = irules vip['policies'] = list() if policies: self._apply_l7_and_esd_policies(listener, policies, vip) @@ -679,9 +687,8 @@ def _apply_fastl4_esd(self, vip, esd): vip['fallbackPersistence'] = esd['lbaas_fallback_persist'] # iRules - vip['rules'] = list() if 'lbaas_irule' in esd: - irules = [] + irules = vip.get('rules', []) for irule in esd['lbaas_irule']: irules.append('/Common/' + irule) vip['rules'] = irules @@ -759,9 +766,8 @@ def _apply_esd(self, vip, esd): vip['fallbackPersistence'] = esd['lbaas_fallback_persist'] # iRules - vip['rules'] = list() if 'lbaas_irule' in esd: - irules = [] + irules = vip.get('rules', []) for irule in esd['lbaas_irule']: irules.append('/Common/' + irule) vip['rules'] = irules diff --git a/f5_openstack_agent/lbaasv2/drivers/bigip/service_launcher.py b/f5_openstack_agent/lbaasv2/drivers/bigip/service_launcher.py new file mode 100644 index 000000000..9474c307a --- /dev/null +++ b/f5_openstack_agent/lbaasv2/drivers/bigip/service_launcher.py @@ -0,0 +1,43 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2014-2018, F5 Networks, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +from oslo_service import service +from oslo_service import systemd + + +class F5ServiceLauncher(service.ServiceLauncher): + + def __init__(self, conf): + super(F5ServiceLauncher, self).__init__(conf) + + def handle_signal(self): + self.signal_handler.add_handler('SIGTERM', self._graceful_shutdown) + self.signal_handler.add_handler('SIGINT', self._fast_exit) + self.signal_handler.add_handler('SIGHUP', self._reload_service) + self.signal_handler.add_handler('SIGALRM', self._on_timeout_exit) + + def wait(self): + systemd.notify_once() + while True: + self.handle_signal() + status, signo = self._wait_for_exit_or_signal() + if not service._is_sighup_and_daemon(signo): + break + self.restart() + + self.services.wait() + return status