group-based-policy/gbpservice/contrib/nfp/configurator/agents/generic_config.py

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)