281 lines
11 KiB
Python
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
|