Merge lp:~rackspace-titan/nova/servers-next into lp:~hudson-openstack/nova/trunk
- servers-next
- Merge into trunk
Status: | Merged | ||||
---|---|---|---|---|---|
Approved by: | Brian Lamar | ||||
Approved revision: | 1589 | ||||
Merged at revision: | 1615 | ||||
Proposed branch: | lp:~rackspace-titan/nova/servers-next | ||||
Merge into: | lp:~hudson-openstack/nova/trunk | ||||
Diff against target: |
362 lines (+216/-9) 5 files modified
nova/api/openstack/common.py (+10/-0) nova/api/openstack/schemas/v1.1/servers_index.rng (+3/-0) nova/api/openstack/servers.py (+35/-7) nova/api/openstack/views/servers.py (+36/-1) nova/tests/api/openstack/test_servers.py (+132/-1) |
||||
To merge this branch: | bzr merge lp:~rackspace-titan/nova/servers-next | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Brian Lamar (community) | Approve | ||
Brian Waldon (community) | Approve | ||
Review via email: mp+75558@code.launchpad.net |
Commit message
Description of the change
Add next links for server lists in OSAPI 1.1. This adds servers_links to the json responses, and an extra atom:link element to the servers node in the xml response.
- 1582. By William Wolf
-
merge with trunk
- 1583. By William Wolf
-
make our own function instead of using urllib.urlencode since we apparently don't suppor urlencoded strings yet
- 1584. By William Wolf
-
merge with trunk
William Wolf (throughnothing) wrote : | # |
I've replaced urllib with a common function that doesn't urlencode the strings from the dict. I think ultimately we should support urlencoded URL's though, and give those in our own links just to ensure everything is correctly set/interpreted.
Brian Waldon (bcwaldon) wrote : | # |
I wouldn't call it elegant, but it definitely works. However, now I'm getting the following url back. Notice the missing 'admin/servers' after 'v1.1':
http://
William Wolf (throughnothing) wrote : | # |
Wow, good catch waldon. Seems I wasn't even checking that in the tests either. Updated the tests and the code, that should be fixed now.
- 1585. By William Wolf
-
remove urllib import
- 1586. By William Wolf
-
oops, add project_id and 'servers' to next links
- 1587. By William Wolf
-
merged with trunk
- 1588. By William Wolf
-
merge from trunk
- 1589. By William Wolf
-
pep8 fixes
Brian Lamar (blamar) : | # |
Preview Diff
1 | === modified file 'nova/api/openstack/common.py' | |||
2 | --- nova/api/openstack/common.py 2011-09-14 15:23:11 +0000 | |||
3 | +++ nova/api/openstack/common.py 2011-09-20 18:47:17 +0000 | |||
4 | @@ -259,6 +259,16 @@ | |||
5 | 259 | headers={'Retry-After': 0}) | 259 | headers={'Retry-After': 0}) |
6 | 260 | 260 | ||
7 | 261 | 261 | ||
8 | 262 | def dict_to_query_str(params): | ||
9 | 263 | # TODO: we should just use urllib.urlencode instead of this | ||
10 | 264 | # But currently we don't work with urlencoded url's | ||
11 | 265 | param_str = "" | ||
12 | 266 | for key, val in params.iteritems(): | ||
13 | 267 | param_str = param_str + '='.join([str(key), str(val)]) + '&' | ||
14 | 268 | |||
15 | 269 | return param_str.rstrip('&') | ||
16 | 270 | |||
17 | 271 | |||
18 | 262 | class MetadataXMLDeserializer(wsgi.XMLDeserializer): | 272 | class MetadataXMLDeserializer(wsgi.XMLDeserializer): |
19 | 263 | 273 | ||
20 | 264 | def extract_metadata(self, metadata_node): | 274 | def extract_metadata(self, metadata_node): |
21 | 265 | 275 | ||
22 | === modified file 'nova/api/openstack/schemas/v1.1/servers_index.rng' | |||
23 | --- nova/api/openstack/schemas/v1.1/servers_index.rng 2011-08-19 19:55:56 +0000 | |||
24 | +++ nova/api/openstack/schemas/v1.1/servers_index.rng 2011-09-20 18:47:17 +0000 | |||
25 | @@ -9,4 +9,7 @@ | |||
26 | 9 | </zeroOrMore> | 9 | </zeroOrMore> |
27 | 10 | </element> | 10 | </element> |
28 | 11 | </zeroOrMore> | 11 | </zeroOrMore> |
29 | 12 | <zeroOrMore> | ||
30 | 13 | <externalRef href="../atom-link.rng"/> | ||
31 | 14 | </zeroOrMore> | ||
32 | 12 | </element> | 15 | </element> |
33 | 13 | 16 | ||
34 | === modified file 'nova/api/openstack/servers.py' | |||
35 | --- nova/api/openstack/servers.py 2011-09-18 21:01:44 +0000 | |||
36 | +++ nova/api/openstack/servers.py 2011-09-20 18:47:17 +0000 | |||
37 | @@ -75,6 +75,9 @@ | |||
38 | 75 | def _build_view(self, req, instance, is_detail=False): | 75 | def _build_view(self, req, instance, is_detail=False): |
39 | 76 | raise NotImplementedError() | 76 | raise NotImplementedError() |
40 | 77 | 77 | ||
41 | 78 | def _build_list(self, req, instances, is_detail=False): | ||
42 | 79 | raise NotImplementedError() | ||
43 | 80 | |||
44 | 78 | def _limit_items(self, items, req): | 81 | def _limit_items(self, items, req): |
45 | 79 | raise NotImplementedError() | 82 | raise NotImplementedError() |
46 | 80 | 83 | ||
47 | @@ -130,10 +133,7 @@ | |||
48 | 130 | search_opts=search_opts) | 133 | search_opts=search_opts) |
49 | 131 | 134 | ||
50 | 132 | limited_list = self._limit_items(instance_list, req) | 135 | limited_list = self._limit_items(instance_list, req) |
55 | 133 | servers = [self._build_view(req, inst, is_detail)['server'] | 136 | return self._build_list(req, limited_list, is_detail=is_detail) |
52 | 134 | for inst in limited_list] | ||
53 | 135 | |||
54 | 136 | return dict(servers=servers) | ||
56 | 137 | 137 | ||
57 | 138 | @scheduler_api.redirect_handler | 138 | @scheduler_api.redirect_handler |
58 | 139 | def show(self, req, id): | 139 | def show(self, req, id): |
59 | @@ -595,6 +595,11 @@ | |||
60 | 595 | builder = nova.api.openstack.views.servers.ViewBuilderV10(addresses) | 595 | builder = nova.api.openstack.views.servers.ViewBuilderV10(addresses) |
61 | 596 | return builder.build(instance, is_detail=is_detail) | 596 | return builder.build(instance, is_detail=is_detail) |
62 | 597 | 597 | ||
63 | 598 | def _build_list(self, req, instances, is_detail=False): | ||
64 | 599 | addresses = nova.api.openstack.views.addresses.ViewBuilderV10() | ||
65 | 600 | builder = nova.api.openstack.views.servers.ViewBuilderV10(addresses) | ||
66 | 601 | return builder.build_list(instances, is_detail=is_detail) | ||
67 | 602 | |||
68 | 598 | def _limit_items(self, items, req): | 603 | def _limit_items(self, items, req): |
69 | 599 | return common.limited(items, req) | 604 | return common.limited(items, req) |
70 | 600 | 605 | ||
71 | @@ -692,6 +697,25 @@ | |||
72 | 692 | 697 | ||
73 | 693 | return builder.build(instance, is_detail=is_detail) | 698 | return builder.build(instance, is_detail=is_detail) |
74 | 694 | 699 | ||
75 | 700 | def _build_list(self, req, instances, is_detail=False): | ||
76 | 701 | params = req.GET.copy() | ||
77 | 702 | pagination_params = common.get_pagination_params(req) | ||
78 | 703 | # Update params with int() values from pagination params | ||
79 | 704 | for key, val in pagination_params.iteritems(): | ||
80 | 705 | params[key] = val | ||
81 | 706 | |||
82 | 707 | project_id = getattr(req.environ['nova.context'], 'project_id', '') | ||
83 | 708 | base_url = req.application_url | ||
84 | 709 | flavor_builder = nova.api.openstack.views.flavors.ViewBuilderV11( | ||
85 | 710 | base_url, project_id) | ||
86 | 711 | image_builder = nova.api.openstack.views.images.ViewBuilderV11( | ||
87 | 712 | base_url, project_id) | ||
88 | 713 | addresses_builder = nova.api.openstack.views.addresses.ViewBuilderV11() | ||
89 | 714 | builder = nova.api.openstack.views.servers.ViewBuilderV11( | ||
90 | 715 | addresses_builder, flavor_builder, image_builder, | ||
91 | 716 | base_url, project_id) | ||
92 | 717 | return builder.build_list(instances, is_detail=is_detail, **params) | ||
93 | 718 | |||
94 | 695 | def _action_change_password(self, input_dict, req, id): | 719 | def _action_change_password(self, input_dict, req, id): |
95 | 696 | context = req.environ['nova.context'] | 720 | context = req.environ['nova.context'] |
96 | 697 | if (not 'changePassword' in input_dict | 721 | if (not 'changePassword' in input_dict |
97 | @@ -939,18 +963,22 @@ | |||
98 | 939 | 'security_group') | 963 | 'security_group') |
99 | 940 | group_elem.set('name', group['name']) | 964 | group_elem.set('name', group['name']) |
100 | 941 | 965 | ||
103 | 942 | for link in server_dict.get('links', []): | 966 | self._populate_links(server_elem, server_dict.get('links', [])) |
104 | 943 | elem = etree.SubElement(server_elem, | 967 | |
105 | 968 | def _populate_links(self, parent, links): | ||
106 | 969 | for link in links: | ||
107 | 970 | elem = etree.SubElement(parent, | ||
108 | 944 | '{%s}link' % xmlutil.XMLNS_ATOM) | 971 | '{%s}link' % xmlutil.XMLNS_ATOM) |
109 | 945 | elem.set('rel', link['rel']) | 972 | elem.set('rel', link['rel']) |
110 | 946 | elem.set('href', link['href']) | 973 | elem.set('href', link['href']) |
111 | 947 | return server_elem | ||
112 | 948 | 974 | ||
113 | 949 | def index(self, servers_dict): | 975 | def index(self, servers_dict): |
114 | 950 | servers = etree.Element('servers', nsmap=self.NSMAP) | 976 | servers = etree.Element('servers', nsmap=self.NSMAP) |
115 | 951 | for server_dict in servers_dict['servers']: | 977 | for server_dict in servers_dict['servers']: |
116 | 952 | server = etree.SubElement(servers, 'server') | 978 | server = etree.SubElement(servers, 'server') |
117 | 953 | self._populate_server(server, server_dict, False) | 979 | self._populate_server(server, server_dict, False) |
118 | 980 | |||
119 | 981 | self._populate_links(servers, servers_dict.get('servers_links', [])) | ||
120 | 954 | return self._to_xml(servers) | 982 | return self._to_xml(servers) |
121 | 955 | 983 | ||
122 | 956 | def detail(self, servers_dict): | 984 | def detail(self, servers_dict): |
123 | 957 | 985 | ||
124 | === modified file 'nova/api/openstack/views/servers.py' | |||
125 | --- nova/api/openstack/views/servers.py 2011-09-09 13:48:38 +0000 | |||
126 | +++ nova/api/openstack/views/servers.py 2011-09-20 18:47:17 +0000 | |||
127 | @@ -40,7 +40,7 @@ | |||
128 | 40 | def __init__(self, addresses_builder): | 40 | def __init__(self, addresses_builder): |
129 | 41 | self.addresses_builder = addresses_builder | 41 | self.addresses_builder = addresses_builder |
130 | 42 | 42 | ||
132 | 43 | def build(self, inst, is_detail): | 43 | def build(self, inst, is_detail=False): |
133 | 44 | """Return a dict that represenst a server.""" | 44 | """Return a dict that represenst a server.""" |
134 | 45 | if inst.get('_is_precooked', False): | 45 | if inst.get('_is_precooked', False): |
135 | 46 | server = dict(server=inst) | 46 | server = dict(server=inst) |
136 | @@ -54,6 +54,16 @@ | |||
137 | 54 | 54 | ||
138 | 55 | return server | 55 | return server |
139 | 56 | 56 | ||
140 | 57 | def build_list(self, server_objs, is_detail=False, **kwargs): | ||
141 | 58 | limit = kwargs.get('limit', None) | ||
142 | 59 | servers = [] | ||
143 | 60 | servers_links = [] | ||
144 | 61 | |||
145 | 62 | for server_obj in server_objs: | ||
146 | 63 | servers.append(self.build(server_obj, is_detail)['server']) | ||
147 | 64 | |||
148 | 65 | return dict(servers=servers) | ||
149 | 66 | |||
150 | 57 | def _build_simple(self, inst): | 67 | def _build_simple(self, inst): |
151 | 58 | """Return a simple model of a server.""" | 68 | """Return a simple model of a server.""" |
152 | 59 | return dict(server=dict(id=inst['id'], name=inst['display_name'])) | 69 | return dict(server=dict(id=inst['id'], name=inst['display_name'])) |
153 | @@ -205,6 +215,31 @@ | |||
154 | 205 | 215 | ||
155 | 206 | response["links"] = links | 216 | response["links"] = links |
156 | 207 | 217 | ||
157 | 218 | def build_list(self, server_objs, is_detail=False, **kwargs): | ||
158 | 219 | limit = kwargs.get('limit', None) | ||
159 | 220 | servers = [] | ||
160 | 221 | servers_links = [] | ||
161 | 222 | |||
162 | 223 | for server_obj in server_objs: | ||
163 | 224 | servers.append(self.build(server_obj, is_detail)['server']) | ||
164 | 225 | |||
165 | 226 | if (len(servers) and limit) and (limit == len(servers)): | ||
166 | 227 | next_link = self.generate_next_link(servers[-1]['id'], | ||
167 | 228 | kwargs, is_detail) | ||
168 | 229 | servers_links = [dict(rel='next', href=next_link)] | ||
169 | 230 | |||
170 | 231 | reval = dict(servers=servers) | ||
171 | 232 | if len(servers_links) > 0: | ||
172 | 233 | reval['servers_links'] = servers_links | ||
173 | 234 | return reval | ||
174 | 235 | |||
175 | 236 | def generate_next_link(self, server_id, params, is_detail=False): | ||
176 | 237 | """ Return an href string with proper limit and marker params""" | ||
177 | 238 | params['marker'] = server_id | ||
178 | 239 | return "%s?%s" % ( | ||
179 | 240 | os.path.join(self.base_url, self.project_id, "servers"), | ||
180 | 241 | common.dict_to_query_str(params)) | ||
181 | 242 | |||
182 | 208 | def generate_href(self, server_id): | 243 | def generate_href(self, server_id): |
183 | 209 | """Create an url that refers to a specific server id.""" | 244 | """Create an url that refers to a specific server id.""" |
184 | 210 | return os.path.join(self.base_url, self.project_id, | 245 | return os.path.join(self.base_url, self.project_id, |
185 | 211 | 246 | ||
186 | === modified file 'nova/tests/api/openstack/test_servers.py' | |||
187 | --- nova/tests/api/openstack/test_servers.py 2011-09-19 20:16:49 +0000 | |||
188 | +++ nova/tests/api/openstack/test_servers.py 2011-09-20 18:47:17 +0000 | |||
189 | @@ -20,6 +20,7 @@ | |||
190 | 20 | import datetime | 20 | import datetime |
191 | 21 | import json | 21 | import json |
192 | 22 | import unittest | 22 | import unittest |
193 | 23 | import urlparse | ||
194 | 23 | from lxml import etree | 24 | from lxml import etree |
195 | 24 | from xml.dom import minidom | 25 | from xml.dom import minidom |
196 | 25 | 26 | ||
197 | @@ -1152,6 +1153,67 @@ | |||
198 | 1152 | self.assertEqual(res.status_int, 400) | 1153 | self.assertEqual(res.status_int, 400) |
199 | 1153 | self.assertTrue('limit' in res.body) | 1154 | self.assertTrue('limit' in res.body) |
200 | 1154 | 1155 | ||
201 | 1156 | def test_get_servers_with_limit_v1_1(self): | ||
202 | 1157 | req = webob.Request.blank('/v1.1/fake/servers?limit=3') | ||
203 | 1158 | res = req.get_response(fakes.wsgi_app()) | ||
204 | 1159 | servers = json.loads(res.body)['servers'] | ||
205 | 1160 | servers_links = json.loads(res.body)['servers_links'] | ||
206 | 1161 | self.assertEqual([s['id'] for s in servers], [0, 1, 2]) | ||
207 | 1162 | self.assertEqual(servers_links[0]['rel'], 'next') | ||
208 | 1163 | |||
209 | 1164 | href_parts = urlparse.urlparse(servers_links[0]['href']) | ||
210 | 1165 | self.assertEqual('/v1.1/fake/servers', href_parts.path) | ||
211 | 1166 | params = urlparse.parse_qs(href_parts.query) | ||
212 | 1167 | self.assertDictMatch({'limit': ['3'], 'marker': ['2']}, params) | ||
213 | 1168 | |||
214 | 1169 | req = webob.Request.blank('/v1.1/fake/servers?limit=aaa') | ||
215 | 1170 | res = req.get_response(fakes.wsgi_app()) | ||
216 | 1171 | self.assertEqual(res.status_int, 400) | ||
217 | 1172 | self.assertTrue('limit' in res.body) | ||
218 | 1173 | |||
219 | 1174 | def test_get_server_details_with_limit_v1_1(self): | ||
220 | 1175 | req = webob.Request.blank('/v1.1/fake/servers/detail?limit=3') | ||
221 | 1176 | res = req.get_response(fakes.wsgi_app()) | ||
222 | 1177 | servers = json.loads(res.body)['servers'] | ||
223 | 1178 | servers_links = json.loads(res.body)['servers_links'] | ||
224 | 1179 | self.assertEqual([s['id'] for s in servers], [0, 1, 2]) | ||
225 | 1180 | self.assertEqual(servers_links[0]['rel'], 'next') | ||
226 | 1181 | |||
227 | 1182 | href_parts = urlparse.urlparse(servers_links[0]['href']) | ||
228 | 1183 | self.assertEqual('/v1.1/fake/servers', href_parts.path) | ||
229 | 1184 | params = urlparse.parse_qs(href_parts.query) | ||
230 | 1185 | self.assertDictMatch({'limit': ['3'], 'marker': ['2']}, params) | ||
231 | 1186 | |||
232 | 1187 | req = webob.Request.blank('/v1.1/fake/servers/detail?limit=aaa') | ||
233 | 1188 | res = req.get_response(fakes.wsgi_app()) | ||
234 | 1189 | self.assertEqual(res.status_int, 400) | ||
235 | 1190 | self.assertTrue('limit' in res.body) | ||
236 | 1191 | |||
237 | 1192 | def test_get_server_details_with_limit_and_other_params_v1_1(self): | ||
238 | 1193 | req = webob.Request.blank('/v1.1/fake/servers/detail?limit=3&blah=2:t') | ||
239 | 1194 | res = req.get_response(fakes.wsgi_app()) | ||
240 | 1195 | servers = json.loads(res.body)['servers'] | ||
241 | 1196 | servers_links = json.loads(res.body)['servers_links'] | ||
242 | 1197 | self.assertEqual([s['id'] for s in servers], [0, 1, 2]) | ||
243 | 1198 | self.assertEqual(servers_links[0]['rel'], 'next') | ||
244 | 1199 | |||
245 | 1200 | href_parts = urlparse.urlparse(servers_links[0]['href']) | ||
246 | 1201 | self.assertEqual('/v1.1/fake/servers', href_parts.path) | ||
247 | 1202 | params = urlparse.parse_qs(href_parts.query) | ||
248 | 1203 | self.assertDictMatch({'limit': ['3'], 'blah': ['2:t'], | ||
249 | 1204 | 'marker': ['2']}, params) | ||
250 | 1205 | |||
251 | 1206 | req = webob.Request.blank('/v1.1/fake/servers/detail?limit=aaa') | ||
252 | 1207 | res = req.get_response(fakes.wsgi_app()) | ||
253 | 1208 | self.assertEqual(res.status_int, 400) | ||
254 | 1209 | self.assertTrue('limit' in res.body) | ||
255 | 1210 | |||
256 | 1211 | def test_get_servers_with_too_big_limit_v1_1(self): | ||
257 | 1212 | req = webob.Request.blank('/v1.1/fake/servers?limit=30') | ||
258 | 1213 | res = req.get_response(fakes.wsgi_app()) | ||
259 | 1214 | res_dict = json.loads(res.body) | ||
260 | 1215 | self.assertTrue('servers_links' not in res_dict) | ||
261 | 1216 | |||
262 | 1155 | def test_get_servers_with_offset(self): | 1217 | def test_get_servers_with_offset(self): |
263 | 1156 | req = webob.Request.blank('/v1.0/servers?offset=2') | 1218 | req = webob.Request.blank('/v1.0/servers?offset=2') |
264 | 1157 | res = req.get_response(fakes.wsgi_app()) | 1219 | res = req.get_response(fakes.wsgi_app()) |
265 | @@ -1952,7 +2014,6 @@ | |||
266 | 1952 | req.headers["content-type"] = "application/json" | 2014 | req.headers["content-type"] = "application/json" |
267 | 1953 | 2015 | ||
268 | 1954 | res = req.get_response(fakes.wsgi_app()) | 2016 | res = req.get_response(fakes.wsgi_app()) |
269 | 1955 | print res | ||
270 | 1956 | self.assertEqual(res.status_int, 202) | 2017 | self.assertEqual(res.status_int, 202) |
271 | 1957 | server = json.loads(res.body)['server'] | 2018 | server = json.loads(res.body)['server'] |
272 | 1958 | self.assertEqual(1, server['id']) | 2019 | self.assertEqual(1, server['id']) |
273 | @@ -2480,6 +2541,7 @@ | |||
274 | 2480 | } | 2541 | } |
275 | 2481 | req = webob.Request.blank('/v1.1/fake/servers/detail') | 2542 | req = webob.Request.blank('/v1.1/fake/servers/detail') |
276 | 2482 | res = req.get_response(fakes.wsgi_app()) | 2543 | res = req.get_response(fakes.wsgi_app()) |
277 | 2544 | print res.body | ||
278 | 2483 | res_dict = json.loads(res.body) | 2545 | res_dict = json.loads(res.body) |
279 | 2484 | 2546 | ||
280 | 2485 | for i, s in enumerate(res_dict['servers']): | 2547 | for i, s in enumerate(res_dict['servers']): |
281 | @@ -4181,6 +4243,7 @@ | |||
282 | 4181 | 4243 | ||
283 | 4182 | TIMESTAMP = "2010-10-11T10:30:22Z" | 4244 | TIMESTAMP = "2010-10-11T10:30:22Z" |
284 | 4183 | SERVER_HREF = 'http://localhost/v1.1/servers/123' | 4245 | SERVER_HREF = 'http://localhost/v1.1/servers/123' |
285 | 4246 | SERVER_NEXT = 'http://localhost/v1.1/servers?limit=%s&marker=%s' | ||
286 | 4184 | SERVER_BOOKMARK = 'http://localhost/servers/123' | 4247 | SERVER_BOOKMARK = 'http://localhost/servers/123' |
287 | 4185 | IMAGE_BOOKMARK = 'http://localhost/images/5' | 4248 | IMAGE_BOOKMARK = 'http://localhost/images/5' |
288 | 4186 | FLAVOR_BOOKMARK = 'http://localhost/flavors/1' | 4249 | FLAVOR_BOOKMARK = 'http://localhost/flavors/1' |
289 | @@ -4599,6 +4662,74 @@ | |||
290 | 4599 | for key, value in link.items(): | 4662 | for key, value in link.items(): |
291 | 4600 | self.assertEqual(link_nodes[i].get(key), value) | 4663 | self.assertEqual(link_nodes[i].get(key), value) |
292 | 4601 | 4664 | ||
293 | 4665 | def test_index_with_servers_links(self): | ||
294 | 4666 | serializer = servers.ServerXMLSerializer() | ||
295 | 4667 | |||
296 | 4668 | expected_server_href = 'http://localhost/v1.1/servers/1' | ||
297 | 4669 | expected_server_next = self.SERVER_NEXT % (2, 2) | ||
298 | 4670 | expected_server_bookmark = 'http://localhost/servers/1' | ||
299 | 4671 | expected_server_href_2 = 'http://localhost/v1.1/servers/2' | ||
300 | 4672 | expected_server_bookmark_2 = 'http://localhost/servers/2' | ||
301 | 4673 | fixture = {"servers": [ | ||
302 | 4674 | { | ||
303 | 4675 | "id": 1, | ||
304 | 4676 | "name": "test_server", | ||
305 | 4677 | 'links': [ | ||
306 | 4678 | { | ||
307 | 4679 | 'href': expected_server_href, | ||
308 | 4680 | 'rel': 'self', | ||
309 | 4681 | }, | ||
310 | 4682 | { | ||
311 | 4683 | 'href': expected_server_bookmark, | ||
312 | 4684 | 'rel': 'bookmark', | ||
313 | 4685 | }, | ||
314 | 4686 | ], | ||
315 | 4687 | }, | ||
316 | 4688 | { | ||
317 | 4689 | "id": 2, | ||
318 | 4690 | "name": "test_server_2", | ||
319 | 4691 | 'links': [ | ||
320 | 4692 | { | ||
321 | 4693 | 'href': expected_server_href_2, | ||
322 | 4694 | 'rel': 'self', | ||
323 | 4695 | }, | ||
324 | 4696 | { | ||
325 | 4697 | 'href': expected_server_bookmark_2, | ||
326 | 4698 | 'rel': 'bookmark', | ||
327 | 4699 | }, | ||
328 | 4700 | ], | ||
329 | 4701 | }, | ||
330 | 4702 | ], | ||
331 | 4703 | "servers_links": [ | ||
332 | 4704 | { | ||
333 | 4705 | 'rel': 'next', | ||
334 | 4706 | 'href': expected_server_next, | ||
335 | 4707 | }, | ||
336 | 4708 | ]} | ||
337 | 4709 | |||
338 | 4710 | output = serializer.serialize(fixture, 'index') | ||
339 | 4711 | print output | ||
340 | 4712 | root = etree.XML(output) | ||
341 | 4713 | xmlutil.validate_schema(root, 'servers_index') | ||
342 | 4714 | server_elems = root.findall('{0}server'.format(NS)) | ||
343 | 4715 | self.assertEqual(len(server_elems), 2) | ||
344 | 4716 | for i, server_elem in enumerate(server_elems): | ||
345 | 4717 | server_dict = fixture['servers'][i] | ||
346 | 4718 | for key in ['name', 'id']: | ||
347 | 4719 | self.assertEqual(server_elem.get(key), str(server_dict[key])) | ||
348 | 4720 | |||
349 | 4721 | link_nodes = server_elem.findall('{0}link'.format(ATOMNS)) | ||
350 | 4722 | self.assertEqual(len(link_nodes), 2) | ||
351 | 4723 | for i, link in enumerate(server_dict['links']): | ||
352 | 4724 | for key, value in link.items(): | ||
353 | 4725 | self.assertEqual(link_nodes[i].get(key), value) | ||
354 | 4726 | |||
355 | 4727 | # Check servers_links | ||
356 | 4728 | servers_links = root.findall('{0}link'.format(ATOMNS)) | ||
357 | 4729 | for i, link in enumerate(fixture['servers_links']): | ||
358 | 4730 | for key, value in link.items(): | ||
359 | 4731 | self.assertEqual(servers_links[i].get(key), value) | ||
360 | 4732 | |||
361 | 4602 | def test_detail(self): | 4733 | def test_detail(self): |
362 | 4603 | serializer = servers.ServerXMLSerializer() | 4734 | serializer = servers.ServerXMLSerializer() |
363 | 4604 | 4735 |
When making a request with a changes-since query param such as this:
curl -i -H "X-Auth-Token: admin:admin" 'http:// localhost: 8774/v1. 1/admin/ servers? limit=2& changes- since=2011- 09-01T00: 00:00Z'
The values of changes-since and limit are respected, but the servers_links value returned is unusable:
http:// localhost: 8774/v1. 1?marker= 6&limit= 2&changes- since=2011- 09-01T00% 3A00%3A00Z
I would expect the changes-since value not to be url-encoded at all.