Merge lp:~rackspace-titan/nova/osapi-pass-update-lp744567 into lp:~hudson-openstack/nova/trunk

Proposed by Brian Waldon
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
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

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

To post a comment you must log in.
Revision history for this message
Brian Lamar (blamar) wrote :

Is the "isinstance(name, basestring)" needed or can you just cast the name to a string? When would it not be a string?

review: Needs Information
Revision history for this message
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.

Revision history for this message
Jay Pipes (jaypipes) wrote :

Hi!

19 + if not isinstance(name, basestring) or name == '':
20 + return exc.HTTPBadRequest()

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.HTTPBadRequest(msg)
elif name.strip() == '':
    msg = _("Null string supplied as server name")
    return exc.HTTPBadRequest(msg)

Other than that nit, looks good, and good work adding tests.

Cheers!
jay

review: Needs Fixing
Revision history for this message
Jay Pipes (jaypipes) wrote :

ooh, please add a test case for the nullstring and non-string args. sorry, meant to mention that above...

review: Needs Fixing
Revision history for this message
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?

Revision history for this message
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.

review: Approve
Revision history for this message
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.

Revision history for this message
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.

Revision history for this message
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.

Revision history for this message
Brian Lamar (blamar) :
review: Approve
Revision history for this message
Jay Pipes (jaypipes) wrote :

Nice work, Brian.

review: Approve
Revision history for this message
Sandy Walsh (sandy-walsh) wrote :

Seems reasonable to me :)

Tests are happy.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'nova/api/openstack/servers.py'
--- nova/api/openstack/servers.py 2011-03-25 18:36:54 +0000
+++ nova/api/openstack/servers.py 2011-03-29 15:19:38 +0000
@@ -150,6 +150,15 @@
150 injected_files = self._get_injected_files(personality)150 injected_files = self._get_injected_files(personality)
151151
152 flavor_id = self._flavor_id_from_req_data(env)152 flavor_id = self._flavor_id_from_req_data(env)
153
154 if not 'name' in env['server']:
155 msg = _("Server name is not defined")
156 return exc.HTTPBadRequest(msg)
157
158 name = env['server']['name']
159 self._validate_server_name(name)
160 name = name.strip()
161
153 try:162 try:
154 (inst,) = self.compute_api.create(163 (inst,) = self.compute_api.create(
155 context,164 context,
@@ -157,8 +166,8 @@
157 image_id,166 image_id,
158 kernel_id=kernel_id,167 kernel_id=kernel_id,
159 ramdisk_id=ramdisk_id,168 ramdisk_id=ramdisk_id,
160 display_name=env['server']['name'],169 display_name=name,
161 display_description=env['server']['name'],170 display_description=name,
162 key_name=key_name,171 key_name=key_name,
163 key_data=key_data,172 key_data=key_data,
164 metadata=metadata,173 metadata=metadata,
@@ -246,20 +255,33 @@
246255
247 ctxt = req.environ['nova.context']256 ctxt = req.environ['nova.context']
248 update_dict = {}257 update_dict = {}
249 if 'adminPass' in inst_dict['server']:258
250 update_dict['admin_pass'] = inst_dict['server']['adminPass']
251 try:
252 self.compute_api.set_admin_password(ctxt, id)
253 except exception.TimeoutException:
254 return exc.HTTPRequestTimeout()
255 if 'name' in inst_dict['server']:259 if 'name' in inst_dict['server']:
256 update_dict['display_name'] = inst_dict['server']['name']260 name = inst_dict['server']['name']
261 self._validate_server_name(name)
262 update_dict['display_name'] = name.strip()
263
264 self._parse_update(ctxt, id, inst_dict, update_dict)
265
257 try:266 try:
258 self.compute_api.update(ctxt, id, **update_dict)267 self.compute_api.update(ctxt, id, **update_dict)
259 except exception.NotFound:268 except exception.NotFound:
260 return faults.Fault(exc.HTTPNotFound())269 return faults.Fault(exc.HTTPNotFound())
270
261 return exc.HTTPNoContent()271 return exc.HTTPNoContent()
262272
273 def _validate_server_name(self, value):
274 if not isinstance(value, basestring):
275 msg = _("Server name is not a string or unicode")
276 raise exc.HTTPBadRequest(msg)
277
278 if value.strip() == '':
279 msg = _("Server name is an empty string")
280 raise exc.HTTPBadRequest(msg)
281
282 def _parse_update(self, context, id, inst_dict, update_dict):
283 pass
284
263 @scheduler_api.redirect_handler285 @scheduler_api.redirect_handler
264 def action(self, req, id):286 def action(self, req, id):
265 """Multi-purpose method used to reboot, rebuild, or287 """Multi-purpose method used to reboot, rebuild, or
@@ -566,6 +588,14 @@
566 def _limit_items(self, items, req):588 def _limit_items(self, items, req):
567 return common.limited(items, req)589 return common.limited(items, req)
568590
591 def _parse_update(self, context, server_id, inst_dict, update_dict):
592 if 'adminPass' in inst_dict['server']:
593 update_dict['admin_pass'] = inst_dict['server']['adminPass']
594 try:
595 self.compute_api.set_admin_password(context, server_id)
596 except exception.TimeoutException:
597 return exc.HTTPRequestTimeout()
598
569599
570class ControllerV11(Controller):600class ControllerV11(Controller):
571 def _image_id_from_req_data(self, data):601 def _image_id_from_req_data(self, data):
572602
=== modified file 'nova/tests/api/openstack/test_servers.py'
--- nova/tests/api/openstack/test_servers.py 2011-03-28 18:31:12 +0000
+++ nova/tests/api/openstack/test_servers.py 2011-03-29 15:19:38 +0000
@@ -392,6 +392,74 @@
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)
393 self._test_create_instance_helper()393 self._test_create_instance_helper()
394394
395 def test_create_instance_no_name(self):
396 self._setup_for_create_instance()
397
398 body = {
399 'server': {
400 'imageId': 3,
401 'flavorId': 1,
402 'metadata': {
403 'hello': 'world',
404 'open': 'stack',
405 },
406 'personality': {},
407 },
408 }
409
410 req = webob.Request.blank('/v1.0/servers')
411 req.method = 'POST'
412 req.body = json.dumps(body)
413 req.headers["content-type"] = "application/json"
414 res = req.get_response(fakes.wsgi_app())
415 self.assertEqual(res.status_int, 400)
416
417 def test_create_instance_nonstring_name(self):
418 self._setup_for_create_instance()
419
420 body = {
421 'server': {
422 'name': 12,
423 'imageId': 3,
424 'flavorId': 1,
425 'metadata': {
426 'hello': 'world',
427 'open': 'stack',
428 },
429 'personality': {},
430 },
431 }
432
433 req = webob.Request.blank('/v1.0/servers')
434 req.method = 'POST'
435 req.body = json.dumps(body)
436 req.headers["content-type"] = "application/json"
437 res = req.get_response(fakes.wsgi_app())
438 self.assertEqual(res.status_int, 400)
439
440 def test_create_instance_whitespace_name(self):
441 self._setup_for_create_instance()
442
443 body = {
444 'server': {
445 'name': ' ',
446 'imageId': 3,
447 'flavorId': 1,
448 'metadata': {
449 'hello': 'world',
450 'open': 'stack',
451 },
452 'personality': {},
453 },
454 }
455
456 req = webob.Request.blank('/v1.0/servers')
457 req.method = 'POST'
458 req.body = json.dumps(body)
459 req.headers["content-type"] = "application/json"
460 res = req.get_response(fakes.wsgi_app())
461 self.assertEqual(res.status_int, 400)
462
395 def test_create_instance_v11(self):463 def test_create_instance_v11(self):
396 self._setup_for_create_instance()464 self._setup_for_create_instance()
397465
@@ -448,39 +516,82 @@
448 res = req.get_response(fakes.wsgi_app())516 res = req.get_response(fakes.wsgi_app())
449 self.assertEqual(res.status_int, 422)517 self.assertEqual(res.status_int, 422)
450518
451 def test_update_bad_params(self):519 def test_update_nonstring_name(self):
452 """ Confirm that update is filtering params """520 """ Confirm that update is filtering params """
453 inst_dict = dict(cat='leopard', name='server_test', adminPass='bacon')521 inst_dict = dict(name=12, adminPass='bacon')
454 self.body = json.dumps(dict(server=inst_dict))522 self.body = json.dumps(dict(server=inst_dict))
455523
456 def server_update(context, id, params):524 req = webob.Request.blank('/v1.0/servers/1')
457 self.update_called = True525 req.method = 'PUT'
458 filtered_dict = dict(name='server_test', admin_pass='bacon')526 req.content_type = "application/json"
459 self.assertEqual(params, filtered_dict)527 req.body = self.body
460528 res = req.get_response(fakes.wsgi_app())
461 self.stubs.Set(nova.db.api, 'instance_update',529 self.assertEqual(res.status_int, 400)
462 server_update)530
463531 def test_update_whitespace_name(self):
464 req = webob.Request.blank('/v1.0/servers/1')532 """ Confirm that update is filtering params """
465 req.method = 'PUT'533 inst_dict = dict(name=' ', adminPass='bacon')
466 req.body = self.body534 self.body = json.dumps(dict(server=inst_dict))
467 req.get_response(fakes.wsgi_app())535
468536 req = webob.Request.blank('/v1.0/servers/1')
469 def test_update_server(self):537 req.method = 'PUT'
470 inst_dict = dict(name='server_test', adminPass='bacon')538 req.content_type = "application/json"
471 self.body = json.dumps(dict(server=inst_dict))539 req.body = self.body
472540 res = req.get_response(fakes.wsgi_app())
473 def server_update(context, id, params):541 self.assertEqual(res.status_int, 400)
474 filtered_dict = dict(name='server_test', admin_pass='bacon')542
475 self.assertEqual(params, filtered_dict)543 def test_update_null_name(self):
476544 """ Confirm that update is filtering params """
477 self.stubs.Set(nova.db.api, 'instance_update',545 inst_dict = dict(name='', adminPass='bacon')
478 server_update)546 self.body = json.dumps(dict(server=inst_dict))
479547
480 req = webob.Request.blank('/v1.0/servers/1')548 req = webob.Request.blank('/v1.0/servers/1')
481 req.method = 'PUT'549 req.method = 'PUT'
482 req.body = self.body550 req.content_type = "application/json"
483 req.get_response(fakes.wsgi_app())551 req.body = self.body
552 res = req.get_response(fakes.wsgi_app())
553 self.assertEqual(res.status_int, 400)
554
555 def test_update_server_v10(self):
556 inst_dict = dict(name='server_test', adminPass='bacon')
557 self.body = json.dumps(dict(server=inst_dict))
558
559 def server_update(context, id, params):
560 filtered_dict = dict(
561 display_name='server_test',
562 admin_pass='bacon',
563 )
564 self.assertEqual(params, filtered_dict)
565 return filtered_dict
566
567 self.stubs.Set(nova.db.api, 'instance_update',
568 server_update)
569
570 req = webob.Request.blank('/v1.0/servers/1')
571 req.method = 'PUT'
572 req.content_type = "application/json"
573 req.body = self.body
574 res = req.get_response(fakes.wsgi_app())
575 self.assertEqual(res.status_int, 204)
576
577 def test_update_server_adminPass_ignored_v11(self):
578 inst_dict = dict(name='server_test', adminPass='bacon')
579 self.body = json.dumps(dict(server=inst_dict))
580
581 def server_update(context, id, params):
582 filtered_dict = dict(display_name='server_test')
583 self.assertEqual(params, filtered_dict)
584 return filtered_dict
585
586 self.stubs.Set(nova.db.api, 'instance_update',
587 server_update)
588
589 req = webob.Request.blank('/v1.1/servers/1')
590 req.method = 'PUT'
591 req.content_type = "application/json"
592 req.body = self.body
593 res = req.get_response(fakes.wsgi_app())
594 self.assertEqual(res.status_int, 204)
484595
485 def test_create_backup_schedules(self):596 def test_create_backup_schedules(self):
486 req = webob.Request.blank('/v1.0/servers/1/backup_schedule')597 req = webob.Request.blank('/v1.0/servers/1/backup_schedule')