
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
247 lines
10 KiB
Python
247 lines
10 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 swift import gettext_ as _
|
|
import time
|
|
|
|
from six.moves.urllib.parse import unquote
|
|
from swift.common.utils import public, csv_append, Timestamp
|
|
from swift.common.constraints import check_metadata
|
|
from swift.common import constraints
|
|
from swift.common.http import HTTP_ACCEPTED, is_success
|
|
from swift.proxy.controllers.base import Controller, delay_denial, \
|
|
cors_validation, clear_info_cache
|
|
from swift.common.storage_policy import POLICIES
|
|
from swift.common.swob import HTTPBadRequest, HTTPForbidden, \
|
|
HTTPNotFound
|
|
|
|
|
|
class ContainerController(Controller):
|
|
"""WSGI controller for container requests"""
|
|
server_type = 'Container'
|
|
|
|
# Ensure these are all lowercase
|
|
pass_through_headers = ['x-container-read', 'x-container-write',
|
|
'x-container-sync-key', 'x-container-sync-to',
|
|
'x-versions-location']
|
|
|
|
def __init__(self, app, account_name, container_name, **kwargs):
|
|
Controller.__init__(self, app)
|
|
self.account_name = unquote(account_name)
|
|
self.container_name = unquote(container_name)
|
|
|
|
def _x_remove_headers(self):
|
|
st = self.server_type.lower()
|
|
return ['x-remove-%s-read' % st,
|
|
'x-remove-%s-write' % st,
|
|
'x-remove-versions-location',
|
|
'x-remove-%s-sync-key' % st,
|
|
'x-remove-%s-sync-to' % st]
|
|
|
|
def _convert_policy_to_index(self, req):
|
|
"""
|
|
Helper method to convert a policy name (from a request from a client)
|
|
to a policy index (for a request to a backend).
|
|
|
|
:param req: incoming request
|
|
"""
|
|
policy_name = req.headers.get('X-Storage-Policy')
|
|
if not policy_name:
|
|
return
|
|
policy = POLICIES.get_by_name(policy_name)
|
|
if not policy:
|
|
raise HTTPBadRequest(request=req,
|
|
content_type="text/plain",
|
|
body=("Invalid %s '%s'"
|
|
% ('X-Storage-Policy', policy_name)))
|
|
if policy.is_deprecated:
|
|
body = 'Storage Policy %r is deprecated' % (policy.name)
|
|
raise HTTPBadRequest(request=req, body=body)
|
|
return int(policy)
|
|
|
|
def clean_acls(self, req):
|
|
if 'swift.clean_acl' in req.environ:
|
|
for header in ('x-container-read', 'x-container-write'):
|
|
if header in req.headers:
|
|
try:
|
|
req.headers[header] = \
|
|
req.environ['swift.clean_acl'](header,
|
|
req.headers[header])
|
|
except ValueError as err:
|
|
return HTTPBadRequest(request=req, body=str(err))
|
|
return None
|
|
|
|
def GETorHEAD(self, req):
|
|
"""Handler for HTTP GET/HEAD requests."""
|
|
if not self.account_info(self.account_name, req)[1]:
|
|
if 'swift.authorize' in req.environ:
|
|
aresp = req.environ['swift.authorize'](req)
|
|
if aresp:
|
|
return aresp
|
|
return HTTPNotFound(request=req)
|
|
part = self.app.container_ring.get_part(
|
|
self.account_name, self.container_name)
|
|
concurrency = self.app.container_ring.replica_count \
|
|
if self.app.concurrent_gets else 1
|
|
node_iter = self.app.iter_nodes(self.app.container_ring, part)
|
|
resp = self.GETorHEAD_base(
|
|
req, _('Container'), node_iter, part,
|
|
req.swift_entity_path, concurrency)
|
|
if 'swift.authorize' in req.environ:
|
|
req.acl = resp.headers.get('x-container-read')
|
|
aresp = req.environ['swift.authorize'](req)
|
|
if aresp:
|
|
return aresp
|
|
if not req.environ.get('swift_owner', False):
|
|
for key in self.app.swift_owner_headers:
|
|
if key in resp.headers:
|
|
del resp.headers[key]
|
|
return resp
|
|
|
|
@public
|
|
@delay_denial
|
|
@cors_validation
|
|
def GET(self, req):
|
|
"""Handler for HTTP GET requests."""
|
|
return self.GETorHEAD(req)
|
|
|
|
@public
|
|
@delay_denial
|
|
@cors_validation
|
|
def HEAD(self, req):
|
|
"""Handler for HTTP HEAD requests."""
|
|
return self.GETorHEAD(req)
|
|
|
|
@public
|
|
@cors_validation
|
|
def PUT(self, req):
|
|
"""HTTP PUT request handler."""
|
|
error_response = \
|
|
self.clean_acls(req) or check_metadata(req, 'container')
|
|
if error_response:
|
|
return error_response
|
|
policy_index = self._convert_policy_to_index(req)
|
|
if not req.environ.get('swift_owner'):
|
|
for key in self.app.swift_owner_headers:
|
|
req.headers.pop(key, None)
|
|
if len(self.container_name) > constraints.MAX_CONTAINER_NAME_LENGTH:
|
|
resp = HTTPBadRequest(request=req)
|
|
resp.body = 'Container name length of %d longer than %d' % \
|
|
(len(self.container_name),
|
|
constraints.MAX_CONTAINER_NAME_LENGTH)
|
|
return resp
|
|
account_partition, accounts, container_count = \
|
|
self.account_info(self.account_name, req)
|
|
if not accounts and self.app.account_autocreate:
|
|
self.autocreate_account(req, self.account_name)
|
|
account_partition, accounts, container_count = \
|
|
self.account_info(self.account_name, req)
|
|
if not accounts:
|
|
return HTTPNotFound(request=req)
|
|
if self.app.max_containers_per_account > 0 and \
|
|
container_count >= self.app.max_containers_per_account and \
|
|
self.account_name not in self.app.max_containers_whitelist:
|
|
container_info = \
|
|
self.container_info(self.account_name, self.container_name,
|
|
req)
|
|
if not is_success(container_info.get('status')):
|
|
resp = HTTPForbidden(request=req)
|
|
resp.body = 'Reached container limit of %s' % \
|
|
self.app.max_containers_per_account
|
|
return resp
|
|
container_partition, containers = self.app.container_ring.get_nodes(
|
|
self.account_name, self.container_name)
|
|
headers = self._backend_requests(req, len(containers),
|
|
account_partition, accounts,
|
|
policy_index)
|
|
clear_info_cache(self.app, req.environ,
|
|
self.account_name, self.container_name)
|
|
resp = self.make_requests(
|
|
req, self.app.container_ring,
|
|
container_partition, 'PUT', req.swift_entity_path, headers)
|
|
return resp
|
|
|
|
@public
|
|
@cors_validation
|
|
def POST(self, req):
|
|
"""HTTP POST request handler."""
|
|
error_response = \
|
|
self.clean_acls(req) or check_metadata(req, 'container')
|
|
if error_response:
|
|
return error_response
|
|
if not req.environ.get('swift_owner'):
|
|
for key in self.app.swift_owner_headers:
|
|
req.headers.pop(key, None)
|
|
account_partition, accounts, container_count = \
|
|
self.account_info(self.account_name, req)
|
|
if not accounts:
|
|
return HTTPNotFound(request=req)
|
|
container_partition, containers = self.app.container_ring.get_nodes(
|
|
self.account_name, self.container_name)
|
|
headers = self.generate_request_headers(req, transfer=True)
|
|
clear_info_cache(self.app, req.environ,
|
|
self.account_name, self.container_name)
|
|
resp = self.make_requests(
|
|
req, self.app.container_ring, container_partition, 'POST',
|
|
req.swift_entity_path, [headers] * len(containers))
|
|
return resp
|
|
|
|
@public
|
|
@cors_validation
|
|
def DELETE(self, req):
|
|
"""HTTP DELETE request handler."""
|
|
account_partition, accounts, container_count = \
|
|
self.account_info(self.account_name, req)
|
|
if not accounts:
|
|
return HTTPNotFound(request=req)
|
|
container_partition, containers = self.app.container_ring.get_nodes(
|
|
self.account_name, self.container_name)
|
|
headers = self._backend_requests(req, len(containers),
|
|
account_partition, accounts)
|
|
clear_info_cache(self.app, req.environ,
|
|
self.account_name, self.container_name)
|
|
resp = self.make_requests(
|
|
req, self.app.container_ring, container_partition, 'DELETE',
|
|
req.swift_entity_path, headers)
|
|
# Indicates no server had the container
|
|
if resp.status_int == HTTP_ACCEPTED:
|
|
return HTTPNotFound(request=req)
|
|
return resp
|
|
|
|
def _backend_requests(self, req, n_outgoing, account_partition, accounts,
|
|
policy_index=None):
|
|
additional = {'X-Timestamp': Timestamp(time.time()).internal}
|
|
if policy_index is None:
|
|
additional['X-Backend-Storage-Policy-Default'] = \
|
|
int(POLICIES.default)
|
|
else:
|
|
additional['X-Backend-Storage-Policy-Index'] = str(policy_index)
|
|
headers = [self.generate_request_headers(req, transfer=True,
|
|
additional=additional)
|
|
for _junk in range(n_outgoing)]
|
|
|
|
for i, account in enumerate(accounts):
|
|
i = i % len(headers)
|
|
|
|
headers[i]['X-Account-Partition'] = account_partition
|
|
headers[i]['X-Account-Host'] = csv_append(
|
|
headers[i].get('X-Account-Host'),
|
|
'%(ip)s:%(port)s' % account)
|
|
headers[i]['X-Account-Device'] = csv_append(
|
|
headers[i].get('X-Account-Device'),
|
|
account['device'])
|
|
|
|
return headers
|