~ubuntu-branches/ubuntu/vivid/swift/vivid-updates

« back to all changes in this revision

Viewing changes to swift/common/middleware/list_endpoints.py

  • Committer: Package Import Robot
  • Author(s): James Page, Chuck Short, James Page
  • Date: 2014-10-06 10:06:11 UTC
  • mfrom: (1.2.31)
  • Revision ID: package-import@ubuntu.com-20141006100611-wdzkkuoru7ubtlml
Tags: 2.1.0-0ubuntu1
[ Chuck Short ]
* debian/patches/fix-doc-no-network.patch: Refreshed.
* debian/control: Add python-oslosphinx as a build dependency.

[ James Page ]
* New upstream release for OpenStack Juno.
* d/copyright: Add linebreaks to fixup file-without-copyright-
  information warning.

Show diffs side-by-side

added added

removed removed

Lines of Context:
20
20
that relies on data locality information to avoid network overhead,
21
21
such as Hadoop.
22
22
 
23
 
Answers requests of the form::
 
23
Using the original API, answers requests of the form::
24
24
 
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}
28
31
 
29
32
with a JSON-encoded list of endpoints of the form::
30
33
 
38
41
    http://10.1.1.1:6000/sda1/2/a/c2
39
42
    http://10.1.1.1:6000/sda1/2/a
40
43
 
 
44
Using the v2 API, answers requests of the form::
 
45
 
 
46
    /endpoints/v2/{account}/{container}/{object}
 
47
    /endpoints/v2/{account}/{container}
 
48
    /endpoints/v2/{account}
 
49
 
 
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
 
53
the endpoints, e.g.::
 
54
 
 
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"}}
 
59
 
 
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.
 
63
 
41
64
The '/endpoints/' path is customizable ('list_endpoints_path'
42
65
configuration parameter).
43
66
 
64
87
from swift.common.storage_policy import POLICIES
65
88
from swift.proxy.controllers.base import get_container_info
66
89
 
 
90
RESPONSE_VERSIONS = (1.0, 2.0)
 
91
 
67
92
 
68
93
class ListEndpointsMiddleware(object):
69
94
    """
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,
 
119
        }
90
120
 
91
121
    def get_object_ring(self, policy_idx):
92
122
        """
97
127
        """
98
128
        return POLICIES.get_object_ring(policy_idx, self.swift_dir)
99
129
 
 
130
    def _parse_version(self, raw_version):
 
131
        err_msg = 'Unsupported version %r' % raw_version
 
132
        try:
 
133
            version = float(raw_version.lstrip('v'))
 
134
        except ValueError:
 
135
            raise ValueError(err_msg)
 
136
        if not any(version == v for v in RESPONSE_VERSIONS):
 
137
            raise ValueError(err_msg)
 
138
        return version
 
139
 
 
140
    def _parse_path(self, request):
 
141
        """
 
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
 
146
        request.
 
147
 
 
148
        :param request: the swob request
 
149
 
 
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.
 
153
        """
 
154
        clean_path = request.path[len(self.endpoints_path) - 1:]
 
155
        # try to peel off version
 
156
        try:
 
157
            raw_version, rest = split_path(clean_path, 1, 2, True)
 
158
        except ValueError:
 
159
            raise ValueError('No account specified')
 
160
        try:
 
161
            version = self._parse_version(raw_version)
 
162
        except ValueError:
 
163
            if raw_version.startswith('v') and '_' not in raw_version:
 
164
                # looks more like a invalid version than an account
 
165
                raise
 
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
 
170
            rest = clean_path
 
171
        else:
 
172
            rest = '/' + rest if rest else '/'
 
173
        try:
 
174
            account, container, obj = split_path(rest, 1, 3, True)
 
175
        except ValueError:
 
176
            raise ValueError('No account specified')
 
177
        return version, account, container, obj
 
178
 
 
179
    def v1_format_response(self, req, endpoints, **kwargs):
 
180
        return Response(json.dumps(endpoints),
 
181
                        content_type='application/json')
 
182
 
 
183
    def v2_format_response(self, req, endpoints, storage_policy_index,
 
184
                           **kwargs):
 
185
        resp = {
 
186
            'endpoints': endpoints,
 
187
            'headers': {},
 
188
        }
 
189
        if storage_policy_index is not None:
 
190
            resp['headers'][
 
191
                'X-Backend-Storage-Policy-Index'] = str(storage_policy_index)
 
192
        return Response(json.dumps(resp),
 
193
                        content_type='application/json')
 
194
 
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)
108
203
 
109
204
        try:
110
 
            clean_path = request.path[len(self.endpoints_path) - 1:]
111
 
            account, container, obj = \
112
 
                split_path(clean_path, 1, 3, True)
113
 
        except ValueError:
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)
115
208
 
116
209
        if account is not None:
117
210
            account = unquote(account)
120
213
        if obj is not None:
121
214
            obj = unquote(obj)
122
215
 
 
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)] == \
127
 
                    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)
159
249
 
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)
162
254
 
163
255
 
164
256
def filter_factory(global_conf, **local_conf):