Merge lp:~cloudbuilders/nova/os-keypair-integration into lp:~hudson-openstack/nova/trunk

Proposed by Jesse Andrews
Status: Merged
Approved by: Vish Ishaya
Approved revision: 1469
Merged at revision: 1518
Proposed branch: lp:~cloudbuilders/nova/os-keypair-integration
Merge into: lp:~hudson-openstack/nova/trunk
Diff against target: 328 lines (+91/-23)
5 files modified
nova/api/openstack/create_instance_helper.py (+13/-17)
nova/api/openstack/servers.py (+19/-1)
nova/api/openstack/views/servers.py (+1/-0)
nova/tests/api/openstack/fakes.py (+9/-2)
nova/tests/api/openstack/test_servers.py (+49/-3)
To merge this branch: bzr merge lp:~cloudbuilders/nova/os-keypair-integration
Reviewer Review Type Date Requested Status
Brian Waldon (community) Approve
Thierry Carrez (community) ffe Approve
Ben McGraw (community) Approve
Devin Carlen (community) Approve
Joshua McKenty (community) Approve
Review via email: mp+72140@code.launchpad.net

Description of the change

Accept keypair when you launch a new server. These properties would be stored along with the other server properties in the database (like they are currently for ec2 api).

To post a comment you must log in.
Revision history for this message
Joshua McKenty (joshua-mckenty) wrote :

lgtm

review: Approve
Revision history for this message
Brian Waldon (bcwaldon) wrote :

I definitely like the idea. Currently, we can set up a key in nova-manage and it will be added to the server without having to specify it on server create (at least that's how I understand it). It appears we lose that ability with this change. I just want to make sure it is what we want.

review: Needs Information
Revision history for this message
Jesse Andrews (anotherjesse) wrote :

bcwaldon - that is correct - this change does that.

We are working on updating novaclient..

   https://github.com/rackspace/python-novaclient/blob/master/novaclient/v1_1/keypairs.py

already has support for managing keypairs via api.

This is making it so you can launch via the api with the keypair as well, and will result in us making a pull request to novaclient to launch with a keypair...

Having the hack to inject the first key is not something we will want in Diablo... You?

Revision history for this message
Brian Waldon (bcwaldon) wrote :

Okay, so per our offline discussion, it looks like you are going to limit this to just v1.1

Revision history for this message
Devin Carlen (devcamcar) wrote :

lgtm

review: Approve
Revision history for this message
Jesse Andrews (anotherjesse) wrote :

Can someone do a branch where they add 1.1 only cleanly?

Revision history for this message
Thierry Carrez (ttx) wrote :

Any reason why we'd need that feature in Diablo ?

review: Needs Information (ffe)
Revision history for this message
Jesse Andrews (anotherjesse) wrote :

ttx - how do you login to instances?

Revision history for this message
Ben McGraw (mcgrue) wrote :

lgtm

review: Approve
Revision history for this message
Thierry Carrez (ttx) wrote :

anotherjesse: I was just after a rationale -- at this point in time, if a needed feature isn't there but is badly needed, there is a flaw in our design process.

review: Approve (ffe)
Revision history for this message
Brian Waldon (bcwaldon) wrote :

As for making this v1.1-only, I think you just need to move line 92 into a version-specific view builder in that same module. We can't really do much about versioning create_instance_helper at the moment.

1462. By Jesse Andrews

merge trunk

1463. By Jesse Andrews

v1.0 of server create injects first users keypair

Revision history for this message
Jesse Andrews (anotherjesse) wrote :

> As for making this v1.1-only, I think you just need to move line 92 into a
> version-specific view builder in that same module. We can't really do much
> about versioning create_instance_helper at the moment.

A view is what happens on the way out (response) not for determining how to add a keypair on create.

I did so by adding a create method to the ControllerV10 but the tests now fail - because it seems that my use of super breaks stubs?

Revision history for this message
Brian Waldon (bcwaldon) wrote :

For the create request:

ControllerV10 and ControllerV11 can have a _get_keypair method that the Controller.create (superclass) can call. It's probably the super call from the Controller subclasses screwing up your stubs.

For the create/update/show response:

Move the key_name line (126) into ViewBuilderV11._build_extra

1464. By Vish Ishaya

merged trunk

1465. By Vish Ishaya

moved key_name per review

1466. By Vish Ishaya

fix keypairs stubs

1467. By Vish Ishaya

change to use _get_key_name to retrieve the key

Revision history for this message
Vish Ishaya (vishvananda) wrote :

bcwaldon: I changed the code to use a _get_key_name() method which is I think what you intended in the irc discussion. Let me know if it looks like what you intended.

1468. By Vish Ishaya

expect key_name attribute in 1.1

Revision history for this message
Brian Waldon (bcwaldon) wrote :

Sorry to be so confusing on IRC! This is exactly what I wanted. I'm good with this once you resolve the merge conflicts.

review: Approve
1469. By Vish Ishaya

merge trunk, fix tests

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'nova/api/openstack/create_instance_helper.py'
2--- nova/api/openstack/create_instance_helper.py 2011-08-30 22:05:39 +0000
3+++ nova/api/openstack/create_instance_helper.py 2011-09-01 04:57:26 +0000
4@@ -19,7 +19,6 @@
5 from webob import exc
6 from xml.dom import minidom
7
8-from nova import db
9 from nova import exception
10 from nova import flags
11 from nova import log as logging
12@@ -74,20 +73,17 @@
13 if not 'server' in body:
14 raise exc.HTTPUnprocessableEntity()
15
16+ context = req.environ['nova.context']
17 server_dict = body['server']
18- context = req.environ['nova.context']
19 password = self.controller._get_server_admin_password(server_dict)
20
21- key_name = None
22- key_data = None
23- # TODO(vish): Key pair access should move into a common library
24- # instead of being accessed directly from the db.
25- key_pairs = db.key_pair_get_all_by_user(context.elevated(),
26- context.user_id)
27- if key_pairs:
28- key_pair = key_pairs[0]
29- key_name = key_pair['name']
30- key_data = key_pair['public_key']
31+ if not 'name' in server_dict:
32+ msg = _("Server name is not defined")
33+ raise exc.HTTPBadRequest(explanation=msg)
34+
35+ name = server_dict['name']
36+ self._validate_server_name(name)
37+ name = name.strip()
38
39 image_href = self.controller._image_ref_from_req_data(body)
40 # If the image href was generated by nova api, strip image_href
41@@ -133,12 +129,10 @@
42 msg = _("Invalid flavorRef provided.")
43 raise exc.HTTPBadRequest(explanation=msg)
44
45- if not 'name' in server_dict:
46- msg = _("Server name is not defined")
47- raise exc.HTTPBadRequest(explanation=msg)
48-
49 zone_blob = server_dict.get('blob')
50
51+ # optional openstack extensions:
52+ key_name = server_dict.get('key_name')
53 user_data = server_dict.get('user_data')
54 self._validate_user_data(user_data)
55
56@@ -176,7 +170,6 @@
57 display_name=name,
58 display_description=name,
59 key_name=key_name,
60- key_data=key_data,
61 metadata=server_dict.get('metadata', {}),
62 access_ip_v4=server_dict.get('accessIPv4'),
63 access_ip_v6=server_dict.get('accessIPv6'),
64@@ -199,6 +192,9 @@
65 except exception.FlavorNotFound as error:
66 msg = _("Invalid flavorRef provided.")
67 raise exc.HTTPBadRequest(explanation=msg)
68+ except exception.KeypairNotFound as error:
69+ msg = _("Invalid key_name provided.")
70+ raise exc.HTTPBadRequest(explanation=msg)
71 except exception.SecurityGroupNotFound as error:
72 raise exc.HTTPBadRequest(explanation=unicode(error))
73 except RemoteError as err:
74
75=== modified file 'nova/api/openstack/servers.py'
76--- nova/api/openstack/servers.py 2011-08-24 17:16:37 +0000
77+++ nova/api/openstack/servers.py 2011-09-01 04:57:26 +0000
78@@ -22,6 +22,7 @@
79 import webob
80
81 from nova import compute
82+from nova import db
83 from nova import exception
84 from nova import flags
85 from nova import log as logging
86@@ -141,10 +142,16 @@
87 except exception.NotFound:
88 raise exc.HTTPNotFound()
89
90+ def _get_key_name(self, req, body):
91+ """ Get default keypair if not set """
92+ raise NotImplementedError()
93+
94 def create(self, req, body):
95 """ Creates a new server for a given user """
96+ if 'server' in body:
97+ body['server']['key_name'] = self._get_key_name(req, body)
98+
99 extra_values = None
100- result = None
101 extra_values, instances = self.helper.create_instance(
102 req, body, self.compute_api.create)
103
104@@ -562,6 +569,13 @@
105 raise exc.HTTPNotFound()
106 return webob.Response(status_int=202)
107
108+ def _get_key_name(self, req, body):
109+ context = req.environ["nova.context"]
110+ keypairs = db.key_pair_get_all_by_user(context,
111+ context.user_id)
112+ if keypairs:
113+ return keypairs[0]['name']
114+
115 def _image_ref_from_req_data(self, data):
116 return data['server']['imageId']
117
118@@ -632,6 +646,10 @@
119 except exception.NotFound:
120 raise exc.HTTPNotFound()
121
122+ def _get_key_name(self, req, body):
123+ if 'server' in body:
124+ return body['server'].get('key_name')
125+
126 def _image_ref_from_req_data(self, data):
127 try:
128 return data['server']['imageRef']
129
130=== modified file 'nova/api/openstack/views/servers.py'
131--- nova/api/openstack/views/servers.py 2011-08-25 15:11:51 +0000
132+++ nova/api/openstack/views/servers.py 2011-09-01 04:57:26 +0000
133@@ -183,6 +183,7 @@
134 def _build_extra(self, response, inst):
135 self._build_links(response, inst)
136 response['uuid'] = inst['uuid']
137+ response['key_name'] = inst.get('key_name', '')
138 self._build_config_drive(response, inst)
139
140 def _build_links(self, response, inst):
141
142=== modified file 'nova/tests/api/openstack/fakes.py'
143--- nova/tests/api/openstack/fakes.py 2011-08-19 20:03:24 +0000
144+++ nova/tests/api/openstack/fakes.py 2011-09-01 04:57:26 +0000
145@@ -107,13 +107,20 @@
146 def key_pair(context, user_id):
147 return [dict(name='key', public_key='public_key')]
148
149+ def one_key_pair(context, user_id, name):
150+ if name == 'key':
151+ return dict(name='key', public_key='public_key')
152+ else:
153+ raise exc.KeypairNotFound(user_id=user_id, name=name)
154+
155 def no_key_pair(context, user_id):
156 return []
157
158 if have_key_pair:
159- stubs.Set(nova.db, 'key_pair_get_all_by_user', key_pair)
160+ stubs.Set(nova.db.api, 'key_pair_get_all_by_user', key_pair)
161+ stubs.Set(nova.db.api, 'key_pair_get', one_key_pair)
162 else:
163- stubs.Set(nova.db, 'key_pair_get_all_by_user', no_key_pair)
164+ stubs.Set(nova.db.api, 'key_pair_get_all_by_user', no_key_pair)
165
166
167 def stub_out_image_service(stubs):
168
169=== modified file 'nova/tests/api/openstack/test_servers.py'
170--- nova/tests/api/openstack/test_servers.py 2011-08-31 23:07:29 +0000
171+++ nova/tests/api/openstack/test_servers.py 2011-09-01 04:57:26 +0000
172@@ -155,7 +155,7 @@
173 public_addresses=None, host=None,
174 vm_state=None, task_state=None,
175 reservation_id="", uuid=FAKE_UUID, image_ref="10",
176- flavor_id="1", interfaces=None, name=None,
177+ flavor_id="1", interfaces=None, name=None, key_name='',
178 access_ipv4=None, access_ipv6=None):
179 metadata = []
180 metadata.append(InstanceMetadata(key='seq', value=id))
181@@ -171,6 +171,11 @@
182 if host is not None:
183 host = str(host)
184
185+ if key_name:
186+ key_data = 'FAKE'
187+ else:
188+ key_data = ''
189+
190 # ReservationID isn't sent back, hack it in there.
191 server_name = name or "server%s" % id
192 if reservation_id != "":
193@@ -187,8 +192,8 @@
194 "kernel_id": "",
195 "ramdisk_id": "",
196 "launch_index": 0,
197- "key_name": "",
198- "key_data": "",
199+ "key_name": key_name,
200+ "key_data": key_data,
201 "vm_state": vm_state or vm_states.BUILDING,
202 "task_state": task_state,
203 "memory_mb": 0,
204@@ -350,6 +355,7 @@
205 "accessIPv4": "",
206 "accessIPv6": "",
207 "hostId": '',
208+ "key_name": '',
209 "image": {
210 "id": "10",
211 "links": [
212@@ -517,6 +523,7 @@
213 "accessIPv4": "",
214 "accessIPv6": "",
215 "hostId": '',
216+ "key_name": '',
217 "image": {
218 "id": "10",
219 "links": [
220@@ -611,6 +618,7 @@
221 "accessIPv4": "",
222 "accessIPv6": "",
223 "hostId": '',
224+ "key_name": '',
225 "image": {
226 "id": "10",
227 "links": [
228@@ -1746,6 +1754,36 @@
229 self.assertEqual('1.2.3.4', server['accessIPv4'])
230 self.assertEqual('fead::1234', server['accessIPv6'])
231
232+ def test_create_instance_v1_1_invalid_key_name(self):
233+ self._setup_for_create_instance()
234+
235+ image_href = 'http://localhost/v1.1/images/2'
236+ flavor_ref = 'http://localhost/flavors/3'
237+ body = dict(server=dict(
238+ name='server_test', imageRef=image_href, flavorRef=flavor_ref,
239+ key_name='nonexistentkey'))
240+ req = webob.Request.blank('/v1.1/fake/servers')
241+ req.method = 'POST'
242+ req.body = json.dumps(body)
243+ req.headers["content-type"] = "application/json"
244+ res = req.get_response(fakes.wsgi_app())
245+ self.assertEqual(res.status_int, 400)
246+
247+ def test_create_instance_v1_1_valid_key_name(self):
248+ self._setup_for_create_instance()
249+
250+ image_href = 'http://localhost/v1.1/images/2'
251+ flavor_ref = 'http://localhost/flavors/3'
252+ body = dict(server=dict(
253+ name='server_test', imageRef=image_href, flavorRef=flavor_ref,
254+ key_name='key'))
255+ req = webob.Request.blank('/v1.1/fake/servers')
256+ req.method = 'POST'
257+ req.body = json.dumps(body)
258+ req.headers["content-type"] = "application/json"
259+ res = req.get_response(fakes.wsgi_app())
260+ self.assertEqual(res.status_int, 202)
261+
262 def test_create_instance_v1_1_invalid_flavor_href(self):
263 self._setup_for_create_instance()
264
265@@ -3617,6 +3655,7 @@
266 "id": 1,
267 "uuid": self.instance['uuid'],
268 "name": "test_server",
269+ "key_name": '',
270 "links": [
271 {
272 "rel": "self",
273@@ -3640,6 +3679,7 @@
274 "id": 1,
275 "uuid": self.instance['uuid'],
276 "name": "test_server",
277+ "key_name": '',
278 "config_drive": None,
279 "links": [
280 {
281@@ -3673,6 +3713,7 @@
282 "accessIPv4": "",
283 "accessIPv6": "",
284 "hostId": '',
285+ "key_name": '',
286 "image": {
287 "id": "5",
288 "links": [
289@@ -3727,6 +3768,7 @@
290 "accessIPv4": "",
291 "accessIPv6": "",
292 "hostId": '',
293+ "key_name": '',
294 "image": {
295 "id": "5",
296 "links": [
297@@ -3778,6 +3820,7 @@
298 "created": "2010-10-10T12:00:00Z",
299 "progress": 0,
300 "name": "test_server",
301+ "key_name": "",
302 "status": "BUILD",
303 "hostId": '',
304 "image": {
305@@ -3833,6 +3876,7 @@
306 "created": "2010-10-10T12:00:00Z",
307 "progress": 0,
308 "name": "test_server",
309+ "key_name": "",
310 "status": "BUILD",
311 "hostId": '',
312 "image": {
313@@ -3895,6 +3939,7 @@
314 "accessIPv4": "",
315 "accessIPv6": "",
316 "hostId": '',
317+ "key_name": '',
318 "image": {
319 "id": "5",
320 "links": [
321@@ -3961,6 +4006,7 @@
322 "name": "test_server",
323 "status": "BUILD",
324 "hostId": 'e4d909c290d0fb1ca068ffaddf22cbd0',
325+ "key_name": '',
326 "accessIPv4": "1.2.3.4",
327 "accessIPv6": "fead::1234",
328 "image": {