Пример #1
0
 def HEAD(self, req):
     """Handle HTTP HEAD request."""
     drive, part, account, container, obj = split_and_validate_path(
         req, 4, 5, True)
     out_content_type = listing_formats.get_listing_content_type(req)
     try:
         check_drive(self.root, drive, self.mount_check)
     except ValueError:
         return HTTPInsufficientStorage(drive=drive, request=req)
     broker = self._get_container_broker(drive, part, account, container,
                                         pending_timeout=0.1,
                                         stale_reads_ok=True)
     info, is_deleted = broker.get_info_is_deleted()
     headers = gen_resp_headers(info, is_deleted=is_deleted)
     if is_deleted:
         return HTTPNotFound(request=req, headers=headers)
     headers.update(
         (str_to_wsgi(key), str_to_wsgi(value))
         for key, (value, timestamp) in broker.metadata.items()
         if value != '' and (key.lower() in self.save_headers or
                             is_sys_or_user_meta('container', key)))
     headers['Content-Type'] = out_content_type
     resp = HTTPNoContent(request=req, headers=headers, charset='utf-8')
     resp.last_modified = math.ceil(float(headers['X-PUT-Timestamp']))
     return resp
Пример #2
0
    def _process_delete(self,
                        resp,
                        pile,
                        obj_name,
                        resp_dict,
                        failed_files,
                        failed_file_response,
                        retry=0):
        if resp.status_int // 100 == 2:
            resp_dict['Number Deleted'] += 1
        elif resp.status_int == HTTP_NOT_FOUND:
            resp_dict['Number Not Found'] += 1
        elif resp.status_int == HTTP_UNAUTHORIZED:
            failed_files.append(
                [wsgi_quote(str_to_wsgi(obj_name)),
                 HTTPUnauthorized().status])
        elif resp.status_int == HTTP_CONFLICT and pile and \
                self.retry_count > 0 and self.retry_count > retry:
            retry += 1
            sleep(self.retry_interval**retry)
            delete_obj_req = Request.blank(resp.environ['PATH_INFO'],
                                           resp.environ)

            def _retry(req, app, obj_name, retry):
                return req.get_response(app), obj_name, retry

            pile.spawn(_retry, delete_obj_req, self.app, obj_name, retry)
        else:
            if resp.status_int // 100 == 5:
                failed_file_response['type'] = HTTPBadGateway
            failed_files.append(
                [wsgi_quote(str_to_wsgi(obj_name)), resp.status])
Пример #3
0
    def _get_container_listing(self, req, version, account, container,
                               prefix, marker=''):
        '''
        :param version: whatever
        :param account: native
        :param container: native
        :param prefix: native
        :param marker: native
        '''
        con_req = make_subrequest(
            req.environ,
            path=wsgi_quote('/'.join([
                '', str_to_wsgi(version),
                str_to_wsgi(account), str_to_wsgi(container)])),
            method='GET',
            headers={'x-auth-token': req.headers.get('x-auth-token')},
            agent=('%(orig)s ' + 'DLO MultipartGET'), swift_source='DLO')
        con_req.query_string = 'prefix=%s' % quote(prefix)
        if marker:
            con_req.query_string += '&marker=%s' % quote(marker)

        con_resp = con_req.get_response(self.dlo.app)
        if not is_success(con_resp.status_int):
            if req.method == 'HEAD':
                con_resp.body = b''
            return con_resp, None
        with closing_if_possible(con_resp.app_iter):
            return None, json.loads(b''.join(con_resp.app_iter))
Пример #4
0
Файл: dlo.py Проект: mahak/swift
    def _get_container_listing(self, req, version, account, container,
                               prefix, marker=''):
        '''
        :param version: whatever
        :param account: native
        :param container: native
        :param prefix: native
        :param marker: native
        '''
        con_req = make_subrequest(
            req.environ,
            path=wsgi_quote('/'.join([
                '', str_to_wsgi(version),
                str_to_wsgi(account), str_to_wsgi(container)])),
            method='GET',
            headers={'x-auth-token': req.headers.get('x-auth-token')},
            agent=('%(orig)s ' + 'DLO MultipartGET'), swift_source='DLO')
        con_req.query_string = 'prefix=%s' % quote(prefix)
        if marker:
            con_req.query_string += '&marker=%s' % quote(marker)

        con_resp = con_req.get_response(self.dlo.app)
        if not is_success(con_resp.status_int):
            if req.method == 'HEAD':
                con_resp.body = b''
            return con_resp, None
        with closing_if_possible(con_resp.app_iter):
            return None, json.loads(b''.join(con_resp.app_iter))
Пример #5
0
 def HEAD(self, req):
     """Handle HTTP HEAD request."""
     drive, part, account, container, obj = get_obj_name_and_placement(req)
     out_content_type = listing_formats.get_listing_content_type(req)
     try:
         check_drive(self.root, drive, self.mount_check)
     except ValueError:
         return HTTPInsufficientStorage(drive=drive, request=req)
     broker = self._get_container_broker(drive,
                                         part,
                                         account,
                                         container,
                                         pending_timeout=0.1,
                                         stale_reads_ok=True)
     info, is_deleted = broker.get_info_is_deleted()
     headers = gen_resp_headers(info, is_deleted=is_deleted)
     if is_deleted:
         return HTTPNotFound(request=req, headers=headers)
     headers.update(
         (str_to_wsgi(key), str_to_wsgi(value))
         for key, (value, timestamp) in broker.metadata.items()
         if value != '' and (key.lower() in self.save_headers
                             or is_sys_or_user_meta('container', key)))
     headers['Content-Type'] = out_content_type
     resp = HTTPNoContent(request=req, headers=headers, charset='utf-8')
     resp.last_modified = math.ceil(float(headers['X-PUT-Timestamp']))
     return resp
Пример #6
0
    def create_listing(self, req, out_content_type, info, resp_headers,
                       metadata, container_list, container):
        for key, (value, _timestamp) in metadata.items():
            if value and (key.lower() in self.save_headers
                          or is_sys_or_user_meta('container', key)):
                resp_headers[str_to_wsgi(key)] = str_to_wsgi(value)
        listing = [
            self.update_data_record(record) for record in container_list
        ]
        if out_content_type.endswith('/xml'):
            body = listing_formats.container_to_xml(listing, container)
        elif out_content_type.endswith('/json'):
            body = json.dumps(listing).encode('ascii')
        else:
            body = listing_formats.listing_to_text(listing)

        ret = Response(request=req,
                       headers=resp_headers,
                       body=body,
                       content_type=out_content_type,
                       charset='utf-8')
        ret.last_modified = math.ceil(float(resp_headers['X-PUT-Timestamp']))
        if not ret.body:
            ret.status_int = HTTP_NO_CONTENT
        return ret
Пример #7
0
 def delete_filter(predicate, objs_to_delete):
     for obj_to_delete in objs_to_delete:
         obj_name = obj_to_delete['name']
         if not obj_name:
             continue
         if not predicate(obj_name):
             continue
         if obj_to_delete.get('error'):
             if obj_to_delete['error']['code'] == HTTP_NOT_FOUND:
                 resp_dict['Number Not Found'] += 1
             else:
                 failed_files.append([
                     wsgi_quote(str_to_wsgi(obj_name)),
                     obj_to_delete['error']['message']
                 ])
             continue
         delete_path = '/'.join(
             ['', vrs, account,
              obj_name.lstrip('/')])
         if not constraints.check_utf8(delete_path):
             failed_files.append([
                 wsgi_quote(str_to_wsgi(obj_name)),
                 HTTPPreconditionFailed().status
             ])
             continue
         yield (obj_name, delete_path,
                obj_to_delete.get('version_id'))
Пример #8
0
    def make_headers(self, hdrs, cfg=None):
        if cfg is None:
            cfg = {}
        headers = {}

        if not cfg.get('no_auth_token'):
            headers['X-Auth-Token'] = self.storage_token

        if cfg.get('use_token'):
            headers['X-Auth-Token'] = cfg.get('use_token')

        if isinstance(hdrs, dict):
            headers.update(
                (str_to_wsgi(h), str_to_wsgi(v)) for h, v in hdrs.items())
        return headers
Пример #9
0
    def make_headers(self, hdrs, cfg=None):
        if cfg is None:
            cfg = {}
        headers = {}

        if not cfg.get('no_auth_token'):
            headers['X-Auth-Token'] = self.storage_token

        if cfg.get('use_token'):
            headers['X-Auth-Token'] = cfg.get('use_token')

        if isinstance(hdrs, dict):
            headers.update((str_to_wsgi(h), str_to_wsgi(v))
                           for h, v in hdrs.items())
        return headers
Пример #10
0
 def _copy_object(self, app, destination):
     req = Request.blank(str_to_wsgi(self.object_path), method='COPY',
                         headers={'Destination': destination})
     resp = req.get_response(app)
     self.assertEqual('201 Created', resp.status)
     self.assertEqual(self.plaintext_etag, resp.headers['Etag'])
     return resp
Пример #11
0
 def _post_object(self, app):
     req = Request.blank(str_to_wsgi(self.object_path), method='POST',
                         headers={'Content-Type': 'application/test',
                                  'X-Object-Meta-Fruit': 'Kiwi'})
     resp = req.get_response(app)
     self.assertEqual('202 Accepted', resp.status)
     return resp
Пример #12
0
 def _copy_object(self, app, destination):
     req = Request.blank(str_to_wsgi(self.object_path), method='COPY',
                         headers={'Destination': destination})
     resp = req.get_response(app)
     self.assertEqual('201 Created', resp.status)
     self.assertEqual(self.plaintext_etag, resp.headers['Etag'])
     return resp
Пример #13
0
 def _post_object(self, app):
     req = Request.blank(str_to_wsgi(self.object_path), method='POST',
                         headers={'Content-Type': 'application/test',
                                  'X-Object-Meta-Fruit': 'Kiwi'})
     resp = req.get_response(app)
     self.assertEqual('202 Accepted', resp.status)
     return resp
Пример #14
0
    def delete_actual_object(self, actual_obj, timestamp, is_async_delete):
        """
        Deletes the end-user object indicated by the actual object name given
        '<account>/<container>/<object>' if and only if the X-Delete-At value
        of the object is exactly the timestamp given.

        :param actual_obj: The name of the end-user object to delete:
                           '<account>/<container>/<object>'
        :param timestamp: The swift.common.utils.Timestamp instance the
                          X-Delete-At value must match to perform the actual
                          delete.
        :param is_async_delete: False if the object should be deleted because
                                of "normal" expiration, or True if it should
                                be async-deleted.
        :raises UnexpectedResponse: if the delete was unsuccessful and
                                    should be retried later
        """
        path = '/v1/' + wsgi_quote(str_to_wsgi(actual_obj.lstrip('/')))
        if is_async_delete:
            headers = {'X-Timestamp': timestamp.normal}
            acceptable_statuses = (2, HTTP_CONFLICT, HTTP_NOT_FOUND)
        else:
            headers = {
                'X-Timestamp': timestamp.normal,
                'X-If-Delete-At': timestamp.normal,
                'X-Backend-Clean-Expiring-Object-Queue': 'no'
            }
            acceptable_statuses = (2, HTTP_CONFLICT)
        self.swift.make_request('DELETE', path, headers, acceptable_statuses)
Пример #15
0
    def test_dlo_post_with_manifest_header(self):
        # verify that performing a POST to a DLO manifest
        # preserves the fact that it is a manifest file.
        # verify that the x-object-manifest header may be updated.

        # create a new manifest for this test to avoid test coupling.
        x_o_m = self.env.container.file('man1').info()['x_object_manifest']
        file_item = self.env.container.file(Utils.create_name())
        file_item.write(b'manifest-contents',
                        hdrs={"X-Object-Manifest": x_o_m})

        # sanity checks
        manifest_contents = file_item.read(parms={'multipart-manifest': 'get'})
        self.assertEqual(b'manifest-contents', manifest_contents)
        expected_contents = ''.join((c * 10) for c in 'abcde').encode('ascii')
        contents = file_item.read(parms={})
        self.assertEqual(expected_contents, contents)

        # POST a modified x-object-manifest value
        new_x_o_m = x_o_m.rstrip('lower') + 'upper'
        file_item.post({
            'x-object-meta-foo': 'bar',
            'x-object-manifest': new_x_o_m
        })

        # verify that x-object-manifest was updated
        file_item.info()
        resp_headers = [(h.lower(), v)
                        for h, v in file_item.conn.response.getheaders()]
        self.assertIn(('x-object-manifest', str_to_wsgi(new_x_o_m)),
                      resp_headers)
        self.assertIn(('x-object-meta-foo', 'bar'), resp_headers)

        # verify that manifest content was not changed
        manifest_contents = file_item.read(parms={'multipart-manifest': 'get'})
        self.assertEqual(b'manifest-contents', manifest_contents)

        # verify that updated manifest points to new content
        expected_contents = ''.join((c * 10) for c in 'ABCDE').encode('ascii')
        contents = file_item.read(parms={})
        self.assertEqual(expected_contents, contents)

        # Now revert the manifest to point to original segments, including a
        # multipart-manifest=get param just to check that has no effect
        file_item.post({'x-object-manifest': x_o_m},
                       parms={'multipart-manifest': 'get'})

        # verify that x-object-manifest was reverted
        info = file_item.info()
        self.assertIn('x_object_manifest', info)
        self.assertEqual(x_o_m, info['x_object_manifest'])

        # verify that manifest content was not changed
        manifest_contents = file_item.read(parms={'multipart-manifest': 'get'})
        self.assertEqual(b'manifest-contents', manifest_contents)

        # verify that updated manifest points new content
        expected_contents = ''.join((c * 10) for c in 'abcde').encode('ascii')
        contents = file_item.read(parms={})
        self.assertEqual(expected_contents, contents)
Пример #16
0
    def test_dlo_post_with_manifest_regular_object(self):
        # verify that performing a POST to a regular object
        # with a manifest header will create a DLO.

        # Put a regular object
        file_item = self.env.container.file(Utils.create_name())
        file_item.write(b'file contents', hdrs={})

        # sanity checks
        file_contents = file_item.read(parms={})
        self.assertEqual(b'file contents', file_contents)

        # get the path associated with man1
        x_o_m = self.env.container.file('man1').info()['x_object_manifest']

        # POST a x-object-manifest value to the regular object
        file_item.post({'x-object-manifest': x_o_m})

        # verify that the file is now a manifest
        manifest_contents = file_item.read(parms={'multipart-manifest': 'get'})
        self.assertEqual(b'file contents', manifest_contents)
        expected_contents = ''.join([(c * 10) for c in 'abcde']).encode()
        contents = file_item.read(parms={})
        self.assertEqual(expected_contents, contents)
        file_item.info()
        resp_headers = [(h.lower(), v)
                        for h, v in file_item.conn.response.getheaders()]
        self.assertIn(('x-object-manifest', str_to_wsgi(x_o_m)), resp_headers)
Пример #17
0
    def delete_actual_object(self, actual_obj, timestamp, is_async_delete):
        """
        Deletes the end-user object indicated by the actual object name given
        '<account>/<container>/<object>' if and only if the X-Delete-At value
        of the object is exactly the timestamp given.

        :param actual_obj: The name of the end-user object to delete:
                           '<account>/<container>/<object>'
        :param timestamp: The swift.common.utils.Timestamp instance the
                          X-Delete-At value must match to perform the actual
                          delete.
        :param is_async_delete: False if the object should be deleted because
                                of "normal" expiration, or True if it should
                                be async-deleted.
        :raises UnexpectedResponse: if the delete was unsuccessful and
                                    should be retried later
        """
        path = '/v1/' + wsgi_quote(str_to_wsgi(actual_obj.lstrip('/')))
        if is_async_delete:
            headers = {'X-Timestamp': timestamp.normal}
            acceptable_statuses = (2, HTTP_CONFLICT, HTTP_NOT_FOUND)
        else:
            headers = {'X-Timestamp': timestamp.normal,
                       'X-If-Delete-At': timestamp.normal,
                       'X-Backend-Clean-Expiring-Object-Queue': 'no'}
            acceptable_statuses = (2, HTTP_CONFLICT)
        self.swift.make_request('DELETE', path, headers, acceptable_statuses)
Пример #18
0
 def _put_object(self, app, body):
     req = Request.blank(
         str_to_wsgi(self.object_path), method='PUT', body=body,
         headers={'Content-Type': 'application/test'})
     resp = req.get_response(app)
     self.assertEqual('201 Created', resp.status)
     self.assertEqual(self.plaintext_etag, resp.headers['Etag'])
     return resp
Пример #19
0
 def do_delete(obj_name, delete_path):
     delete_obj_req = make_subrequest(
         req.environ, method='DELETE',
         path=wsgi_quote(str_to_wsgi(delete_path)),
         headers={'X-Auth-Token': req.headers.get('X-Auth-Token')},
         body='', agent='%(orig)s ' + user_agent,
         swift_source=swift_source)
     return (delete_obj_req.get_response(self.app), obj_name, 0)
Пример #20
0
 def _put_object(self, app, body):
     req = Request.blank(
         str_to_wsgi(self.object_path), method='PUT', body=body,
         headers={'Content-Type': 'application/test'})
     resp = req.get_response(app)
     self.assertEqual('201 Created', resp.status)
     self.assertEqual(self.plaintext_etag, resp.headers['Etag'])
     return resp
Пример #21
0
    def object_request(self, req, api_version, account, container, obj,
                       allow_versioned_writes):
        """
        Handle request for object resource.

        Note that account, container, obj should be unquoted by caller
        if the url path is under url encoding (e.g. %FF)

        :param req: swift.common.swob.Request instance
        :param api_version: should be v1 unless swift bumps api version
        :param account: account name string
        :param container: container name string
        :param object: object name string
        """
        resp = None
        is_enabled = config_true_value(allow_versioned_writes)
        container_info = get_container_info(
            req.environ, self.app)

        # To maintain backwards compatibility, container version
        # location could be stored as sysmeta or not, need to check both.
        # If stored as sysmeta, check if middleware is enabled. If sysmeta
        # is not set, but versions property is set in container_info, then
        # for backwards compatibility feature is enabled.
        versions_cont = container_info.get(
            'sysmeta', {}).get('versions-location')
        versioning_mode = container_info.get(
            'sysmeta', {}).get('versions-mode', 'stack')
        if not versions_cont:
            versions_cont = container_info.get('versions')
            # if allow_versioned_writes is not set in the configuration files
            # but 'versions' is configured, enable feature to maintain
            # backwards compatibility
            if not allow_versioned_writes and versions_cont:
                is_enabled = True

        if is_enabled and versions_cont:
            versions_cont = wsgi_unquote(str_to_wsgi(
                versions_cont)).split('/')[0]
            vw_ctx = VersionedWritesContext(self.app, self.logger)
            if req.method == 'PUT':
                resp = vw_ctx.handle_obj_versions_put(
                    req, versions_cont, api_version, account,
                    obj)
            # handle DELETE
            elif versioning_mode == 'history':
                resp = vw_ctx.handle_obj_versions_delete_push(
                    req, versions_cont, api_version, account,
                    container, obj)
            else:
                resp = vw_ctx.handle_obj_versions_delete_pop(
                    req, versions_cont, api_version, account,
                    container, obj)

        if resp:
            return resp
        else:
            return self.app
Пример #22
0
    def object_request(self, req, api_version, account, container, obj,
                       allow_versioned_writes):
        """
        Handle request for object resource.

        Note that account, container, obj should be unquoted by caller
        if the url path is under url encoding (e.g. %FF)

        :param req: swift.common.swob.Request instance
        :param api_version: should be v1 unless swift bumps api version
        :param account: account name string
        :param container: container name string
        :param object: object name string
        """
        resp = None
        is_enabled = config_true_value(allow_versioned_writes)
        container_info = get_container_info(
            req.environ, self.app, swift_source='VW')

        # To maintain backwards compatibility, container version
        # location could be stored as sysmeta or not, need to check both.
        # If stored as sysmeta, check if middleware is enabled. If sysmeta
        # is not set, but versions property is set in container_info, then
        # for backwards compatibility feature is enabled.
        versions_cont = container_info.get(
            'sysmeta', {}).get('versions-location')
        versioning_mode = container_info.get(
            'sysmeta', {}).get('versions-mode', 'stack')
        if not versions_cont:
            versions_cont = container_info.get('versions')
            # if allow_versioned_writes is not set in the configuration files
            # but 'versions' is configured, enable feature to maintain
            # backwards compatibility
            if not allow_versioned_writes and versions_cont:
                is_enabled = True

        if is_enabled and versions_cont:
            versions_cont = wsgi_unquote(str_to_wsgi(
                versions_cont)).split('/')[0]
            vw_ctx = VersionedWritesContext(self.app, self.logger)
            if req.method == 'PUT':
                resp = vw_ctx.handle_obj_versions_put(
                    req, versions_cont, api_version, account,
                    obj)
            # handle DELETE
            elif versioning_mode == 'history':
                resp = vw_ctx.handle_obj_versions_delete_push(
                    req, versions_cont, api_version, account,
                    container, obj)
            else:
                resp = vw_ctx.handle_obj_versions_delete_pop(
                    req, versions_cont, api_version, account,
                    container, obj)

        if resp:
            return resp
        else:
            return self.app
Пример #23
0
 def _make_key_id(self, path, secret_id, version):
     if version in ('1', '2'):
         path = str_to_wsgi(path)
     key_id = {'v': version, 'path': path}
     if secret_id:
         # stash secret_id so that decrypter can pass it back to get the
         # same keys
         key_id['secret_id'] = secret_id
     return key_id
Пример #24
0
    def _check_match_requests(self, method, app, object_path=None):
        object_path = str_to_wsgi(object_path or self.object_path)
        # verify conditional match requests
        expected_body = self.plaintext if method == 'GET' else b''

        # If-Match matches
        req = Request.blank(object_path, method=method,
                            headers={'If-Match': '"%s"' % self.plaintext_etag})
        resp = req.get_response(app)
        self.assertEqual('200 OK', resp.status)
        self.assertEqual(expected_body, resp.body)
        self.assertEqual(self.plaintext_etag, resp.headers['Etag'])
        self.assertEqual('Kiwi', resp.headers['X-Object-Meta-Fruit'])

        # If-Match wildcard
        req = Request.blank(object_path, method=method,
                            headers={'If-Match': '*'})
        resp = req.get_response(app)
        self.assertEqual('200 OK', resp.status)
        self.assertEqual(expected_body, resp.body)
        self.assertEqual(self.plaintext_etag, resp.headers['Etag'])
        self.assertEqual('Kiwi', resp.headers['X-Object-Meta-Fruit'])

        # If-Match does not match
        req = Request.blank(object_path, method=method,
                            headers={'If-Match': '"not the etag"'})
        resp = req.get_response(app)
        self.assertEqual('412 Precondition Failed', resp.status)
        self.assertEqual(b'', resp.body)
        self.assertEqual(self.plaintext_etag, resp.headers['Etag'])

        # If-None-Match matches
        req = Request.blank(
            object_path, method=method,
            headers={'If-None-Match': '"%s"' % self.plaintext_etag})
        resp = req.get_response(app)
        self.assertEqual('304 Not Modified', resp.status)
        self.assertEqual(b'', resp.body)
        self.assertEqual(self.plaintext_etag, resp.headers['Etag'])

        # If-None-Match wildcard
        req = Request.blank(object_path, method=method,
                            headers={'If-None-Match': '*'})
        resp = req.get_response(app)
        self.assertEqual('304 Not Modified', resp.status)
        self.assertEqual(b'', resp.body)
        self.assertEqual(self.plaintext_etag, resp.headers['Etag'])

        # If-None-Match does not match
        req = Request.blank(object_path, method=method,
                            headers={'If-None-Match': '"not the etag"'})
        resp = req.get_response(app)
        self.assertEqual('200 OK', resp.status)
        self.assertEqual(expected_body, resp.body)
        self.assertEqual(self.plaintext_etag, resp.headers['Etag'])
        self.assertEqual('Kiwi', resp.headers['X-Object-Meta-Fruit'])
Пример #25
0
    def _check_match_requests(self, method, app, object_path=None):
        object_path = str_to_wsgi(object_path or self.object_path)
        # verify conditional match requests
        expected_body = self.plaintext if method == 'GET' else b''

        # If-Match matches
        req = Request.blank(object_path, method=method,
                            headers={'If-Match': '"%s"' % self.plaintext_etag})
        resp = req.get_response(app)
        self.assertEqual('200 OK', resp.status)
        self.assertEqual(expected_body, resp.body)
        self.assertEqual(self.plaintext_etag, resp.headers['Etag'])
        self.assertEqual('Kiwi', resp.headers['X-Object-Meta-Fruit'])

        # If-Match wildcard
        req = Request.blank(object_path, method=method,
                            headers={'If-Match': '*'})
        resp = req.get_response(app)
        self.assertEqual('200 OK', resp.status)
        self.assertEqual(expected_body, resp.body)
        self.assertEqual(self.plaintext_etag, resp.headers['Etag'])
        self.assertEqual('Kiwi', resp.headers['X-Object-Meta-Fruit'])

        # If-Match does not match
        req = Request.blank(object_path, method=method,
                            headers={'If-Match': '"not the etag"'})
        resp = req.get_response(app)
        self.assertEqual('412 Precondition Failed', resp.status)
        self.assertEqual(b'', resp.body)
        self.assertEqual(self.plaintext_etag, resp.headers['Etag'])

        # If-None-Match matches
        req = Request.blank(
            object_path, method=method,
            headers={'If-None-Match': '"%s"' % self.plaintext_etag})
        resp = req.get_response(app)
        self.assertEqual('304 Not Modified', resp.status)
        self.assertEqual(b'', resp.body)
        self.assertEqual(self.plaintext_etag, resp.headers['Etag'])

        # If-None-Match wildcard
        req = Request.blank(object_path, method=method,
                            headers={'If-None-Match': '*'})
        resp = req.get_response(app)
        self.assertEqual('304 Not Modified', resp.status)
        self.assertEqual(b'', resp.body)
        self.assertEqual(self.plaintext_etag, resp.headers['Etag'])

        # If-None-Match does not match
        req = Request.blank(object_path, method=method,
                            headers={'If-None-Match': '"not the etag"'})
        resp = req.get_response(app)
        self.assertEqual('200 OK', resp.status)
        self.assertEqual(expected_body, resp.body)
        self.assertEqual(self.plaintext_etag, resp.headers['Etag'])
        self.assertEqual('Kiwi', resp.headers['X-Object-Meta-Fruit'])
Пример #26
0
def pax_key_to_swift_header(pax_key):
    if (pax_key == u"SCHILY.xattr.user.mime_type" or
            pax_key == u"LIBARCHIVE.xattr.user.mime_type"):
        return "Content-Type"
    elif pax_key.startswith(u"SCHILY.xattr.user.meta."):
        useful_part = pax_key[len(u"SCHILY.xattr.user.meta."):]
        if six.PY2:
            return "X-Object-Meta-" + useful_part.encode("utf-8")
        return str_to_wsgi("X-Object-Meta-" + useful_part)
    elif pax_key.startswith(u"LIBARCHIVE.xattr.user.meta."):
        useful_part = pax_key[len(u"LIBARCHIVE.xattr.user.meta."):]
        if six.PY2:
            return "X-Object-Meta-" + useful_part.encode("utf-8")
        return str_to_wsgi("X-Object-Meta-" + useful_part)
    else:
        # You can get things like atime/mtime/ctime or filesystem ACLs in
        # pax headers; those aren't really user metadata. The same goes for
        # other, non-user metadata.
        return None
Пример #27
0
 def _create_container(self, app, policy_name='one', container_path=None):
     if not container_path:
         # choose new container name so that the policy can be specified
         self.container_name = uuid.uuid4().hex
         self.container_path = 'http://foo:8080/v1/a/' + self.container_name
         self.object_name = 'o'
         self.object_path = self.container_path + '/' + self.object_name
         container_path = self.container_path
     req = Request.blank(str_to_wsgi(container_path),
                         method='PUT',
                         headers={'X-Storage-Policy': policy_name})
     resp = req.get_response(app)
     self.assertEqual('201 Created', resp.status)
     # sanity check
     req = Request.blank(str_to_wsgi(container_path),
                         method='HEAD',
                         headers={'X-Storage-Policy': policy_name})
     resp = req.get_response(app)
     self.assertEqual(policy_name, resp.headers['X-Storage-Policy'])
Пример #28
0
 def do_delete(obj_name, delete_path):
     delete_obj_req = make_subrequest(
         req.environ,
         method='DELETE',
         path=wsgi_quote(str_to_wsgi(delete_path)),
         headers={'X-Auth-Token': req.headers.get('X-Auth-Token')},
         body='',
         agent='%(orig)s ' + user_agent,
         swift_source=swift_source)
     return (delete_obj_req.get_response(self.app), obj_name, 0)
Пример #29
0
 def _create_container(self, app, policy_name='one', container_path=None):
     if not container_path:
         # choose new container name so that the policy can be specified
         self.container_name = uuid.uuid4().hex
         self.container_path = 'http://foo:8080/v1/a/' + self.container_name
         self.object_name = 'o'
         self.object_path = self.container_path + '/' + self.object_name
         container_path = self.container_path
     req = Request.blank(
         str_to_wsgi(container_path), method='PUT',
         headers={'X-Storage-Policy': policy_name})
     resp = req.get_response(app)
     self.assertEqual('201 Created', resp.status)
     # sanity check
     req = Request.blank(
         str_to_wsgi(container_path), method='HEAD',
         headers={'X-Storage-Policy': policy_name})
     resp = req.get_response(app)
     self.assertEqual(policy_name, resp.headers['X-Storage-Policy'])
Пример #30
0
def pax_key_to_swift_header(pax_key):
    if (pax_key == u"SCHILY.xattr.user.mime_type"
            or pax_key == u"LIBARCHIVE.xattr.user.mime_type"):
        return "Content-Type"
    elif pax_key.startswith(u"SCHILY.xattr.user.meta."):
        useful_part = pax_key[len(u"SCHILY.xattr.user.meta."):]
        if six.PY2:
            return "X-Object-Meta-" + useful_part.encode("utf-8")
        return str_to_wsgi("X-Object-Meta-" + useful_part)
    elif pax_key.startswith(u"LIBARCHIVE.xattr.user.meta."):
        useful_part = pax_key[len(u"LIBARCHIVE.xattr.user.meta."):]
        if six.PY2:
            return "X-Object-Meta-" + useful_part.encode("utf-8")
        return str_to_wsgi("X-Object-Meta-" + useful_part)
    else:
        # You can get things like atime/mtime/ctime or filesystem ACLs in
        # pax headers; those aren't really user metadata. The same goes for
        # other, non-user metadata.
        return None
Пример #31
0
    def create_listing(self, req, out_content_type, info, resp_headers,
                       metadata, container_list, container):
        for key, (value, timestamp) in metadata.items():
            if value and (key.lower() in self.save_headers or
                          is_sys_or_user_meta('container', key)):
                resp_headers[str_to_wsgi(key)] = str_to_wsgi(value)
        listing = [self.update_data_record(record)
                   for record in container_list]
        if out_content_type.endswith('/xml'):
            body = listing_formats.container_to_xml(listing, container)
        elif out_content_type.endswith('/json'):
            body = json.dumps(listing).encode('ascii')
        else:
            body = listing_formats.listing_to_text(listing)

        ret = Response(request=req, headers=resp_headers, body=body,
                       content_type=out_content_type, charset='utf-8')
        ret.last_modified = math.ceil(float(resp_headers['X-PUT-Timestamp']))
        if not ret.body:
            ret.status_int = HTTP_NO_CONTENT
        return ret
Пример #32
0
    def handle_delete(self, req, start_response):
        """
        Handle request to delete a user's container.

        As part of deleting a container, this middleware will also delete
        the hidden container holding object versions.

        Before a user's container can be deleted, swift must check
        if there are still old object versions from that container.
        Only after disabling versioning and deleting *all* object versions
        can a container be deleted.
        """
        container_info = get_container_info(req.environ,
                                            self.app,
                                            swift_source='OV')

        versions_cont = unquote(
            container_info.get('sysmeta', {}).get('versions-container', ''))

        if versions_cont:
            account = req.split_path(3, 3, True)[1]
            # using a HEAD request here as opposed to get_container_info
            # to make sure we get an up-to-date value
            versions_req = make_pre_authed_request(
                req.environ,
                method='HEAD',
                swift_source='OV',
                path=wsgi_quote('/v1/%s/%s' %
                                (account, str_to_wsgi(versions_cont))),
                headers={'X-Backend-Allow-Reserved-Names': 'true'})
            vresp = versions_req.get_response(self.app)
            drain_and_close(vresp)
            if vresp.is_success and int(
                    vresp.headers.get('X-Container-Object-Count', 0)) > 0:
                raise HTTPConflict(
                    'Delete all versions before deleting container.',
                    request=req)
            elif not vresp.is_success and vresp.status_int != 404:
                raise HTTPInternalServerError(
                    'Error deleting versioned container')
            else:
                versions_req.method = 'DELETE'
                resp = versions_req.get_response(self.app)
                drain_and_close(resp)
                if not is_success(resp.status_int) and resp.status_int != 404:
                    raise HTTPInternalServerError(
                        'Error deleting versioned container')

        app_resp = self._app_call(req.environ)

        start_response(self._response_status, self._response_headers,
                       self._response_exc_info)
        return app_resp
Пример #33
0
def get_response_headers(broker):
    info = broker.get_info()
    resp_headers = {
        'X-Account-Container-Count': info['container_count'],
        'X-Account-Object-Count': info['object_count'],
        'X-Account-Bytes-Used': info['bytes_used'],
        'X-Timestamp': Timestamp(info['created_at']).normal,
        'X-PUT-Timestamp': Timestamp(info['put_timestamp']).normal
    }
    policy_stats = broker.get_policy_stats()
    for policy_idx, stats in policy_stats.items():
        policy = POLICIES.get_by_index(policy_idx)
        if not policy:
            continue
        header_prefix = 'X-Account-Storage-Policy-%s-%%s' % policy.name
        for key, value in stats.items():
            header_name = header_prefix % key.replace('_', '-')
            resp_headers[header_name] = value
    resp_headers.update(
        (str_to_wsgi(key), str_to_wsgi(value))
        for key, (value, _timestamp) in broker.metadata.items() if value != '')
    return resp_headers
Пример #34
0
 def delete_filter(predicate, objs_to_delete):
     for obj_to_delete in objs_to_delete:
         obj_name = obj_to_delete['name']
         if not obj_name:
             continue
         if not predicate(obj_name):
             continue
         if obj_to_delete.get('error'):
             if obj_to_delete['error']['code'] == HTTP_NOT_FOUND:
                 resp_dict['Number Not Found'] += 1
             else:
                 failed_files.append([
                     wsgi_quote(str_to_wsgi(obj_name)),
                     obj_to_delete['error']['message']])
             continue
         delete_path = '/'.join(['', vrs, account,
                                 obj_name.lstrip('/')])
         if not constraints.check_utf8(delete_path):
             failed_files.append([wsgi_quote(str_to_wsgi(obj_name)),
                                  HTTPPreconditionFailed().status])
             continue
         yield (obj_name, delete_path)
Пример #35
0
    def _check_GET_and_HEAD(self, app, object_path=None):
        object_path = str_to_wsgi(object_path or self.object_path)
        req = Request.blank(object_path, method='GET')
        resp = req.get_response(app)
        self.assertEqual('200 OK', resp.status)
        self.assertEqual(self.plaintext, resp.body)
        self.assertEqual('Kiwi', resp.headers['X-Object-Meta-Fruit'])

        req = Request.blank(object_path, method='HEAD')
        resp = req.get_response(app)
        self.assertEqual('200 OK', resp.status)
        self.assertEqual(b'', resp.body)
        self.assertEqual('Kiwi', resp.headers['X-Object-Meta-Fruit'])
Пример #36
0
    def _check_GET_and_HEAD(self, app, object_path=None):
        object_path = str_to_wsgi(object_path or self.object_path)
        req = Request.blank(object_path, method='GET')
        resp = req.get_response(app)
        self.assertEqual('200 OK', resp.status)
        self.assertEqual(self.plaintext, resp.body)
        self.assertEqual('Kiwi', resp.headers['X-Object-Meta-Fruit'])

        req = Request.blank(object_path, method='HEAD')
        resp = req.get_response(app)
        self.assertEqual('200 OK', resp.status)
        self.assertEqual(b'', resp.body)
        self.assertEqual('Kiwi', resp.headers['X-Object-Meta-Fruit'])
Пример #37
0
 def _test_get_path(self, host, path, anonymous=False, expected_status=200,
                    expected_in=[], expected_not_in=[]):
     self.env.account.conn.make_request(
         'GET', str_to_wsgi(path),
         hdrs={'X-Web-Mode': str(not anonymous), 'Host': host},
         cfg={'no_auth_token': anonymous, 'absolute_path': True})
     self.assert_status(expected_status)
     body = self.env.account.conn.response.read()
     if not six.PY2:
         body = body.decode('utf8')
     for string in expected_in:
         self.assertIn(string, body)
     for string in expected_not_in:
         self.assertNotIn(string, body)
Пример #38
0
 def _check_listing(self, app, expect_mismatch=False, container_path=None):
     container_path = str_to_wsgi(container_path or self.container_path)
     req = Request.blank(
         container_path, method='GET', query_string='format=json')
     resp = req.get_response(app)
     self.assertEqual('200 OK', resp.status)
     listing = json.loads(resp.body)
     self.assertEqual(1, len(listing))
     self.assertEqual(self.object_name, listing[0]['name'])
     self.assertEqual(len(self.plaintext), listing[0]['bytes'])
     if expect_mismatch:
         self.assertNotEqual(self.plaintext_etag, listing[0]['hash'])
     else:
         self.assertEqual(self.plaintext_etag, listing[0]['hash'])
Пример #39
0
 def _check_listing(self, app, expect_mismatch=False, container_path=None):
     container_path = str_to_wsgi(container_path or self.container_path)
     req = Request.blank(
         container_path, method='GET', query_string='format=json')
     resp = req.get_response(app)
     self.assertEqual('200 OK', resp.status)
     listing = json.loads(resp.body)
     self.assertEqual(1, len(listing))
     self.assertEqual(self.object_name, listing[0]['name'])
     self.assertEqual(len(self.plaintext), listing[0]['bytes'])
     if expect_mismatch:
         self.assertNotEqual(self.plaintext_etag, listing[0]['hash'])
     else:
         self.assertEqual(self.plaintext_etag, listing[0]['hash'])
Пример #40
0
    def _process_delete(self, resp, pile, obj_name, resp_dict,
                        failed_files, failed_file_response, retry=0):
        if resp.status_int // 100 == 2:
            resp_dict['Number Deleted'] += 1
        elif resp.status_int == HTTP_NOT_FOUND:
            resp_dict['Number Not Found'] += 1
        elif resp.status_int == HTTP_UNAUTHORIZED:
            failed_files.append([wsgi_quote(str_to_wsgi(obj_name)),
                                 HTTPUnauthorized().status])
        elif resp.status_int == HTTP_CONFLICT and pile and \
                self.retry_count > 0 and self.retry_count > retry:
            retry += 1
            sleep(self.retry_interval ** retry)
            delete_obj_req = Request.blank(resp.environ['PATH_INFO'],
                                           resp.environ)

            def _retry(req, app, obj_name, retry):
                return req.get_response(app), obj_name, retry
            pile.spawn(_retry, delete_obj_req, self.app, obj_name, retry)
        else:
            if resp.status_int // 100 == 5:
                failed_file_response['type'] = HTTPBadGateway
            failed_files.append([wsgi_quote(str_to_wsgi(obj_name)),
                                 resp.status])
Пример #41
0
    def object_request(self, req, api_version, account, container, obj):
        """
        Handle request for object resource.

        Note that account, container, obj should be unquoted by caller
        if the url path is under url encoding (e.g. %FF)

        :param req: swift.common.swob.Request instance
        :param api_version: should be v1 unless swift bumps api version
        :param account: account name string
        :param container: container name string
        :param object: object name string
        """
        resp = None
        container_info = get_container_info(req.environ,
                                            self.app,
                                            swift_source='OV')

        versions_cont = container_info.get('sysmeta',
                                           {}).get('versions-container', '')
        is_enabled = config_true_value(
            container_info.get('sysmeta', {}).get('versions-enabled'))

        if versions_cont:
            versions_cont = wsgi_unquote(
                str_to_wsgi(versions_cont)).split('/')[0]

        if req.params.get('version-id'):
            vw_ctx = OioObjectContext(self.app, self.logger)
            resp = vw_ctx.handle_versioned_request(req, versions_cont,
                                                   api_version, account,
                                                   container, obj, is_enabled,
                                                   req.params['version-id'])
        elif versions_cont:
            # handle object request for a enabled versioned container
            vw_ctx = OioObjectContext(self.app, self.logger)
            resp = vw_ctx.handle_request(req, versions_cont, api_version,
                                         account, container, obj, is_enabled)

        if resp:
            return resp
        else:
            return self.app
Пример #42
0
 def verify_v1_keys_for_path(self, wsgi_path, expected_keys, key_id=None):
     put_keys = None
     self.app.meta_version_to_write = '1'
     for method, resp_class, status in (('PUT', swob.HTTPCreated, '201'),
                                        ('POST', swob.HTTPAccepted, '202'),
                                        ('GET', swob.HTTPOk, '200'),
                                        ('HEAD', swob.HTTPNoContent,
                                         '204')):
         resp_headers = {}
         self.swift.register(method, '/v1' + wsgi_path, resp_class,
                             resp_headers, b'')
         req = Request.blank('/v1' + wsgi_path,
                             environ={'REQUEST_METHOD': method})
         start_response, calls = capture_start_response()
         self.app(req.environ, start_response)
         self.assertEqual(1, len(calls))
         self.assertTrue(calls[0][0].startswith(status))
         self.assertNotIn('swift.crypto.override', req.environ)
         self.assertIn(CRYPTO_KEY_CALLBACK, req.environ,
                       '%s not set in env' % CRYPTO_KEY_CALLBACK)
         keys = req.environ.get(CRYPTO_KEY_CALLBACK)(key_id=key_id)
         self.assertIn('id', keys)
         id = keys.pop('id')
         path = swob.wsgi_to_str(wsgi_path)
         if '//' in path:
             path = path[path.index('//') + 1:]
         if six.PY2:
             self.assertEqual(path, id['path'])
         else:
             self.assertEqual(swob.str_to_wsgi(path), id['path'])
         self.assertEqual('1', id['v'])
         keys.pop('all_ids')
         self.assertListEqual(
             sorted(expected_keys), sorted(keys.keys()),
             '%s %s got keys %r, but expected %r' %
             (method, path, keys.keys(), expected_keys))
         if put_keys is not None:
             # check all key sets were consistent for this path
             self.assertDictEqual(put_keys, keys)
         else:
             put_keys = keys
     self.app.meta_version_to_write = '2'  # Clean up after ourselves
     return put_keys
Пример #43
0
    def handle_container(self, env, start_response):
        """
        Handles a possible static web request for a container.

        :param env: The original WSGI environment dict.
        :param start_response: The original WSGI start_response hook.
        """
        container_info = self._get_container_info(env)
        req = Request(env)
        req.acl = container_info['read_acl']
        # we checked earlier that swift.authorize is set in env
        aresp = env['swift.authorize'](req)
        if aresp:
            resp = aresp(env, self._start_response)
            return self._error_response(resp, env, start_response)

        if not self._listings and not self._index:
            if config_true_value(env.get('HTTP_X_WEB_MODE', 'f')):
                return HTTPNotFound()(env, start_response)
            return self.app(env, start_response)
        if not env['PATH_INFO'].endswith('/'):
            return self._redirect_with_slash(env, start_response)
        if not self._index:
            return self._listing(env, start_response)
        tmp_env = dict(env)
        tmp_env['HTTP_USER_AGENT'] = \
            '%s StaticWeb' % env.get('HTTP_USER_AGENT')
        tmp_env['swift.source'] = 'SW'
        tmp_env['PATH_INFO'] += str_to_wsgi(self._index)
        resp = self._app_call(tmp_env)
        status_int = self._get_status_int()
        if status_int == HTTP_NOT_FOUND:
            return self._listing(env, start_response)
        elif not is_success(self._get_status_int()) and \
                not is_redirection(self._get_status_int()):
            return self._error_response(resp, env, start_response)
        start_response(self._response_status, self._response_headers,
                       self._response_exc_info)
        return resp
Пример #44
0
    def _get_from_shards(self, req, resp):
        # construct listing using shards described by the response body
        shard_ranges = [ShardRange.from_dict(data)
                        for data in json.loads(resp.body)]
        self.app.logger.debug('GET listing from %s shards for: %s',
                              len(shard_ranges), req.path_qs)
        if not shard_ranges:
            # can't find ranges or there was a problem getting the ranges. So
            # return what we have.
            return resp

        objects = []
        req_limit = int(req.params.get('limit', CONTAINER_LISTING_LIMIT))
        params = req.params.copy()
        params.pop('states', None)
        req.headers.pop('X-Backend-Record-Type', None)
        reverse = config_true_value(params.get('reverse'))
        marker = params.get('marker')
        end_marker = params.get('end_marker')

        limit = req_limit
        for shard_range in shard_ranges:
            params['limit'] = limit
            # Always set marker to ensure that object names less than or equal
            # to those already in the listing are not fetched; if the listing
            # is empty then the original request marker, if any, is used. This
            # allows misplaced objects below the expected shard range to be
            # included in the listing.
            if objects:
                last_name = objects[-1].get('name',
                                            objects[-1].get('subdir', u''))
                params['marker'] = last_name.encode('utf-8')
            elif marker:
                params['marker'] = marker
            else:
                params['marker'] = ''
            # Always set end_marker to ensure that misplaced objects beyond the
            # expected shard range are not fetched. This prevents a misplaced
            # object obscuring correctly placed objects in the next shard
            # range.
            if end_marker and end_marker in shard_range:
                params['end_marker'] = end_marker
            elif reverse:
                params['end_marker'] = str_to_wsgi(shard_range.lower_str)
            else:
                params['end_marker'] = str_to_wsgi(shard_range.end_marker)

            if (shard_range.account == self.account_name and
                    shard_range.container == self.container_name):
                # directed back to same container - force GET of objects
                headers = {'X-Backend-Record-Type': 'object'}
            else:
                headers = None
            self.app.logger.debug('Getting from %s %s with %s',
                                  shard_range, shard_range.name, headers)
            objs, shard_resp = self._get_container_listing(
                req, shard_range.account, shard_range.container,
                headers=headers, params=params)

            if not objs:
                # tolerate errors or empty shard containers
                continue

            objects.extend(objs)
            limit -= len(objs)

            if limit <= 0:
                break
            if (end_marker and reverse and
                (wsgi_to_bytes(end_marker) >=
                 objects[-1]['name'].encode('utf-8'))):
                break
            if (end_marker and not reverse and
                (wsgi_to_bytes(end_marker) <=
                 objects[-1]['name'].encode('utf-8'))):
                break

        resp.body = json.dumps(objects).encode('ascii')
        constrained = any(req.params.get(constraint) for constraint in (
            'marker', 'end_marker', 'path', 'prefix', 'delimiter'))
        if not constrained and len(objects) < req_limit:
            self.app.logger.debug('Setting object count to %s' % len(objects))
            # prefer the actual listing stats over the potentially outdated
            # root stats. This condition is only likely when a sharded
            # container is shrinking or in tests; typically a sharded container
            # will have more than CONTAINER_LISTING_LIMIT objects so any
            # unconstrained listing will be capped by the limit and total
            # object stats cannot therefore be inferred from the listing.
            resp.headers['X-Container-Object-Count'] = len(objects)
            resp.headers['X-Container-Bytes-Used'] = sum(
                [o['bytes'] for o in objects])
        return resp
Пример #45
0
    def _perform_subrequest(self, orig_env, attributes, fp, keys):
        """
        Performs the subrequest and returns the response.

        :param orig_env: The WSGI environment dict; will only be used
                         to form a new env for the subrequest.
        :param attributes: dict of the attributes of the form so far.
        :param fp: The file-like object containing the request body.
        :param keys: The account keys to validate the signature with.
        :returns: (status_line, headers_list)
        """
        if not keys:
            raise FormUnauthorized('invalid signature')
        try:
            max_file_size = int(attributes.get('max_file_size') or 0)
        except ValueError:
            raise FormInvalid('max_file_size not an integer')
        subenv = make_pre_authed_env(orig_env, 'PUT', agent=None,
                                     swift_source='FP')
        if 'QUERY_STRING' in subenv:
            del subenv['QUERY_STRING']
        subenv['HTTP_TRANSFER_ENCODING'] = 'chunked'
        subenv['wsgi.input'] = _CappedFileLikeObject(fp, max_file_size)
        if not subenv['PATH_INFO'].endswith('/') and \
                subenv['PATH_INFO'].count('/') < 4:
            subenv['PATH_INFO'] += '/'
        subenv['PATH_INFO'] += str_to_wsgi(
            attributes['filename'] or 'filename')
        if 'x_delete_at' in attributes:
            try:
                subenv['HTTP_X_DELETE_AT'] = int(attributes['x_delete_at'])
            except ValueError:
                raise FormInvalid('x_delete_at not an integer: '
                                  'Unix timestamp required.')
        if 'x_delete_after' in attributes:
            try:
                subenv['HTTP_X_DELETE_AFTER'] = int(
                    attributes['x_delete_after'])
            except ValueError:
                raise FormInvalid('x_delete_after not an integer: '
                                  'Number of seconds required.')
        if 'content-type' in attributes:
            subenv['CONTENT_TYPE'] = \
                attributes['content-type'] or 'application/octet-stream'
        if 'content-encoding' in attributes:
            subenv['HTTP_CONTENT_ENCODING'] = attributes['content-encoding']
        try:
            if int(attributes.get('expires') or 0) < time():
                raise FormUnauthorized('form expired')
        except ValueError:
            raise FormInvalid('expired not an integer')
        hmac_body = '%s\n%s\n%s\n%s\n%s' % (
            wsgi_to_str(orig_env['PATH_INFO']),
            attributes.get('redirect') or '',
            attributes.get('max_file_size') or '0',
            attributes.get('max_file_count') or '0',
            attributes.get('expires') or '0')
        if six.PY3:
            hmac_body = hmac_body.encode('utf-8')

        has_valid_sig = False
        for key in keys:
            # Encode key like in swift.common.utls.get_hmac.
            if not isinstance(key, six.binary_type):
                key = key.encode('utf8')
            sig = hmac.new(key, hmac_body, sha1).hexdigest()
            if streq_const_time(sig, (attributes.get('signature') or
                                      'invalid')):
                has_valid_sig = True
        if not has_valid_sig:
            raise FormUnauthorized('invalid signature')

        substatus = [None]
        subheaders = [None]

        wsgi_input = subenv['wsgi.input']

        def _start_response(status, headers, exc_info=None):
            if wsgi_input.file_size_exceeded:
                raise EOFError("max_file_size exceeded")

            substatus[0] = status
            subheaders[0] = headers

        # reiterate to ensure the response started,
        # but drop any data on the floor
        close_if_possible(reiterate(self.app(subenv, _start_response)))
        return substatus[0], subheaders[0]
Пример #46
0
    def __call__(self, env, start_response):
        if not self.storage_domain:
            return self.app(env, start_response)
        if 'HTTP_HOST' in env:
            requested_host = env['HTTP_HOST']
        else:
            requested_host = env['SERVER_NAME']
        given_domain = wsgi_to_str(requested_host)
        port = ''
        if ':' in given_domain:
            given_domain, port = given_domain.rsplit(':', 1)
        if is_valid_ip(given_domain):
            return self.app(env, start_response)
        a_domain = given_domain
        if not self._domain_endswith_in_storage_domain(a_domain):
            if self.memcache is None:
                self.memcache = cache_from_env(env)
            error = True
            for tries in range(self.lookup_depth):
                found_domain = None
                if self.memcache:
                    memcache_key = ''.join(['cname-', a_domain])
                    found_domain = self.memcache.get(memcache_key)
                    if six.PY2 and found_domain:
                        found_domain = found_domain.encode('utf-8')
                if found_domain is None:
                    ttl, found_domain = lookup_cname(a_domain, self.resolver)
                    if self.memcache and ttl > 0:
                        memcache_key = ''.join(['cname-', given_domain])
                        self.memcache.set(memcache_key, found_domain, time=ttl)
                if not found_domain or found_domain == a_domain:
                    # no CNAME records or we're at the last lookup
                    error = True
                    found_domain = None
                    break
                elif self._domain_endswith_in_storage_domain(found_domain):
                    # Found it!
                    self.logger.info(
                        _('Mapped %(given_domain)s to %(found_domain)s') % {
                            'given_domain': given_domain,
                            'found_domain': found_domain
                        })
                    if port:
                        env['HTTP_HOST'] = ':'.join(
                            [str_to_wsgi(found_domain), port])
                    else:
                        env['HTTP_HOST'] = str_to_wsgi(found_domain)
                    error = False
                    break
                else:
                    # try one more deep in the chain
                    self.logger.debug(
                        _('Following CNAME chain for  '
                          '%(given_domain)s to %(found_domain)s') % {
                              'given_domain': given_domain,
                              'found_domain': found_domain
                          })
                    a_domain = found_domain
            if error:
                if found_domain:
                    msg = 'CNAME lookup failed after %d tries' % \
                        self.lookup_depth
                else:
                    msg = 'CNAME lookup failed to resolve to a valid domain'
                resp = HTTPBadRequest(request=Request(env),
                                      body=msg,
                                      content_type='text/plain')
                return resp(env, start_response)
            else:
                context = _CnameLookupContext(self.app, requested_host,
                                              env['HTTP_HOST'])
                return context.handle_request(env, start_response)

        return self.app(env, start_response)
Пример #47
0
    def _get_from_shards(self, req, resp):
        # Construct listing using shards described by the response body.
        # The history of containers that have returned shard ranges is
        # maintained in the request environ so that loops can be avoided by
        # forcing an object listing if the same container is visited again.
        # This can happen in at least two scenarios:
        #   1. a container has filled a gap in its shard ranges with a
        #      shard range pointing to itself
        #   2. a root container returns a (stale) shard range pointing to a
        #      shard that has shrunk into the root, in which case the shrunken
        #      shard may return the root's shard range.
        shard_listing_history = req.environ.setdefault(
            'swift.shard_listing_history', [])
        shard_listing_history.append((self.account_name, self.container_name))
        shard_ranges = [
            ShardRange.from_dict(data) for data in json.loads(resp.body)
        ]
        self.app.logger.debug('GET listing from %s shards for: %s',
                              len(shard_ranges), req.path_qs)
        if not shard_ranges:
            # can't find ranges or there was a problem getting the ranges. So
            # return what we have.
            return resp

        objects = []
        req_limit = constrain_req_limit(req, CONTAINER_LISTING_LIMIT)
        params = req.params.copy()
        params.pop('states', None)
        req.headers.pop('X-Backend-Record-Type', None)
        reverse = config_true_value(params.get('reverse'))
        marker = wsgi_to_str(params.get('marker'))
        end_marker = wsgi_to_str(params.get('end_marker'))
        prefix = wsgi_to_str(params.get('prefix'))

        limit = req_limit
        for i, shard_range in enumerate(shard_ranges):
            params['limit'] = limit
            # Always set marker to ensure that object names less than or equal
            # to those already in the listing are not fetched; if the listing
            # is empty then the original request marker, if any, is used. This
            # allows misplaced objects below the expected shard range to be
            # included in the listing.
            if objects:
                last_name = objects[-1].get('name',
                                            objects[-1].get('subdir', u''))
                params['marker'] = bytes_to_wsgi(last_name.encode('utf-8'))
            elif marker:
                params['marker'] = str_to_wsgi(marker)
            else:
                params['marker'] = ''
            # Always set end_marker to ensure that misplaced objects beyond the
            # expected shard range are not fetched. This prevents a misplaced
            # object obscuring correctly placed objects in the next shard
            # range.
            if end_marker and end_marker in shard_range:
                params['end_marker'] = str_to_wsgi(end_marker)
            elif reverse:
                params['end_marker'] = str_to_wsgi(shard_range.lower_str)
            else:
                params['end_marker'] = str_to_wsgi(shard_range.end_marker)

            headers = {}
            if ((shard_range.account, shard_range.container)
                    in shard_listing_history):
                # directed back to same container - force GET of objects
                headers['X-Backend-Record-Type'] = 'object'
            if config_true_value(req.headers.get('x-newest', False)):
                headers['X-Newest'] = 'true'

            if prefix:
                if prefix > shard_range:
                    continue
                try:
                    just_past = prefix[:-1] + chr(ord(prefix[-1]) + 1)
                except ValueError:
                    pass
                else:
                    if just_past < shard_range:
                        continue

            self.app.logger.debug(
                'Getting listing part %d from shard %s %s with %s', i,
                shard_range, shard_range.name, headers)
            objs, shard_resp = self._get_container_listing(
                req,
                shard_range.account,
                shard_range.container,
                headers=headers,
                params=params)

            sharding_state = shard_resp.headers.get('x-backend-sharding-state',
                                                    'unknown')

            if objs is None:
                # tolerate errors
                self.app.logger.debug(
                    'Failed to get objects from shard (state=%s), total = %d',
                    sharding_state, len(objects))
                continue

            self.app.logger.debug(
                'Found %d objects in shard (state=%s), total = %d', len(objs),
                sharding_state,
                len(objs) + len(objects))

            if not objs:
                # tolerate empty shard containers
                continue

            objects.extend(objs)
            limit -= len(objs)

            if limit <= 0:
                break
            last_name = objects[-1].get('name', objects[-1].get('subdir', u''))
            if six.PY2:
                last_name = last_name.encode('utf8')
            if end_marker and reverse and end_marker >= last_name:
                break
            if end_marker and not reverse and end_marker <= last_name:
                break

        resp.body = json.dumps(objects).encode('ascii')
        constrained = any(
            req.params.get(constraint)
            for constraint in ('marker', 'end_marker', 'path', 'prefix',
                               'delimiter'))
        if not constrained and len(objects) < req_limit:
            self.app.logger.debug('Setting object count to %s' % len(objects))
            # prefer the actual listing stats over the potentially outdated
            # root stats. This condition is only likely when a sharded
            # container is shrinking or in tests; typically a sharded container
            # will have more than CONTAINER_LISTING_LIMIT objects so any
            # unconstrained listing will be capped by the limit and total
            # object stats cannot therefore be inferred from the listing.
            resp.headers['X-Container-Object-Count'] = len(objects)
            resp.headers['X-Container-Bytes-Used'] = sum(
                [o['bytes'] for o in objects])
        return resp
Пример #48
0
    def _get_from_shards(self, req, resp):
        # construct listing using shards described by the response body
        shard_ranges = [
            ShardRange.from_dict(data) for data in json.loads(resp.body)
        ]
        self.app.logger.debug('GET listing from %s shards for: %s',
                              len(shard_ranges), req.path_qs)
        if not shard_ranges:
            # can't find ranges or there was a problem getting the ranges. So
            # return what we have.
            return resp

        objects = []
        req_limit = int(req.params.get('limit', CONTAINER_LISTING_LIMIT))
        params = req.params.copy()
        params.pop('states', None)
        req.headers.pop('X-Backend-Record-Type', None)
        reverse = config_true_value(params.get('reverse'))
        marker = params.get('marker')
        end_marker = params.get('end_marker')

        limit = req_limit
        for shard_range in shard_ranges:
            params['limit'] = limit
            # Always set marker to ensure that object names less than or equal
            # to those already in the listing are not fetched; if the listing
            # is empty then the original request marker, if any, is used. This
            # allows misplaced objects below the expected shard range to be
            # included in the listing.
            if objects:
                last_name = objects[-1].get('name',
                                            objects[-1].get('subdir', u''))
                params['marker'] = last_name.encode('utf-8')
            elif marker:
                params['marker'] = marker
            else:
                params['marker'] = ''
            # Always set end_marker to ensure that misplaced objects beyond the
            # expected shard range are not fetched. This prevents a misplaced
            # object obscuring correctly placed objects in the next shard
            # range.
            if end_marker and end_marker in shard_range:
                params['end_marker'] = end_marker
            elif reverse:
                params['end_marker'] = str_to_wsgi(shard_range.lower_str)
            else:
                params['end_marker'] = str_to_wsgi(shard_range.end_marker)

            if (shard_range.account == self.account_name
                    and shard_range.container == self.container_name):
                # directed back to same container - force GET of objects
                headers = {'X-Backend-Record-Type': 'object'}
            else:
                headers = None
            self.app.logger.debug('Getting from %s %s with %s', shard_range,
                                  shard_range.name, headers)
            objs, shard_resp = self._get_container_listing(
                req,
                shard_range.account,
                shard_range.container,
                headers=headers,
                params=params)

            if not objs:
                # tolerate errors or empty shard containers
                continue

            objects.extend(objs)
            limit -= len(objs)

            if limit <= 0:
                break
            if (end_marker and reverse
                    and (wsgi_to_bytes(end_marker) >=
                         objects[-1]['name'].encode('utf-8'))):
                break
            if (end_marker and not reverse
                    and (wsgi_to_bytes(end_marker) <=
                         objects[-1]['name'].encode('utf-8'))):
                break

        resp.body = json.dumps(objects).encode('ascii')
        constrained = any(
            req.params.get(constraint)
            for constraint in ('marker', 'end_marker', 'path', 'prefix',
                               'delimiter'))
        if not constrained and len(objects) < req_limit:
            self.app.logger.debug('Setting object count to %s' % len(objects))
            # prefer the actual listing stats over the potentially outdated
            # root stats. This condition is only likely when a sharded
            # container is shrinking or in tests; typically a sharded container
            # will have more than CONTAINER_LISTING_LIMIT objects so any
            # unconstrained listing will be capped by the limit and total
            # object stats cannot therefore be inferred from the listing.
            resp.headers['X-Container-Object-Count'] = len(objects)
            resp.headers['X-Container-Bytes-Used'] = sum(
                [o['bytes'] for o in objects])
        return resp