diff --git a/gbpservice/neutron/services/servicechain/plugins/ncp/node_drivers/nfp_node_driver.py b/gbpservice/neutron/services/servicechain/plugins/ncp/node_drivers/nfp_node_driver.py index 079764f1d8..4af0c717e2 100644 --- a/gbpservice/neutron/services/servicechain/plugins/ncp/node_drivers/nfp_node_driver.py +++ b/gbpservice/neutron/services/servicechain/plugins/ncp/node_drivers/nfp_node_driver.py @@ -14,6 +14,9 @@ # limitations under the License. import eventlet +eventlet.monkey_patch() + +from eventlet import greenpool from keystoneclient import exceptions as k_exceptions from keystoneclient.v2_0 import client as keyclient from neutron._i18n import _LE @@ -230,6 +233,9 @@ class NFPNodeDriver(driver_base.NodeDriverBase): def __init__(self): super(NFPNodeDriver, self).__init__() self._lbaas_plugin = None + self.thread_pool = greenpool.GreenPool(10) + self.active_threads = [] + self.sc_node_count = 0 @property def name(self): @@ -376,9 +382,20 @@ def create(self, context): self._set_node_instance_network_function_map( context.plugin_session, context.current_node['id'], context.instance['id'], network_function_id) - self._wait_for_network_function_operation_completion( + + # Check for NF status in a separate thread + gth = self.thread_pool.spawn( + self._wait_for_network_function_operation_completion, context, network_function_id, operation='create') + self.active_threads.append(gth) + + # At last wait for the threads to complete, success/failure/timeout + if len(self.active_threads) == self.sc_node_count: + for gth in self.active_threads: + gth.wait() + self.active_threads = [] + def update(self, context): context._plugin_context = self._get_resource_owner_context( context._plugin_context) @@ -657,23 +674,49 @@ def _get_service_targets(self, context): {'service_type': service_type}) raise Exception("Service Targets are not created for the Node") - service_target_info = {'provider_ports': [], 'provider_pts': [], - 'consumer_ports': [], 'consumer_pts': []} + service_target_info = { + 'provider_ports': [], + 'provider_subnet': None, + 'provider_pts': [], + 'provider_pt_objs': [], + 'provider_ptg': [], + 'consumer_ports': [], + 'consumer_subnet': None, + 'consumer_pts': [], + 'consumer_pt_objs': [], + 'consumer_ptg': []} + for service_target in provider_service_targets: policy_target = context.gbp_plugin.get_policy_target( context.plugin_context, service_target.policy_target_id) + policy_target_group = context.gbp_plugin.get_policy_target_group( + context.plugin_context, policy_target['policy_target_group_id']) port = context.core_plugin.get_port( context.plugin_context, policy_target['port_id']) + port['ip_address'] = port['fixed_ips'][0]['ip_address'] + subnet = context.core_plugin.get_subnet( + context.plugin_context, port['fixed_ips'][0]['subnet_id']) service_target_info['provider_ports'].append(port) + service_target_info['provider_subnet'] = subnet service_target_info['provider_pts'].append(policy_target['id']) + service_target_info['provider_pt_objs'].append(policy_target) + service_target_info['provider_ptg'].append(policy_target_group) for service_target in consumer_service_targets: policy_target = context.gbp_plugin.get_policy_target( context.plugin_context, service_target.policy_target_id) + policy_target_group = context.gbp_plugin.get_policy_target_group( + context.plugin_context, policy_target['policy_target_group_id']) port = context.core_plugin.get_port( context.plugin_context, policy_target['port_id']) + port['ip_address'] = port['fixed_ips'][0]['ip_address'] + subnet = context.core_plugin.get_subnet( + context.plugin_context, port['fixed_ips'][0]['subnet_id']) service_target_info['consumer_ports'].append(port) + service_target_info['consumer_subnet'] = subnet service_target_info['consumer_pts'].append(policy_target['id']) + service_target_info['consumer_pt_objs'].append(policy_target) + service_target_info['consumer_ptg'].append(policy_target_group) return service_target_info @@ -685,6 +728,7 @@ def _is_node_order_in_spec_supported(self, context): for spec in current_specs: node_list.extend(spec['nodes']) + self.sc_node_count = len(node_list) for node_id in node_list: node_info = context.sc_plugin.get_servicechain_node( context.plugin_context, node_id) @@ -707,9 +751,65 @@ def _is_node_order_in_spec_supported(self, context): raise InvalidNodeOrderInChain( node_order=allowed_chain_combinations) + def _get_consumers_for_provider(self, context, provider): + ''' + { + consuming_ptgs_details: [{'ptg': <>, 'subnets': <>}] + consuming_eps_details: [] + } + ''' + + consuming_ptgs_details = [] + consuming_eps_details = [] + + provided_prs_id = provider['provided_policy_rule_sets'][0] + provided_prs = context.gbp_plugin.get_policy_rule_set( + context.plugin_context, provided_prs_id) + consuming_ptg_ids = provided_prs['consuming_policy_target_groups'] + consuming_ep_ids = provided_prs['consuming_external_policies'] + + consuming_ptgs = context.gbp_plugin.get_policy_target_groups( + context.plugin_context, filters={'id':consuming_ptg_ids}) + consuming_eps_details = context.gbp_plugin.get_external_policies( + context.plugin_context, filters={'id': consuming_ep_ids}) + + for ptg in consuming_ptgs: + subnet_ids = ptg['subnets'] + subnets = context.core_plugin.get_subnets(context.plugin_context, filters={'id':subnet_ids}) + consuming_ptgs_details.append({'ptg':ptg, 'subnets':subnets}) + + return consuming_ptgs_details, consuming_eps_details + + def _create_network_function(self, context): + """ + nfp_create_nf_data :- + + {'resource_owner_context': <>, + 'service_chain_instance': <>, + 'service_chain_node': <>, + 'service_profile': <>, + 'service_config': context.current_node.get('config'), + 'provider': {'pt':<>, 'ptg':<>, 'port':<>, 'subnet':<>}, + 'consumer': {'pt':<>, 'ptg':<>, 'port':<>, 'subnet':<>}, + 'management': {'pt':<>, 'ptg':<>, 'port':<>, 'subnet':<>}, + 'management_ptg_id': <>, + 'network_function_mode': nfp_constants.GBP_MODE, + 'tenant_id': <>, + 'consuming_ptgs_details': [], + 'consuming_eps_details': [] + } + + """ + nfp_create_nf_data = {} + sc_instance = context.instance service_targets = self._get_service_targets(context) + + consuming_ptgs_details, consuming_eps_details = \ + self._get_consumers_for_provider(context, + service_targets['provider_ptg'][0]) + if context.current_profile['service_type'] == pconst.LOADBALANCER: config_param_values = sc_instance.get('config_param_values', {}) if config_param_values: @@ -727,35 +827,58 @@ def _create_network_function(self, context): context.core_plugin.update_port( context.plugin_context, provider_port['id'], port) - port_info = [] - if service_targets.get('provider_pts'): - # Device case, for Base mode ports won't be available. - port_info = [ - { - 'id': service_targets['provider_pts'][0], - 'port_model': nfp_constants.GBP_PORT, - 'port_classification': nfp_constants.PROVIDER, - } - ] - if service_targets.get('consumer_ports'): - port_info.append({ - 'id': service_targets['consumer_pts'][0], - 'port_model': nfp_constants.GBP_PORT, - 'port_classification': nfp_constants.CONSUMER, - }) - network_function = { - 'tenant_id': context.provider['tenant_id'], - 'service_chain_id': sc_instance['id'], - 'service_id': context.current_node['id'], - 'service_profile_id': context.current_profile['id'], - 'management_ptg_id': sc_instance['management_ptg_id'], + provider = { + 'pt': service_targets.get('provider_pt_objs', [None])[0], + 'ptg': service_targets.get('provider_ptg', [None])[0], + 'port': service_targets.get('provider_ports', [None])[0], + 'subnet': service_targets.get('provider_subnet', None), + 'port_model': nfp_constants.GBP_PORT, + 'port_classification': nfp_constants.PROVIDER} + + consumer_pt = None + consumer_ptg = None + consumer_ports = None + + if service_targets['consumer_pt_objs']: + consumer_pt = service_targets.get('consumer_pt_objs', [None])[0] + if service_targets['consumer_ptg']: + consumer_ptg = service_targets.get('consumer_ptg', [None])[0] + if service_targets['consumer_ports']: + consumer_ports = service_targets.get('consumer_ports', [None])[0] + + consumer = { + 'pt': consumer_pt, + 'ptg': consumer_ptg, + 'port': consumer_ports, + 'subnet': service_targets.get('consumer_subnet', None), + 'port_model': nfp_constants.GBP_PORT, + 'port_classification': nfp_constants.CONSUMER} + + management = { + 'pt': None, + 'ptg': None, + 'port': None, + 'subnet': None, + 'port_model': nfp_constants.GBP_NETWORK, + 'port_classification': nfp_constants.MANAGEMENT} + + nfp_create_nf_data = { + 'resource_owner_context': context._plugin_context.to_dict(), + 'service_chain_instance': sc_instance, + 'service_chain_node': context.current_node, + 'service_profile': context.current_profile, 'service_config': context.current_node.get('config'), - 'port_info': port_info, + 'provider': provider, + 'consumer': consumer, + 'management': management, + 'management_ptg_id': sc_instance['management_ptg_id'], 'network_function_mode': nfp_constants.GBP_MODE, - } + 'tenant_id': context.provider['tenant_id'], + 'consuming_ptgs_details': consuming_ptgs_details, + 'consuming_eps_details': consuming_eps_details} return self.nfp_notifier.create_network_function( - context.plugin_context, network_function=network_function)['id'] + context.plugin_context, network_function=nfp_create_nf_data)['id'] def _set_node_instance_network_function_map( self, session, sc_node_id, sc_instance_id, network_function_id): diff --git a/gbpservice/neutron/tests/unit/nfp/config_orchestrator/agent/__init__.py b/gbpservice/neutron/tests/unit/nfp/config_orchestrator/agent/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/gbpservice/neutron/tests/unit/nfp/config_orchestrator/agent/test_config_agent.py b/gbpservice/neutron/tests/unit/nfp/config_orchestrator/agent/test_config_agent.py deleted file mode 100644 index 83894a2ebd..0000000000 --- a/gbpservice/neutron/tests/unit/nfp/config_orchestrator/agent/test_config_agent.py +++ /dev/null @@ -1,653 +0,0 @@ -# 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 gbpservice.nfp.config_orchestrator.agent import firewall -from gbpservice.nfp.config_orchestrator.agent import loadbalancer -from gbpservice.nfp.config_orchestrator.agent import notification_handler -from gbpservice.nfp.config_orchestrator.agent import vpn -import mock -from neutron import context as ctx -import unittest -import uuid - - -class TestContext(object): - - def get_context(self): - try: - return ctx.Context('some_user', 'some_tenant') - except Exception: - return ctx.Context('some_user', 'some_tenant') - - -class Conf(object): - - class Test_RPC(object): - - def __init__(self): - self.topic = 'xyz_topic' - - def __init__(self): - self.host = 'dummy_host' - self.backend = 'rpc' - self.RPC = self.Test_RPC() - - -class RpcMethods(object): - - def cast(self, context, method, **kwargs): - return - - def call(self, context, method, **kwargs): - return {} - - -def call_network_function_info(): - data = {'network_function': { - 'id': str(uuid.uuid4()), - 'description': {} - }} - return data - - -class GeneralConfigStructure(object): - - def _check_general_structure(self, request_data, rsrc_name, resource=None): - flag = 0 - if all(key in request_data for key in ["info", "config"]): - header_data = request_data['info'] - if all(key in header_data for key in ["context", "service_type", - "service_vendor"]): - if not self.\ - _check_resource_header_data(rsrc_name, - header_data["context"], - resource): - return False - data = request_data['config'] - for ele in data: - if all(key in ele for key in ["resource", - "resource_data"]): - if self._check_resource_structure(rsrc_name, - ele['resource_data'], - resource): - flag = 1 - else: - flag = 0 - else: - flag = 0 - if flag == 1: - return True - return False - - def verify_firewall_structure(self, blob_data, resource=None): - if all(k in blob_data for k in ["neutron_context", "host", - "firewall"]): - context = blob_data['neutron_context'] - try: - if context['service_info']: - data = context['service_info'] - if all(k in data for k in ["firewalls", - "firewall_policies", - "firewall_rules"]): - return True - except AttributeError: - return False - return False - - def verify_firewall_header_data(self, data, resource=None): - if all(k in data for k in ["neutron_context", "network_function_id", - "fw_mac", "requester"]): - if data['requester'] == 'nas_service': - return True - return False - - def verify_loadbalancer_header_data(self, data, resource=None): - if all(k in data for k in ["neutron_context", "requester"]): - if resource == "vip": - if not all(k in data for k in ["network_function_id", - "vip_id"]): - return False - if data['requester'] == 'nas_service': - return True - return False - - def verify_vpn_header_data(self, data, resource=None): - if all(k in data for k in ["neutron_context", "requester"]): - if resource == "ipsec_site_connection": - if not all(k in data for k in ["network_function_id", - "ipsec_site_connection_id"]): - return False - if data['requester'] == 'nas_service': - return True - return False - - def verify_loadbalancer_structure(self, blob_data, resource): - if all(k in blob_data for k in ["neutron_context", resource]): - context = blob_data["neutron_context"] - try: - if context['service_info']: - data = context['service_info'] - if all(k in data for k in ["pools", "vips", "members", - "health_monitors", - "subnets", "ports"]): - return True - except AttributeError: - return False - return False - - def verify_vpn_structure(self, blob_data, resource): - if all(k in blob_data for k in ["neutron_context", "resource", - "rsrc_id", "reason"]): - context = blob_data["neutron_context"] - try: - if context['service_info']: - data = context['service_info'] - if resource.lower() == "vpn_service": - if all(k in data for k in ["vpnservices"]): - return True - elif resource.lower() == "ipsec_site_connection": - if all(k in data for k in ["vpnservices", - "ikepolicies", - "ipsecpolicies", - "ipsec_site_conns", - "subnets", - "routers"]): - return True - except AttributeError: - return False - return False - - def _check_resource_structure(self, rsrc_name, data, resource=None): - mod = self - mod_method = getattr(mod, "verify_%s_structure" % rsrc_name) - return mod_method(data, resource) - - def _check_resource_header_data(self, rsrc_name, data, resource): - mod = self - mod_method = getattr(mod, "verify_%s_header_data" % rsrc_name) - return mod_method(data, resource) - - -class FirewallTestCase(unittest.TestCase): - - def setUp(self): - self.conf = Conf() - self.fw_handler = firewall.FwAgent(self.conf, 'sc') - self.context = TestContext().get_context() - self.rpc_methods = RpcMethods() - self.fw = self._firewall_data() - self.host = 'host' - import_path = ("neutron_fwaas.db.firewall.firewall_db." - "Firewall_db_mixin") - self.import_fw_api = import_path + '.get_firewalls' - self.import_fwp_api = import_path + '.get_firewall_policies' - self.import_fwr_api = import_path + '.get_firewall_rules' - self.import_lib = 'gbpservice.nfp.lib.transport' - self._call = 'oslo_messaging.rpc.client._CallContext.call' - - def _firewall_data(self): - return {'tenant_id': str(uuid.uuid4()), - 'description': str({'network_function_id': str(uuid.uuid4())}), - 'firewall_policy_id': str(uuid.uuid4()) - } - - def _cast_firewall(self, conf, context, body, - method_type, device_config=False, - network_function_event=False): - g_cnfg = GeneralConfigStructure() - self.assertTrue(g_cnfg._check_general_structure(body, 'firewall')) - - def _call_to_get_network_function_desc(self, context, method, **kwargs): - data = call_network_function_info() - data['network_function']['description'] = "\n" + str( - {'provider_ptg_info': [str(uuid.uuid4())], - 'service_vendor': 'xyz'}) - return data - - def test_create_firewall(self): - import_send = self.import_lib + '.send_request_to_configurator' - with mock.patch(self.import_fw_api) as gfw,\ - mock.patch(self.import_fwp_api) as gfwp,\ - mock.patch(self.import_fwr_api) as gfwr,\ - mock.patch(self._call) as mock_call,\ - mock.patch(import_send) as mock_send: - gfw.return_value = [] - gfwp.return_value = [] - gfwr.return_value = [] - mock_call.side_effect = self._call_to_get_network_function_desc - mock_send.side_effect = self._cast_firewall - self.fw_handler.create_firewall(self.context, self.fw, self.host) - - def test_delete_firewall(self): - import_send = self.import_lib + '.send_request_to_configurator' - with mock.patch(self.import_fw_api) as gfw,\ - mock.patch(self.import_fwp_api) as gfwp,\ - mock.patch(self.import_fwr_api) as gfwr,\ - mock.patch(self._call) as mock_call,\ - mock.patch(import_send) as mock_send: - gfw.return_value = [] - gfwp.return_value = [] - gfwr.return_value = [] - mock_call.side_effect = self._call_to_get_network_function_desc - mock_send.side_effect = self._cast_firewall - self.fw_handler.delete_firewall(self.context, self.fw, self.host) - - -class LoadBalanceTestCase(unittest.TestCase): - - def setUp(self): - self.conf = Conf() - self.lb_handler = loadbalancer.LbAgent(self.conf, 'sc') - self.context = TestContext().get_context() - import_path = ("neutron_lbaas.db.loadbalancer.loadbalancer_db." - "LoadBalancerPluginDb") - self.import_gp_api = import_path + '.get_pools' - self.import_gv_api = import_path + '.get_vips' - self.import_gm_api = import_path + '.get_members' - self.import_ghm_api = import_path + '.get_health_monitors' - self.import_lib = 'gbpservice.nfp.lib.transport' - self._call = 'oslo_messaging.rpc.client._CallContext.call' - self._get_pool = import_path + '.get_pool' - - def _cast_loadbalancer(self, conf, context, body, - method_type, device_config=False, - network_function_event=False): - g_cnfg = GeneralConfigStructure() - try: - resource = body['config'][0]['resource'] - if resource == 'pool_health_monitor': - resource = 'health_monitor' - self.assertTrue(g_cnfg._check_general_structure( - body, 'loadbalancer', resource)) - except Exception: - self.assertTrue(False) - - def _call_data(self, context, method, **kwargs): - if method.lower() == "get_network_function_details": - data = call_network_function_info() - data['network_function']['description'] = "\n" + str( - {'service_vendor': 'xyz'}) - return data - - return [] - - def _loadbalancer_data(self, resource): - data = {'tenant_id': str(uuid.uuid4()), - 'id': str(uuid.uuid4()) - } - if resource.lower() not in ['member', 'health_monitor']: - desc = str({'network_function_id': str(uuid.uuid4())}) - data.update({'description': desc}) - if resource.lower() == 'vip': - data.update({'pool_id': str(uuid.uuid4())}) - return data - - def _get_mocked_pool(self, context, pool_id): - return {'id': pool_id, - 'description': str({'network_function_id': str(uuid.uuid4())})} - - def test_create_vip(self): - import_send = self.import_lib + '.send_request_to_configurator' - with mock.patch(self.import_gp_api) as gp,\ - mock.patch(self.import_gv_api) as gv,\ - mock.patch(self.import_gm_api) as gm,\ - mock.patch(self.import_ghm_api) as ghm,\ - mock.patch(self._call) as mock_call,\ - mock.patch(import_send) as mock_send: - gp.return_value = [] - gv.return_value = [] - gm.return_value = [] - ghm.return_value = [] - mock_call.side_effect = self._call_data - mock_send.side_effect = self._cast_loadbalancer - vip = self._loadbalancer_data('vip') - self.lb_handler.create_vip(self.context, vip) - - def test_update_vip(self): - import_send = self.import_lib + '.send_request_to_configurator' - with mock.patch(self.import_gp_api) as gp,\ - mock.patch(self.import_gv_api) as gv,\ - mock.patch(self.import_gm_api) as gm,\ - mock.patch(self.import_ghm_api) as ghm,\ - mock.patch(self._call) as mock_call,\ - mock.patch(import_send) as mock_send: - gp.return_value = [] - gv.return_value = [] - gm.return_value = [] - ghm.return_value = [] - mock_call.side_effect = self._call_data - mock_send.side_effect = self._cast_loadbalancer - old_vip = self._loadbalancer_data('vip') - vip = self._loadbalancer_data('vip') - self.lb_handler.update_vip(self.context, old_vip, vip) - - def test_delete_vip(self): - import_send = self.import_lib + '.send_request_to_configurator' - with mock.patch(self.import_gp_api) as gp,\ - mock.patch(self.import_gv_api) as gv,\ - mock.patch(self.import_gm_api) as gm,\ - mock.patch(self.import_ghm_api) as ghm,\ - mock.patch(self._call) as mock_call,\ - mock.patch(import_send) as mock_send: - gp.return_value = [] - gv.return_value = [] - gm.return_value = [] - ghm.return_value = [] - mock_call.side_effect = self._call_data - mock_send.side_effect = self._cast_loadbalancer - vip = self._loadbalancer_data('vip') - self.lb_handler.delete_vip(self.context, vip) - - def test_create_pool(self): - import_send = self.import_lib + '.send_request_to_configurator' - with mock.patch(self.import_gp_api) as gp,\ - mock.patch(self.import_gv_api) as gv,\ - mock.patch(self.import_gm_api) as gm,\ - mock.patch(self.import_ghm_api) as ghm,\ - mock.patch(self._call) as mock_call,\ - mock.patch(import_send) as mock_send: - gp.return_value = [] - gv.return_value = [] - gm.return_value = [] - ghm.return_value = [] - mock_call.side_effect = self._call_data - mock_send.side_effect = self._cast_loadbalancer - pool = self._loadbalancer_data('pool') - driver_name = "dummy" - self.lb_handler.create_pool(self.context, pool, driver_name) - - def test_update_pool(self): - import_send = self.import_lib + '.send_request_to_configurator' - with mock.patch(self.import_gp_api) as gp,\ - mock.patch(self.import_gv_api) as gv,\ - mock.patch(self.import_gm_api) as gm,\ - mock.patch(self.import_ghm_api) as ghm,\ - mock.patch(self._call) as mock_call,\ - mock.patch(import_send) as mock_send: - gp.return_value = [] - gv.return_value = [] - gm.return_value = [] - ghm.return_value = [] - mock_call.side_effect = self._call_data - mock_send.side_effect = self._cast_loadbalancer - old_pool = self._loadbalancer_data('pool') - pool = self._loadbalancer_data('pool') - self.lb_handler.update_pool(self.context, old_pool, pool) - - def test_delete_pool(self): - import_send = self.import_lib + '.send_request_to_configurator' - with mock.patch(self.import_gp_api) as gp,\ - mock.patch(self.import_gv_api) as gv,\ - mock.patch(self.import_gm_api) as gm,\ - mock.patch(self.import_ghm_api) as ghm,\ - mock.patch(self._call) as mock_call,\ - mock.patch(import_send) as mock_send: - gp.return_value = [] - gv.return_value = [] - gm.return_value = [] - ghm.return_value = [] - mock_call.side_effect = self._call_data - mock_send.side_effect = self._cast_loadbalancer - pool = self._loadbalancer_data('pool') - self.lb_handler.delete_pool(self.context, pool) - - def test_create_member(self): - import_send = self.import_lib + '.send_request_to_configurator' - with mock.patch(self.import_gp_api) as gp,\ - mock.patch(self.import_gv_api) as gv,\ - mock.patch(self.import_gm_api) as gm,\ - mock.patch(self.import_ghm_api) as ghm,\ - mock.patch(self._call) as mock_call,\ - mock.patch(self._get_pool) as mock_pool,\ - mock.patch(import_send) as mock_send: - gp.return_value = [] - gv.return_value = [] - gm.return_value = [] - ghm.return_value = [] - mock_call.side_effect = self._call_data - mock_send.side_effect = self._cast_loadbalancer - mock_pool.side_effect = self._get_mocked_pool - member = self._loadbalancer_data('member') - member.update({'pool_id': str(uuid.uuid4())}) - self.lb_handler.create_member(self.context, member) - - def test_update_member(self): - import_send = self.import_lib + '.send_request_to_configurator' - with mock.patch(self.import_gp_api) as gp,\ - mock.patch(self.import_gv_api) as gv,\ - mock.patch(self.import_gm_api) as gm,\ - mock.patch(self.import_ghm_api) as ghm,\ - mock.patch(self._call) as mock_call,\ - mock.patch(self._get_pool) as mock_pool,\ - mock.patch(import_send) as mock_send: - gp.return_value = [] - gv.return_value = [] - gm.return_value = [] - ghm.return_value = [] - mock_call.side_effect = self._call_data - mock_send.side_effect = self._cast_loadbalancer - mock_pool.side_effect = self._get_mocked_pool - old_member = self._loadbalancer_data('member') - member = self._loadbalancer_data('member') - pool_id = str(uuid.uuid4()) - old_member.update({'pool_id': pool_id}) - member.update({'pool_id': pool_id}) - self.lb_handler.update_member(self.context, old_member, member) - - def test_delete_member(self): - import_send = self.import_lib + '.send_request_to_configurator' - with mock.patch(self.import_gp_api) as gp,\ - mock.patch(self.import_gv_api) as gv,\ - mock.patch(self.import_gm_api) as gm,\ - mock.patch(self.import_ghm_api) as ghm,\ - mock.patch(self._call) as mock_call,\ - mock.patch(self._get_pool) as mock_pool,\ - mock.patch(import_send) as mock_send: - gp.return_value = [] - gv.return_value = [] - gm.return_value = [] - ghm.return_value = [] - mock_call.side_effect = self._call_data - mock_send.side_effect = self._cast_loadbalancer - mock_pool.side_effect = self._get_mocked_pool - member = self._loadbalancer_data('member') - member.update({'pool_id': str(uuid.uuid4())}) - self.lb_handler.delete_member(self.context, member) - - def test_create_pool_health_monitor(self): - import_send = self.import_lib + '.send_request_to_configurator' - with mock.patch(self.import_gp_api) as gp,\ - mock.patch(self.import_gv_api) as gv,\ - mock.patch(self.import_gm_api) as gm,\ - mock.patch(self.import_ghm_api) as ghm,\ - mock.patch(self._call) as mock_call,\ - mock.patch(self._get_pool) as mock_pool,\ - mock.patch(import_send) as mock_send: - gp.return_value = [] - gv.return_value = [] - gm.return_value = [] - ghm.return_value = [] - mock_call.side_effect = self._call_data - mock_send.side_effect = self._cast_loadbalancer - mock_pool.side_effect = self._get_mocked_pool - hm = self._loadbalancer_data('health_monitor') - pool_id = str(uuid.uuid4()) - self.lb_handler.create_pool_health_monitor( - self.context, hm, pool_id) - - def test_update_pool_health_monitor(self): - import_send = self.import_lib + '.send_request_to_configurator' - with mock.patch(self.import_gp_api) as gp,\ - mock.patch(self.import_gv_api) as gv,\ - mock.patch(self.import_gm_api) as gm,\ - mock.patch(self.import_ghm_api) as ghm,\ - mock.patch(self._call) as mock_call,\ - mock.patch(self._get_pool) as mock_pool,\ - mock.patch(import_send) as mock_send: - gp.return_value = [] - gv.return_value = [] - gm.return_value = [] - ghm.return_value = [] - mock_call.side_effect = self._call_data - mock_send.side_effect = self._cast_loadbalancer - mock_pool.side_effect = self._get_mocked_pool - old_hm = self._loadbalancer_data('health_monitor') - hm = self._loadbalancer_data('health_monitor') - pool_id = str(uuid.uuid4()) - self.lb_handler.update_pool_health_monitor( - self.context, old_hm, hm, pool_id) - - def test_delete_pool_health_monitor(self): - import_send = self.import_lib + '.send_request_to_configurator' - with mock.patch(self.import_gp_api) as gp,\ - mock.patch(self.import_gv_api) as gv,\ - mock.patch(self.import_gm_api) as gm,\ - mock.patch(self.import_ghm_api) as ghm,\ - mock.patch(self._call) as mock_call,\ - mock.patch(self._get_pool) as mock_pool,\ - mock.patch(import_send) as mock_send: - gp.return_value = [] - gv.return_value = [] - gm.return_value = [] - ghm.return_value = [] - mock_call.side_effect = self._call_data - mock_send.side_effect = self._cast_loadbalancer - mock_pool.side_effect = self._get_mocked_pool - hm = self._loadbalancer_data('health_monitor') - pool_id = str(uuid.uuid4()) - self.lb_handler.delete_pool_health_monitor( - self.context, hm, pool_id) - - -class VPNTestCase(unittest.TestCase): - - def setUp(self): - self.conf = Conf() - self.vpn_handler = vpn.VpnAgent(self.conf, 'sc') - self.context = TestContext().get_context() - import_path = "neutron_vpnaas.db.vpn.vpn_db.VPNPluginDb" - self.import_gvs_api = import_path + '.get_vpnservices' - self.import_gikp_api = import_path + '.get_ikepolicies' - self.import_gipsp_api = import_path + '.get_ipsecpolicies' - self.import_gisc_api = import_path + '.get_ipsec_site_connections' - self.import_lib = 'gbpservice.nfp.lib.transport' - self._call = 'oslo_messaging.rpc.client._CallContext.call' - - def _cast_vpn(self, conf, context, body, - method_type, device_config=False, - network_function_event=False): - g_cnfg = GeneralConfigStructure() - try: - resource = body['config'][0]['resource'] - self.assertTrue(g_cnfg._check_general_structure( - body, 'vpn', resource)) - except Exception: - self.assertTrue(False) - - def _call_data(self, context, method, **kwargs): - if method.lower() == "get_network_function_details": - data = call_network_function_info() - data['network_function']['description'] = "\n" +\ - ("ipsec_site_connection_id=%s;service_vendor=xyz" % ( - str(uuid.uuid4()))) - return data - - return [] - - def _prepare_request_data(self, reason, rsrc_type): - resource = {'tenant_id': str(uuid.uuid4()), - 'id': str(uuid.uuid4()), - 'description': ( - "{'network_function_id':'%s'}" % (str(uuid.uuid4()))) - } - if rsrc_type.lower() == 'ipsec_site_connection': - resource.update({'vpnservice_id': str(uuid.uuid4()), - 'ikepolicy_id': str(uuid.uuid4()), - 'ipsecpolicy_id': str(uuid.uuid4())}) - elif rsrc_type.lower() == 'vpn_service': - resource.update({'subnet_id': str(uuid.uuid4()), - 'router_id': str(uuid.uuid4())}) - return {'resource': resource, - 'rsrc_type': rsrc_type, - 'reason': reason, - 'rsrc_id': str(uuid.uuid4()) - } - - def test_update_vpnservice_for_vpnservice(self): - import_send = self.import_lib + '.send_request_to_configurator' - with mock.patch(self.import_gvs_api) as gvs,\ - mock.patch(self.import_gikp_api) as gikp,\ - mock.patch(self.import_gipsp_api) as gipsp,\ - mock.patch(self.import_gisc_api) as gisc,\ - mock.patch(self._call) as mock_call,\ - mock.patch(import_send) as mock_send: - gvs.return_value = [] - gikp.return_value = [] - gipsp.return_value = [] - gisc.return_value = [] - mock_call.side_effect = self._call_data - mock_send.side_effect = self._cast_vpn - rsrc_type = 'vpn_service' - reason = 'create' - kwargs = self._prepare_request_data(reason, rsrc_type) - self.vpn_handler.vpnservice_updated(self.context, **kwargs) - - def test_update_vpnservice_for_ipsec_site_connection(self): - import_send = self.import_lib + '.send_request_to_configurator' - with mock.patch(self.import_gvs_api) as gvs,\ - mock.patch(self.import_gikp_api) as gikp,\ - mock.patch(self.import_gipsp_api) as gipsp,\ - mock.patch(self.import_gisc_api) as gisc,\ - mock.patch(self._call) as mock_call,\ - mock.patch(import_send) as mock_send: - gvs.return_value = [] - gikp.return_value = [] - gipsp.return_value = [] - gisc.return_value = [] - mock_call.side_effect = self._call_data - mock_send.side_effect = self._cast_vpn - rsrc_type = 'ipsec_site_connection' - reason = 'delete' - kwargs = self._prepare_request_data(reason, rsrc_type) - self.vpn_handler.vpnservice_updated(self.context, **kwargs) - - -class NotificationHandlerTestCase(unittest.TestCase): - - def setUp(self): - self.conf = Conf() - self.n_handler = notification_handler.NotificationAgent( - self.conf, 'sc') - self.context = TestContext().get_context() - self.n_fw = ("gbpservice.nfp.config_orchestrator.agent" - ".firewall.FirewallNotifier") - - def _fw_nh_api(self, context, notification_data): - return - - def test_network_function_notification(self): - notification_data = \ - {'info': - {'service_type': 'firewall'}, - 'notification': [ - {'data': - {'notification_type': 'set_firewall_status'} - }] - } - with mock.patch(self.n_fw + '.set_firewall_status') as mock_fw: - mock_fw.side_effect = self._fw_nh_api - self.n_handler.network_function_notification(self.context, - notification_data) - - -if __name__ == '__main__': - unittest.main() diff --git a/gbpservice/neutron/tests/unit/nfp/configurator/agents/test_generic_config.py b/gbpservice/neutron/tests/unit/nfp/configurator/agents/test_generic_config.py index 386f4caeb7..34d5935f3f 100644 --- a/gbpservice/neutron/tests/unit/nfp/configurator/agents/test_generic_config.py +++ b/gbpservice/neutron/tests/unit/nfp/configurator/agents/test_generic_config.py @@ -206,8 +206,6 @@ def _test_handle_event(self, ev): driver, 'clear_routes') as mock_delete_src_routes, \ mock.patch.object( sc, 'poll_event') as mock_hm_poll_event, \ - mock.patch.object( - sc, 'poll_event_done') as mock_hm_poll_event_done, \ mock.patch.object( driver, 'configure_healthmonitor', return_value='SUCCESS'), \ mock.patch.object( @@ -238,7 +236,7 @@ def _test_handle_event(self, ev): elif periodicity == gen_cfg_const.FOREVER: mock_hm_poll_event.assert_called_with(ev) elif ev.id == 'CLEAR_HEALTHMONITOR': - mock_hm_poll_event_done.assert_called_with(ev) + pass def _test_handle_periodic_event(self, ev): """ Test handle periodic event method of generic config agent @@ -260,12 +258,9 @@ def _test_handle_periodic_event(self, ev): mock.patch.object( driver, 'configure_healthmonitor', return_value='SUCCESS'), \ - mock.patch.object( - sc, 'poll_event_done') as mock_poll_event_done, \ mock.patch.object(subprocess, 'check_output', return_value=True): agent.handle_configure_healthmonitor(ev) - mock_poll_event_done.assert_called_with(ev) def test_configure_interfaces_genericconfigeventhandler(self): """ Implements test case for configure interfaces method diff --git a/gbpservice/neutron/tests/unit/nfp/core/EventHandler/__init__.py b/gbpservice/neutron/tests/unit/nfp/core/EventHandler/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/gbpservice/neutron/tests/unit/nfp/core/EventHandler/test_event_handlers.py b/gbpservice/neutron/tests/unit/nfp/core/EventHandler/test_event_handlers.py deleted file mode 100644 index d30e9fbc30..0000000000 --- a/gbpservice/neutron/tests/unit/nfp/core/EventHandler/test_event_handlers.py +++ /dev/null @@ -1,119 +0,0 @@ -# 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 gbpservice.neutron.nsf.core import main - -from gbpservice.nfp.core import controller -from gbpservice.nfp.core import poll -import os -from oslo_log import log as logging -import time -LOG = logging.getLogger(__name__) - - -class Handler_Class(poll.PollEventDesc): - - def __init__(self, sc): - self._sc = sc - self.counter = 0 - self.timer = 0 - - def handle_poll_event(self, ev): - if os.getpid() == ev.desc.worker_attached and \ - ev.id == 'DUMMY_SERVICE_EVENT2' and self.counter == 0: - self._sc._event.set() - if ev.id == 'DUMMY_SERVICE_EVENT3': - self.counter = self.counter + 1 - if ev.id == 'DUMMY_SERVICE_EVENT4': - self.counter = self.counter + 1 - if self.counter == 2: - self._sc._event.set() - LOG.debug("Poll event (%s)" % (str(ev))) - print("Poll event %s", (ev.desc.uid)) - - def handle_event(self, ev): - LOG.debug("Process ID :%d" % (os.getpid())) - if ev.id == 'DUMMY_SERVICE_EVENT1': - self._handle_dummy_event1(ev) - elif ev.id == 'DUMMY_SERVICE_EVENT2': - self._handle_dummy_event2(ev) - elif ev.id == 'DUMMY_SERVICE_EVENT3': - self._handle_dummy_event3(ev) - elif ev.id == 'DUMMY_SERVICE_EVENT4': - self._handle_dummy_event4(ev) - elif ev.id == 'DUMMY_SERVICE_EVENT5': - self._handle_dummy_event5(ev) - elif ev.id == 'DUMMY_SERVICE_EVENT6': - self._handle_dummy_event6(ev) - - def _handle_dummy_event6(self, ev): - self._sc.event_done(ev) - self._sc.poll_event(ev, max_times=2) - - def _handle_dummy_event5(self, ev): - self._sc.event_done(ev) - self._sc.poll_event(ev, max_times=2) - - def _handle_dummy_event4(self, ev): - self._sc.poll_event(ev, max_times=1) - - def _handle_dummy_event3(self, ev): - self._sc.poll_event(ev, max_times=2) - - def _handle_dummy_event1(self, ev): - if os.getpid() == ev.desc.worker_attached: - self._sc._event.set() - - def _handle_dummy_event2(self, ev): - self._sc.event_done(ev) - self._sc.poll_event(ev, max_times=1) - - def poll_event_cancel(self, ev): - if os.getpid() == ev.desc.worker_attached and self.counter == 2: - self._sc._event.set() - LOG.debug("Poll event Canceled counter(%s)" % (str(ev))) - - @poll.poll_event_desc(event='DUMMY_SERVICE_EVENT5', spacing=10) - def dummy_event5_poll_event(self, ev): - if self.counter == 0: - self.timer = time.time() - self.counter = self.counter + 1 - else: - time_now = time.time() - time_elapsed = int(round(time_now - self.timer)) - if ev.id == 'DUMMY_SERVICE_EVENT5' and time_elapsed == 10: - self._sc._event.set() - LOG.debug("Poll event (%s)" % (str(ev))) - print ("Decorator Poll event %s", (ev.desc.uid)) - - @poll.poll_event_desc(event='DUMMY_SERVICE_EVENT6', spacing=20) - def dummy_event6_poll_event(self, ev): - if self.counter == 0: - self.timer = time.time() - self.counter = self.counter + 1 - else: - time_now = time.time() - time_elapsed = int(round(time_now - self.timer)) - if ev.id == 'DUMMY_SERVICE_EVENT6' and time_elapsed == 20: - self._sc._event.set() - LOG.debug("Poll event (%s)" % (str(ev))) - print ("Decorator Poll event %s", (ev.desc.uid)) - - -def nfp_module_init(sc, conf): - evs = [ - controller.Event(id='DUMMY_SERVICE_EVENT1', handler=Handler_Class(sc)), - controller.Event(id='DUMMY_SERVICE_EVENT2', handler=Handler_Class(sc)), - controller.Event(id='DUMMY_SERVICE_EVENT3', handler=Handler_Class(sc)), - controller.Event(id='DUMMY_SERVICE_EVENT4', handler=Handler_Class(sc)), - controller.Event(id='DUMMY_SERVICE_EVENT5', handler=Handler_Class(sc)), - controller.Event(id='DUMMY_SERVICE_EVENT6', handler=Handler_Class(sc)) - ] - sc.register_events(evs) diff --git a/gbpservice/neutron/tests/unit/nfp/config_orchestrator/__init__.py b/gbpservice/neutron/tests/unit/nfp/core/test_nfp_module/__init__.py similarity index 100% rename from gbpservice/neutron/tests/unit/nfp/config_orchestrator/__init__.py rename to gbpservice/neutron/tests/unit/nfp/core/test_nfp_module/__init__.py diff --git a/gbpservice/neutron/tests/unit/nfp/core/test_nfp_module/nfp_module.py b/gbpservice/neutron/tests/unit/nfp/core/test_nfp_module/nfp_module.py new file mode 100644 index 0000000000..807d5473ca --- /dev/null +++ b/gbpservice/neutron/tests/unit/nfp/core/test_nfp_module/nfp_module.py @@ -0,0 +1,92 @@ +# 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 gbpservice.neutron.nsf.core import main + +from gbpservice.nfp.core import event +from gbpservice.nfp.core import module as nfp_api +from oslo_log import log as logging +LOG = logging.getLogger(__name__) + + +class EventsHandler(nfp_api.NfpEventHandler): + + def __init__(self, controller): + self.controller = controller + + def handle_event(self, event): + if event.id == 'TEST_EVENT_ACK_FROM_WORKER': + self.controller.event_ack_handler_cb_obj.set() + + if event.id == 'TEST_POST_EVENT_FROM_WORKER': + self.controller.post_event_worker_wait_obj.set() + + if event.id == 'TEST_POLL_EVENT_FROM_WORKER': + self.controller.poll_event_worker_wait_obj.set() + self.controller.poll_event(event, spacing=1) + + if event.id == 'TEST_POLL_EVENT_CANCEL_FROM_WORKER': + self.controller.poll_event_worker_wait_obj.set() + self.controller.poll_event(event, spacing=1, max_times=2) + + def handle_poll_event(self, event): + if event.id == 'TEST_POLL_EVENT_FROM_WORKER': + self.controller.poll_event_poll_wait_obj.set() + if event.id == 'TEST_POLL_EVENT_CANCEL_FROM_WORKER': + self.controller.poll_event_poll_wait_obj.set() + + def event_cancelled(self, event, reason): + if event.id == 'TEST_POLL_EVENT_CANCEL_FROM_WORKER': + if reason == 'MAX_TIMED_OUT': + self.controller.poll_event_poll_cancel_wait_obj.set() + + @nfp_api.poll_event_desc(event='POLL_EVENT_DECORATOR', spacing=2) + def handle_poll_event_desc(self, event): + pass + + +def nfp_module_post_init(controller, conf): + if hasattr(controller, 'nfp_module_post_init_wait_obj'): + controller.nfp_module_post_init_wait_obj.set() + + +def nfp_module_init(controller, conf): + if hasattr(controller, 'nfp_module_init_wait_obj'): + controller.nfp_module_init_wait_obj.set() + + evs = [ + event.Event(id='EVENT_1', handler=EventsHandler(controller)), + event.Event(id='EVENT_LOAD_1', handler=EventsHandler(controller)), + event.Event(id='EVENT_LOAD_2', handler=EventsHandler(controller)), + event.Event(id='EVENT_LOAD_3', handler=EventsHandler(controller)), + event.Event(id='EVENT_LOAD_4', handler=EventsHandler(controller)), + event.Event(id='EVENT_LOAD_5', handler=EventsHandler(controller)), + event.Event(id='EVENT_LOAD_6', handler=EventsHandler(controller)), + event.Event(id='EVENT_LOAD_7', handler=EventsHandler(controller)), + event.Event(id='EVENT_LOAD_8', handler=EventsHandler(controller)), + event.Event(id='EVENT_LOAD_9', handler=EventsHandler(controller)), + event.Event(id='EVENT_LOAD_10', handler=EventsHandler(controller)), + event.Event(id='SEQUENCE_EVENT_1', handler=EventsHandler(controller)), + event.Event(id='SEQUENCE_EVENT_2', handler=EventsHandler(controller)), + event.Event(id='POLL_EVENT', handler=EventsHandler(controller)), + event.Event(id='POLL_EVENT_DECORATOR', + handler=EventsHandler(controller)), + event.Event(id='POLL_EVENT_WITHOUT_SPACING', + handler=EventsHandler(controller)), + event.Event(id='TEST_EVENT_ACK_FROM_WORKER', + handler=EventsHandler(controller)), + event.Event(id='TEST_POST_EVENT_FROM_WORKER', + handler=EventsHandler(controller)), + event.Event(id='TEST_POLL_EVENT_FROM_WORKER', + handler=EventsHandler(controller)), + event.Event(id='TEST_POLL_EVENT_CANCEL_FROM_WORKER', + handler=EventsHandler(controller)) + ] + controller.register_events(evs) diff --git a/gbpservice/neutron/tests/unit/nfp/core/test_process_model.py b/gbpservice/neutron/tests/unit/nfp/core/test_process_model.py index 3eb9600cf7..e081054b74 100644 --- a/gbpservice/neutron/tests/unit/nfp/core/test_process_model.py +++ b/gbpservice/neutron/tests/unit/nfp/core/test_process_model.py @@ -10,308 +10,786 @@ # License for the specific language governing permissions and limitations # under the License. -from gbpservice.nfp.core import cfg as nfp_config -from gbpservice.nfp.core import controller +from gbpservice.nfp.core import controller as nfp_controller +from gbpservice.nfp.core import event as nfp_event +from gbpservice.nfp.core import worker as nfp_worker import mock import multiprocessing as multiprocessing -from neutron.agent.common import config as n_config -import os from oslo_config import cfg as oslo_config from oslo_log import log as oslo_logging -import sys +import random import time import unittest LOG = oslo_logging.getLogger(__name__) +NFP_MODULES_PATH = 'gbpservice.neutron.tests.unit.nfp.core.test_nfp_module' + + +class MockedPipe(object): + + def __init__(self): + self.fd = random.randint(14, 34) + self.other_end_event_proc_func = None + + def poll(self, *args, **kwargs): + return False + + def send(self, event): + self.other_end_event_proc_func(event) + + +class MockedProcess(object): + + def __init__(self, parent_pipe=None, child_pipe=None, controller=None): + self.parent_pipe = parent_pipe + self.child_pipe = child_pipe + self.controller = controller + self.daemon = True + self.pid = random.randint(8888, 9999) + + def start(self): + self.worker = nfp_worker.NfpWorker({}, threads=0) + self.worker.parent_pipe = self.parent_pipe + self.worker.pipe = self.child_pipe + self.worker.controller = nfp_controller.NfpController( + self.controller._conf) + + # fork a new controller object + self.worker.controller.PROCESS_TYPE = "worker" + self.worker.controller._pipe = self.worker.pipe + self.worker.controller._event_handlers = \ + self.controller._event_handlers + self.worker.event_handlers = self.controller.get_event_handlers() + + self.parent_pipe.other_end_event_proc_func = \ + self.worker._process_event + self.child_pipe.other_end_event_proc_func = \ + self.controller._process_event + + +def mocked_pipe(**kwargs): + return MockedPipe(), MockedPipe() + + +def mocked_process(target=None, args=None): + return MockedProcess(parent_pipe=args[1], + child_pipe=args[2], controller=args[3]) + +nfp_controller.PIPE = mocked_pipe +nfp_controller.PROCESS = mocked_process + + +class Object(object): + + def __init__(self): + pass + class Test_Process_Model(unittest.TestCase): + def _mocked_fork(self, args): + proc = Object() + pid = random.randint(8888, 9999) + setattr(proc, 'pid', pid) + return proc + + def _mocked_oslo_wrap(self): + wrap = Object() + setattr(wrap, 'service', {}) + return wrap + + def _mocked_event_ack(self, event): + if event.id == 'TEST_EVENT_ACK_FROM_WORKER': + if hasattr(event, 'desc'): + if event.desc.worker: + self.controller.event_ack_wait_obj.set() + + def test_nfp_module_init(self): + conf = oslo_config.CONF + conf.nfp_modules_path = NFP_MODULES_PATH + controller = nfp_controller.NfpController(conf) + wait_obj = multiprocessing.Event() + setattr(controller, 'nfp_module_init_wait_obj', wait_obj) + nfp_controller.load_nfp_modules(conf, controller) + controller.nfp_module_init_wait_obj.wait(1) + called = controller.nfp_module_init_wait_obj.is_set() + self.assertTrue(called) + + def test_nfp_module_init_wrong_path(self): + conf = oslo_config.CONF + conf.nfp_modules_path = 'tmp.nfp' + controller = nfp_controller.NfpController(oslo_config.CONF) + wait_obj = multiprocessing.Event() + setattr(controller, 'nfp_module_init_wait_obj', wait_obj) + nfp_controller.load_nfp_modules(conf, controller) + controller.nfp_module_init_wait_obj.wait(1) + called = controller.nfp_module_init_wait_obj.is_set() + self.assertFalse(called) + + def test_nfp_module_post_init_called(self): + conf = oslo_config.CONF + conf.nfp_modules_path = NFP_MODULES_PATH + controller = nfp_controller.NfpController(conf) + wait_obj = multiprocessing.Event() + setattr(controller, 'nfp_module_post_init_wait_obj', wait_obj) + nfp_modules = nfp_controller.load_nfp_modules(conf, controller) + nfp_controller.nfp_modules_post_init(conf, nfp_modules, controller) + controller.nfp_module_post_init_wait_obj.wait(1) + called = controller.nfp_module_post_init_wait_obj.is_set() + self.assertTrue(called) + + def test_nfp_module_post_init_ignored(self): + # None the post_init method in test handler + from gbpservice.neutron.tests.unit.nfp.core.test_nfp_module \ + import nfp_module + del nfp_module.nfp_module_post_init + + conf = oslo_config.CONF + conf.nfp_modules_path = NFP_MODULES_PATH + controller = nfp_controller.NfpController(conf) + wait_obj = multiprocessing.Event() + setattr(controller, 'nfp_module_post_init_wait_obj', wait_obj) + nfp_modules = nfp_controller.load_nfp_modules(conf, controller) + nfp_controller.nfp_modules_post_init(conf, nfp_modules, controller) + controller.nfp_module_post_init_wait_obj.wait(1) + called = controller.nfp_module_post_init_wait_obj.is_set() + self.assertFalse(called) + + @mock.patch( + 'gbpservice.nfp.core.controller.NfpController._fork' + ) + def test_nfp_controller_launch_2_workers(self, mock_fork): + mock_fork.side_effect = self._mocked_fork + conf = oslo_config.CONF + conf.nfp_modules_path = '' + controller = nfp_controller.NfpController(conf) + controller.launch(2) + # Check if 2 childrens are created + childrens = controller.get_childrens() + pids = childrens.keys() + self.assertTrue(len(pids) == 2) + self.assertTrue(pid in range(8888, 9999) for pid in pids) + + @mock.patch( + 'gbpservice.nfp.core.controller.NfpController._fork' + ) + def test_nfp_controller_launch_4_workers(self, mock_fork): + mock_fork.side_effect = self._mocked_fork + conf = oslo_config.CONF + conf.nfp_modules_path = '' + controller = nfp_controller.NfpController(conf) + controller.launch(4) + # Check if 4 childrens are created + childrens = controller.get_childrens() + pids = childrens.keys() + self.assertTrue(len(pids) == 4) + self.assertTrue(pid in range(8888, 9999) for pid in pids) + + @mock.patch( + 'gbpservice.nfp.core.controller.NfpController._fork' + ) + def test_nfp_rsrc_manager_new_childs(self, mock_fork): + mock_fork.side_effect = self._mocked_fork + conf = oslo_config.CONF + conf.nfp_modules_path = '' + controller = nfp_controller.NfpController(conf) + controller.launch(2) + controller._update_manager() + # Check if 2 childrens are added to manager + pids = controller._manager._resource_map.keys() + self.assertTrue(len(pids) == 2) + self.assertTrue(pid in range(8888, 9999) for pid in pids) + + @mock.patch( + 'gbpservice.nfp.core.controller.NfpController._fork' + ) + def test_nfp_rsrc_manager_kill_child(self, mock_fork): + mock_fork.side_effect = self._mocked_fork + conf = oslo_config.CONF + conf.nfp_modules_path = '' + controller = nfp_controller.NfpController(conf) + controller.launch(2) + controller._update_manager() + # run so that it stores the snapshot + controller._manager.manager_run() + # Mock killing a child, remove it from childrens list + childrens = controller.get_childrens() + old_childs = list(childrens.keys()) + del controller.children[old_childs[0]] + # Mock creating a new child which replaces the killed one + wrap = self._mocked_oslo_wrap() + pid = controller.fork_child(wrap) + controller.children[pid] = wrap + + # Run one more time and check if it detects the difference + controller._manager.manager_run() + pids = controller._manager._resource_map.keys() + self.assertTrue(len(pids) == 2) + self.assertFalse(old_childs[0] in pids) + self.assertTrue(old_childs[1] in pids) + + def test_post_event_with_no_handler(self): + conf = oslo_config.CONF + conf.nfp_modules_path = '' + controller = nfp_controller.NfpController(conf) + event = controller.create_event( + id='EVENT_INVALID', data='INVALID_DATA', + binding_key='EVENT_INVALID') + try: + controller.post_event(event) + except AssertionError: + return + + self.assertTrue(False) + + def mocked_pipe_send(self, pipe, event): + if event.id == 'EVENT_1': + if hasattr(event, 'desc'): + if event.desc.worker: + self.controller.nfp_event_1_wait_obj.set() + elif 'EVENT_LOAD' in event.id: + if hasattr(event, 'desc'): + if event.desc.worker == event.data: + self.controller.nfp_event_load_wait_obj.set() + elif 'SEQUENCE' in event.id: + if hasattr(event, 'desc'): + if event.desc.worker: + if 'EVENT_1' in event.id: + self.controller.sequence_event_1_wait_obj.set() + elif 'EVENT_2' in event.id: + self.controller.sequence_event_2_wait_obj.set() + elif 'POLL' in event.id: + if hasattr(event, 'desc'): + if hasattr(event.desc, 'poll_desc'): + if event.desc.worker: + if event.id == 'POLL_EVENT': + self.controller.poll_event_wait_obj.set() + if event.id == 'POLL_EVENT_DECORATOR': + self.controller.poll_event_dec_wait_obj.set() + + @mock.patch( + 'gbpservice.nfp.core.controller.NfpController.pipe_send' + ) + def test_post_event_in_distributor(self, mock_pipe_send): + mock_pipe_send.side_effect = self.mocked_pipe_send + conf = oslo_config.CONF + conf.nfp_modules_path = NFP_MODULES_PATH + controller = nfp_controller.NfpController(conf) + nfp_controller.load_nfp_modules(conf, controller) + # Mock launching of a worker + controller.launch(1) + controller._update_manager() + wait_obj = multiprocessing.Event() + setattr(controller, 'nfp_event_1_wait_obj', wait_obj) + event = controller.create_event( + id='EVENT_1', + data='post_event_in_distributor') + + # Store in class object + self.controller = controller + controller.post_event(event) + controller.nfp_event_1_wait_obj.wait(1) + called = controller.nfp_event_1_wait_obj.is_set() + self.assertTrue(called) + + @mock.patch( + 'gbpservice.nfp.core.controller.NfpController.pipe_send' + ) + def test_load_distribution_to_workers(self, mock_pipe_send): + mock_pipe_send.side_effect = self.mocked_pipe_send + conf = oslo_config.CONF + conf.nfp_modules_path = NFP_MODULES_PATH + controller = nfp_controller.NfpController(conf) + self.controller = controller + nfp_controller.load_nfp_modules(conf, controller) + # Mock launching of a worker + controller.launch(3) + controller._update_manager() + + # Load distribution as -> worker1 - 2, worker2 - 4, worker3 - 6 + # 10 events to be distributed. + # worker1 will get 5 + # worker2 will get 4 + # worker3 will get 1 + # At the end all workers should be @load 7 + + # Initialize with above load + init_load = [6, 4, 2] + worker_pids = [] + resource_map = controller._manager._resource_map + for pid, em in resource_map.iteritems(): + load = init_load.pop() + em._load = load + worker_pids.append(pid) + + events = [ + controller.create_event(id='EVENT_LOAD_1', data=worker_pids[0]), + controller.create_event(id='EVENT_LOAD_2', data=worker_pids[0]), + controller.create_event(id='EVENT_LOAD_3', data=worker_pids[0]), + controller.create_event(id='EVENT_LOAD_4', data=worker_pids[1]), + controller.create_event(id='EVENT_LOAD_5', data=worker_pids[0]), + controller.create_event(id='EVENT_LOAD_6', data=worker_pids[1]), + controller.create_event(id='EVENT_LOAD_7', data=worker_pids[0]), + controller.create_event(id='EVENT_LOAD_8', data=worker_pids[1]), + controller.create_event(id='EVENT_LOAD_9', data=worker_pids[2])] + + for i in range(0, 9): + wait_obj = multiprocessing.Event() + setattr(controller, 'nfp_event_load_wait_obj', wait_obj) + event = events[i] + controller.post_event(event) + controller.nfp_event_load_wait_obj.wait(1) + called = controller.nfp_event_load_wait_obj.is_set() + self.assertTrue(called) + + def test_new_event_with_sequence_and_no_binding_key(self): + conf = oslo_config.CONF + conf.nfp_modules_path = '' + controller = nfp_controller.NfpController(conf) + event = controller.create_event( + id='EVENT_SEQUENCE', data='NO_DATA', + serialize=True) + self.assertTrue(event is None) + + @mock.patch( + 'gbpservice.nfp.core.controller.NfpController.pipe_send' + ) + def test_events_sequencing_with_same_binding_key(self, mock_pipe_send): + mock_pipe_send.side_effect = self.mocked_pipe_send + conf = oslo_config.CONF + conf.nfp_modules_path = NFP_MODULES_PATH + controller = nfp_controller.NfpController(conf) + self.controller = controller + nfp_controller.load_nfp_modules(conf, controller) + # Mock launching of a worker + controller.launch(1) + controller._update_manager() + self.controller = controller + + wait_obj = multiprocessing.Event() + setattr(controller, 'sequence_event_1_wait_obj', wait_obj) + wait_obj = multiprocessing.Event() + setattr(controller, 'sequence_event_2_wait_obj', wait_obj) + event_1 = controller.create_event( + id='SEQUENCE_EVENT_1', data='NO_DATA', + serialize=True, binding_key='SEQUENCE') + event_2 = controller.create_event( + id='SEQUENCE_EVENT_2', data='NO_DATA', + serialize=True, binding_key='SEQUENCE') + controller.post_event(event_1) + controller.post_event(event_2) + + controller._manager.manager_run() + controller.sequence_event_1_wait_obj.wait(1) + called = controller.sequence_event_1_wait_obj.is_set() + self.assertTrue(called) + controller.event_complete(event_1) + controller._manager.manager_run() + controller.sequence_event_2_wait_obj.wait(1) + called = controller.sequence_event_2_wait_obj.is_set() + self.assertTrue(called) + controller.event_complete(event_2) + @mock.patch( - 'gbpservice.nfp.core.controller.Controller._pipe_send' + 'gbpservice.nfp.core.controller.NfpController.pipe_send' ) - def test_event_create(self, mock_put): - event = self.sc.new_event( - id='DUMMY_SERVICE_EVENT1', data=self.service1, - binding_key=self.service1['id'], - key=self.service1['id'], serialize=True - ) - self.sc.post_event(event) - self.assertIsNotNone(event.desc.worker_attached) - pipe = self.sc._worker_pipe_map[event.desc.worker_attached] - mock_put.assert_called_once_with(pipe, event) + def test_events_sequencing_with_diff_binding_key(self, mock_pipe_send): + mock_pipe_send.side_effect = self.mocked_pipe_send + conf = oslo_config.CONF + conf.nfp_modules_path = NFP_MODULES_PATH + controller = nfp_controller.NfpController(conf) + self.controller = controller + nfp_controller.load_nfp_modules(conf, controller) + # Mock launching of a worker + controller.launch(1) + controller._update_manager() + self.controller = controller + + wait_obj = multiprocessing.Event() + setattr(controller, 'sequence_event_1_wait_obj', wait_obj) + wait_obj = multiprocessing.Event() + setattr(controller, 'sequence_event_2_wait_obj', wait_obj) + event_1 = controller.create_event( + id='SEQUENCE_EVENT_1', data='NO_DATA', + serialize=True, binding_key='SEQUENCE_1') + event_2 = controller.create_event( + id='SEQUENCE_EVENT_2', data='NO_DATA', + serialize=True, binding_key='SEQUENCE_2') + controller.post_event(event_1) + controller.post_event(event_2) + + controller._manager.manager_run() + controller.sequence_event_1_wait_obj.wait(1) + called = controller.sequence_event_1_wait_obj.is_set() + self.assertTrue(called) + controller.sequence_event_2_wait_obj.wait(1) + called = controller.sequence_event_2_wait_obj.is_set() + self.assertTrue(called) @mock.patch( - 'gbpservice.nfp.core.controller.Controller._pipe_send' + 'gbpservice.nfp.core.controller.NfpController.pipe_send' ) - def test_events_with_same_binding_keys(self, mock_put): - event1 = self.sc.new_event( - id='DUMMY_SERVICE_EVENT1', data=self.service1, - binding_key=self.service1['tenant'], - key=self.service1['id'], serialize=True - ) - event2 = self.sc.new_event( - id='DUMMY_SERVICE_EVENT2', data=self.service1, - binding_key=self.service1['tenant'], - key=self.service1['id'], serialize=True - ) - self.sc.post_event(event1) - self.sc.post_event(event2) - self.assertIsNotNone(event1.desc.worker_attached) - self.assertIsNotNone(event2.desc.worker_attached) - self.assertEqual( - event1.desc.worker_attached, event2.desc.worker_attached) - self.assertEqual(mock_put.call_count, 2) + def test_events_sequencing_negative(self, mock_pipe_send): + mock_pipe_send.side_effect = self.mocked_pipe_send + conf = oslo_config.CONF + conf.nfp_modules_path = NFP_MODULES_PATH + controller = nfp_controller.NfpController(conf) + self.controller = controller + nfp_controller.load_nfp_modules(conf, controller) + # Mock launching of a worker + controller.launch(1) + controller._update_manager() + self.controller = controller + + wait_obj = multiprocessing.Event() + setattr(controller, 'sequence_event_1_wait_obj', wait_obj) + wait_obj = multiprocessing.Event() + setattr(controller, 'sequence_event_2_wait_obj', wait_obj) + event_1 = controller.create_event( + id='SEQUENCE_EVENT_1', data='NO_DATA', + serialize=True, binding_key='SEQUENCE') + event_2 = controller.create_event( + id='SEQUENCE_EVENT_2', data='NO_DATA', + serialize=True, binding_key='SEQUENCE') + controller.post_event(event_1) + controller.post_event(event_2) + + controller._manager.manager_run() + controller.sequence_event_1_wait_obj.wait(1) + called = controller.sequence_event_1_wait_obj.is_set() + self.assertTrue(called) + controller._manager.manager_run() + controller.sequence_event_2_wait_obj.wait(1) + called = controller.sequence_event_2_wait_obj.is_set() + # Should not be called + self.assertFalse(called) + controller.event_complete(event_1) + controller.event_complete(event_2) @mock.patch( - 'gbpservice.nfp.core.controller.Controller._pipe_send' + 'gbpservice.nfp.core.controller.NfpController.pipe_send' ) - def test_events_with_no_binding_key(self, mock_put): - event1 = self.sc.new_event( - id='DUMMY_SERVICE_EVENT1', data=self.service1, - key=self.service1['id'], serialize=False - ) - event2 = self.sc.new_event( - id='DUMMY_SERVICE_EVENT2', data=self.service1, - key=self.service1['id'], serialize=False - ) - self.sc.post_event(event1) - self.sc.post_event(event2) - self.assertIsNotNone(event1.desc.worker_attached) - self.assertIsNotNone(event2.desc.worker_attached) - self.assertNotEqual( - event1.desc.worker_attached, event2.desc.worker_attached) - self.assertEqual(mock_put.call_count, 2) + def test_poll_event(self, mock_pipe_send): + mock_pipe_send.side_effect = self.mocked_pipe_send + conf = oslo_config.CONF + conf.nfp_modules_path = NFP_MODULES_PATH + controller = nfp_controller.NfpController(conf) + self.controller = controller + nfp_controller.load_nfp_modules(conf, controller) + # Mock launching of a worker + controller.launch(1) + controller._update_manager() + self.controller = controller + + wait_obj = multiprocessing.Event() + setattr(controller, 'poll_event_wait_obj', wait_obj) + event = controller.create_event( + id='POLL_EVENT', data='NO_DATA') + + # Update descriptor + desc = nfp_event.EventDesc(**{}) + setattr(event, 'desc', desc) + event.desc.worker = controller.get_childrens().keys()[0] + + controller.poll_event(event, spacing=1) + # controller._manager.manager_run() + + start_time = time.time() + # relinquish for 1sec + time.sleep(1) + + controller.poll() + controller.poll_event_wait_obj.wait(0.1) + called = controller.poll_event_wait_obj.is_set() + end_time = time.time() + self.assertTrue(called) + self.assertTrue(round(end_time - start_time) == 1.0) @mock.patch( - 'gbpservice.nfp.core.controller.Controller._pipe_send' + 'gbpservice.nfp.core.controller.NfpController.pipe_send' ) - def test_loadbalancing_events(self, mock_put): - event1 = self.sc.new_event( - id='SERVICE_CREATE', data=self.service1, - binding_key=self.service1['id'], - key=self.service1['id'], serialize=False - ) - self.sc.post_event(event1) - count = 0 - for worker in self.sc._workers: - if event1.desc.worker_attached == worker[0].pid: - rrid_event1 = count - break - count = count + 1 - - event2 = self.sc.new_event( - id='SERVICE_CREATE', data=self.service2, - binding_key=self.service2['id'], - key=self.service2['id'], serialize=False - ) - self.sc.post_event(event2) - if rrid_event1 + 1 == len(self.sc._workers): - self.assertEqual(event2.desc.worker_attached, - self.sc._workers[0][0].pid) - else: - self.assertEqual( - event2.desc.worker_attached, - self.sc._workers[rrid_event1 + 1][0].pid - ) - self.assertEqual(mock_put.call_count, 2) - - @mock.patch('gbpservice.nfp.core.controller.EventSequencer.add') - def test_serialize_events_serialize_false(self, mock_sequencer): - event1 = self.mock_event( - id='SERVICE_CREATE', data=self.service1, - binding_key=self.service1['id'], - key=self.service1['id'], serialize=False, - worker_attached=self.sc._workers[0][0].pid - ) - sequenced_event1 = self.sc.sequencer_put_event(event1) - self.assertEqual(mock_sequencer.call_count, 0) - self.assertEqual(sequenced_event1, event1) - - @mock.patch('gbpservice.nfp.core.controller.EventSequencer.add') - def test_serialize_events_serialze_true(self, mock_sequencer): - event1 = self.mock_event( - id='SERVICE_CREATE', data=self.service1, - binding_key=self.service1['id'], - key=self.service1['id'], serialize=True, - worker_attached=self.sc._workers[0][0].pid - ) - mock_sequencer.return_value = True - sequenced_event1 = self.sc.sequencer_put_event(event1) - mock_sequencer.assert_called_once_with(event1) - self.assertEqual(sequenced_event1, None) - mock_sequencer.return_value = False - sequenced_event1 = self.sc.sequencer_put_event(event1) - self.assertEqual(sequenced_event1, event1) - - @mock.patch('gbpservice.nfp.core.controller.EventSequencer') - def test_EventSequencer_add(self, mocked_sequencer): - event1 = self.mock_event( - id='SERVICE_CREATE', data=self.service1, - binding_key=self.service1['id'], - key=self.service1['id'], serialize=True, - worker_attached=self.sc._workers[0][0].pid - ) - mocked_sequencer_map = mock.Mock() - mocked_sequencer._sequencer_map = mocked_sequencer_map - mocked_sequencer_map = {} - self.assertFalse(self.EventSequencer.add(event1)) - mocked_sequencer_map = self.create_sequencer_map( - self.sc._workers[0][0].pid, - self.service1['id'] - ) - self.assertTrue(self.EventSequencer.add(event1)) - - def test_handle_event_on_queue(self): - event1 = self.sc.new_event( - id='DUMMY_SERVICE_EVENT1', data=self.service1, - binding_key=self.service1['id'], - key=self.service1['id'], serialize=True - ) - self.sc.post_event(event1) - time.sleep(10) - handle_event_invoked = self.sc._event.wait(1) - self.assertTrue(handle_event_invoked) - - def test_poll_handle_event(self): - ev = self.sc.new_event( - id='DUMMY_SERVICE_EVENT2', data=self.service1, - binding_key=self.service1['id'], - key=self.service1['id'], serialize=True - ) - self.sc.post_event(ev) - time.sleep(30) - poll_handle_event_invoked = self.sc._event.wait(1) - self.assertTrue(poll_handle_event_invoked) - - def test_poll_event_maxtimes(self): - ev = self.sc.new_event( - id='DUMMY_SERVICE_EVENT3', data=self.service1, - binding_key=self.service1['id'], - key=self.service1['id'], serialize=True - ) - self.sc.post_event(ev) - time.sleep(80) - event_polled_maxtimes = self.sc._event.wait(1) - self.assertTrue(event_polled_maxtimes) - - def test_poll_event_done(self): - ev = self.sc.new_event( - id='DUMMY_SERVICE_EVENT4', data=self.service1, - binding_key=self.service1['id'], - key=self.service1['id'], serialize=True - ) - self.sc.post_event(ev) - time.sleep(30) - sc_event_set = self.sc._event.wait(1) - self.assertFalse(sc_event_set) - - def test_periodic_method_withspacing_10(self): - ev = self.sc.new_event( - id='DUMMY_SERVICE_EVENT5', data=self.service1, - binding_key=self.service1['id'], - key=self.service1['id'], serialize=True) - self.sc.post_event(ev) - time.sleep(30) - called_with_correct_spacing = self.sc._event.wait(1) - self.assertTrue(called_with_correct_spacing) - - def test_periodic_method_withspacing_20(self): - ev = self.sc.new_event( - id='DUMMY_SERVICE_EVENT6', data=self.service1, - binding_key=self.service1['id'], - key=self.service1['id'], serialize=True) - self.sc.post_event(ev) - time.sleep(30) - called_with_correct_spacing = self.sc._event.wait(1) - self.assertTrue(called_with_correct_spacing) - - def test_worker_process_initilized(self): - workers = self.sc._workers - test_process = multiprocessing.Process() - self.assertEqual(len(workers), 4) - for worker in workers: - self.assertTrue(type(worker[0]), type(test_process)) - - def create_sequencer_map(self, worker_attached, binding_key): - sequencer_map = {} - sequencer_map[worker_attached] = {} - mapp = sequencer_map[worker_attached] - mapp[binding_key] = {'in_use': True, 'queue': []} - return sequencer_map - - def mock_event(self, **kwargs): - event = self.sc.new_event(**kwargs) - event.desc.poll_event = \ - kwargs.get('poll_event') if 'poll_event' in kwargs else None - event.desc.worker_attached = \ - kwargs.get( - 'worker_attached') if 'worker_attached' in kwargs else None - event.last_run = kwargs.get( - 'last_run') if 'last_run' in kwargs else None - event.max_times = kwargs.get( - 'max_times') if 'max_times' in kwargs else -1 - return event - - def modules_import(self): - modules = [] - modules_dir = 'gbpservice.neutron.tests.unit.nfp.core.EventHandler' - base_module = __import__( - modules_dir, - globals(), locals(), - ['modules'], -1 - ) - modules_dir_test = base_module.__path__[0] - syspath = sys.path - sys.path = [modules_dir_test] + syspath + def test_poll_event_with_no_worker(self, mock_pipe_send): + mock_pipe_send.side_effect = self.mocked_pipe_send + conf = oslo_config.CONF + conf.nfp_modules_path = NFP_MODULES_PATH + controller = nfp_controller.NfpController(conf) + self.controller = controller + nfp_controller.load_nfp_modules(conf, controller) + # Mock launching of a worker + controller.launch(1) + controller._update_manager() + self.controller = controller + + wait_obj = multiprocessing.Event() + setattr(controller, 'poll_event_wait_obj', wait_obj) + event = controller.create_event( + id='POLL_EVENT', data='NO_DATA') + + # Update descriptor + desc = nfp_event.EventDesc(**{}) + setattr(event, 'desc', desc) + # Explicitly make it none + event.desc.worker = None + + controller.poll_event(event, spacing=1) + # controller._manager.manager_run() + + start_time = time.time() + # relinquish for 1sec + time.sleep(1) + + controller.poll() + controller.poll_event_wait_obj.wait(0.1) + called = controller.poll_event_wait_obj.is_set() + end_time = time.time() + self.assertTrue(called) + self.assertTrue(round(end_time - start_time) == 1.0) + + @mock.patch( + 'gbpservice.nfp.core.controller.NfpController.pipe_send' + ) + def test_poll_event_with_decorator_spacing(self, mock_pipe_send): + mock_pipe_send.side_effect = self.mocked_pipe_send + conf = oslo_config.CONF + conf.nfp_modules_path = NFP_MODULES_PATH + controller = nfp_controller.NfpController(conf) + self.controller = controller + nfp_controller.load_nfp_modules(conf, controller) + # Mock launching of a worker + controller.launch(1) + controller._update_manager() + self.controller = controller + + wait_obj = multiprocessing.Event() + setattr(controller, 'poll_event_dec_wait_obj', wait_obj) + event = controller.create_event( + id='POLL_EVENT_DECORATOR', data='NO_DATA') + + # Update descriptor + desc = nfp_event.EventDesc(**{}) + setattr(event, 'desc', desc) + # Explicitly make it none + event.desc.worker = None + + controller.poll_event(event) + # controller._manager.manager_run() + + start_time = time.time() + # relinquish for 2secs + time.sleep(2) + + controller.poll() + controller.poll_event_dec_wait_obj.wait(0.1) + called = controller.poll_event_dec_wait_obj.is_set() + end_time = time.time() + self.assertTrue(called) + self.assertTrue(round(end_time - start_time) == 2.0) + + def test_poll_event_with_no_spacing(self): + conf = oslo_config.CONF + conf.nfp_modules_path = NFP_MODULES_PATH + controller = nfp_controller.NfpController(conf) + event = controller.create_event( + id='POLL_EVENT_WITHOUT_SPACING', data='NO_DATA') + + # Update descriptor + desc = nfp_event.EventDesc(**{}) + setattr(event, 'desc', desc) + # Explicitly make it none + event.desc.worker = None + try: - files = os.listdir(modules_dir_test) - except OSError: - LOG.error(_("Failed to read files..")) - files = [] - for fname in files: - if fname.endswith(".py") and fname != '__init__.py': - module = __import__( - modules_dir, - globals(), locals(), - [fname[:-3]], -1 - ) - modules += [__import__(fname[:-3])] - sys.path = syspath - return modules - - def setUp(self): - oslo_config.CONF.register_opts(nfp_config.OPTS) - modules = self.modules_import() - n_config.register_interface_driver_opts_helper(oslo_config.CONF) - n_config.register_agent_state_opts_helper(oslo_config.CONF) - n_config.register_root_helper(oslo_config.CONF) - oslo_config.CONF.workers = 4 - self.service1 = { - 'id': 'sc2f2b13-e284-44b1-9d9a-2597e216271a', - 'tenant': '40af8c0695dd49b7a4980bd1b47e1a1b', - 'servicechain': 'sc2f2b13-e284-44b1-9d9a-2597e2161c', - 'servicefunction': 'sf2f2b13-e284-44b1-9d9a-2597e216561d', - 'vip_id': '13948da4-8dd9-44c6-adef-03a6d8063daa', - 'service_vendor': 'haproxy', - 'service_type': 'loadbalancer', - 'ip': '192.168.20.199' - } - self.service2 = { - 'id': 'sc2f2b13-e284-44b1-9d9a-2597e216272a', - 'tenant': '40af8c0695dd49b7a4980bd1b47e1a2b', - 'servicechain': 'sc2f2b13-e284-44b1-9d9a-2597e216562c', - 'servicefunction': 'sf2f2b13-e284-44b1-9d9a-2597e216562d', - 'mac_address': 'fa:16:3e:3f:93:05', - 'service_vendor': 'vyos', - 'service_type': 'firewall', - 'ip': '192.168.20.197' - } - n_config.setup_logging() - self._conf = oslo_config.CONF - self._modules = modules - self.sc = controller.Controller(oslo_config.CONF, modules) - self.EventSequencer = controller.EventSequencer(self.sc) - self.sc.start() + controller.poll_event(event) + except AssertionError as aerr: + if aerr.message == "No spacing specified for polling": + self.assertTrue(True) + return + + self.assertTrue(False) + + def test_poll_event_with_no_handler(self): + conf = oslo_config.CONF + conf.nfp_modules_path = NFP_MODULES_PATH + controller = nfp_controller.NfpController(conf) + event = controller.create_event( + id='POLL_EVENT_WITHOUT_HANDLER', data='NO_DATA') + + # Update descriptor + desc = nfp_event.EventDesc(**{}) + setattr(event, 'desc', desc) + # Explicitly make it none + event.desc.worker = None + + try: + controller.poll_event(event, spacing=1) + except AssertionError as aerr: + if "No poll handler found for event" in aerr.message: + self.assertTrue(True) + return + + self.assertTrue(False) + + @mock.patch( + 'gbpservice.nfp.core.manager.NfpResourceManager._event_acked' + ) + def test_event_ack_from_worker(self, mock_event_acked): + mock_event_acked.side_effect = self._mocked_event_ack + conf = oslo_config.CONF + conf.nfp_modules_path = NFP_MODULES_PATH + controller = nfp_controller.NfpController(conf) + self.controller = controller + nfp_controller.load_nfp_modules(conf, controller) + # Mock launching of a worker + controller.launch(1) + controller._update_manager() + self.controller = controller + + # Check if 1 worker is added to manager + pids = controller._manager._resource_map.keys() + self.assertTrue(len(pids) == 1) + self.assertTrue(pid in range(8888, 9999) for pid in pids) + + wait_obj = multiprocessing.Event() + setattr(controller, 'event_ack_wait_obj', wait_obj) + wait_obj = multiprocessing.Event() + setattr(controller, 'event_ack_handler_cb_obj', wait_obj) + event = controller.create_event( + id='TEST_EVENT_ACK_FROM_WORKER', data='NO_DATA') + controller.post_event(event) + controller._manager.manager_run() + + # wait for event to be acked + controller.event_ack_wait_obj.wait(1) + called = controller.event_ack_wait_obj.is_set() + self.assertTrue(called) + + # Check if event handler callback is invoked + controller.event_ack_handler_cb_obj.wait(1) + called = controller.event_ack_handler_cb_obj.is_set() + self.assertTrue(called) + + def test_post_event_from_worker(self): + conf = oslo_config.CONF + conf.nfp_modules_path = NFP_MODULES_PATH + controller = nfp_controller.NfpController(conf) + self.controller = controller + nfp_controller.load_nfp_modules(conf, controller) + # Mock launching of a worker + controller.launch(1) + controller._update_manager() + self.controller = controller + + # Check if 1 worker is added to manager + pids = controller._manager._resource_map.keys() + self.assertTrue(len(pids) == 1) + self.assertTrue(pid in range(8888, 9999) for pid in pids) + + wait_obj = multiprocessing.Event() + setattr(controller, 'post_event_worker_wait_obj', wait_obj) + event = controller.create_event( + id='TEST_POST_EVENT_FROM_WORKER', data='NO_DATA') + worker_process = controller._worker_process.values()[0] + worker_process.worker.controller.post_event(event) + + controller._manager.manager_run() + + # Check if event handler callback is invoked + controller.post_event_worker_wait_obj.wait(1) + called = controller.post_event_worker_wait_obj.is_set() + self.assertTrue(called) + + def test_poll_event_from_worker(self): + conf = oslo_config.CONF + conf.nfp_modules_path = NFP_MODULES_PATH + controller = nfp_controller.NfpController(conf) + self.controller = controller + nfp_controller.load_nfp_modules(conf, controller) + # Mock launching of a worker + controller.launch(1) + controller._update_manager() + self.controller = controller + + # Check if 1 worker is added to manager + pids = controller._manager._resource_map.keys() + self.assertTrue(len(pids) == 1) + self.assertTrue(pid in range(8888, 9999) for pid in pids) + + wait_obj = multiprocessing.Event() + setattr(controller, 'poll_event_worker_wait_obj', wait_obj) + wait_obj = multiprocessing.Event() + setattr(controller, 'poll_event_poll_wait_obj', wait_obj) + + event = controller.create_event( + id='TEST_POLL_EVENT_FROM_WORKER', data='NO_DATA') + worker_process = controller._worker_process.values()[0] + worker_process.worker.controller.post_event(event) + + controller._manager.manager_run() + + # Check if event handler callback is invoked + controller.poll_event_worker_wait_obj.wait(1) + called = controller.poll_event_worker_wait_obj.is_set() + self.assertTrue(called) + + time.sleep(1) + controller.poll() + + controller.poll_event_poll_wait_obj.wait(1) + called = controller.poll_event_poll_wait_obj.is_set() + self.assertTrue(called) + + def test_poll_event_cancelled_from_worker(self): + conf = oslo_config.CONF + conf.nfp_modules_path = NFP_MODULES_PATH + controller = nfp_controller.NfpController(conf) + self.controller = controller + nfp_controller.load_nfp_modules(conf, controller) + # Mock launching of a worker + controller.launch(1) + controller._update_manager() + self.controller = controller + + # Check if 1 worker is added to manager + pids = controller._manager._resource_map.keys() + self.assertTrue(len(pids) == 1) + self.assertTrue(pid in range(8888, 9999) for pid in pids) + + wait_obj = multiprocessing.Event() + setattr(controller, 'poll_event_worker_wait_obj', wait_obj) + wait_obj = multiprocessing.Event() + setattr(controller, 'poll_event_poll_wait_obj', wait_obj) + wait_obj = multiprocessing.Event() + setattr(controller, 'poll_event_poll_cancel_wait_obj', wait_obj) + + event = controller.create_event( + id='TEST_POLL_EVENT_CANCEL_FROM_WORKER', data='NO_DATA') + worker_process = controller._worker_process.values()[0] + worker_process.worker.controller.post_event(event) + + controller._manager.manager_run() + + # Check if event handler callback is invoked + controller.poll_event_worker_wait_obj.wait(1) + called = controller.poll_event_worker_wait_obj.is_set() + self.assertTrue(called) + + time.sleep(1) + controller.poll() + + controller.poll_event_poll_wait_obj.wait(1) + called = controller.poll_event_poll_wait_obj.is_set() + self.assertTrue(called) + + time.sleep(1) + controller.poll() + + controller.poll_event_poll_wait_obj.wait(1) + called = controller.poll_event_poll_wait_obj.is_set() + self.assertTrue(called) + + controller.poll_event_poll_cancel_wait_obj.wait(1) + called = controller.poll_event_poll_cancel_wait_obj.is_set() + self.assertTrue(called) if __name__ == '__main__': unittest.main() diff --git a/gbpservice/nfp/bin/nfp_config_orch.ini b/gbpservice/nfp/bin/nfp_config_orch.ini index b1cb2b75da..0749b381e6 100644 --- a/gbpservice/nfp/bin/nfp_config_orch.ini +++ b/gbpservice/nfp/bin/nfp_config_orch.ini @@ -4,7 +4,7 @@ debug=False kombu_reconnect_delay=1.0 rabbit_use_ssl=False rabbit_virtual_host=/ -modules_dir=gbpservice.nfp.config_orchestrator.modules +nfp_modules_path=gbpservice.nfp.config_orchestrator.modules backend=rpc [RPC] diff --git a/gbpservice/nfp/bin/nfp_configurator.ini b/gbpservice/nfp/bin/nfp_configurator.ini index f6f52abfb0..52279c94a4 100644 --- a/gbpservice/nfp/bin/nfp_configurator.ini +++ b/gbpservice/nfp/bin/nfp_configurator.ini @@ -8,14 +8,15 @@ rabbit_hosts=127.0.0.1 rabbit_port=5672 rabbit_host= kombu_reconnect_delay=1.0 +control_exchange = openstack rabbit_use_ssl=False rabbit_virtual_host=/ -workers=1 -modules_dir=gbpservice.nfp.configurator.modules +workers=2 +nfp_modules_path=gbpservice.nfp.configurator.modules reportstate_interval=10 -periodic_interval=9 +periodic_interval=2 log_forward_ip_address= log_forward_port=514 diff --git a/gbpservice/nfp/bin/nfp_orch_agent.ini b/gbpservice/nfp/bin/nfp_orch_agent.ini index d9a706ce81..96e5371083 100644 --- a/gbpservice/nfp/bin/nfp_orch_agent.ini +++ b/gbpservice/nfp/bin/nfp_orch_agent.ini @@ -1,10 +1,10 @@ [DEFAULT] -workers=1 +workers=2 debug=False kombu_reconnect_delay=1.0 rabbit_use_ssl=False rabbit_virtual_host=/ -modules_dir=gbpservice.nfp.orchestrator.modules +nfp_modules_path=gbpservice.nfp.orchestrator.modules backend=rpc [REST] diff --git a/gbpservice/nfp/bin/nfp_proxy_agent.ini b/gbpservice/nfp/bin/nfp_proxy_agent.ini index d3655ae41a..f51adf78ab 100644 --- a/gbpservice/nfp/bin/nfp_proxy_agent.ini +++ b/gbpservice/nfp/bin/nfp_proxy_agent.ini @@ -4,10 +4,13 @@ debug=False kombu_reconnect_delay=1.0 rabbit_use_ssl=False rabbit_virtual_host=/ -modules_dir=gbpservice.nfp.proxy_agent.modules +nfp_modules_path=gbpservice.nfp.proxy_agent.modules backend=unix_rest -periodic_interval=10 +periodic_interval=2 [REST] #rest_server_address=192.168.102.22 #rest_server_port=8070 + +[oslo_policy] +policy_file = /etc/neutron/policy.json diff --git a/gbpservice/nfp/bin/proxy.ini b/gbpservice/nfp/bin/proxy.ini index 564cc9dda0..18babe5eea 100644 --- a/gbpservice/nfp/bin/proxy.ini +++ b/gbpservice/nfp/bin/proxy.ini @@ -5,7 +5,8 @@ max_connections=10 rest_server_address= 11.0.0.3 ##for docker ## rest_server_port= 8070 -worker_threads=40 +#[Note: worker threads should not be less than connect_max_wait_timeout/{periodic_interval or spacing for pull_notification}] +worker_threads=100 connect_max_wait_timeout=120 idle_max_wait_timeout=120 idle_min_wait_timeout=0.1 diff --git a/gbpservice/nfp/config_orchestrator/handlers/event/handler.py b/gbpservice/nfp/config_orchestrator/handlers/event/handler.py index ebb28eaabc..1d4ddc5a3b 100644 --- a/gbpservice/nfp/config_orchestrator/handlers/event/handler.py +++ b/gbpservice/nfp/config_orchestrator/handlers/event/handler.py @@ -19,7 +19,7 @@ from gbpservice.nfp.config_orchestrator.common import common from gbpservice.nfp.core.event import Event from gbpservice.nfp.core import log as nfp_logging -from gbpservice.nfp.core import poll as core_pt +from gbpservice.nfp.core import module as nfp_api from gbpservice.nfp.lib import transport from neutron import context as n_context @@ -62,7 +62,7 @@ def event_init(sc, conf): """Periodic Class to service events for visiblity.""" -class EventsHandler(core_pt.PollEventDesc): +class EventsHandler(nfp_api.NfpEventHandler): def __init__(self, sc, conf): self._sc = sc @@ -449,7 +449,7 @@ def _trigger_service_event(self, context, event_data, event_type): data=new_event_data) self._sc.post_event(new_ev) - @core_pt.poll_event_desc(event='SERVICE_CREATE_PENDING', spacing=5) + @nfp_api.poll_event_desc(event='SERVICE_CREATE_PENDING', spacing=5) def create_sevice_pending_event(self, ev): event_data = copy.deepcopy(ev.data) try: @@ -468,7 +468,7 @@ def create_sevice_pending_event(self, ev): LOG.error(msg) return STOP_POLLING - @core_pt.poll_event_desc(event='SERVICE_OPERATION_POLL_EVENT', spacing=5) + @nfp_api.poll_event_desc(event='SERVICE_OPERATION_POLL_EVENT', spacing=5) def service_operation_poll_stash_event(self, ev): events = self._sc.get_stashed_events() msg = ("Stash Queue is: %s" % (events)) diff --git a/gbpservice/nfp/config_orchestrator/modules/config_orch.py b/gbpservice/nfp/config_orchestrator/modules/config_orch.py index 3594da8c66..fef50b65f4 100644 --- a/gbpservice/nfp/config_orchestrator/modules/config_orch.py +++ b/gbpservice/nfp/config_orchestrator/modules/config_orch.py @@ -45,7 +45,7 @@ def rpc_init(sc, conf): 'agent_type': 'NFP Loadbalancer agent', 'configurations': {'device_drivers': ['loadbalancer']}, 'start_flag': True, - 'report_interval': conf.reportstate_interval + 'report_interval': 10 } lbrpcmgr = lb.LbAgent(conf, sc) lbagent = RpcAgent( @@ -64,7 +64,7 @@ def rpc_init(sc, conf): 'agent_type': 'NFP Vpn agent', 'configurations': {'device_drivers': ['vpn']}, 'start_flag': True, - 'report_interval': conf.reportstate_interval + 'report_interval': 10 } vpnrpcmgr = vpn.VpnAgent(conf, sc) vpnagent = RpcAgent( diff --git a/gbpservice/nfp/configurator/agents/agent_base.py b/gbpservice/nfp/configurator/agents/agent_base.py index 810b2d6a44..fdd509e0f7 100644 --- a/gbpservice/nfp/configurator/agents/agent_base.py +++ b/gbpservice/nfp/configurator/agents/agent_base.py @@ -10,6 +10,7 @@ # License for the specific language governing permissions and limitations # under the License. +from gbpservice.nfp.core import module as nfp_api from gbpservice.nfp.configurator.lib import constants as const from gbpservice.nfp.core import log as nfp_logging @@ -128,7 +129,7 @@ def _notification(self, data): self.sc.stash_event(event) -class AgentBaseEventHandler(object): +class AgentBaseEventHandler(nfp_api.NfpEventHandler): def __init__(self, sc, drivers, rpcmgr): self.sc = sc diff --git a/gbpservice/nfp/configurator/agents/firewall.py b/gbpservice/nfp/configurator/agents/firewall.py index dd5ceb1633..e7110c2065 100644 --- a/gbpservice/nfp/configurator/agents/firewall.py +++ b/gbpservice/nfp/configurator/agents/firewall.py @@ -20,6 +20,7 @@ from gbpservice.nfp.configurator.lib import utils as load_driver from gbpservice.nfp.core import event as nfp_event from gbpservice.nfp.core import log as nfp_logging +from gbpservice.nfp.core import module as nfp_api LOG = nfp_logging.getLogger(__name__) @@ -163,7 +164,7 @@ def delete_firewall(self, context, firewall, host): """ -class FWaasEventHandler(object): +class FWaasEventHandler(nfp_api.NfpEventHandler): def __init__(self, sc, drivers, rpcmgr, conf): """ Instantiates class object. diff --git a/gbpservice/nfp/configurator/agents/generic_config.py b/gbpservice/nfp/configurator/agents/generic_config.py index 9ba5159cfd..315fb40295 100644 --- a/gbpservice/nfp/configurator/agents/generic_config.py +++ b/gbpservice/nfp/configurator/agents/generic_config.py @@ -18,8 +18,8 @@ from gbpservice.nfp.configurator.lib import constants as common_const from gbpservice.nfp.configurator.lib import utils from gbpservice.nfp.core import event as nfp_event +from gbpservice.nfp.core import module as nfp_api from gbpservice.nfp.core import log as nfp_logging -from gbpservice.nfp.core import poll as nfp_poll LOG = nfp_logging.getLogger(__name__) @@ -171,7 +171,7 @@ def clear_healthmonitor(self, context, resource_data): class GenericConfigEventHandler(agent_base.AgentBaseEventHandler, - nfp_poll.PollEventDesc): + nfp_api.NfpEventHandler): def __init__(self, sc, drivers, rpcmgr): super(GenericConfigEventHandler, self).__init__( sc, drivers, rpcmgr) @@ -268,8 +268,8 @@ def _process_event(self, ev): if (resource_data.get('periodicity') == gen_cfg_const.INITIAL and result == common_const.SUCCESS): notification_data = self._prepare_notification_data(ev, result) - self.sc.poll_event_done(ev) self.notify._notification(notification_data) + return {'poll': False} elif resource_data.get('periodicity') == gen_cfg_const.FOREVER: if result == common_const.FAILED: """If health monitoring fails continuously for 5 times @@ -282,8 +282,8 @@ def _process_event(self, ev): notification_data = self._prepare_notification_data( ev, result) - self.sc.poll_event_done(ev) self.notify._notification(notification_data) + return {'poll': False} elif result == common_const.SUCCESS: """set fail_count to 0 if it had failed earlier even once """ @@ -293,8 +293,8 @@ def _process_event(self, ev): that particular service vm's health monitor """ notification_data = self._prepare_notification_data(ev, result) - self.sc.poll_event_done(ev) self.notify._notification(notification_data) + return {'poll': False} else: """For other events, irrespective of result send notification""" notification_data = self._prepare_notification_data(ev, result) @@ -355,7 +355,7 @@ def poll_event_cancel(self, ev): notification_data = self._prepare_notification_data(ev, result) self.notify._notification(notification_data) - @nfp_poll.poll_event_desc( + @nfp_api.poll_event_desc( event=gen_cfg_const.EVENT_CONFIGURE_HEALTHMONITOR, spacing=5) def handle_configure_healthmonitor(self, ev): @@ -367,7 +367,7 @@ def handle_configure_healthmonitor(self, ev): Returns: None """ - self._process_event(ev) + return self._process_event(ev) def events_init(sc, drivers, rpcmgr): diff --git a/gbpservice/nfp/configurator/agents/loadbalancer_v1.py b/gbpservice/nfp/configurator/agents/loadbalancer_v1.py index 25d54036f7..7c3d6cf587 100644 --- a/gbpservice/nfp/configurator/agents/loadbalancer_v1.py +++ b/gbpservice/nfp/configurator/agents/loadbalancer_v1.py @@ -16,11 +16,13 @@ from gbpservice.nfp.configurator.lib import lb_constants from gbpservice.nfp.configurator.lib import utils from gbpservice.nfp.core import event as nfp_event +from gbpservice.nfp.core import module as nfp_api from gbpservice.nfp.core import log as nfp_logging -from gbpservice.nfp.core import poll as nfp_poll + from neutron import context LOG = nfp_logging.getLogger(__name__) + """ Implements LBaaS response path to Neutron plugin. Methods of this class are invoked by the LBaasEventHandler class and also by driver class for sending response from driver to the LBaaS Neutron plugin. @@ -381,7 +383,7 @@ def agent_updated(self, context, payload): class LBaaSEventHandler(agent_base.AgentBaseEventHandler, - nfp_poll.PollEventDesc): + nfp_api.NfpEventHandler): instance_mapping = {} def __init__(self, sc, drivers, rpcmgr): @@ -458,7 +460,7 @@ def handle_event(self, ev): else: msg = ("Calling event done for event=%s" % (ev.id)) LOG.info(msg) - self.sc.event_done(ev) + self.sc.event_complete(ev) def _handle_event_vip(self, ev, operation): data = ev.data @@ -646,7 +648,7 @@ def _agent_updated(self, ev): def _collect_stats(self, ev): self.sc.poll_event(ev) - @nfp_poll.poll_event_desc(event=lb_constants.EVENT_COLLECT_STATS, + @nfp_api.poll_event_desc(event=lb_constants.EVENT_COLLECT_STATS, spacing=60) def collect_stats(self, ev): for pool_id, driver_name in LBaaSEventHandler.instance_mapping.items(): diff --git a/gbpservice/nfp/configurator/agents/vpn.py b/gbpservice/nfp/configurator/agents/vpn.py index fb458ebb5b..6b9e6b302f 100644 --- a/gbpservice/nfp/configurator/agents/vpn.py +++ b/gbpservice/nfp/configurator/agents/vpn.py @@ -18,9 +18,10 @@ from gbpservice.nfp.configurator.lib import data_filter from gbpservice.nfp.configurator.lib import utils from gbpservice.nfp.configurator.lib import vpn_constants as const -from gbpservice.nfp.core import event as main +from gbpservice.nfp.core import controller as main +from gbpservice.nfp.core.event import Event +from gbpservice.nfp.core import module as nfp_api from gbpservice.nfp.core import log as nfp_logging -from gbpservice.nfp.core import poll as nfp_poll import oslo_messaging as messaging @@ -161,7 +162,7 @@ def vpnservice_updated(self, context, **resource_data): self.sc.post_event(ev) -class VPNaasEventHandler(nfp_poll.PollEventDesc): +class VPNaasEventHandler(nfp_api.NfpEventHandler): """ Handler class to invoke the vpn driver methods. For every event that gets invoked from worker process lands over here @@ -285,7 +286,7 @@ def _sync_ipsec_conns(self, context, svc_context): % str(err).capitalize()) LOG.error(msg) - @nfp_poll.poll_event_desc(event='VPN_SYNC', spacing=10) + @nfp_api.poll_event_desc(event='VPN_SYNC', spacing=10) def sync(self, ev): """Periodically updates the status of vpn service, whether the tunnel is UP or DOWN. @@ -315,9 +316,9 @@ def events_init(sc, drivers): Returns: None """ evs = [ - main.Event(id='VPNSERVICE_UPDATED', + Event(id='VPNSERVICE_UPDATED', handler=VPNaasEventHandler(sc, drivers)), - main.Event(id='VPN_SYNC', + Event(id='VPN_SYNC', handler=VPNaasEventHandler(sc, drivers))] sc.register_events(evs) diff --git a/gbpservice/nfp/configurator/api/v1/controllers/controller.py b/gbpservice/nfp/configurator/api/v1/controllers/controller.py index 2dba97e3a9..d24c7d92a8 100644 --- a/gbpservice/nfp/configurator/api/v1/controllers/controller.py +++ b/gbpservice/nfp/configurator/api/v1/controllers/controller.py @@ -12,7 +12,7 @@ import oslo_serialization.jsonutils as jsonutils -from neutron.agent.common import config +# from neutron.agent.common import config from neutron.common import rpc as n_rpc from oslo_config import cfg from oslo_log import log as logging @@ -141,8 +141,8 @@ def post(self, **body): pecan.response.status = 400 msg = ("Failed to serve HTTP post request %s %s." % (self.method_name, str(err).capitalize())) - extra_import = ("need to remove this import %s" % config) - LOG.debug(extra_import) + # extra_import = ("need to remove this import %s" % config) + # LOG.debug(extra_import) LOG.error(msg) error_data = self._format_description(msg) return jsonutils.dumps(error_data) diff --git a/gbpservice/nfp/core/cfg.py b/gbpservice/nfp/core/cfg.py index 5052adab5d..345bc24531 100644 --- a/gbpservice/nfp/core/cfg.py +++ b/gbpservice/nfp/core/cfg.py @@ -10,61 +10,57 @@ # License for the specific language governing permissions and limitations # under the License. -from oslo_config import cfg -es_openstack_opts = [ - cfg.StrOpt('auth_host', - default='localhost', - help='Openstack controller IP Address'), - cfg.StrOpt('admin_user', - help='Admin user name to create service VMs'), - cfg.StrOpt('admin_password', - help='Admin password to create service VMs'), - cfg.StrOpt('admin_tenant_name', - help='Admin tenant name to create service VMs'), - cfg.StrOpt('admin_tenant_id', - help='Admin tenant ID to create service VMs'), - cfg.StrOpt('auth_protocol', - default='http', help='Auth protocol used.'), - cfg.IntOpt('auth_port', - default='5000', help='Auth protocol used.'), - cfg.IntOpt('bind_port', - default='9696', help='Auth protocol used.'), - cfg.StrOpt('auth_version', - default='v2.0', help='Auth protocol used.'), - cfg.StrOpt('auth_uri', - default='', help='Auth URI.'), -] +from oslo_config import cfg as oslo_config + +# from gbpservice.nfp.core import version -OPTS = [ - cfg.IntOpt( +NFP_OPTS = [ + oslo_config.IntOpt( 'workers', - default=4, + default=1, help='Number of event worker process to be created.' ), - cfg.StrOpt( - 'modules_dir', + oslo_config.StrOpt( + 'nfp_modules_path', default='gbpservice.nfp.core.test', help='Path for NFP modules.' 'All modules from this path are autloaded by framework' - ), - cfg.IntOpt( - 'periodic_interval', - default=2, - help='Interval for event polling task in seconds.' - 'Polling task wakesup with this interval and' - 'checks for timedout events.' - ), - cfg.IntOpt( - 'reportstate_interval', - default=10, - help='Interval for report state task in seconds.' - 'Reporting task will report neutron agents state' - 'to the plugins at this interval' - ), - cfg.BoolOpt( - 'core_debug', - default=False, - help='Main debug' ) ] + +es_openstack_opts = [ + oslo_config.StrOpt('auth_host', + default='localhost', + help='Openstack controller IP Address'), + oslo_config.StrOpt('admin_user', + help='Admin user name to create service VMs'), + oslo_config.StrOpt('admin_password', + help='Admin password to create service VMs'), + oslo_config.StrOpt('admin_tenant_name', + help='Admin tenant name to create service VMs'), + oslo_config.StrOpt('admin_tenant_id', + help='Admin tenant ID to create service VMs'), + oslo_config.StrOpt('auth_protocol', + default='http', help='Auth protocol used.'), + oslo_config.IntOpt('auth_port', + default='5000', help='Auth protocol used.'), + oslo_config.IntOpt('bind_port', + default='9696', help='Auth protocol used.'), + oslo_config.StrOpt('auth_version', + default='v2.0', help='Auth protocol used.'), + oslo_config.StrOpt('auth_uri', + default='', help='Auth URI.'), +] + + +def init(args, **kwargs): + """Initialize the configuration. """ + oslo_config.CONF.register_opts(NFP_OPTS) + oslo_config.CONF.register_opts(es_openstack_opts, "keystone_authtoken") + oslo_config.CONF(args=args, project='nfp', + version='%%(prog)s %s' % ('version'), + #version.version_info.release_string()), + **kwargs) + + return oslo_config.CONF diff --git a/gbpservice/nfp/core/common.py b/gbpservice/nfp/core/common.py index b15a52eaec..f0d0866ce3 100644 --- a/gbpservice/nfp/core/common.py +++ b/gbpservice/nfp/core/common.py @@ -10,14 +10,22 @@ # License for the specific language governing permissions and limitations # under the License. -import collections +import os import pdb -import Queue -import sys import sys +import time + +from oslo_config import cfg as oslo_cfg +from oslo_log import log as oslo_logging + +oslo_logging.register_options(oslo_cfg.CONF) -deque = collections.deque +class Object(object): pass +def init(): + """Initialize logging. """ + product_name = "nfp" + oslo_logging.setup(oslo_cfg.CONF, product_name) class ForkedPdb(pdb.Pdb): @@ -61,90 +69,27 @@ def _name(obj): def identify(obj): - """Helper method to display identify an object. + """Helper method to display identity an object. Useful for logging. Decodes based on the type of obj. Supports 'class' & 'method' types for now. + + :param obj: Object (Class/Method supported.) + Returns: String. Identification of the object. """ + prefix = obj._NAME_ if hasattr(obj, '_NAME_') else '' try: - return "(%s)" % (_name(obj)) + return "([%s] %s)" % (prefix, _name(obj)) except Exception: - """Some unknown type, returning empty """ + # Some unknown type, returning empty return "" -"""Wrapper class over python deque. - - Implements firsinfirsout logic. - New methods to support 'get' more than one element, - 'copy' the queue, 'remove' multiple messages are added. -""" - - -class NfpFifo(object): - - class Empty(Exception): - - """Exception raised when queue is empty and dequeue is attempted. - """ - pass - - class Full(Exception): - - """Exception raised when queue is full and enqueue is attempted. - """ - pass - - def __init__(self, sc, maxsize=-1): - self._sc = sc - self._size = sys.maxint if maxsize == -1 else maxsize - self._queue = deque() +def time_stamp(): + """Current time stamp in milliseconds. - def _qsize(self): - return len(self._queue) - - def _is_empty(self): - if not self._qsize(): - raise Queue.Empty() - - def _is_full(self): - if self._size == self._qsize(): - raise Queue.Full() - - def _pop(self, out): - self._is_empty() - out.append(self._queue.popleft()) - return out - - def put(self, msg): - """Puts a message in queue. """ - self._is_full() - self._queue.append(msg) - - def get(self, limit=sys.maxint): - """Get max requested number of messages. - - If there are less messages in the queue than requested, - then available number of messages are returned. - """ - msgs = [] - try: - for i in range(0, limit): - msgs = self._pop(msgs) - except Queue.Empty: - pass - finally: - return msgs - - def copy(self): - """Return the copy of queue. """ - qu = list(self._queue) - return qu + Returns: time stamp in milliseconds. + """ + _time_ms = lambda: int(round(time.time() * 1000.0)) + return _time_ms() - def remove(self, msgs): - """Remove list of messages from the fifo """ - try: - for msg in msgs: - self._queue.remove(msg) - except ValueError as err: - err = err diff --git a/gbpservice/nfp/core/context.py b/gbpservice/nfp/core/context.py new file mode 100644 index 0000000000..e002ca322b --- /dev/null +++ b/gbpservice/nfp/core/context.py @@ -0,0 +1,34 @@ +# 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 threading + +nfp_context_store = threading.local() + +class NfpContext(object): + def __init__(self, context): + self.context = context + + def get_context(self): + return self.context + +def store_nfp_context(context): + nfp_context_store.context = NfpContext(context) + +def clear_nfp_context(): + nfp_context_store.context = None + +def get_nfp_context(): + context = getattr(nfp_context_store, 'context', None) + if context: + return context.get_context() + return {} diff --git a/gbpservice/nfp/core/controller.py b/gbpservice/nfp/core/controller.py index 1acc85b8d4..26c1d116bd 100644 --- a/gbpservice/nfp/core/controller.py +++ b/gbpservice/nfp/core/controller.py @@ -17,128 +17,184 @@ import multiprocessing import operator import os +import pickle import Queue import sys import time import zlib -from neutron.agent.common import config as n_config -from neutron.common import config as n_common_config - -from oslo_config import cfg as oslo_config from oslo_service import service as oslo_service -from gbpservice.nfp.core import cfg as nfp_config +from gbpservice.nfp.core import cfg as nfp_cfg from gbpservice.nfp.core import common as nfp_common from gbpservice.nfp.core import event as nfp_event -from gbpservice.nfp.core import event_lb as nfp_lb +from gbpservice.nfp.core import launcher as nfp_launcher +from gbpservice.nfp.core import manager as nfp_manager from gbpservice.nfp.core import poll as nfp_poll from gbpservice.nfp.core import rpc as nfp_rpc +from gbpservice.nfp.core import worker as nfp_worker from gbpservice.nfp.core import log as nfp_logging +# REVISIT (MAK): Unused, but needed for orchestrator, +# remove from here and add in orchestrator +from neutron.common import config + LOG = nfp_logging.getLogger(__name__) +PIPE = multiprocessing.Pipe +PROCESS = multiprocessing.Process identify = nfp_common.identify +"""Implements NFP service. -"""Implements table of registered event handlers. """ + Base class for nfp modules, modules can invoke methods + of this class to interact with core. +""" -class EventHandlers(object): +class NfpService(object): - def __init__(self): - self._event_handlers = {} + def __init__(self, conf): + self._conf = conf + self._event_handlers = nfp_event.NfpEventHandlers() + self._rpc_agents = list() + + def _make_new_event(self, event): + """Make a new event from the object passed. """ + desc = event.desc + event_dict = event.__dict__ + event = self.create_event(**event_dict) + event.desc.from_desc(desc) + return event - def register(self, event_desc): - """Register an event handler. """ - ehs = self._event_handlers - LOG.debug("Registering handler %s" % - (self.identify(event_desc))) - try: - ehs[event_desc.id].extend([event_desc]) - except KeyError: - ehs[event_desc.id] = [event_desc] + def get_event_handlers(self): + return self._event_handlers + + def register_events(self, event_descs): + """Register event handlers with core. """ + # REVISIT (MAK): Can the name be changed + # to register_event_handlers() ? + for event_desc in event_descs: + self._event_handlers.register(event_desc.id, event_desc.handler) + + def register_rpc_agents(self, agents): + """Register rpc handlers with core. """ + for agent in agents: + self._rpc_agents.append((agent,)) + + def new_event(self, **kwargs): + """Define and return a new event. """ + return self.create_event(**kwargs) - def get(self, event_desc): - """Return handler of an event. """ - ehs = self._event_handlers + def create_event(self, **kwargs): + """To create a new event. """ + event = None try: - # There could be multiple handlers for a given event, - # returning first one in the list. - # REVISIT(mak): should multiple handlers be allowed ? - return ehs[event_desc.id][0].handler - except KeyError: - return None - - def identify(self, event): - return "%s - %s" % (event.identify(), identify(event.handler)) - - -"""Common class implements all the APIs & cache. - - Common class used across modules and classes to access the - cache of required objects. - Also, implements the abstracted APIs for NFP modules. - All the registered handlers, NFP modules, worker process, rpc agents - etc all instantiated, stored, maintained in this class. - mixin class for interactions between different classes - like event handler, poll handler etc. - Distributor & each worker process has copy of Controller object. -""" + event = nfp_event.Event(**kwargs) + except AssertionError as aerr: + LOG.exception("%s" % (aerr)) + return event + def post_event_graph(self, event): + """Post a event graph. -class Controller(object): + As base class, set only the required + attributes of event. + """ + event.desc.type = nfp_event.EVENT_GRAPH + event.desc.flag = '' + event.desc.pid = os.getpid() + return event - def __init__(self, conf, modules): - # Configuration object - self._conf = conf - # Cache of auto-loaded NFP modules - self._modules = modules - # Sequencer to sequence the related events. - self._sequencer = nfp_event.EventSequencer(self) - # Map of worker pid to pipe assoc with it. - self._worker_pipe_map = {} - # Identity of process which is executing this controller obj. - self._process_name = 'distributor-process' - # To wait on an event to be complete. - self._event = multiprocessing.Event() - # - # Queue to stash events. - self._stashq = multiprocessing.Queue() + def post_event(self, event): + """Post an event. - def start(self): - """Starts all the execution contexts. + As a base class, it only does the descriptor preparation. + NfpController class implements the required functionality. + """ + handler = self._event_handlers.get_event_handler(event.id) + assert handler, "No handler registered for event %s" % (event.id) + event.desc.type = nfp_event.SCHEDULE_EVENT + event.desc.flag = nfp_event.EVENT_NEW + event.desc.pid = os.getpid() + return event - Starts worker process, rpc agents, polling task, - report task. + # REVISIT (MAK): spacing=0, caller must explicitly specify + def poll_event(self, event, spacing=2, max_times=sys.maxint): + """To poll for an event. - Executor: distributor-process + As a base class, it only does the polling + descriptor preparation. + NfpController class implements the required functionality. """ - self._init() + ev_spacing = self._event_handlers.get_poll_spacing(event.id) + assert spacing or ev_spacing, "No spacing specified for polling" + if ev_spacing: + spacing = ev_spacing - for worker in self._workers: - worker[0].start() - self._worker_pipe_map[worker[0].pid] = worker[1] + handler = self._event_handlers.get_poll_handler(event.id) + assert handler, "No poll handler found for event %s" % (event.id) - # Polling task to poll for timer events - self._polling_task = nfp_poll.PollingTask(self) - # Seperate task for reporting as report state rpc is a 'call' - self._reportstate_task = nfp_rpc.ReportStateTask(self) + refuuid = event.desc.uuid + event = self._make_new_event(event) + event.lifetime = 0 + event.desc.type = nfp_event.POLL_EVENT - for idx, agent in enumerate(self._rpc_agents): - launcher = oslo_service.launch(oslo_config.CONF, agent[0]) - self._rpc_agents[idx] = agent + (launcher,) + kwargs = {'spacing': spacing, + 'max_times': max_times, + 'ref': refuuid} + poll_desc = nfp_event.PollDesc(**kwargs) - # Wait for every worker to be up - time.sleep(self._conf.workers * 1 + 1) + setattr(event.desc, 'poll_desc', poll_desc) + return event - def wait(self): - """To wait for workers. + def event_complete(self, event, result=None): + """To declare and event complete. """ + try: + pickle.dumps(result) + event.sequence = False + event.desc.flag = nfp_event.EVENT_COMPLETE + event.result = result + return event + except Exception as e: + raise e - Executor: distributor-process - """ - # self.rpc_agents.wait() - for w in self._workers: - w[0].join() + def create_work(self, work): + """Create a work, collection of events. """ + pass + + +"""NFP Controller class mixin other nfp classes. + + Nfp modules get the instance of this class when + they are initialized. + Nfp modules interact with core using the methods + of 'Service' class, whose methods are implemented + in this class. + Also, it mixes the other nfp core classes to complete + a nfp module request. +""" + + +class NfpController(nfp_launcher.NfpLauncher, NfpService): + + def __init__(self, conf): + # Init the super classes. + nfp_launcher.NfpLauncher.__init__(self, conf) + NfpService.__init__(self, conf) + + # For book keeping + self._worker_process = {} + self._conf = conf + self._pipe = None + # Queue to stash events. + self._stashq = multiprocessing.Queue() + + self._manager = nfp_manager.NfpResourceManager(conf, self) + self._worker = nfp_worker.NfpWorker(conf) + self._poll_handler = nfp_poll.NfpPollHandler(conf) + + # ID of process handling this controller obj + self.PROCESS_TYPE = "distributor" def compress(self, event): if event.data and not event.zipped: @@ -158,164 +214,208 @@ def decompress(self, event): e)) raise e - def post_event(self, event): - """API for NFP module to generate a new internal event. - - Schedules this event to one of the worker. 'binding_key' is - glue between different events, all events with same 'binding_key' - are scheduled to same worker process. + def pipe_send(self, pipe, event): + self.compress(event) + pipe.send(event) - Executor: distributor-process, worker-process + def _fork(self, args): + proc = PROCESS(target=self.child, args=args) + proc.daemon = True + proc.start() + return proc + + def _manager_task(self): + while True: + # Run 'Manager' here to monitor for workers and + # events. + self._manager.manager_run() + eventlet.greenthread.sleep(0.01) + + def _update_manager(self): + childs = self.get_childrens() + for pid, wrapper in childs.iteritems(): + pipe = wrapper.child_pipe_map[pid] + # Inform 'Manager' class about the new_child. + self._manager.new_child(pid, pipe) + + def _process_event(self, event): + self._manager.process_events([event]) + + def get_childrens(self): + # oslo_process.ProcessLauncher has this dictionary, + # 'NfpLauncher' derives oslo_service.ProcessLauncher + return self.children + + def fork_child(self, wrap): + """Forks a child. + + Creates a full duplex pipe for child & parent + to communicate. + + Returns: Multiprocess object. """ - if self._process_name == 'worker-process': - # Worker cannot distribute events, so post it to distributor - pipe = self._worker_pipe_map[os.getpid()] - self._pipe_send(pipe, event) - LOG.debug("%s - post event - " - "worker >> distributor" % (event.identify())) - else: - worker = self._loadbalancer.get(event.binding_key) - event.desc.worker_attached = worker[0].pid - # Getting queue element of tuple (mp_process, mp_pipe, - # eventq_handler) - pipe = worker[1] - self._pipe_send(pipe, event) - LOG.debug("%s - post event - " - "distributor >> worker:%d" - % (event.identify(), event.desc.worker_attached)) - - if event.lifetime: - self._add_lifetime_event(event) - - def event_done(self, event): - """API for NFP modules to mark an event complete. - - This is how framework learns that an event is complete and - any other sequenced event can now be scheduled. - Ideally, for event module at some point should call event_done. - Executor: worker-process - """ - LOG.debug("%s - event complete" % (event.identify())) - seq_map = self._sequencer.copy() + parent_pipe, child_pipe = PIPE(duplex=True) + + # Registered event handlers of nfp module. + # Workers need copy of this data to dispatch an + # event to module. + proc = self._fork(args=(wrap.service, parent_pipe, child_pipe, self)) + + LOG.info("Forked a new child: %d" + "Parent Pipe: % s, Child Pipe: % s" % ( + proc.pid, str(parent_pipe), str(child_pipe))) + try: - seq_q = seq_map[event.binding_key]['queue'] - for seq_event in seq_q: - if seq_event.desc.uid == event.desc.uid: - LOG.debug("%s - removing from sequencer" - % (seq_event.identify())) - self._sequencer.remove(seq_event) - break - self._sequencer.delete_eventmap(event) - except KeyError as err: - LOG.debug("%s - event not in sequencer" % - (event.identify())) - # Event not in sequence map - # Not an issue, event might not have serialized - err = err - pass + wrap.child_pipe_map[proc.pid] = parent_pipe + except AttributeError: + setattr(wrap, 'child_pipe_map', {}) + wrap.child_pipe_map[proc.pid] = parent_pipe + + self._worker_process[proc.pid] = proc + return proc.pid - def poll_event(self, event, max_times=sys.maxint): - """API for NFP modules to generate a new poll event. + def launch(self, workers): + """Launch the controller. - Adds event to pollq for the poller to poll on it - periodically. - max_times - Defines the max number of times this event - can timeout, after that event is auto cancelled. + Uses Oslo Service to launch with configured #of workers. + Spawns a manager task to manager nfp events & workers. - Executor: distributor-process, worker-process + :param workers: #of workers to be launched + + Returns: None """ - if self._process_name == 'distributor-process': - if not event.desc.worker_attached: - LOG.debug("%s - poll event - " - "no worker is associated" % (event.identify())) - # Get some worker from the pool. - worker = self._loadbalancer.get(None) - event.desc.worker_attached = worker[0].pid - LOG.debug("%s - poll event - " - "distributor - adding to poller" % ( - event.identify())) - event.desc.poll_event = 'POLL_EVENT' - event.max_times = max_times - self._pollhandler.add_event(event) - else: - LOG.debug("%s - poll event - " - "worker:%d >> distributor" - % (event.identify(), os.getpid())) - event.desc.poll_event = 'POLL_EVENT' - event.max_times = max_times - event.desc.worker_attached = os.getpid() - pipe = self._worker_pipe_map[os.getpid()] - self._pipe_send(pipe, event) + super(NfpController, self).launch_service( + self._worker, workers=workers) - def poll_event_timedout(self, eh, event): - """Abstract method for poll handler. + def post_launch(self): + """Post processing after workers launch. - Demuxes the type of timedout and invokes - the proper method. + Tasks which needs to run only on distributor + process and any other resources which are not + expected to be forked are initialized here. + """ + self._update_manager() + + # Launch rpc_agents + for index, rpc_agent in enumerate(self._rpc_agents): + # Use threads for launching service + launcher = oslo_service.launch( + self._conf, rpc_agent[0], workers=None) + self._rpc_agents[index] = rpc_agent + (launcher,) + + # One task to manage the resources - workers & events. + eventlet.spawn_n(self._manager_task) + # Oslo periodic task to poll for timer events + nfp_poll.PollingTask(self._conf, self) + # Oslo periodic task for state reporting + nfp_rpc.ReportStateTask(self._conf, self) + + def poll_add(self, event, timeout, callback): + """Add an event to poller. """ + self._poll_handler.poll_add( + event, timeout, callback) + + def poll(self): + """Invoked in periodic task to poll for timedout events. """ + self._poll_handler.run() - Executor: worker-process + def report_state(self): + """Invoked by report_task to report states of all agents. """ + for agent in self._rpc_agents: + rpc_agent = operator.itemgetter(0)(agent) + rpc_agent.report_state() + + def post_event_graph(self, event): + """Post a new event graph into system. + + Graph is a collection of events to be + executed in a certain manner. Use the + commonly defined 'Event' class to define + even the graph. + + :param event: Object of 'Event' class. + + Return: None """ - LOG.debug("%s - poll event timedout - " % - (event.identify())) - if event.id == 'EVENT_EXPIRED': - self._pollhandler.event_expired(eh, event) - # Call done to auto cancel the event - self.event_done(event) + event = super(NfpController, self).post_event_graph(event) + LOG.debug("(event - %s) - New event" % (event.identify())) + if self.PROCESS_TYPE == "worker": + # Event posted in worker context, send it to parent process + LOG.debug("(event - %s) - new event in worker" + "posting to distributor process" % (event.identify())) + # Send it to the distributor process + self.pipe_send(self._pipe, event) else: - self._pollhandler.event_timedout(eh, event) + LOG.debug("(event - %s) - new event in distributor" + "processing event" % (event.identify())) + self._manager.process_events([event]) + + def post_event(self, event): + """Post a new event into the system. - def poll_event_done(self, event): - """API for NFP modules to mark a poll event complete. + If distributor(main) process posts an event, it + is delivered to the worker. + If worker posts an event, it is deliverd to + distributor for processing, where it can decide + to loadbalance & sequence events. - If on any condition, module logic decides to stop polling - for an event before it gets auto cancelled, then this - method can be invoked. + :param event: Object of 'Event' class. - Executor: worker-process + Returns: None """ - LOG.debug("%s - poll event complete" % (event.identify())) - event.id = 'POLL_EVENT_CANCEL' - self.poll_event(event) + event = super(NfpController, self).post_event(event) + LOG.debug("(event - %s) - New event" % (event.identify())) + if self.PROCESS_TYPE == "worker": + # Event posted in worker context, send it to parent process + LOG.debug("(event - %s) - new event in worker" + "posting to distributor process" % (event.identify())) + # Send it to the distributor process + self.pipe_send(self._pipe, event) + else: + LOG.debug("(event - %s) - new event in distributor" + "processing event" % (event.identify())) + self._manager.process_events([event]) - def new_event(self, **kwargs): - """API for NFP modules to prep an Event from passed args """ - event = nfp_event.Event(**kwargs) - logging_context = nfp_logging.get_logging_context() - event.context = logging_context - desc = nfp_event.EventDesc(**kwargs) - setattr(event, 'desc', desc) - return event + def poll_event(self, event, spacing=2, max_times=sys.maxint): + """Post a poll event into the system. - def register_events(self, events): - """API for NFP modules to register events """ - # Ignore if happens from worker - if self._process_name == 'worker': - return - for event in events: - LOG.debug("%s - registered handler - %s" - % (event.identify(), identify(event.handler))) - self._event_handlers.register(event) + Core will poll for this event to timeout, after + timeout registered handler of module is invoked. - def register_rpc_agents(self, agents): - """API for NFP mofules to register rpc agents """ - # Ignore if happens from worker - if self._process_name == 'worker': - return - for agent in agents: - self._rpc_agents.extend([(agent,)]) + :param event: Object of 'Event' class. + :param spacing: Spacing at which event should timeout. + :param max_times: Max #of times the event can timeout, + after the max_times, event is auto cancelled by + the core and the registered handler of module + is invoked. - def init_complete(self): - """Invokes NFP modules init_complete() to do any post init logic """ - for module in self._modules: - LOG.debug("Invoking init_complete() of module %s" - % (identify(module))) - try: - module.nfp_module_post_init(self, self._conf) - except AttributeError: - LOG.debug("Module %s does not implement" - "nfp_module_post_init() method - skipping" - % (identify(module))) + Returns: None + """ + # Poll event can only be posted by worker not by listener process + if self.PROCESS_TYPE != "worker": + LOG.debug( + "(event - %s) - poll event in distributor") + # 'Service' class to construct the poll event descriptor + event = super(NfpController, self).poll_event( + event, spacing=spacing, max_times=max_times) + self._manager.process_events([event]) + else: + ''' + # Only event which is delivered to a worker can be polled for, coz, + # after event timeouts, it should be delivered to the same worker, + # hence the check to make sure the correct event is been asked for + # polling. + assert event.desc.worker, "No worker for event %s" % ( + event.identify()) + LOG.debug("(event - %s) - poll event in worker" % + (event.identify())) + ''' + # 'Service' class to construct the poll event descriptor + event = super(NfpController, self).poll_event( + event, spacing=spacing, max_times=max_times) + # Send to the distributor process. + self.pipe_send(self._pipe, event) def stash_event(self, event): """To stash an event. @@ -326,12 +426,11 @@ def stash_event(self, event): Executor: worker-process """ - if self._process_name == 'distributor-process': - LOG.error("%s - distributor - cannot stash event" % ( + if self.PROCESS_TYPE == "distributor": + LOG.error("(event - %s) - distributor cannot stash" % ( event.identify())) else: - LOG.debug("%s - worker - stashed" % (event.identify())) - self.compress(event) + LOG.debug("(event - %s) - stashed" % (event.identify())) self._stashq.put(event) def get_stashed_events(self): @@ -344,245 +443,122 @@ def get_stashed_events(self): Executor: distributor-process """ events = [] + # return at max 5 events + maxx = 5 + # wait sometime for first event in the queue timeout = 0.1 - try: - event = self._stashq.get(timeout=timeout) - self.decompress(event) - events.append(event) - timeout = 0 - except Queue.Empty: - pass + while maxx: + try: + event = self._stashq.get(timeout=timeout) + self.decompress(event) + events.append(event) + timeout = 0 + maxx -= 1 + except Queue.Empty: + maxx = 0 + pass return events - def sequencer_put_event(self, event): - """Put an event in sequencer. + def event_complete(self, event, result=None): + """To mark an event complete. - Check if event needs to be sequenced, this is module logic choice. - If yes, then invokes sequencer. If this is the first event in - sequence, it is returned immediately, all subsequent events will be - sequenced by sequencer till this event is complete. + Module can invoke this API to mark an event complete. + a) Next event in sequence will be scheduled. + b) Event from cache is removed. + c) Polling for event is stopped. + d) If the worker dies before event is complete, the + event is scheduled to other available workers. - Executor: worker-process. - """ - if not event.serialize: - return event - if not self._sequencer.add(event): - return event - return None + :param event: Obj of 'Event' class - def sequencer_get_event(self): - """Get an event from the sequencer map. - - Executor: worker-process. + Returns: None """ - return self._sequencer.get() - - def report_state(self): - """Invoked by report_task to report states of all agents. - - Executor: report-task of distributor-process. - """ - for agent in self._rpc_agents: - rpc_agent = operator.itemgetter(0)(agent) - rpc_agent.report_state() - - def timeout(self): - """Invoked by poll task to handle timer events. - - Executor: periodic-task of distributor-process. - """ - self._pollhandler.run() - - def post_timedoutevent(self, event): - """To post a timedout event to a worker. - - Timedout event should be posted to same worker - which generated it. - Invoked by timer task to schedule a timedout - event to the correct worker. - - Executor: poll-task of distributor-process - """ - if not event.desc.worker_attached: - LOG.error("%s - timedoutevent - " - "no worker attached, dropping" % (event.identify())) + LOG.debug("(event - %s) complete" % (event.identify())) + event = super(NfpController, self).event_complete(event, result=result) + if self.PROCESS_TYPE == "distributor": + self._manager.process_events([event]) else: - pipe = self._worker_pipe_map[event.desc.worker_attached] - LOG.debug("%s - timedoutevent -" - "to worker:%d" % ( - event.identify(), event.desc.worker_attached)) - self._pipe_send(pipe, event) - - def modules_init(self, modules): - """Initializes all the loaded NFP modules. - - Invokes "nfp_module_init" method of each module. - Module can register its rpc & event handlers. - - Executor: distributor-process - """ - inited_modules = [] - for module in modules: - LOG.debug("Initializing module %s" % - (identify(module))) - try: - module.nfp_module_init(self, self._conf) - inited_modules.append(module) - LOG.info("module - %s - initialized" % - (identify(module))) - except AttributeError: - LOG.error("module - %s - " - "nfp_module_init() missing, skip loading" - % (identify(module))) - continue - return inited_modules - - def _poll_handler_init(self): - """Initialize poll handler. - - Pollhandler will process the events to be polled - from worker. - - Executor: distributor-process - """ - # Prepare list of parent side of pipes with each child - pipes = [] - for worker in self._workers: - pipes.append(worker[1]) - - handler = nfp_poll.PollQueueHandler(self, pipes, self._event_handlers) - return handler - - def _init(self): - """Intializes the NFP multi process framework. - - Top level method to initialize all the resources required. - - Executor: distributor-process - """ - self._event_handlers = EventHandlers() - self._rpc_agents = [] - self._modules = self.modules_init(self._modules) - self._workers = self._workers_init() - self._pollhandler = self._poll_handler_init() - self._loadbalancer = nfp_lb.StickyRoundRobin(self._workers) - - def _workers_init(self): - """Initialize the configured number of worker process. + # Send to the distributor process. + self.pipe_send(self._pipe, event) - This method just creates the process and not start them. - An event queue per worker process is created. - - Executor: distributor-process. - """ - wc = oslo_config.CONF.workers - LOG.info("Creating %d number of workers" % (wc)) - - ev_workers = [tuple() for w in range(0, wc)] - - for w in range(0, wc): - # Create pipe for communication. - ppipe, cpipe = multiprocessing.Pipe(duplex=True) - # Worker class. - evq_handler = nfp_event.EventQueueHandler( - self, self._conf, cpipe, self._event_handlers, self._modules) - mp_process = multiprocessing.Process - # create process using multiprocessing. - worker = mp_process(target=evq_handler.run, args=(cpipe,)) - worker.daemon = True - ev_workers[w] = ev_workers[w] + (worker, ppipe, evq_handler) - return ev_workers - - def _add_lifetime_event(self, event): - """Add a timer event to poll for lifetime of an event. - - For every new event generated in core and lifetime - set, polling event is auto added. After event expires - it is autocancelled and module is informed. - - Executor: distributor-process - """ - # convert event lifetime in to polling time - max_times = int( - event.lifetime / self._conf.periodic_interval) - if event.lifetime % self._conf.periodic_interval: - max_times += 1 - - timer_ev = self.new_event( - id='POLL_EVENT_EXPIRY', data=event, - binding_key=event.binding_key, key=event.desc.uid) - timer_ev.desc.worker_attached = event.desc.worker_attached - self.poll_event(timer_ev, max_times=max_times) - - def _pipe_send(self, pipe, event): - """Send data to a pipe. - - """ - self.compress(event) - pipe.send(event) - - -def modules_import(): - """Imports all the .py files from specified modules dir """ - modules = [] - base_module = __import__(oslo_config.CONF.modules_dir, - globals(), locals(), ['modules'], -1) - - modules_dir = base_module.__path__[0] - - syspath = sys.path - sys.path = [modules_dir] + syspath +def load_nfp_modules(conf, controller): + """ Load all nfp modules from configured directory. """ + pymodules = [] try: - files = os.listdir(modules_dir) - except OSError: - LOG.error("Failed to read files from directory %s" % - (modules_dir)) - files = [] - - for fname in files: - if fname.endswith(".py") and fname != '__init__.py': - try: - module = __import__(oslo_config.CONF.modules_dir, - globals(), locals(), [fname[:-3]], -1) - modules += [eval('module.%s' % (fname[:-3]))] - except Exception as exc: - LOG.error("NFP module %s import failed." % (fname)) - - sys.path = syspath - return modules - - -def common_init(): - oslo_config.CONF.register_opts(nfp_config.OPTS) - oslo_config.CONF.register_opts( - nfp_config.es_openstack_opts, "keystone_authtoken") - - # Since other imports are registering the logging configuration - # parameters, these are overridden to make sure that the core - # configuration parameters are effective. - oslo_config.CONF.set_override('use_syslog', 'True') - oslo_config.CONF.set_override('syslog_log_facility', 'local1') - - # n_config.register_interface_driver_opts_helper(oslo_config.CONF) - # n_config.register_agent_state_opts_helper(oslo_config.CONF) - # n_config.register_root_helper(oslo_config.CONF) - - n_common_config.init(sys.argv[1:]) - n_config.setup_logging() - + base_module = __import__(conf.nfp_modules_path, + globals(), locals(), ['modules'], -1) + modules_dir = base_module.__path__[0] + try: + files = os.listdir(modules_dir) + for pyfile in set([f for f in files if f.endswith(".py")]): + try: + pymodule = __import__(conf.nfp_modules_path, + globals(), locals(), + [pyfile[:-3]], -1) + pymodule = eval('pymodule.%s' % (pyfile[:-3])) + try: + pymodule.nfp_module_init(controller, conf) + pymodules += [pymodule] + LOG.debug("(module - %s) - Initialized" % + (identify(pymodule))) + except AttributeError as e: + import sys + import traceback + exc_type, exc_value, exc_traceback = sys.exc_info() + print traceback.format_exception(exc_type, exc_value, + exc_traceback) + LOG.warn("(module - %s) - " + "does not implement" + "nfp_module_init()" % (identify(pymodule))) + except ImportError: + LOG.error( + "Failed to import module %s" % (pyfile)) + except OSError: + LOG.error( + "Failed to read files from %s" % (modules_dir)) + except ImportError: + LOG.error( + "Failed to import module from path %s" % (conf.nfp_modules_path)) + + return pymodules + + +def controller_init(conf, nfp_controller): + nfp_controller.launch(conf.workers) + # Wait for conf.workers*1 + 1 secs for workers to comeup + time.sleep(conf.workers * 1 + 1) + nfp_controller.post_launch() + + +def nfp_modules_post_init(conf, nfp_modules, nfp_controller): + for module in nfp_modules: + try: + module.nfp_module_post_init(nfp_controller, conf) + except AttributeError: + LOG.debug("(module - %s) - " + "does not implement" + "nfp_module_post_init(), ignoring" % (identify(module))) def main(): - common_init() - - # Importing all the nfp modules from conf.modules_dir - modules = modules_import() - - sc = Controller(oslo_config.CONF, modules) - # Start the controller to start all contexts. - sc.start() - # Inform each loaded module about init complete - sc.init_complete() - # Wait for the workers - sc.wait() + conf = nfp_cfg.init(sys.argv[1:]) + nfp_common.init() + nfp_controller = NfpController(conf) + # Load all nfp modules from path configured + nfp_modules = load_nfp_modules(conf, nfp_controller) + # Init the controller, launch required contexts + controller_init(conf, nfp_controller) + # post_init of each module + nfp_modules_post_init(conf, nfp_modules, nfp_controller) + # eventlet.spawn_n(self_test_task, nfp_modules, nfp_controller, conf) + # Wait for every exec context to complete + nfp_controller.wait() + + +def self_test_task(modules, controller, conf): + while True: + for module in modules: + module.module_test(controller, conf) + eventlet.greenthread.sleep(10) + return diff --git a/gbpservice/nfp/core/event.py b/gbpservice/nfp/core/event.py index 2ca48b5428..45b64502c9 100644 --- a/gbpservice/nfp/core/event.py +++ b/gbpservice/nfp/core/event.py @@ -10,283 +10,446 @@ # License for the specific language governing permissions and limitations # under the License. +import collections import multiprocessing -import os -import time import uuid as pyuuid -from gbpservice.nfp.core import log as nfp_logging from gbpservice.nfp.core import common as nfp_common -from gbpservice.nfp.core import threadpool as nfp_tp +from gbpservice.nfp.core import module as nfp_api +from gbpservice.nfp.core import sequencer as nfp_seq +from gbpservice.nfp.core import log as nfp_logging LOG = nfp_logging.getLogger(__name__) identify = nfp_common.identify -"""Descriptor of event. """ +"""Event Types """ +SCHEDULE_EVENT = 'schedule_event' +POLL_EVENT = 'poll_event' +STASH_EVENT = 'stash_event' +EVENT_EXPIRED = 'event_expired' +EVENT_GRAPH = 'event_graph' + +"""Event Flag """ +EVENT_NEW = 'new_event' +EVENT_COMPLETE = 'event_done' +EVENT_ACK = 'event_ack' + +"""Sequencer status. """ +SequencerEmpty = nfp_seq.SequencerEmpty +SequencerBusy = nfp_seq.SequencerBusy + +deque = collections.deque + +class EventGraphNode(object): + def __init__(self, event, p_event=None): + self.p_link = () + self.c_links = [] + self.w_links = [] + self.e_links = [] + self.event = event + self.result = None + + if p_event: + self.p_link = p_event + + def __getstate__(self): + return (self.p_link, self.c_links, + self.e_links, self.w_links, self.event, self.result) + + def __setstate__(self, state): + (self.p_link, self.c_links, self.e_links, + self.w_links, self.event, self.result) = state + + def add_link(self, event): + self.c_links.append(event) + self.w_links.append(event) + + def remove_link(self, event): + self.e_links.append(event) + self.w_links.remove(event) + + def remove_c_link(self, event): + try: + self.c_links.remove(event) + except ValueError: + pass + + def get_c_links(self): + return self.c_links + + def get_w_links(self): + return self.w_links + + def get_executed_links(self): + return self.e_links + + +class EventGraph(object): + + def __init__(self, event): + self.root_node = EventGraphNode(event.desc.uuid) + self.nodes = {event.desc.uuid: self.root_node} + + def __getstate__(self): + return self.root_node, self.nodes + + def __setstate__(self, state): + self.root_node, self.nodes = state + + def add_node(self, event, p_event): + node = EventGraphNode(event.desc.uuid, p_event.desc.uuid) + self.nodes.update({event.desc.uuid: node}) + p_node = self.nodes.get(p_event.desc.uuid) + p_node.add_link(event.desc.uuid) + + def remove_node(self, node): + p_node = self.nodes.get(node.p_link) + if p_node: + p_node.remove_link(node.event) + return p_node + + def unlink_node(self, node): + p_node = self.nodes.get(node.p_link) + if p_node: + p_node.remove_c_link(node.event) + + def get_pending_leaf_nodes(self, node): + c_links = node.get_c_links() + c_nodes = [] + for link in c_links: + c_nodes.append(self.nodes[link]) + + return c_nodes + + def waiting_events(self, node): + return len(node.get_w_links()) + + def get_leaf_node_results(self, event): + results = [] + node = self.nodes[event.desc.uuid] + e_links = node.get_executed_links() + for link in e_links: + node = self.nodes[link] + uuid = node.event + key, id = uuid.split(':') + result = nfp_common.Object() + setattr(result, 'id', id) + setattr(result, 'key', key) + setattr(result, 'result', node.result) + results.append(result) + return results + + def get_node(self, event): + return self.nodes[event] + +"""Defines poll descriptor of an event. + + Holds all of the polling information of an + event. +""" + + +class PollDesc(object): + + def __init__(self, **kwargs): + # Spacing of the event, event will timeout @this spacing. + self.spacing = kwargs.get('spacing') + # Max times event can be polled, is autocancelled after. + self.max_times = kwargs.get('max_times') + # Reference to original event, UUID. + self.ref = kwargs.get('ref') + +"""Defines the descriptor of an event. + + Holds the metadata for an event. Useful + for event processing. Not exposed to nfp modules. +""" class EventDesc(object): def __init__(self, **kwargs): - # Unique id of the event, generated if not passed. - self.uid = kwargs.get('key', pyuuid.uuid4()) - # Poll descriptor of the event. - self.poll_event = None - # Worker handling this event. - self.worker_attached = None - # When this event was last run - self.last_run = None - - if not self.uid: - self.uid = pyuuid.uuid4() - -"""Definition of an 'EVENT' in NFP framework. - - NFP modules instantiates object of this class to define and - create internal events. + # Unique id of the event, use what user passed or + # generate a new unique id. + uuid = kwargs.get('key', pyuuid.uuid4()) + id = kwargs.get('id', '') + + self.uuid = str(uuid) + ':' + id + + # see 'Event Types' + self.type = kwargs.get('type') + # see 'Event Flag' + self.flag = kwargs.get('flag') + # PID of worker which is handling this event + self.worker = kwargs.get('worker') + # Polling descriptor of event + self.poll_desc = kwargs.get('poll_desc') + + def from_desc(self, desc): + self.type = desc.type + self.flag = desc.flag + self.worker = desc.worker + self.poll_desc = desc.poll_desc + + def to_dict(self): + return {'uuid': self.uuid, + 'type': self.type, + 'flag': self.flag, + 'worker': self.worker, + 'poll_desc': self.poll_desc + } + +"""Defines the event structure. + + Nfp modules need to create object of the class + to create an event. """ class Event(object): def __init__(self, **kwargs): - # ID of the event, can be same for multiple events + # ID of event as passed by module self.id = kwargs.get('id') - # Module context, not decoded by core - self.data = kwargs.get('data', None) - # Handler used only @the time of registration - self.handler = kwargs.get('handler', None) - # To serialize this event. - self.serialize = kwargs.get('serialize', False) - # Events with same binding_key are related. - self.binding_key = kwargs.get('binding_key', None) - # Lifetime of event in seconds + # Data blob + self.data = kwargs.get('data') + # Whether to sequence this event w.r.t + # other related events. + self.sequence = kwargs.get('serialize', False) + # Unique key to be associated with the event + self.key = kwargs.get('key') + # Binding key to define relation between + # different events. + self.binding_key = kwargs.get('binding_key') + # Handler of the event. + self.handler = kwargs.get('handler') + # Lifetime of the event in seconds. self.lifetime = kwargs.get('lifetime', 0) - # Max number of times this event can be polled. - # Default, till stopped or forever. - self.max_times = -1 # Identifies whether event.data is zipped self.zipped = False - # Added for log metadata - self.context = kwargs.get('context', None) + # Log metadata context + self.context = kwargs.get('context', {}) + # Prepare the base descriptor + desc = kwargs.get('desc_dict') + if desc: + desc['key'] = self.key + desc['id'] = self.id + desc = EventDesc(**desc) + elif self.key: + desc = EventDesc(**{'key': self.key, + 'id': self.id}) + else: + desc = EventDesc(**{'id': self.id}) + self.desc = desc + + # Will be set if this event is a event graph + self.graph = kwargs.get('graph', None) + self.result = None + + cond = self.sequence is True and self.binding_key is None + assert not cond + + def set_fields(self, **kwargs): + if 'graph' in kwargs: + self.graph = kwargs['graph'] def identify(self): if hasattr(self, 'desc'): - return "(Event -> id=%s,key=%s)" % (self.id, self.desc.uid) - else: - return "(Event -> id=%s,key=%s)" % (self.id, '') + return "uuid=%s,id=%s,type=%s,flag=%s" % ( + self.desc.uuid, self.id, self.desc.type, self.desc.flag) + return "id=%s" % (self.id) -"""Handles the sequencing of related events. - If Event needs to be sequenced it is queued otherwise - it is scheduled. Caller will fetch the sequenced events - waiting to be scheduled in subsequent calls. +"""Table of event handler's. + + Maintains cache of every module's event handlers. + Also, maintains the polling against event_id + which are provided as decorators. """ -class EventSequencer(object): +class NfpEventHandlers(object): - def __init__(self, sc): - self._sc = sc - """ - sequenced events are stored in following format : - {'binding_key':{'in_use':True, 'queue':[]}} - """ - self._sequencer_map = {} + def __init__(self): + # {'event.id': [(event_handler, poll_handler, spacing)] + self._event_desc_table = {} - def get(self): - """Get an event from the sequencer map. + def _log_meta(self, event_id, event_handler=None): + if event_handler: + return "(event_id - %s) - (event_handler - %s)" % ( + event_id, identify(event_handler)) + else: + return "(event_id - %s) - (event_handler - None)" % (event_id) - Invoked by workers to get the first event in sequencer map. - Since it is a FIFO, first event could be waiting long to be - scheduled. - Loops over copy of sequencer map and returns the first waiting - event. - """ - seq_map = self._sequencer_map - for bkey, val in seq_map.iteritems(): - in_use = val['in_use'] - if not in_use and val['queue']: - # Return the first element of the - # queue in first free sequencer. - # should not pop here, event done will - # remove it. useful in restart cases later. - event = val['queue'][0] - val['in_use'] = True - LOG.debug("%s - sequencer_get - returning" - % (event.identify())) - return event - - def add(self, event): - """Add the event to the sequencer. - - Checks if there is already a related event scheduled, - if not, will not queue the event. If yes, then will - queue this event. - Returns True(queued)/False(not queued). + def register(self, event_id, event_handler): + """Registers a handler for event_id. + + Also fetches the decorated poll handlers if any + for the event and caches it. """ - queued = False + if not isinstance(event_handler, nfp_api.NfpEventHandler): + LOG.error("%s - Handler is not" + "instance of NfpEventHandler" % + (self._log_meta(event_id, event_handler))) + return try: - seq_map = self._sequencer_map[event.binding_key] - seq_map['queue'].append(event) - queued = True - LOG.debug("%s - sequencer_add - an event" - "already in progress, queueing" % (event.identify())) - except KeyError as err: - self._sequencer_map[event.binding_key] = { - 'in_use': True, 'queue': [event]} - err = err - LOG.debug( - "%s - sequencer_add - first event " - "in sequence, scheduling it" % (event.identify())) - return queued - - def copy(self): - """Returns the copy of sequencer_map to caller. - - Used by the caller to iterate over the sequencer / - read operations. - """ - copy = dict(self._sequencer_map) - return copy + poll_desc_table = event_handler.get_poll_desc_table() + poll_handler = poll_desc_table[event_id] + spacing = poll_handler._spacing + except KeyError: + # Default the poll handler and spacing values + poll_handler = event_handler.handle_poll_event + spacing = 0 - def remove(self, event): - """Removes an event from sequencer map. + try: + self._event_desc_table[event_id].append( + (event_handler, poll_handler, spacing)) + except KeyError: + self._event_desc_table[event_id] = [ + (event_handler, poll_handler, spacing)] + + LOG.debug("%s - Registered handler" % + (self._log_meta(event_id, event_handler))) + + def get_event_handler(self, event_id): + """Get the handler for the event_id. """ + eh = None + try: + eh = self._event_desc_table[event_id][0][0] + finally: + LOG.debug("%s - Returning event handler" % + (self._log_meta(event_id, eh))) + return eh + + def get_poll_handler(self, event_id): + """Get the poll handler for event_id. """ + ph = None + try: + ph = self._event_desc_table[event_id][0][1] + finally: + LOG.debug("%s - Returning poll handler" % + (self._log_meta(event_id, ph))) + return ph + + def get_poll_spacing(self, event_id): + """Return the spacing for event_id. """ + spacing = 0 + try: + spacing = self._event_desc_table[event_id][0][2] + finally: + LOG.debug("%s - Poll spacing %d" % + (self._log_meta(event_id), spacing)) + return spacing - If this is the last related event in the map, then - the complete entry is deleted from sequencer map. - """ - bkey = event.binding_key - self._sequencer_map[bkey]['queue'].remove(event) - self._sequencer_map[bkey]['in_use'] = False - LOG.debug("%s - sequencer - removed" % ( - event.identify())) - - def delete_eventmap(self, event): - """Internal method to delete event map, if it is empty. """ - seq_map = self._sequencer_map[event.binding_key] - if seq_map['queue'] == []: - LOG.debug( - "sequencer - no events -" - "deleting entry - %s" - % (event.binding_key)) - del self._sequencer_map[ - event.desc.worker_attached][event.binding_key] - -"""Handles the processing of evens in event queue. - - Executes in the context of worker process, runs in loop to fetch - the events and process them. As processing, invokes the registered - handler for the event. + +"""Manages the lifecycle of event of a process. + + Each process (worker/distributor) is associated + with a event manager. Event manager pulls events + from the pipe, caches it, sequences & dispatches + the events. """ -class EventQueueHandler(object): +class NfpEventManager(object): - def __init__(self, sc, conf, pipe, ehs, modules): - # Pool of green threads per process + def __init__(self, conf, controller, sequencer, pipe=None, pid=-1): self._conf = conf - self._tpool = nfp_tp.ThreadPool() + self._controller = controller + # PID of process to which this event manager is associated + self._pid = pid + # Duplex pipe to read & write events self._pipe = pipe - self._ehs = ehs - self._nfp_modules = modules - self._sc = sc - - def _get(self): - """Internal function to get an event for processing. - - First checks in sequencer map - these events could be - waiting for long. - If no events, then fetch the events from event_queue - - listener process adds events into this queue. - Returns the event to be processed. + # Cache of UUIDs of events which are dispatched to + # the worker which is handled by this em. + self._cache = deque() + # Load on this event manager - num of events pending to be completed + self._load = 0 + + def _log_meta(self, event=None): + if event: + return "(event - %s) - (event_manager - %d)" % ( + event.identify(), self._pid) + else: + return "(event_manager - %d" % (self._pid) + + def _wait_for_events(self, pipe, timeout=0.01): + """Wait & pull event from the pipe. + + Wait till timeout for the first event and then + pull as many as available. + Returns: Events[] pulled from pipe. + """ + events = [] + try: + while pipe.poll(timeout): + timeout = 0 + events.append(pipe.recv()) + except multiprocessing.TimeoutError as err: + LOG.exception("%s" % (err)) + return events + + def init_from_event_manager(self, em): + """Initialize from existing event manager. + + Invoked when an event manager has to take over + existing event manager. + + Whole cache is replaced and events are replayed. + This is used in case where a worker dies, dead + workers event manager is assigned to new worker. + """ + # Replay all the events from cache. + self._cache = em._cache + + def get_pending_events(self): + return list(self._cache) + + def get_load(self): + """Return current load on the manager.""" + return self._load + + def pop_event(self, event): + """Pop the passed event from cache. + + Is called when an event is complete/cancelled. + If the event was sequenced, then sequencer is + released to schedule next event. + + Removes event from cache. """ - # Check if any event can be pulled from serialize_map - this evs may be - # waiting long enough - event = self._sc.sequencer_get_event() - if not event: - try: - if self._pipe.poll(0.1): - event = self._pipe.recv() - except multiprocessing.TimeoutError as err: - err = err - pass - if event: - # If this event needs to be serialized and is first event - # then the same is returned back, otherwise None is - # returned. If event need not be serialized then it is - # returned. - event = self._sc.sequencer_put_event(event) - return event - - def _invoke_nfp_module_cb(self, event_handle, event, is_poll_event=False): - nfp_logging.store_logging_context(**event.context) - if is_poll_event : - self._sc.poll_event_timedout(event_handle, event) - else: - event_handle(event) - nfp_logging.clear_logging_context() - - def _dispatch_poll_event(self, eh, ev): - """Internal function to handle the poll event. - - Poll task adds the timedout events to the worker process. - This method handles such timedout events in worker context. - Invoke the decorated timeout handler for the event, if any. - (or) invoke the default 'handle_poll_event' method of registered - handler. - """ - t = self._tpool.dispatch(self._invoke_nfp_module_cb, eh, ev, - is_poll_event=True) - LOG.debug( - "%s - dispatch poll event - " - "to event handler: %s - " - "in thread: %s" - % (ev.identify(), identify(eh), t.identify())) - - def run(self, pipe): - """Worker process loop to fetch & process events from event queue. - - Gets the events from event queue which is - python multiprocessing.queue. - Listener process adds events into this queue for worker process - to handle it. - Handles 3 different type of events - - a) POLL_EVENT - Event added by poller due to timeout. - b) POLL_EVENT_CANCELLED - Event added by poller due to event - getting cancelled as it timedout configured number of - max times. - c) EVENT - Internal event added by listener process. + LOG.debug("%s - pop event" % (self._log_meta(event))) + try: + self._cache.remove(event.desc.uuid) + self._load -= 1 + except ValueError as verr: + verr = verr + LOG.warn("%s - event not in cache" % + (self._log_meta(event))) + + def dispatch_event(self, event, event_type=None, + inc_load=True, cache=True): + """Dispatch event to the worker. + + Sends the event to worker through pipe. + Increments load if event_type is SCHEDULED event, + poll_event does not contribute to load. """ - LOG.info( - "%d - worker started" % (os.getpid())) - # Update my identity on my copy of controller - self._sc._process_name = 'worker-process' - # Update my pid in worker map - self._sc._worker_pipe_map[os.getpid()] = pipe - # Initialize the nfp modules again from worker. - # This is because modules are initializing some contexts - # in module_init which is invoked before starting workers, - # forked workers get the copy of such contexts, and module - # logic end up using stale contexts. - # Better to initialize again and ignore re registrations. - self._sc.modules_init(self._nfp_modules) - while True: - event = self._get() - if event: - self._sc.decompress(event) - LOG.debug( - "%s - worker - got new event" % (event.identify())) - eh = self._ehs.get(event) - if not event.desc.poll_event: - t = self._tpool.dispatch(self._invoke_nfp_module_cb, - eh.handle_event, event, - is_poll_event=False) - LOG.debug("%s - dispatch internal event -" - "to event handler:%s - " - "in thread:%s" % ( - event.identify(), - identify(eh), t.identify())) - else: - self._dispatch_poll_event(eh, event) - time.sleep(0) # Yield the CPU + LOG.debug("%s - Dispatching to worker %d" % + (self._log_meta(event), self._pid)) + # Update the worker information in the event. + event.desc.worker = self._pid + # Update the event with passed type + if event_type: + event.desc.type = event_type + # Send to the worker + self._controller.pipe_send(self._pipe, event) + + self._load = (self._load + 1) if inc_load else self._load + # Add to the cache + if cache: + self._cache.append(event.desc.uuid) + + def event_watcher(self, timeout=0.01): + """Watch for events. """ + return self._wait_for_events(self._pipe, timeout=timeout) diff --git a/gbpservice/nfp/core/event_lb.py b/gbpservice/nfp/core/event_lb.py deleted file mode 100644 index c80c8e19b9..0000000000 --- a/gbpservice/nfp/core/event_lb.py +++ /dev/null @@ -1,43 +0,0 @@ -# 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. - - -"""Implements round robin algo with stickiness to a worker. - - All the events with same rsrcid, are scheduled to same - worker. Maintains the map in dict. -""" - - -class StickyRoundRobin(object): - - def __init__(self, workers): - self._workers = workers - self._assoc = {} - self._rridx = 0 - self._rrsize = len(self._workers) - - def _rr(self): - item = self._workers[self._rridx] - self._rridx = (self._rridx + 1) % (self._rrsize) - return item - - def get(self, rsrcid): - if not rsrcid: - return self._rr() - - if rsrcid in self._assoc.keys(): - worker = self._assoc[rsrcid] - else: - worker = self._rr() - self._assoc[rsrcid] = worker - return worker diff --git a/gbpservice/nfp/core/executor.py b/gbpservice/nfp/core/executor.py new file mode 100644 index 0000000000..e63ec6fc28 --- /dev/null +++ b/gbpservice/nfp/core/executor.py @@ -0,0 +1,183 @@ +from gbpservice.nfp.core import threadpool as core_tp +from gbpservice.nfp.core import log as nfp_logging + +LOG = nfp_logging.getLogger(__name__) + + +class InUse(Exception): + + """Exception raised when same task executor instance + is fired twice or jobs + added after executor is fired. + """ + pass + + +def check_in_use(f): + """Check if instance of task executor is already + fired and executing jobs. + """ + def wrapped(self, *args, **kwargs): + if self.fired: + raise InUse("Executor in use") + return f(self, *args, **kwargs) + return wrapped + +class TaskExecutor(object): + """Executes given jobs in green threads. + + Any number of jobs can be added till executor + is fired. When fired, executes all jobs in + paralell in green threads. Waits for threads + to complete, captures the return values of thread + function. + Caller can choose to pass result_store where the + return value will be updated. + """ + def __init__(self, jobs=0): + if not jobs: + self.thread_pool = core_tp.ThreadPool() + else: + self.thread_pool = core_tp.ThreadPool(thread_pool_size=jobs) + + self.pipe_line = [] + self.fired = False + + @check_in_use + def add_job(self, id, func, *args, **kwargs): + result_store = kwargs.pop('result_store', None) + + job = { + 'id': id, 'method': func, + 'args': args, 'kwargs': kwargs + } + + if result_store is not None: + job.update({'result_store': result_store}) + + LOG.debug("TaskExecutor - (job - %s) added to pipeline" % + (str(job))) + + self.pipe_line.append(job) + + def _complete(self): + LOG.debug("TaskExecutor - complete") + self.pipe_line = [] + self.fired = False + + @check_in_use + def fire(self): + self.fired = True + for job in self.pipe_line: + LOG.debug( + "TaskExecutor - (job - %s) dispatched" % + (str(job))) + + th = self.thread_pool.dispatch( + job['method'], *job['args'], **job['kwargs']) + job['thread'] = th + + for job in self.pipe_line: + result = job['thread'].wait() + LOG.debug( + "TaskExecutor - (job - %s) complete" % + (str(job))) + + job.pop('thread') + job['result'] = result + if 'result_store' in job.keys(): + job['result_store']['result'] = result + + done_jobs = self.pipe_line[:] + self._complete() + return done_jobs + + +def set_node(f): + """To find and set a graph node for a + given event. + """ + def decorator(self, *args, **kwargs): + node = kwargs.get('node') + event = kwargs.get('event') + if not node: + if not event: + kwargs['node'] = self.graph.root_node + else: + kwargs['node'] = self.graph.get_node(event) + return f(self, *args, **kwargs) + return decorator + +class EventGraphExecutor(object): + """Executor which executs a graph of events. + + An event graph can consist of events defined + in any combination of paralell and sequence + events. Executor will execute them in the + order and manner specified. + Eg., E1 -> (E2, E3) + [E1 should execute after E2, E3 completes, + while E2 & E3 can happen in paralell] + E2 -> (E4, E5) + [E2 should execute after E4, E5 completes, + while E4 & E5 should happen in sequence] + E3 -> (None) + [No child events for E3] + + Executor will run the above graph and execute events + in the exact specific order mentioned. + At each level, parent event holds the result of child + events, caller can use parent event complete notification + to get the child events execution status. + """ + + def __init__(self, manager, graph): + self.manager = manager + self.graph = graph + + @set_node + def run(self, event=None, node=None): + LOG.debug("GraphExecutor - (event - %s)" % + (node.event)) + + # Call to check if event would get sequenced + if self.manager.schedule_graph_event( + node.event, self.graph, dispatch=False): + LOG.debug("GraphExecutor - " + "(event - %s) - sequenced" % + (node.event)) + # Event would have got added to sequencer, + # unlink it from pending links of graph + return self.graph.unlink_node(node) + + l_nodes = self.graph.get_pending_leaf_nodes(node) + LOG.debug("GraphExecutor - " + "(event - %s) - number of leaf nodes - %d" % + (node.event, len(l_nodes))) + + if not l_nodes: + if not self.graph.waiting_events(node): + LOG.debug("GraphExecutor - " + "(event - %s) - Scheduling event" % + (node.event)) + self.manager.schedule_graph_event(node.event, self.graph) + self.graph.unlink_node(node) + + if l_nodes: + for l_node in l_nodes: + LOG.debug("GraphExecutor -" + "(event - %s) executing leaf node" % + (node.event)) + self.run(node=l_node) + + @set_node + def event_complete(self, result, event=None, node=None): + LOG.debug("GraphExecutor - (event - %s) complete" % + (node.event)) + node.result = result + p_node = self.graph.remove_node(node) + if p_node: + LOG.debug("GraphExecutor - " + "(event - %s) complete, rerunning parent - %s" % + (node.event, p_node.event)) + self.run(node=p_node) diff --git a/gbpservice/nfp/core/launcher.py b/gbpservice/nfp/core/launcher.py new file mode 100644 index 0000000000..64880c3548 --- /dev/null +++ b/gbpservice/nfp/core/launcher.py @@ -0,0 +1,93 @@ +# 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 time + +from oslo_service import service as oslo_service + +from gbpservice.nfp.core import common as nfp_common +from gbpservice.nfp.core import log as nfp_logging + +LOG = nfp_logging.getLogger(__name__) +ProcessLauncher = oslo_service.ProcessLauncher + +"""Worker process launcher. + + Derives the oslo process launcher to + launch childrens with python multiprocessing + as oppose to os.fork(), coz, communication + is needed from parent->child not just the + paralell execution. +""" + + +class NfpLauncher(ProcessLauncher): + + def __init__(self, conf): + super(NfpLauncher, self).__init__(conf) + + def child(self, service, ppipe, cpipe, controller): + service.parent_pipe = ppipe + service.pipe = cpipe + service.controller = controller + self.launcher = self._child_process(service) + while True: + self._child_process_handle_signal() + status, signo = self._child_wait_for_exit_or_signal( + self.launcher) + if not oslo_service._is_sighup_and_daemon(signo): + self.launcher.wait() + break + self.launcher.restart() + + os._exit(status) + + def _start_child(self, wrap): + if len(wrap.forktimes) > wrap.workers: + # Limit ourselves to one process a second (over the period of + # number of workers * 1 second). This will allow workers to + # start up quickly but ensure we don't fork off children that + # die instantly too quickly. + if time.time() - wrap.forktimes[0] < wrap.workers: + # LOG.info(_LI('Forking too fast, sleeping')) + time.sleep(1) + + wrap.forktimes.pop(0) + + wrap.forktimes.append(time.time()) + + pid = self.fork_child(wrap) + + LOG.debug("Started Child Process %d" % (pid)) + + wrap.children.add(pid) + self.children[pid] = wrap + + return pid + + def fork_child(self, wrap): + # Default use os.fork to create a child + pid = os.fork() + if pid == 0: + self.launcher = self._child_process(wrap.service) + while True: + self._child_process_handle_signal() + status, signo = self._child_wait_for_exit_or_signal( + self.launcher) + if not _is_sighup_and_daemon(signo): + self.launcher.wait() + break + self.launcher.restart() + + os._exit(status) + return pid diff --git a/gbpservice/nfp/core/manager.py b/gbpservice/nfp/core/manager.py new file mode 100644 index 0000000000..04b9f20963 --- /dev/null +++ b/gbpservice/nfp/core/manager.py @@ -0,0 +1,435 @@ +# 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 collections +import os +# from random import randrange + +from gbpservice.nfp.core import common as nfp_common +from gbpservice.nfp.core import event as nfp_event +from gbpservice.nfp.core import sequencer as nfp_sequencer +from gbpservice.nfp.core import log as nfp_logging +from gbpservice.nfp.core import executor as nfp_executor + +LOG = nfp_logging.getLogger(__name__) +NfpEventManager = nfp_event.NfpEventManager + +deque = collections.deque + + +def IS_SCHEDULED_EVENT_ACK(event): + return event.desc.type == nfp_event.SCHEDULE_EVENT and \ + event.desc.flag == nfp_event.EVENT_ACK + + +def IS_SCHEDULED_NEW_EVENT(event): + return event.desc.type == nfp_event.SCHEDULE_EVENT and \ + event.desc.flag == nfp_event.EVENT_NEW + + +def IS_SCHEDULED_EVENT_GRAPHEVENT(event): + return IS_SCHEDULED_NEW_EVENT(event) and (event.graph) + +def IS_EVENT_GRAPH(event): + return event.desc.type == nfp_event.EVENT_GRAPH + +''' +def IS_SCHEDULED_EVENT_COMPLETE(event): + return event.desc.type == nfp_event.SCHEDULE_EVENT and \ + event.desc.flag == nfp_event.EVENT_COMPLETE +''' + + +def IS_EVENT_COMPLETE(event): + return event.desc.flag == nfp_event.EVENT_COMPLETE + + +"""Manages the forked childs. + + Invoked periodically, compares the alive childs with + snapshot and reports the difference to the caller. +""" + + +class NfpProcessManager(object): + + def __init__(self, conf, controller): + self._conf = conf + self._controller = controller + self._child_snapshot = [] + + def new_child(self, pid, pipe): + # Pass, as we will learn from comparision as watcher + pass + + def _dead(self, dead): + for proc in dead: + self._child_snapshot.remove(proc) + + def _new(self, new): + if new: + self._child_snapshot.extend(new) + + def child_watcher(self): + # Get the current set of childrens + current = self._controller.get_childrens() + set1 = set(current) + set2 = set(self._child_snapshot) + new = set1 - set2 + dead = set2 - set1 + + self._dead(dead) + self._new(new) + + return list(dead), list(new) + + +"""Manager for nfp resources. + + Manages all the nfp resources - process, events, polling queue etc. + Mixes the specific managers. +""" + + +class NfpResourceManager(NfpProcessManager, NfpEventManager): + + def __init__(self, conf, controller): + self._conf = conf + self._controller = controller + # Process, Event mixin, {'pid': event_manager} + self._resource_map = {} + # Cache of event objects - {'uuid':} + self._event_cache = {} + # Not processed. Events Stored for future. + self._stash = deque() + # ID of the distributor process + self._distributor_process_id = os.getpid() + # Single sequencer to be used by all event managers + self._event_sequencer = nfp_sequencer.EventSequencer() + + NfpProcessManager.__init__(self, conf, controller) + NfpEventManager.__init__(self, conf, controller, self._event_sequencer) + + def new_child(self, pid, pipe): + """Invoked when a new child is spawned. + + Associates an event manager with this child, maintains + the map. Manages the process. If this process is killed, + the event_manager is assigned to new process. + + :param process: Context of new process. + :param pipe: Pipe to communicate with this child. + """ + ev_manager = NfpEventManager( + self._conf, self._controller, + self._event_sequencer, + pipe=pipe, pid=pid) + self._resource_map.update(dict({pid: ev_manager})) + super(NfpResourceManager, self).new_child(pid, pipe) + + def manager_run(self): + """Invoked periodically to check on resources. + + a) Checks if childrens are active or any killed. + b) Checks if there are messages from any of workers. + c) Dispatches the events ready to be handled to workers. + """ + self._child_watcher() + self._event_watcher() + + def _event_acked(self, event): + """Post handling after event is dispatched to worker. """ + if event.lifetime: + LOG.debug( + "(event - %s) - dispatched, polling for expiry" % + (event.identify())) + self._controller.poll_add( + event, event.lifetime, self._event_life_timedout) + + def _dispatch_event(self, event): + """Dispatch event to a worker. """ + load_info = self._load_init() + event_manager, load_info = self._get_min_loaded_em(load_info) + event_manager.dispatch_event(event) + + def _execute_event_graph(self, event, state=None): + graph = event.graph + g_executor = nfp_executor.EventGraphExecutor(self, graph) + g_executor.run(event=state) + + def _graph_event_complete(self, event): + if not event.graph: return + + graph = event.graph + g_executor = nfp_executor.EventGraphExecutor(self, graph) + g_executor.event_complete(event.result, event=event.desc.uuid) + + def _scheduled_event_graph(self, event): + if event.graph == True: + # Cache the event object + self._event_cache[event.desc.uuid] = event + else: + # This case happens when a serialized event of + # a graph is desequenced and is processed. + self._execute_event_graph(event, state=event.desc.uuid) + + def _get_event_from_cache(self, uuid): + try: + return self._event_cache[uuid] + except KeyError as ke: + LOG.error("(event - %s) - no event with uuid" % + (uuid)) + raise ke + + def schedule_graph_event(self, uuid, graph, dispatch=True): + # Get event from cache + event = self._get_event_from_cache(uuid) + # Update the graph in event, which will be stored in cache + event.graph = graph + # Schedule the event + return self._scheduled_new_event(event, dispatch=dispatch) + + def _scheduled_new_event(self, event, dispatch=True): + # Cache the event object + self._event_cache[event.desc.uuid] = event + # Event needs to be sequenced ? + if not event.sequence: + if dispatch: + # Dispatch to a worker + self._dispatch_event(event) + else: + LOG.debug("(event - %s) - sequencing" % + (event.identify())) + # Sequence the event which will be processed later + self._event_sequencer.sequence(event.binding_key, event) + + return event.sequence + + def _scheduled_event_ack(self, ack_event): + try: + event = self._event_cache[ack_event.desc.uuid] + evmanager = self._get_event_manager(event.desc.worker) + assert evmanager + # Pop from the pending list of evmanager + evmanager.pop_event(event) + # May be start polling for lifetime of event + self._event_acked(event) + except KeyError as kerr: + kerr = kerr + LOG.error("(event - %s) - acked," + "missing from cache" % (event.identify())) + except AssertionError as aerr: + aerr = aerr + LOG.error("(event - %s) - acked," + "process handling is dead, event will be" + "replayed in new process" % + (event.identify())) + + def _scheduled_event_complete(self, event, expired=False): + # Pop it from cache + cached_event = None + try: + cached_event = self._event_cache.pop(event.desc.uuid) + cached_event.result = event.result + # Get the em managing the event + evmanager = self._get_event_manager(event.desc.worker) + assert evmanager + evmanager.pop_event(event) + # If event expired, send a cancelled event back to worker + if expired: + event.desc.type = nfp_event.EVENT_EXPIRED + evmanager.dispatch_event(event, inc_load=False, cache=False) + except KeyError as kerr: + kerr = kerr + LOG.error("(event - %s) - completed, not in cache" % + (event.identify())) + except AssertionError as aerr: + aerr = aerr + # No event manager for the event, worker could have got + # killed, ignore. + LOG.error("(event - %s) - assertion error" % + (event.identify())) + pass + finally: + # Release the sequencer for this sequence, + # so that next event can get scheduled. + self._event_sequencer.release(event.binding_key, event) + self._graph_event_complete(cached_event) + + def _non_schedule_event(self, event): + if event.desc.type == nfp_event.POLL_EVENT: + LOG.debug( + "(event - %s) - polling for event, spacing(%d)" % + (event.identify(), event.desc.poll_desc.spacing)) + # If the poll event is new -> create one in cache, + # In most of the cases, polling is done for an existing + # event. + ref_uuid = event.desc.poll_desc.ref + if ref_uuid not in self._event_cache.keys(): + # Assign random worker for this poll event + event.desc.worker = self._resource_map.keys()[0] + self._event_cache[ref_uuid] = event + + self._controller.poll_add( + event, + event.desc.poll_desc.spacing, + self._event_timedout) + else: + LOG.error( + "(event - %s) - Unknown non scheduled event" % + (event.identify())) + + def process_events_by_ids(self, event_ids): + for event_id in event_ids: + try: + event = self._event_cache[event_id] + self.process_events([event]) + except KeyError as kerr: + kerr = kerr + LOG.error("%s - event missing in cache" % + (event_id)) + + def process_events(self, events): + """Process the consumed event. + + Based on the event type, new event will + be added to cache, completed event is + removed from cache, poll event is added + to pollq. + + """ + for event in events: + LOG.debug("%s - processing event" % (event.identify())) + + if IS_EVENT_GRAPH(event): + self._execute_event_graph(event) + elif IS_SCHEDULED_EVENT_GRAPHEVENT(event): + self._scheduled_event_graph(event) + elif IS_SCHEDULED_EVENT_ACK(event): + self._scheduled_event_ack(event) + elif IS_SCHEDULED_NEW_EVENT(event): + self._scheduled_new_event(event) + elif IS_EVENT_COMPLETE(event): + self._scheduled_event_complete(event) + else: + self._non_schedule_event(event) + + def _event_watcher(self): + """Watches for events for each event manager. + + Invokes each event manager to get events from workers. + Also checks parent process event manager. + """ + events = [] + # Get events from sequencer + events = self._event_sequencer.run() + for pid, event_manager in self._resource_map.iteritems(): + events += event_manager.event_watcher(timeout=0.01) + # Process the type of events received, dispatch only the + # required ones. + self.process_events(events) + + def _init_event_manager(self, from_em, to_em): + pending_event_ids = to_em.init_from_event_manager(from_em) + # Reprocess all the pending events, module handlers can + # continue processing of unacked events. + self.process_events_by_ids(pending_event_ids) + + def _replace_child(self, killed, new): + childrens = self._controller.get_childrens() + wrap = childrens[new] + pipe = wrap.child_pipe_map[new] + self.new_child(new, pipe) + new_em = self._resource_map[new] + killed_em = self._resource_map[killed] + new_em.init_from_event_manager(killed_em) + # Dispatch the pending events to the new worker through new em + self._replay_events(new_em) + + def _replay_events(self, event_manager): + pending_event_ids = event_manager.get_pending_events() + for event_id in pending_event_ids: + try: + LOG.info("%s - replaying event" % (event_id)) + event_manager.dispatch_event( + self._event_cache[event_id], cache=False) + except KeyError as kerr: + kerr = kerr + LOG.error("%s - eventid missing in cache" % + (event_id)) + + def _child_watcher(self): + dead, new = super(NfpResourceManager, self).child_watcher() + if len(dead) and len(dead) != len(new): + LOG.error("Killed process - %s, " + "New Process - %s, " + "does not match in count, few killed process" + "will not be replaced" % (str(dead), str(new))) + + # Loop over dead workers and assign its + # event manager to one of the new worker + for killed_proc in dead: + new_proc = new.pop() + self._replace_child(killed_proc, new_proc) + del self._resource_map[killed_proc] + + def _load_init(self): + """Intializes load with current information. """ + load_info = [] + for pid, event_manager in self._resource_map.iteritems(): + load = event_manager.get_load() + load_info.append([event_manager, load, pid]) + + return load_info + + def _get_min_loaded_em(self, load_info): + """Returns the min loaded event_manager. """ + minloaded = min(load_info, key=lambda x: x[1]) + load = minloaded[1] + 1 + load_info[load_info.index(minloaded)][1] = load + return minloaded[0], load_info + + def _get_event_manager(self, pid): + """Returns event manager of a process. """ + if pid == self._distributor_process_id: + return self + else: + return self._resource_map.get(pid) + + def _event_life_timedout(self, event): + """Callback for poller when event expires. """ + LOG.debug("(event - %s) - expired" % (event.identify())) + self._scheduled_event_complete(event, expired=True) + + def _event_timedout(self, event): + """Callback for poller when event timesout. """ + LOG.debug("(event - %s) - timedout" % (event.identify())) + try: + ref_event = self._event_cache[event.desc.poll_desc.ref] + evmanager = self._get_event_manager(ref_event.desc.worker) + assert evmanager + evmanager.dispatch_event( + event, event_type=nfp_event.POLL_EVENT, + inc_load=False, cache=False) + except KeyError as err: + err = err + LOG.error("(event - %s) - timedout, not in cache" % + (event.identify())) + except AssertionError as aerr: + aerr = aerr + # Process associated with event could be killed. + # Ignore. + pass + + def stash_event(self, event): + """Stash the given event. """ + self._stash.put(event) diff --git a/gbpservice/nfp/core/module.py b/gbpservice/nfp/core/module.py new file mode 100644 index 0000000000..25e66d4d71 --- /dev/null +++ b/gbpservice/nfp/core/module.py @@ -0,0 +1,111 @@ +# 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 abc import ABCMeta +from abc import abstractmethod +import six + + +def poll_event_desc(*args, **kwargs): + """Poll Event Decorator. + + NFP modules can define the poll handlers using + this decorator. + """ + def decorator(f): + f._desc = True + f._spacing = kwargs.pop('spacing', 0) + f._event = kwargs.pop('event', None) + return f + + return decorator + +"""Meta class. """ + + +class _Meta(type): + + def __init__(cls, names, bases, dict_): + """Metaclass that allows us to collect decorated periodic tasks.""" + super(_Meta, cls).__init__(names, bases, dict_) + + try: + cls._poll_desc_table = dict(cls._poll_desc_table) + except AttributeError: + cls._poll_desc_table = {} + + for value in cls.__dict__.values(): + if getattr(value, '_desc', False): + desc = value + cls._poll_desc_table[desc._event] = desc + +"""Base class for nfp event handlers. + +Nfp modules derive and implement event handlers +of this class. +""" + + +@six.add_metaclass(_Meta) +class NfpEventHandler(object): + __metaclass__ = ABCMeta + + def __init__(self): + super(NfpEventHandler, self).__init__() + + def get_poll_desc_table(self): + return self._poll_desc_table + + @abstractmethod + def handle_event(self, event): + """To handle an event. + + :param event: Object of 'Event' class. + + Returns: None + """ + pass + + @abstractmethod + def handle_poll_event(self, event): + """To handle a poll event. + + Core framework will inovke this method of event handler + when an event timesout. + + :param event: Object of 'Event' class. + + Returns: {'poll':True/False, 'event':} + 'poll': To repoll for the event. + 'event': Updated event, if not passed core will + repoll on the old event. + """ + pass + + @abstractmethod + def event_cancelled(self, event, reason): + """Notifies that an event is cancelled by core. + + Event could get cancelled, + a) Event expired. Module can set lifetime for + an event. If event is not complete with in + the time, it is auto expired by core. + b) Event max timedout. Module can set max number + of times to poll for an event. Event is cancelled + after the max times. + + :param event: Cancelled event. Object of 'Event' class. + :param reason: Reason for cancellation. String. + + Returns: None + """ + pass diff --git a/gbpservice/nfp/core/poll.py b/gbpservice/nfp/core/poll.py index 2d078043fc..78646b63f1 100644 --- a/gbpservice/nfp/core/poll.py +++ b/gbpservice/nfp/core/poll.py @@ -10,393 +10,74 @@ # License for the specific language governing permissions and limitations # under the License. -import multiprocessing -import os -import random -import six -import time +import eventlet +import heapq +import sched +import time as pytime - -from oslo_config import cfg as oslo_config from oslo_service import loopingcall as oslo_looping_call from oslo_service import periodic_task as oslo_periodic_task -from gbpservice.nfp.core import common as nfp_common -from gbpservice.nfp.core import log as nfp_logging - -LOG = nfp_logging.getLogger(__name__) -identify = nfp_common.identify - -"""Decorator definition """ - - -def poll_event_desc(*args, **kwargs): - def decorator(f): - f._desc = True - f._spacing = kwargs.pop('spacing', 0) - f._event = kwargs.pop('event', None) - return f - - return decorator - -"""Meta class. """ - - -class _Meta(type): +Scheduler = sched.scheduler - def __init__(cls, names, bases, dict_): - """Metaclass that allows us to collect decorated periodic tasks.""" - super(_Meta, cls).__init__(names, bases, dict_) +"""Handles the queue of poll events. - try: - cls._poll_event_descs = dict(cls._poll_event_descs) - except AttributeError: - cls._poll_event_descs = {} - - for value in cls.__dict__.values(): - if getattr(value, '_desc', False): - desc = value - # name = desc.__name__ - cls._poll_event_descs[desc._event] = desc - -"""Implements the logic to manage periodicity of events. - Reference to corresponding decorated methods are returned - if event has timedout. + Derives from python scheduler, since base scheduler does + a tight loop and does not leave the invoked context. + Derived here to return if no event timedout, invoked + periodically by caller to check for timedout events. """ -@six.add_metaclass(_Meta) -class PollEventDesc(object): - - def __init__(self): - super(PollEventDesc, self).__init__() - - def _nearest_boundary(self, last_run, spacing): - """Find nearest boundary which is in the past, - which is a multiple of the - spacing with the last run as an offset. - - Eg if last run was 10 and spacing was 7, - the new last run could be: 17, 24, - 31, 38... - - 0% to 5% of the spacing value will be added - to this value to ensure tasks - do not synchronize. This jitter is rounded - to the nearest second, this - means that spacings smaller than 20 seconds - will not have jitter. - """ - current_time = time.time() - if last_run is None: - return current_time - delta = current_time - last_run - offset = delta % spacing - # Add up to 5% jitter - jitter = int(spacing * (random.random() / 20)) - return current_time - offset + jitter - - def _timedout(self, desc, event): - """Check if event timedout w.r.t its spacing. """ - spacing = desc._spacing - last_run = event.desc.last_run - delta = 0 - - if last_run: - delta = last_run + spacing - time.time() - if delta > 0: - return None - event.desc.last_run = self._nearest_boundary(last_run, spacing) - return event +class NfpPollHandler(Scheduler): - def check_timedout(self, event): - """Check if event timedout w.r.t its spacing. + def __init__(self, conf): + self._conf = conf + Scheduler.__init__(self, pytime.time, eventlet.greenthread.sleep) - First check if the spacing is set for this event, if - not then return the event - in this case events timeout - at the periodicity of polling task. - If yes, then check if event timedout. - """ - try: - desc = self._poll_event_descs[event.id] - return self._timedout(desc, event) - except KeyError as exc: - exc = exc - return event - - def get_poll_event_desc(self, event): - """Get the registered event handler for the event. - - Check if the event has a specific periodic handler - defined, if then return it. - """ - try: - return self._poll_event_descs[event.id] - except KeyError as exc: - exc = exc - return None - - -"""Periodic task to poll for nfp events. - - Derived from oslo periodic task, polls periodically for the - NFP events, invokes registered event handler for the timedout - event. + def run(self): + """Run to find timedout event. """ + q = self._queue + timefunc = self.timefunc + # delayfunc = self.delayfunc + pop = heapq.heappop + if q: + time, priority, action, argument = checked_event = q[0] + now = timefunc() + if now < time: + return + else: + event = pop(q) + # Verify that the event was not removed or altered + # by another thread after we last looked at q[0]. + if event is checked_event: + action(*argument) + # delayfunc(0) # Let other threads run + else: + heapq.heappush(q, event) + + def poll_add(self, event, timeout, method): + """Enter the event to be polled. """ + self.enter(timeout, 1, method, (event,)) + +"""Periodic task to poll for timer events. + + Periodically checks for expiry of events. """ class PollingTask(oslo_periodic_task.PeriodicTasks): - def __init__(self, sc): - super(PollingTask, self).__init__(oslo_config.CONF) - self._sc = sc + def __init__(self, conf, controller): + super(PollingTask, self).__init__(conf) + + self._controller = controller pulse = oslo_looping_call.FixedIntervalLoopingCall( self.run_periodic_tasks, None, None) pulse.start( - interval=oslo_config.CONF.periodic_interval, initial_delay=None) + interval=1, initial_delay=None) - @oslo_periodic_task.periodic_task(spacing=2) - def periodic_sync_task(self, context): + @oslo_periodic_task.periodic_task(spacing=1) + def poll(self, context): # invoke the common class to handle event timeouts - self._sc.timeout() - -"""Handles the polling queue, searches for the timedout events. - - Invoked in PollingTask, fetches new events from pollQ to cache them. - Searches in cache for timedout events, enqueues timedout events to - respective worker process. Event stays in cache till it is declared to - be complete or cancelled. - Event gets cancelled, if it is polled for max number of times. By default, - it is huge number unless otherwise specified by logic which enqueues this - event. -""" - - -class PollQueueHandler(object): - - def __init__(self, sc, pipes, ehs): - self._sc = sc - self._ehs = ehs - self._pipes = pipes - self._cache = nfp_common.NfpFifo(sc) - - def run(self): - """Invoked in loop of periodic task to check for timedout events. """ - # Fill the cache first - self._fill_polling_cache() - cache = self._cache.copy() - for event in cache: - self._process_event(cache, event) - - def add_event(self, event): - """Adds an event to the poll cache. - - Invoked in context of worker process - to send event to polling task. - """ - LOG.debug("%s - added for polling" % (event.identify())) - self._cache.put(event) - - def event_expired(self, eh, event): - """Invoked when an event is expired. - - Invokes the nfp module method to notify - that event has expired. - - Executor: worker-process - """ - try: - LOG.debug("%s - event expired" % (event.identify())) - eh.event_cancelled(event.data, reason='EVENT_EXPIRED') - except AttributeError: - LOG.debug( - "%s - handler does not implement" - "event_cancelled method" % (identify(eh))) - - def event_timedout(self, eh, event): - """Invoked when an event timedout. - - When worker recieves a timedout, this method - will invoke approp method of nfp module based - on the type of timedout event and registered - handler. - - Executor: worker-process. - """ - if isinstance(eh, PollEventDesc): - # Check if this event has a decorated timeout method - peh = eh.get_poll_event_desc(event) - if peh: - ret = peh(eh, event) - LOG.debug( - "%s - timedout - invoking method:%s - " - "of handler:%s" % ( - event.identify(), identify(peh), identify(eh))) - else: - ret = eh.handle_poll_event(event) - LOG.debug( - "%s - timedout - " - "invoking method:handle_poll_event - " - "of handler:%s" % ( - event.identify(), identify(eh))) - else: - ret = eh.handle_poll_event(event) - LOG.debug( - "%s - timedout - invoking method:handle_poll_event - " - "of handler:%s" % ( - event.identify(), identify(eh))) - - self._event_dispatched(eh, event, ret) - - def _get(self, pipe, timeout=0.1): - """Internal method to get messages from pollQ. - - Handles the empty queue exception. - """ - try: - if pipe.poll(timeout): - return pipe.recv() - except multiprocessing.TimeoutError as err: - err = err - return None - - def _poll_event_cancelled(self, eh, event): - try: - LOG.debug( - "%s - poll event cancelled - " - "invoking method:poll_event_cancel - " - "of handler:%s" - % (event.identify(), identify(eh))) - eh.poll_event_cancel(event) - except AttributeError: - LOG.debug( - "%s - poll event cancelled - " - "handler:%s - does not implement" - "poll_event_cancel method" % ( - event.identify(), identify(eh))) - return - - def _get_default_status(self, event, ret): - status = {'poll': True, 'event': event} - if ret and 'event' in ret.keys(): - status['event'] = ret['event'] - if ret and 'poll' in ret.keys(): - status['poll'] = ret['poll'] - return status - - def _event_dispatched(self, eh, event, ret): - status = self._get_default_status(event, ret) - uevent = status['event'] - poll = status['poll'] - - uevent.max_times = event.max_times - 1 - - if not uevent.max_times: - return self._poll_event_cancelled(eh, event) - - if poll: - uevent.serialize = False - return self._sc.poll_event(uevent, max_times=uevent.max_times) - - def _schedule(self, ev): - """Schedule the event to approp worker. - - Checks if the event has timedout and if yes, - then schedules it to the approp worker. Approp worker - - worker which handled this event earlier. - - Executor: distributor-process - """ - eh = self._ehs.get(ev) - # Check if the event has any defined spacing interval, if yes - # then did it timeout w.r.t the spacing ? - # If yes, then event is scheduled. - # Spacing for event can only be defined if the registered event - # handler is derived from periodic task class. Following check - # is for same. - if isinstance(eh, PollEventDesc): - if eh.check_timedout(ev): - self._sc.post_timedoutevent(ev) - return ev - else: - self._sc.post_timedoutevent(ev) - return ev - return None - - def _poll_event_scheduled(self, ev): - """Marks the event as complete. - - Invoked by caller to mark the event as complete. - Removes the event from internal cache. - """ - self._cache.remove([ev]) - - def _schedule_poll_event(self, ev): - """Schedule a timedout to worker. """ - ev.desc.poll_event = 'POLL_EVENT' - ev.serialize = False - ev = self._schedule(ev) - if ev: - self._poll_event_scheduled(ev) - - def _process_event(self, cache, ev): - """Process different types of poll event. - - 'POLL_EVENT_CANCEL' - stop polling on this event. - 'POLL_EVENT_EXPIRY' - Poll for expiry of an event. - <*> - Poll for timeout w.r.t its spacing - - Executor: distributor-process - """ - - LOG.debug("%s - processing - from worker:%d" % - (ev.identify(), os.getpid())) - - if ev.desc.poll_event != 'POLL_EVENT': - self._cache.remove([ev]) - return self._sc.post_event(ev) - - if ev.id == 'POLL_EVENT_CANCEL': - return self._poll_event_scheduled(ev) - - if ev.id == 'POLL_EVENT_EXPIRY': - ev.max_times -= 1 - if not ev.max_times: - # Mark event as expired and schedule event - ev.id = 'EVENT_EXPIRED' - self._schedule_poll_event(ev) - else: - self._schedule_poll_event(ev) - - def _pull_event(self, pipe, timeout=0.1): - """Pull event from multiprocessing queue. - - Wait for some timeout if event is not - available. - """ - event = self._get(pipe, timeout=timeout) - if event: - LOG.debug( - "%s - new poll event" % (event.identify())) - self._cache.put(event) - return event - - def _fill_polling_cache(self): - """Fill polling cache with events from poll queue. - - Fetch messages from poll queue which is - python mutiprocessing.queue and fill local cache. - Events need to persist and polled they are declated complete - or cancelled. - """ - # Wait for the first event and for subsequent, - # pull in as much as possible with some max limit. - # as the same thread has to poll for already pulled - # events. - for pipe in self._pipes: - timeout = 0.01 - counter = 0 - # REVISIT(mak): Can the constant 10 be derived ? - while counter < 1 and self._pull_event(pipe, timeout=timeout): - timeout = 0 - counter += 1 + self._controller.poll() diff --git a/gbpservice/nfp/core/rpc.py b/gbpservice/nfp/core/rpc.py index 5b1e2a3f2b..e74fe57970 100644 --- a/gbpservice/nfp/core/rpc.py +++ b/gbpservice/nfp/core/rpc.py @@ -10,7 +10,6 @@ # License for the specific language governing permissions and limitations # under the License. - from oslo_config import cfg as oslo_config from oslo_service import loopingcall as oslo_looping_call @@ -25,7 +24,8 @@ from gbpservice.nfp.core import log as nfp_logging LOG = nfp_logging.getLogger(__name__) -identify = nfp_common.identify + +n_rpc.init(oslo_config.CONF) """Wrapper class for Neutron RpcAgent definition. @@ -104,16 +104,16 @@ def report(self): class ReportStateTask(oslo_periodic_task.PeriodicTasks): - def __init__(self, sc): - super(ReportStateTask, self).__init__(oslo_config.CONF) - self._sc = sc + def __init__(self, conf, controller): + super(ReportStateTask, self).__init__(conf) + self._controller = controller # Start a looping at the defined pulse pulse = oslo_looping_call.FixedIntervalLoopingCall( self.run_periodic_tasks, None, None) pulse.start( - interval=oslo_config.CONF.reportstate_interval, initial_delay=None) + interval=1, initial_delay=None) - @oslo_periodic_task.periodic_task(spacing=5) + @oslo_periodic_task.periodic_task(spacing=10) def report_state(self, context): # trigger the state reporting - self._sc.report_state() + self._controller.report_state() diff --git a/gbpservice/nfp/core/sequencer.py b/gbpservice/nfp/core/sequencer.py new file mode 100644 index 0000000000..024f75913c --- /dev/null +++ b/gbpservice/nfp/core/sequencer.py @@ -0,0 +1,118 @@ +# 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 collections + +from gbpservice.nfp.core import common as nfp_common +from gbpservice.nfp.core import log as nfp_logging + +LOG = nfp_logging.getLogger(__name__) + +deque = collections.deque + + +class SequencerEmpty(Exception): + pass + + +class SequencerBusy(Exception): + pass + +"""Sequences the events. """ + + +class EventSequencer(object): + + class Sequencer(object): + + def __init__(self): + # Events not scheduled are queued + self._waitq = deque() + # Currently scheduled event + self._scheduled = None + + def _is_busy(self): + if self._scheduled: + raise SequencerBusy + + def _is_empty(self): + if not len(self._waitq): + raise SequencerEmpty + + def sequence(self, event): + self._waitq.append(event) + + def run(self): + """Run to get event to be scheduled. + + If sequencer is busy - i.e, an event is already + scheduled and in progress raises busy except. + If sequencer is empty - i.e, no event in sequencer + raises empty except. + """ + self._is_busy() + self._is_empty() + # Pop the first element in the queue - FIFO + self._scheduled = self._waitq.popleft() + return self._scheduled + + def is_scheduled(self, event): + if self._scheduled: + return self._scheduled.desc.uuid == event.desc.uuid and \ + self._scheduled.id == event.id + return True + + def release(self): + self._scheduled = None + + def __init__(self): + # Sequence of related events + # {key: sequencer()} + self._sequencer = {} + + def sequence(self, key, event): + try: + self._sequencer[key].sequence(event) + except KeyError: + self._sequencer[key] = self.Sequencer() + self._sequencer[key].sequence(event) + LOG.error("Sequenced event - %s" % + (event.identify())) + + def run(self): + events = [] + # Loop over copy and delete from original + sequencers = dict(self._sequencer) + for key, sequencer in sequencers.iteritems(): + try: + event = sequencer.run() + if event: + LOG.error("Desequence event - %s" % + (event.identify())) + event.sequence = False + events.append(event) + except SequencerBusy as exc: + pass + except SequencerEmpty as exc: + exc = exc + LOG.debug("Sequencer empty") + del self._sequencer[key] + return events + + def release(self, key, event): + try: + LOG.debug("(event - %s) checking to release" %(event.identify())) + if self._sequencer[key].is_scheduled(event): + LOG.debug("(event - %s) Releasing sequencer" %(event.identify())) + self._sequencer[key].release() + except KeyError: + return diff --git a/gbpservice/nfp/core/test/nfp_core_test.py b/gbpservice/nfp/core/test/nfp_core_test.py index 50424e0b58..ce7dbd4e6e 100755 --- a/gbpservice/nfp/core/test/nfp_core_test.py +++ b/gbpservice/nfp/core/test/nfp_core_test.py @@ -10,228 +10,72 @@ # License for the specific language governing permissions and limitations # under the License. -import os import time -from oslo_config import cfg -from oslo_log import log as logging -import oslo_messaging as messaging - from gbpservice.nfp.core import common as nfp_common from gbpservice.nfp.core import event as nfp_event -from gbpservice.nfp.core import poll as nfp_poll -from gbpservice.nfp.core import rpc as nfp_rpc +from gbpservice.nfp.core import module as nfp_api from gbpservice.nfp.core import log as nfp_logging +_NAME_ = 'Visibility' -LOG = nfp_logging.getLogger(__name__) -Event = nfp_event.Event -PollEventDesc = nfp_poll.PollEventDesc -RpcAgent = nfp_rpc.RpcAgent - -VISIBILITY_RPC_TOPIC = "visiblity_topic" +EVENT = nfp_event.Event - -def rpc_init(sc, conf): - rpcmgr = RpcManager(conf, sc) - agent = RpcAgent( - sc, - host=cfg.CONF.host, - topic=VISIBILITY_RPC_TOPIC, - manager=rpcmgr - ) - sc.register_rpc_agents([agent]) +LOG = nfp_logging.getLogger(__name__) -def events_init(sc): +def nfp_module_init(controller, conf): evs = [ - Event(id='SERVICE_CREATE', handler=Agent(sc)), - Event(id='SERVICE_DELETE', handler=Agent(sc)), - Event(id='SERVICE_DUMMY_EVENT', handler=Agent(sc)), - Event(id='EVENT_EXPIRED', handler=Agent(sc))] - sc.register_events(evs) - - -def nfp_module_init(sc, conf): - events_init(sc) - rpc_init(sc, conf) - - -def nfp_module_post_init(sc, conf): - unit_test(conf, sc) - - -def unit_test(conf, sc): - for i in range(0, 1): - test_service_create(conf, sc) - - -def test_service_create(conf, sc): - """Write the unit test logic here - """ - service1 = {'id': 'sc2f2b13-e284-44b1-9d9a-2597e216271a', - 'tenant': '40af8c0695dd49b7a4980bd1b47e1a1b', - 'servicechain': 'sc2f2b13-e284-44b1-9d9a-2597e2161c', - 'servicefunction': 'sf2f2b13-e284-44b1-9d9a-2597e216561d', - 'vip_id': '13948da4-8dd9-44c6-adef-03a6d8063daa', - 'service_vendor': 'haproxy', - 'service_type': 'loadbalancer', - 'ip': '192.168.20.199' - } - # Collector(service).create() - # Event with timer - ev = sc.new_event(id='SERVICE_CREATE', data=service1, - binding_key=service1['id'], - # binding_key='SERIALIZE', - key=service1['id'], lifetime=11, serialize=True) - sc.post_event(ev) - - service2 = {'id': 'sc2f2b13-e284-44b1-9d9a-2597e216272a', - 'tenant': '40af8c0695dd49b7a4980bd1b47e1a2b', - 'servicechain': 'sc2f2b13-e284-44b1-9d9a-2597e216562c', - 'servicefunction': 'sf2f2b13-e284-44b1-9d9a-2597e216562d', - 'mac_address': 'fa:16:3e:3f:93:05', - 'service_vendor': 'vyos', - 'service_type': 'firewall', - 'ip': '192.168.20.197' - } - # event Without Timer - ev = sc.new_event(id='SERVICE_CREATE', data=service2, - binding_key=service2['id'], - # binding_key='SERIALIZE', - key=service2['id'], serialize=True) - sc.post_event(ev) - - service3 = {'id': 'sc2f2b13-e284-44b1-9d9a-2597e216273a', - 'tenant': '40af8c0695dd49b7a4980bd1b47e1a2b', - 'servicechain': 'sc2f2b13-e284-44b1-9d9a-2597e216563c', - 'servicefunction': 'sf2f2b13-e284-44b1-9d9a-2597e216563d', - 'mac_address': 'fa:16:3e:3f:93:05', - 'service_vendor': 'vyos', - 'service_type': 'vpn', - 'ip': '192.168.20.197' - } - - ev = sc.new_event(id='SERVICE_CREATE', data=service3, - binding_key=service3['id'], - # binding_key='SERIALIZE', - key=service3['id'], serialize=True) - sc.post_event(ev) - - time.sleep(5) - ev = sc.new_event(id='SERVICE_DELETE', data=service1, - binding_key=service1['id'], - key=service1['id'], serialize=True) - sc.post_event(ev) - - ev = sc.new_event(id='SERVICE_DUMMY_EVENT', key='dummy_event') - sc.post_event(ev) - - time.sleep(1) - while True: - events = sc.get_stashed_events() - for event in events: - LOG.info("Stashed event %s " % (event.identify())) - time.sleep(1) - - -class Collector(object): - - def __init__(self, service): - self._service = service - - def create(self): - pass - - def delete(self): - pass - - -class RpcManager(object): - RPC_API_VERSION = '1.0' - target = messaging.Target(version=RPC_API_VERSION) - - def __init__(self, conf, sc): - super(RpcManager, self).__init__() - self.conf = conf - self._sc = sc - - def service_created(self, context, **kwargs): - pass - - def service_deleted(self, context, **kwargs): - pass - - -class Agent(PollEventDesc): - - def __init__(self, sc): - self._sc = sc - - def handle_poll_event(self, ev): - self._handle_poll_event(ev) - - def handle_event(self, ev): - LOG.info("Handle event :%s, Process ID :%d" % - (ev.identify(), os.getpid())) - if ev.id == 'SERVICE_CREATE': - self._handle_create_event(ev) - elif ev.id == 'SERVICE_DELETE': - self._handle_delete_event(ev) - elif ev.id == 'SERVICE_DUMMY_EVENT': - self._handle_dummy_event(ev) - - def event_cancelled(self, ev, reason=''): - LOG.info( - "In event_cancel method of Handler for" - "Event %s - reason %s " % - (ev.identify(), reason)) - - def _handle_create_event(self, ev): - """Driver logic here. - """ - self._sc.event_done(ev) - self._sc.poll_event(ev) - - def _handle_dummy_event(self, ev): - self._sc.poll_event(ev, max_times=2) - event = self._sc.new_event( - id='STASH_EVENT', key='STASH_EVENT', data={}) - self._sc.stash_event(event) - - def _handle_delete_event(self, ev): - """Driver logic here. - """ - self._sc.event_done(ev) - # self._sc.poll_event_done(ev) - - def poll_event_cancel(self, event): - LOG.info("In poll_event_cancel" - "method of Handler for Event %s " % - (event)) - - @nfp_poll.poll_event_desc(event='SERVICE_CREATE', spacing=1) - def service_create_poll_event(self, ev): - self._sc.stash_event(ev) - poll = True - - if not ev.data.get('count'): - ev.data['count'] = 2 - else: - ev.data['count'] -= 1 - if not ev.data['count']: - poll = False - - LOG.info("Poll event (%s)" % (ev.identify())) - return {'poll': poll, 'event': ev} - - @nfp_poll.poll_event_desc(event='SERVICE_DUMMY_EVENT', spacing=1) - def service_dummy_poll_event(self, ev): - self._sc.stash_event(ev) - LOG.info("Poll event (%s)" % (ev.identify())) - self._sc.poll_event_done(ev) - - def _handle_poll_event(self, ev): - """Driver logic here - """ - LOG.info("Poll event (%s)" % (ev.identify())) + EVENT(id='EVENT_ID_1', handler=EventsHandler(controller)), + EVENT(id='EVENT_ID_2', handler=EventsHandler(controller)), + EVENT(id='EVENT_ID_3', handler=EventsHandler(controller)), + EVENT(id='EVENT_ID_4', handler=EventsHandler(controller))] + controller.register_events(evs) + + +def module_test(controller, conf): + event = controller.create_event( + id='EVENT_ID_1', data='Ahmed', + serialize=True, binding_key='EVENT_ID_1', lifetime=30) + controller.post_event(event) + + +class EventsHandler(nfp_api.NfpEventHandler): + + def __init__(self, controller): + self._controller = controller + + def handle_event(self, event): + LOG.info("Handle Event %s" % (event.identify())) + if event.id == 'EVENT_ID_1': + # LOG.info("ADDING POLL EVENT %s CURR TIME %s" % + # (event.identify(), time.time())) + self._controller.poll_event(event, max_times=10, spacing=1) + event = self._controller.create_event( + id='EVENT_ID_2', data='Ahmed', + serialize=True, binding_key='EVENT_ID_1', lifetime=40) + self._controller.post_event(event) + elif event.id == 'EVENT_ID_2': + self._controller.poll_event(event, max_times=10) + event = self._controller.create_event( + id='EVENT_ID_3', data='Ahmed', + serialize=True, binding_key='EVENT_ID_1', lifetime=30) + self._controller.post_event(event) + elif event.id == 'EVENT_ID_3': + event = self._controller.create_event( + id='EVENT_ID_4', data='Ahmed', + serialize=True, binding_key='EVENT_ID_1', lifetime=10) + self._controller.post_event(event) + + def handle_poll_event(self, event): + LOG.info("Event TIMEDOUT %s CURR TIME : %s" % + (event.identify(), time.time())) + + def event_cancelled(self, event, reason): + LOG.info("Event %s cancelled reason %s" % + (event.identify(), reason)) + + @nfp_api.poll_event_desc(event='EVENT_ID_2', spacing=2) + def event_1_poll_event(self, event): + LOG.info("Event TIMEDOUT %s CURR TIME : %s" % + (event.identify(), time.time())) diff --git a/gbpservice/nfp/core/version.py b/gbpservice/nfp/core/version.py new file mode 100644 index 0000000000..4221e38908 --- /dev/null +++ b/gbpservice/nfp/core/version.py @@ -0,0 +1,17 @@ +# Copyright 2011 OpenStack Foundation +# +# 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 pbr.version + +version_info = pbr.version.VersionInfo('nfp') diff --git a/gbpservice/nfp/core/worker.py b/gbpservice/nfp/core/worker.py new file mode 100644 index 0000000000..1e3907f1b3 --- /dev/null +++ b/gbpservice/nfp/core/worker.py @@ -0,0 +1,148 @@ +# 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 time + +from oslo_service import service as oslo_service + +from gbpservice.nfp.core import common as nfp_common +from gbpservice.nfp.core import event as nfp_event +from gbpservice.nfp.core import log as nfp_logging + +LOG = nfp_logging.getLogger(__name__) +Service = oslo_service.Service +identify = nfp_common.identify + +"""Implements worker process. + + Derives from oslo service. + Implements the worker functionality. + Waits for the events from distributor, handles them, + invokes the registered event handler in a thread. +""" + + +class NfpWorker(Service): + + def __init__(self, conf, threads=10): + # REVISIT(MAK): Can threads be configured ? + Service.__init__(self, threads=threads) + # Parent end of duplex pipe + self.parent_pipe = None + # Pipe to recv/send messages to distributor + self.pipe = None + # Cache of event handlers + self.controller = None + self._conf = conf + self._threads = threads + + def start(self): + """Service start, runs here till dies. + + When a oslo service is launched, this method + is invoked. + Polls for messages from distributor and process + them. + """ + # Update the process type in controller. + self.controller.PROCESS_TYPE = "worker" + self.controller._pipe = self.pipe + self.event_handlers = self.controller.get_event_handlers() + while True: + try: + event = None + if self.pipe.poll(): + event = self.pipe.recv() + if event: + LOG.debug("%s - received event" % + (self._log_meta(event))) + self.controller.decompress(event) + self._process_event(event) + except Exception as e: + LOG.error("Exception - %s" % (e)) + # Yeild cpu + time.sleep(0) + + def _log_meta(self, event=None): + if event: + return "(event - %s) - (worker - %d)" % ( + event.identify(), os.getpid()) + else: + return "(worker - %d)" % (os.getpid()) + + def _send_event_ack(self, event): + # Create new event from existing one + ack_event = nfp_event.Event(id=event.id) + ack_event.id = event.id + desc = nfp_event.EventDesc(**event.desc.__dict__) + desc.uuid = event.desc.uuid + desc.flag = nfp_event.EVENT_ACK + setattr(ack_event, 'desc', desc) + self.pipe.send(ack_event) + + def _process_event(self, event): + """Process & dispatch the event. + + Decodes the event type and performs the required + action. + Executes the registered event handler in one of the + thread. + """ + if event.desc.type == nfp_event.SCHEDULE_EVENT: + self._send_event_ack(event) + eh = self.event_handlers.get_event_handler(event.id) + self.dispatch(eh.handle_event, event) + elif event.desc.type == nfp_event.POLL_EVENT: + self.dispatch(self._handle_poll_event, event) + elif event.desc.type == nfp_event.EVENT_EXPIRED: + eh = self.event_handlers.get_event_handler(event.id) + self.dispatch(eh.event_cancelled, event, 'EXPIRED') + + def _build_poll_status(self, ret, event): + status = {'poll': True, 'event': event} + if ret: + status['poll'] = ret.get('poll', status['poll']) + status['event'] = ret.get('event', status['event']) + status['event'].desc = event.desc + + return status + + def _repoll(self, ret, event, eh): + status = self._build_poll_status(ret, event) + if status['poll']: + if event.desc.poll_desc.max_times: + self.pipe.send(status['event']) + else: + eh.event_cancelled(event, 'MAX_TIMED_OUT') + + def _handle_poll_event(self, event): + ret = {} + event.desc.poll_desc.max_times -= 1 + poll_handler = self.event_handlers.get_poll_handler(event.id) + event_handler = self.event_handlers.get_event_handler(event.id) + try: + ret = poll_handler(event) + except TypeError: + ret = poll_handler(event_handler, event) + self._repoll(ret, event, event_handler) + + def dispatch(self, handler, *args): + if self._threads: + th = self.tg.add_thread(handler, *args) + LOG.debug("%s - (handler - %s) - " + "dispatched to thread %d" % + (self._log_meta(), identify(handler), th.ident)) + else: + handler(*args) + LOG.debug("%s - (handler - %s) - invoked" % + (self._log_meta(), identify(handler))) diff --git a/gbpservice/nfp/orchestrator/coal/networking/nfp_gbp_network_driver.py b/gbpservice/nfp/orchestrator/coal/networking/nfp_gbp_network_driver.py index e692375320..184042ab4c 100644 --- a/gbpservice/nfp/orchestrator/coal/networking/nfp_gbp_network_driver.py +++ b/gbpservice/nfp/orchestrator/coal/networking/nfp_gbp_network_driver.py @@ -42,12 +42,19 @@ def update_port(self, token, port_id, port): port) return pt['port_id'] + def get_neutron_port_details(self, token, port_id): + #self.network_handler = openstack_driver.NeutronClient(self.config) + port_details = super(NFPGBPNetworkDriver, self).get_port_and_subnet_details( + token, port_id) + #self.network_handler = openstack_driver.GBPClient(self.config) + return port_details + def get_port_details(self, token, port_id): _port_id = self.get_port_id(token, port_id) - self.network_handler = openstack_driver.NeutronClient(self.config) + #self.network_handler = openstack_driver.NeutronClient(self.config) port_details = super(NFPGBPNetworkDriver, self).get_port_details( token, _port_id) - self.network_handler = openstack_driver.GBPClient(self.config) + #self.network_handler = openstack_driver.GBPClient(self.config) return port_details def get_networks(self, token, filters): @@ -56,9 +63,14 @@ def get_networks(self, token, filters): def set_promiscuos_mode(self, token, port_id): port_id = self.get_port_id(token, port_id) - self.network_handler = openstack_driver.NeutronClient(self.config) + #self.network_handler = openstack_driver.NeutronClient(self.config) + super(NFPGBPNetworkDriver, self).set_promiscuos_mode(token, port_id) + #self.network_handler = openstack_driver.GBPClient(self.config) + + def set_promiscuos_mode_v1(self, token, port_id): + #self.network_handler = openstack_driver.NeutronClient(self.config) super(NFPGBPNetworkDriver, self).set_promiscuos_mode(token, port_id) - self.network_handler = openstack_driver.GBPClient(self.config) + #self.network_handler = openstack_driver.GBPClient(self.config) def get_service_profile(self, token, service_profile_id): return self.network_handler.get_service_profile(token, diff --git a/gbpservice/nfp/orchestrator/coal/networking/nfp_neutron_network_driver.py b/gbpservice/nfp/orchestrator/coal/networking/nfp_neutron_network_driver.py index 7c727b77a0..79c12c869b 100644 --- a/gbpservice/nfp/orchestrator/coal/networking/nfp_neutron_network_driver.py +++ b/gbpservice/nfp/orchestrator/coal/networking/nfp_neutron_network_driver.py @@ -17,29 +17,48 @@ class NFPNeutronNetworkDriver(ndb.NFPNetworkDriverBase): + def __init__(self, config): - self.network_handler = openstack_driver.NeutronClient(config) + # self.network_handler = openstack_driver.NeutronClient(config) + self.neutron_client = openstack_driver.NeutronClient(config) def setup_traffic_steering(self): pass def create_port(self, token, tenant_id, net_id, name=None): - port = self.network_handler.create_port(token, tenant_id, net_id, - attrs={'name': name}) + port = self.neutron_client.create_port(token, tenant_id, net_id, + attrs={'name': name}) return port def delete_port(self, token, port_id): - self.network_handler.delete_port(token, port_id) + self.neutron_client.delete_port(token, port_id) def get_port_id(self, token, port_id): return port_id def update_port(self, token, port_id, port): - port = self.network_handler.update_port(token, port_id, port) + port = self.neutron_client.update_port(token, port_id, port) return port['port'] + def get_port_and_subnet_details(self, token, port_id): + port = self.neutron_client.get_port(token, port_id) + + # ip + ip = port['port']['fixed_ips'][0]['ip_address'] + + # mac + mac = port['port']['mac_address'] + + # gateway ip + subnet_id = port['port']['fixed_ips'][0]['subnet_id'] + subnet = self.neutron_client.get_subnet(token, subnet_id) + cidr = subnet['subnet']['cidr'] + gateway_ip = subnet['subnet']['gateway_ip'] + + return (ip, mac, cidr, gateway_ip, port, subnet) + def get_port_details(self, token, port_id): - port = self.network_handler.get_port(token, port_id) + port = self.neutron_client.get_port(token, port_id) # ip ip = port['port']['fixed_ips'][0]['ip_address'] @@ -49,16 +68,16 @@ def get_port_details(self, token, port_id): # gateway ip subnet_id = port['port']['fixed_ips'][0]['subnet_id'] - subnet = self.network_handler.get_subnet(token, subnet_id) + subnet = self.neutron_client.get_subnet(token, subnet_id) cidr = subnet['subnet']['cidr'] gateway_ip = subnet['subnet']['gateway_ip'] return (ip, mac, cidr, gateway_ip) def set_promiscuos_mode(self, token, port_id): - self.network_handler.update_port(token, port_id, - security_groups=[], - port_security_enabled=False) + self.neutron_client.update_port(token, port_id, + security_groups=[], + port_security_enabled=False) def get_service_profile(self, token, service_profile_id): return {} diff --git a/gbpservice/nfp/orchestrator/config_drivers/heat_driver.py b/gbpservice/nfp/orchestrator/config_drivers/heat_driver.py index 3510d87da7..82a6e55f33 100644 --- a/gbpservice/nfp/orchestrator/config_drivers/heat_driver.py +++ b/gbpservice/nfp/orchestrator/config_drivers/heat_driver.py @@ -90,12 +90,23 @@ class HeatDriver(object): + def __init__(self, config): self.keystoneclient = KeystoneClient(config) self.gbp_client = GBPClient(config) self.neutron_client = NeutronClient(config) # self.resource_owner_tenant_id = None + keystone_conf = cfg.CONF.keystone_authtoken + keystone_version = keystone_conf.auth_version + self.v2client = self.keystoneclient._get_v2_keystone_admin_client() + self.admin_id = self.v2client.users.find( + name=keystone_conf.admin_user).id + self.admin_role = self._get_role_by_name( + self.v2client, "admin", keystone_version) + self.heat_role = self._get_role_by_name( + self.v2client, "heat_stack_owner", keystone_version) + ''' @property def resource_owner_tenant_id(self): @@ -133,7 +144,10 @@ def _get_resource_owner_context(self): self.keystoneclient.get_keystone_creds() auth_token = self.keystoneclient.get_scoped_keystone_token( user, pwd, tenant_name, tenant_id) - return auth_token, tenant_id + + tenant_id = self.keystoneclient.get_tenant_id( + auth_token, tenant_name) + return auth_token, tenant_id def _get_role_by_name(self, keystone_client, name, keystone_version): if keystone_version == 'v2.0': @@ -155,29 +169,25 @@ def get_allocated_roles(self, v2client, user, tenant_id=None): allocated_role_names.append(role.name) return allocated_role_names + def _assign_admin_user_to_project_v2(self, project_id): + allocated_role_names = self.get_allocated_roles( + self.v2client, self.admin_id, project_id) + if self.admin_role: + if self.admin_role.name not in allocated_role_names: + self.v2client.roles.add_user_role( + self.admin_id, self.admin_role.id, tenant=project_id) + if self.heat_role: + if self.heat_role.name not in allocated_role_names: + self.v2client.roles.add_user_role(self.admin_id, + self.heat_role.id, + tenant=project_id) + def _assign_admin_user_to_project(self, project_id): keystone_conf = cfg.CONF.keystone_authtoken keystone_version = keystone_conf.auth_version if keystone_version == 'v2.0': - v2client = self.keystoneclient._get_v2_keystone_admin_client() - admin_id = v2client.users.find(name=keystone_conf.admin_user).id - admin_role = self._get_role_by_name(v2client, "admin", - keystone_version) - allocated_role_names = self.get_allocated_roles( - v2client, admin_id, project_id) - - if admin_role: - if admin_role.name not in allocated_role_names: - v2client.roles.add_user_role( - admin_id, admin_role.id, tenant=project_id) - - heat_role = self._get_role_by_name(v2client, "heat_stack_owner", - keystone_version) - if heat_role: - if heat_role.name not in allocated_role_names: - v2client.roles.add_user_role(admin_id, heat_role.id, - tenant=project_id) + return self._assign_admin_user_to_project_v2(project_id) else: v3client = self.keystoneclient._get_v3_keystone_admin_client() admin_id = v3client.users.find(name=keystone_conf.admin_user).id @@ -200,6 +210,36 @@ def keystone(self, user, pwd, tenant_name, tenant_id=None): return self.keystoneclient.get_scoped_keystone_token( user, pwd, tenant_name) + def _get_heat_client_v1(self, tenant_id, assign_admin=False): + if assign_admin: + try: + self._assign_admin_user_to_project(tenant_id) + except Exception: + LOG.exception(_LE("Failed to assign admin user to project")) + return None + + user, password, tenant, auth_url =\ + self.keystoneclient.get_keystone_creds() + + auth_token = self.keystone(user, password, tenant, tenant_id=tenant_id) + + timeout_mins, timeout_seconds = divmod(STACK_ACTION_WAIT_TIME, 60) + if timeout_seconds: + timeout_mins = timeout_mins + 1 + try: + heat_client = HeatClient( + user, + tenant_id, + cfg.CONF.heat_driver.heat_uri, + password, + auth_token=auth_token, + timeout_mins=timeout_mins) + except Exception: + LOG.exception(_LE("Failed to create heatclient object")) + return None + + return heat_client + def _get_heat_client(self, resource_owner_tenant_id, tenant_id=None): user_tenant_id = tenant_id or resource_owner_tenant_id try: @@ -207,6 +247,7 @@ def _get_heat_client(self, resource_owner_tenant_id, tenant_id=None): except Exception: LOG.exception(_LE("Failed to assign admin user to project")) return None + user, password, tenant, auth_url =\ self.keystoneclient.get_keystone_creds() admin_token = self.keystone( @@ -445,6 +486,70 @@ def _get_all_heat_resource_keys(self, template_resource_dict, resource_keys.append(key) return resource_keys + def _create_firewall_template(self, auth_token, + service_details, stack_template): + + provider = service_details['provider_ptg'] + + consuming_ptgs_details = service_details['consuming_ptgs_details'] + consumer_eps = service_details['consuming_external_policies'] + + if (not consuming_ptgs_details) and (not consumer_eps): + return None + + is_template_aws_version = stack_template.get( + 'AWSTemplateFormatVersion', False) + resources_key = 'Resources' if is_template_aws_version else 'resources' + properties_key = ('Properties' if is_template_aws_version + else 'properties') + fw_rule_keys = self._get_all_heat_resource_keys( + stack_template[resources_key], is_template_aws_version, + 'OS::Neutron::FirewallRule') + fw_policy_key = self._get_all_heat_resource_keys( + stack_template['resources'], is_template_aws_version, + 'OS::Neutron::FirewallPolicy')[0] + + provider_subnet = service_details['provider_subnet'] + provider_cidr = provider_subnet['cidr'] + + fw_template_properties = dict( + resources_key=resources_key, properties_key=properties_key, + is_template_aws_version=is_template_aws_version, + fw_rule_keys=fw_rule_keys, + fw_policy_key=fw_policy_key) + + for consumer in consuming_ptgs_details: + ptg = consumer['ptg'] + subnets = consumer['subnets'] + + # Skip the stitching PTG + if ptg['proxied_group_id']: + continue + + fw_template_properties.update({'name': ptg['id'][:3]}) + for subnet in subnets: + if subnet['name'].startswith(APIC_OWNED_RES): + continue + + consumer_cidr = subnet['cidr'] + self._append_firewall_rule(stack_template, + provider_cidr, consumer_cidr, + fw_template_properties, ptg['id']) + + for consumer_ep in consumer_eps: + fw_template_properties.update({'name': consumer_ep['id'][:3]}) + self._append_firewall_rule(stack_template, provider_cidr, + "0.0.0.0/0", fw_template_properties, + consumer_ep['id']) + + for rule_key in fw_rule_keys: + del stack_template[resources_key][rule_key] + stack_template[resources_key][fw_policy_key][ + properties_key]['firewall_rules'].remove( + {'get_resource': rule_key}) + + return stack_template + def _update_firewall_template(self, auth_token, provider, stack_template): consumer_ptgs, consumer_eps = self._get_consumers_for_chain( auth_token, provider) @@ -606,6 +711,119 @@ def _get_site_conn_keys(self, template_resource_dict, keys.append(key) return keys + def _create_node_config_data(self, auth_token, tenant_id, + service_chain_node, service_chain_instance, + provider, provider_port, consumer, + consumer_port, network_function, + mgmt_ip, service_details): + + nf_desc = None + common_desc = {'network_function_id': network_function['id']} + + service_type = service_details['service_details']['service_type'] + service_vendor = service_details['service_details']['service_vendor'] + device_type = service_details['service_details']['device_type'] + base_mode_support = (True if device_type == 'None' + else False) + + _, stack_template_str = self.parse_template_config_string( + service_chain_node.get('config')) + try: + stack_template = (jsonutils.loads(stack_template_str) if + stack_template_str.startswith('{') else + yaml.load(stack_template_str)) + except Exception: + LOG.error(_LE( + "Unable to load stack template for service chain " + "node: %(node_id)s") % {'node_id': service_chain_node}) + return None, None + config_param_values = service_chain_instance.get( + 'config_param_values', '{}') + stack_params = {} + try: + config_param_values = jsonutils.loads(config_param_values) + except Exception: + LOG.error(_LE("Unable to load config parameters")) + return None, None + + is_template_aws_version = stack_template.get( + 'AWSTemplateFormatVersion', False) + resources_key = ('Resources' if is_template_aws_version + else 'resources') + parameters_key = ('Parameters' if is_template_aws_version + else 'parameters') + properties_key = ('Properties' if is_template_aws_version + else 'properties') + + if not base_mode_support: + provider_port_mac = provider_port['mac_address'] + provider_cidr = service_details['provider_subnet']['cidr'] + provider_subnet = service_details['provider_subnet'] + else: + provider_port_mac = '' + provider_cidr = '' + standby_provider_port_mac = None + + if service_type == pconst.LOADBALANCER: + self._generate_pool_members( + auth_token, stack_template, config_param_values, + provider, is_template_aws_version) + config_param_values['Subnet'] = provider_subnet['id'] + config_param_values['service_chain_metadata'] = "" + if not base_mode_support: + config_param_values[ + 'service_chain_metadata'] = str(common_desc) + nf_desc = str((SC_METADATA % (service_chain_instance['id'], + mgmt_ip, + provider_port_mac, + standby_provider_port_mac, + network_function['id'], + service_vendor))) + + lb_pool_key = self._get_heat_resource_key( + stack_template[resources_key], + is_template_aws_version, + 'OS::Neutron::Pool') + stack_template[resources_key][lb_pool_key][properties_key][ + 'description'] = str(common_desc) + elif service_type == pconst.FIREWALL: + stack_template = self._create_firewall_template( + auth_token, service_details, stack_template) + + if not stack_template: + return None, None + self._modify_fw_resources_name( + stack_template, provider, is_template_aws_version) + if not base_mode_support: + firewall_desc = {'vm_management_ip': mgmt_ip, + 'provider_ptg_info': [provider_port_mac], + 'provider_cidr': provider_cidr, + 'service_vendor': service_vendor, + 'network_function_id': network_function[ + 'id']} + + fw_key = self._get_heat_resource_key( + stack_template[resources_key], + is_template_aws_version, + 'OS::Neutron::Firewall') + stack_template[resources_key][fw_key][properties_key][ + 'description'] = str(common_desc) + + nf_desc = str(firewall_desc) + + if nf_desc: + network_function['description'] = network_function[ + 'description'] + '\n' + nf_desc + + for parameter in stack_template.get(parameters_key) or []: + if parameter in config_param_values: + stack_params[parameter] = config_param_values[parameter] + + LOG.info(_LI('Final stack_template : %(stack_data)s, ' + 'stack_params : %(params)s') % + {'stack_data': stack_template, 'params': stack_params}) + return (stack_template, stack_params) + def _update_node_config(self, auth_token, tenant_id, service_profile, service_chain_node, service_chain_instance, provider, consumer_port, network_function, @@ -1072,6 +1290,42 @@ def is_config_complete(self, stack_id, tenant_id, {'stack': stack_id}) return failure_status + def check_config_complete(self, nfp_context): + success_status = "COMPLETED" + failure_status = "ERROR" + intermediate_status = "IN_PROGRESS" + + provider_tenant_id = nfp_context['tenant_id'] + stack_id = nfp_context['heat_stack_id'] + + heatclient = self._get_heat_client_v1(provider_tenant_id) + if not heatclient: + return failure_status + try: + stack = heatclient.get(stack_id) + if stack.stack_status == 'DELETE_FAILED': + return failure_status + elif stack.stack_status == 'CREATE_COMPLETE': + return success_status + elif stack.stack_status == 'UPDATE_COMPLETE': + return success_status + elif stack.stack_status == 'DELETE_COMPLETE': + LOG.info(_LI("Stack %(stack)s is deleted"), + {'stack': stack_id}) + return failure_status + elif stack.stack_status == 'CREATE_FAILED': + return failure_status + elif stack.stack_status == 'UPDATE_FAILED': + return failure_status + elif stack.stack_status not in [ + 'UPDATE_IN_PROGRESS', 'CREATE_IN_PROGRESS', + 'DELETE_IN_PROGRESS']: + return intermediate_status + except Exception: + LOG.exception(_LE("Retrieving the stack %(stack)s failed."), + {'stack': stack_id}) + return failure_status + def is_config_delete_complete(self, stack_id, tenant_id): success_status = "COMPLETED" failure_status = "ERROR" @@ -1105,6 +1359,47 @@ def is_config_delete_complete(self, stack_id, tenant_id): {'stack': stack_id}) return failure_status + def get_service_details_from_nfp_context(self, nfp_context): + network_function = nfp_context['network_function'] + network_function_instance = nfp_context['network_function_instance'] + service_details = nfp_context['service_details'] + mgmt_ip = nfp_context['management']['port']['ip_address'] + heat_stack_id = network_function['heat_stack_id'] + service_id = network_function['service_id'] + service_chain_id = network_function['service_chain_id'] + servicechain_instance = nfp_context['service_chain_instance'] + servicechain_node = nfp_context['service_chain_node'] + + consumer_policy_target_group = nfp_context['consumer']['ptg'] + provider_policy_target_group = nfp_context['provider']['ptg'] + provider_port = nfp_context['provider']['port'] + provider_subnet = nfp_context['provider']['subnet'] + consumer_port = nfp_context['consumer']['port'] + consumer_subnet = nfp_context['consumer']['subnet'] + service_details['consuming_external_policies'] = nfp_context[ + 'consuming_eps_details'] + service_details['consuming_ptgs_details'] = nfp_context[ + 'consuming_ptgs_details'] + + return { + 'service_profile': None, + 'service_details': service_details, + 'servicechain_node': servicechain_node, + 'servicechain_instance': servicechain_instance, + 'consumer_port': consumer_port, + 'consumer_subnet': consumer_subnet, + 'provider_port': provider_port, + 'provider_subnet': provider_subnet, + 'mgmt_ip': mgmt_ip, + 'heat_stack_id': heat_stack_id, + 'provider_ptg': provider_policy_target_group, + 'consumer_ptg': consumer_policy_target_group, + 'consuming_external_policies': + service_details['consuming_external_policies'], + 'consuming_ptgs_details': + service_details['consuming_ptgs_details'] + } + def apply_config(self, network_function_details): service_details = self.get_service_details(network_function_details) service_profile = service_details['service_profile'] @@ -1161,6 +1456,65 @@ def apply_config(self, network_function_details): return stack_id + def apply_heat_config(self, nfp_context): + service_details = self.get_service_details_from_nfp_context( + nfp_context) + + network_function = nfp_context['network_function'] + service_profile = service_details['service_profile'] + service_chain_node = service_details['servicechain_node'] + service_chain_instance = service_details['servicechain_instance'] + provider = service_details['provider_ptg'] + consumer = service_details['consumer_ptg'] + consumer_port = service_details['consumer_port'] + provider_port = service_details['provider_port'] + mgmt_ip = service_details['mgmt_ip'] + + auth_token = nfp_context['resource_owner_context']['admin_token'] + provider_tenant_id = nfp_context['tenant_id'] + heatclient = self._get_heat_client_v1(provider_tenant_id, + assign_admin=True) + if not heatclient: + return None + + stack_template, stack_params = self._create_node_config_data( + auth_token, provider_tenant_id, + service_chain_node, service_chain_instance, + provider, provider_port, consumer, consumer_port, + network_function, mgmt_ip, service_details) + + if not stack_template and not stack_params: + return None + + if not heatclient: + return None + + stack_name = ("stack_" + service_chain_instance['name'] + + service_chain_node['name'] + + service_chain_instance['id'][:8] + + service_chain_node['id'][:8] + '-' + + time.strftime("%Y%m%d%H%M%S")) + # Heat does not accept space in stack name + stack_name = stack_name.replace(" ", "") + + try: + stack = heatclient.create(stack_name, stack_template, stack_params) + except Exception as err: + LOG.error(_LE("Heat stack creation failed for template : " + "%(template)s and stack parameters : %(params)s " + "with Error: %(error)s") % + {'template': stack_template, 'params': stack_params, + 'error': err}) + return None + + stack_id = stack['stack']['id'] + LOG.info(_LI("Created stack with ID %(stack_id)s and " + "name %(stack_name)s for provider PTG %(provider)s"), + {'stack_id': stack_id, 'stack_name': stack_name, + 'provider': provider['id']}) + + return stack_id, heatclient + def delete_config(self, stack_id, tenant_id): auth_token, resource_owner_tenant_id =\ self._get_resource_owner_context() diff --git a/gbpservice/nfp/orchestrator/db/nfp_db.py b/gbpservice/nfp/orchestrator/db/nfp_db.py index 98e956f9aa..21ce258cd0 100644 --- a/gbpservice/nfp/orchestrator/db/nfp_db.py +++ b/gbpservice/nfp/orchestrator/db/nfp_db.py @@ -205,7 +205,7 @@ def _set_mgmt_port_for_nfd(self, session, network_function_device_db, session.add(port_info_db) session.flush() nfd_db.mgmt_port_id = port_info_db['id'] - del network_function_device['mgmt_port_id'] + # del network_function_device['mgmt_port_id'] def _set_plugged_in_port_for_nfd_interface(self, session, nfd_interface_db, interface, is_update=False): @@ -327,7 +327,11 @@ def update_network_function_device(self, session, session, network_function_device_db, updated_network_function_device) + mgmt_port_id = updated_network_function_device.pop('mgmt_port_id', None) + if mgmt_port_id: + updated_network_function_device['mgmt_port_id'] = mgmt_port_id['id'] network_function_device_db.update(updated_network_function_device) + updated_network_function_device['mgmt_port_id'] = mgmt_port_id return self._make_network_function_device_dict( network_function_device_db) diff --git a/gbpservice/nfp/orchestrator/drivers/orchestration_driver.py b/gbpservice/nfp/orchestrator/drivers/orchestration_driver.py index 35045faf33..c014579b6c 100644 --- a/gbpservice/nfp/orchestrator/drivers/orchestration_driver.py +++ b/gbpservice/nfp/orchestrator/drivers/orchestration_driver.py @@ -29,6 +29,8 @@ from gbpservice.nfp.core import log as nfp_logging +from gbpservice.nfp.core import executor as nfp_executor + LOG = nfp_logging.getLogger(__name__) @@ -56,6 +58,7 @@ class OrchestrationDriver(object): Launches the VM with all the management and data ports and a new VM is launched for each Network Service Instance """ + def __init__(self, config, supports_device_sharing=True, supports_hotplug=True, max_interfaces=10): self.service_vendor = 'general' @@ -104,8 +107,8 @@ def _get_admin_tenant_id(self, token=None): if not token: token = self.identity_handler.get_admin_token() admin_tenant_id = self.identity_handler.get_tenant_id( - token, - admin_tenant_name) + token, + admin_tenant_name) return admin_tenant_id except Exception: LOG.error(_LE("Failed to get admin's tenant ID")) @@ -150,22 +153,21 @@ def _decrement_stats_counter(self, metric, by=1): def _is_device_sharing_supported(self): return self.supports_device_sharing - def _create_management_interface(self, device_data, network_handler=None): - token = self._get_token(device_data.get('token')) - if not token: - return None + def _create_management_interface(self, token, admin_tenant_id, + device_data, network_handler): name = nfp_constants.MANAGEMENT_INTERFACE_NAME mgmt_interface = network_handler.create_port( - token, - self._get_admin_tenant_id(token=token), - device_data['management_network_info']['id'], - name=name) + token, + admin_tenant_id, + device_data['management_network_info']['id'], + name=name) return {'id': mgmt_interface['id'], + 'port_id': mgmt_interface['port_id'], 'port_model': (nfp_constants.GBP_PORT if device_data['service_details'][ - 'network_mode'] == + 'network_mode'] == nfp_constants.GBP_MODE else nfp_constants.NEUTRON_PORT), 'port_classification': nfp_constants.MANAGEMENT, @@ -218,33 +220,38 @@ def _create_advance_sharing_interfaces(self, device_data, 'network_mode'] == nfp_constants.GBP_MODE else nfp_constants.NEUTRON_PORT) advance_sharing_network_id = self._get_advance_sharing_network_id( - admin_tenant_id, - network_handler) + admin_tenant_id, + network_handler) for i in range(self.maximum_interfaces): port = network_handler.create_port(token, admin_tenant_id, advance_sharing_network_id) port_infos.append({'id': port['id'], - 'port_model': port_model, - 'port_classification': nfp_constants.ADVANCE_SHARING, - 'port_role': None, - 'plugged_in_pt_id': network_handler.get_port_id(token, - port['id'])}) + 'port_model': port_model, + 'port_classification': nfp_constants.ADVANCE_SHARING, + 'port_role': None, + 'plugged_in_pt_id': network_handler.get_port_id(token, + port['id'])}) return port_infos - def _get_interfaces_for_device_create(self, device_data, - network_handler=None): - mgmt_interface = self._create_management_interface( + def _get_interfaces_for_device_create(self, token, admin_tenant_id, network_handler, device_data): + try: + mgmt_interface = self._create_management_interface( + token, + admin_tenant_id, device_data, - network_handler=network_handler) - return [mgmt_interface] + network_handler) + device_data['interfaces'] = [mgmt_interface] + except Exception as e: + LOG.exception(_LE('Failed to get interfaces for device creation.' + 'Error: %(error)s'), {'error', e}) def _delete_interfaces(self, device_data, interfaces, network_handler=None): for interface in interfaces: self._delete_interface( - device_data, interface, - network_handler=network_handler) + device_data, interface, + network_handler=network_handler) def _verify_vendor_data(self, image_name, metadata): vendor_data = {} @@ -266,9 +273,27 @@ def _get_vendor_data(self, device_data, image_name): return None try: metadata = self.compute_handler_nova.get_image_metadata( - token, - self._get_admin_tenant_id(token=token), - image_name) + token, + self._get_admin_tenant_id(token=token), + image_name) + except Exception as e: + self._increment_stats_counter('image_details_get_failures') + LOG.error(_LE('Failed to get image metadata for image ' + 'name: %(image_name)s. Error: %(error)s'), + {'image_name': image_name, 'error': e}) + return None + vendor_data = self._verify_vendor_data(image_name, metadata) + if not vendor_data: + return None + return vendor_data + + def _get_vendor_data_v1(self, token, + admin_tenant_id, image_name, device_data): + try: + metadata = self.compute_handler_nova.get_image_metadata( + token, + admin_tenant_id, + image_name) except Exception as e: self._increment_stats_counter('image_details_get_failures') LOG.error(_LE('Failed to get image metadata for image ' @@ -298,11 +323,36 @@ def _update_vendor_data(self, device_data, token=None): {'vendor_data': vendor_data}) if vendor_data: self._update_self_with_vendor_data(vendor_data, - nfp_constants.MAXIMUM_INTERFACES) + nfp_constants.MAXIMUM_INTERFACES) self._update_self_with_vendor_data(vendor_data, - nfp_constants.SUPPORTS_SHARING) + nfp_constants.SUPPORTS_SHARING) self._update_self_with_vendor_data(vendor_data, - nfp_constants.SUPPORTS_HOTPLUG) + nfp_constants.SUPPORTS_HOTPLUG) + else: + LOG.info(_LI("No vendor data specified in image, " + "proceeding with default values")) + except Exception: + LOG.error(_LE("Error while getting metadata for image name: %s," + " proceeding with default values") + % (image_name)) + + def _update_vendor_data_v1(self, token, admin_tenant_id, + image_name, device_data): + try: + vendor_data = self._get_vendor_data_v1( + token, admin_tenant_id, image_name, device_data) + LOG.info(_LI("Vendor data, specified in image: %(vendor_data)s"), + {'vendor_data': vendor_data}) + if vendor_data: + self._update_self_with_vendor_data( + vendor_data, + nfp_constants.MAXIMUM_INTERFACES) + self._update_self_with_vendor_data( + vendor_data, + nfp_constants.SUPPORTS_SHARING) + self._update_self_with_vendor_data( + vendor_data, + nfp_constants.SUPPORTS_HOTPLUG) else: LOG.info(_LI("No vendor data specified in image, " "proceeding with default values")) @@ -321,11 +371,12 @@ def _get_image_name(self, device_data): % (device_data['service_details']['service_vendor'])) image_name = device_data['service_details']['service_vendor'] image_name = '%s' % image_name.lower() + device_data['service_details']['image_name'] = image_name return image_name def _get_service_type(self, token, service_profile_id, network_handler): - service_profile = network_handler.get_service_profile(token, - service_profile_id) + service_profile = network_handler.get_service_profile( + token, service_profile_id) return service_profile['service_type'] def _get_device_service_types_map(self, token, devices, network_handler): @@ -333,9 +384,9 @@ def _get_device_service_types_map(self, token, devices, network_handler): for device in devices: for network_function in device['network_functions']: service_type = self._get_service_type( - token, - network_function['service_profile_id'], - network_handler) + token, + network_function['service_profile_id'], + network_handler) device_service_types_map[device['id']].add(service_type) return device_service_types_map @@ -369,19 +420,16 @@ def get_network_function_device_sharing_info(self, device_data): ): raise exceptions.IncompleteData() - image_name = self._get_image_name(device_data) - if image_name: - self._update_vendor_data(device_data, - device_data.get('token')) if not self._is_device_sharing_supported(): return None + return { - 'filters': { - 'tenant_id': [device_data['tenant_id']], - 'service_vendor': [device_data['service_details'][ - 'service_vendor']], - 'status': [nfp_constants.ACTIVE] - } + 'filters': { + 'tenant_id': [device_data['tenant_id']], + 'service_vendor': [device_data['service_details'][ + 'service_vendor']], + 'status': [nfp_constants.ACTIVE] + } } @_set_network_handler @@ -427,7 +475,7 @@ def select_network_function_device(self, devices, device_data, image_name = self._get_image_name(device_data) if image_name: self._update_vendor_data(device_data, - device_data.get('token')) + device_data.get('token')) if not self._is_device_sharing_supported(): return None @@ -437,8 +485,8 @@ def select_network_function_device(self, devices, device_data, hotplug_ports_count = 2 device_service_types_map = ( - self._get_device_service_types_map(token, devices, - network_handler)) + self._get_device_service_types_map(token, devices, + network_handler)) service_type = device_data['service_details']['service_type'] for device in devices: if ( @@ -446,7 +494,7 @@ def select_network_function_device(self, devices, device_data, self.maximum_interfaces ): if (service_type.lower() == nfp_constants.VPN.lower() and - service_type in device_service_types_map[device['id']]): + service_type in device_service_types_map[device['id']]): # Restrict multiple VPN services to share same device # If nfd request service type is VPN and current filtered # device already has VPN service instantiated, ignore this @@ -455,6 +503,55 @@ def select_network_function_device(self, devices, device_data, return device return None + def get_image_id(self, nova, token, admin_tenant_id, image_name): + try: + image_id = nova.get_image_id(token, admin_tenant_id, image_name) + return image_id + except Exception as e: + LOG.error(_LE('Failed to get image id for device creation.' + ' image name: %(image_name)s. Error: %(error)s'), + {'image_name': image_name, 'error': e}) + + def create_instance(self, nova, token, admin_tenant_id, + image_id, flavor, interfaces_to_attach, + instance_name): + try: + instance_id = nova.create_instance( + token, admin_tenant_id, + image_id, flavor, interfaces_to_attach, instance_name) + return instance_id + except Exception as e: + LOG.error(_LE('Failed to create %(device_type)s instance.' + 'Error: %(error)s'), + {'device_type': ( + device_data['service_details']['device_type']), + 'error': e}) + + def get_neutron_port_details(self, network_handler, token, port_id): + try: + (mgmt_ip_address, + mgmt_mac, mgmt_cidr, gateway_ip, + mgmt_port, mgmt_subnet) = \ + network_handler.get_neutron_port_details(token, port_id) + + result = {'neutron_port': mgmt_port['port'], + 'neutron_subnet': mgmt_subnet['subnet'], + 'ip_address': mgmt_ip_address, + 'mac': mgmt_mac, + 'cidr': mgmt_cidr, + 'gateway_ip': gateway_ip} + return result + except Exception as e: + import sys + import traceback + exc_type, exc_value, exc_traceback = sys.exc_info() + print traceback.format_exception(exc_type, exc_value, + exc_traceback) + LOG.error(traceback.format_exception(exc_type, exc_value, + exc_traceback)) + LOG.error(_LE('Failed to get management port details. ' + 'Error: %(error)s'), {'error': e}) + @_set_network_handler def create_network_function_device(self, device_data, network_handler=None): @@ -503,46 +600,40 @@ def create_network_function_device(self, device_data, raise exceptions.ComputePolicyNotSupported( compute_policy=device_data['service_details']['device_type']) + token = device_data['token'] + admin_tenant_id = device_data['admin_tenant_id'] image_name = self._get_image_name(device_data) - if image_name: - self._update_vendor_data(device_data, - device_data.get('token')) - try: - interfaces = self._get_interfaces_for_device_create( - device_data, - network_handler=network_handler - ) - except Exception as e: - LOG.exception(_LE('Failed to get interfaces for device creation.' - 'Error: %(error)s'), {'error', e}) + + executor = nfp_executor.TaskExecutor(jobs=3) + + image_id_result = {} + + executor.add_job('UPDATE_VENDOR_DATA', + self._update_vendor_data_v1, + token, admin_tenant_id, image_name, device_data) + executor.add_job('GET_INTERFACES_FOR_DEVICE_CREATE', + self._get_interfaces_for_device_create, + token, admin_tenant_id, network_handler, device_data) + executor.add_job('GET_IMAGE_ID', + self.get_image_id, + self.compute_handler_nova, token, admin_tenant_id, + image_name, result_store=image_id_result) + + completed = executor.fire() + + interfaces = device_data.pop('interfaces', None) + if not interfaces: + LOG.exception(_LE('Failed to get interfaces for device creation.')) return None else: + management_interface = interfaces[0] self._increment_stats_counter('management_interfaces', by=len(interfaces)) - token = self._get_token(device_data.get('token')) - if not token: - return None - - if device_data['service_details'].get('image_name'): - image_name = device_data['service_details']['image_name'] - else: - LOG.info(_LI("No image name provided in service profile's " - "service flavor field, image will be selected " - "based on service vendor's name : %s") - % (device_data['service_details']['service_vendor'])) - image_name = device_data['service_details']['service_vendor'] - image_name = '%s' % image_name.lower() - try: - image_id = self.compute_handler_nova.get_image_id( - token, - self._get_admin_tenant_id(token=token), - image_name) - except Exception as e: + image_id = image_id_result.get('result', None) + if not image_id: self._increment_stats_counter('image_details_get_failures') - LOG.error(_LE('Failed to get image id for device creation.' - ' image name: %(image_name)s. Error: %(error)s'), - {'image_name': image_name, 'error': e}) + LOG.error(_LE('Failed to get image id for device creation.')) self._delete_interfaces(device_data, interfaces, network_handler=network_handler) self._decrement_stats_counter('management_interfaces', @@ -561,8 +652,7 @@ def create_network_function_device(self, device_data, advance_sharing_interfaces = [] try: for interface in interfaces: - port_id = network_handler.get_port_id(token, interface['id']) - interfaces_to_attach.append({'port': port_id}) + interfaces_to_attach.append({'port': interface['port_id']}) if not self.supports_hotplug: if self.setup_mode.get(nfp_constants.NEUTRON_MODE): @@ -571,25 +661,25 @@ def create_network_function_device(self, device_data, if (port['port_classification'] == nfp_constants.PROVIDER): if (device_data['service_details'][ - 'service_type'].lower() + 'service_type'].lower() in [nfp_constants.FIREWALL.lower(), nfp_constants.VPN.lower()]): network_handler.set_promiscuos_mode( - token, port['id']) + token, port['id']) port_id = network_handler.get_port_id( - token, port['id']) + token, port['id']) interfaces_to_attach.append({'port': port_id}) for port in device_data['ports']: if (port['port_classification'] == nfp_constants.CONSUMER): if (device_data['service_details'][ - 'service_type'].lower() + 'service_type'].lower() in [nfp_constants.FIREWALL.lower(), nfp_constants.VPN.lower()]): network_handler.set_promiscuos_mode( - token, port['id']) + token, port['id']) port_id = network_handler.get_port_id( - token, port['id']) + token, port['id']) interfaces_to_attach.append({'port': port_id}) elif self.setup_mode.get(nfp_constants.APIC_MODE): advance_sharing_interfaces = ( @@ -606,7 +696,7 @@ def create_network_function_device(self, device_data, except Exception as e: self._increment_stats_counter('port_details_get_failures') LOG.error(_LE('Failed to fetch list of interfaces to attach' - ' for device creation %(error)s'), {'error': e}) + ' for device creation %(error)s'), {'error': e}) self._delete_interfaces(device_data, interfaces, network_handler=network_handler) self._decrement_stats_counter('management_interfaces', @@ -614,18 +704,28 @@ def create_network_function_device(self, device_data, return None instance_name = device_data['name'] - try: - instance_id = self.compute_handler_nova.create_instance( - token, self._get_admin_tenant_id(token=token), - image_id, flavor, - interfaces_to_attach, instance_name) - except Exception as e: + instance_id_result = {} + port_details_result = {} + + executor.add_job('CREATE_INSTANCE', + self.create_instance, + self.compute_handler_nova, + token, admin_tenant_id, image_id, flavor, + interfaces_to_attach, instance_name, + result_store=instance_id_result) + + executor.add_job('GET_NEUTRON_PORT_DETAILS', + self.get_neutron_port_details, + network_handler, token, + management_interface['port_id'], + result_store=port_details_result) + + completed = executor.fire() + + instance_id = instance_id_result.get('result', None) + if not instance_id: self._increment_stats_counter('instance_launch_failures') - LOG.error(_LE('Failed to create %(device_type)s instance.' - 'Error: %(error)s'), - {'device_type': ( - device_data['service_details']['device_type']), - 'error': e}) + LOG.error(_LE('Failed to create %(device_type)s instance.')) self._delete_interfaces(device_data, interfaces, network_handler=network_handler) self._decrement_stats_counter('management_interfaces', @@ -635,24 +735,16 @@ def create_network_function_device(self, device_data, self._increment_stats_counter('instances') mgmt_ip_address = None - try: - for interface in interfaces: - if interface['port_classification'] == ( - nfp_constants.MANAGEMENT): - (mgmt_ip_address, - dummy, dummy, - dummy) = network_handler.get_port_details( - token, interface['id']) - except Exception as e: + mgmt_neutron_port_info = port_details_result.get('result', None) + + if not mgmt_neutron_port_info: self._increment_stats_counter('port_details_get_failures') - LOG.error(_LE('Failed to get management port details. ' - 'Error: %(error)s'), {'error': e}) + LOG.error(_LE('Failed to get management port details. ')) try: self.compute_handler_nova.delete_instance( - token, - self._get_admin_tenant_id( - token=token), - instance_id) + token, + admin_tenant_id, + instance_id) except Exception as e: self._increment_stats_counter('instance_delete_failures') LOG.error(_LE('Failed to delete %(device_type)s instance.' @@ -667,10 +759,12 @@ def create_network_function_device(self, device_data, by=len(interfaces)) return None + mgmt_ip_address = mgmt_neutron_port_info['ip_address'] return {'id': instance_id, 'name': instance_name, 'mgmt_ip_address': mgmt_ip_address, 'mgmt_port_id': interfaces[0], + 'mgmt_neutron_port_info': mgmt_neutron_port_info, 'max_interfaces': self.maximum_interfaces, 'interfaces_in_use': len(interfaces_to_attach), 'advance_sharing_interfaces': advance_sharing_interfaces, @@ -720,7 +814,7 @@ def delete_network_function_device(self, device_data, image_name = self._get_image_name(device_data) if image_name: self._update_vendor_data(device_data, - device_data.get('token')) + device_data.get('token')) token = self._get_token(device_data.get('token')) if not token: return None @@ -732,10 +826,10 @@ def delete_network_function_device(self, device_data, # once the device instance deletion is completed try: self.compute_handler_nova.delete_instance( - token, - self._get_admin_tenant_id( - token=token), - device_data['id']) + token, + self._get_admin_tenant_id( + token=token), + device_data['id']) except Exception: self._increment_stats_counter('instance_delete_failures') LOG.error(_LE('Failed to delete %s instance') @@ -749,7 +843,7 @@ def delete_network_function_device(self, device_data, interfaces.extend(device_data['advance_sharing_interfaces']) self._delete_interfaces(device_data, interfaces, - network_handler=network_handler) + network_handler=network_handler) except Exception as e: LOG.error(_LE('Failed to delete the management data port(s). ' 'Error: %(error)s'), {'error': e}) @@ -790,15 +884,11 @@ def get_network_function_device_status(self, device_data, raise exceptions.ComputePolicyNotSupported( compute_policy=device_data['service_details']['device_type']) - token = self._get_token(device_data.get('token')) - if not token: - return None - try: device = self.compute_handler_nova.get_instance( - token, - self._get_admin_tenant_id(token=token), - device_data['id']) + device_data['token'], + device_data['tenant_id'], + device_data['id']) except Exception: if ignore_failure: return None @@ -853,13 +943,8 @@ def plug_network_function_device_interfaces(self, device_data, raise exceptions.ComputePolicyNotSupported( compute_policy=device_data['service_details']['device_type']) - token = self._get_token(device_data.get('token')) - if not token: - return None - - image_name = self._get_image_name(device_data) - if image_name: - self._update_vendor_data(device_data) + token = device_data['token'] + tenant_id = device_data['tenant_id'] update_ifaces = [] try: @@ -868,8 +953,8 @@ def plug_network_function_device_interfaces(self, device_data, if self.setup_mode.get(nfp_constants.APIC_MODE): required_ports = len(device_data['ports']) unused_ifaces = self._get_unused_interfaces( - device_data['advance_sharing_interfaces'], - required_ports) + device_data['advance_sharing_interfaces'], + required_ports) data_port_ids = [] for port in device_data['ports']: @@ -885,51 +970,56 @@ def plug_network_function_device_interfaces(self, device_data, for data_port_id, iface in zip(data_port_ids, unused_ifaces): self._update_attached_port_with_data_port(token, - iface, - data_port_id, - network_handler, - stitch=True) + iface, + data_port_id, + network_handler, + stitch=True) iface['mapped_real_port_id'] = data_port_id update_ifaces = unused_ifaces elif self.setup_mode.get(nfp_constants.NEUTRON_MODE): pass else: + executor = nfp_executor.TaskExecutor(jobs=10) + for port in device_data['ports']: if port['port_classification'] == nfp_constants.PROVIDER: - if ( - device_data['service_details'][ - 'service_type'].lower() - in [nfp_constants.FIREWALL.lower(), - nfp_constants.VPN.lower()] - ): - network_handler.set_promiscuos_mode(token, - port['id']) - port_id = network_handler.get_port_id(token, - port['id']) - self.compute_handler_nova.attach_interface( - token, - self._get_admin_tenant_id(token=token), - device_data['id'], - port_id) + service_type = device_data[ + 'service_details']['service_type'].lower() + if service_type.lower() in \ + [nfp_constants.FIREWALL.lower(), + nfp_constants.VPN.lower()]: + executor.add_job( + 'SET_PROMISCUOS_MODE', + network_handler.set_promiscuos_mode_v1, + token, port['id']) + executor.add_job( + 'ATTACH_INTERFACE', + self.compute_handler_nova.attach_interface, + token, tenant_id, device_data['id'], + port['id']) break + # Configurator expects interface to attach in order + # executor.fire() + for port in device_data['ports']: if port['port_classification'] == nfp_constants.CONSUMER: - if ( - device_data['service_details'][ - 'service_type'].lower() - in [nfp_constants.FIREWALL.lower(), - nfp_constants.VPN.lower()] - ): - network_handler.set_promiscuos_mode(token, - port['id']) - port_id = network_handler.get_port_id(token, - port['id']) - self.compute_handler_nova.attach_interface( - token, - self._get_admin_tenant_id(token=token), - device_data['id'], - port_id) + service_type = device_data[ + 'service_details']['service_type'].lower() + if service_type.lower() in \ + [nfp_constants.FIREWALL.lower(), + nfp_constants.VPN.lower()]: + executor.add_job( + 'SET_PROMISCUOS_MODE', + network_handler.set_promiscuos_mode_v1, + token, port['id']) + executor.add_job( + 'ATTACH_INTERFACE', + self.compute_handler_nova.attach_interface, + token, tenant_id, device_data['id'], + port['id']) break + executor.fire() + except Exception as e: self._increment_stats_counter('interface_plug_failures') LOG.error(_LE('Failed to plug interface(s) to the device.' @@ -948,8 +1038,8 @@ def _update_attached_port_with_data_port(self, token, # to specify fabric/controller to stitch these interfaces port_id = network_handler.get_port_id(token, data_port_id) description = "%s%s" % ( - PROXY_PORT_PREFIX, - port_id) + PROXY_PORT_PREFIX, + port_id) # TODO(ashu): update attached port mac with data port mac. else: # configure attached interface pt with empty string, @@ -961,13 +1051,13 @@ def _update_attached_port_with_data_port(self, token, def _get_unused_interfaces(self, advance_sharing_ifaces, required_ports): # sort the interfaces based on interface position advance_sharing_ifaces.sort(key=operator.itemgetter( - 'interface_position')) + 'interface_position')) unused_interfaces = [] for iface in advance_sharing_ifaces: if not iface['mapped_real_port_id']: current_position = iface['interface_position'] unused_interfaces = advance_sharing_ifaces[ - current_position:required_ports] + current_position:required_ports] break return unused_interfaces @@ -1054,7 +1144,7 @@ def unplug_network_function_device_interfaces(self, device_data, image_name = self._get_image_name(device_data) if image_name: self._update_vendor_data(device_data, - device_data.get('token')) + device_data.get('token')) token = self._get_token(device_data.get('token')) if not token: @@ -1072,19 +1162,19 @@ def unplug_network_function_device_interfaces(self, device_data, break for port in device_data['ports']: if (port['port_classification'] == - nfp_constants.CONSUMER): + nfp_constants.CONSUMER): data_port_ids.append(port['id']) used_ifaces = self._get_used_interfaces( - device_data['advance_sharing_interfaces'], - data_port_ids) + device_data['advance_sharing_interfaces'], + data_port_ids) for data_port_id, iface in zip(data_port_ids, used_ifaces): self._update_attached_port_with_data_port(token, - iface, - data_port_id, - network_handler, - stitch=False) + iface, + data_port_id, + network_handler, + stitch=False) iface['mapped_real_port_id'] = '' update_ifaces = used_ifaces elif self.setup_mode.get(nfp_constants.NEUTRON_MODE): @@ -1093,10 +1183,10 @@ def unplug_network_function_device_interfaces(self, device_data, for port in device_data['ports']: port_id = network_handler.get_port_id(token, port['id']) self.compute_handler_nova.detach_interface( - token, - self._get_admin_tenant_id(token=token), - device_data['id'], - port_id) + token, + self._get_admin_tenant_id(token=token), + device_data['id'], + port_id) except Exception as e: self._increment_stats_counter('interface_unplug_failures') @@ -1214,7 +1304,7 @@ def get_network_function_device_config_info(self, device_data, if port['port_classification'] == nfp_constants.PROVIDER: try: (provider_ip, provider_mac, provider_cidr, dummy) = ( - network_handler.get_port_details(token, port['id']) + network_handler.get_port_details(token, port['id']) ) except Exception: self._increment_stats_counter('port_details_get_failures') @@ -1225,7 +1315,7 @@ def get_network_function_device_config_info(self, device_data, try: (consumer_ip, consumer_mac, consumer_cidr, consumer_gateway_ip) = ( - network_handler.get_port_details(token, port['id']) + network_handler.get_port_details(token, port['id']) ) except Exception: self._increment_stats_counter('port_details_get_failures') @@ -1264,3 +1354,76 @@ def get_network_function_device_config_info(self, device_data, } ] } + + @_set_network_handler + def get_create_network_function_device_config_info(self, device_data, + network_handler=None): + """ Get the configuration information for NFD + + :param device_data: NFD + :type device_data: dict + + :returns: None -- On Failure + :returns: dict -- It has the following scheme + { + 'config': [ + { + 'resource': 'interfaces', + 'resource_data': { + ... + } + }, + { + 'resource': 'routes', + 'resource_data': { + ... + } + } + ] + } + + :raises: exceptions.IncompleteData + """ + + mgmt_ip = device_data.get('mgmt_ip', None) + provider_ip = device_data.get('provider_ip', None) + provider_mac = device_data.get('provider_mac', None) + provider_cidr = device_data.get('provider_cidr', None) + provider_gateway_ip = device_data.get('provider_gateway_ip', None) + consumer_ip = device_data.get('consumer_ip', None) + consumer_mac = device_data.get('consumer_mac', None) + consumer_cidr = device_data.get('consumer_cidr', None) + consumer_gateway_ip = device_data.get('consumer_gateway_ip', None) + + return { + 'config': [ + { + 'resource': nfp_constants.INTERFACE_RESOURCE, + 'resource_data': { + 'mgmt_ip': mgmt_ip, + 'provider_ip': provider_ip, + 'provider_cidr': provider_cidr, + 'provider_interface_index': 2, + 'stitching_ip': consumer_ip, + 'stitching_cidr': consumer_cidr, + 'stitching_interface_index': 3, + 'provider_mac': provider_mac, + 'stitching_mac': consumer_mac, + }, + + }, + { + 'resource': nfp_constants.ROUTES_RESOURCE, + 'resource_data': { + 'mgmt_ip': mgmt_ip, + 'source_cidrs': ([provider_cidr, consumer_cidr] + if consumer_cidr + else [provider_cidr]), + 'destination_cidr': consumer_cidr, + 'provider_mac': provider_mac, + 'gateway_ip': consumer_gateway_ip, + 'provider_interface_index': 2 + } + } + ] + } diff --git a/gbpservice/nfp/orchestrator/modules/device_orchestrator.py b/gbpservice/nfp/orchestrator/modules/device_orchestrator.py index ac80a09df5..9a5682d933 100644 --- a/gbpservice/nfp/orchestrator/modules/device_orchestrator.py +++ b/gbpservice/nfp/orchestrator/modules/device_orchestrator.py @@ -17,8 +17,8 @@ from gbpservice.nfp.common import constants as nfp_constants from gbpservice.nfp.common import topics as nsf_topics from gbpservice.nfp.core.event import Event -from gbpservice.nfp.core.poll import poll_event_desc -from gbpservice.nfp.core.poll import PollEventDesc +from gbpservice.nfp.core import event as nfp_event +from gbpservice.nfp.core import module as nfp_api from gbpservice.nfp.core.rpc import RpcAgent from gbpservice.nfp.lib import transport from gbpservice.nfp.orchestrator.db import api as nfp_db_api @@ -51,11 +51,15 @@ def rpc_init(controller, config): def events_init(controller, config, device_orchestrator): events = ['CREATE_NETWORK_FUNCTION_DEVICE', 'DEVICE_SPAWNING', - 'DEVICE_HEALTHY', 'CONFIGURE_DEVICE', + 'DEVICE_HEALTHY', 'HEALTH_MONITOR_COMPLETE', + 'DEVICE_UP', + 'CONFIGURE_DEVICE', 'CREATE_DEVICE_CONFIGURATION', + 'CONFIGURATION_COMPLETE', 'DEVICE_CONFIGURED', "DELETE_CONFIGURATION", 'DELETE_NETWORK_FUNCTION_DEVICE', 'DELETE_CONFIGURATION_COMPLETED', 'DEVICE_BEING_DELETED', - 'DEVICE_NOT_REACHABLE', 'DEVICE_CONFIGURATION_FAILED'] + 'DEVICE_NOT_REACHABLE', + 'DEVICE_CONFIGURATION_FAILED', 'PERFORM_HEALTH_CHECK','PLUG_INTERFACES'] events_to_register = [] for event in events: events_to_register.append( @@ -92,16 +96,16 @@ def __init__(self, conf, controller): self.conf = conf self._controller = controller self.rpc_event_mapping = { - 'healthmonitor': ['DEVICE_HEALTHY', - 'DEVICE_NOT_REACHABLE', - 'DEVICE_NOT_REACHABLE'], - 'interfaces': ['DEVICE_CONFIGURED', - 'DELETE_CONFIGURATION_COMPLETED', - 'DEVICE_CONFIGURATION_FAILED'], - 'routes': ['DEVICE_CONFIGURED', - 'DELETE_CONFIGURATION_COMPLETED', - 'DEVICE_CONFIGURATION_FAILED'], - } + 'healthmonitor': ['HEALTH_MONITOR_COMPLETE', + 'DEVICE_NOT_REACHABLE', + 'DEVICE_NOT_REACHABLE'], + 'interfaces': ['DEVICE_CONFIGURED', + 'DELETE_CONFIGURATION_COMPLETED', + 'DEVICE_CONFIGURATION_FAILED'], + 'routes': ['DEVICE_CONFIGURED', + 'DELETE_CONFIGURATION_COMPLETED', + 'DEVICE_CONFIGURATION_FAILED'], + } def _log_event_created(self, event_id, event_data): LOG.info(_LI("Device Orchestrator, RPC Handler, Created event " @@ -113,9 +117,9 @@ def _create_event(self, event_id, event_data=None, if is_poll_event: ev = self._controller.new_event( id=event_id, data=event_data, - serialize=original_event.serialize, + serialize=original_event.sequence, binding_key=original_event.binding_key, - key=original_event.desc.uid) + key=original_event.desc.uuid) LOG.debug("poll event started for %s" % (ev.id)) self._controller.poll_event(ev, max_times=10) else: @@ -132,6 +136,7 @@ def network_function_notification(self, context, notification_data): request_info = info.get('context') operation = request_info.get('operation') logging_context = request_info.get('logging_context') + nfp_context = request_info.get('nfp_context') nfp_logging.store_logging_context(**logging_context) for response in responses: @@ -162,12 +167,13 @@ def network_function_notification(self, context, notification_data): request_info['network_function_device_id'] = nfd_id event_data = request_info event_data['id'] = request_info['network_function_device_id'] + self._create_event(event_id=event_id, - event_data=event_data) + event_data=event_data) nfp_logging.clear_logging_context() -class DeviceOrchestrator(PollEventDesc): +class DeviceOrchestrator(nfp_api.NfpEventHandler): """device Orchestrator For Network Services This class handles the orchestration of Network Function Device lifecycle. @@ -210,23 +216,24 @@ def __init__(self, controller, config): self.config) self.status_map = { - 'INIT': 'Created Network Service Device with status INIT.', - 'PENDING_CREATE': '', - 'DEVICE_SPAWNING': ('Creating NSD, launched the new device, ' + - 'polling on its status'), - 'DEVICE_UP': 'Device is UP/ACTIVE', - 'HEALTH_CHECK_PENDING': ('Device health check is going on ' + - ' through configurator'), - 'HEALTH_CHECK_COMPLETED': 'Health check succesfull for device', - 'INTERFACES_PLUGGED': 'Interfaces Plugging successfull', - 'PENDING_CONFIGURATION_CREATE': ('Started configuring device ' - + 'for routes, license, etc'), - 'DEVICE_READY': 'Device is ready to use', - 'ACTIVE': 'Device is Active.', - 'DEVICE_NOT_UP': 'Device not became UP/ACTIVE', + 'INIT': 'Created Network Service Device with status INIT.', + 'PENDING_CREATE': '', + 'DEVICE_SPAWNING': ('Creating NSD, launched the new device, ' + + 'polling on its status'), + 'DEVICE_UP': 'Device is UP/ACTIVE', + 'PERFORM_HEALTH_CHECK': 'perform health check of service vm', + 'HEALTH_CHECK_PENDING': ('Device health check is going on ' + + ' through configurator'), + 'HEALTH_CHECK_COMPLETED': 'Health check succesfull for device', + 'INTERFACES_PLUGGED': 'Interfaces Plugging successfull', + 'PENDING_CONFIGURATION_CREATE': ('Started configuring device ' + + 'for routes, license, etc'), + 'DEVICE_READY': 'Device is ready to use', + 'ACTIVE': 'Device is Active.', + 'DEVICE_NOT_UP': 'Device not became UP/ACTIVE', } self.orchestration_driver = orchestration_driver.OrchestrationDriver( - self.config) + self.config) @property def db_session(self): @@ -236,9 +243,14 @@ def event_method_mapping(self, event_id): event_handler_mapping = { "CREATE_NETWORK_FUNCTION_DEVICE": ( self.create_network_function_device), - "DEVICE_UP": self.perform_health_check, + "PERFORM_HEALTH_CHECK": self.perform_health_check, + "DEVICE_UP": self.device_up, + "PLUG_INTERFACES": self.plug_interfaces_v1, "DEVICE_HEALTHY": self.plug_interfaces, - "CONFIGURE_DEVICE": self.create_device_configuration, + "HEALTH_MONITOR_COMPLETE": self.health_monitor_complete, + "CONFIGURE_DEVICE": self.configure_device, + "CREATE_DEVICE_CONFIGURATION": self.create_device_configuration, + "CONFIGURATION_COMPLETE": self.configuration_complete, "DEVICE_CONFIGURED": self.device_configuration_complete, "DELETE_NETWORK_FUNCTION_DEVICE": ( @@ -265,10 +277,10 @@ def event_method_mapping(self, event_id): def handle_event(self, event): try: nf_id = (event.data['network_function_id'] - if 'network_function_id' in event.data else None) + if 'network_function_id' in event.data else None) LOG.info(_LI("NDO: received event %(id)s for network function : " "%(nf_id)s"), - {'id': event.id, 'nf_id': nf_id}) + {'id': event.id, 'nf_id': nf_id}) event_handler = self.event_method_mapping(event.id) event_handler(event) except Exception as e: @@ -292,9 +304,9 @@ def _create_event(self, event_id, event_data=None, if is_poll_event: ev = self._controller.new_event( id=event_id, data=event_data, - serialize=original_event.serialize, + serialize=original_event.sequence, binding_key=original_event.binding_key, - key=original_event.desc.uid) + key=original_event.desc.uuid) LOG.debug("poll event started for %s" % (ev.id)) self._controller.poll_event(ev, max_times=20) else: @@ -373,20 +385,20 @@ def _create_advance_sharing_interfaces(self, device, interfaces_infos): interface.get('port_role')) nfd_interfaces.append( - self.nsf_db.create_network_function_device_interface( - self.db_session, interface) - ) + self.nsf_db.create_network_function_device_interface( + self.db_session, interface) + ) LOG.debug("Created following entries in port_infos table : %s, " - " network function device interfaces table: %s." % - (port_infos, nfd_interfaces)) + " network function device interfaces table: %s." % + (port_infos, nfd_interfaces)) def _get_advance_sharing_interfaces(self, device_id): filters = {'network_function_device_id': [device_id]} network_function_device_interfaces = ( - self.nsf_db.get_network_function_device_interfaces( - self.db_session, - filters=filters) - ) + self.nsf_db.get_network_function_device_interfaces( + self.db_session, + filters=filters) + ) return network_function_device_interfaces def _update_advance_sharing_interfaces(self, device, nfd_ifaces): @@ -395,21 +407,21 @@ def _update_advance_sharing_interfaces(self, device, nfd_ifaces): if port['id'] == nfd_iface['mapped_real_port_id']: nfd_iface['mapped_real_port_id'] = port['id'] nfd_iface['plugged_in_port_id'] = ( - self.nsf_db.get_port_info( - self.db_session, - nfd_iface['plugged_in_port_id'])) + self.nsf_db.get_port_info( + self.db_session, + nfd_iface['plugged_in_port_id'])) self.nsf_db.update_network_function_device_interface( - self.db_session, - nfd_iface['id'], - nfd_iface) + self.db_session, + nfd_iface['id'], + nfd_iface) break def _delete_advance_sharing_interfaces(self, nfd_ifaces): for nfd_iface in nfd_ifaces: port_id = nfd_iface['id'] self.nsf_db.delete_network_function_device_interface( - self.db_session, - port_id) + self.db_session, + port_id) def _create_network_function_device_db(self, device_info, state): advance_sharing_interfaces = [] @@ -421,17 +433,13 @@ def _create_network_function_device_db(self, device_info, state): device_info['reference_count'] = 0 if device_info.get('advance_sharing_interfaces'): advance_sharing_interfaces = ( - device_info.pop('advance_sharing_interfaces')) + device_info.pop('advance_sharing_interfaces')) device_info['interfaces_in_use'] = 0 device = self.nsf_db.create_network_function_device(self.db_session, device_info) - mgmt_port_id = device.pop('mgmt_port_id') - mgmt_port_id = self._get_port(mgmt_port_id) - device['mgmt_port_id'] = mgmt_port_id - if advance_sharing_interfaces: self._create_advance_sharing_interfaces(device, - advance_sharing_interfaces) + advance_sharing_interfaces) return device def _update_network_function_device_db(self, device, state, @@ -442,10 +450,10 @@ def _update_network_function_device_db(self, device, state, def _delete_network_function_device_db(self, device_id, device): advance_sharing_interfaces = device.get( - 'advance_sharing_interfaces', []) + 'advance_sharing_interfaces', []) if advance_sharing_interfaces: self._delete_advance_sharing_interfaces( - advance_sharing_interfaces) + advance_sharing_interfaces) self.nsf_db.delete_network_function_device(self.db_session, device_id) def _get_network_function_info(self, device_id): @@ -454,22 +462,22 @@ def _get_network_function_info(self, device_id): self.nsf_db.get_network_function_instances(self.db_session, nfi_filters)) network_function_ids = [nf['network_function_id'] - for nf in network_function_instances] + for nf in network_function_instances] network_functions = ( self.nsf_db.get_network_functions(self.db_session, - {'id': network_function_ids})) + {'id': network_function_ids})) return network_functions def _get_network_function_devices(self, filters=None): network_function_devices = self.nsf_db.get_network_function_devices( - self.db_session, filters) + self.db_session, filters) for device in network_function_devices: mgmt_port_id = device.pop('mgmt_port_id') mgmt_port_id = self._get_port(mgmt_port_id) device['mgmt_port_id'] = mgmt_port_id network_functions = ( - self._get_network_function_info(device['id'])) + self._get_network_function_info(device['id'])) device['network_functions'] = network_functions return network_function_devices @@ -498,19 +506,20 @@ def _get_device_to_reuse(self, device_data, dev_sharing_info): devices = self._get_network_function_devices(device_filters) device = orchestration_driver.select_network_function_device(devices, - device_data) + device_data) return device def _get_device_data(self, nfd_request): + device_data = {} network_function = nfd_request.get('network_function') network_function_instance = nfd_request['network_function_instance'] service_details = nfd_request['service_details'] device_data['name'] = network_function_instance['name'] device_data['share_existing_device'] = ( - nfd_request.get('share_existing_device')) + nfd_request.get('share_existing_device')) device_data['management_network_info'] = ( - nfd_request.get('management_network_info')) + nfd_request.get('management_network_info')) if network_function: device_data['network_function_id'] = network_function['id'] @@ -518,7 +527,7 @@ def _get_device_data(self, nfd_request): network_function['service_chain_id']) device_data['network_function_instance_id'] = ( - network_function_instance['id']) + network_function_instance['id']) device_data['tenant_id'] = network_function_instance['tenant_id'] nsi_port_info = [] @@ -536,6 +545,7 @@ def _get_device_data(self, nfd_request): device_data['service_details']['network_mode'] = ( nfp_constants.NEUTRON_MODE) device_data['service_vendor'] = service_details['service_vendor'] + return device_data def _get_nsf_db_resource(self, resource_name, resource_id): @@ -546,6 +556,56 @@ def _update_device_data(self, device, device_data): device.update(device_data) return device + def _prepare_device_data_from_nfp_context(self, nfp_context): + device_data = {} + + network_function = nfp_context['network_function'] + network_function_instance = nfp_context['network_function_instance'] + service_details = nfp_context['service_details'] + + device_data['token'] = nfp_context[ + 'resource_owner_context']['admin_token'] + device_data['admin_tenant_id'] = nfp_context[ + 'resource_owner_context']['admin_tenant_id'] + device_data['name'] = network_function_instance['name'] + device_data['share_existing_device'] = nfp_context[ + 'share_existing_device'] + + management_network_info = { + 'id': nfp_context['management_ptg_id'], + 'port_model': nfp_constants.GBP_NETWORK + } + + consumer = nfp_context['consumer'] + provider = nfp_context['provider'] + ports = [] + + if consumer['port']: + ports.append({ + 'id': consumer['port']['id'], + 'port_classification': consumer['port_classification'], + 'port_model': consumer['port_model']}) + + if provider['port']: + ports.append({ + 'id': provider['port']['id'], + 'port_classification': provider['port_classification'], + 'port_model': provider['port_model']}) + + device_data['management_network_info'] = management_network_info + + device_data['network_function_id'] = network_function['id'] + device_data['service_chain_id'] = network_function['service_chain_id'] + device_data[ + 'network_function_instance_id'] = network_function_instance['id'] + device_data['tenant_id'] = network_function_instance['tenant_id'] + device_data['ports'] = ports + device_data['service_details'] = service_details + device_data['service_details']['network_mode'] = nfp_constants.GBP_MODE + device_data['service_vendor'] = service_details['service_vendor'] + + return device_data + # Create path def create_network_function_device(self, event): """ Returns device instance for a new service @@ -553,19 +613,27 @@ def create_network_function_device(self, event): This method either returns existing device which could be reused for a new service or it creates new device instance """ - nfd_request = event.data + device = None + nfd_request = event.data + nfp_context = event.data + + service_details = nfp_context['service_details'] + LOG.info(_LI("Device Orchestrator received create network service " "device request with data %(data)s"), {'data': nfd_request}) - device_data = self._get_device_data(nfd_request) orchestration_driver = self._get_orchestration_driver( - device_data['service_details']['service_vendor']) + service_details['service_vendor']) + + device_data = self._prepare_device_data_from_nfp_context(nfp_context) + dev_sharing_info = ( orchestration_driver.get_network_function_device_sharing_info( device_data)) + if dev_sharing_info: device = self._get_device_to_reuse(device_data, dev_sharing_info) if device: @@ -583,7 +651,9 @@ def create_network_function_device(self, event): {'device': device}) else: LOG.info(_LI("No Device exists for sharing, Creating new device," - "device request: %(device)s"), {'device': nfd_request}) + "device request: %(device)s"), {'device': nfd_request}) + LOG.info("Prepared device_data %s" % (device_data)) + driver_device_info = ( orchestration_driver.create_network_function_device( device_data)) @@ -594,41 +664,110 @@ def create_network_function_device(self, event): is_internal_event=True) return None + management = nfp_context['management'] + management['port'] = driver_device_info[ + 'mgmt_neutron_port_info']['neutron_port'] + management['port']['ip_address'] = management[ + 'port']['fixed_ips'][0]['ip_address'] + management['subnet'] = driver_device_info[ + 'mgmt_neutron_port_info']['neutron_subnet'] + # Update newly created device with required params device = self._update_device_data(driver_device_info, device_data) device['network_function_device_id'] = device['id'] # Create DB entry with status as DEVICE_SPAWNING - self._create_network_function_device_db(device, - 'DEVICE_SPAWNING') + network_function_device = self._create_network_function_device_db(device, + 'DEVICE_SPAWNING') + + #[mak: TODO] Wrong by nfp_db method needs in this format + network_function_device['mgmt_port_id'] = device['mgmt_port_id'] + nfp_context['network_function_device'] = network_function_device + # Create an event to NSO, to give device_id device_created_data = { 'network_function_instance_id': ( nfd_request['network_function_instance']['id']), 'network_function_device_id': device['id'] } - self._create_event(event_id='DEVICE_CREATED', - event_data=device_created_data) + self._create_event(event_id='DEVICE_SPAWNING', - event_data=device, + event_data=nfp_context, is_poll_event=True, original_event=event) + self._create_event(event_id='DEVICE_CREATED', + event_data=device_created_data) + + + def _post_device_up_event_graph(self, nfp_context): + nf_id = nfp_context['network_function']['id'] + du_event = self._controller.new_event(id="DEVICE_UP", + key=nf_id, + data=nfp_context, + graph=True) + self._controller.post_event(du_event) + + hc_event = self._controller.new_event(id="PERFORM_HEALTH_CHECK", + key=nf_id, + data=nfp_context, + graph=True) + self._controller.post_event(hc_event) + + plug_int_event = self._controller.new_event(id="PLUG_INTERFACES", + key=nf_id, + data=nfp_context, + graph=True) + self._controller.post_event(plug_int_event) + + + graph = nfp_event.EventGraph(du_event) + graph.add_node(hc_event, du_event) + graph.add_node(plug_int_event, du_event) - @poll_event_desc(event='DEVICE_SPAWNING', spacing=20) + graph_event = self._controller.new_event(id="HEALTH_MONITOR_GRAPH", + graph=graph) + self._controller.post_event_graph(graph_event) + + + @nfp_api.poll_event_desc(event='DEVICE_SPAWNING', spacing=2) def check_device_is_up(self, event): - device = event.data + nfp_context = event.data + + service_details = nfp_context['service_details'] + network_function_device = nfp_context['network_function_device'] + token = nfp_context['resource_owner_context']['admin_token'] + tenant_id = nfp_context['resource_owner_context']['tenant_id'] + + device = { + 'token': token, + 'tenant_id': tenant_id, + 'id': network_function_device['id'], + 'service_details': service_details} orchestration_driver = self._get_orchestration_driver( - device['service_details']['service_vendor']) + service_details['service_vendor']) + is_device_up = ( orchestration_driver.get_network_function_device_status(device)) + if is_device_up == nfp_constants.ACTIVE: - # create event DEVICE_UP - self._create_event(event_id='DEVICE_UP', - event_data=device, - is_internal_event=True) - self._update_network_function_device_db(device, - 'DEVICE_UP') + # [(mak)TODO] - Update interfaces count here before + # sending health monitor rpc in PERFORM_HEALTH_CHECK event. + # [HACK] to handle a very corner case where + # PLUG_INTERFACES completes later than HEALTHMONITOR. + # till proper fix is identified. + provider = nfp_context['provider']['ptg'] + consumer = nfp_context['consumer']['ptg'] + network_function_device = nfp_context['network_function_device'] + + if provider: + network_function_device['interfaces_in_use'] += 1 + if consumer: + network_function_device['interfaces_in_use'] += 1 + + nf_id = nfp_context['network_function']['id'] + self._post_device_up_event_graph(nfp_context) + return STOP_POLLING elif is_device_up == nfp_constants.ERROR: # create event DEVICE_NOT_UP @@ -636,32 +775,90 @@ def check_device_is_up(self, event): event_data=device, is_internal_event=True) self._update_network_function_device_db(device, - 'DEVICE_NOT_UP') + 'DEVICE_NOT_UP') return STOP_POLLING else: # Continue polling until device status became ACTIVE/ERROR. return CONTINUE_POLLING + def _post_configure_device_graph(self, nfp_context): + nf_id = nfp_context['network_function']['id'] + device_configured_event = self._controller.new_event(id='CONFIGURATION_COMPLETE', + key=nf_id, + data=nfp_context, + graph=True) + self._controller.post_event(device_configured_event) + device_configure_event = self._controller.new_event(id='CREATE_DEVICE_CONFIGURATION', + key=nf_id, + data=nfp_context, + graph=True) + self._controller.post_event(device_configure_event) + device_active_event = self._controller.new_event(id='DEVICE_ACTIVE', + key=nf_id, + data=nfp_context, + graph=True) + self._controller.post_event(device_active_event) + + graph = nfp_event.EventGraph(device_configured_event) + graph.add_node(device_configure_event, device_configured_event) + graph.add_node(device_active_event, device_configured_event) + + event_graph = self._controller.new_event(id='DEVICE_CONFIGURATION_GRAPH', + graph=graph) + self._controller.post_event_graph(event_graph) + + def device_up(self, event): + nfp_context = event.data + + # Get the results of PLUG_INTERFACES & PERFORM_HEALTH_CHECK events + # results. + results = event.graph.get_leaf_node_results(event) + + for result in results: + if result.result.lower() != 'success': + return self._controller.event_complete(event, result='FAILED') + + self._post_configure_device_graph(nfp_context) + self._controller.event_complete(event) + def perform_health_check(self, event): + nfp_context = event.data + + service_details = nfp_context['service_details'] + network_function_device = nfp_context['network_function_device'] + network_function = nfp_context['network_function'] + network_function_instance = nfp_context['network_function_instance'] + mgmt_ip_address = nfp_context['management']['port']['ip_address'] + # The driver tells which protocol / port to monitor ?? - device = event.data orchestration_driver = self._get_orchestration_driver( - device['service_details']['service_vendor']) + service_details['service_vendor']) + nfp_context['event_desc'] = event.desc.to_dict() + device = { + 'id': network_function_device['id'], + 'mgmt_ip_address': mgmt_ip_address, + 'service_details': service_details, + 'network_function_id': network_function['id'], + 'network_function_instance_id': network_function_instance['id'], + 'nfp_context': nfp_context + } + hm_req = ( orchestration_driver.get_network_function_device_healthcheck_info( - device)) + device)) if not hm_req: - self._create_event(event_id='DRIVER_ERROR', - event_data=device, - is_internal_event=True) + self._controller.event_complete(event, result="FAILED") return None + self.configurator_rpc.create_network_function_device_config(device, hm_req) LOG.debug("Health Check RPC sent to configurator for device: " "%s with health check parameters: %s" % ( - device['id'], hm_req)) + device['id'], hm_req)) + + device['status'] = 'HEALTH_CHECK_PENDING' self._update_network_function_device_db(device, - 'HEALTH_CHECK_PENDING') + 'HEALTH_CHECK_PENDING') def _get_service_type(self, service_profile_id): admin_token = self.keystoneclient.get_admin_token() @@ -674,29 +871,29 @@ def _prepare_device_data(self, device_info): network_function_device_id = device_info['network_function_device_id'] #network_function_device_id = device_info['id'] network_function_instance_id = ( - device_info['network_function_instance_id']) + device_info['network_function_instance_id']) network_function = self._get_nsf_db_resource( - 'network_function', - network_function_id) + 'network_function', + network_function_id) network_function_device = self._get_nsf_db_resource( - 'network_function_device', - network_function_device_id) + 'network_function_device', + network_function_device_id) network_function_instance = self._get_nsf_db_resource( - 'network_function_instance', - network_function_instance_id) + 'network_function_instance', + network_function_instance_id) admin_token = self.keystoneclient.get_admin_token() service_profile = self.gbpclient.get_service_profile( admin_token, network_function['service_profile_id']) service_details = transport.parse_service_flavor_string( - service_profile['service_flavor']) + service_profile['service_flavor']) device_info.update({ - 'network_function_instance': network_function_instance}) + 'network_function_instance': network_function_instance}) device_info.update({'id': network_function_device_id}) service_details.update({'service_type': self._get_service_type( - network_function['service_profile_id'])}) + network_function['service_profile_id'])}) device_info.update({'service_details': service_details}) device = self._get_device_data(device_info) @@ -708,9 +905,22 @@ def _prepare_device_data(self, device_info): device['network_function_id'] = network_function_id device['advance_sharing_interfaces'] = ( - self._get_advance_sharing_interfaces(device['id'])) + self._get_advance_sharing_interfaces(device['id'])) return device + def health_monitor_complete(self, event, result='SUCCESS'): + nfp_context = event.data['nfp_context'] + device = nfp_context['network_function_device'] + network_function = nfp_context['network_function'] + + # Invoke event_complete for original event which is + # PERFORM_HEALTH_CHECK + event_desc = nfp_context.pop('event_desc') + event = self._controller.new_event(id="PERFORM_HEALTH_CHECK", + key = network_function['id'], desc_dict=event_desc) + self._controller.event_complete(event, result=result) + + def plug_interfaces(self, event, is_event_call=True): if is_event_call: device_info = event.data @@ -730,8 +940,8 @@ def plug_interfaces(self, event, is_event_call=True): if _ifaces_plugged_in: if advance_sharing_ifaces: self._update_advance_sharing_interfaces( - device, - advance_sharing_ifaces) + device, + advance_sharing_ifaces) self._increment_device_interface_count(device) self._create_event(event_id='CONFIGURE_DEVICE', event_data=device, @@ -741,13 +951,66 @@ def plug_interfaces(self, event, is_event_call=True): event_data=device, is_internal_event=True) - def create_device_configuration(self, event): + def plug_interfaces_v1(self, event): + + # In this case, the event will be + # happening in paralell with HEALTHMONITORIN, + # so, we should not generate CONFIGURE_DEVICE & should not update + # DB with HEALTH_CHECK_COMPLETED. + + nfp_context = event.data + + service_details = nfp_context['service_details'] + network_function_device = nfp_context['network_function_device'] + token = nfp_context['resource_owner_context']['admin_token'] + tenant_id = nfp_context['resource_owner_context']['admin_tenant_id'] + + consumer = nfp_context['consumer'] + provider = nfp_context['provider'] + + orchestration_driver = self._get_orchestration_driver( + service_details['service_vendor']) + + ports = [] + if consumer['port']: + ports.append({'id': consumer['port']['id'], + 'port_classification': consumer['port_classification'], + 'port_model': consumer['port_model']}) + if provider['port']: + ports.append({'id': provider['port']['id'], + 'port_classification': provider['port_classification'], + 'port_model': provider['port_model']}) + device = { + 'id': network_function_device['id'], + 'ports': ports, + 'service_details': service_details, + 'token': token, + 'tenant_id': tenant_id, + 'interfaces_in_use': network_function_device['interfaces_in_use'], + 'status': network_function_device['status']} + + _ifaces_plugged_in, advance_sharing_ifaces = ( + orchestration_driver.plug_network_function_device_interfaces( + device)) + if _ifaces_plugged_in: + if advance_sharing_ifaces: + self._update_advance_sharing_interfaces( + device, + advance_sharing_ifaces) + self._increment_device_interface_count(device) + #[mak: TODO] - Check how incremented ref count can be updated in DB + self._controller.event_complete(event, result="SUCCESS") + else: + self._controller.event_complete(event, result="FAILED") + + + def configure_device(self, event): device = event.data orchestration_driver = self._get_orchestration_driver( device['service_details']['service_vendor']) config_params = ( orchestration_driver.get_network_function_device_config_info( - device)) + device)) if not config_params: self._create_event(event_id='DRIVER_ERROR', event_data=device, @@ -755,36 +1018,97 @@ def create_device_configuration(self, event): return None # Sends RPC to configurator to create generic config self.configurator_rpc.create_network_function_device_config( - device, config_params) + device, config_params) + + def create_device_configuration(self, event): + nfp_context = event.data + + service_details = nfp_context['service_details'] + token = nfp_context['resource_owner_context']['admin_token'] + tenant_id = nfp_context['resource_owner_context']['tenant_id'] + consumer = nfp_context['consumer'] + provider = nfp_context['provider'] + management = nfp_context['management'] + network_function = nfp_context['network_function'] + network_function_instance = nfp_context['network_function_instance'] + network_function_device = nfp_context['network_function_device'] + + orchestration_driver = self._get_orchestration_driver( + service_details['service_vendor']) + device = { + 'mgmt_ip': management['port']['ip_address'], + 'provider_ip': provider['port']['ip_address'], + 'provider_cidr': provider['subnet']['cidr'], + 'provider_mac': provider['port']['mac_address'], + 'provider_gateway_ip': provider['subnet']['gateway_ip']} + + if consumer['port'] and consumer['subnet']: + device.update({'consumer_ip': consumer['port']['ip_address'], + 'consumer_cidr': consumer['subnet']['cidr'], + 'consumer_mac': consumer['port']['mac_address'], + 'consumer_gateway_ip': consumer['subnet']['gateway_ip']}) + + config_params = ( + orchestration_driver.get_create_network_function_device_config_info( + device)) + nfp_context['event_desc'] = event.desc.to_dict() + device.update({ + 'id': network_function_device['id'], + 'mgmt_ip_address': management['port']['ip_address'], + 'service_details': service_details, + 'network_function_id': network_function['id'], + 'network_function_instance_id': network_function_instance['id'], + 'nfp_context': nfp_context}) + + if not config_params: + self._controller.event_complete(event, result="FAILED") + return None + # Sends RPC to configurator to create generic config + self.configurator_rpc.create_network_function_device_config( + device, config_params) + + def configuration_complete(self, event): + nfp_context = event.data + nf_id = nfp_context['network_function']['id'] + event_results = event.graph.get_leaf_node_results(event) + for result in event_results: + if result.result != "SUCCESS": + self._controller.event_complete(event) + return + sc_event = self._controller.new_event(id="SERVICE_CONFIGURED", + key=nf_id, + data=nfp_context) + self._controller.post_event(sc_event) + self._controller.event_complete(event) + + def device_configuration_complete(self, event, result='SUCCESS'): + nfp_context = event.data['nfp_context'] + + device = nfp_context['network_function_device'] + network_function = nfp_context['network_function'] - def device_configuration_complete(self, event): - device_info = event.data - device = self._prepare_device_data(device_info) - # Change status to active in DB and generate an event DEVICE_ACTIVE - # to inform NSO self._increment_device_ref_count(device) self._update_network_function_device_db(device, nfp_constants.ACTIVE) LOG.info(_LI("Device Configuration completed for device: %(device_id)s" - "Updated DB status to ACTIVE, Incremented device " - "reference count for %(device)s"), + "Updated DB status to ACTIVE, Incremented device " + "reference count for %(device)s"), {'device_id': device['id'], 'device': device}) - device_created_data = { - 'network_function_id': ( - device['network_function_id']), - 'network_function_instance_id': ( - device['network_function_instance_id']), - 'network_function_device_id': device['id'] - } - # DEVICE_ACTIVE event for NSO. - self._create_event(event_id='DEVICE_ACTIVE', - event_data=device_created_data) + + # Invoke event_complete for original event which is + # CREATE_DEVICE_CONFIGURATION + network_function = nfp_context['network_function'] + event_desc = nfp_context.pop('event_desc') + event = self._controller.new_event(id="CREATE_DEVICE_CONFIGURATION", + key = network_function['id'], desc_dict=event_desc) + self._controller.event_complete(event, result=result) + # Delete path def delete_network_function_device(self, event): delete_nfd_request = event.data network_function_instance = ( - delete_nfd_request.pop('network_function_instance')) + delete_nfd_request.pop('network_function_instance')) delete_nfd_request['network_function_instance_id'] = ( network_function_instance['id']) device = self._prepare_device_data(delete_nfd_request) @@ -802,7 +1126,7 @@ def delete_device_configuration(self, event): device['service_details']['service_vendor']) config_params = ( orchestration_driver.get_network_function_device_config_info( - device)) + device)) if not config_params: self._create_event(event_id='DRIVER_ERROR', event_data=device, @@ -810,7 +1134,7 @@ def delete_device_configuration(self, event): return None # Sends RPC call to configurator to delete generic config API self.configurator_rpc.delete_network_function_device_config(device, - config_params) + config_params) def unplug_interfaces(self, event): device_info = event.data @@ -824,8 +1148,8 @@ def unplug_interfaces(self, event): if is_interface_unplugged: if advance_sharing_ifaces: self._update_advance_sharing_interfaces( - device, - advance_sharing_ifaces) + device, + advance_sharing_ifaces) mgmt_port_id = device['mgmt_port_id'] self._decrement_device_interface_count(device) device['mgmt_port_id'] = mgmt_port_id @@ -859,13 +1183,13 @@ def delete_device(self, event): self._create_event(event_id='DEVICE_DELETED', event_data=device) - @poll_event_desc(event='DEVICE_BEING_DELETED', spacing=2) + @nfp_api.poll_event_desc(event='DEVICE_BEING_DELETED', spacing=2) def check_device_deleted(self, event): device = event.data orchestration_driver = self._get_orchestration_driver( device['service_details']['service_vendor']) status = orchestration_driver.get_network_function_device_status( - device, ignore_failure=True) + device, ignore_failure=True) if not status: device_id = device['id'] del device['id'] @@ -904,6 +1228,7 @@ def handle_device_not_reachable(self, event): device['network_function_device_id'] = device['id'] self._create_event(event_id='DEVICE_CREATE_FAILED', event_data=device) + self.health_monitor_complete(event, result='FAILED') def handle_device_config_failed(self, event): device = event.data @@ -915,6 +1240,7 @@ def handle_device_config_failed(self, event): event_data=device) LOG.debug("Device create failed for device: %s, with " "data: %s" % (device['id'], device)) + self.device_configuration_complete(event, result='FAILED') def handle_interfaces_setup_failed(self, event): device = event.data @@ -930,7 +1256,7 @@ def handle_interfaces_setup_failed(self, event): def handle_driver_error(self, event): device = event.data LOG.error(_LE("Exception occured in driver, driver returned None " - " for device %(device)s"), {'device': device}) + " for device %(device)s"), {'device': device}) status = nfp_constants.ERROR desc = 'Exception in driver, driver return None' self._update_network_function_device_db(device, status, desc) @@ -950,17 +1276,19 @@ def __init__(self, context, conf): self.context = context self.client = n_rpc.get_client(self.target) self.rpc_api = self.client.prepare(version=self.API_VERSION, - topic=nsf_topics.NFP_NDO_CONFIGURATOR_TOPIC) + topic=nsf_topics.NFP_NDO_CONFIGURATOR_TOPIC) def _get_request_info(self, device, operation): request_info = { - 'nf_id': device['network_function_id'], - 'nfi_id': ( - device['network_function_instance_id']), - 'nfd_id': device['id'], - 'requester': nfp_constants.DEVICE_ORCHESTRATOR, - 'operation': operation, - 'logging_context': nfp_logging.get_logging_context() + 'nf_id': device['network_function_id'], + 'nfi_id': ( + device['network_function_instance_id']), + 'nfd_id': device['id'], + 'requester': nfp_constants.DEVICE_ORCHESTRATOR, + 'operation': operation, + 'logging_context': nfp_logging.get_logging_context(), + # So that notification callbacks can work on cached data + 'nfp_context': device.get('nfp_context', None) } nfd_ip = device['mgmt_ip_address'] request_info.update({'device_ip': nfd_ip}) diff --git a/gbpservice/nfp/orchestrator/modules/service_orchestrator.py b/gbpservice/nfp/orchestrator/modules/service_orchestrator.py index 2a58c7428c..d69008dc97 100644 --- a/gbpservice/nfp/orchestrator/modules/service_orchestrator.py +++ b/gbpservice/nfp/orchestrator/modules/service_orchestrator.py @@ -27,11 +27,14 @@ from gbpservice.nfp.orchestrator.db import api as nfp_db_api from gbpservice.nfp.orchestrator.db import nfp_db as nfp_db from gbpservice.nfp.orchestrator.openstack import openstack_driver +from gbpservice.nfp.core import module as nfp_api import sys import traceback from gbpservice.nfp.core import log as nfp_logging +from gbpservice.nfp.core import context as nfp_core_context + LOG = nfp_logging.getLogger(__name__) STOP_POLLING = {'poll': False} @@ -69,7 +72,8 @@ def events_init(controller, config, service_orchestrator): 'DELETE_USER_CONFIG_IN_PROGRESS', 'CONFIG_APPLIED', 'USER_CONFIG_APPLIED', 'USER_CONFIG_DELETED', 'USER_CONFIG_DELETE_FAILED', 'USER_CONFIG_UPDATE_FAILED', - 'USER_CONFIG_FAILED'] + 'USER_CONFIG_FAILED', 'CHECK_USER_CONFIG_COMPLETE', + 'SERVICE_CONFIGURED'] events_to_register = [] for event in events: events_to_register.append( @@ -258,7 +262,7 @@ def _create_event(self, event_id, event_data=None, if is_poll_event: ev = self._controller.new_event( id=event_id, data=event_data, - serialize=original_event.serialize, + serialize=original_event.sequence, binding_key=original_event.binding_key, key=original_event.key) LOG.debug("poll event started for %s" % (ev.id)) @@ -331,7 +335,7 @@ def network_function_notification(self, context, notification_data): nfp_logging.clear_logging_context() -class ServiceOrchestrator(object): +class ServiceOrchestrator(nfp_api.NfpEventHandler): """Orchestrator For Network Services @@ -411,13 +415,15 @@ def event_method_mapping(self, event_id): "CONSUMER_ADD": self.consumer_ptg_add_user_config, "CONSUMER_REMOVE": self.consumer_ptg_remove_user_config, "APPLY_USER_CONFIG_IN_PROGRESS": ( + self.apply_user_config_in_progress), + "CHECK_USER_CONFIG_COMPLETE": ( self.check_for_user_config_complete), "UPDATE_USER_CONFIG_PREPARING_TO_START": ( self.check_for_user_config_deleted), "UPDATE_USER_CONFIG_IN_PROGRESS": ( self.handle_continue_update_user_config), "UPDATE_USER_CONFIG_STILL_IN_PROGRESS": ( - self.check_for_user_config_complete), + self.apply_user_config_in_progress), "DELETE_USER_CONFIG_IN_PROGRESS": ( self.check_for_user_config_deleted), "CONFIG_APPLIED": self.handle_config_applied, @@ -425,7 +431,8 @@ def event_method_mapping(self, event_id): "USER_CONFIG_DELETED": self.handle_user_config_deleted, "USER_CONFIG_DELETE_FAILED": self.handle_user_config_delete_failed, "USER_CONFIG_UPDATE_FAILED": self.handle_update_user_config_failed, - "USER_CONFIG_FAILED": self.handle_user_config_failed + "USER_CONFIG_FAILED": self.handle_user_config_failed, + "SERVICE_CONFIGURED": self.handle_service_configured } if event_id not in event_handler_mapping: raise Exception("Invalid Event ID") @@ -469,18 +476,18 @@ def _create_event(self, event_id, event_data=None, if is_poll_event: ev = self._controller.new_event( id=event_id, data=event_data, - serialize=original_event.serialize, + serialize=original_event.sequence, binding_key=original_event.binding_key, - key=original_event.desc.uid) + key=original_event.desc.uuid) LOG.debug("poll event started for %s" % (ev.id)) self._controller.poll_event(ev, max_times=20) else: if original_event: ev = self._controller.new_event( id=event_id, data=event_data, - serialize=original_event.serialize, + serialize=original_event.sequence, binding_key=original_event.binding_key, - key=original_event.desc.uid) + key=original_event.desc.uuid) else: ev = self._controller.new_event( id=event_id, @@ -491,9 +498,9 @@ def _create_event(self, event_id, event_data=None, if original_event: event = self._controller.new_event( id=event_id, data=event_data, - serialize=original_event.serialize, + serialize=original_event.sequence, binding_key=original_event.binding_key, - key=original_event.desc.uid) + key=original_event.desc.uuid) else: # Same module API, so calling corresponding function # directly. @@ -536,9 +543,11 @@ def update_network_function_user_config(self, network_function_id, if tag_str != nfp_constants.CONFIG_INIT_TAG: network_function_details = self.get_network_function_details( network_function_id) - service_type = self._get_service_type( - network_function_details['network_function'][ - 'service_profile_id']) + service_type = network_function_details.pop('service_type') + if not service_type: + service_type = self._get_service_type( + network_function_details['network_function'][ + 'service_profile_id']) network_function_data = { 'network_function_details': network_function_details, 'service_type': service_type @@ -624,14 +633,21 @@ def _report_logging_info(self, nf, nfi, service_type, def create_network_function(self, context, network_function_info): self._validate_create_service_input(context, network_function_info) + + admin_token = self.keystoneclient.get_admin_token() + admin_tenant_id = self.keystoneclient.get_admin_tenant_id(admin_token) + + network_function_info['resource_owner_context']['admin_token'] = admin_token + network_function_info['resource_owner_context']['admin_tenant_id'] = admin_tenant_id + + tenant_id = network_function_info['tenant_id'] + # GBP or Neutron mode = network_function_info['network_function_mode'] - service_profile_id = network_function_info['service_profile_id'] - service_id = network_function_info['service_id'] - admin_token = self.keystoneclient.get_admin_token() - service_profile = self.gbpclient.get_service_profile( - admin_token, service_profile_id) - service_chain_id = network_function_info.get('service_chain_id') + service_profile = network_function_info['service_profile'] + service_profile_id = service_profile['id'] + service_id = network_function_info['service_chain_node']['id'] + service_chain_id = network_function_info['service_chain_instance']['id'] service_details = transport.parse_service_flavor_string( service_profile['service_flavor']) base_mode_support = (True if service_details['device_type'] == 'None' @@ -644,7 +660,7 @@ def create_network_function(self, context, network_function_info): network_function = { 'name': name, 'description': '', - 'tenant_id': network_function_info['tenant_id'], + 'tenant_id': tenant_id, 'service_id': service_id, # GBP Service Node or Neutron Service ID 'service_chain_id': service_chain_id, # GBP SC instance ID 'service_profile_id': service_profile_id, @@ -675,26 +691,19 @@ def create_network_function(self, context, network_function_info): service_config_str) return network_function - if mode == nfp_constants.GBP_MODE: - management_network_info = { - 'id': network_function_info['management_ptg_id'], - 'port_model': nfp_constants.GBP_NETWORK - } - else: - management_network_info = {} - create_network_function_instance_request = { - 'network_function': network_function, - 'network_function_port_info': network_function_info['port_info'], - 'management_network_info': management_network_info, - 'service_type': service_profile['service_type'], - 'service_details': service_details, - 'share_existing_device': False # Extend service profile if needed - } + nfp_context = network_function_info + + service_details['service_type'] = service_profile['service_type'] + service_details['network_mode'] = nfp_context['network_function_mode'] + nfp_context['network_function'] = network_function + nfp_context['service_details'] = service_details + nfp_context['share_existing_device'] = False # Create and event to perform Network service instance self._create_event('CREATE_NETWORK_FUNCTION_INSTANCE', - event_data=create_network_function_instance_request, + event_data=nfp_context, is_internal_event=True) + nfp_logging.clear_logging_context() return network_function @@ -785,10 +794,24 @@ def _get_network_function_instance_for_multi_service_sharing(self, return None def create_network_function_instance(self, event): - request_data = event.data - name = '%s_%s' % (request_data['network_function']['name'], - request_data['network_function']['id']) - port_info = request_data['network_function_port_info'] + nfp_context = event.data + + network_function = nfp_context['network_function'] + service_profile = nfp_context['service_profile'] + service_details = nfp_context['service_details'] + consumer = nfp_context['consumer'] + provider = nfp_context['provider'] + + port_info = [] + for ele in [consumer, provider]: + if ele['pt']: + port_info.append({'id': ele['pt']['id'], + 'port_model': ele['port_model'], + 'port_classification': ele['port_classification'] + }) + + name = '%s_%s' % (network_function['name'], + network_function['id']) network_function_instance = ( self._get_network_function_instance_for_multi_service_sharing( port_info)) @@ -796,13 +819,12 @@ def create_network_function_instance(self, event): port_info = [] create_nfi_request = { 'name': name, - 'tenant_id': request_data['network_function']['tenant_id'], + 'tenant_id': network_function['tenant_id'], 'status': nfp_constants.PENDING_CREATE, - 'network_function_id': request_data['network_function']['id'], - 'service_type': request_data['service_type'], - 'service_vendor': ( - request_data['service_details']['service_vendor']), - 'share_existing_device': request_data['share_existing_device'], + 'network_function_id': network_function['id'], + 'service_type': service_details['service_type'], + 'service_vendor': service_details['service_vendor'], + 'share_existing_device': nfp_context['share_existing_device'], 'port_info': port_info, } nfi_db = self.db_handler.create_network_function_instance( @@ -826,25 +848,16 @@ def create_network_function_instance(self, event): return # Sending LogMeta Details to visibility - self._report_logging_info(request_data['network_function'], nfi_db, - request_data['service_type'], - request_data['service_details'][ - 'service_vendor']) - - request_data['service_details'].update( - service_type=request_data['service_type']) - create_nfd_request = { - 'network_function': request_data['network_function'], - 'network_function_instance': nfi_db, - 'management_network_info': request_data['management_network_info'], - 'service_vendor': ( - request_data['service_details']['service_vendor']), - 'service_details': request_data['service_details'], - 'share_existing_device': request_data['share_existing_device'], - } + self._report_logging_info(network_function, + nfi_db, + service_details['service_type'], + service_details['service_vendor']) + + nfp_context['network_function_instance'] = nfi_db + LOG.info(_LI("[Event:CreateService]")) self._create_event('CREATE_NETWORK_FUNCTION_DEVICE', - event_data=create_nfd_request) + event_data=nfp_context) def handle_device_created(self, event): request_data = event.data @@ -857,7 +870,15 @@ def handle_device_created(self, event): return def handle_device_active(self, event): - request_data = event.data + nfp_context = event.data + + network_function_instance = nfp_context['network_function_instance'] + network_function_device = nfp_context['network_function_device'] + network_function = nfp_context['network_function'] + + request_data = {'network_function_device_id': network_function_device['id'], + 'network_function_instance_id': network_function_instance['id']} + nfi = { 'status': nfp_constants.ACTIVE, 'network_function_device_id': request_data[ @@ -865,37 +886,56 @@ def handle_device_active(self, event): } nfi = self.db_handler.update_network_function_instance( self.db_session, request_data['network_function_instance_id'], nfi) - network_function = self.db_handler.get_network_function( - self.db_session, nfi['network_function_id']) - service_config = network_function['service_config'] + network_function_instance['status'] = nfp_constants.ACTIVE + network_function_instance['network_function_device_id'] = network_function_device['id'] + service_config = network_function['service_config'] + nfp_core_context.store_nfp_context(nfp_context) + nfp_context['event_desc'] = event.desc.to_dict() self.create_network_function_user_config(network_function['id'], service_config) def apply_user_config(self, event): request_data = event.data + nfp_context = event.data['nfp_context'] + + network_function = nfp_context['network_function'] network_function_details = self.get_network_function_details( - request_data['network_function_id']) - request_data['heat_stack_id'] = self.config_driver.apply_config( - network_function_details) # Heat driver to launch stack - network_function = network_function_details['network_function'] + network_function['id']) + request_data['heat_stack_id'], heat_client = self.config_driver.apply_heat_config( + nfp_context) # Heat driver to launch stack request_data['network_function_id'] = network_function['id'] + if not request_data['heat_stack_id']: + device_active_event = self._controller.new_event(id='DEVICE_ACTIVE', + key=network_function['id']) + event_desc = nfp_context.pop('event_desc') + device_active_event = self._set_event_desc_to_event( + device_active_event, + event_desc) + self._controller.event_complete(device_active_event, result="FAILED") self._create_event('USER_CONFIG_FAILED', event_data=request_data, is_internal_event=True) + self._controller.event_complete(event) return - request_data['tenant_id'] = network_function['tenant_id'] - request_data['network_function_details'] = network_function_details + LOG.debug("handle_device_active heat_stack_id: %s" % (request_data['heat_stack_id'])) + + nfp_context['heat_stack_id'] = request_data['heat_stack_id'] + nfp_context['network_function'].update({ + 'heat_stack_id': request_data['heat_stack_id'], + 'description': network_function['description']}) + + self._create_event('CHECK_USER_CONFIG_COMPLETE', + event_data=nfp_context, + is_poll_event=True, + original_event=event) + self.db_handler.update_network_function( self.db_session, network_function['id'], {'heat_stack_id': request_data['heat_stack_id'], 'description': network_function['description']}) - self._create_event('APPLY_USER_CONFIG_IN_PROGRESS', - event_data=request_data, - is_poll_event=True, - original_event=event) def handle_update_user_config(self, event): request_data = event.data @@ -1061,8 +1101,10 @@ def delete_network_function_instance(self, event): # FIXME: Add all possible validations here def _validate_create_service_input(self, context, create_service_request): - required_attributes = ["tenant_id", "service_id", "service_chain_id", - "service_profile_id", "network_function_mode"] + required_attributes = ["resource_owner_context","service_chain_instance", + "service_chain_node", "service_profile", + "service_config", "provider", "consumer", + "network_function_mode"] if (set(required_attributes) & set(create_service_request.keys()) != set(required_attributes)): missing_keys = (set(required_attributes) - @@ -1071,8 +1113,7 @@ def _validate_create_service_input(self, context, create_service_request): required_data=", ".join(missing_keys), request="Create Network Function") if create_service_request['network_function_mode'].lower() == "gbp": - gbp_required_attributes = ["port_info", "service_chain_id", - "management_ptg_id"] + gbp_required_attributes = ["management_ptg_id"] if (set(gbp_required_attributes) & set(create_service_request.keys()) != set(gbp_required_attributes)): @@ -1082,7 +1123,7 @@ def _validate_create_service_input(self, context, create_service_request): required_data=", ".join(missing_keys), request="Create Network Function") - def check_for_user_config_complete(self, event): + def apply_user_config_in_progress(self, event): request_data = event.data config_status = self.config_driver.is_config_complete( request_data['heat_stack_id'], request_data['tenant_id'], @@ -1098,7 +1139,7 @@ def check_for_user_config_complete(self, event): self.db_session, request_data['network_function_id'], updated_network_function) - self._controller.event_done(event) + self._controller.event_complete(event) return STOP_POLLING # Trigger RPC to notify the Create_Service caller with status elif config_status == nfp_constants.COMPLETED: @@ -1111,7 +1152,60 @@ def check_for_user_config_complete(self, event): self.db_session, request_data['network_function_id'], updated_network_function) - self._controller.event_done(event) + self._controller.event_complete(event) + return STOP_POLLING + # Trigger RPC to notify the Create_Service caller with status + elif config_status == nfp_constants.IN_PROGRESS: + return CONTINUE_POLLING + + def handle_service_configured(self, event): + nfp_context = event.data + network_function = nfp_context['network_function'] + updated_network_function = {'status': nfp_constants.ACTIVE} + LOG.info(_LI("NSO: applying user config is successfull moving " + "network function %(network_function_id)s to ACTIVE"), + {'network_function_id':network_function['id']}) + self.db_handler.update_network_function( + self.db_session, + network_function['id'], + updated_network_function) + self._controller.event_complete(event) + + def check_for_user_config_complete(self, event): + nfp_context = event.data + + network_function = nfp_context['network_function'] + config_status = self.config_driver.check_config_complete(nfp_context) + + if config_status == nfp_constants.ERROR: + + LOG.info(_LI("NSO: applying user config failed for " + "network function %(network_function_id)s data " + "%(data)s"), {'data': nfp_context, + 'network_function_id': + network_function['id']}) + updated_network_function = {'status': nfp_constants.ERROR} + self.db_handler.update_network_function( + self.db_session, + network_function['id'], + updated_network_function) + + # Complete the original event DEVICE_ACTIVE here + event_desc = nfp_context.pop('event_desc') + device_active_event = self._controller.new_event(id='DEVICE_ACTIVE', + key=network_function['id'], + desc_dict=event_desc) + self._controller.event_complete(device_active_event, result="FAILED") + return STOP_POLLING + # Trigger RPC to notify the Create_Service caller with status + elif config_status == nfp_constants.COMPLETED: + # Complete the original event DEVICE_ACTIVE here + event_desc = nfp_context.pop('event_desc') + device_active_event = self._controller.new_event(id='DEVICE_ACTIVE', + key=network_function['id'], + desc_dict=event_desc) + self._controller.event_complete(device_active_event, result="SUCCESS") + return STOP_POLLING # Trigger RPC to notify the Create_Service caller with status elif config_status == nfp_constants.IN_PROGRESS: @@ -1131,12 +1225,12 @@ def check_for_user_config_deleted(self, event): " completion."), {'err': err}) self._create_event('USER_CONFIG_DELETE_FAILED', event_data=event_data, is_internal_event=True) - self._controller.event_done(event) + self._controller.event_complete(event) return STOP_POLLING if config_status == nfp_constants.ERROR: self._create_event('USER_CONFIG_DELETE_FAILED', event_data=event_data, is_internal_event=True) - self._controller.event_done(event) + self._controller.event_complete(event) return STOP_POLLING # Trigger RPC to notify the Create_Service caller with status elif config_status == nfp_constants.COMPLETED: @@ -1156,7 +1250,7 @@ def check_for_user_config_deleted(self, event): self._create_event('USER_CONFIG_DELETED', event_data=event_data, is_internal_event=True) - self._controller.event_done(event) + self._controller.event_complete(event) return STOP_POLLING # Trigger RPC to notify the Create_Service caller with status elif config_status == nfp_constants.IN_PROGRESS: @@ -1341,6 +1435,7 @@ def policy_target_add_user_config(self, event): 'network_function_details': network_function_details } if not config_id: + self._controller.event_complete(event) self._create_event('USER_CONFIG_FAILED', event_data=request_data, is_internal_event=True) return @@ -1348,6 +1443,7 @@ def policy_target_add_user_config(self, event): self.db_session, network_function['id'], {'heat_stack_id': config_id}) + self._controller.event_complete(event) self._create_event('APPLY_USER_CONFIG_IN_PROGRESS', event_data=request_data, is_poll_event=True, original_event=event) @@ -1403,6 +1499,7 @@ def policy_target_remove_user_config(self, event): 'network_function_details': network_function_details } if not config_id: + self._controller.event_complete(event) self._create_event('USER_CONFIG_FAILED', event_data=request_data, is_internal_event=True) return @@ -1410,6 +1507,8 @@ def policy_target_remove_user_config(self, event): self.db_session, network_function['id'], {'heat_stack_id': config_id}) + + self._controller.event_complete(event) self._create_event('APPLY_USER_CONFIG_IN_PROGRESS', event_data=request_data, is_poll_event=True, original_event=event) @@ -1472,10 +1571,12 @@ def consumer_ptg_add_user_config(self, event): 'operation': request_data['operation'], 'consumer_ptg': request_data['consumer_ptg'] } + self._controller.event_complete(event) self._create_event('UPDATE_USER_CONFIG_PREPARING_TO_START', event_data=request_data, is_poll_event=True, original_event=event) else: + self._controller.event_complete(event) self._create_event('UPDATE_USER_CONFIG_IN_PROGRESS', event_data=event.data, is_internal_event=True) @@ -1538,10 +1639,13 @@ def consumer_ptg_remove_user_config(self, event): 'operation': request_data['operation'], 'consumer_ptg': request_data['consumer_ptg'] } + + self._controller.event_complete(event) self._create_event('UPDATE_USER_CONFIG_PREPARING_TO_START', event_data=request_data, is_poll_event=True, original_event=event) else: + self._controller.event_complete(event) self._create_event('UPDATE_USER_CONFIG_IN_PROGRESS', event_data=event.data, is_internal_event=True) @@ -1558,24 +1662,44 @@ def get_port_info(self, port_id): return None def get_network_function_details(self, network_function_id): - network_function = self.db_handler.get_network_function( - self.db_session, network_function_id) + network_function = None + network_function_instance = None + network_function_device = None + service_type = None + + nfp_context = nfp_core_context.get_nfp_context() + if nfp_context: + network_function = nfp_context['network_function'] + network_function_instance = nfp_context['network_function_instance'] + network_function_device = nfp_context['network_function_device'] + service_type = nfp_context['service_details']['service_type'] + + if not network_function: + network_function = self.db_handler.get_network_function( + self.db_session, network_function_id) + network_function_details = { - 'network_function': network_function + 'network_function': network_function, + 'service_type': service_type } - network_function_instances = network_function[ - 'network_function_instances'] - if not network_function_instances: - return network_function_details - nfi = self.db_handler.get_network_function_instance( - self.db_session, network_function_instances[0]) - network_function_details['network_function_instance'] = nfi - if nfi['network_function_device_id']: - network_function_device = ( - self.db_handler.get_network_function_device( - self.db_session, nfi['network_function_device_id'])) - network_function_details['network_function_device'] = ( - network_function_device) + + if not network_function_instance: + network_function_instances = network_function[ + 'network_function_instances'] + if not network_function_instances: + return network_function_details + network_function_instance = self.db_handler.get_network_function_instance( + self.db_session, network_function_instances[0]) + + network_function_details['network_function_instance'] = network_function_instance + + if not network_function_device: + if network_function_instance['network_function_device_id']: + network_function_device = ( + self.db_handler.get_network_function_device( + self.db_session, network_function_instance['network_function_device_id'])) + network_function_details['network_function_device'] = ( + network_function_device) return network_function_details def get_network_function_context(self, network_function_id): @@ -1634,7 +1758,8 @@ def _get_request_info(self, user_config_data, operation): 'nfd_id': None, 'requester': nfp_constants.SERVICE_ORCHESTRATOR, 'operation': operation, - 'logging_context': nfp_logging.get_logging_context() + 'logging_context': nfp_logging.get_logging_context(), + 'nfp_context': nfp_core_context.get_nfp_context() } if operation in ['consumer_add', 'consumer_remove']: request_info.update({'consumer_ptg': user_config_data[ diff --git a/gbpservice/nfp/orchestrator/openstack/openstack_driver.py b/gbpservice/nfp/orchestrator/openstack/openstack_driver.py index 869983c1b4..24a3091297 100644 --- a/gbpservice/nfp/orchestrator/openstack/openstack_driver.py +++ b/gbpservice/nfp/orchestrator/openstack/openstack_driver.py @@ -41,6 +41,7 @@ def __init__(self, config, username=None, self.tenant_name = (tenant_name or config.keystone_authtoken.admin_tenant_name) self.token = None + self.admin_tenant_id = None class KeystoneClient(OpenstackApi): @@ -103,6 +104,13 @@ def get_scoped_keystone_token(self, user, password, tenant_name, else: return scoped_token + def get_admin_tenant_id(self, token): + if not self.admin_tenant_id: + _,_,name,_ = self.get_keystone_creds() + self.admin_tenant_id = self.get_tenant_id(token, name) + + return self.admin_tenant_id + def get_tenant_id(self, token, tenant_name): """ Get the tenant UUID associated to tenant name diff --git a/gbpservice/nfp/proxy_agent/notifications/pull.py b/gbpservice/nfp/proxy_agent/notifications/pull.py index a2f6a89528..eb086d4ed4 100644 --- a/gbpservice/nfp/proxy_agent/notifications/pull.py +++ b/gbpservice/nfp/proxy_agent/notifications/pull.py @@ -10,7 +10,7 @@ # License for the specific language governing permissions and limitations # under the License. -from gbpservice.nfp.core import poll as core_pt +from gbpservice.nfp.core import module as nfp_api import gbpservice.nfp.lib.transport as transport from gbpservice.nfp.proxy_agent.lib import topics as a_topics from gbpservice.nfp.core import log as nfp_logging @@ -32,7 +32,7 @@ """Periodic Class to pull notification from configurator""" -class PullNotification(core_pt.PollEventDesc): +class PullNotification(nfp_api.NfpEventHandler): def __init__(self, sc, conf): self._sc = sc @@ -58,7 +58,7 @@ def _method_handler(self, notification): except Exception as e: raise Exception(e) - @core_pt.poll_event_desc(event='PULL_NOTIFICATIONS', spacing=1) + @nfp_api.poll_event_desc(event='PULL_NOTIFICATIONS', spacing=2) def pull_notifications(self, ev): """Pull and handle notification from configurator.""" notifications = transport.get_response_from_configurator(self._conf) diff --git a/gbpservice/nfp/proxy_agent/proxy/proxy.py b/gbpservice/nfp/proxy_agent/proxy/proxy.py index 2c5bd73d8a..c3ca442d2c 100644 --- a/gbpservice/nfp/proxy_agent/proxy/proxy.py +++ b/gbpservice/nfp/proxy_agent/proxy/proxy.py @@ -143,6 +143,7 @@ def __init__(self, conf, socket, type='unix'): self._start_time = time.time() self._end_time = time.time() self.type = type + self.socket_id = self._socket.fileno() def _tick(self): self._idle_count += 1 @@ -167,7 +168,9 @@ def idle_reset(self): def _wait(self, timeout): if self.type == 'unix': eventlet.sleep(timeout) - self._socket.settimeout(timeout) + self._socket.setblocking(0) + else: + self._socket.settimeout(timeout) def recv(self): self._wait(self._idle_wait) @@ -184,14 +187,21 @@ def recv(self): return None def send(self, data): - self._socket.send(data) + self._socket.setblocking(1) + self._socket.sendall(data) + self._socket.setblocking(0) def close(self): LOG.debug("Closing Socket - %d" % (self.identify())) - self._socket.close() + try: + self._socket.shutdown(socket.SHUT_RDWR) + self._socket.close() + except Exception as exc: + LOG.error("%s - exception while closing - %s" % + (self.identify(), str(exc))) def identify(self): - return self._socket.fileno() + return self.socket_id """ diff --git a/gbpservice/nfp/service_vendor_agents/vyos/oc_config_server/static_ip.py b/gbpservice/nfp/service_vendor_agents/vyos/oc_config_server/static_ip.py index 7af02662da..d17e755baf 100644 --- a/gbpservice/nfp/service_vendor_agents/vyos/oc_config_server/static_ip.py +++ b/gbpservice/nfp/service_vendor_agents/vyos/oc_config_server/static_ip.py @@ -24,10 +24,17 @@ def __init__(self): self.hotplug_timeout = 25 def save(self): - session.commit() - session.save() - time.sleep(3) - session.teardown_config_session() + retries = 10 + while retries: + try: + session.commit() + session.save() + time.sleep(3) + session.teardown_config_session() + return + except Exception: + time.sleep(1) + retries -= 1 def discard(self): session.discard()