421 lines
15 KiB
Python
421 lines
15 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
|
|
from datetime import datetime as dt
|
|
import http.server
|
|
import inspect
|
|
import json
|
|
import os
|
|
import ssl
|
|
import threading
|
|
import time
|
|
from urllib.parse import urlparse
|
|
|
|
from oslo_log import log as logging
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
|
|
|
|
def PrepareRequestHandler(manager):
|
|
class DummyRequestHandler(http.server.CGIHTTPRequestHandler):
|
|
"""HTTP request handler for dummy server."""
|
|
|
|
def __init__(self, request, client_address, server):
|
|
super().__init__(request, client_address, server)
|
|
return
|
|
|
|
def _is_match_with_list(self):
|
|
"""Return given path is listed in dictionary or not.
|
|
|
|
Return:
|
|
True/False
|
|
"""
|
|
func_uri_list = manager._methods[self.command]
|
|
for objChkUrl in func_uri_list:
|
|
# Check which requested path is in our list.
|
|
LOG.debug('path for check:%s' % objChkUrl)
|
|
if(self.path.startswith(objChkUrl)):
|
|
return True
|
|
return False
|
|
|
|
def _returned_callback(self, path, mock_info):
|
|
"""Send responses to client. Called in do_* methods.
|
|
|
|
This method do not handle message when error is occured.
|
|
|
|
Args:
|
|
path (str): URI path
|
|
mock_info (tuple): callback informations from caller.
|
|
"""
|
|
request_headers = dict(self.headers._headers)
|
|
request_body = self._parse_request_body()
|
|
response_body_str = b''
|
|
|
|
(status_code, mock_headers, mock_body) = self._get_mock_info(
|
|
mock_info, request_headers, request_body)
|
|
self.send_response(status_code)
|
|
|
|
# Check what I should return to client ?
|
|
if mock_info.get('content') is not None:
|
|
response_body_str = open(mock_info.get('content'), 'rb').read()
|
|
elif len(mock_body) > 0:
|
|
response_body_str = json.dumps(mock_body).encode('utf-8')
|
|
if '/token' in self.path or 'v2' in self.path or (
|
|
isinstance(mock_body, dict) and
|
|
'v2' in mock_body.get('_links', {}).get(
|
|
'vnfLcmOpOcc', {}).get('href')):
|
|
pass
|
|
else:
|
|
mock_headers['Content-Length'] = str(len(
|
|
response_body_str))
|
|
|
|
# Send custom header if exist
|
|
for key, val in mock_headers.items():
|
|
self.send_header(key, val)
|
|
self.end_headers()
|
|
|
|
if len(response_body_str) > 0:
|
|
self.wfile.write(response_body_str)
|
|
|
|
manager.add_history(path, RequestHistory(
|
|
status_code=status_code,
|
|
request_headers=request_headers,
|
|
request_body=request_body,
|
|
response_headers=copy.deepcopy(mock_headers),
|
|
response_body=copy.deepcopy(mock_body)))
|
|
|
|
if mock_info.get('content') is None and 'v2/grants' not in path:
|
|
self.end_headers()
|
|
|
|
def _parse_request_body(self):
|
|
if '/token' in self.path:
|
|
return {}
|
|
|
|
if 'content-length' not in self.headers:
|
|
return {}
|
|
|
|
request_content_len = int(self.headers.get('content-length'))
|
|
if request_content_len == 0:
|
|
return {}
|
|
|
|
decode_request_body = self.rfile.read(
|
|
request_content_len).decode('utf-8')
|
|
|
|
return json.loads(decode_request_body)
|
|
|
|
def _get_mock_info(self, mock_info, request_headers, request_body):
|
|
"""Call mock(callback) and get responses
|
|
|
|
This method is called from _returned_callback().
|
|
|
|
Args:
|
|
mock_info (tuple): callback informations from caller.
|
|
request_headers (dict): Request headers
|
|
request_body (dict): Request Bodies
|
|
|
|
Returns:
|
|
(tuple): status_code, response headers, response bodies.
|
|
response body will be converted into JSON string
|
|
with json.dumps().
|
|
"""
|
|
# Prepare response contents
|
|
func = mock_info.get('callback')
|
|
status_code = mock_info.get('status_code')
|
|
mock_headers = mock_info.get('response_headers')
|
|
mock_body = mock_info.get('response_body')
|
|
|
|
# Call function if callable.
|
|
if callable(func):
|
|
mock_body = func(request_headers, request_body)
|
|
|
|
return (status_code, mock_headers, mock_body)
|
|
|
|
def do_DELETE(self):
|
|
self.send_response(http.HTTPStatus.NO_CONTENT)
|
|
self.end_headers()
|
|
|
|
def do_GET(self):
|
|
"""Process GET request"""
|
|
LOG.debug('[Start] %s.%s()' % (self.__class__.__name__,
|
|
inspect.currentframe().f_code.co_name))
|
|
|
|
# Check URI in request.
|
|
if self._is_match_with_list():
|
|
# Request is registered in our list.
|
|
tplUri = urlparse(self.path)
|
|
self._returned_callback(tplUri.path,
|
|
manager._funcs_gets[tplUri.path])
|
|
else:
|
|
# Unregistered URI is requested
|
|
LOG.debug('GET Recv. Unknown URL: "%s"' % self.path)
|
|
self.send_response(http.HTTPStatus.BAD_REQUEST)
|
|
self.end_headers()
|
|
|
|
LOG.debug('[ End ] %s.%s()' %
|
|
(self.__class__.__name__,
|
|
inspect.currentframe().f_code.co_name))
|
|
|
|
def do_POST(self):
|
|
"""Process POST request"""
|
|
LOG.debug(
|
|
'[Start] %s.%s()' %
|
|
(self.__class__.__name__,
|
|
inspect.currentframe().f_code.co_name))
|
|
|
|
# URI might have trailing uuid or not.
|
|
if self._is_match_with_list():
|
|
# Request is registered in our list.
|
|
tplUri = urlparse(self.path)
|
|
if self.path.startswith('/server_notification'):
|
|
for key in manager._funcs_posts.keys():
|
|
if self.path.startswith(key):
|
|
self._returned_callback(tplUri.path,
|
|
manager._funcs_posts[key])
|
|
else:
|
|
self._returned_callback(tplUri.path,
|
|
manager._funcs_posts[tplUri.path])
|
|
else:
|
|
# Unregistered URI is requested
|
|
LOG.debug('POST Recv. Unknown URL: "%s"' % self.path)
|
|
self.send_response(http.HTTPStatus.BAD_REQUEST)
|
|
self.end_headers()
|
|
|
|
LOG.debug(
|
|
'[ End ] %s.%s()' %
|
|
(self.__class__.__name__,
|
|
inspect.currentframe().f_code.co_name))
|
|
|
|
def do_PUT(self):
|
|
"""Process PUT request"""
|
|
LOG.debug(
|
|
'[Start] %s.%s()' %
|
|
(self.__class__.__name__,
|
|
inspect.currentframe().f_code.co_name))
|
|
|
|
# URI might have trailing uuid or not.
|
|
if self._is_match_with_list():
|
|
# Request is registered in our list.
|
|
tplUri = urlparse(self.path)
|
|
self._returned_callback(tplUri.path,
|
|
manager._funcs_puts[tplUri.path])
|
|
else:
|
|
# Unregistered URI is requested
|
|
LOG.debug('PUT Recv. Unknown URL: "%s"' % self.path)
|
|
self.send_response(http.HTTPStatus.BAD_REQUEST)
|
|
self.end_headers()
|
|
|
|
LOG.debug(
|
|
'[ End ] %s.%s()' %
|
|
(self.__class__.__name__,
|
|
inspect.currentframe().f_code.co_name))
|
|
|
|
return DummyRequestHandler
|
|
|
|
|
|
class RequestHistory:
|
|
"""Storage class for storing requested data(Maybe POSTed datas)."""
|
|
|
|
def __init__(
|
|
self,
|
|
status_code,
|
|
request_headers=None,
|
|
request_body=None,
|
|
response_headers=None,
|
|
response_body=None):
|
|
self.timestamp = dt.now()
|
|
self.status_code = status_code
|
|
self.request_headers = request_headers
|
|
self.request_body = request_body
|
|
self.response_headers = response_headers
|
|
self.response_body = response_body
|
|
|
|
|
|
class FakeServerManager(object):
|
|
"""Manager class to manage dummy server setting and control"""
|
|
|
|
SERVER_PORT = 9990
|
|
SERVER_PORT_T1 = 9995
|
|
SERVER_PORT_T2 = 9996
|
|
SERVER_INVOKE_CHECK_INTERVAL = 10
|
|
|
|
def __init__(self):
|
|
# Initialize class-specific variables.
|
|
|
|
# Storage for request header/body and response header/body
|
|
# history (dict) is updated using RequestHistory class.
|
|
self._history = {}
|
|
|
|
# Initialize function list for each request method.
|
|
# DELETE/PUT method is listed but not supported currently.
|
|
self._funcs_deletes = {}
|
|
self._funcs_gets = {}
|
|
self._funcs_posts = {}
|
|
self._funcs_puts = {}
|
|
self._methods = {
|
|
'DELETE': self._funcs_deletes,
|
|
'GET': self._funcs_gets,
|
|
'POST': self._funcs_posts,
|
|
'PUT': self._funcs_puts}
|
|
|
|
def set_callback(
|
|
self,
|
|
method,
|
|
uri,
|
|
status_code=None,
|
|
response_headers=None,
|
|
response_body=None,
|
|
content=None,
|
|
callback=None):
|
|
"""Set callback function and some stuff for specified URI.
|
|
|
|
ALL additional parameter is set default to None, so you have to
|
|
specify what your callback-function need. response_header and
|
|
response_body will be passed to callback.
|
|
|
|
Args:
|
|
method (str): Reqested method
|
|
uri (str): Requested URI
|
|
status_code (http.HTTPStatus): HTTP status code and
|
|
reason phrase.
|
|
response_headers (dict): Addtional response header.
|
|
response_body (dict): Response body. Must be Jason Bourne
|
|
content (str): File path that you want client to download.
|
|
callback (callable): Callback function. Must return serializable
|
|
object json.dumps() can handle.
|
|
"""
|
|
callbacks = self._methods[method]
|
|
callbacks[uri] = {
|
|
'status_code': status_code or http.HTTPStatus.OK,
|
|
'response_headers': response_headers or {},
|
|
'response_body': response_body or {},
|
|
'content': content,
|
|
'callback': callback,
|
|
}
|
|
self._methods[method].update(callbacks)
|
|
|
|
# Check file existence for content
|
|
if content is not None:
|
|
if not os.path.isfile(content):
|
|
raise FileNotFoundError
|
|
|
|
LOG.debug('Set callback for %s(%s): %s' %
|
|
(method, uri, callback))
|
|
|
|
def add_history(self, path, history):
|
|
"""Add Request/Response header/body to history.
|
|
|
|
This method maybe called in DummyRequestHandler._returned_callback()
|
|
only. This method should not be called from outside of This class.
|
|
|
|
Args:
|
|
path (str): URI path
|
|
history (RequestHistory): Storage container for each request.
|
|
"""
|
|
if path in self._history:
|
|
self._history[path].append(history)
|
|
else:
|
|
self._history[path] = [history]
|
|
|
|
def clear_history(self, path=None):
|
|
"""Clear Request/Response header/body of history.
|
|
|
|
Args:
|
|
path (str): URI path
|
|
"""
|
|
if not path:
|
|
self._history = {}
|
|
return
|
|
|
|
if path in self._history:
|
|
self._history.pop(path)
|
|
|
|
def get_history(self, path=None):
|
|
"""Get Request/Response header/body from history.
|
|
|
|
Args:
|
|
path (str): URI path
|
|
|
|
Returns:
|
|
history list(RequestHistory): Storage container for each request.
|
|
"""
|
|
history = copy.deepcopy(self._history)
|
|
|
|
if not path:
|
|
return history
|
|
|
|
return history.get(path) or []
|
|
|
|
def prepare_http_server(
|
|
self,
|
|
address="localhost",
|
|
port=SERVER_PORT):
|
|
"""Set up HTTPd server your behalf.
|
|
|
|
Args:
|
|
address (str): bind address for listen
|
|
port (int): por number for listen
|
|
"""
|
|
LOG.debug(
|
|
'[Start] %s.%s()' %
|
|
(self.__class__.__name__,
|
|
inspect.currentframe().f_code.co_name))
|
|
while True:
|
|
try:
|
|
RequestHandler = PrepareRequestHandler(self)
|
|
self.objHttpd = http.server.HTTPServer(
|
|
(address, port), RequestHandler)
|
|
except OSError:
|
|
time.sleep(self.SERVER_INVOKE_CHECK_INTERVAL)
|
|
continue
|
|
else:
|
|
break
|
|
self.thread_server = threading.Thread(None, self.run)
|
|
LOG.debug(
|
|
'[ End ] %s.%s()' %
|
|
(self.__class__.__name__,
|
|
inspect.currentframe().f_code.co_name))
|
|
|
|
def set_https_server(self):
|
|
CERTFILE = "/etc/https_server/ssl/https_server.pem"
|
|
context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
|
|
context.load_cert_chain(CERTFILE)
|
|
self.objHttpd.socket = context.wrap_socket(
|
|
self.objHttpd.socket, server_side=True)
|
|
|
|
def start_server(self):
|
|
"""Start server in thread."""
|
|
LOG.debug('[START] %s()' % inspect.currentframe().f_code.co_name)
|
|
self.thread_server.start()
|
|
LOG.debug('[ END ] %s()' % inspect.currentframe().f_code.co_name)
|
|
|
|
def run(self):
|
|
"""HTTPd server runner"""
|
|
LOG.debug('[START] %s()' % inspect.currentframe().f_code.co_name)
|
|
try:
|
|
self.objHttpd.serve_forever()
|
|
except KeyboardInterrupt:
|
|
self.stop_server()
|
|
finally:
|
|
self.objHttpd.server_close()
|
|
LOG.debug('[ END ] %s()' % inspect.currentframe().f_code.co_name)
|
|
|
|
def stop_server(self):
|
|
"""Stop HTTP Server"""
|
|
LOG.debug('[START] %s()' % inspect.currentframe().f_code.co_name)
|
|
thread_shutdown = threading.Thread(None, self.objHttpd.shutdown)
|
|
thread_shutdown.daemon = True
|
|
thread_shutdown.start()
|
|
self.thread_server.join()
|
|
LOG.debug('[ END ] %s()' % inspect.currentframe().f_code.co_name)
|