
This change adds 2 new parameters to enable and control concurrent GETs in swift, these are 'concurrent_gets' and 'concurrency_timeout'. 'concurrent_gets' allows you to turn on or off concurrent GETs, when on it will set the GET/HEAD concurrency to replica count. And in the case of EC HEADs it will set it to ndata. The proxy will then serve only the first valid source to respond. This applies to all account, container and object GETs except for EC. For EC only HEAD requests are effected. It achieves this by changing the request sending mechanism to using GreenAsyncPile and green threads with a time out between each request. 'concurrency_timeout' is related to concurrent_gets. And is the amount of time to wait before firing the next thread. A value of 0 will fire at the same time (fully concurrent), setting another value will stagger the firing allowing you the ability to give a node a shorter chance to respond before firing the next. This value is a float and should be somewhere between 0 and node_timeout. The default is conn_timeout. Meaning by default it will stagger the firing. DocImpact Implements: blueprint concurrent-reads Change-Id: I789d39472ec48b22415ff9d9821b1eefab7da867
155 lines
6.8 KiB
Python
155 lines
6.8 KiB
Python
# Copyright (c) 2010-2012 OpenStack Foundation
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
# implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
|
|
from six.moves.urllib.parse import unquote
|
|
|
|
from swift import gettext_ as _
|
|
|
|
from swift.account.utils import account_listing_response
|
|
from swift.common.request_helpers import get_listing_content_type
|
|
from swift.common.middleware.acl import parse_acl, format_acl
|
|
from swift.common.utils import public
|
|
from swift.common.constraints import check_metadata
|
|
from swift.common import constraints
|
|
from swift.common.http import HTTP_NOT_FOUND, HTTP_GONE
|
|
from swift.proxy.controllers.base import Controller, clear_info_cache
|
|
from swift.common.swob import HTTPBadRequest, HTTPMethodNotAllowed
|
|
from swift.common.request_helpers import get_sys_meta_prefix
|
|
|
|
|
|
class AccountController(Controller):
|
|
"""WSGI controller for account requests"""
|
|
server_type = 'Account'
|
|
|
|
def __init__(self, app, account_name, **kwargs):
|
|
Controller.__init__(self, app)
|
|
self.account_name = unquote(account_name)
|
|
if not self.app.allow_account_management:
|
|
self.allowed_methods.remove('PUT')
|
|
self.allowed_methods.remove('DELETE')
|
|
|
|
def add_acls_from_sys_metadata(self, resp):
|
|
if resp.environ['REQUEST_METHOD'] in ('HEAD', 'GET', 'PUT', 'POST'):
|
|
prefix = get_sys_meta_prefix('account') + 'core-'
|
|
name = 'access-control'
|
|
(extname, intname) = ('x-account-' + name, prefix + name)
|
|
acl_dict = parse_acl(version=2, data=resp.headers.pop(intname))
|
|
if acl_dict: # treat empty dict as empty header
|
|
resp.headers[extname] = format_acl(
|
|
version=2, acl_dict=acl_dict)
|
|
|
|
def GETorHEAD(self, req):
|
|
"""Handler for HTTP GET/HEAD requests."""
|
|
if len(self.account_name) > constraints.MAX_ACCOUNT_NAME_LENGTH:
|
|
resp = HTTPBadRequest(request=req)
|
|
resp.body = 'Account name length of %d longer than %d' % \
|
|
(len(self.account_name),
|
|
constraints.MAX_ACCOUNT_NAME_LENGTH)
|
|
return resp
|
|
|
|
partition = self.app.account_ring.get_part(self.account_name)
|
|
concurrency = self.app.account_ring.replica_count \
|
|
if self.app.concurrent_gets else 1
|
|
node_iter = self.app.iter_nodes(self.app.account_ring, partition)
|
|
resp = self.GETorHEAD_base(
|
|
req, _('Account'), node_iter, partition,
|
|
req.swift_entity_path.rstrip('/'), concurrency)
|
|
if resp.status_int == HTTP_NOT_FOUND:
|
|
if resp.headers.get('X-Account-Status', '').lower() == 'deleted':
|
|
resp.status = HTTP_GONE
|
|
elif self.app.account_autocreate:
|
|
resp = account_listing_response(self.account_name, req,
|
|
get_listing_content_type(req))
|
|
if req.environ.get('swift_owner'):
|
|
self.add_acls_from_sys_metadata(resp)
|
|
else:
|
|
for header in self.app.swift_owner_headers:
|
|
resp.headers.pop(header, None)
|
|
return resp
|
|
|
|
@public
|
|
def PUT(self, req):
|
|
"""HTTP PUT request handler."""
|
|
if not self.app.allow_account_management:
|
|
return HTTPMethodNotAllowed(
|
|
request=req,
|
|
headers={'Allow': ', '.join(self.allowed_methods)})
|
|
error_response = check_metadata(req, 'account')
|
|
if error_response:
|
|
return error_response
|
|
if len(self.account_name) > constraints.MAX_ACCOUNT_NAME_LENGTH:
|
|
resp = HTTPBadRequest(request=req)
|
|
resp.body = 'Account name length of %d longer than %d' % \
|
|
(len(self.account_name),
|
|
constraints.MAX_ACCOUNT_NAME_LENGTH)
|
|
return resp
|
|
account_partition, accounts = \
|
|
self.app.account_ring.get_nodes(self.account_name)
|
|
headers = self.generate_request_headers(req, transfer=True)
|
|
clear_info_cache(self.app, req.environ, self.account_name)
|
|
resp = self.make_requests(
|
|
req, self.app.account_ring, account_partition, 'PUT',
|
|
req.swift_entity_path, [headers] * len(accounts))
|
|
self.add_acls_from_sys_metadata(resp)
|
|
return resp
|
|
|
|
@public
|
|
def POST(self, req):
|
|
"""HTTP POST request handler."""
|
|
if len(self.account_name) > constraints.MAX_ACCOUNT_NAME_LENGTH:
|
|
resp = HTTPBadRequest(request=req)
|
|
resp.body = 'Account name length of %d longer than %d' % \
|
|
(len(self.account_name),
|
|
constraints.MAX_ACCOUNT_NAME_LENGTH)
|
|
return resp
|
|
error_response = check_metadata(req, 'account')
|
|
if error_response:
|
|
return error_response
|
|
account_partition, accounts = \
|
|
self.app.account_ring.get_nodes(self.account_name)
|
|
headers = self.generate_request_headers(req, transfer=True)
|
|
clear_info_cache(self.app, req.environ, self.account_name)
|
|
resp = self.make_requests(
|
|
req, self.app.account_ring, account_partition, 'POST',
|
|
req.swift_entity_path, [headers] * len(accounts))
|
|
if resp.status_int == HTTP_NOT_FOUND and self.app.account_autocreate:
|
|
self.autocreate_account(req, self.account_name)
|
|
resp = self.make_requests(
|
|
req, self.app.account_ring, account_partition, 'POST',
|
|
req.swift_entity_path, [headers] * len(accounts))
|
|
self.add_acls_from_sys_metadata(resp)
|
|
return resp
|
|
|
|
@public
|
|
def DELETE(self, req):
|
|
"""HTTP DELETE request handler."""
|
|
# Extra safety in case someone typos a query string for an
|
|
# account-level DELETE request that was really meant to be caught by
|
|
# some middleware.
|
|
if req.query_string:
|
|
return HTTPBadRequest(request=req)
|
|
if not self.app.allow_account_management:
|
|
return HTTPMethodNotAllowed(
|
|
request=req,
|
|
headers={'Allow': ', '.join(self.allowed_methods)})
|
|
account_partition, accounts = \
|
|
self.app.account_ring.get_nodes(self.account_name)
|
|
headers = self.generate_request_headers(req)
|
|
clear_info_cache(self.app, req.environ, self.account_name)
|
|
resp = self.make_requests(
|
|
req, self.app.account_ring, account_partition, 'DELETE',
|
|
req.swift_entity_path, [headers] * len(accounts))
|
|
return resp
|