Merge lp:~rackspace-titan/nova/osapi-pass-update-lp744567 into lp:~hudson-openstack/nova/trunk
- osapi-pass-update-lp744567
- Merge into trunk
Status: | Merged |
---|---|
Approved by: | Monty Taylor |
Approved revision: | 908 |
Merged at revision: | 920 |
Proposed branch: | lp:~rackspace-titan/nova/osapi-pass-update-lp744567 |
Merge into: | lp:~hudson-openstack/nova/trunk |
Diff against target: |
281 lines (+183/-42) 2 files modified
nova/api/openstack/servers.py (+39/-9) nova/tests/api/openstack/test_servers.py (+144/-33) |
To merge this branch: | bzr merge lp:~rackspace-titan/nova/osapi-pass-update-lp744567 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Sandy Walsh (community) | Approve | ||
Jay Pipes (community) | Approve | ||
Brian Lamar (community) | Approve | ||
Review via email: mp+55239@code.launchpad.net |
Commit message
Description of the change
Moving server update adminPass support to be v1.0-specific
OS API servers update tests actually assert and pass now
Enforcing server name being a string of length > 0
Brian Waldon (bcwaldon) wrote : | # |
> Is the "isinstance(name, basestring)" needed or can you just cast the name to
> a string? When would it not be a string?
I typically operate under the assumption that the users of the api will make mistakes. Would it make sense to cast a dict as the server name? It would be easily correctable, but should we prevent it from happening in the first place? The v1.1 spec doesn't seem to offer any help.
Jay Pipes (jaypipes) wrote : | # |
Hi!
19 + if not isinstance(name, basestring) or name == '':
20 + return exc.HTTPBadRequ
Not going to weigh in on Brian Lamar's question, but I would say that if you stick with raising an HTTPBadRequest in this way, that you should, at a minimum, provide an error message. I'd hate to receive a standard 400 Bad Request and have no idea why ;)
something like this would work:
if not isinstance(name, basestring):
msg = _("Name supplied for server (%r) is not a string or unicode") % name
return exc.HTTPBadRequ
elif name.strip() == '':
msg = _("Null string supplied as server name")
return exc.HTTPBadRequ
Other than that nit, looks good, and good work adding tests.
Cheers!
jay
Jay Pipes (jaypipes) wrote : | # |
ooh, please add a test case for the nullstring and non-string args. sorry, meant to mention that above...
Brian Waldon (bcwaldon) wrote : | # |
Jay,
I incorporated your suggestions into my code. However, this branch now exceeds the bug it is aimed at fixing. I started copying my server name validation to create, too, but I thought I should get your opinion on how to accomplish that. Is it okay to do that within this branch?
Jay Pipes (jaypipes) wrote : | # |
In this case, you are tightening existing code with better error checking. It's always a bit of a choice, but in this case, the additional code blocks doing checks are fine, the patch size is still very manageable, and you are fixing things that came up during the review process (not randomly changing code in other modules that don't have anything to do with your bug or something like that...).
And more/better test cases are always appreciated, regardless.
So, yes, this is perfectly fine with me.
Jay Pipes (jaypipes) wrote : | # |
Brian, if you'd to put the same validation in the create method (and possibly have a _validate_name() method or similar, my opinion is to do so in this patch. The additional code won't be more than a few lines of code, and the patch size is minimal.
Brian Waldon (bcwaldon) wrote : | # |
> Brian, if you'd to put the same validation in the create method (and possibly
> have a _validate_name() method or similar, my opinion is to do so in this
> patch. The additional code won't be more than a few lines of code, and the
> patch size is minimal.
Great. I'll get this done right now.
Brian Waldon (bcwaldon) wrote : | # |
Jay,
I would appreciate it if you could look over the last couple of revisions. It's a pretty straightforward patch.
Brian Lamar (blamar) : | # |
Sandy Walsh (sandy-walsh) wrote : | # |
Seems reasonable to me :)
Tests are happy.
Preview Diff
1 | === modified file 'nova/api/openstack/servers.py' | |||
2 | --- nova/api/openstack/servers.py 2011-03-25 18:36:54 +0000 | |||
3 | +++ nova/api/openstack/servers.py 2011-03-29 15:19:38 +0000 | |||
4 | @@ -150,6 +150,15 @@ | |||
5 | 150 | injected_files = self._get_injected_files(personality) | 150 | injected_files = self._get_injected_files(personality) |
6 | 151 | 151 | ||
7 | 152 | flavor_id = self._flavor_id_from_req_data(env) | 152 | flavor_id = self._flavor_id_from_req_data(env) |
8 | 153 | |||
9 | 154 | if not 'name' in env['server']: | ||
10 | 155 | msg = _("Server name is not defined") | ||
11 | 156 | return exc.HTTPBadRequest(msg) | ||
12 | 157 | |||
13 | 158 | name = env['server']['name'] | ||
14 | 159 | self._validate_server_name(name) | ||
15 | 160 | name = name.strip() | ||
16 | 161 | |||
17 | 153 | try: | 162 | try: |
18 | 154 | (inst,) = self.compute_api.create( | 163 | (inst,) = self.compute_api.create( |
19 | 155 | context, | 164 | context, |
20 | @@ -157,8 +166,8 @@ | |||
21 | 157 | image_id, | 166 | image_id, |
22 | 158 | kernel_id=kernel_id, | 167 | kernel_id=kernel_id, |
23 | 159 | ramdisk_id=ramdisk_id, | 168 | ramdisk_id=ramdisk_id, |
26 | 160 | display_name=env['server']['name'], | 169 | display_name=name, |
27 | 161 | display_description=env['server']['name'], | 170 | display_description=name, |
28 | 162 | key_name=key_name, | 171 | key_name=key_name, |
29 | 163 | key_data=key_data, | 172 | key_data=key_data, |
30 | 164 | metadata=metadata, | 173 | metadata=metadata, |
31 | @@ -246,20 +255,33 @@ | |||
32 | 246 | 255 | ||
33 | 247 | ctxt = req.environ['nova.context'] | 256 | ctxt = req.environ['nova.context'] |
34 | 248 | update_dict = {} | 257 | update_dict = {} |
41 | 249 | if 'adminPass' in inst_dict['server']: | 258 | |
36 | 250 | update_dict['admin_pass'] = inst_dict['server']['adminPass'] | ||
37 | 251 | try: | ||
38 | 252 | self.compute_api.set_admin_password(ctxt, id) | ||
39 | 253 | except exception.TimeoutException: | ||
40 | 254 | return exc.HTTPRequestTimeout() | ||
42 | 255 | if 'name' in inst_dict['server']: | 259 | if 'name' in inst_dict['server']: |
44 | 256 | update_dict['display_name'] = inst_dict['server']['name'] | 260 | name = inst_dict['server']['name'] |
45 | 261 | self._validate_server_name(name) | ||
46 | 262 | update_dict['display_name'] = name.strip() | ||
47 | 263 | |||
48 | 264 | self._parse_update(ctxt, id, inst_dict, update_dict) | ||
49 | 265 | |||
50 | 257 | try: | 266 | try: |
51 | 258 | self.compute_api.update(ctxt, id, **update_dict) | 267 | self.compute_api.update(ctxt, id, **update_dict) |
52 | 259 | except exception.NotFound: | 268 | except exception.NotFound: |
53 | 260 | return faults.Fault(exc.HTTPNotFound()) | 269 | return faults.Fault(exc.HTTPNotFound()) |
54 | 270 | |||
55 | 261 | return exc.HTTPNoContent() | 271 | return exc.HTTPNoContent() |
56 | 262 | 272 | ||
57 | 273 | def _validate_server_name(self, value): | ||
58 | 274 | if not isinstance(value, basestring): | ||
59 | 275 | msg = _("Server name is not a string or unicode") | ||
60 | 276 | raise exc.HTTPBadRequest(msg) | ||
61 | 277 | |||
62 | 278 | if value.strip() == '': | ||
63 | 279 | msg = _("Server name is an empty string") | ||
64 | 280 | raise exc.HTTPBadRequest(msg) | ||
65 | 281 | |||
66 | 282 | def _parse_update(self, context, id, inst_dict, update_dict): | ||
67 | 283 | pass | ||
68 | 284 | |||
69 | 263 | @scheduler_api.redirect_handler | 285 | @scheduler_api.redirect_handler |
70 | 264 | def action(self, req, id): | 286 | def action(self, req, id): |
71 | 265 | """Multi-purpose method used to reboot, rebuild, or | 287 | """Multi-purpose method used to reboot, rebuild, or |
72 | @@ -566,6 +588,14 @@ | |||
73 | 566 | def _limit_items(self, items, req): | 588 | def _limit_items(self, items, req): |
74 | 567 | return common.limited(items, req) | 589 | return common.limited(items, req) |
75 | 568 | 590 | ||
76 | 591 | def _parse_update(self, context, server_id, inst_dict, update_dict): | ||
77 | 592 | if 'adminPass' in inst_dict['server']: | ||
78 | 593 | update_dict['admin_pass'] = inst_dict['server']['adminPass'] | ||
79 | 594 | try: | ||
80 | 595 | self.compute_api.set_admin_password(context, server_id) | ||
81 | 596 | except exception.TimeoutException: | ||
82 | 597 | return exc.HTTPRequestTimeout() | ||
83 | 598 | |||
84 | 569 | 599 | ||
85 | 570 | class ControllerV11(Controller): | 600 | class ControllerV11(Controller): |
86 | 571 | def _image_id_from_req_data(self, data): | 601 | def _image_id_from_req_data(self, data): |
87 | 572 | 602 | ||
88 | === modified file 'nova/tests/api/openstack/test_servers.py' | |||
89 | --- nova/tests/api/openstack/test_servers.py 2011-03-28 18:31:12 +0000 | |||
90 | +++ nova/tests/api/openstack/test_servers.py 2011-03-29 15:19:38 +0000 | |||
91 | @@ -392,6 +392,74 @@ | |||
92 | 392 | fakes.stub_out_key_pair_funcs(self.stubs, have_key_pair=False) | 392 | fakes.stub_out_key_pair_funcs(self.stubs, have_key_pair=False) |
93 | 393 | self._test_create_instance_helper() | 393 | self._test_create_instance_helper() |
94 | 394 | 394 | ||
95 | 395 | def test_create_instance_no_name(self): | ||
96 | 396 | self._setup_for_create_instance() | ||
97 | 397 | |||
98 | 398 | body = { | ||
99 | 399 | 'server': { | ||
100 | 400 | 'imageId': 3, | ||
101 | 401 | 'flavorId': 1, | ||
102 | 402 | 'metadata': { | ||
103 | 403 | 'hello': 'world', | ||
104 | 404 | 'open': 'stack', | ||
105 | 405 | }, | ||
106 | 406 | 'personality': {}, | ||
107 | 407 | }, | ||
108 | 408 | } | ||
109 | 409 | |||
110 | 410 | req = webob.Request.blank('/v1.0/servers') | ||
111 | 411 | req.method = 'POST' | ||
112 | 412 | req.body = json.dumps(body) | ||
113 | 413 | req.headers["content-type"] = "application/json" | ||
114 | 414 | res = req.get_response(fakes.wsgi_app()) | ||
115 | 415 | self.assertEqual(res.status_int, 400) | ||
116 | 416 | |||
117 | 417 | def test_create_instance_nonstring_name(self): | ||
118 | 418 | self._setup_for_create_instance() | ||
119 | 419 | |||
120 | 420 | body = { | ||
121 | 421 | 'server': { | ||
122 | 422 | 'name': 12, | ||
123 | 423 | 'imageId': 3, | ||
124 | 424 | 'flavorId': 1, | ||
125 | 425 | 'metadata': { | ||
126 | 426 | 'hello': 'world', | ||
127 | 427 | 'open': 'stack', | ||
128 | 428 | }, | ||
129 | 429 | 'personality': {}, | ||
130 | 430 | }, | ||
131 | 431 | } | ||
132 | 432 | |||
133 | 433 | req = webob.Request.blank('/v1.0/servers') | ||
134 | 434 | req.method = 'POST' | ||
135 | 435 | req.body = json.dumps(body) | ||
136 | 436 | req.headers["content-type"] = "application/json" | ||
137 | 437 | res = req.get_response(fakes.wsgi_app()) | ||
138 | 438 | self.assertEqual(res.status_int, 400) | ||
139 | 439 | |||
140 | 440 | def test_create_instance_whitespace_name(self): | ||
141 | 441 | self._setup_for_create_instance() | ||
142 | 442 | |||
143 | 443 | body = { | ||
144 | 444 | 'server': { | ||
145 | 445 | 'name': ' ', | ||
146 | 446 | 'imageId': 3, | ||
147 | 447 | 'flavorId': 1, | ||
148 | 448 | 'metadata': { | ||
149 | 449 | 'hello': 'world', | ||
150 | 450 | 'open': 'stack', | ||
151 | 451 | }, | ||
152 | 452 | 'personality': {}, | ||
153 | 453 | }, | ||
154 | 454 | } | ||
155 | 455 | |||
156 | 456 | req = webob.Request.blank('/v1.0/servers') | ||
157 | 457 | req.method = 'POST' | ||
158 | 458 | req.body = json.dumps(body) | ||
159 | 459 | req.headers["content-type"] = "application/json" | ||
160 | 460 | res = req.get_response(fakes.wsgi_app()) | ||
161 | 461 | self.assertEqual(res.status_int, 400) | ||
162 | 462 | |||
163 | 395 | def test_create_instance_v11(self): | 463 | def test_create_instance_v11(self): |
164 | 396 | self._setup_for_create_instance() | 464 | self._setup_for_create_instance() |
165 | 397 | 465 | ||
166 | @@ -448,39 +516,82 @@ | |||
167 | 448 | res = req.get_response(fakes.wsgi_app()) | 516 | res = req.get_response(fakes.wsgi_app()) |
168 | 449 | self.assertEqual(res.status_int, 422) | 517 | self.assertEqual(res.status_int, 422) |
169 | 450 | 518 | ||
203 | 451 | def test_update_bad_params(self): | 519 | def test_update_nonstring_name(self): |
204 | 452 | """ Confirm that update is filtering params """ | 520 | """ Confirm that update is filtering params """ |
205 | 453 | inst_dict = dict(cat='leopard', name='server_test', adminPass='bacon') | 521 | inst_dict = dict(name=12, adminPass='bacon') |
206 | 454 | self.body = json.dumps(dict(server=inst_dict)) | 522 | self.body = json.dumps(dict(server=inst_dict)) |
207 | 455 | 523 | ||
208 | 456 | def server_update(context, id, params): | 524 | req = webob.Request.blank('/v1.0/servers/1') |
209 | 457 | self.update_called = True | 525 | req.method = 'PUT' |
210 | 458 | filtered_dict = dict(name='server_test', admin_pass='bacon') | 526 | req.content_type = "application/json" |
211 | 459 | self.assertEqual(params, filtered_dict) | 527 | req.body = self.body |
212 | 460 | 528 | res = req.get_response(fakes.wsgi_app()) | |
213 | 461 | self.stubs.Set(nova.db.api, 'instance_update', | 529 | self.assertEqual(res.status_int, 400) |
214 | 462 | server_update) | 530 | |
215 | 463 | 531 | def test_update_whitespace_name(self): | |
216 | 464 | req = webob.Request.blank('/v1.0/servers/1') | 532 | """ Confirm that update is filtering params """ |
217 | 465 | req.method = 'PUT' | 533 | inst_dict = dict(name=' ', adminPass='bacon') |
218 | 466 | req.body = self.body | 534 | self.body = json.dumps(dict(server=inst_dict)) |
219 | 467 | req.get_response(fakes.wsgi_app()) | 535 | |
220 | 468 | 536 | req = webob.Request.blank('/v1.0/servers/1') | |
221 | 469 | def test_update_server(self): | 537 | req.method = 'PUT' |
222 | 470 | inst_dict = dict(name='server_test', adminPass='bacon') | 538 | req.content_type = "application/json" |
223 | 471 | self.body = json.dumps(dict(server=inst_dict)) | 539 | req.body = self.body |
224 | 472 | 540 | res = req.get_response(fakes.wsgi_app()) | |
225 | 473 | def server_update(context, id, params): | 541 | self.assertEqual(res.status_int, 400) |
226 | 474 | filtered_dict = dict(name='server_test', admin_pass='bacon') | 542 | |
227 | 475 | self.assertEqual(params, filtered_dict) | 543 | def test_update_null_name(self): |
228 | 476 | 544 | """ Confirm that update is filtering params """ | |
229 | 477 | self.stubs.Set(nova.db.api, 'instance_update', | 545 | inst_dict = dict(name='', adminPass='bacon') |
230 | 478 | server_update) | 546 | self.body = json.dumps(dict(server=inst_dict)) |
231 | 479 | 547 | ||
232 | 480 | req = webob.Request.blank('/v1.0/servers/1') | 548 | req = webob.Request.blank('/v1.0/servers/1') |
233 | 481 | req.method = 'PUT' | 549 | req.method = 'PUT' |
234 | 482 | req.body = self.body | 550 | req.content_type = "application/json" |
235 | 483 | req.get_response(fakes.wsgi_app()) | 551 | req.body = self.body |
236 | 552 | res = req.get_response(fakes.wsgi_app()) | ||
237 | 553 | self.assertEqual(res.status_int, 400) | ||
238 | 554 | |||
239 | 555 | def test_update_server_v10(self): | ||
240 | 556 | inst_dict = dict(name='server_test', adminPass='bacon') | ||
241 | 557 | self.body = json.dumps(dict(server=inst_dict)) | ||
242 | 558 | |||
243 | 559 | def server_update(context, id, params): | ||
244 | 560 | filtered_dict = dict( | ||
245 | 561 | display_name='server_test', | ||
246 | 562 | admin_pass='bacon', | ||
247 | 563 | ) | ||
248 | 564 | self.assertEqual(params, filtered_dict) | ||
249 | 565 | return filtered_dict | ||
250 | 566 | |||
251 | 567 | self.stubs.Set(nova.db.api, 'instance_update', | ||
252 | 568 | server_update) | ||
253 | 569 | |||
254 | 570 | req = webob.Request.blank('/v1.0/servers/1') | ||
255 | 571 | req.method = 'PUT' | ||
256 | 572 | req.content_type = "application/json" | ||
257 | 573 | req.body = self.body | ||
258 | 574 | res = req.get_response(fakes.wsgi_app()) | ||
259 | 575 | self.assertEqual(res.status_int, 204) | ||
260 | 576 | |||
261 | 577 | def test_update_server_adminPass_ignored_v11(self): | ||
262 | 578 | inst_dict = dict(name='server_test', adminPass='bacon') | ||
263 | 579 | self.body = json.dumps(dict(server=inst_dict)) | ||
264 | 580 | |||
265 | 581 | def server_update(context, id, params): | ||
266 | 582 | filtered_dict = dict(display_name='server_test') | ||
267 | 583 | self.assertEqual(params, filtered_dict) | ||
268 | 584 | return filtered_dict | ||
269 | 585 | |||
270 | 586 | self.stubs.Set(nova.db.api, 'instance_update', | ||
271 | 587 | server_update) | ||
272 | 588 | |||
273 | 589 | req = webob.Request.blank('/v1.1/servers/1') | ||
274 | 590 | req.method = 'PUT' | ||
275 | 591 | req.content_type = "application/json" | ||
276 | 592 | req.body = self.body | ||
277 | 593 | res = req.get_response(fakes.wsgi_app()) | ||
278 | 594 | self.assertEqual(res.status_int, 204) | ||
279 | 484 | 595 | ||
280 | 485 | def test_create_backup_schedules(self): | 596 | def test_create_backup_schedules(self): |
281 | 486 | req = webob.Request.blank('/v1.0/servers/1/backup_schedule') | 597 | req = webob.Request.blank('/v1.0/servers/1/backup_schedule') |
Is the "isinstance(name, basestring)" needed or can you just cast the name to a string? When would it not be a string?