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