"Fossies" - the Fresh Open Source Software Archive

Member "swift-2.21.0/test/unit/common/middleware/s3api/helpers.py" (25 Mar 2019, 7271 Bytes) of package /linux/misc/openstack/swift-2.21.0.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Python source code syntax highlighting (style: standard) with prefixed line numbers. Alternatively you can here view or download the uninterpreted source code file. See also the latest Fossies "Diffs" side-by-side code changes report for "helpers.py": 2.19.1_vs_2.21.0.

    1 # Copyright (c) 2013 OpenStack Foundation
    2 #
    3 # Licensed under the Apache License, Version 2.0 (the "License");
    4 # you may not use this file except in compliance with the License.
    5 # You may obtain a copy of the License at
    6 #
    7 #    http://www.apache.org/licenses/LICENSE-2.0
    8 #
    9 # Unless required by applicable law or agreed to in writing, software
   10 # distributed under the License is distributed on an "AS IS" BASIS,
   11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
   12 # implied.
   13 # See the License for the specific language governing permissions and
   14 # limitations under the License.
   15 
   16 # This stuff can't live in test/unit/__init__.py due to its swob dependency.
   17 
   18 from copy import deepcopy
   19 from hashlib import md5
   20 from swift.common import swob
   21 from swift.common.utils import split_path
   22 from swift.common.request_helpers import is_sys_meta
   23 
   24 
   25 class FakeSwift(object):
   26     """
   27     A good-enough fake Swift proxy server to use in testing middleware.
   28     """
   29 
   30     def __init__(self, s3_acl=False):
   31         self._calls = []
   32         self.req_method_paths = []
   33         self.swift_sources = []
   34         self.uploaded = {}
   35         # mapping of (method, path) --> (response class, headers, body)
   36         self._responses = {}
   37         self.s3_acl = s3_acl
   38         self.remote_user = 'authorized'
   39 
   40     def _fake_auth_middleware(self, env):
   41         if 'swift.authorize_override' in env:
   42             return
   43 
   44         if 's3api.auth_details' not in env:
   45             return
   46 
   47         tenant_user = env['s3api.auth_details']['access_key']
   48         tenant, user = tenant_user.rsplit(':', 1)
   49 
   50         path = env['PATH_INFO']
   51         env['PATH_INFO'] = path.replace(tenant_user, 'AUTH_' + tenant)
   52 
   53         if self.remote_user:
   54             env['REMOTE_USER'] = self.remote_user
   55 
   56         if env['REQUEST_METHOD'] == 'TEST':
   57 
   58             def authorize_cb(req):
   59                 # Assume swift owner, if not yet set
   60                 req.environ.setdefault('swift_owner', True)
   61                 # But then default to blocking authz, to ensure we've replaced
   62                 # the default auth system
   63                 return swob.HTTPForbidden(request=req)
   64 
   65             env['swift.authorize'] = authorize_cb
   66         else:
   67             env['swift.authorize'] = lambda req: None
   68 
   69     def __call__(self, env, start_response):
   70         if self.s3_acl:
   71             self._fake_auth_middleware(env)
   72 
   73         req = swob.Request(env)
   74         method = env['REQUEST_METHOD']
   75         path = env['PATH_INFO']
   76         _, acc, cont, obj = split_path(env['PATH_INFO'], 0, 4,
   77                                        rest_with_last=True)
   78         if env.get('QUERY_STRING'):
   79             path += '?' + env['QUERY_STRING']
   80 
   81         if 'swift.authorize' in env:
   82             resp = env['swift.authorize'](req)
   83             if resp:
   84                 return resp(env, start_response)
   85 
   86         headers = req.headers
   87         self._calls.append((method, path, headers))
   88         self.swift_sources.append(env.get('swift.source'))
   89 
   90         try:
   91             resp_class, raw_headers, body = self._responses[(method, path)]
   92             headers = swob.HeaderKeyDict(raw_headers)
   93         except KeyError:
   94             # FIXME: suppress print state error for python3 compatibility.
   95             # pylint: disable-msg=E1601
   96             if (env.get('QUERY_STRING')
   97                     and (method, env['PATH_INFO']) in self._responses):
   98                 resp_class, raw_headers, body = self._responses[
   99                     (method, env['PATH_INFO'])]
  100                 headers = swob.HeaderKeyDict(raw_headers)
  101             elif method == 'HEAD' and ('GET', path) in self._responses:
  102                 resp_class, raw_headers, _ = self._responses[('GET', path)]
  103                 body = None
  104                 headers = swob.HeaderKeyDict(raw_headers)
  105             elif method == 'GET' and obj and path in self.uploaded:
  106                 resp_class = swob.HTTPOk
  107                 headers, body = self.uploaded[path]
  108             else:
  109                 print("Didn't find %r in allowed responses" %
  110                       ((method, path),))
  111                 raise
  112 
  113         # simulate object PUT
  114         if method == 'PUT' and obj:
  115             input = env['wsgi.input'].read()
  116             etag = md5(input).hexdigest()
  117             headers.setdefault('Etag', etag)
  118             headers.setdefault('Content-Length', len(input))
  119 
  120             # keep it for subsequent GET requests later
  121             self.uploaded[path] = (deepcopy(headers), input)
  122             if "CONTENT_TYPE" in env:
  123                 self.uploaded[path][0]['Content-Type'] = env["CONTENT_TYPE"]
  124 
  125         # range requests ought to work, but copies are special
  126         support_range_and_conditional = not (
  127             method == 'PUT' and
  128             'X-Copy-From' in req.headers and
  129             'Range' in req.headers)
  130         if isinstance(body, list):
  131             app_iter = body
  132             body = None
  133         else:
  134             app_iter = None
  135         resp = resp_class(
  136             req=req, headers=headers, body=body, app_iter=app_iter,
  137             conditional_response=support_range_and_conditional)
  138         return resp(env, start_response)
  139 
  140     @property
  141     def calls(self):
  142         return [(method, path) for method, path, headers in self._calls]
  143 
  144     @property
  145     def calls_with_headers(self):
  146         return self._calls
  147 
  148     @property
  149     def call_count(self):
  150         return len(self._calls)
  151 
  152     def register(self, method, path, response_class, headers, body):
  153         # assuming the path format like /v1/account/container/object
  154         resource_map = ['account', 'container', 'object']
  155         index = len(list(filter(None, split_path(path, 0, 4, True)[1:]))) - 1
  156         resource = resource_map[index]
  157         if (method, path) in self._responses:
  158             old_headers = self._responses[(method, path)][1]
  159             headers = headers.copy()
  160             for key, value in old_headers.items():
  161                 if is_sys_meta(resource, key) and key not in headers:
  162                     # keep old sysmeta for s3acl
  163                     headers.update({key: value})
  164 
  165         self._responses[(method, path)] = (response_class, headers, body)
  166 
  167     def register_unconditionally(self, method, path, response_class, headers,
  168                                  body):
  169         # register() keeps old sysmeta around, but
  170         # register_unconditionally() keeps nothing.
  171         self._responses[(method, path)] = (response_class, headers, body)
  172 
  173     def clear_calls(self):
  174         del self._calls[:]
  175 
  176 
  177 class UnreadableInput(object):
  178     # Some clients will send neither a Content-Length nor a Transfer-Encoding
  179     # header, which will cause (some versions of?) eventlet to bomb out on
  180     # reads. This class helps us simulate that behavior.
  181     def __init__(self, test_case):
  182         self.calls = 0
  183         self.test_case = test_case
  184 
  185     def read(self, *a, **kw):
  186         self.calls += 1
  187         # Calling wsgi.input.read with neither a Content-Length nor
  188         # a Transfer-Encoding header will raise TypeError (See
  189         # https://bugs.launchpad.net/swift3/+bug/1593870 in detail)
  190         # This unreadable class emulates the behavior
  191         raise TypeError
  192 
  193     def __enter__(self):
  194         return self
  195 
  196     def __exit__(self, *args):
  197         self.test_case.assertEqual(0, self.calls)