Beispiel #1
0
    def create(self, req, body):
        """Create a new backup."""
        LOG.debug('Creating new backup %s', body)

        context = req.environ['cinder.context']
        req_version = req.api_version_request

        backup = body['backup']
        container = backup.get('container', None)
        volume_id = backup['volume_id']

        self.validate_name_and_description(backup, check_length=False)
        name = backup.get('name', None)
        description = backup.get('description', None)
        incremental = strutils.bool_from_string(backup.get(
            'incremental', False),
                                                strict=True)
        force = strutils.bool_from_string(backup.get('force', False),
                                          strict=True)
        snapshot_id = backup.get('snapshot_id', None)
        metadata = backup.get('metadata', None) if req_version.matches(
            mv.BACKUP_METADATA) else None

        if req_version.matches(mv.BACKUP_AZ):
            availability_zone = backup.get('availability_zone', None)
        else:
            availability_zone = None
        az_text = ' in az %s' % availability_zone if availability_zone else ''

        LOG.info(
            "Creating backup of volume %(volume_id)s in container"
            " %(container)s%(az)s", {
                'volume_id': volume_id,
                'container': container,
                'az': az_text
            },
            context=context)

        try:
            new_backup = self.backup_api.create(context, name, description,
                                                volume_id, container,
                                                incremental, availability_zone,
                                                force, snapshot_id, metadata)
        except (exception.InvalidVolume, exception.InvalidSnapshot,
                exception.InvalidVolumeMetadata,
                exception.InvalidVolumeMetadataSize) as error:
            raise exc.HTTPBadRequest(explanation=error.msg)
        # Other not found exceptions will be handled at the wsgi level
        except exception.ServiceNotFound as error:
            raise exc.HTTPServiceUnavailable(explanation=error.msg)

        retval = self._view_builder.summary(req, dict(new_backup))
        return retval
Beispiel #2
0
    def create(self, req, body):
        if not body:
            raise exc.HTTPUnprocessableEntity()

        context = req.environ["nova.context"]
        authorize(context)

        network = body["network"]
        keys = ["cidr", "cidr_v6", "ipam", "vlan_start", "network_size",
                "num_networks"]
        kwargs = dict((k, network.get(k)) for k in keys)

        label = network["label"]

        if not (kwargs["cidr"] or kwargs["cidr_v6"]):
            msg = _("No CIDR requested")
            raise exc.HTTPBadRequest(explanation=msg)
        if kwargs["cidr"]:
            try:
                net = netaddr.IPNetwork(kwargs["cidr"])
                if net.size < 4:
                    msg = _("Requested network does not contain "
                            "enough (2+) usable hosts")
                    raise exc.HTTPBadRequest(explanation=msg)
            except netexc.AddrFormatError:
                msg = _("CIDR is malformed.")
                raise exc.HTTPBadRequest(explanation=msg)
            except netexc.AddrConversionError:
                msg = _("Address could not be converted.")
                raise exc.HTTPBadRequest(explanation=msg)

        networks = []
        try:
            if CONF.enable_network_quota:
                reservation = QUOTAS.reserve(context, networks=1)
        except exception.OverQuota:
            msg = _("Quota exceeded, too many networks.")
            raise exc.HTTPBadRequest(explanation=msg)

        try:
            networks = self.network_api.create(context,
                                               label=label, **kwargs)
            if CONF.enable_network_quota:
                QUOTAS.commit(context, reservation)
        except exception.PolicyNotAuthorized as e:
            raise exc.HTTPForbidden(explanation=str(e))
        except Exception:
            if CONF.enable_network_quota:
                QUOTAS.rollback(context, reservation)
            msg = _("Create networks failed")
            LOG.exception(msg, extra=network)
            raise exc.HTTPServiceUnavailable(explanation=msg)
        return {"network": network_dict(networks[0])}
Beispiel #3
0
def code2exception(code, detail):
    """Transforms a code + detail into a WebOb exception"""
    if code == 400:
        return exc.HTTPBadRequest(detail)
    if code == 401:
        return exc.HTTPUnauthorized(detail)
    if code == 402:
        return exc.HTTPPaymentRequired(detail)
    if code == 403:
        return exc.HTTPForbidden(detail)
    if code == 404:
        return exc.HTTPNotFound(detail)
    if code == 405:
        return exc.HTTPMethodNotAllowed(detail)
    if code == 406:
        return exc.HTTPNotAcceptable(detail)
    if code == 407:
        return exc.HTTPProxyAuthenticationRequired(detail)
    if code == 408:
        return exc.HTTPRequestTimeout(detail)
    if code == 409:
        return exc.HTTPConflict(detail)
    if code == 410:
        return exc.HTTPGone(detail)
    if code == 411:
        return exc.HTTPLengthRequired(detail)
    if code == 412:
        return exc.HTTPPreconditionFailed(detail)
    if code == 413:
        return exc.HTTPRequestEntityTooLarge(detail)
    if code == 414:
        return exc.HTTPRequestURITooLong(detail)
    if code == 415:
        return exc.HTTPUnsupportedMediaType(detail)
    if code == 416:
        return exc.HTTPRequestRangeNotSatisfiable(detail)
    if code == 417:
        return exc.HTTPExpectationFailed(detail)
    if code == 500:
        return exc.HTTPInternalServerError(detail)
    if code == 501:
        return exc.HTTPNotImplemented(detail)
    if code == 502:
        return exc.HTTPBadGateway(detail)
    if code == 503:
        return exc.HTTPServiceUnavailable(detail)
    if code == 504:
        return exc.HTTPGatewayTimeout(detail)
    if code == 505:
        return exc.HTTPVersionNotSupported(detail)

    raise NotImplementedError(code)
Beispiel #4
0
    def create(self, req, body):
        """Create a new backup."""
        LOG.debug('Creating new backup %s', body)
        self.assert_valid_body(body, 'backup')

        context = req.environ['cinder.context']
        backup = body['backup']
        req_version = req.api_version_request

        try:
            volume_id = backup['volume_id']
        except KeyError:
            msg = _("Incorrect request body format")
            raise exc.HTTPBadRequest(explanation=msg)
        container = backup.get('container', None)
        if container:
            utils.check_string_length(container,
                                      'Backup container',
                                      min_length=0,
                                      max_length=255)
        self.validate_name_and_description(backup)
        name = backup.get('name', None)
        description = backup.get('description', None)
        incremental = backup.get('incremental', False)
        force = backup.get('force', False)
        snapshot_id = backup.get('snapshot_id', None)
        metadata = backup.get('metadata', None) if req_version.matches(
            mv.BACKUP_METADATA) else None
        LOG.info(
            "Creating backup of volume %(volume_id)s in container"
            " %(container)s", {
                'volume_id': volume_id,
                'container': container
            },
            context=context)

        try:
            new_backup = self.backup_api.create(context, name, description,
                                                volume_id, container,
                                                incremental, None, force,
                                                snapshot_id, metadata)
        except (exception.InvalidVolume, exception.InvalidSnapshot,
                exception.InvalidVolumeMetadata,
                exception.InvalidVolumeMetadataSize) as error:
            raise exc.HTTPBadRequest(explanation=error.msg)
        # Other not found exceptions will be handled at the wsgi level
        except exception.ServiceNotFound as error:
            raise exc.HTTPServiceUnavailable(explanation=error.msg)

        retval = self._view_builder.summary(req, dict(new_backup))
        return retval
Beispiel #5
0
    def make_imaged_request(self, method, imaged_url, headers, body, stream,
                            connection_timeout=None, read_timeout=None):
        logging.debug("Connecting to host at %s", imaged_url)
        logging.debug("Outgoing headers to host:\n" +
                      '\n'.join(('  {}: {}'.format(k, headers[k])
                                 for k in sorted(headers))))

        try:
            # TODO Pool requests, keep the session somewhere?
            # TODO Otherwise, we can use request.prepare()
            imaged_session = requests.Session()
            imaged_req = requests.Request(
                method, imaged_url, headers=headers, data=body)
            imaged_req.body_file = body
            # TODO log the request to vdsm
            imaged_prepped = imaged_session.prepare_request(imaged_req)
            imaged_resp = imaged_session.send(
                imaged_prepped, verify=config.engine_ca_cert_file,
                timeout=(connection_timeout, read_timeout), stream=stream)
        except requests.Timeout:
            s = "Timed out connecting to host"
            raise exc.HTTPGatewayTimeout(s)
        except requests.URLRequired:
            s = "Invalid host URI for host"
            raise exc.HTTPBadRequest(s)
        except requests.ConnectionError as e:
            s = "Failed communicating with host: " + e.__doc__
            logging.error(s, exc_info=True)
            raise exc.HTTPServiceUnavailable(s)
        except requests.RequestException as e:
            s = "Failed communicating with host: " + e.__doc__
            logging.error(s, exc_info=True)
            raise exc.HTTPInternalServerError(s)

        logging.debug("Incoming headers from host:\n" +
                      '\n'.join(('  {}: {}'
                                 .format(k, imaged_resp.headers.get(k))
                                 for k in sorted(imaged_resp.headers))))

        if imaged_resp.status_code not in http_success_codes:
            s = "Failed response from host: %d %s" % \
                (imaged_resp.status_code, imaged_resp.content)
            raise exc.status_map[imaged_resp.status_code](s)

        logging.debug(
            "Successful request to host: HTTP %d %s",
            imaged_resp.status_code,
            httplib.responses[imaged_resp.status_code]
        )
        return imaged_resp
Beispiel #6
0
 def service_GET(self, req):
     """GET request: used to pre-populate the initial setup page."""
     # This is required in order to bypass the required role.
     entry = UserProfile.get(req.envid, 0) # user '0', likely 'palette'
     if entry.hashed_password:
         # Configuration was already done
         raise exc.HTTPServiceUnavailable()
     data = self.setup.service_GET(req)
     data['license-key'] = req.palette_domain.license_key
     data['version'] = display_version()
     proxy_https = req.system[SystemKeys.PROXY_HTTPS]
     if proxy_https:
         data['proxy-https'] = proxy_https
     return data
Beispiel #7
0
    def create(self, req, body):
        context = req.environ["compute.context"]
        authorize(context)

        network = body["network"]
        keys = [
            "cidr", "cidr_v6", "ipam", "vlan_start", "network_size",
            "num_networks"
        ]
        kwargs = {k: network.get(k) for k in keys}

        label = network["label"]

        if kwargs["cidr"]:
            try:
                net = netaddr.IPNetwork(kwargs["cidr"])
                if net.size < 4:
                    msg = _("Requested network does not contain "
                            "enough (2+) usable hosts")
                    raise exc.HTTPBadRequest(explanation=msg)
            except netexc.AddrConversionError:
                msg = _("Address could not be converted.")
                raise exc.HTTPBadRequest(explanation=msg)

        networks = []
        try:
            if CONF.enable_network_quota:
                reservation = QUOTAS.reserve(context, networks=1)
        except exception.OverQuota:
            msg = _("Quota exceeded, too many networks.")
            raise exc.HTTPBadRequest(explanation=msg)

        kwargs['project_id'] = context.project_id

        try:
            networks = self.network_api.create(context, label=label, **kwargs)
            if CONF.enable_network_quota:
                QUOTAS.commit(context, reservation)
        except exception.PolicyNotAuthorized as e:
            raise exc.HTTPForbidden(explanation=six.text_type(e))
        except exception.CidrConflict as e:
            raise exc.HTTPConflict(explanation=e.format_message())
        except Exception:
            if CONF.enable_network_quota:
                QUOTAS.rollback(context, reservation)
            msg = _("Create networks failed")
            LOG.exception(msg, extra=network)
            raise exc.HTTPServiceUnavailable(explanation=msg)
        return {"network": network_dict(networks[0])}
Beispiel #8
0
    def proxy_to_dest(self, request, dest):
        """Do the actual proxying, without applying any transformations"""
        # We need to remove caching headers, since the upstream parts of Deliverance
        # can't handle Not-Modified responses.
        # Not using request.copy because I don't want to copy wsgi.input
        request = Request(request.environ.copy())
        request.remove_conditional_headers()

        try:
            proxy_req = self.construct_proxy_request(request, dest)
        except TypeError:
            return self.proxy_to_file(request, dest)

        proxy_req.path_info += request.path_info

        if proxy_req.query_string and request.query_string:
            proxy_req.query_string = '%s&%s' % \
                (proxy_req.query_string, request.query_string)
        elif request.query_string:
            proxy_req.query_string = request.query_string

        proxy_req.accept_encoding = None
        try:
            resp = proxy_req.get_response(proxy_exact_request)
            if resp.status_int == 500:
                print('Request:')
                print(proxy_req)
                print('Response:')
                print(resp)
        except socket.error as e:
            ## FIXME: really wsgiproxy should handle this
            ## FIXME: which error?
            ## 502 HTTPBadGateway, 503 HTTPServiceUnavailable, 504 HTTPGatewayTimeout?
            if isinstance(e.args, tuple) and len(e.args) > 1:
                error = e.args[1]
            else:
                error = str(e)
            resp = exc.HTTPServiceUnavailable(
                'Could not proxy the request to %s:%s : %s' %
                (proxy_req.server_name, proxy_req.server_port, error))

        dest = url_normalize(dest)
        orig_base = url_normalize(request.application_url)
        proxied_url = url_normalize(
            '%s://%s%s' %
            (proxy_req.scheme, proxy_req.host, proxy_req.path_qs))

        return resp, orig_base, dest, proxied_url
Beispiel #9
0
    def service_save(self, req):
        """Handler for the 'Save Settings' button at the bottom of the page."""
        entry = UserProfile.get(req.envid, 0) # user '0', likely 'palette'
        if entry.hashed_password:
            # Configuration was already done
            raise exc.HTTPServiceUnavailable()

        if req.platform.product != req.platform.PRODUCT_PRO:
            try:
                self._set_license_key(req)
            except LicenseException, ex:
                if ex.status == 404:
                    reason = 'Invalid license key'
                else:
                    reason = ex.reason
                return {'status': 'FAILED', 'error': reason}
            self.setup.url.service_POST(req)
            self.setup.mail.service_POST(req)
    def execute(self, request, body):
        policy.check("execute_action", request.context, {})

        class_name = body.get('className')
        method_name = body.get('methodName')
        if not class_name or not method_name:
            msg = _('Class name and method name must be specified for '
                    'static action')
            LOG.error(msg)
            raise exc.HTTPBadRequest(msg)

        args = body.get('parameters')
        pkg_name = body.get('packageName')
        class_version = body.get('classVersion', '=0')

        LOG.debug('StaticAction:Execute <MethodName: {0}, '
                  'ClassName: {1}>'.format(method_name, class_name))

        credentials = {
            'token': request.context.auth_token,
            'project_id': request.context.tenant,
            'user_id': request.context.user
        }

        try:
            return static_actions.StaticActionServices.execute(
                method_name, class_name, pkg_name, class_version, args,
                credentials)
        except client.RemoteError as e:
            LOG.error(
                _LE('Exception during call of the method {method_name}: '
                    '{exc}').format(method_name=method_name, exc=str(e)))
            if e.exc_type in ('NoClassFound', 'NoMethodFound',
                              'NoPackageFound', 'NoPackageForClassFound',
                              'MethodNotExposed', 'NoMatchingMethodException'):
                raise exc.HTTPNotFound(e.value)
            elif e.exc_type == 'ContractViolationException':
                raise exc.HTTPBadRequest(e.value)
            raise exc.HTTPServiceUnavailable(e.value)
        except ValueError as e:
            LOG.error(
                _LE('Exception during call of the method {method_name}: '
                    '{exc}').format(method_name=method_name, exc=str(e)))
            raise exc.HTTPBadRequest(e.message)
Beispiel #11
0
 def action(self, req, body):
     _actions = {
             'start': self._start_coverage,
             'stop': self._stop_coverage,
             'report': self._report_coverage,
     }
     authorize(req.environ['nova.context'])
     if not self.coverInst:
         msg = _("Python coverage module is not installed.")
         raise exc.HTTPServiceUnavailable(explanation=msg)
     for action, data in body.iteritems():
         if action == 'stop':
             return _actions[action](req)
         elif action == 'report' or action == 'start':
             return _actions[action](req, body)
         else:
             msg = _("Coverage doesn't have %s action") % action
             raise exc.HTTPBadRequest(explanation=msg)
     raise exc.HTTPBadRequest(explanation=_("Invalid request body"))
    def export(self, tools=None, send_email=False, **kw):
        """
        Initiate a bulk export of the project data.

        Must be given a list of tool mount points to include in the export.
        The list can either be comma-separated or a repeated param, e.g.,
        `export?tools=tickets&tools=discussion`.

        If the tools are not provided, an invalid mount point is listed, or
        there is some other problems with the arguments, a `400 Bad Request`
        response will be returned.

        If an export is already currently running for this project, a
        `503 Unavailable` response will be returned.

        Otherwise, a JSON object of the form
        `{"status": "in progress", "filename": FILENAME}` will be returned,
        where `FILENAME` is the filename of the export artifact relative to
        the users shell account directory.
        """
        if not asbool(config.get('bulk_export_enabled', True)):
            raise exc.HTTPNotFound()
        if not tools:
            raise exc.HTTPBadRequest(
                'Must give at least one tool mount point to export')
        tools = aslist(tools, ',')
        exportable_tools = AdminApp.exportable_tools_for(c.project)
        allowed = set(t.options.mount_point for t in exportable_tools)
        if not set(tools).issubset(allowed):
            raise exc.HTTPBadRequest('Invalid tool')
        if c.project.bulk_export_status() == 'busy':
            raise exc.HTTPServiceUnavailable(
                'Export for project %s already running' % c.project.shortname)
        # filename (potentially) includes a timestamp, so we have
        # to pre-generate to be able to return it to the user
        filename = c.project.bulk_export_filename()
        export_tasks.bulk_export.post(tools, filename, send_email=send_email)
        return {
            'status': 'in progress',
            'filename': filename,
        }
Beispiel #13
0
    def import_record(self, req, body):
        """Import a backup."""
        LOG.debug('Importing record from %s.', body)
        context = req.environ['cinder.context']
        import_data = body['backup-record']
        backup_service = import_data['backup_service']
        backup_url = import_data['backup_url']

        LOG.debug('Importing backup using %(service)s and url %(url)s.',
                  {'service': backup_service, 'url': backup_url})

        try:
            new_backup = self.backup_api.import_record(context,
                                                       backup_service,
                                                       backup_url)
        except exception.InvalidBackup as error:
            raise exc.HTTPBadRequest(explanation=error.msg)
        # Other Not found exceptions will be handled at the wsgi level
        except exception.ServiceNotFound as error:
            raise exc.HTTPServiceUnavailable(explanation=error.msg)

        retval = self._view_builder.summary(req, dict(new_backup))
        LOG.debug('Import record output: %s.', retval)
        return retval
Beispiel #14
0
    def __call__(self, req):
        """Ensures that the requested and token tenants match

        Handle incoming requests by checking tenant info from the
        headers and url ({tenant_id} url attribute), if using v1 or v1.1
        APIs. If using the v2 API, this function will check the token
        tenant and the requested tenent in the headers.

        Pass request downstream on success.
        Reject request if tenant_id from headers is not equal to the
        tenant_id from url or v2 project header.
        """
        path = req.environ['PATH_INFO']
        if path != '/':
            token_tenant = req.environ.get("HTTP_X_TENANT_ID")
            if not token_tenant:
                LOG.warning(_LW("Can't get tenant_id from env"))
                raise ex.HTTPServiceUnavailable()

            try:
                if path.startswith('/v2'):
                    version, rest = strutils.split_path(path, 2, 2, True)
                    requested_tenant = req.headers.get('OpenStack-Project-ID')
                else:

                    version, requested_tenant, rest = strutils.split_path(
                        path, 3, 3, True)
            except ValueError:
                LOG.warning(_LW("Incorrect path: {path}").format(path=path))
                raise ex.HTTPNotFound(_("Incorrect path"))

            if token_tenant != requested_tenant:
                LOG.debug("Unauthorized: token tenant != requested tenant")
                raise ex.HTTPUnauthorized(
                    _('Token tenant != requested tenant'))
        return self.application
Beispiel #15
0
    def proxy_to_dest(self, request, dest):
        """Do the actual proxying, without applying any transformations"""
        # Not using request.copy because I don't want to copy wsgi.input:

        try:
            proxy_req = self.construct_proxy_request(request, dest)
        except TypeError:
            return self.proxy_to_file(request, dest)

        proxy_req.path_info += request.path_info

        if proxy_req.query_string and request.query_string:
            proxy_req.query_string = '%s&%s' % \
                (proxy_req.query_string, request.query_string)
        elif request.query_string:
            proxy_req.query_string = request.query_string

        proxy_req.accept_encoding = None
        try:
            resp = proxy_req.get_response(proxy_exact_request)
            if resp.status_int == 500:
                print 'Request:'
                print proxy_req
                print 'Response:'
                print resp
        except socket.error, e:
            ## FIXME: really wsgiproxy should handle this
            ## FIXME: which error?
            ## 502 HTTPBadGateway, 503 HTTPServiceUnavailable, 504 HTTPGatewayTimeout?
            if isinstance(e.args, tuple) and len(e.args) > 1:
                error = e.args[1]
            else:
                error = str(e)
            resp = exc.HTTPServiceUnavailable(
                'Could not proxy the request to %s:%s : %s' 
                % (proxy_req.server_name, proxy_req.server_port, error))
Beispiel #16
0
            raise exc.HTTPForbidden()
        except AccessUnauthorized, instance:
            self._log.error("request {0}: unauthorized {1}".format(
                user_request_id, instance))
            raise exc.HTTPUnauthorized()
        except Exception, instance:
            self._log.exception("request {0}: auth exception {1}".format(
                user_request_id, instance))
            raise exc.HTTPBadRequest()

        try:
            key = urllib.unquote_plus(key)
            key = key.decode("utf-8")
        except Exception, instance:
            self._log.exception("request {0}".format(user_request_id))
            raise exc.HTTPServiceUnavailable(str(instance))

        version_id = None
        if "version_identifier" in req.GET:
            version_identifier = req.GET["version_identifier"]
            version_identifier = urllib.unquote_plus(version_identifier)
            version_id = self._id_translator.internal_id(version_identifier)

        lower_bound = 0
        upper_bound = None
        slice_offset = 0
        slice_size = None
        if "range" in req.headers:
            lower_bound, upper_bound, slice_offset, slice_size = \
                _parse_range_header(req.headers["range"])
Beispiel #17
0
 def _check_coverage_module_loaded(self):
     if not self.coverInst:
         msg = _("Python coverage module is not installed.")
         raise exc.HTTPServiceUnavailable(explanation=msg)
Beispiel #18
0
 def check_fping(self):
     if not os.access(CONF.api.fping_path, os.X_OK):
         raise exc.HTTPServiceUnavailable(
             explanation=_("fping utility is not found."))
Beispiel #19
0
    def _retrieve_key(self, req, match_object, user_request_id):
        unified_id = int(match_object.group("unified_id"))
        conjoined_part = int(match_object.group("conjoined_part"))

        lower_bound = 0
        upper_bound = None
        slice_offset = 0
        slice_size = None
        total_file_size = None
        if "range" in req.headers:
            lower_bound, upper_bound, slice_offset, slice_size = \
                _parse_range_header(req.headers["range"])
            if not "x-nimbus-io-expected-content-length" in req.headers:
                message = "expected x-nimbus-io-expected-content-length header"
                self._log.error("request {0} {1}".format(
                    user_request_id, message))
                raise exc.HTTPBadRequest(message)
            total_file_size = \
                int(req.headers["x-nimbus-io-expected-content-length"])

        assert slice_offset % block_size == 0, slice_offset
        block_offset = slice_offset / block_size
        if slice_size is None:
            block_count = None
        else:
            assert slice_size % block_size == 0, slice_size
            block_count = slice_size / block_size

        connected_data_readers = _connected_clients(self.data_readers)

        if len(connected_data_readers) < _min_connected_clients:
            self._log.error("request {0} too few connected readers {1}".format(
                user_request_id, len(connected_data_readers)))
            raise exc.HTTPServiceUnavailable(
                "Too few connected readers {0}".format(
                    len(connected_data_readers)))

        result = self._get_params_from_memcache(unified_id, conjoined_part)
        if result is None:
            self._log.warn("request {0} cache miss unified-id {1} {2}".format(
                user_request_id, unified_id, conjoined_part))
            result = self._get_params_from_database(unified_id, conjoined_part)
        if result is None:
            error_message = "unknown unified-id {0} {1}".format(
                unified_id, conjoined_part)
            self._log.error("request {0} {1}".format(user_request_id,
                                                     error_message))
            raise exc.HTTPServiceUnavailable(error_message)
        collection_id, key = result

        description = "request {0} retrieve: ({1}) key={2} unified_id={3}-{4} {5}:{6}".format(
            user_request_id, collection_id, key, unified_id, conjoined_part,
            slice_offset, slice_size)
        self._log.info(description)

        start_time = time.time()
        self._stats["retrieves"] += 1

        retriever = Retriever(self._node_local_connection, self.data_readers,
                              collection_id, key, unified_id, conjoined_part,
                              block_offset, block_count, _min_segments,
                              user_request_id)

        retrieved = retriever.retrieve(_reply_timeout)

        try:
            first_segments = retrieved.next()
        except RetrieveFailedError, instance:
            self._log.error("retrieve failed: {0} {1}".format(
                description, instance))
            self._stats["retrieves"] -= 1
            return exc.HTTPNotFound(str(instance))