220 lines
7.8 KiB
Python
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
|