547 lines
20 KiB
Python
547 lines
20 KiB
Python
# 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 copy
|
|
import os
|
|
|
|
import six
|
|
|
|
from gbpservice.contrib.nfp.configurator.agents import agent_base
|
|
from gbpservice.contrib.nfp.configurator.lib import (
|
|
generic_config_constants as gen_cfg_const)
|
|
from gbpservice.contrib.nfp.configurator.lib import constants as common_const
|
|
from gbpservice.contrib.nfp.configurator.lib import data_parser
|
|
from gbpservice.contrib.nfp.configurator.lib import utils
|
|
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__)
|
|
|
|
|
|
class GenericConfigRpcManager(agent_base.AgentBaseRPCManager):
|
|
"""Implements APIs invoked by configurator for processing RPC messages.
|
|
|
|
RPC client of configurator module receives RPC messages from REST server
|
|
and invokes the API of this class. The instance of this class is registered
|
|
with configurator module using register_service_agent API. Configurator
|
|
module identifies the service agent object based on service type and
|
|
invokes ones of the methods of this class to configure the device.
|
|
|
|
"""
|
|
|
|
def __init__(self, sc, conf):
|
|
"""Instantiates child and parent class objects.
|
|
|
|
Passes the instances of core service controller and oslo configuration
|
|
to parent instance inorder to provide event enqueue facility for batch
|
|
processing event.
|
|
|
|
:param sc: Service Controller object that is used for interfacing
|
|
with core service controller.
|
|
:param conf: Configuration object that is used for configuration
|
|
parameter access.
|
|
|
|
"""
|
|
|
|
self.parse = data_parser.DataParser()
|
|
super(GenericConfigRpcManager, self).__init__(sc, conf)
|
|
|
|
def _send_event(self, context, resource_data, event_id, event_key=None):
|
|
"""Posts an event to framework.
|
|
|
|
:param context: The agent info dictionary prepared in demuxer library
|
|
which contains the API context alongside other information.
|
|
:param kwargs: Keyword arguments which are passed as data to event
|
|
:param event_id: Unique identifier for the event
|
|
:param event_key: Event key for serialization
|
|
|
|
"""
|
|
|
|
arg_dict = {'context': context,
|
|
'resource_data': resource_data}
|
|
ev = self.sc.new_event(id=event_id, data=arg_dict, key=event_key)
|
|
self.sc.post_event(ev)
|
|
|
|
def configure_interfaces(self, context, resource_data):
|
|
"""Enqueues event for worker to process configure interfaces request.
|
|
|
|
:param context: The agent info dictionary prepared in demuxer library
|
|
which contains the API context alongside other information.
|
|
:param kwargs: RPC Request data
|
|
|
|
Returns: None
|
|
|
|
"""
|
|
|
|
self._send_event(context,
|
|
resource_data,
|
|
gen_cfg_const.EVENT_CONFIGURE_INTERFACES)
|
|
|
|
def clear_interfaces(self, context, resource_data):
|
|
"""Enqueues event for worker to process clear interfaces request.
|
|
|
|
:param context: The agent info dictionary prepared in demuxer library
|
|
which contains the API context alongside other information.
|
|
:param kwargs: RPC Request data
|
|
|
|
Returns: None
|
|
|
|
"""
|
|
|
|
self._send_event(context,
|
|
resource_data,
|
|
gen_cfg_const.EVENT_CLEAR_INTERFACES)
|
|
|
|
def configure_routes(self, context, resource_data):
|
|
"""Enqueues event for worker to process configure routes request.
|
|
|
|
:param context: The agent info dictionary prepared in demuxer library
|
|
which contains the API context alongside other information.
|
|
:param kwargs: RPC Request data
|
|
|
|
Returns: None
|
|
|
|
"""
|
|
|
|
self._send_event(context,
|
|
resource_data,
|
|
gen_cfg_const.EVENT_CONFIGURE_ROUTES)
|
|
|
|
def clear_routes(self, context, resource_data):
|
|
"""Enqueues event for worker to process clear routes request.
|
|
|
|
:param context: The agent info dictionary prepared in demuxer library
|
|
which contains the API context alongside other information.
|
|
:param kwargs: RPC Request data
|
|
|
|
Returns: None
|
|
|
|
"""
|
|
|
|
self._send_event(context,
|
|
resource_data,
|
|
gen_cfg_const.EVENT_CLEAR_ROUTES)
|
|
|
|
def configure_healthmonitor(self, context, resource_data):
|
|
"""Enqueues event for worker to process configure healthmonitor
|
|
request.
|
|
|
|
:param context: The agent info dictionary prepared in demuxer library
|
|
which contains the API context alongside other information.
|
|
:param kwargs: RPC Request data
|
|
|
|
Returns: None
|
|
|
|
"""
|
|
LOG.info("Received configure health monitor api for nfds:"
|
|
"%(nfds)s",
|
|
{'nfds': resource_data['nfds']})
|
|
resource_data['fail_count'] = 0
|
|
self._send_event(context,
|
|
resource_data,
|
|
gen_cfg_const.EVENT_CONFIGURE_HEALTHMONITOR,
|
|
resource_data['nfds'][0]['vmid'])
|
|
|
|
def clear_healthmonitor(self, context, resource_data):
|
|
"""Enqueues event for worker to process clear healthmonitor request.
|
|
|
|
:param context: The agent info dictionary prepared in demuxer library
|
|
which contains the API context alongside other information.
|
|
:param kwargs: RPC Request data
|
|
|
|
Returns: None
|
|
|
|
"""
|
|
LOG.info("Received clear health monitor api for nfds:"
|
|
"%(nfds)s",
|
|
{'nfds': resource_data['nfds']})
|
|
event_key = resource_data['nfds'][0]['vmid']
|
|
poll_event_id = gen_cfg_const.EVENT_CONFIGURE_HEALTHMONITOR
|
|
self.sc.stop_poll_event(event_key, poll_event_id)
|
|
|
|
|
|
class GenericConfigEventHandler(agent_base.AgentBaseEventHandler,
|
|
nfp_api.NfpEventHandler):
|
|
"""Implements event handlers and their helper methods.
|
|
|
|
Object of this class is registered with the event class of core service
|
|
controller. Based on the event key, handle_event method of this class is
|
|
invoked by core service controller.
|
|
"""
|
|
|
|
def __init__(self, sc, drivers, rpcmgr):
|
|
super(GenericConfigEventHandler, self).__init__(
|
|
sc, drivers, rpcmgr)
|
|
self.sc = sc
|
|
|
|
def _get_driver(self, service_type, service_vendor, service_feature):
|
|
"""Retrieves service driver object based on service type input.
|
|
|
|
Currently, service drivers are identified with service type. Support
|
|
for single driver per service type is provided. When multi-vendor
|
|
support is going to be provided, the driver should be selected based
|
|
on both service type and vendor name.
|
|
|
|
:param service_type: Service type - firewall/vpn/loadbalancer
|
|
|
|
Returns: Service driver instance
|
|
|
|
"""
|
|
|
|
return self.drivers[service_type + service_vendor + service_feature]
|
|
|
|
def handle_event(self, ev):
|
|
"""Processes the generated events in worker context.
|
|
|
|
Processes the following events.
|
|
- Configure Interfaces
|
|
- Clear Interfaces
|
|
- Configure routes
|
|
- Clear routes
|
|
- Configure health monitor
|
|
- Clear health monitor
|
|
Enqueues responses into notification queue.
|
|
|
|
Returns: None
|
|
|
|
"""
|
|
try:
|
|
event_data = ev.data
|
|
if ev.id == 'PROCESS_BATCH':
|
|
NFI = event_data['sa_req_list'][0][
|
|
'agent_info']['context']['nfi_id']
|
|
NF = event_data['sa_req_list'][0][
|
|
'agent_info']['context']['nf_id']
|
|
else:
|
|
NFI = event_data['context']['context']['nfi_id']
|
|
NF = event_data['context']['context']['nf_id']
|
|
except Exception:
|
|
NFI = None
|
|
NF = None
|
|
|
|
msg = ("Handling event '%s', with NF:%s and NFI:%s"
|
|
% (ev.id, NF, NFI))
|
|
LOG.info(msg)
|
|
|
|
# Process batch of request data blobs
|
|
try:
|
|
# Process batch of request data blobs
|
|
if ev.id == common_const.EVENT_PROCESS_BATCH:
|
|
self.process_batch(ev)
|
|
return
|
|
# Process HM poll events
|
|
elif ev.id == gen_cfg_const.EVENT_CONFIGURE_HEALTHMONITOR:
|
|
resource_data = ev.data.get('resource_data')
|
|
periodicity = resource_data['nfds'][0]['periodicity']
|
|
EV_CONF_HM_MAXRETRY = (
|
|
gen_cfg_const.EVENT_CONFIGURE_HEALTHMONITOR_MAXRETRY)
|
|
if periodicity == gen_cfg_const.INITIAL:
|
|
self.sc.poll_event(
|
|
ev,
|
|
max_times=EV_CONF_HM_MAXRETRY)
|
|
|
|
elif periodicity == gen_cfg_const.FOREVER:
|
|
self.sc.poll_event(ev)
|
|
else:
|
|
self._process_event(ev)
|
|
except Exception as err:
|
|
msg = ("Failed to process event %s, reason %s "
|
|
% (ev.data, err))
|
|
LOG.error(msg)
|
|
return
|
|
|
|
def send_periodic_hm_notification(self, ev, nfd, result, notification_id):
|
|
ev_copy = copy.deepcopy(ev)
|
|
ev_copy.data["context"]["notification_data"] = {}
|
|
ev_copy.data["context"]["context"]["nfp_context"]["id"] = (
|
|
notification_id)
|
|
ev_copy.data['context']['context']['nfd_id'] = nfd.get('vmid')
|
|
notification_data = self._prepare_notification_data(ev_copy, result)
|
|
self.notify._notification(notification_data)
|
|
|
|
def handle_periodic_hm(self, ev, result):
|
|
resource_data = ev.data['resource_data']
|
|
nfd = ev.data["resource_data"]['nfds'][0]
|
|
periodic_polling_reason = nfd["periodic_polling_reason"]
|
|
if result == common_const.FAILED:
|
|
"""If health monitoring fails continuously for MAX_FAIL_COUNT times
|
|
send fail notification to orchestrator
|
|
"""
|
|
resource_data['fail_count'] = resource_data.get('fail_count') + 1
|
|
if (resource_data.get('fail_count') >=
|
|
gen_cfg_const.MAX_FAIL_COUNT):
|
|
# REVISIT(Shishir): Remove statefull logic from here,
|
|
# need to come up with statleless logic.
|
|
if periodic_polling_reason == (
|
|
gen_cfg_const.DEVICE_TO_BECOME_DOWN):
|
|
notification_id = gen_cfg_const.DEVICE_NOT_REACHABLE
|
|
self.send_periodic_hm_notification(ev, nfd, result,
|
|
notification_id)
|
|
nfd["periodic_polling_reason"] = (
|
|
gen_cfg_const.DEVICE_TO_BECOME_UP)
|
|
elif result == common_const.SUCCESS:
|
|
"""set fail_count to 0 if it had failed earlier even once
|
|
"""
|
|
resource_data['fail_count'] = 0
|
|
if periodic_polling_reason == gen_cfg_const.DEVICE_TO_BECOME_UP:
|
|
notification_id = gen_cfg_const.DEVICE_REACHABLE
|
|
self.send_periodic_hm_notification(ev, nfd, result,
|
|
notification_id)
|
|
nfd["periodic_polling_reason"] = (
|
|
gen_cfg_const.DEVICE_TO_BECOME_DOWN)
|
|
|
|
def _process_event(self, ev):
|
|
LOG.debug(" Handling event %s ", (ev.data))
|
|
# Process single request data blob
|
|
resource_data = ev.data['resource_data']
|
|
# The context inside ev.data is the agent info dictionary prepared
|
|
# in demuxer library which contains the API context alongside
|
|
# other information like service vendor, type etc..
|
|
agent_info = ev.data['context']
|
|
context = agent_info['context']
|
|
service_type = agent_info['resource_type']
|
|
service_vendor = agent_info['service_vendor']
|
|
service_feature = agent_info.get('service_feature', '')
|
|
|
|
try:
|
|
msg = ("Worker process with ID: %s starting "
|
|
"to handle task: %s for service type: %s. "
|
|
% (os.getpid(), ev.id, str(service_type)))
|
|
LOG.debug(msg)
|
|
|
|
driver = self._get_driver(service_type, service_vendor,
|
|
service_feature)
|
|
|
|
# Invoke service driver methods based on event type received
|
|
result = getattr(driver, "%s" % ev.id.lower())(context,
|
|
resource_data)
|
|
except Exception as err:
|
|
msg = ("Failed to process ev.id=%s, ev=%s reason=%s" %
|
|
(ev.id, ev.data, err))
|
|
LOG.error(msg)
|
|
result = common_const.FAILED
|
|
|
|
if ev.id == gen_cfg_const.EVENT_CONFIGURE_HEALTHMONITOR:
|
|
if (resource_data['nfds'][0][
|
|
'periodicity'] == gen_cfg_const.INITIAL and
|
|
result == common_const.SUCCESS):
|
|
notification_data = self._prepare_notification_data(ev,
|
|
result)
|
|
self.notify._notification(notification_data)
|
|
msg = ("VM Health check successful")
|
|
LOG.info(msg)
|
|
return {'poll': False}
|
|
elif resource_data['nfds'][0]['periodicity'] == (
|
|
gen_cfg_const.FOREVER):
|
|
ev.data["context"]["resource"] = gen_cfg_const.PERIODIC_HM
|
|
self.handle_periodic_hm(ev, result)
|
|
else:
|
|
"""For other events, irrespective of result send notification"""
|
|
notification_data = self._prepare_notification_data(ev, result)
|
|
self.notify._notification(notification_data)
|
|
|
|
def prepare_notification_result(self, result):
|
|
if result in common_const.SUCCESS:
|
|
data = {'status_code': common_const.SUCCESS}
|
|
else:
|
|
data = {'status_code': common_const.FAILURE,
|
|
'error_msg': result}
|
|
return data
|
|
|
|
def _prepare_notification_data(self, ev, result):
|
|
"""Prepare notification data as expected by config agent
|
|
|
|
:param ev: event object
|
|
:param result: result of the handled event
|
|
|
|
Returns: notification_data
|
|
|
|
"""
|
|
agent_info = ev.data['context']
|
|
context = agent_info['context']
|
|
|
|
# Retrieve notification and remove it from context. Context is used
|
|
# as transport from batch processing function to this last event
|
|
# processing function. To keep the context unchanged, delete the
|
|
# notification_data before invoking driver API.
|
|
notification_data = agent_info['notification_data']
|
|
service_type = agent_info['resource_type']
|
|
resource = agent_info['resource']
|
|
|
|
data = self.prepare_notification_result(result)
|
|
msg = {'info': {'service_type': service_type,
|
|
'context': context},
|
|
'notification': [{'resource': resource,
|
|
'data': data}]
|
|
}
|
|
if not notification_data:
|
|
notification_data.update(msg)
|
|
else:
|
|
data = {'resource': resource,
|
|
'data': data}
|
|
notification_data['notification'].append(data)
|
|
return notification_data
|
|
|
|
def event_cancelled(self, ev, reason):
|
|
"""Invoked by process framework when poll ev object reaches
|
|
polling threshold ev.max_times.
|
|
Finally it Enqueues response into notification queue.
|
|
|
|
:param ev: Event object
|
|
|
|
Returns: None
|
|
|
|
"""
|
|
msg = ('Cancelled poll event. Event Data: %s ' % (ev.data))
|
|
LOG.error(msg)
|
|
result = common_const.FAILED
|
|
notification_data = self._prepare_notification_data(ev, result)
|
|
self.notify._notification(notification_data)
|
|
|
|
@nfp_api.poll_event_desc(
|
|
event=gen_cfg_const.EVENT_CONFIGURE_HEALTHMONITOR,
|
|
spacing=gen_cfg_const.EVENT_CONFIGURE_HEALTHMONITOR_SPACING)
|
|
def handle_configure_healthmonitor(self, ev):
|
|
"""Decorator method called for poll event CONFIGURE_HEALTHMONITOR
|
|
Finally it Enqueues response into notification queue.
|
|
|
|
:param ev: Event object
|
|
|
|
Returns: None
|
|
|
|
"""
|
|
return self._process_event(ev)
|
|
|
|
|
|
def events_init(sc, drivers, rpcmgr):
|
|
"""Registers events with core service controller.
|
|
|
|
All the events will come to handle_event method of class instance
|
|
registered in 'handler' field.
|
|
|
|
:param drivers: Driver instances registered with the service agent
|
|
:param rpcmgr: Instance to receive all the RPC messages from configurator
|
|
module.
|
|
|
|
Returns: None
|
|
|
|
"""
|
|
|
|
event_id_list = [
|
|
gen_cfg_const.EVENT_CONFIGURE_INTERFACES,
|
|
gen_cfg_const.EVENT_CLEAR_INTERFACES,
|
|
gen_cfg_const.EVENT_CONFIGURE_ROUTES,
|
|
gen_cfg_const.EVENT_CLEAR_ROUTES,
|
|
gen_cfg_const.EVENT_CONFIGURE_HEALTHMONITOR,
|
|
gen_cfg_const.EVENT_CLEAR_HEALTHMONITOR,
|
|
common_const.EVENT_PROCESS_BATCH
|
|
]
|
|
events = []
|
|
|
|
for event in event_id_list:
|
|
events.append(
|
|
nfp_event.Event(
|
|
id=event,
|
|
handler=GenericConfigEventHandler(sc, drivers, rpcmgr)))
|
|
|
|
sc.register_events(events)
|
|
|
|
|
|
def load_drivers(conf):
|
|
"""Imports all the driver files.
|
|
|
|
Returns: Dictionary of driver objects with a specified service type and
|
|
vendor name
|
|
|
|
"""
|
|
|
|
cutils = utils.ConfiguratorUtils(conf)
|
|
drivers = cutils.load_drivers()
|
|
|
|
for service_type, driver_name in six.iteritems(drivers):
|
|
driver_obj = driver_name(conf=conf)
|
|
drivers[service_type] = driver_obj
|
|
LOG.info("Generic config agent loaded drivers drivers:"
|
|
"%(drivers)s",
|
|
{'drivers': drivers})
|
|
return drivers
|
|
|
|
|
|
def register_service_agent(cm, sc, conf, rpcmgr):
|
|
"""Registers generic configuration service agent with configurator module.
|
|
|
|
:param cm: Instance of configurator module
|
|
:param sc: Instance of core service controller
|
|
:param conf: Instance of oslo configuration
|
|
:param rpcmgr: Instance containing RPC methods which are invoked by
|
|
configurator module on corresponding RPC message arrival
|
|
|
|
"""
|
|
|
|
service_type = gen_cfg_const.SERVICE_TYPE
|
|
cm.register_service_agent(service_type, rpcmgr)
|
|
|
|
|
|
def init_agent(cm, sc, conf):
|
|
"""Initializes generic configuration agent.
|
|
|
|
:param cm: Instance of configuration module
|
|
:param sc: Instance of core service controller
|
|
:param conf: Instance of oslo configuration
|
|
|
|
"""
|
|
|
|
try:
|
|
drivers = load_drivers(conf)
|
|
except Exception as err:
|
|
msg = ("Generic configuration agent failed to load service drivers."
|
|
"Error:%s"
|
|
% (str(err).capitalize()))
|
|
LOG.error(msg)
|
|
raise err
|
|
else:
|
|
msg = ("Generic configuration agent loaded service"
|
|
" drivers successfully.")
|
|
LOG.debug(msg)
|
|
|
|
rpcmgr = GenericConfigRpcManager(sc, conf)
|
|
|
|
try:
|
|
events_init(sc, drivers, rpcmgr)
|
|
except Exception as err:
|
|
msg = ("Generic configuration agent failed to initialize events. %s"
|
|
% (str(err).capitalize()))
|
|
LOG.error(msg)
|
|
raise err
|
|
else:
|
|
msg = ("Generic configuration agent initialized"
|
|
" events successfully.")
|
|
LOG.debug(msg)
|
|
|
|
try:
|
|
register_service_agent(cm, sc, conf, rpcmgr)
|
|
except Exception as err:
|
|
msg = ("Failed to register generic configuration agent with"
|
|
" configurator module. %s" % (str(err).capitalize()))
|
|
LOG.error(msg)
|
|
raise err
|
|
else:
|
|
msg = ("Generic configuration agent registered with configuration"
|
|
" module successfully.")
|
|
LOG.debug(msg)
|
|
|
|
|
|
def init_agent_complete(cm, sc, conf):
|
|
msg = ("Initialization of generic configuration agent completed.")
|
|
LOG.info(msg)
|