Merge lp:~cloudbuilders/nova/os-keypair-integration into lp:~hudson-openstack/nova/trunk
- os-keypair-integration
- Merge into trunk
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 | ||||
Related bugs: |
|
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 |
Commit message
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).
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.
Jesse Andrews (anotherjesse) wrote : | # |
bcwaldon - that is correct - this change does that.
We are working on updating novaclient..
https:/
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?
Brian Waldon (bcwaldon) wrote : | # |
Okay, so per our offline discussion, it looks like you are going to limit this to just v1.1
Jesse Andrews (anotherjesse) wrote : | # |
Can someone do a branch where they add 1.1 only cleanly?
Thierry Carrez (ttx) wrote : | # |
Any reason why we'd need that feature in Diablo ?
Jesse Andrews (anotherjesse) wrote : | # |
ttx - how do you login to instances?
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.
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_
- 1462. By Jesse Andrews
-
merge trunk
- 1463. By Jesse Andrews
-
v1.0 of server create injects first users keypair
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_
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?
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.
- 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
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
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.
- 1469. By Vish Ishaya
-
merge trunk, fix tests
Preview Diff
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": { |
lgtm