Remove singleton class in FakeServerManager class
The current fake NFVO server design is based on a singleton class pattern which restricts the instantiation of a class to one server object. Multi-tenant functional test case environment requires multiple fake NFVO servers(one server per tenant). This patch removes the SingletonMixin class and modifies the FakeServerManager class to instantiate multiple server class objects. Implement: blueprint multi-tenant-policy Change-Id: Ia83aad3a8255ea6ee8cddc01e6a75c7e0e7945b8
This commit is contained in:
parent
f55ed58501
commit
1cb068c74a
@ -26,228 +26,162 @@ from oslo_log import log as logging
|
|||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class SingletonMixin:
|
def PrepareRequestHandler(manager):
|
||||||
"""Mixin class to make your class a Singleton class."""
|
class DummyRequestHandler(http.server.CGIHTTPRequestHandler):
|
||||||
|
"""HTTP request handler for dummy server."""
|
||||||
|
|
||||||
_instance = None
|
def __init__(self, request, client_address, server):
|
||||||
_rlock = threading.RLock()
|
super().__init__(request, client_address, server)
|
||||||
_inside_instance = False
|
return
|
||||||
|
|
||||||
@classmethod
|
def _is_match_with_list(self):
|
||||||
def get_instance(cls, *args, **kwargs):
|
"""Return given path is listed in dictionary or not.
|
||||||
"""Get *the* instance of the class, constructed when needed using(kw)args.
|
|
||||||
|
|
||||||
Return the instance of the class. If it did not yet exist, create
|
Return:
|
||||||
it by calling the "constructor" with whatever arguments and keyword
|
True/False
|
||||||
arguments provided.
|
"""
|
||||||
|
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
|
||||||
|
|
||||||
This routine is thread-safe. It uses the *double-checked locking*
|
def _returned_callback(self, path, mock_info):
|
||||||
design pattern ``https://en.wikipedia.org/wiki/Double-checked_locking``
|
"""Send responses to client. Called in do_* methods.
|
||||||
for this.
|
|
||||||
|
|
||||||
:param args: Used for constructing the instance, when not performed
|
This method do not handle message when error is occured.
|
||||||
yet.
|
|
||||||
:param kwargs: Used for constructing the instance, when not
|
|
||||||
perfored yet.
|
|
||||||
:return: An instance of the class.
|
|
||||||
"""
|
|
||||||
if cls._instance is not None:
|
|
||||||
return cls._instance
|
|
||||||
with cls._rlock:
|
|
||||||
# re-check, perhaps it was created in the mean time...
|
|
||||||
if cls._instance is None:
|
|
||||||
cls._inside_instance = True
|
|
||||||
try:
|
|
||||||
cls._instance = cls(*args, **kwargs)
|
|
||||||
finally:
|
|
||||||
cls._inside_instance = False
|
|
||||||
return cls._instance
|
|
||||||
|
|
||||||
def __new__(cls, *args, **kwargs):
|
Args:
|
||||||
"""Raise Exception when not called from the :func:``instance``
|
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''
|
||||||
|
|
||||||
Class method.
|
(status_code, mock_headers, mock_body) = self._get_mock_info(
|
||||||
This method raises RuntimeError when not called from the
|
mock_info, request_headers, request_body)
|
||||||
instance class method.
|
self.send_response(status_code)
|
||||||
|
|
||||||
:param args: Arguments eventually passed to
|
# Check what I should return to client ?
|
||||||
:func:``__init__``_.
|
if mock_info.get('content') is not None:
|
||||||
:param kwargs: Keyword arguments eventually passed to
|
response_body_str = open(mock_info.get('content'), 'rb').read()
|
||||||
:func:``__init__``_
|
elif len(mock_body) > 0:
|
||||||
:return: the created instance.
|
response_body_str = json.dumps(mock_body).encode('utf-8')
|
||||||
"""
|
mock_headers['Content-Length'] = str(len(response_body_str))
|
||||||
if cls is SingletonMixin:
|
|
||||||
raise TypeError(
|
|
||||||
"Attempt to instantiate\
|
|
||||||
mixin class {}".format(cls.__qualname__)
|
|
||||||
)
|
|
||||||
|
|
||||||
if cls._instance is None:
|
# Send custom header if exist
|
||||||
with cls._rlock:
|
for key, val in mock_headers.items():
|
||||||
if cls._instance is None and cls._inside_instance:
|
self.send_header(key, val)
|
||||||
return super().__new__(cls, *args, **kwargs)
|
self.end_headers()
|
||||||
|
|
||||||
raise RuntimeError(
|
if len(response_body_str) > 0:
|
||||||
"Attempt to create a {}\
|
self.wfile.write(response_body_str)
|
||||||
instance outside of instance()".format(cls.__qualname__)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
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)))
|
||||||
|
|
||||||
class DummyRequestHander(http.server.CGIHTTPRequestHandler):
|
if mock_info.get('content') is None:
|
||||||
"""HTTP request handler for dummy server."""
|
self.end_headers()
|
||||||
|
|
||||||
def __init__(self, request, client_address, server):
|
def _parse_request_body(self):
|
||||||
super().__init__(request, client_address, server)
|
if 'content-length' not in self.headers:
|
||||||
return
|
return {}
|
||||||
|
|
||||||
def _is_match_with_list(self):
|
request_content_len = int(self.headers.get('content-length'))
|
||||||
"""Return given path is listed in dictionary or not.
|
if request_content_len == 0:
|
||||||
|
return {}
|
||||||
|
|
||||||
Return:
|
decode_request_body = self.rfile.read(
|
||||||
True/False
|
request_content_len).decode('utf-8')
|
||||||
"""
|
|
||||||
manager = FakeServerManager.get_instance()
|
|
||||||
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
|
return json.loads(decode_request_body)
|
||||||
|
|
||||||
def _returned_callback(self, path, mock_info):
|
def _get_mock_info(self, mock_info, request_headers, request_body):
|
||||||
"""Send responses to client. Called in do_* methods.
|
"""Call mock(callback) and get responses
|
||||||
|
|
||||||
This method do not handle message when error is occured.
|
This method is called from _returned_callback().
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
path (str): URI path
|
mock_info (tuple): callback informations from caller.
|
||||||
mock_info (tuple): callback informations from caller.
|
request_headers (dict): Request headers
|
||||||
"""
|
request_body (dict): Request Bodies
|
||||||
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(
|
Returns:
|
||||||
mock_info, request_headers, request_body)
|
(tuple): status_code, response headers, response bodies.
|
||||||
self.send_response(status_code)
|
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')
|
||||||
|
|
||||||
# Check what I should return to client ?
|
# Call function if callable.
|
||||||
if mock_info.get('content') is not None:
|
if callable(func):
|
||||||
response_body_str = open(mock_info.get('content'), 'rb').read()
|
mock_body = func(request_headers, request_body)
|
||||||
elif len(mock_body) > 0:
|
|
||||||
response_body_str = json.dumps(mock_body).encode('utf-8')
|
|
||||||
mock_headers['Content-Length'] = str(len(response_body_str))
|
|
||||||
|
|
||||||
# Send custom header if exist
|
return (status_code, mock_headers, mock_body)
|
||||||
for key, val in mock_headers.items():
|
|
||||||
self.send_header(key, val)
|
|
||||||
self.end_headers()
|
|
||||||
|
|
||||||
if len(response_body_str) > 0:
|
def do_DELETE(self):
|
||||||
self.wfile.write(response_body_str)
|
raise NotImplementedError
|
||||||
|
|
||||||
FakeServerManager.get_instance().add_history(path, RequestHistory(
|
def do_GET(self):
|
||||||
status_code=status_code,
|
"""Process GET request"""
|
||||||
request_headers=request_headers,
|
LOG.debug('[Start] %s.%s()' % (self.__class__.__name__,
|
||||||
request_body=request_body,
|
inspect.currentframe().f_code.co_name))
|
||||||
response_headers=copy.deepcopy(mock_headers),
|
|
||||||
response_body=copy.deepcopy(mock_body))
|
|
||||||
)
|
|
||||||
|
|
||||||
def _parse_request_body(self):
|
# Check URI in request.
|
||||||
if 'content-length' not in self.headers:
|
if self._is_match_with_list():
|
||||||
return {}
|
# 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()
|
||||||
|
|
||||||
request_content_len = int(self.headers.get('content-length'))
|
LOG.debug('[ End ] %s.%s()' %
|
||||||
if request_content_len == 0:
|
(self.__class__.__name__,
|
||||||
return {}
|
inspect.currentframe().f_code.co_name))
|
||||||
|
|
||||||
decode_request_body = self.rfile.read(
|
def do_POST(self):
|
||||||
request_content_len).decode('utf-8')
|
"""Process POST request"""
|
||||||
|
LOG.debug(
|
||||||
|
'[Start] %s.%s()' %
|
||||||
|
(self.__class__.__name__,
|
||||||
|
inspect.currentframe().f_code.co_name))
|
||||||
|
|
||||||
return json.loads(decode_request_body)
|
# 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_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()
|
||||||
|
|
||||||
def _get_mock_info(self, mock_info, request_headers, request_body):
|
LOG.debug(
|
||||||
"""Call mock(callback) and get responses
|
'[ End ] %s.%s()' %
|
||||||
|
(self.__class__.__name__,
|
||||||
|
inspect.currentframe().f_code.co_name))
|
||||||
|
|
||||||
This method is called from _returned_callback().
|
def do_PUT(self):
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
Args:
|
return DummyRequestHandler
|
||||||
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):
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
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,
|
|
||||||
FakeServerManager.get_instance()._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)
|
|
||||||
self._returned_callback(tplUri.path,
|
|
||||||
FakeServerManager.get_instance()._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):
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
|
|
||||||
class RequestHistory:
|
class RequestHistory:
|
||||||
@ -268,7 +202,7 @@ class RequestHistory:
|
|||||||
self.response_body = response_body
|
self.response_body = response_body
|
||||||
|
|
||||||
|
|
||||||
class FakeServerManager(SingletonMixin):
|
class FakeServerManager(object):
|
||||||
"""Manager class to manage dummy server setting and control"""
|
"""Manager class to manage dummy server setting and control"""
|
||||||
|
|
||||||
SERVER_PORT = 9990
|
SERVER_PORT = 9990
|
||||||
@ -347,11 +281,10 @@ class FakeServerManager(SingletonMixin):
|
|||||||
path (str): URI path
|
path (str): URI path
|
||||||
history (RequestHistory): Storage container for each request.
|
history (RequestHistory): Storage container for each request.
|
||||||
"""
|
"""
|
||||||
with self._rlock:
|
if path in self._history:
|
||||||
if path in self._history:
|
self._history[path].append(history)
|
||||||
self._history[path].append(history)
|
else:
|
||||||
else:
|
self._history[path] = [history]
|
||||||
self._history[path] = [history]
|
|
||||||
|
|
||||||
def clear_history(self, path=None):
|
def clear_history(self, path=None):
|
||||||
"""Clear Request/Response header/body of history.
|
"""Clear Request/Response header/body of history.
|
||||||
@ -359,13 +292,12 @@ class FakeServerManager(SingletonMixin):
|
|||||||
Args:
|
Args:
|
||||||
path (str): URI path
|
path (str): URI path
|
||||||
"""
|
"""
|
||||||
with self._rlock:
|
if not path:
|
||||||
if not path:
|
self._history = {}
|
||||||
self._history = {}
|
return
|
||||||
return
|
|
||||||
|
|
||||||
if path in self._history:
|
if path in self._history:
|
||||||
self._history.pop(path)
|
self._history.pop(path)
|
||||||
|
|
||||||
def get_history(self, path=None):
|
def get_history(self, path=None):
|
||||||
"""Get Request/Response header/body from history.
|
"""Get Request/Response header/body from history.
|
||||||
@ -399,8 +331,9 @@ class FakeServerManager(SingletonMixin):
|
|||||||
inspect.currentframe().f_code.co_name))
|
inspect.currentframe().f_code.co_name))
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
|
RequestHandler = PrepareRequestHandler(self)
|
||||||
self.objHttpd = http.server.HTTPServer(
|
self.objHttpd = http.server.HTTPServer(
|
||||||
(address, port), DummyRequestHander)
|
(address, port), RequestHandler)
|
||||||
except OSError:
|
except OSError:
|
||||||
time.sleep(self.SERVER_INVOKE_CHECK_INTERVAL)
|
time.sleep(self.SERVER_INVOKE_CHECK_INTERVAL)
|
||||||
continue
|
continue
|
||||||
|
@ -38,7 +38,7 @@ VNF_DELETE_COMPLETION_WAIT = 60
|
|||||||
VNF_HEAL_TIMEOUT = 600
|
VNF_HEAL_TIMEOUT = 600
|
||||||
VNF_LCM_DONE_TIMEOUT = 1200
|
VNF_LCM_DONE_TIMEOUT = 1200
|
||||||
RETRY_WAIT_TIME = 5
|
RETRY_WAIT_TIME = 5
|
||||||
FAKE_SERVER_MANAGER = FakeServerManager.get_instance()
|
FAKE_SERVER_MANAGER = FakeServerManager()
|
||||||
FAKE_SERVER_PORT = 9990
|
FAKE_SERVER_PORT = 9990
|
||||||
MOCK_NOTIFY_CALLBACK_URL = '/notification/callback'
|
MOCK_NOTIFY_CALLBACK_URL = '/notification/callback'
|
||||||
UUID_RE = r'\w{8}-\w{4}-\w{4}-\w{4}-\w{12}'
|
UUID_RE = r'\w{8}-\w{4}-\w{4}-\w{4}-\w{12}'
|
||||||
|
@ -33,7 +33,7 @@ from tacker.tests.functional.sol_v2 import utils
|
|||||||
from tacker.tests import utils as base_utils
|
from tacker.tests import utils as base_utils
|
||||||
from tacker import version
|
from tacker import version
|
||||||
|
|
||||||
FAKE_SERVER_MANAGER = FakeServerManager.get_instance()
|
FAKE_SERVER_MANAGER = FakeServerManager()
|
||||||
MOCK_NOTIFY_CALLBACK_URL = '/notification/callback'
|
MOCK_NOTIFY_CALLBACK_URL = '/notification/callback'
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
|
Loading…
Reference in New Issue
Block a user