Beispiel #1
0
 def _make_request(self, nodes, part, method, path, headers, query,
                   logger_thread_locals):
     self.app.logger.thread_locals = logger_thread_locals
     for node in nodes:
         try:
             with ConnectionTimeout(self.app.conn_timeout):
                 conn = http_connect(node['ip'],
                                     node['port'],
                                     node['device'],
                                     part,
                                     method,
                                     path,
                                     headers=headers,
                                     query_string=query)
                 conn.node = node
             with Timeout(self.app.node_timeout):
                 resp = conn.getresponse()
                 if not is_informational(resp.status) and \
                    not is_server_error(resp.status):
                     return resp.status, resp.reason, resp.read()
                 elif resp.status == HTTP_INSUFFICIENT_STORAGE:
                     self.error_limit(node)
         except (Exception, Timeout):
             self.exception_occurred(
                 node, self.server_type,
                 _('Trying to %(method)s %(path)s') % {
                     'method': method,
                     'path': path
                 })
Beispiel #2
0
 def _make_request(self, nodes, part, method, path, headers, query, logger_thread_locals):
     self.app.logger.thread_locals = logger_thread_locals
     for node in nodes:
         try:
             with ConnectionTimeout(self.app.conn_timeout):
                 conn = http_connect(
                     node["ip"],
                     node["port"],
                     node["device"],
                     part,
                     method,
                     path,
                     headers=headers,
                     query_string=query,
                 )
                 conn.node = node
             with Timeout(self.app.node_timeout):
                 resp = conn.getresponse()
                 if not is_informational(resp.status) and not is_server_error(resp.status):
                     return resp.status, resp.reason, resp.read()
                 elif resp.status == HTTP_INSUFFICIENT_STORAGE:
                     self.error_limit(node)
         except (Exception, Timeout):
             self.exception_occurred(
                 node, self.server_type, _("Trying to %(method)s %(path)s") % {"method": method, "path": path}
             )
Beispiel #3
0
    def _make_request(self, nodes, part, method, path, headers, query,
                      logger_thread_locals):
        """
        Iterates over the given node iterator, sending an HTTP request to one
        node at a time.  The first non-informational, non-server-error
        response is returned.  If no non-informational, non-server-error
        response is received from any of the nodes, returns None.

        :param nodes: an iterator of the backend server and handoff servers
        :param part: the partition number
        :param method: the method to send to the backend
        :param path: the path to send to the backend
                     (full path ends up being /<$device>/<$part>/<$path>)
        :param headers: a list of dicts, where each dict represents one
                        backend request that should be made.
        :param query: query string to send to the backend.
        :param logger_thread_locals: The thread local values to be set on the
                                     self.app.logger to retain transaction
                                     logging information.
        :returns: a swob.Response object, or None if no responses were received
        """
        self.app.logger.thread_locals = logger_thread_locals
        for node in nodes:
            try:
                start_node_timing = time.time()
                with ConnectionTimeout(self.app.conn_timeout):
                    conn = http_connect(node['ip'],
                                        node['port'],
                                        node['device'],
                                        part,
                                        method,
                                        path,
                                        headers=headers,
                                        query_string=query)
                    conn.node = node
                self.app.set_node_timing(node, time.time() - start_node_timing)
                with Timeout(self.app.node_timeout):
                    resp = conn.getresponse()
                    if not is_informational(resp.status) and \
                            not is_server_error(resp.status):
                        return resp.status, resp.reason, resp.getheaders(), \
                            resp.read()
                    elif resp.status == HTTP_INSUFFICIENT_STORAGE:
                        self.app.error_limit(node,
                                             _('ERROR Insufficient Storage'))
            except (Exception, Timeout):
                self.app.exception_occurred(
                    node, self.server_type,
                    _('Trying to %(method)s %(path)s') % {
                        'method': method,
                        'path': path
                    })
Beispiel #4
0
def retry(func, *args, **kwargs):
    """
    Helper function to retry a given function a number of times.

    :param func: callable to be called
    :param retries: number of retries
    :param error_log: logger for errors
    :param args: arguments to send to func
    :param kwargs: keyward arguments to send to func (if retries or
                   error_log are sent, they will be deleted from kwargs
                   before sending on to func)
    :returns: result of func
    :raises ClientException: all retries failed
    """
    retries = 5
    if 'retries' in kwargs:
        retries = kwargs['retries']
        del kwargs['retries']
    error_log = None
    if 'error_log' in kwargs:
        error_log = kwargs['error_log']
        del kwargs['error_log']
    attempts = 0
    backoff = 1
    while attempts <= retries:
        attempts += 1
        try:
            return attempts, func(*args, **kwargs)
        except (socket.error, HTTPException, Timeout) as err:
            if error_log:
                error_log(err)
            if attempts > retries:
                raise
        except ClientException as err:
            if error_log:
                error_log(err)
            if attempts > retries or not is_server_error(err.http_status) or \
                    err.http_status == HTTP_INSUFFICIENT_STORAGE:
                raise
        sleep(backoff)
        backoff *= 2
    # Shouldn't actually get down here, but just in case.
    if args and 'ip' in args[0]:
        raise ClientException('Raise too many retries',
                              http_host=args[0]['ip'],
                              http_port=args[0]['port'],
                              http_device=args[0]['device'])
    else:
        raise ClientException('Raise too many retries')
Beispiel #5
0
    def _make_request(self, nodes, part, method, path, headers, query,
                      logger_thread_locals):
        """
        Sends an HTTP request to a single node and aggregates the result.
        It attempts the primary node, then iterates over the handoff nodes
        as needed.

        :param nodes: an iterator of the backend server and handoff servers
        :param part: the partition number
        :param method: the method to send to the backend
        :param path: the path to send to the backend
        :param headers: a list of dicts, where each dict represents one
                        backend request that should be made.
        :param query: query string to send to the backend.
        :param logger_thread_locals: The thread local values to be set on the
                                     self.app.logger to retain transaction
                                     logging information.
        :returns: a swob.Response object
        """
        self.app.logger.thread_locals = logger_thread_locals
        for node in nodes:
            try:
                start_node_timing = time.time()
                with ConnectionTimeout(self.app.conn_timeout):
                    conn = http_connect(node['ip'],
                                        node['port'],
                                        node['device'],
                                        part,
                                        method,
                                        path,
                                        headers=headers,
                                        query_string=query)
                    conn.node = node
                self.app.set_node_timing(node, time.time() - start_node_timing)
                with Timeout(self.app.node_timeout):
                    resp = conn.getresponse()
                    if not is_informational(resp.status) and \
                            not is_server_error(resp.status):
                        return resp.status, resp.reason, resp.getheaders(), \
                            resp.read()
                    elif resp.status == HTTP_INSUFFICIENT_STORAGE:
                        self.error_limit(node, _('ERROR Insufficient Storage'))
            except (Exception, Timeout):
                self.exception_occurred(
                    node, self.server_type,
                    _('Trying to %(method)s %(path)s') % {
                        'method': method,
                        'path': path
                    })
Beispiel #6
0
def retry(func, *args, **kwargs):
    """
    Helper function to retry a given function a number of times.

    :param func: callable to be called
    :param retries: number of retries
    :param error_log: logger for errors
    :param args: arguments to send to func
    :param kwargs: keyward arguments to send to func (if retries or
                   error_log are sent, they will be deleted from kwargs
                   before sending on to func)
    :returns: result of func
    :raises ClientException: all retries failed
    """
    retries = 5
    if 'retries' in kwargs:
        retries = kwargs['retries']
        del kwargs['retries']
    error_log = None
    if 'error_log' in kwargs:
        error_log = kwargs['error_log']
        del kwargs['error_log']
    attempts = 0
    backoff = 1
    while attempts <= retries:
        attempts += 1
        try:
            return attempts, func(*args, **kwargs)
        except (socket.error, HTTPException, Timeout) as err:
            if error_log:
                error_log(err)
            if attempts > retries:
                raise
        except ClientException as err:
            if error_log:
                error_log(err)
            if attempts > retries or not is_server_error(err.http_status) or \
                    err.http_status == HTTP_INSUFFICIENT_STORAGE:
                raise
        sleep(backoff)
        backoff *= 2
    # Shouldn't actually get down here, but just in case.
    if args and 'ip' in args[0]:
        raise ClientException('Raise too many retries',
                              http_host=args[
                              0]['ip'], http_port=args[0]['port'],
                              http_device=args[0]['device'])
    else:
        raise ClientException('Raise too many retries')
Beispiel #7
0
    def get_controller(self, req):
        """
        Get the controller to handle a request.

        :param req: the request
        :returns: tuple of (controller class, path dictionary)

        :raises ValueError: (thrown by split_path) if given invalid path
        """
        if req.path == '/info':
            d = dict(version=None,
                     expose_info=self.expose_info,
                     disallowed_sections=self.disallowed_sections,
                     admin_key=self.admin_key)
            return InfoController, d

        version, account, container, obj = split_path(wsgi_to_str(req.path), 1,
                                                      4, True)
        d = dict(version=version,
                 account_name=account,
                 container_name=container,
                 object_name=obj)
        if account and not valid_api_version(version):
            raise APIVersionError('Invalid path')
        if obj and container and account:
            info = get_container_info(req.environ, self)
            if is_server_error(info.get('status')):
                raise HTTPServiceUnavailable(request=req)
            policy_index = req.headers.get('X-Backend-Storage-Policy-Index',
                                           info['storage_policy'])
            policy = POLICIES.get_by_index(policy_index)
            if not policy:
                # This indicates that a new policy has been created,
                # with rings, deployed, released (i.e. deprecated =
                # False), used by a client to create a container via
                # another proxy that was restarted after the policy
                # was released, and is now cached - all before this
                # worker was HUPed to stop accepting new
                # connections.  There should never be an "unknown"
                # index - but when there is - it's probably operator
                # error and hopefully temporary.
                raise HTTPServiceUnavailable('Unknown Storage Policy')
            return self.obj_controller_router[policy], d
        elif container and account:
            return ContainerController, d
        elif account and not container and not obj:
            return AccountController, d
        return None, d
Beispiel #8
0
    def _make_request(self, nodes, part, method, path, headers, query, logger_thread_locals):
        """
        Iterates over the given node iterator, sending an HTTP request to one
        node at a time.  The first non-informational, non-server-error
        response is returned.  If no non-informational, non-server-error
        response is received from any of the nodes, returns None.

        :param nodes: an iterator of the backend server and handoff servers
        :param part: the partition number
        :param method: the method to send to the backend
        :param path: the path to send to the backend
        :param headers: a list of dicts, where each dict represents one
                        backend request that should be made.
        :param query: query string to send to the backend.
        :param logger_thread_locals: The thread local values to be set on the
                                     self.app.logger to retain transaction
                                     logging information.
        :returns: a swob.Response object, or None if no responses were received
        """
        self.app.logger.thread_locals = logger_thread_locals
        for node in nodes:
            try:
                start_node_timing = time.time()
                with ConnectionTimeout(self.app.conn_timeout):
                    conn = http_connect(
                        node["ip"],
                        node["port"],
                        node["device"],
                        part,
                        method,
                        path,
                        headers=headers,
                        query_string=query,
                    )
                    conn.node = node
                self.app.set_node_timing(node, time.time() - start_node_timing)
                with Timeout(self.app.node_timeout):
                    resp = conn.getresponse()
                    if not is_informational(resp.status) and not is_server_error(resp.status):
                        return resp.status, resp.reason, resp.getheaders(), resp.read()
                    elif resp.status == HTTP_INSUFFICIENT_STORAGE:
                        self.error_limit(node, _("ERROR Insufficient Storage"))
            except (Exception, Timeout):
                self.exception_occurred(
                    node, self.server_type, _("Trying to %(method)s %(path)s") % {"method": method, "path": path}
                )
Beispiel #9
0
    def _connect_put_node(self, nodes, part, path, headers,
                          logger_thread_locals):
        """
        Make a connection for a replicated object.

        Connects to the first working node that it finds in node_iter
        and sends over the request headers. Returns an HTTPConnection
        object to handle the rest of the streaming.
        """
        self.app.logger.thread_locals = logger_thread_locals
        for node in nodes:
            try:
                start_time = time.time()
                with ConnectionTimeout(self.app.conn_timeout):
                    conn = http_connect(
                        node['ip'], node['port'], node['device'], part, 'PUT',
                        path, headers)
                self.app.set_node_timing(node, time.time() - start_time)
                with Timeout(self.app.node_timeout):
                    resp = conn.getexpect()
                if resp.status == HTTP_CONTINUE:
                    conn.resp = None
                    conn.node = node
                    return conn
                elif is_success(resp.status) or resp.status == HTTP_CONFLICT:
                    conn.resp = resp
                    conn.node = node
                    return conn
                elif headers['If-None-Match'] is not None and \
                        resp.status == HTTP_PRECONDITION_FAILED:
                    conn.resp = resp
                    conn.node = node
                    return conn
                elif resp.status == HTTP_INSUFFICIENT_STORAGE:
                    self.app.error_limit(node, _('ERROR Insufficient Storage'))
                elif is_server_error(resp.status):
                    self.app.error_occurred(
                        node,
                        _('ERROR %(status)d Expect: 100-continue '
                          'From Object Server') % {
                              'status': resp.status})
            except (Exception, Timeout):
                self.app.exception_occurred(
                    node, _('Object'),
                    _('Expect: 100-continue on %s') % path)
Beispiel #10
0
 def _connect_put_node(self, nodes, part, path, headers,
                       logger_thread_locals):
     """
     Make a connection for a replicated object.
     Connects to the first working node that it finds in node_iter
     and sends over the request headers. Returns an HTTPConnection
     object to handle the rest of the streaming.
     """
     self.app.logger.thread_locals = logger_thread_locals
     for node in nodes:
         print("===In _connect_put_node===", node)
         try:
             start_time = time.time()
             with ConnectionTimeout(self.app.conn_timeout):
                 conn = http_connect(node['ip'], node['port'],
                                     node['device'], part, 'PUT', path,
                                     headers)
             self.app.set_node_timing(node, time.time() - start_time)
             with Timeout(self.app.node_timeout):
                 resp = conn.getexpect()
             if resp.status == HTTP_CONTINUE:
                 conn.resp = None
                 conn.node = node
                 return conn
             elif is_success(resp.status) or resp.status == HTTP_CONFLICT:
                 conn.resp = resp
                 conn.node = node
                 return conn
             elif headers['If-None-Match'] is not None and \
                     resp.status == HTTP_PRECONDITION_FAILED:
                 conn.resp = resp
                 conn.node = node
                 return conn
             elif resp.status == HTTP_INSUFFICIENT_STORAGE:
                 self.app.error_limit(node, _('ERROR Insufficient Storage'))
             elif is_server_error(resp.status):
                 self.app.error_occurred(
                     node,
                     _('ERROR %(status)d Expect: 100-continue '
                       'From Object Server') % {'status': resp.status})
         except (Exception, Timeout):
             self.app.exception_occurred(
                 node, _('Object'),
                 _('Expect: 100-continue on %s') % path)
Beispiel #11
0
    def _make_request(self, nodes, part, method, path, headers, query,
                      logger_thread_locals):
        """
        Sends an HTTP request to a single node and aggregates the result.
        It attempts the primary node, then iterates over the handoff nodes
        as needed.

        :param nodes: an iterator of the backend server and handoff servers
        :param part: the partition number
        :param method: the method to send to the backend
        :param path: the path to send to the backend
        :param headers: a list of dicts, where each dict represents one
                        backend request that should be made.
        :param query: query string to send to the backend.
        :param logger_thread_locals: The thread local values to be set on the
                                     self.app.logger to retain transaction
                                     logging information.
        :returns: a swob.Response object
        """
        self.app.logger.thread_locals = logger_thread_locals
        for node in nodes:
            try:
                start_node_timing = time.time()
                with ConnectionTimeout(self.app.conn_timeout):
                    conn = http_connect(node['ip'], node['port'],
                                        node['device'], part, method, path,
                                        headers=headers, query_string=query)
                    conn.node = node
                self.app.set_node_timing(node, time.time() - start_node_timing)
                with Timeout(self.app.node_timeout):
                    resp = conn.getresponse()
                    if not is_informational(resp.status) and \
                            not is_server_error(resp.status):
                        return resp.status, resp.reason, resp.getheaders(), \
                            resp.read()
                    elif resp.status == HTTP_INSUFFICIENT_STORAGE:
                        self.error_limit(node, _('ERROR Insufficient Storage'))
            except (Exception, Timeout):
                self.exception_occurred(node, self.server_type,
                                        _('Trying to %(method)s %(path)s') %
                                        {'method': method, 'path': path})
Beispiel #12
0
 def _connect_put_node(self, nodes, part, path, headers,
                       logger_thread_locals):
     """Method for a file PUT connect"""
     self.app.logger.thread_locals = logger_thread_locals
     for node in nodes:
         try:
             start_time = time.time()
             with ConnectionTimeout(self.app.conn_timeout):
                 conn = http_connect(node['ip'], node['port'],
                                     node['device'], part, 'PUT', path,
                                     headers)
             self.app.set_node_timing(node, time.time() - start_time)
             with Timeout(self.app.node_timeout):
                 resp = conn.getexpect()
             if resp.status == HTTP_CONTINUE:
                 conn.resp = None
                 conn.node = node
                 return conn
             elif is_success(resp.status) or resp.status == HTTP_CONFLICT:
                 conn.resp = resp
                 conn.node = node
                 return conn
             elif headers['If-None-Match'] is not None and \
                     resp.status == HTTP_PRECONDITION_FAILED:
                 conn.resp = resp
                 conn.node = node
                 return conn
             elif resp.status == HTTP_INSUFFICIENT_STORAGE:
                 self.app.error_limit(node, _('ERROR Insufficient Storage'))
             elif is_server_error(resp.status):
                 self.app.error_occurred(
                     node,
                     _('ERROR %(status)d Expect: 100-continue '
                       'From Object Server') % {'status': resp.status})
         except (Exception, Timeout):
             self.app.exception_occurred(
                 node, _('Object'),
                 _('Expect: 100-continue on %s') % path)
Beispiel #13
0
def retry(func, *args, **kwargs):
    """
    Helper function to retry a given function a number of times.

    :param func: callable to be called
    :param retries: number of retries
    :param error_log: logger for errors
    :param args: arguments to send to func
    :param kwargs: keyward arguments to send to func (if retries or
                   error_log are sent, they will be deleted from kwargs
                   before sending on to func)
    :returns: restult of func
    """
    retries = 5
    if 'retries' in kwargs:
        retries = kwargs['retries']
        del kwargs['retries']
    error_log = None
    if 'error_log' in kwargs:
        error_log = kwargs['error_log']
        del kwargs['error_log']
    attempts = 0
    backoff = 1
    while attempts <= retries:
        attempts += 1
        try:
            return attempts, func(*args, **kwargs)
        except (socket.error, HTTPException, Timeout), err:
            if error_log:
                error_log(err)
            if attempts > retries:
                raise
        except ClientException, err:
            if error_log:
                error_log(err)
            if attempts > retries or not is_server_error(err.http_status) or \
                    err.http_status == HTTP_INSUFFICIENT_STORAGE:
                raise
Beispiel #14
0
 def _connect_put_node(self, nodes, part, path, headers,
                       logger_thread_locals):
     """Method for a file PUT connect"""
     self.app.logger.thread_locals = logger_thread_locals
     for node in nodes:
         try:
             start_time = time.time()
             with ConnectionTimeout(self.app.conn_timeout):
                 conn = http_connect(
                     node['ip'], node['port'], node['device'], part, 'PUT',
                     path, headers)
             self.app.set_node_timing(node, time.time() - start_time)
             with Timeout(self.app.node_timeout):
                 resp = conn.getexpect()
             if resp.status == HTTP_CONTINUE:
                 conn.resp = None
                 conn.node = node
                 return conn
             elif is_success(resp.status):
                 conn.resp = resp
                 conn.node = node
                 return conn
             elif headers['If-None-Match'] is not None and \
                     resp.status == HTTP_PRECONDITION_FAILED:
                 conn.resp = resp
                 conn.node = node
                 return conn
             elif resp.status == HTTP_INSUFFICIENT_STORAGE:
                 self.app.error_limit(node, _('ERROR Insufficient Storage'))
             elif is_server_error(resp.status):
                 self.app.error_occurred(
                     node, _('ERROR %(status)d Expect: 100-continue '
                             'From Object Server') % {
                                 'status': resp.status})
         except (Exception, Timeout):
             self.app.exception_occurred(
                 node, _('Object'),
                 _('Expect: 100-continue on %s') % path)
Beispiel #15
0
def retry(func, *args, **kwargs):
    """
    Helper function to retry a given function a number of times.

    :param func: callable to be called
    :param retries: number of retries
    :param error_log: logger for errors
    :param args: arguments to send to func
    :param kwargs: keyward arguments to send to func (if retries or
                   error_log are sent, they will be deleted from kwargs
                   before sending on to func)
    :returns: restult of func
    """
    retries = 5
    if 'retries' in kwargs:
        retries = kwargs['retries']
        del kwargs['retries']
    error_log = None
    if 'error_log' in kwargs:
        error_log = kwargs['error_log']
        del kwargs['error_log']
    attempts = 0
    backoff = 1
    while attempts <= retries:
        attempts += 1
        try:
            return attempts, func(*args, **kwargs)
        except (socket.error, HTTPException, Timeout), err:
            if error_log:
                error_log(err)
            if attempts > retries:
                raise
        except ClientException, err:
            if error_log:
                error_log(err)
            if attempts > retries or not is_server_error(err.http_status) or \
                    err.http_status == HTTP_INSUFFICIENT_STORAGE:
                raise
Beispiel #16
0
 def _make_request(self, nodes, part, method, path, headers, query,
                   logger_thread_locals):
     self.app.logger.thread_locals = logger_thread_locals
     for node in nodes:
         try:
             start_node_timing = time.time()
             with ConnectionTimeout(self.app.conn_timeout):
                 conn = http_connect(node['ip'], node['port'],
                                     node['device'], part, method, path,
                                     headers=headers, query_string=query)
                 conn.node = node
             self.app.set_node_timing(node, time.time() - start_node_timing)
             with Timeout(self.app.node_timeout):
                 resp = conn.getresponse()
                 if not is_informational(resp.status) and \
                         not is_server_error(resp.status):
                     return resp.status, resp.reason, resp.read()
                 elif resp.status == HTTP_INSUFFICIENT_STORAGE:
                     self.error_limit(node, _('ERROR Insufficient Storage'))
         except (Exception, Timeout):
             self.exception_occurred(node, self.server_type,
                                     _('Trying to %(method)s %(path)s') %
                                     {'method': method, 'path': path})
Beispiel #17
0
    def GETorHEAD_base(self, req, server_type, partition, nodes, path, attempts):
        """
        Base handler for HTTP GET or HEAD requests.

        :param req: swob.Request object
        :param server_type: server type
        :param partition: partition
        :param nodes: nodes
        :param path: path for the request
        :param attempts: number of attempts to try
        :returns: swob.Response object
        """
        statuses = []
        reasons = []
        bodies = []
        sources = []
        newest = config_true_value(req.headers.get("x-newest", "f"))
        nodes = iter(nodes)
        while len(statuses) < attempts:
            try:
                node = nodes.next()
            except StopIteration:
                break
            if self.error_limited(node):
                continue
            try:
                with ConnectionTimeout(self.app.conn_timeout):
                    headers = dict(req.headers)
                    headers["Connection"] = "close"
                    conn = http_connect(
                        node["ip"],
                        node["port"],
                        node["device"],
                        partition,
                        req.method,
                        path,
                        headers=headers,
                        query_string=req.query_string,
                    )
                with Timeout(self.app.node_timeout):
                    possible_source = conn.getresponse()
                    # See NOTE: swift_conn at top of file about this.
                    possible_source.swift_conn = conn
            except (Exception, Timeout):
                self.exception_occurred(
                    node, server_type, _("Trying to %(method)s %(path)s") % {"method": req.method, "path": req.path}
                )
                continue
            if self.is_good_source(possible_source):
                # 404 if we know we don't have a synced copy
                if not float(possible_source.getheader("X-PUT-Timestamp", 1)):
                    statuses.append(HTTP_NOT_FOUND)
                    reasons.append("")
                    bodies.append("")
                    self.close_swift_conn(possible_source)
                else:
                    statuses.append(possible_source.status)
                    reasons.append(possible_source.reason)
                    bodies.append("")
                    sources.append(possible_source)
                    if not newest:  # one good source is enough
                        break
            else:
                statuses.append(possible_source.status)
                reasons.append(possible_source.reason)
                bodies.append(possible_source.read())
                if possible_source.status == HTTP_INSUFFICIENT_STORAGE:
                    self.error_limit(node)
                elif is_server_error(possible_source.status):
                    self.error_occurred(
                        node,
                        _("ERROR %(status)d %(body)s " "From %(type)s Server")
                        % {"status": possible_source.status, "body": bodies[-1][:1024], "type": server_type},
                    )
        if sources:
            sources.sort(key=source_key)
            source = sources.pop()
            for src in sources:
                self.close_swift_conn(src)
            res = Response(request=req, conditional_response=True)
            if req.method == "GET" and source.status in (HTTP_OK, HTTP_PARTIAL_CONTENT):
                res.app_iter = self._make_app_iter(node, source)
                # See NOTE: swift_conn at top of file about this.
                res.swift_conn = source.swift_conn
            res.status = source.status
            update_headers(res, source.getheaders())
            if not res.environ:
                res.environ = {}
            res.environ["swift_x_timestamp"] = source.getheader("x-timestamp")
            res.accept_ranges = "bytes"
            res.content_length = source.getheader("Content-Length")
            if source.getheader("Content-Type"):
                res.charset = None
                res.content_type = source.getheader("Content-Type")
            return res
        return self.best_response(req, statuses, reasons, bodies, "%s %s" % (server_type, req.method))
Beispiel #18
0
    def make_request(
            self, method, path, headers, acceptable_statuses, body_file=None,
            params=None):
        """Makes a request to Swift with retries.

        :param method: HTTP method of request.
        :param path: Path of request.
        :param headers: Headers to be sent with request.
        :param acceptable_statuses: List of acceptable statuses for request.
        :param body_file: Body file to be passed along with request,
                          defaults to None.
        :param params: A dict of params to be set in request query string,
                       defaults to None.

        :returns: Response object on success.

        :raises UnexpectedResponse: Exception raised when make_request() fails
                                    to get a response with an acceptable status
        :raises Exception: Exception is raised when code fails in an
                           unexpected way.
        """

        headers = dict(headers)
        headers['user-agent'] = self.user_agent
        for attempt in range(self.request_tries):
            resp = exc_type = exc_value = exc_traceback = None
            req = Request.blank(
                path, environ={'REQUEST_METHOD': method}, headers=headers)
            if body_file is not None:
                if hasattr(body_file, 'seek'):
                    body_file.seek(0)
                req.body_file = body_file
            if params:
                req.params = params
            try:
                resp = req.get_response(self.app)
            except (Exception, Timeout):
                exc_type, exc_value, exc_traceback = exc_info()
            else:
                if resp.status_int in acceptable_statuses or \
                        resp.status_int // 100 in acceptable_statuses:
                    return resp
                elif not is_server_error(resp.status_int):
                    # No sense retrying when we expect the same result
                    break
            # sleep only between tries, not after each one
            if attempt < self.request_tries - 1:
                if resp:
                    # always close any resp.app_iter before we discard it
                    with closing_if_possible(resp.app_iter):
                        # for non 2XX requests it's safe and useful to drain
                        # the response body so we log the correct status code
                        if resp.status_int // 100 != 2:
                            for iter_body in resp.app_iter:
                                pass
                sleep(2 ** (attempt + 1))
        if resp:
            msg = 'Unexpected response: %s' % resp.status
            if resp.status_int // 100 != 2 and resp.body:
                # provide additional context (and drain the response body) for
                # non 2XX responses
                msg += ' (%s)' % resp.body
            raise UnexpectedResponse(msg, resp)
        if exc_type:
            # To make pep8 tool happy, in place of raise t, v, tb:
            six.reraise(exc_type, exc_value, exc_traceback)
Beispiel #19
0
    def GETorHEAD_base(self, req, server_type, ring, partition, path):
        """
        Base handler for HTTP GET or HEAD requests.

        :param req: swob.Request object
        :param server_type: server type
        :param ring: the ring to obtain nodes from
        :param partition: partition
        :param path: path for the request
        :returns: swob.Response object
        """
        statuses = []
        reasons = []
        bodies = []
        source_headers = []
        sources = []
        newest = config_true_value(req.headers.get('x-newest', 'f'))
        headers = self.generate_request_headers(req, additional=req.headers)
        for node in self.iter_nodes(ring, partition):
            start_node_timing = time.time()
            try:
                with ConnectionTimeout(self.app.conn_timeout):
                    conn = http_connect(
                        node['ip'], node['port'], node['device'], partition,
                        req.method, path, headers=headers,
                        query_string=req.query_string)
                self.app.set_node_timing(node, time.time() - start_node_timing)
                with Timeout(self.app.node_timeout):
                    possible_source = conn.getresponse()
                    # See NOTE: swift_conn at top of file about this.
                    possible_source.swift_conn = conn
            except (Exception, Timeout):
                self.exception_occurred(
                    node, server_type, _('Trying to %(method)s %(path)s') %
                    {'method': req.method, 'path': req.path})
                continue
            if self.is_good_source(possible_source):
                # 404 if we know we don't have a synced copy
                if not float(possible_source.getheader('X-PUT-Timestamp', 1)):
                    statuses.append(HTTP_NOT_FOUND)
                    reasons.append('')
                    bodies.append('')
                    source_headers.append('')
                    self.close_swift_conn(possible_source)
                else:
                    statuses.append(possible_source.status)
                    reasons.append(possible_source.reason)
                    bodies.append('')
                    source_headers.append('')
                    sources.append((possible_source, node))
                    if not newest:  # one good source is enough
                        break
            else:
                statuses.append(possible_source.status)
                reasons.append(possible_source.reason)
                bodies.append(possible_source.read())
                source_headers.append(possible_source.getheaders())
                if possible_source.status == HTTP_INSUFFICIENT_STORAGE:
                    self.error_limit(node, _('ERROR Insufficient Storage'))
                elif is_server_error(possible_source.status):
                    self.error_occurred(node, _('ERROR %(status)d %(body)s '
                                                'From %(type)s Server') %
                                        {'status': possible_source.status,
                                         'body': bodies[-1][:1024],
                                         'type': server_type})
        res = None
        if sources:
            sources.sort(key=lambda s: source_key(s[0]))
            source, node = sources.pop()
            for src, _junk in sources:
                self.close_swift_conn(src)
            res = Response(request=req)
            if req.method == 'GET' and \
                    source.status in (HTTP_OK, HTTP_PARTIAL_CONTENT):
                res.app_iter = self._make_app_iter(node, source)
                # See NOTE: swift_conn at top of file about this.
                res.swift_conn = source.swift_conn
            res.status = source.status
            update_headers(res, source.getheaders())
            if not res.environ:
                res.environ = {}
            res.environ['swift_x_timestamp'] = \
                source.getheader('x-timestamp')
            res.accept_ranges = 'bytes'
            res.content_length = source.getheader('Content-Length')
            if source.getheader('Content-Type'):
                res.charset = None
                res.content_type = source.getheader('Content-Type')
        if not res:
            res = self.best_response(req, statuses, reasons, bodies,
                                     '%s %s' % (server_type, req.method),
                                     headers=source_headers)
        try:
            (account, container) = split_path(req.path_info, 1, 2)
            _set_info_cache(self.app, req.environ, account, container, res)
        except ValueError:
            pass
        try:
            (account, container, obj) = split_path(req.path_info, 3, 3, True)
            _set_object_info_cache(self.app, req.environ, account,
                                   container, obj, res)
        except ValueError:
            pass
        return res
Beispiel #20
0
    def account_info(self, account, autocreate=False):
        """
        Get account information, and also verify that the account exists.

        :param account: name of the account to get the info for
        :returns: tuple of (account partition, account nodes, container_count)
                  or (None, None, None) if it does not exist
        """
        partition, nodes = self.app.account_ring.get_nodes(account)
        account_info = {'status': 0,
                        'container_count': 0,
                        'total_object_count': None,
                        'bytes': None,
                        'meta': {}}
        # 0 = no responses, 200 = found, 404 = not found, -1 = mixed responses
        if self.app.memcache:
            cache_key = get_account_memcache_key(account)
            cache_value = self.app.memcache.get(cache_key)
            if not isinstance(cache_value, dict):
                result_code = cache_value
                container_count = 0
            else:
                result_code = cache_value['status']
                try:
                    container_count = int(cache_value['container_count'])
                except ValueError:
                    container_count = 0
            if result_code == HTTP_OK:
                return partition, nodes, container_count
            elif result_code == HTTP_NOT_FOUND and not autocreate:
                return None, None, None
        result_code = 0
        path = '/%s' % account
        headers = {'x-trans-id': self.trans_id, 'Connection': 'close'}
        for node in self.iter_nodes(self.app.account_ring, partition):
            try:
                start_node_timing = time.time()
                with ConnectionTimeout(self.app.conn_timeout):
                    conn = http_connect(node['ip'], node['port'],
                                        node['device'], partition, 'HEAD',
                                        path, headers)
                self.app.set_node_timing(node, time.time() - start_node_timing)
                with Timeout(self.app.node_timeout):
                    resp = conn.getresponse()
                    body = resp.read()
                    if is_success(resp.status):
                        result_code = HTTP_OK
                        account_info.update(
                            headers_to_account_info(resp.getheaders()))
                        break
                    elif resp.status == HTTP_NOT_FOUND:
                        if result_code == 0:
                            result_code = HTTP_NOT_FOUND
                        elif result_code != HTTP_NOT_FOUND:
                            result_code = -1
                    elif resp.status == HTTP_INSUFFICIENT_STORAGE:
                        self.error_limit(node, _('ERROR Insufficient Storage'))
                        continue
                    else:
                        result_code = -1
                        if is_server_error(resp.status):
                            self.error_occurred(
                                node,
                                _('ERROR %(status)d %(body)s From Account '
                                  'Server') %
                                {'status': resp.status, 'body': body[:1024]})
            except (Exception, Timeout):
                self.exception_occurred(node, _('Account'),
                                        _('Trying to get account info for %s')
                                        % path)
        if result_code == HTTP_NOT_FOUND and autocreate:
            if len(account) > MAX_ACCOUNT_NAME_LENGTH:
                return None, None, None
            headers = {'X-Timestamp': normalize_timestamp(time.time()),
                       'X-Trans-Id': self.trans_id,
                       'Connection': 'close'}
            resp = self.make_requests(Request.blank('/v1' + path),
                                      self.app.account_ring, partition, 'PUT',
                                      path, [headers] * len(nodes))
            if not is_success(resp.status_int):
                self.app.logger.warning('Could not autocreate account %r' %
                                        path)
                return None, None, None
            result_code = HTTP_OK
        if self.app.memcache and result_code in (HTTP_OK, HTTP_NOT_FOUND):
            if result_code == HTTP_OK:
                cache_timeout = self.app.recheck_account_existence
            else:
                cache_timeout = self.app.recheck_account_existence * 0.1
            account_info.update(status=result_code)
            self.app.memcache.set(cache_key,
                                  account_info,
                                  time=cache_timeout)
        if result_code == HTTP_OK:
            try:
                container_count = int(account_info['container_count'])
            except ValueError:
                container_count = 0
            return partition, nodes, container_count
        return None, None, None
Beispiel #21
0
    def container_info(self, account, container, account_autocreate=False):
        """
        Get container information and thusly verify container existence.
        This will also make a call to account_info to verify that the
        account exists.

        :param account: account name for the container
        :param container: container name to look up
        :returns: dict containing at least container partition ('partition'),
                  container nodes ('containers'), container read
                  acl ('read_acl'), container write acl ('write_acl'),
                  and container sync key ('sync_key').
                  Values are set to None if the container does not exist.
        """
        part, nodes = self.app.container_ring.get_nodes(account, container)
        path = '/%s/%s' % (account, container)
        container_info = {'status': 0, 'read_acl': None,
                          'write_acl': None, 'sync_key': None,
                          'count': None, 'bytes': None,
                          'versions': None, 'partition': None,
                          'nodes': None}
        if self.app.memcache:
            cache_key = get_container_memcache_key(account, container)
            cache_value = self.app.memcache.get(cache_key)
            if isinstance(cache_value, dict):
                if 'container_size' in cache_value:
                    cache_value['count'] = cache_value['container_size']
                if is_success(cache_value['status']):
                    container_info.update(cache_value)
                    container_info['partition'] = part
                    container_info['nodes'] = nodes
                return container_info
        if not self.account_info(account, autocreate=account_autocreate)[1]:
            return container_info
        headers = {'x-trans-id': self.trans_id, 'Connection': 'close'}
        for node in self.iter_nodes(self.app.container_ring, part):
            try:
                start_node_timing = time.time()
                with ConnectionTimeout(self.app.conn_timeout):
                    conn = http_connect(node['ip'], node['port'],
                                        node['device'], part, 'HEAD',
                                        path, headers)
                self.app.set_node_timing(node, time.time() - start_node_timing)
                with Timeout(self.app.node_timeout):
                    resp = conn.getresponse()
                    body = resp.read()
                if is_success(resp.status):
                    container_info.update(
                        headers_to_container_info(resp.getheaders()))
                    break
                elif resp.status == HTTP_NOT_FOUND:
                    container_info['status'] = HTTP_NOT_FOUND
                else:
                    container_info['status'] = -1
                    if resp.status == HTTP_INSUFFICIENT_STORAGE:
                        self.error_limit(node, _('ERROR Insufficient Storage'))
                    elif is_server_error(resp.status):
                        self.error_occurred(node, _(
                            'ERROR %(status)d %(body)s From Container '
                            'Server') %
                            {'status': resp.status, 'body': body[:1024]})
            except (Exception, Timeout):
                self.exception_occurred(
                    node, _('Container'),
                    _('Trying to get container info for %s') % path)
        if self.app.memcache:
            if container_info['status'] == HTTP_OK:
                self.app.memcache.set(
                    cache_key, container_info,
                    time=self.app.recheck_container_existence)
            elif container_info['status'] == HTTP_NOT_FOUND:
                self.app.memcache.set(
                    cache_key, container_info,
                    time=self.app.recheck_container_existence * 0.1)
        if container_info['status'] == HTTP_OK:
            container_info['partition'] = part
            container_info['nodes'] = nodes
        return container_info
    def _requests_to_bytes_iter(self):
        # Take the requests out of self._coalesce_requests, actually make
        # the requests, and generate the bytes from the responses.
        #
        # Yields 2-tuples (segment-name, byte-chunk). The segment name is
        # used for logging.
        for data_or_req, seg_etag, seg_size in self._coalesce_requests():
            if isinstance(data_or_req, bytes):  # ugly, awful overloading
                yield ('data segment', data_or_req)
                continue
            seg_req = data_or_req
            seg_resp = seg_req.get_response(self.app)
            if not is_success(seg_resp.status_int):
                # Error body should be short
                body = seg_resp.body
                if not six.PY2:
                    body = body.decode('utf8')
                msg = 'While processing manifest %s, got %d (%s) ' \
                    'while retrieving %s' % (
                        self.name, seg_resp.status_int,
                        body if len(body) <= 60 else body[:57] + '...',
                        seg_req.path)
                if is_server_error(seg_resp.status_int):
                    self.logger.error(msg)
                    raise HTTPServiceUnavailable(request=seg_req,
                                                 content_type='text/plain')
                raise SegmentError(msg)
            elif (
                (seg_etag and (seg_resp.etag != seg_etag)) or
                (seg_size and
                 (seg_resp.content_length != seg_size) and not seg_req.range)):
                # The content-length check is for security reasons. Seems
                # possible that an attacker could upload a >1mb object and
                # then replace it with a much smaller object with same
                # etag. Then create a big nested SLO that calls that
                # object many times which would hammer our obj servers. If
                # this is a range request, don't check content-length
                # because it won't match.
                close_if_possible(seg_resp.app_iter)
                raise SegmentError(
                    'Object segment no longer valid: '
                    '%(path)s etag: %(r_etag)s != %(s_etag)s or '
                    '%(r_size)s != %(s_size)s.' % {
                        'path': seg_req.path,
                        'r_etag': seg_resp.etag,
                        'r_size': seg_resp.content_length,
                        's_etag': seg_etag,
                        's_size': seg_size
                    })
            else:
                self.current_resp = seg_resp

            resp_len = 0
            seg_hash = None
            if seg_resp.etag and not seg_req.headers.get('Range'):
                # Only calculate the MD5 if it we can use it to validate
                seg_hash = md5(usedforsecurity=False)

            document_iters = maybe_multipart_byteranges_to_document_iters(
                seg_resp.app_iter, seg_resp.headers['Content-Type'])

            for chunk in itertools.chain.from_iterable(document_iters):
                if seg_hash:
                    seg_hash.update(chunk)
                    resp_len += len(chunk)
                yield (seg_req.path, chunk)
            close_if_possible(seg_resp.app_iter)

            if seg_hash:
                if resp_len != seg_resp.content_length:
                    raise SegmentError(
                        "Bad response length for %(seg)s as part of %(name)s: "
                        "headers had %(from_headers)s, but response length "
                        "was actually %(actual)s" % {
                            'seg': seg_req.path,
                            'from_headers': seg_resp.content_length,
                            'name': self.name,
                            'actual': resp_len
                        })
                if seg_hash.hexdigest() != seg_resp.etag:
                    raise SegmentError(
                        "Bad MD5 checksum for %(seg)s as part of %(name)s: "
                        "headers had %(etag)s, but object MD5 was actually "
                        "%(actual)s" % {
                            'seg': seg_req.path,
                            'etag': seg_resp.etag,
                            'name': self.name,
                            'actual': seg_hash.hexdigest()
                        })
Beispiel #23
0
    def _get_source_and_node(self):

        self.statuses = []
        self.reasons = []
        self.bodies = []
        self.source_headers = []
        sources = []

        for node in self.app.iter_nodes(self.ring, self.partition):
            if node in self.used_nodes:
                continue
            start_node_timing = time.time()
            try:
                with ConnectionTimeout(self.app.conn_timeout):
                    conn = http_connect(node['ip'],
                                        node['port'],
                                        node['device'],
                                        self.partition,
                                        self.req_method,
                                        self.path,
                                        headers=self.backend_headers,
                                        query_string=self.req_query_string)
                self.app.set_node_timing(node, time.time() - start_node_timing)

                with Timeout(self.app.node_timeout):
                    possible_source = conn.getresponse()
                    # See NOTE: swift_conn at top of file about this.
                    possible_source.swift_conn = conn
            except (Exception, Timeout):
                self.app.exception_occurred(
                    node, self.server_type,
                    _('Trying to %(method)s %(path)s') % {
                        'method': self.req_method,
                        'path': self.req_path
                    })
                continue
            if self.is_good_source(possible_source):
                # 404 if we know we don't have a synced copy
                if not float(possible_source.getheader('X-PUT-Timestamp', 1)):
                    self.statuses.append(HTTP_NOT_FOUND)
                    self.reasons.append('')
                    self.bodies.append('')
                    self.source_headers.append('')
                    close_swift_conn(possible_source)
                else:
                    if self.used_source_etag:
                        src_headers = dict(
                            (k.lower(), v)
                            for k, v in possible_source.getheaders())
                        if src_headers.get('etag', '').strip('"') != \
                                self.used_source_etag:
                            self.statuses.append(HTTP_NOT_FOUND)
                            self.reasons.append('')
                            self.bodies.append('')
                            self.source_headers.append('')
                            continue

                    self.statuses.append(possible_source.status)
                    self.reasons.append(possible_source.reason)
                    self.bodies.append('')
                    self.source_headers.append('')
                    sources.append((possible_source, node))
                    if not self.newest:  # one good source is enough
                        break
            else:
                self.statuses.append(possible_source.status)
                self.reasons.append(possible_source.reason)
                self.bodies.append(possible_source.read())
                self.source_headers.append(possible_source.getheaders())
                if possible_source.status == HTTP_INSUFFICIENT_STORAGE:
                    self.app.error_limit(node, _('ERROR Insufficient Storage'))
                elif is_server_error(possible_source.status):
                    self.app.error_occurred(
                        node,
                        _('ERROR %(status)d %(body)s '
                          'From %(type)s Server') % {
                              'status': possible_source.status,
                              'body': self.bodies[-1][:1024],
                              'type': self.server_type
                          })

        if sources:
            sources.sort(key=lambda s: source_key(s[0]))
            source, node = sources.pop()
            for src, _junk in sources:
                close_swift_conn(src)
            self.used_nodes.append(node)
            src_headers = dict(
                (k.lower(), v) for k, v in possible_source.getheaders())
            self.used_source_etag = src_headers.get('etag', '').strip('"')
            return source, node
        return None, None
Beispiel #24
0
    def GETorHEAD_base(self, req, server_type, ring, partition, path):
        """
        Base handler for HTTP GET or HEAD requests.

        :param req: swob.Request object
        :param server_type: server type
        :param ring: the ring to obtain nodes from
        :param partition: partition
        :param path: path for the request
        :returns: swob.Response object
        """
        statuses = []
        reasons = []
        bodies = []
        sources = []
        newest = config_true_value(req.headers.get('x-newest', 'f'))
        for node in self.iter_nodes(ring, partition):
            start_node_timing = time.time()
            try:
                with ConnectionTimeout(self.app.conn_timeout):
                    headers = dict(req.headers)
                    headers['Connection'] = 'close'
                    conn = http_connect(node['ip'],
                                        node['port'],
                                        node['device'],
                                        partition,
                                        req.method,
                                        path,
                                        headers=headers,
                                        query_string=req.query_string)
                self.app.set_node_timing(node, time.time() - start_node_timing)
                with Timeout(self.app.node_timeout):
                    possible_source = conn.getresponse()
                    # See NOTE: swift_conn at top of file about this.
                    possible_source.swift_conn = conn
            except (Exception, Timeout):
                self.exception_occurred(
                    node, server_type,
                    _('Trying to %(method)s %(path)s') % {
                        'method': req.method,
                        'path': req.path
                    })
                continue
            if self.is_good_source(possible_source):
                # 404 if we know we don't have a synced copy
                if not float(possible_source.getheader('X-PUT-Timestamp', 1)):
                    statuses.append(HTTP_NOT_FOUND)
                    reasons.append('')
                    bodies.append('')
                    self.close_swift_conn(possible_source)
                else:
                    statuses.append(possible_source.status)
                    reasons.append(possible_source.reason)
                    bodies.append('')
                    sources.append((possible_source, node))
                    if not newest:  # one good source is enough
                        break
            else:
                statuses.append(possible_source.status)
                reasons.append(possible_source.reason)
                bodies.append(possible_source.read())
                if possible_source.status == HTTP_INSUFFICIENT_STORAGE:
                    self.error_limit(node, _('ERROR Insufficient Storage'))
                elif is_server_error(possible_source.status):
                    self.error_occurred(
                        node,
                        _('ERROR %(status)d %(body)s '
                          'From %(type)s Server') % {
                              'status': possible_source.status,
                              'body': bodies[-1][:1024],
                              'type': server_type
                          })
        if sources:
            sources.sort(key=lambda s: source_key(s[0]))
            source, node = sources.pop()
            for src, _junk in sources:
                self.close_swift_conn(src)
            res = Response(request=req, conditional_response=True)
            if req.method == 'GET' and \
                    source.status in (HTTP_OK, HTTP_PARTIAL_CONTENT):
                res.app_iter = self._make_app_iter(node, source)
                # See NOTE: swift_conn at top of file about this.
                res.swift_conn = source.swift_conn
            res.status = source.status
            update_headers(res, source.getheaders())
            if not res.environ:
                res.environ = {}
            res.environ['swift_x_timestamp'] = \
                source.getheader('x-timestamp')
            res.accept_ranges = 'bytes'
            res.content_length = source.getheader('Content-Length')
            if source.getheader('Content-Type'):
                res.charset = None
                res.content_type = source.getheader('Content-Type')
            return res
        return self.best_response(req, statuses, reasons, bodies,
                                  '%s %s' % (server_type, req.method))
Beispiel #25
0
    def _get_source_and_node(self):
        self.statuses = []
        self.reasons = []
        self.bodies = []
        self.source_headers = []
        sources = []

        node_timeout = self.app.node_timeout
        if self.server_type == 'Object' and not self.newest:
            node_timeout = self.app.recoverable_node_timeout
        for node in self.app.iter_nodes(self.ring, self.partition):
            if node in self.used_nodes:
                continue
            start_node_timing = time.time()
            try:
                with ConnectionTimeout(self.app.conn_timeout):
                    conn = http_connect(
                        node['ip'], node['port'], node['device'],
                        self.partition, self.req_method, self.path,
                        headers=self.backend_headers,
                        query_string=self.req_query_string)
                self.app.set_node_timing(node, time.time() - start_node_timing)

                with Timeout(node_timeout):
                    possible_source = conn.getresponse()
                    # See NOTE: swift_conn at top of file about this.
                    possible_source.swift_conn = conn
            except (Exception, Timeout):
                self.app.exception_occurred(
                    node, self.server_type,
                    _('Trying to %(method)s %(path)s') %
                    {'method': self.req_method, 'path': self.req_path})
                continue
            if self.is_good_source(possible_source):
                # 404 if we know we don't have a synced copy
                if not float(possible_source.getheader('X-PUT-Timestamp', 1)):
                    self.statuses.append(HTTP_NOT_FOUND)
                    self.reasons.append('')
                    self.bodies.append('')
                    self.source_headers.append('')
                    close_swift_conn(possible_source)
                else:
                    if self.used_source_etag:
                        src_headers = dict(
                            (k.lower(), v) for k, v in
                            possible_source.getheaders())
                        if src_headers.get('etag', '').strip('"') != \
                                self.used_source_etag:
                            self.statuses.append(HTTP_NOT_FOUND)
                            self.reasons.append('')
                            self.bodies.append('')
                            self.source_headers.append('')
                            continue

                    self.statuses.append(possible_source.status)
                    self.reasons.append(possible_source.reason)
                    self.bodies.append('')
                    self.source_headers.append('')
                    sources.append((possible_source, node))
                    if not self.newest:  # one good source is enough
                        break
            else:
                self.statuses.append(possible_source.status)
                self.reasons.append(possible_source.reason)
                self.bodies.append(possible_source.read())
                self.source_headers.append(possible_source.getheaders())
                if possible_source.status == HTTP_INSUFFICIENT_STORAGE:
                    self.app.error_limit(node, _('ERROR Insufficient Storage'))
                elif is_server_error(possible_source.status):
                    self.app.error_occurred(
                        node, _('ERROR %(status)d %(body)s '
                                'From %(type)s Server') %
                        {'status': possible_source.status,
                         'body': self.bodies[-1][:1024],
                         'type': self.server_type})

        if sources:
            sources.sort(key=lambda s: source_key(s[0]))
            source, node = sources.pop()
            for src, _junk in sources:
                close_swift_conn(src)
            self.used_nodes.append(node)
            src_headers = dict(
                (k.lower(), v) for k, v in
                possible_source.getheaders())
            self.used_source_etag = src_headers.get('etag', '').strip('"')
            return source, node
        return None, None
Beispiel #26
0
    def container_info(self, account, container, account_autocreate=False):
        """
        Get container information and thusly verify container existence.
        This will also make a call to account_info to verify that the
        account exists.

        :param account: account name for the container
        :param container: container name to look up
        :returns: dict containing at least container partition ('partition'),
                  container nodes ('containers'), container read
                  acl ('read_acl'), container write acl ('write_acl'),
                  and container sync key ('sync_key').
                  Values are set to None if the container does not exist.
        """
        part, nodes = self.app.container_ring.get_nodes(account, container)
        path = '/%s/%s' % (account, container)
        container_info = {
            'status': 0,
            'read_acl': None,
            'write_acl': None,
            'sync_key': None,
            'count': None,
            'bytes': None,
            'versions': None,
            'partition': None,
            'nodes': None
        }
        if self.app.memcache:
            cache_key = get_container_memcache_key(account, container)
            cache_value = self.app.memcache.get(cache_key)
            if isinstance(cache_value, dict):
                if 'container_size' in cache_value:
                    cache_value['count'] = cache_value['container_size']
                if is_success(cache_value['status']):
                    container_info.update(cache_value)
                    container_info['partition'] = part
                    container_info['nodes'] = nodes
                return container_info
        if not self.account_info(account, autocreate=account_autocreate)[1]:
            return container_info
        headers = {'x-trans-id': self.trans_id, 'Connection': 'close'}
        for node in self.iter_nodes(self.app.container_ring, part):
            try:
                start_node_timing = time.time()
                with ConnectionTimeout(self.app.conn_timeout):
                    conn = http_connect(node['ip'], node['port'],
                                        node['device'], part, 'HEAD', path,
                                        headers)
                self.app.set_node_timing(node, time.time() - start_node_timing)
                with Timeout(self.app.node_timeout):
                    resp = conn.getresponse()
                    body = resp.read()
                if is_success(resp.status):
                    container_info.update(
                        headers_to_container_info(resp.getheaders()))
                    break
                elif resp.status == HTTP_NOT_FOUND:
                    container_info['status'] = HTTP_NOT_FOUND
                else:
                    container_info['status'] = -1
                    if resp.status == HTTP_INSUFFICIENT_STORAGE:
                        self.error_limit(node, _('ERROR Insufficient Storage'))
                    elif is_server_error(resp.status):
                        self.error_occurred(
                            node,
                            _('ERROR %(status)d %(body)s From Container '
                              'Server') % {
                                  'status': resp.status,
                                  'body': body[:1024]
                              })
            except (Exception, Timeout):
                self.exception_occurred(
                    node, _('Container'),
                    _('Trying to get container info for %s') % path)
        if self.app.memcache:
            if container_info['status'] == HTTP_OK:
                self.app.memcache.set(
                    cache_key,
                    container_info,
                    time=self.app.recheck_container_existence)
            elif container_info['status'] == HTTP_NOT_FOUND:
                self.app.memcache.set(
                    cache_key,
                    container_info,
                    time=self.app.recheck_container_existence * 0.1)
        if container_info['status'] == HTTP_OK:
            container_info['partition'] = part
            container_info['nodes'] = nodes
        return container_info
Beispiel #27
0
    def account_info(self, account, autocreate=False):
        """
        Get account information, and also verify that the account exists.

        :param account: name of the account to get the info for
        :returns: tuple of (account partition, account nodes, container_count)
                  or (None, None, None) if it does not exist
        """
        partition, nodes = self.app.account_ring.get_nodes(account)
        account_info = {
            'status': 0,
            'container_count': 0,
            'total_object_count': None,
            'bytes': None,
            'meta': {}
        }
        # 0 = no responses, 200 = found, 404 = not found, -1 = mixed responses
        if self.app.memcache:
            cache_key = get_account_memcache_key(account)
            cache_value = self.app.memcache.get(cache_key)
            if not isinstance(cache_value, dict):
                result_code = cache_value
                container_count = 0
            else:
                result_code = cache_value['status']
                try:
                    container_count = int(cache_value['container_count'])
                except ValueError:
                    container_count = 0
            if result_code == HTTP_OK:
                return partition, nodes, container_count
            elif result_code == HTTP_NOT_FOUND and not autocreate:
                return None, None, None
        result_code = 0
        path = '/%s' % account
        headers = {'x-trans-id': self.trans_id, 'Connection': 'close'}
        for node in self.iter_nodes(self.app.account_ring, partition):
            try:
                start_node_timing = time.time()
                with ConnectionTimeout(self.app.conn_timeout):
                    conn = http_connect(node['ip'], node['port'],
                                        node['device'], partition, 'HEAD',
                                        path, headers)
                self.app.set_node_timing(node, time.time() - start_node_timing)
                with Timeout(self.app.node_timeout):
                    resp = conn.getresponse()
                    body = resp.read()
                    if is_success(resp.status):
                        result_code = HTTP_OK
                        account_info.update(
                            headers_to_account_info(resp.getheaders()))
                        break
                    elif resp.status == HTTP_NOT_FOUND:
                        if result_code == 0:
                            result_code = HTTP_NOT_FOUND
                        elif result_code != HTTP_NOT_FOUND:
                            result_code = -1
                    elif resp.status == HTTP_INSUFFICIENT_STORAGE:
                        self.error_limit(node, _('ERROR Insufficient Storage'))
                        continue
                    else:
                        result_code = -1
                        if is_server_error(resp.status):
                            self.error_occurred(
                                node,
                                _('ERROR %(status)d %(body)s From Account '
                                  'Server') % {
                                      'status': resp.status,
                                      'body': body[:1024]
                                  })
            except (Exception, Timeout):
                self.exception_occurred(
                    node, _('Account'),
                    _('Trying to get account info for %s') % path)
        if result_code == HTTP_NOT_FOUND and autocreate:
            if len(account) > MAX_ACCOUNT_NAME_LENGTH:
                return None, None, None
            headers = {
                'X-Timestamp': normalize_timestamp(time.time()),
                'X-Trans-Id': self.trans_id,
                'Connection': 'close'
            }
            resp = self.make_requests(Request.blank('/v1' + path),
                                      self.app.account_ring, partition, 'PUT',
                                      path, [headers] * len(nodes))
            if not is_success(resp.status_int):
                self.app.logger.warning('Could not autocreate account %r' %
                                        path)
                return None, None, None
            result_code = HTTP_OK
        if self.app.memcache and result_code in (HTTP_OK, HTTP_NOT_FOUND):
            if result_code == HTTP_OK:
                cache_timeout = self.app.recheck_account_existence
            else:
                cache_timeout = self.app.recheck_account_existence * 0.1
            account_info.update(status=result_code)
            self.app.memcache.set(cache_key, account_info, time=cache_timeout)
        if result_code == HTTP_OK:
            try:
                container_count = int(account_info['container_count'])
            except ValueError:
                container_count = 0
            return partition, nodes, container_count
        return None, None, None
Beispiel #28
0
    def account_info(self, account, req=None):
        """
        Get account information, and also verify that the account exists.

        :param account: name of the account to get the info for
        :param req: caller's HTTP request context object (optional)
        :param autocreate: whether or not to automatically create the given
                           account or not (optional, default: False)
        :returns: tuple of (account partition, account nodes, container_count)
                  or (None, None, None) if it does not exist
        """
        partition, nodes = self.app.account_ring.get_nodes(account)
        account_info = {
            'status': 0,
            'container_count': 0,
            'total_object_count': None,
            'bytes': None,
            'meta': {}
        }
        # 0 = no responses, 200 = found, 404 = not found, -1 = mixed responses
        if self.app.memcache:
            cache_key = get_account_memcache_key(account)
            cache_value = self.app.memcache.get(cache_key)
            if not isinstance(cache_value, dict):
                result_code = cache_value
                container_count = 0
            else:
                result_code = cache_value['status']
                try:
                    container_count = int(cache_value['container_count'])
                except ValueError:
                    container_count = 0
            if result_code == HTTP_OK:
                return partition, nodes, container_count
            elif result_code == HTTP_NOT_FOUND:
                return None, None, None
        result_code = 0
        path = '/%s' % account
        headers = self.generate_request_headers(req)
        for node in self.iter_nodes(self.app.account_ring, partition):
            try:
                start_node_timing = time.time()
                with ConnectionTimeout(self.app.conn_timeout):
                    conn = http_connect(node['ip'], node['port'],
                                        node['device'], partition, 'HEAD',
                                        path, headers)
                self.app.set_node_timing(node, time.time() - start_node_timing)
                with Timeout(self.app.node_timeout):
                    resp = conn.getresponse()
                    body = resp.read()
                    if is_success(resp.status):
                        result_code = HTTP_OK
                        account_info.update(
                            headers_to_account_info(resp.getheaders()))
                        break
                    elif resp.status == HTTP_NOT_FOUND:
                        if result_code == 0:
                            result_code = HTTP_NOT_FOUND
                        elif result_code != HTTP_NOT_FOUND:
                            result_code = -1
                    elif resp.status == HTTP_INSUFFICIENT_STORAGE:
                        self.error_limit(node, _('ERROR Insufficient Storage'))
                        continue
                    else:
                        result_code = -1
                        if is_server_error(resp.status):
                            self.error_occurred(
                                node,
                                _('ERROR %(status)d %(body)s From Account '
                                  'Server') % {
                                      'status': resp.status,
                                      'body': body[:1024]
                                  })
            except (Exception, Timeout):
                self.exception_occurred(
                    node, _('Account'),
                    _('Trying to get account info for %s') % path)
        if self.app.memcache and result_code in (HTTP_OK, HTTP_NOT_FOUND):
            if result_code == HTTP_OK:
                cache_timeout = self.app.recheck_account_existence
            else:
                cache_timeout = self.app.recheck_account_existence * 0.1
            account_info.update(status=result_code)
            self.app.memcache.set(cache_key, account_info, time=cache_timeout)
        if result_code == HTTP_OK:
            try:
                container_count = int(account_info['container_count'])
            except ValueError:
                container_count = 0
            return partition, nodes, container_count
        return None, None, None
Beispiel #29
0
    def GETorHEAD_base(self, req, server_type, partition, nodes, path,
                       attempts):
        """
        Base handler for HTTP GET or HEAD requests.

        :param req: webob.Request object
        :param server_type: server type
        :param partition: partition
        :param nodes: nodes
        :param path: path for the request
        :param attempts: number of attempts to try
        :returns: webob.Response object
        """
        statuses = []
        reasons = []
        bodies = []
        source = None
        sources = []
        newest = req.headers.get('x-newest', 'f').lower() in TRUE_VALUES
        nodes = iter(nodes)
        while len(statuses) < attempts:
            try:
                node = nodes.next()
            except StopIteration:
                break
            if self.error_limited(node):
                continue
            try:
                with ConnectionTimeout(self.app.conn_timeout):
                    headers = dict(req.headers)
                    headers['Connection'] = 'close'
                    conn = http_connect(node['ip'], node['port'],
                        node['device'], partition, req.method, path,
                        headers=headers,
                        query_string=req.query_string)
                with Timeout(self.app.node_timeout):
                    possible_source = conn.getresponse()
                    # See NOTE: swift_conn at top of file about this.
                    possible_source.swift_conn = conn
            except (Exception, Timeout):
                self.exception_occurred(node, server_type,
                    _('Trying to %(method)s %(path)s') %
                    {'method': req.method, 'path': req.path})
                continue
            if possible_source.status == HTTP_INSUFFICIENT_STORAGE:
                self.error_limit(node)
                continue
            if is_success(possible_source.status) or \
               is_redirection(possible_source.status):
                # 404 if we know we don't have a synced copy
                if not float(possible_source.getheader('X-PUT-Timestamp', 1)):
                    statuses.append(HTTP_NOT_FOUND)
                    reasons.append('')
                    bodies.append('')
                    possible_source.read()
                    continue
                if newest:
                    if sources:
                        ts = float(source.getheader('x-put-timestamp') or
                                   source.getheader('x-timestamp') or 0)
                        pts = float(
                            possible_source.getheader('x-put-timestamp') or
                            possible_source.getheader('x-timestamp') or 0)
                        if pts > ts:
                            sources.insert(0, possible_source)
                        else:
                            sources.append(possible_source)
                    else:
                        sources.insert(0, possible_source)
                    source = sources[0]
                    statuses.append(source.status)
                    reasons.append(source.reason)
                    bodies.append('')
                    continue
                else:
                    source = possible_source
                    break
            statuses.append(possible_source.status)
            reasons.append(possible_source.reason)
            bodies.append(possible_source.read())
            if is_server_error(possible_source.status):
                self.error_occurred(node, _('ERROR %(status)d %(body)s ' \
                    'From %(type)s Server') %
                    {'status': possible_source.status,
                    'body': bodies[-1][:1024], 'type': server_type})
        if source:
            if req.method == 'GET' and \
               source.status in (HTTP_OK, HTTP_PARTIAL_CONTENT):
                if newest:
                    # we need to close all hanging swift_conns
                    sources.pop(0)
                    for src in sources:
                        self.close_swift_conn(src)

                res = Response(request=req, conditional_response=True)
                res.app_iter = self._make_app_iter(node, source, res)
                # See NOTE: swift_conn at top of file about this.
                res.swift_conn = source.swift_conn
                update_headers(res, source.getheaders())
                # Used by container sync feature
                if res.environ is None:
                    res.environ = dict()
                res.environ['swift_x_timestamp'] = \
                    source.getheader('x-timestamp')
                update_headers(res, {'accept-ranges': 'bytes'})
                res.status = source.status
                res.content_length = source.getheader('Content-Length')
                if source.getheader('Content-Type'):
                    res.charset = None
                    res.content_type = source.getheader('Content-Type')
                return res
            elif is_success(source.status) or is_redirection(source.status):
                res = status_map[source.status](request=req)
                update_headers(res, source.getheaders())
                # Used by container sync feature
                if res.environ is None:
                    res.environ = dict()
                res.environ['swift_x_timestamp'] = \
                    source.getheader('x-timestamp')
                update_headers(res, {'accept-ranges': 'bytes'})
                res.content_length = source.getheader('Content-Length')
                if source.getheader('Content-Type'):
                    res.charset = None
                    res.content_type = source.getheader('Content-Type')
                return res
        return self.best_response(req, statuses, reasons, bodies,
                                  '%s %s' % (server_type, req.method))
Beispiel #30
0
    def GETorHEAD_base(self, req, server_type, partition, nodes, path,
                       attempts):
        """
        Base handler for HTTP GET or HEAD requests.

        :param req: webob.Request object
        :param server_type: server type
        :param partition: partition
        :param nodes: nodes
        :param path: path for the request
        :param attempts: number of attempts to try
        :returns: webob.Response object
        """
        statuses = []
        reasons = []
        bodies = []
        source = None
        sources = []
        newest = req.headers.get('x-newest', 'f').lower() in TRUE_VALUES
        nodes = iter(nodes)
        while len(statuses) < attempts:
            try:
                node = nodes.next()
            except StopIteration:
                break
            if self.error_limited(node):
                continue
            try:
                with ConnectionTimeout(self.app.conn_timeout):
                    headers = dict(req.headers)
                    headers['Connection'] = 'close'
                    conn = http_connect(node['ip'],
                                        node['port'],
                                        node['device'],
                                        partition,
                                        req.method,
                                        path,
                                        headers=headers,
                                        query_string=req.query_string)
                with Timeout(self.app.node_timeout):
                    possible_source = conn.getresponse()
                    # See NOTE: swift_conn at top of file about this.
                    possible_source.swift_conn = conn
            except (Exception, Timeout):
                self.exception_occurred(
                    node, server_type,
                    _('Trying to %(method)s %(path)s') % {
                        'method': req.method,
                        'path': req.path
                    })
                continue
            if possible_source.status == HTTP_INSUFFICIENT_STORAGE:
                self.error_limit(node)
                continue
            if is_success(possible_source.status) or \
               is_redirection(possible_source.status):
                # 404 if we know we don't have a synced copy
                if not float(possible_source.getheader('X-PUT-Timestamp', 1)):
                    statuses.append(HTTP_NOT_FOUND)
                    reasons.append('')
                    bodies.append('')
                    possible_source.read()
                    continue
                if newest:
                    if sources:
                        ts = float(
                            source.getheader('x-put-timestamp')
                            or source.getheader('x-timestamp') or 0)
                        pts = float(
                            possible_source.getheader('x-put-timestamp')
                            or possible_source.getheader('x-timestamp') or 0)
                        if pts > ts:
                            sources.insert(0, possible_source)
                        else:
                            sources.append(possible_source)
                    else:
                        sources.insert(0, possible_source)
                    source = sources[0]
                    statuses.append(source.status)
                    reasons.append(source.reason)
                    bodies.append('')
                    continue
                else:
                    source = possible_source
                    break
            statuses.append(possible_source.status)
            reasons.append(possible_source.reason)
            bodies.append(possible_source.read())
            if is_server_error(possible_source.status):
                self.error_occurred(node, _('ERROR %(status)d %(body)s ' \
                    'From %(type)s Server') %
                    {'status': possible_source.status,
                    'body': bodies[-1][:1024], 'type': server_type})
        if source:
            if req.method == 'GET' and \
               source.status in (HTTP_OK, HTTP_PARTIAL_CONTENT):
                if newest:
                    # we need to close all hanging swift_conns
                    sources.pop(0)
                    for src in sources:
                        self.close_swift_conn(src)

                res = Response(request=req, conditional_response=True)
                res.app_iter = self._make_app_iter(node, source)
                # See NOTE: swift_conn at top of file about this.
                res.swift_conn = source.swift_conn
                update_headers(res, source.getheaders())
                # Used by container sync feature
                if res.environ is None:
                    res.environ = dict()
                res.environ['swift_x_timestamp'] = \
                    source.getheader('x-timestamp')
                update_headers(res, {'accept-ranges': 'bytes'})
                res.status = source.status
                res.content_length = source.getheader('Content-Length')
                if source.getheader('Content-Type'):
                    res.charset = None
                    res.content_type = source.getheader('Content-Type')
                return res
            elif is_success(source.status) or is_redirection(source.status):
                res = status_map[source.status](request=req)
                update_headers(res, source.getheaders())
                # Used by container sync feature
                if res.environ is None:
                    res.environ = dict()
                res.environ['swift_x_timestamp'] = \
                    source.getheader('x-timestamp')
                update_headers(res, {'accept-ranges': 'bytes'})
                res.content_length = source.getheader('Content-Length')
                if source.getheader('Content-Type'):
                    res.charset = None
                    res.content_type = source.getheader('Content-Type')
                return res
        return self.best_response(req, statuses, reasons, bodies,
                                  '%s %s' % (server_type, req.method))
Beispiel #31
0
    def GETorHEAD_base(self, req, server_type, partition, nodes, path,
                       attempts):
        """
        Base handler for HTTP GET or HEAD requests.

        :param req: swob.Request object
        :param server_type: server type
        :param partition: partition
        :param nodes: nodes
        :param path: path for the request
        :param attempts: number of attempts to try
        :returns: swob.Response object
        """
        statuses = []
        reasons = []
        bodies = []
        sources = []
        newest = config_true_value(req.headers.get('x-newest', 'f'))
        nodes = iter(nodes)
        while len(statuses) < attempts:
            try:
                node = nodes.next()
            except StopIteration:
                break
            if self.error_limited(node):
                continue
            start_node_timing = time.time()
            try:
                with ConnectionTimeout(self.app.conn_timeout):
                    headers = dict(req.headers)
                    headers['Connection'] = 'close'
                    conn = http_connect(
                        node['ip'], node['port'], node['device'], partition,
                        req.method, path, headers=headers,
                        query_string=req.query_string)
                self.app.set_node_timing(node, time.time() - start_node_timing)
                with Timeout(self.app.node_timeout):
                    possible_source = conn.getresponse()
                    # See NOTE: swift_conn at top of file about this.
                    possible_source.swift_conn = conn
            except (Exception, Timeout):
                self.exception_occurred(
                    node, server_type, _('Trying to %(method)s %(path)s') %
                    {'method': req.method, 'path': req.path})
                continue
            if self.is_good_source(possible_source):
                # 404 if we know we don't have a synced copy
                if not float(possible_source.getheader('X-PUT-Timestamp', 1)):
                    statuses.append(HTTP_NOT_FOUND)
                    reasons.append('')
                    bodies.append('')
                    self.close_swift_conn(possible_source)
                else:
                    statuses.append(possible_source.status)
                    reasons.append(possible_source.reason)
                    bodies.append('')
                    sources.append(possible_source)
                    if not newest:  # one good source is enough
                        break
            else:
                statuses.append(possible_source.status)
                reasons.append(possible_source.reason)
                bodies.append(possible_source.read())
                if possible_source.status == HTTP_INSUFFICIENT_STORAGE:
                    self.error_limit(node)
                elif is_server_error(possible_source.status):
                    self.error_occurred(node, _('ERROR %(status)d %(body)s '
                                                'From %(type)s Server') %
                                        {'status': possible_source.status,
                                         'body': bodies[-1][:1024],
                                         'type': server_type})
        if sources:
            sources.sort(key=source_key)
            source = sources.pop()
            for src in sources:
                self.close_swift_conn(src)
            res = Response(request=req, conditional_response=True)
            if req.method == 'GET' and \
                    source.status in (HTTP_OK, HTTP_PARTIAL_CONTENT):
                res.app_iter = self._make_app_iter(node, source)
                # See NOTE: swift_conn at top of file about this.
                res.swift_conn = source.swift_conn
            res.status = source.status
            update_headers(res, source.getheaders())
            if not res.environ:
                res.environ = {}
            res.environ['swift_x_timestamp'] = \
                source.getheader('x-timestamp')
            res.accept_ranges = 'bytes'
            res.content_length = source.getheader('Content-Length')
            if source.getheader('Content-Type'):
                res.charset = None
                res.content_type = source.getheader('Content-Type')
            return res
        return self.best_response(req, statuses, reasons, bodies,
                                  '%s %s' % (server_type, req.method))
Beispiel #32
0
    def account_info(self, account, req=None):
        """
        Get account information, and also verify that the account exists.

        :param account: name of the account to get the info for
        :param req: caller's HTTP request context object (optional)
        :param autocreate: whether or not to automatically create the given
                           account or not (optional, default: False)
        :returns: tuple of (account partition, account nodes, container_count)
                  or (None, None, None) if it does not exist
        """
        partition, nodes = self.app.account_ring.get_nodes(account)
        account_info = {'status': 0,
                        'container_count': 0,
                        'total_object_count': None,
                        'bytes': None,
                        'meta': {}}
        # 0 = no responses, 200 = found, 404 = not found, -1 = mixed responses
        if self.app.memcache:
            cache_key = get_account_memcache_key(account)
            cache_value = self.app.memcache.get(cache_key)
            if not isinstance(cache_value, dict):
                result_code = cache_value
                container_count = 0
            else:
                result_code = cache_value['status']
                try:
                    container_count = int(cache_value['container_count'])
                except ValueError:
                    container_count = 0
            if result_code == HTTP_OK:
                return partition, nodes, container_count
            elif result_code == HTTP_NOT_FOUND:
                return None, None, None
        result_code = 0
        path = '/%s' % account
        headers = self.generate_request_headers(req)
        for node in self.iter_nodes(self.app.account_ring, partition):
            try:
                start_node_timing = time.time()
                with ConnectionTimeout(self.app.conn_timeout):
                    conn = http_connect(node['ip'], node['port'],
                                        node['device'], partition, 'HEAD',
                                        path, headers)
                self.app.set_node_timing(node, time.time() - start_node_timing)
                with Timeout(self.app.node_timeout):
                    resp = conn.getresponse()
                    body = resp.read()
                    if is_success(resp.status):
                        result_code = HTTP_OK
                        account_info.update(
                            headers_to_account_info(resp.getheaders()))
                        break
                    elif resp.status == HTTP_NOT_FOUND:
                        if result_code == 0:
                            result_code = HTTP_NOT_FOUND
                        elif result_code != HTTP_NOT_FOUND:
                            result_code = -1
                    elif resp.status == HTTP_INSUFFICIENT_STORAGE:
                        self.error_limit(node, _('ERROR Insufficient Storage'))
                        continue
                    else:
                        result_code = -1
                        if is_server_error(resp.status):
                            self.error_occurred(
                                node,
                                _('ERROR %(status)d %(body)s From Account '
                                  'Server') %
                                {'status': resp.status, 'body': body[:1024]})
            except (Exception, Timeout):
                self.exception_occurred(node, _('Account'),
                                        _('Trying to get account info for %s')
                                        % path)
        if self.app.memcache and result_code in (HTTP_OK, HTTP_NOT_FOUND):
            if result_code == HTTP_OK:
                cache_timeout = self.app.recheck_account_existence
            else:
                cache_timeout = self.app.recheck_account_existence * 0.1
            account_info.update(status=result_code)
            self.app.memcache.set(cache_key,
                                  account_info,
                                  time=cache_timeout)
        if result_code == HTTP_OK:
            try:
                container_count = int(account_info['container_count'])
            except ValueError:
                container_count = 0
            return partition, nodes, container_count
        return None, None, None
Beispiel #33
0
    def make_request(self,
                     method,
                     path,
                     headers,
                     acceptable_statuses,
                     body_file=None,
                     params=None):
        """Makes a request to Swift with retries.

        :param method: HTTP method of request.
        :param path: Path of request.
        :param headers: Headers to be sent with request.
        :param acceptable_statuses: List of acceptable statuses for request.
        :param body_file: Body file to be passed along with request,
                          defaults to None.
        :param params: A dict of params to be set in request query string,
                       defaults to None.

        :returns: Response object on success.

        :raises UnexpectedResponse: Exception raised when make_request() fails
                                    to get a response with an acceptable status
        :raises Exception: Exception is raised when code fails in an
                           unexpected way.
        """

        headers = dict(headers)
        headers['user-agent'] = self.user_agent
        for attempt in range(self.request_tries):
            resp = exc_type = exc_value = exc_traceback = None
            req = Request.blank(path,
                                environ={'REQUEST_METHOD': method},
                                headers=headers)
            if body_file is not None:
                if hasattr(body_file, 'seek'):
                    body_file.seek(0)
                req.body_file = body_file
            if params:
                req.params = params
            try:
                resp = req.get_response(self.app)
            except (Exception, Timeout):
                exc_type, exc_value, exc_traceback = exc_info()
            else:
                if resp.status_int in acceptable_statuses or \
                        resp.status_int // 100 in acceptable_statuses:
                    return resp
                elif not is_server_error(resp.status_int):
                    # No sense retrying when we expect the same result
                    break
            # sleep only between tries, not after each one
            if attempt < self.request_tries - 1:
                if resp:
                    # always close any resp.app_iter before we discard it
                    with closing_if_possible(resp.app_iter):
                        # for non 2XX requests it's safe and useful to drain
                        # the response body so we log the correct status code
                        if resp.status_int // 100 != 2:
                            for iter_body in resp.app_iter:
                                pass
                sleep(2**(attempt + 1))
        if resp:
            msg = 'Unexpected response: %s' % resp.status
            if resp.status_int // 100 != 2 and resp.body:
                # provide additional context (and drain the response body) for
                # non 2XX responses
                msg += ' (%s)' % resp.body
            raise UnexpectedResponse(msg, resp)
        if exc_type:
            # To make pep8 tool happy, in place of raise t, v, tb:
            six.reraise(exc_type, exc_value, exc_traceback)
Beispiel #34
0
    def _get_source_and_node(self):

        self.statuses = []
        self.reasons = []
        self.bodies = []
        self.source_headers = []
        sources = []

        for node in self.app.iter_nodes(self.ring, self.partition):
            if node in self.used_nodes:
                continue
            start_node_timing = time.time()
            try:
                with ConnectionTimeout(self.app.conn_timeout):
                    conn = http_connect(
                        node["ip"],
                        node["port"],
                        node["device"],
                        self.partition,
                        self.req_method,
                        self.path,
                        headers=self.backend_headers,
                        query_string=self.req_query_string,
                    )
                self.app.set_node_timing(node, time.time() - start_node_timing)

                with Timeout(self.app.node_timeout):
                    possible_source = conn.getresponse()
                    # See NOTE: swift_conn at top of file about this.
                    possible_source.swift_conn = conn
            except (Exception, Timeout):
                self.app.exception_occurred(
                    node,
                    self.server_type,
                    _("Trying to %(method)s %(path)s") % {"method": self.req_method, "path": self.req_path},
                )
                continue
            if self.is_good_source(possible_source):
                # 404 if we know we don't have a synced copy
                if not float(possible_source.getheader("X-PUT-Timestamp", 1)):
                    self.statuses.append(HTTP_NOT_FOUND)
                    self.reasons.append("")
                    self.bodies.append("")
                    self.source_headers.append("")
                    close_swift_conn(possible_source)
                else:
                    if self.used_source_etag:
                        src_headers = dict((k.lower(), v) for k, v in possible_source.getheaders())
                        if src_headers.get("etag", "").strip('"') != self.used_source_etag:
                            self.statuses.append(HTTP_NOT_FOUND)
                            self.reasons.append("")
                            self.bodies.append("")
                            self.source_headers.append("")
                            continue

                    self.statuses.append(possible_source.status)
                    self.reasons.append(possible_source.reason)
                    self.bodies.append("")
                    self.source_headers.append("")
                    sources.append((possible_source, node))
                    if not self.newest:  # one good source is enough
                        break
            else:
                self.statuses.append(possible_source.status)
                self.reasons.append(possible_source.reason)
                self.bodies.append(possible_source.read())
                self.source_headers.append(possible_source.getheaders())
                if possible_source.status == HTTP_INSUFFICIENT_STORAGE:
                    self.app.error_limit(node, _("ERROR Insufficient Storage"))
                elif is_server_error(possible_source.status):
                    self.app.error_occurred(
                        node,
                        _("ERROR %(status)d %(body)s " "From %(type)s Server")
                        % {"status": possible_source.status, "body": self.bodies[-1][:1024], "type": self.server_type},
                    )

        if sources:
            sources.sort(key=lambda s: source_key(s[0]))
            source, node = sources.pop()
            for src, _junk in sources:
                close_swift_conn(src)
            self.used_nodes.append(node)
            src_headers = dict((k.lower(), v) for k, v in possible_source.getheaders())
            self.used_source_etag = src_headers.get("etag", "").strip('"')
            return source, node
        return None, None