github.com/swiftstack/ProxyFS@v0.0.0-20210203235616-4017c267d62f/pfs_middleware/tests/helpers.py (about) 1 # Copyright (c) 2015-2021, NVIDIA CORPORATION. 2 # SPDX-License-Identifier: Apache-2.0 3 4 5 import collections 6 from swift.common import swob 7 8 9 class FakeProxy(object): 10 """ 11 Vaguely Swift-proxy-server-ish WSGI application used in testing the 12 ProxyFS middleware. 13 """ 14 def __init__(self): 15 self._calls = [] 16 17 # key is a 2-tuple (request-method, url-path) 18 # 19 # value is how many WSGI iterables were created but not destroyed; 20 # if all is well-behaved, the value should be 0 upon completion of 21 # the user's request. 22 self._unclosed_req_paths = collections.defaultdict(int) 23 24 # key is a 2-tuple (request-method, url-path) 25 # 26 # value is a 3-tuple (response status, headers, body) 27 self._responses = {} 28 29 @property 30 def calls(self): 31 return tuple(self._calls) 32 33 def register(self, method, path, response_status, headers, body=''): 34 self._responses[(method, path)] = (response_status, headers, body) 35 36 def __call__(self, env, start_response): 37 method = env['REQUEST_METHOD'] 38 path = env['PATH_INFO'] 39 40 req = swob.Request(env) 41 self._calls.append((method, path, swob.HeaderKeyDict(req.headers))) 42 43 if (env.get('swift.authorize') and not env.get( 44 'swift.authorize_override')): 45 denial_response = env['swift.authorize'](req) 46 if denial_response: 47 return denial_response 48 49 try: 50 status_int, headers, body = self._responses[(method, path)] 51 except KeyError: 52 print("Didn't find \"%s %s\" in registered responses" % ( 53 method, path)) 54 raise 55 56 if method in ('PUT', 'COALESCE'): 57 bytes_read = 0 58 # consume the whole request body, just like a PUT would 59 for chunk in iter(env['wsgi.input'].read, b''): 60 bytes_read += len(chunk) 61 cl = req.headers.get('Content-Length') 62 63 if cl is not None and int(cl) != bytes_read: 64 error_resp = swob.HTTPClientDisconnect( 65 request=req, 66 body=("Content-Length didn't match" 67 " body length (says FakeProxy)")) 68 return error_resp(env, start_response) 69 70 if cl is None \ 71 and "chunked" not in req.headers.get("Transfer-Encoding", ""): 72 error_resp = swob.HTTPLengthRequired( 73 request=req, 74 body="No Content-Length (says FakeProxy)") 75 return error_resp(env, start_response) 76 77 resp = swob.Response( 78 body=body, status=status_int, headers=headers, 79 # We cheat a little here and use swob's handling of the Range 80 # header instead of doing it ourselves. 81 conditional_response=True) 82 83 return resp(env, start_response) 84 85 86 class FakeJsonRpc(object): 87 """ 88 Fake out JSON-RPC calls. 89 90 This object is used to replace the JsonRpcClient helper object. 91 """ 92 def __init__(self): 93 self._calls = [] 94 self._rpc_handlers = {} 95 96 @property 97 def calls(self): 98 return tuple(self._calls) 99 100 def register_handler(self, method, handler): 101 """ 102 :param method: name of JSON-RPC method, e.g. Server.RpcGetObject 103 104 :param handler: callable to handle that method. Callable must take 105 one JSON-RPC object (dict) as argument and return a single 106 JSON-RPC object (dict). 107 """ 108 self._rpc_handlers[method] = handler 109 110 def call(self, rpc_request, _timeout, raise_on_rpc_error=True): 111 # Note: rpc_request here is a JSON-RPC request object. In Python 112 # terms, it's a dictionary with a particular format. 113 114 call_id = rpc_request.get('id') 115 116 # let any exceptions out so callers can see them and fix their 117 # request generators 118 assert rpc_request['jsonrpc'] == '2.0' 119 method = rpc_request['method'] 120 121 # rpc.* are reserved by JSON-RPC 2.0 122 assert not method.startswith("rpc.") 123 124 # let KeyError out so callers can see it and fix their mocks 125 handler = self._rpc_handlers[method] 126 127 # params may be omitted, a list (positional), or a dict (by name) 128 if 'params' not in rpc_request: 129 rpc_response = handler() 130 self._calls.append((method, ())) 131 elif isinstance(rpc_request['params'], (list, tuple)): 132 rpc_response = handler(*(rpc_request['params'])) 133 self._calls.append((method, tuple(rpc_request['params']))) 134 elif isinstance(rpc_request['params'], (dict,)): 135 raise NotImplementedError("haven't needed this yet") 136 else: 137 raise ValueError( 138 "FakeJsonRpc can't handle params of type %s (%r)" % 139 (type(rpc_request['params']), rpc_request['params'])) 140 141 if call_id is None: 142 # a JSON-RPC request without an "id" parameter is a 143 # "notification", i.e. a special request that receives no 144 # response. 145 return None 146 elif 'id' in rpc_response and rpc_response['id'] != call_id: 147 # We don't enforce that the handler pay any attention to 'id', 148 # but if it does, it has to get it right. 149 raise ValueError("handler for %s set 'id' attr to bogus value" % 150 (method,)) 151 else: 152 rpc_response['id'] = call_id 153 return rpc_response