1
# Copyright (c) 2010-2012 OpenStack, LLC.
3
# Licensed under the Apache License, Version 2.0 (the "License");
4
# you may not use this file except in compliance with the License.
5
# You may obtain a copy of the License at
7
# http://www.apache.org/licenses/LICENSE-2.0
9
# Unless required by applicable law or agreed to in writing, software
10
# distributed under the License is distributed on an "AS IS" BASIS,
11
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
13
# See the License for the specific language governing permissions and
14
# limitations under the License.
17
# You'll see swift_conn passed around a few places in this file. This is the
18
# source httplib connection of whatever it is attached to.
19
# It is used when early termination of reading from the connection should
20
# happen, such as when a range request is satisfied but there's still more the
21
# source connection would like to send. To prevent having to read all the data
22
# that could be left, the source connection can be .close() and then reads
23
# commence to empty out any buffers.
24
# These shenanigans are to ensure all related objects can be garbage
25
# collected. We've seen objects hang around forever otherwise.
28
from urllib import unquote
29
from random import shuffle
31
from webob.exc import HTTPBadRequest, HTTPForbidden, HTTPNotFound
33
from swift.common.utils import normalize_timestamp, public
34
from swift.common.constraints import check_metadata, MAX_CONTAINER_NAME_LENGTH
35
from swift.common.http import HTTP_ACCEPTED
36
from swift.proxy.controllers.base import Controller, delay_denial, \
37
get_container_memcache_key
40
class ContainerController(Controller):
41
"""WSGI controller for container requests"""
42
server_type = 'Container'
44
# Ensure these are all lowercase
45
pass_through_headers = ['x-container-read', 'x-container-write',
46
'x-container-sync-key', 'x-container-sync-to',
47
'x-versions-location']
49
def __init__(self, app, account_name, container_name, **kwargs):
50
Controller.__init__(self, app)
51
self.account_name = unquote(account_name)
52
self.container_name = unquote(container_name)
54
def clean_acls(self, req):
55
if 'swift.clean_acl' in req.environ:
56
for header in ('x-container-read', 'x-container-write'):
57
if header in req.headers:
59
req.headers[header] = \
60
req.environ['swift.clean_acl'](header,
62
except ValueError, err:
63
return HTTPBadRequest(request=req, body=str(err))
66
def GETorHEAD(self, req):
67
"""Handler for HTTP GET/HEAD requests."""
68
if not self.account_info(self.account_name)[1]:
69
return HTTPNotFound(request=req)
70
part, nodes = self.app.container_ring.get_nodes(
71
self.account_name, self.container_name)
73
resp = self.GETorHEAD_base(req, _('Container'), part, nodes,
74
req.path_info, len(nodes))
77
# set the memcache container size for ratelimiting
78
cache_key = get_container_memcache_key(self.account_name,
80
self.app.memcache.set(cache_key,
81
{'status': resp.status_int,
82
'read_acl': resp.headers.get('x-container-read'),
83
'write_acl': resp.headers.get('x-container-write'),
84
'sync_key': resp.headers.get('x-container-sync-key'),
85
'container_size': resp.headers.get('x-container-object-count'),
86
'versions': resp.headers.get('x-versions-location')},
87
timeout=self.app.recheck_container_existence)
89
if 'swift.authorize' in req.environ:
90
req.acl = resp.headers.get('x-container-read')
91
aresp = req.environ['swift.authorize'](req)
94
if not req.environ.get('swift_owner', False):
95
for key in ('x-container-read', 'x-container-write',
96
'x-container-sync-key', 'x-container-sync-to'):
97
if key in resp.headers:
104
"""Handler for HTTP GET requests."""
105
return self.GETorHEAD(req)
110
"""Handler for HTTP HEAD requests."""
111
return self.GETorHEAD(req)
115
"""HTTP PUT request handler."""
117
self.clean_acls(req) or check_metadata(req, 'container')
119
return error_response
120
if len(self.container_name) > MAX_CONTAINER_NAME_LENGTH:
121
resp = HTTPBadRequest(request=req)
122
resp.body = 'Container name length of %d longer than %d' % \
123
(len(self.container_name), MAX_CONTAINER_NAME_LENGTH)
125
account_partition, accounts, container_count = \
126
self.account_info(self.account_name,
127
autocreate=self.app.account_autocreate)
128
if self.app.max_containers_per_account > 0 and \
129
container_count >= self.app.max_containers_per_account and \
130
self.account_name not in self.app.max_containers_whitelist:
131
resp = HTTPForbidden(request=req)
132
resp.body = 'Reached container limit of %s' % \
133
self.app.max_containers_per_account
136
return HTTPNotFound(request=req)
137
container_partition, containers = self.app.container_ring.get_nodes(
138
self.account_name, self.container_name)
140
for account in accounts:
141
nheaders = {'X-Timestamp': normalize_timestamp(time.time()),
142
'x-trans-id': self.trans_id,
143
'X-Account-Host': '%(ip)s:%(port)s' % account,
144
'X-Account-Partition': account_partition,
145
'X-Account-Device': account['device'],
146
'Connection': 'close'}
147
self.transfer_headers(req.headers, nheaders)
148
headers.append(nheaders)
149
if self.app.memcache:
150
cache_key = get_container_memcache_key(self.account_name,
152
self.app.memcache.delete(cache_key)
153
resp = self.make_requests(req, self.app.container_ring,
154
container_partition, 'PUT', req.path_info, headers)
159
"""HTTP POST request handler."""
161
self.clean_acls(req) or check_metadata(req, 'container')
163
return error_response
164
account_partition, accounts, container_count = \
165
self.account_info(self.account_name,
166
autocreate=self.app.account_autocreate)
168
return HTTPNotFound(request=req)
169
container_partition, containers = self.app.container_ring.get_nodes(
170
self.account_name, self.container_name)
171
headers = {'X-Timestamp': normalize_timestamp(time.time()),
172
'x-trans-id': self.trans_id,
173
'Connection': 'close'}
174
self.transfer_headers(req.headers, headers)
175
if self.app.memcache:
176
cache_key = get_container_memcache_key(self.account_name,
178
self.app.memcache.delete(cache_key)
179
resp = self.make_requests(req, self.app.container_ring,
180
container_partition, 'POST', req.path_info,
181
[headers] * len(containers))
185
def DELETE(self, req):
186
"""HTTP DELETE request handler."""
187
account_partition, accounts, container_count = \
188
self.account_info(self.account_name)
190
return HTTPNotFound(request=req)
191
container_partition, containers = self.app.container_ring.get_nodes(
192
self.account_name, self.container_name)
194
for account in accounts:
195
headers.append({'X-Timestamp': normalize_timestamp(time.time()),
196
'X-Trans-Id': self.trans_id,
197
'X-Account-Host': '%(ip)s:%(port)s' % account,
198
'X-Account-Partition': account_partition,
199
'X-Account-Device': account['device'],
200
'Connection': 'close'})
201
if self.app.memcache:
202
cache_key = get_container_memcache_key(self.account_name,
204
self.app.memcache.delete(cache_key)
205
resp = self.make_requests(req, self.app.container_ring,
206
container_partition, 'DELETE', req.path_info, headers)
207
# Indicates no server had the container
208
if resp.status_int == HTTP_ACCEPTED:
209
return HTTPNotFound(request=req)