group-based-policy/gbpservice/nfp/base_configurator/controllers/controller.py

220 lines
7.8 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 subprocess
from subprocess import CalledProcessError
import time
from oslo_log import log as logging
import oslo_serialization.jsonutils as jsonutils
import pecan
import requests
from gbpservice._i18n import _
from gbpservice.nfp.pecan import base_controller
LOG = logging.getLogger(__name__)
TOPIC = 'configurator'
NFP_SERVICE_LIST = ['heat', 'ansible']
SUCCESS_RESULTS = ['unhandled', 'success']
FAILURE = 'failure'
notifications = []
cache_ips = set()
class Controller(base_controller.BaseController):
"""Implements all the APIs Invoked by HTTP requests.
Implements following HTTP methods.
-get
-post
"""
def __init__(self, method_name):
try:
self.method_name = method_name
super(Controller, self).__init__()
except Exception as err:
msg = (
"Failed to initialize Controller class %s." %
str(err).capitalize())
LOG.error(msg)
self.vm_port = '8080'
self.max_retries = 60
def _push_notification(self, context, result, config_data, service_type):
global notifications
resource = config_data['resource']
if result.lower() in SUCCESS_RESULTS:
data = {'status_code': result}
else:
data = {'status_code': FAILURE,
'error_msg': result}
response = {'info': {'service_type': service_type,
'context': context},
'notification': [{
'resource': resource,
'data': data}]
}
notifications.append(response)
def _verify_vm_reachability(self, vm_ip, vm_port):
reachable = False
command = 'nc ' + vm_ip + ' ' + vm_port + ' -z'
ping_command = 'ping -c1 ' + vm_ip
for x in range(self.max_retries):
try:
subprocess.check_output(ping_command, stderr=subprocess.STDOUT,
shell=True)
subprocess.check_output(command, stderr=subprocess.STDOUT,
shell=True)
reachable = True
break
except CalledProcessError as err:
msg = ("Exception: %s " % err)
LOG.error(msg)
time.sleep(5)
except Exception:
time.sleep(5)
return reachable
@pecan.expose(method='GET', content_type='application/json')
def get(self):
"""Method of REST server to handle request get_notifications.
This method send an RPC call to configurator and returns Notification
data to config-agent
Returns: Dictionary that contains Notification data
"""
global cache_ips
global notifications
try:
if not cache_ips:
notification_data = jsonutils.dumps(notifications)
msg = ("Notification sent. Notification Data: %s"
% notification_data)
LOG.info(msg)
notifications = []
return notification_data
else:
for ip in cache_ips:
notification_response = requests.get(
'http://' + str(ip) + ':' + self.vm_port +
'/v1/nfp/get_notifications')
notification = jsonutils.loads(notification_response.text)
notifications.extend(notification)
cache_ips.remove(ip)
if ip not in cache_ips:
break
notification_data = jsonutils.dumps(notifications)
msg = ("Notification sent. Notification Data: %s"
% notification_data)
LOG.info(msg)
notifications = []
return notification_data
except Exception as err:
pecan.response.status = 400
msg = ("Failed to get notification_data %s."
% str(err).capitalize())
LOG.error(msg)
error_data = self._format_description(msg)
return jsonutils.dumps(error_data)
@pecan.expose(method='POST', content_type='application/json')
def post(self, **body):
"""Method of REST server to handle all the post requests.
This method sends an RPC cast to configurator according to the
HTTP request.
:param body: This method excepts dictionary as a parameter in HTTP
request and send this dictionary to configurator with RPC cast.
Returns: None
"""
try:
global cache_ips
global notifications
body = None
if pecan.request.is_body_readable:
body = pecan.request.json_body
# Assuming config list will have only one element
config_data = body['config'][0]
info_data = body['info']
context = info_data['context']
service_type = info_data['service_type']
resource = config_data['resource']
operation = context['operation']
msg1 = ("Request recieved :: %s" % body)
LOG.info(msg1)
if 'device_ip' in context:
msg3 = ("POSTING DATA TO VM :: %s" % body)
LOG.info(msg3)
device_ip = context['device_ip']
ip = str(device_ip)
if operation == 'delete':
return
msg5 = ("Verifying vm reachability on ip: %s, port: %s" % (
ip, self.vm_port))
LOG.info(msg5)
is_vm_reachable = self._verify_vm_reachability(ip,
self.vm_port)
if is_vm_reachable:
requests.post(
'http://' + ip + ':' + self.vm_port + '/v1/nfp/' +
self.method_name, data=jsonutils.dumps(body))
msg4 = ("requests successfull for data: %s" % body)
LOG.info(msg4)
else:
raise Exception(_('VM is not reachable'))
cache_ips.add(device_ip)
else:
if (resource in NFP_SERVICE_LIST):
result = "unhandled"
self._push_notification(context,
result, config_data, service_type)
else:
result = "Unsupported resource type"
self._push_notification(context,
result, config_data, service_type)
except Exception as err:
pecan.response.status = 400
msg = ("Failed to serve HTTP post request %s %s."
% (self.method_name, str(err).capitalize()))
LOG.error(msg)
error_data = self._format_description(msg)
return jsonutils.dumps(error_data)
def _format_description(self, msg):
"""This methgod formats error description.
:param msg: An error message that is to be formatted
Returns: error_data dictionary
"""
error_data = {'failure_desc': {'msg': msg}}
return error_data