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
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 injected_files = self._get_injected_files(personality)
6
7 flavor_id = self._flavor_id_from_req_data(env)
8+
9+ if not 'name' in env['server']:
10+ msg = _("Server name is not defined")
11+ return exc.HTTPBadRequest(msg)
12+
13+ name = env['server']['name']
14+ self._validate_server_name(name)
15+ name = name.strip()
16+
17 try:
18 (inst,) = self.compute_api.create(
19 context,
20@@ -157,8 +166,8 @@
21 image_id,
22 kernel_id=kernel_id,
23 ramdisk_id=ramdisk_id,
24- display_name=env['server']['name'],
25- display_description=env['server']['name'],
26+ display_name=name,
27+ display_description=name,
28 key_name=key_name,
29 key_data=key_data,
30 metadata=metadata,
31@@ -246,20 +255,33 @@
32
33 ctxt = req.environ['nova.context']
34 update_dict = {}
35- if 'adminPass' in inst_dict['server']:
36- update_dict['admin_pass'] = inst_dict['server']['adminPass']
37- try:
38- self.compute_api.set_admin_password(ctxt, id)
39- except exception.TimeoutException:
40- return exc.HTTPRequestTimeout()
41+
42 if 'name' in inst_dict['server']:
43- update_dict['display_name'] = inst_dict['server']['name']
44+ name = inst_dict['server']['name']
45+ self._validate_server_name(name)
46+ update_dict['display_name'] = name.strip()
47+
48+ self._parse_update(ctxt, id, inst_dict, update_dict)
49+
50 try:
51 self.compute_api.update(ctxt, id, **update_dict)
52 except exception.NotFound:
53 return faults.Fault(exc.HTTPNotFound())
54+
55 return exc.HTTPNoContent()
56
57+ def _validate_server_name(self, value):
58+ if not isinstance(value, basestring):
59+ msg = _("Server name is not a string or unicode")
60+ raise exc.HTTPBadRequest(msg)
61+
62+ if value.strip() == '':
63+ msg = _("Server name is an empty string")
64+ raise exc.HTTPBadRequest(msg)
65+
66+ def _parse_update(self, context, id, inst_dict, update_dict):
67+ pass
68+
69 @scheduler_api.redirect_handler
70 def action(self, req, id):
71 """Multi-purpose method used to reboot, rebuild, or
72@@ -566,6 +588,14 @@
73 def _limit_items(self, items, req):
74 return common.limited(items, req)
75
76+ def _parse_update(self, context, server_id, inst_dict, update_dict):
77+ if 'adminPass' in inst_dict['server']:
78+ update_dict['admin_pass'] = inst_dict['server']['adminPass']
79+ try:
80+ self.compute_api.set_admin_password(context, server_id)
81+ except exception.TimeoutException:
82+ return exc.HTTPRequestTimeout()
83+
84
85 class ControllerV11(Controller):
86 def _image_id_from_req_data(self, data):
87
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 fakes.stub_out_key_pair_funcs(self.stubs, have_key_pair=False)
93 self._test_create_instance_helper()
94
95+ def test_create_instance_no_name(self):
96+ self._setup_for_create_instance()
97+
98+ body = {
99+ 'server': {
100+ 'imageId': 3,
101+ 'flavorId': 1,
102+ 'metadata': {
103+ 'hello': 'world',
104+ 'open': 'stack',
105+ },
106+ 'personality': {},
107+ },
108+ }
109+
110+ req = webob.Request.blank('/v1.0/servers')
111+ req.method = 'POST'
112+ req.body = json.dumps(body)
113+ req.headers["content-type"] = "application/json"
114+ res = req.get_response(fakes.wsgi_app())
115+ self.assertEqual(res.status_int, 400)
116+
117+ def test_create_instance_nonstring_name(self):
118+ self._setup_for_create_instance()
119+
120+ body = {
121+ 'server': {
122+ 'name': 12,
123+ 'imageId': 3,
124+ 'flavorId': 1,
125+ 'metadata': {
126+ 'hello': 'world',
127+ 'open': 'stack',
128+ },
129+ 'personality': {},
130+ },
131+ }
132+
133+ req = webob.Request.blank('/v1.0/servers')
134+ req.method = 'POST'
135+ req.body = json.dumps(body)
136+ req.headers["content-type"] = "application/json"
137+ res = req.get_response(fakes.wsgi_app())
138+ self.assertEqual(res.status_int, 400)
139+
140+ def test_create_instance_whitespace_name(self):
141+ self._setup_for_create_instance()
142+
143+ body = {
144+ 'server': {
145+ 'name': ' ',
146+ 'imageId': 3,
147+ 'flavorId': 1,
148+ 'metadata': {
149+ 'hello': 'world',
150+ 'open': 'stack',
151+ },
152+ 'personality': {},
153+ },
154+ }
155+
156+ req = webob.Request.blank('/v1.0/servers')
157+ req.method = 'POST'
158+ req.body = json.dumps(body)
159+ req.headers["content-type"] = "application/json"
160+ res = req.get_response(fakes.wsgi_app())
161+ self.assertEqual(res.status_int, 400)
162+
163 def test_create_instance_v11(self):
164 self._setup_for_create_instance()
165
166@@ -448,39 +516,82 @@
167 res = req.get_response(fakes.wsgi_app())
168 self.assertEqual(res.status_int, 422)
169
170- def test_update_bad_params(self):
171- """ Confirm that update is filtering params """
172- inst_dict = dict(cat='leopard', name='server_test', adminPass='bacon')
173- self.body = json.dumps(dict(server=inst_dict))
174-
175- def server_update(context, id, params):
176- self.update_called = True
177- filtered_dict = dict(name='server_test', admin_pass='bacon')
178- self.assertEqual(params, filtered_dict)
179-
180- self.stubs.Set(nova.db.api, 'instance_update',
181- server_update)
182-
183- req = webob.Request.blank('/v1.0/servers/1')
184- req.method = 'PUT'
185- req.body = self.body
186- req.get_response(fakes.wsgi_app())
187-
188- def test_update_server(self):
189- inst_dict = dict(name='server_test', adminPass='bacon')
190- self.body = json.dumps(dict(server=inst_dict))
191-
192- def server_update(context, id, params):
193- filtered_dict = dict(name='server_test', admin_pass='bacon')
194- self.assertEqual(params, filtered_dict)
195-
196- self.stubs.Set(nova.db.api, 'instance_update',
197- server_update)
198-
199- req = webob.Request.blank('/v1.0/servers/1')
200- req.method = 'PUT'
201- req.body = self.body
202- req.get_response(fakes.wsgi_app())
203+ def test_update_nonstring_name(self):
204+ """ Confirm that update is filtering params """
205+ inst_dict = dict(name=12, adminPass='bacon')
206+ self.body = json.dumps(dict(server=inst_dict))
207+
208+ req = webob.Request.blank('/v1.0/servers/1')
209+ req.method = 'PUT'
210+ req.content_type = "application/json"
211+ req.body = self.body
212+ res = req.get_response(fakes.wsgi_app())
213+ self.assertEqual(res.status_int, 400)
214+
215+ def test_update_whitespace_name(self):
216+ """ Confirm that update is filtering params """
217+ inst_dict = dict(name=' ', adminPass='bacon')
218+ self.body = json.dumps(dict(server=inst_dict))
219+
220+ req = webob.Request.blank('/v1.0/servers/1')
221+ req.method = 'PUT'
222+ req.content_type = "application/json"
223+ req.body = self.body
224+ res = req.get_response(fakes.wsgi_app())
225+ self.assertEqual(res.status_int, 400)
226+
227+ def test_update_null_name(self):
228+ """ Confirm that update is filtering params """
229+ inst_dict = dict(name='', adminPass='bacon')
230+ self.body = json.dumps(dict(server=inst_dict))
231+
232+ req = webob.Request.blank('/v1.0/servers/1')
233+ req.method = 'PUT'
234+ req.content_type = "application/json"
235+ req.body = self.body
236+ res = req.get_response(fakes.wsgi_app())
237+ self.assertEqual(res.status_int, 400)
238+
239+ def test_update_server_v10(self):
240+ inst_dict = dict(name='server_test', adminPass='bacon')
241+ self.body = json.dumps(dict(server=inst_dict))
242+
243+ def server_update(context, id, params):
244+ filtered_dict = dict(
245+ display_name='server_test',
246+ admin_pass='bacon',
247+ )
248+ self.assertEqual(params, filtered_dict)
249+ return filtered_dict
250+
251+ self.stubs.Set(nova.db.api, 'instance_update',
252+ server_update)
253+
254+ req = webob.Request.blank('/v1.0/servers/1')
255+ req.method = 'PUT'
256+ req.content_type = "application/json"
257+ req.body = self.body
258+ res = req.get_response(fakes.wsgi_app())
259+ self.assertEqual(res.status_int, 204)
260+
261+ def test_update_server_adminPass_ignored_v11(self):
262+ inst_dict = dict(name='server_test', adminPass='bacon')
263+ self.body = json.dumps(dict(server=inst_dict))
264+
265+ def server_update(context, id, params):
266+ filtered_dict = dict(display_name='server_test')
267+ self.assertEqual(params, filtered_dict)
268+ return filtered_dict
269+
270+ self.stubs.Set(nova.db.api, 'instance_update',
271+ server_update)
272+
273+ req = webob.Request.blank('/v1.1/servers/1')
274+ req.method = 'PUT'
275+ req.content_type = "application/json"
276+ req.body = self.body
277+ res = req.get_response(fakes.wsgi_app())
278+ self.assertEqual(res.status_int, 204)
279
280 def test_create_backup_schedules(self):
281 req = webob.Request.blank('/v1.0/servers/1/backup_schedule')