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

281 lines
11 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.
from neutron_lib import rpc as n_rpc
from oslo_config import cfg
import oslo_messaging as messaging
from gbpservice.contrib.nfp.configurator.lib import constants as const
from gbpservice.nfp.core import log as nfp_logging
from gbpservice.nfp.core import module as nfp_api
n_rpc.init(cfg.CONF)
LOG = nfp_logging.getLogger(__name__)
class AgentBaseRPCManager(object):
"""Implements base class for all service agents.
Common methods for service agents are implemented in this class.
Configurator module invokes these methods through the service
agent's child class instance.
"""
def __init__(self, sc, conf):
self.sc = sc
self.conf = conf
def validate_request(self, sa_req_list, notification_data):
"""Preliminary validation of function input.
:param sa_req_list: List of data blobs prepared by de-multiplexer
for service agents processing.
:param notification_data: Notification blobs prepared by the service
agents after processing requests blobs. Each request blob will have
a corresponding notification blob.
Returns: True if validation passes. False if validation fails.
"""
if (isinstance(sa_req_list, list) and
isinstance(notification_data, dict)):
return True
else:
return False
def get_diff_of_dict(self, old_dict, new_dict):
"""Getting difference between two dict.
:param Two dictionary
Returns: Two dictionary which has different values for same keys.
"""
diff_values = []
new_val = {}
old_val = {}
for key in new_dict:
if old_dict.get(key) != new_dict.get(key):
diff_values.append(key)
for value in diff_values:
if value == 'description':
pass
else:
new_val[value] = new_dict.get(value)
old_val[value] = old_dict.get(value)
return old_val, new_val
def process_request(self, sa_req_list, notification_data):
"""Forwards the RPC message from configurator to service agents.
Checks if the request message contains multiple data blobs. If multiple
data blobs are found, a batch event is generated otherwise a single
event.
:param sa_req_list: List of data blobs prepared by de-multiplexer
for service agents processing.
:param notification_data: Notification blobs prepared by the service
agents after processing requests blobs. Each request blob will have
a corresponding notification blob.
Returns: None
"""
# In case of malformed input, send failure notification
if not self.validate_request(sa_req_list, notification_data):
# REVISIT(JAGADISH): Need to send failure notification
return
# Multiple request data blobs needs batch processing. Send batch
# processing event or do direct processing of single request data blob
if (len(sa_req_list) > 1):
LOG.info("Creating event PROCESS BATCH")
args_dict = {
'sa_req_list': sa_req_list,
'notification_data': notification_data
}
ev = self.sc.new_event(id=const.EVENT_PROCESS_BATCH,
data=args_dict, key=None)
self.sc.post_event(ev)
else:
agent_info = sa_req_list[0]['agent_info']
# Renaming the neutron context in resource data of *aaS to context.
# Adding agent_info which contains information required for
# demux and response data in agent to neutron_context in *aaS
if not sa_req_list[0]['is_generic_config'] and not (
agent_info['resource'] in const.NFP_SERVICE_LIST):
# Here, the neutron context is overloaded with agent_info
# dict which contains the API context in addition to other
# fields like service type, service vendor, resource etc.
# The agent_info dict is constructed inside the demuxer library
sa_req_list[0]['resource_data']['neutron_context'].update(
{'agent_info': agent_info})
# When calling the *aaS or NFPService agents, the
# "neutron context" passed inside the resource data is
# renamed to "context"
sa_req_list[0]['resource_data']['context'] = sa_req_list[0][
'resource_data'].pop('neutron_context')
getattr(self, sa_req_list[0]['method'])(
**sa_req_list[0]['resource_data'])
else:
sa_req_list[0]['agent_info'].update(
{'notification_data': notification_data})
getattr(self, sa_req_list[0]['method'])(
agent_info, sa_req_list[0]['resource_data'])
class AgentBaseNotification(object):
"""Enqueues notification event into notification queue
Responses from the REST calls made to the VM are fed to under the
cloud components using this notification handle.
"""
API_VERSION = '1.0'
def __init__(self, sc):
self.sc = sc
self.topic = const.NOTIFICATION_QUEUE
target = messaging.Target(topic=self.topic,
version=self.API_VERSION)
self.client = n_rpc.get_client(target)
self.cctxt = self.client.prepare(version=self.API_VERSION,
topic=self.topic)
def _notification(self, data):
"""Enqueues notification event into const.NOTIFICATION_QUEUE
These events are enqueued into notification queue and are retrieved
when get_notifications() API lands on configurator.
:param data: Event data blob
Returns: None
"""
self.cctxt.cast(self, 'send_notification', notification_data=[data])
def to_dict(self):
return {}
class AgentBaseEventHandler(nfp_api.NfpEventHandler):
""" Super class for all agents to handle batch events.
"""
def __init__(self, sc, drivers, rpcmgr):
self.sc = sc
self.drivers = drivers
self.rpcmgr = rpcmgr
self.notify = AgentBaseNotification(self.sc)
def process_batch(self, ev):
"""Processes a request with multiple data blobs.
Configurator processes the request with multiple data blobs and sends
a list of service information to be processed. This function goes
through the list of service information and invokes specific service
driver methods. After processing each request data blob, notification
data blob is prepared.
:param ev: Event instance that contains information of event type and
corresponding event data to be processed.
"""
# Get service agent information list and notification data list
# from the event data
sa_req_list = ev.data.get('sa_req_list')
notification_data = ev.data.get('notification_data')
for request in sa_req_list:
try:
# Process the first data blob from the request list.
# Get necessary parameters needed for driver method invocation.
method = request['method']
is_generic_config = request['is_generic_config']
resource_data = request['resource_data']
agent_info = request['agent_info']
resource = agent_info['resource']
# agent_info contains the API context.
context = agent_info['context']
service_vendor = agent_info['service_vendor']
service_type = agent_info['resource_type']
service_feature = agent_info['service_feature']
if not is_generic_config:
sa_req_list[0]['resource_data']['context'] = sa_req_list[
0]['resource_data'].pop('neutron_context')
# Get the service driver and invoke its method
driver = self._get_driver(service_type, service_vendor,
service_feature)
# Service driver should return "success" on successful API
# processing. All other return values and exceptions are
# treated as failures.
if is_generic_config:
result = getattr(driver, method)(context, resource_data)
else:
result = getattr(driver, method)(**resource_data)
success = True if result == 'SUCCESS' else False
except Exception as err:
result = ("Failed to process %s request. %s" %
(method, str(err).capitalize()))
success = False
finally:
# Prepare success notification and populate notification
# data list
if result in const.SUCCESS:
data = {'status_code': const.SUCCESS}
else:
data = {'status_code': const.FAILURE,
'error_msg': result}
msg = {'info': {'service_type': service_type,
'context': context},
'notification': [{'resource': resource,
'data': data}]
}
# If the data processed is first one, then prepare notification
# dict. Otherwise, append the notification to the kwargs list.
# Whether it is a data batch or single data blob request,
# notification generated will be single dictionary. In case of
# batch, multiple notifications are sent in the kwargs list.
if not notification_data:
notification_data.update(msg)
else:
data = {'resource': resource,
'data': data}
notification_data['notification'].append(data)
if not success:
self.notify._notification(notification_data)
raise Exception(msg)
self.notify._notification(notification_data)
def init_agent_complete(cm, sc, conf):
"""Placeholder method to satisfy configurator module agent loading."""
pass
def init_agent(cm, sc, conf):
"""Placeholder method to satisfy configurator module agent loading."""
pass