20
20
that relies on data locality information to avoid network overhead,
23
Answers requests of the form::
23
Using the original API, answers requests of the form::
25
25
/endpoints/{account}/{container}/{object}
26
26
/endpoints/{account}/{container}
27
27
/endpoints/{account}
28
/endpoints/v1/{account}/{container}/{object}
29
/endpoints/v1/{account}/{container}
30
/endpoints/v1/{account}
29
32
with a JSON-encoded list of endpoints of the form::
38
41
http://10.1.1.1:6000/sda1/2/a/c2
39
42
http://10.1.1.1:6000/sda1/2/a
44
Using the v2 API, answers requests of the form::
46
/endpoints/v2/{account}/{container}/{object}
47
/endpoints/v2/{account}/{container}
48
/endpoints/v2/{account}
50
with a JSON-encoded dictionary containing a key 'endpoints' that maps to a list
51
of endpoints having the same form as described above, and a key 'headers' that
52
maps to a dictionary of headers that should be sent with a request made to
55
{ "endpoints": {"http://10.1.1.1:6010/sda1/2/a/c3/o1",
56
"http://10.1.1.1:6030/sda3/2/a/c3/o1",
57
"http://10.1.1.1:6040/sda4/2/a/c3/o1"},
58
"headers": {"X-Backend-Storage-Policy-Index": "1"}}
60
In this example, the 'headers' dictionary indicates that requests to the
61
endpoint URLs should include the header 'X-Backend-Storage-Policy-Index: 1'
62
because the object's container is using storage policy index 1.
41
64
The '/endpoints/' path is customizable ('list_endpoints_path'
42
65
configuration parameter).
87
112
self.endpoints_path = conf.get('list_endpoints_path', '/endpoints/')
88
113
if not self.endpoints_path.endswith('/'):
89
114
self.endpoints_path += '/'
115
self.default_response_version = 1.0
116
self.response_map = {
117
1.0: self.v1_format_response,
118
2.0: self.v2_format_response,
91
121
def get_object_ring(self, policy_idx):
98
128
return POLICIES.get_object_ring(policy_idx, self.swift_dir)
130
def _parse_version(self, raw_version):
131
err_msg = 'Unsupported version %r' % raw_version
133
version = float(raw_version.lstrip('v'))
135
raise ValueError(err_msg)
136
if not any(version == v for v in RESPONSE_VERSIONS):
137
raise ValueError(err_msg)
140
def _parse_path(self, request):
142
Parse path parts of request into a tuple of version, account,
143
container, obj. Unspecified path parts are filled in as None,
144
except version which is always returned as a float using the
145
configured default response version if not specified in the
148
:param request: the swob request
150
:returns: parsed path parts as a tuple with version filled in as
151
configured default response version if not specified.
152
:raises: ValueError if path is invalid, message will say why.
154
clean_path = request.path[len(self.endpoints_path) - 1:]
155
# try to peel off version
157
raw_version, rest = split_path(clean_path, 1, 2, True)
159
raise ValueError('No account specified')
161
version = self._parse_version(raw_version)
163
if raw_version.startswith('v') and '_' not in raw_version:
164
# looks more like a invalid version than an account
166
# probably no version specified, but if the client really
167
# said /endpoints/v_3/account they'll probably be sorta
168
# confused by the useless response and lack of error.
169
version = self.default_response_version
172
rest = '/' + rest if rest else '/'
174
account, container, obj = split_path(rest, 1, 3, True)
176
raise ValueError('No account specified')
177
return version, account, container, obj
179
def v1_format_response(self, req, endpoints, **kwargs):
180
return Response(json.dumps(endpoints),
181
content_type='application/json')
183
def v2_format_response(self, req, endpoints, storage_policy_index,
186
'endpoints': endpoints,
189
if storage_policy_index is not None:
191
'X-Backend-Storage-Policy-Index'] = str(storage_policy_index)
192
return Response(json.dumps(resp),
193
content_type='application/json')
100
195
def __call__(self, env, start_response):
101
196
request = Request(env)
102
197
if not request.path.startswith(self.endpoints_path):
107
202
req=request, headers={"Allow": "GET"})(env, start_response)
110
clean_path = request.path[len(self.endpoints_path) - 1:]
111
account, container, obj = \
112
split_path(clean_path, 1, 3, True)
114
return HTTPBadRequest('No account specified')(env, start_response)
205
version, account, container, obj = self._parse_path(request)
206
except ValueError as err:
207
return HTTPBadRequest(str(err))(env, start_response)
116
209
if account is not None:
117
210
account = unquote(account)
120
213
if obj is not None:
121
214
obj = unquote(obj)
216
storage_policy_index = None
123
217
if obj is not None:
124
# remove 'endpoints' from call to get_container_info
125
stripped = request.environ
126
if stripped['PATH_INFO'][:len(self.endpoints_path)] == \
128
stripped['PATH_INFO'] = "/v1/" + \
129
stripped['PATH_INFO'][len(self.endpoints_path):]
130
218
container_info = get_container_info(
131
stripped, self.app, swift_source='LE')
132
obj_ring = self.get_object_ring(container_info['storage_policy'])
219
{'PATH_INFO': '/v1/%s/%s' % (account, container)},
220
self.app, swift_source='LE')
221
storage_policy_index = container_info['storage_policy']
222
obj_ring = self.get_object_ring(storage_policy_index)
133
223
partition, nodes = obj_ring.get_nodes(
134
224
account, container, obj)
135
225
endpoint_template = 'http://{ip}:{port}/{device}/{partition}/' + \
157
247
obj=quote(obj or ''))
158
248
endpoints.append(endpoint)
160
return Response(json.dumps(endpoints),
161
content_type='application/json')(env, start_response)
250
resp = self.response_map[version](
251
request, endpoints=endpoints,
252
storage_policy_index=storage_policy_index)
253
return resp(env, start_response)
164
256
def filter_factory(global_conf, **local_conf):