Merge lp:~rackspace-titan/nova/fix_rebuild into lp:~hudson-openstack/nova/trunk

Proposed by Dan Prince
Status: Merged
Approved by: Mark Washenberger
Approved revision: 1123
Merged at revision: 1125
Proposed branch: lp:~rackspace-titan/nova/fix_rebuild
Merge into: lp:~hudson-openstack/nova/trunk
Diff against target: 322 lines (+165/-26)
6 files modified
nova/api/openstack/servers.py (+6/-4)
nova/compute/api.py (+8/-5)
nova/compute/manager.py (+3/-2)
nova/db/sqlalchemy/api.py (+34/-13)
nova/tests/integrated/api/client.py (+8/-2)
nova/tests/integrated/test_servers.py (+106/-0)
To merge this branch: bzr merge lp:~rackspace-titan/nova/fix_rebuild
Reviewer Review Type Date Requested Status
Mark Washenberger (community) Approve
Devin Carlen (community) Approve
Jay Pipes (community) Approve
Review via email: mp+62785@code.launchpad.net

Description of the change

Update the rebuild_instance function in the compute manager so that it accepts the arguments that our current compute API sends.

Fixes to the SQLAlchmeny API such that metadata is saved on an instance_update. Added integration test to ensure that instance metadata is updated on a rebuild.

To post a comment you must log in.
Revision history for this message
Jay Pipes (jaypipes) wrote :

ah, metadata. :) lgtm.

review: Approve
Revision history for this message
Dan Prince (dan-prince) wrote :

Add 'name' to that list as well.

v1.1 server rebuilds should also support the ability to optionally update the name of the server as well.

Setting to WIP until we get that in there too.

Revision history for this message
Dan Prince (dan-prince) wrote :

Updated so that 'name' can be set when doing a rebuild. Also added a fix to the way we update metadata so that it can be reset when doing a rebuild (if an empty dict is posted).

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

lgtm, nice work :)

review: Approve
Revision history for this message
Mark Washenberger (markwash) wrote :

I don't love the "purge" flag. Do you think it would be cleaner if there were an instance_metadata_delete_all(context, instance_id)? Or perhaps you would prefer not to change the db/api.py interface.

lp:~rackspace-titan/nova/fix_rebuild updated
1123. By Dan Prince

Use a new instance_metadata_delete_all DB api call to delete existing
metadata when updating a server.

Revision history for this message
Dan Prince (dan-prince) wrote :

Mark,

Just swapped the "purge" flag for a instance_metadata_delete_all. I like this better too. Give it a look and let me know. Thanks.

Revision history for this message
Mark Washenberger (markwash) wrote :

Looking good to me. Also merges clean with recent trunk and all tests are passing.

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-05-25 17:55:51 +0000
3+++ nova/api/openstack/servers.py 2011-05-31 18:41:32 +0000
4@@ -708,14 +708,16 @@
5
6 image_id = common.get_id_from_href(image_ref)
7 personalities = info["rebuild"].get("personality", [])
8- metadata = info["rebuild"].get("metadata", {})
9+ metadata = info["rebuild"].get("metadata")
10+ name = info["rebuild"].get("name")
11
12- self._validate_metadata(metadata)
13+ if metadata:
14+ self._validate_metadata(metadata)
15 self._decode_personalities(personalities)
16
17 try:
18- self.compute_api.rebuild(context, instance_id, image_id, metadata,
19- personalities)
20+ self.compute_api.rebuild(context, instance_id, image_id, name,
21+ metadata, personalities)
22 except exception.BuildInProgress:
23 msg = _("Instance %d is currently being rebuilt.") % instance_id
24 LOG.debug(msg)
25
26=== modified file 'nova/compute/api.py'
27--- nova/compute/api.py 2011-05-25 20:58:40 +0000
28+++ nova/compute/api.py 2011-05-31 18:41:32 +0000
29@@ -530,7 +530,7 @@
30 """Reboot the given instance."""
31 self._cast_compute_message('reboot_instance', context, instance_id)
32
33- def rebuild(self, context, instance_id, image_id, metadata=None,
34+ def rebuild(self, context, instance_id, image_id, name=None, metadata=None,
35 files_to_inject=None):
36 """Rebuild the given instance with the provided metadata."""
37 instance = db.api.instance_get(context, instance_id)
38@@ -539,13 +539,16 @@
39 msg = _("Instance already building")
40 raise exception.BuildInProgress(msg)
41
42- metadata = metadata or {}
43- self._check_metadata_properties_quota(context, metadata)
44-
45 files_to_inject = files_to_inject or []
46 self._check_injected_file_quota(context, files_to_inject)
47
48- self.db.instance_update(context, instance_id, {"metadata": metadata})
49+ values = {}
50+ if metadata is not None:
51+ self._check_metadata_properties_quota(context, metadata)
52+ values['metadata'] = metadata
53+ if name is not None:
54+ values['display_name'] = name
55+ self.db.instance_update(context, instance_id, values)
56
57 rebuild_params = {
58 "image_id": image_id,
59
60=== modified file 'nova/compute/manager.py'
61--- nova/compute/manager.py 2011-05-25 19:05:20 +0000
62+++ nova/compute/manager.py 2011-05-31 18:41:32 +0000
63@@ -331,7 +331,7 @@
64
65 @exception.wrap_exception
66 @checks_instance_lock
67- def rebuild_instance(self, context, instance_id, image_id):
68+ def rebuild_instance(self, context, instance_id, **kwargs):
69 """Destroy and re-make this instance.
70
71 A 'rebuild' effectively purges all existing data from the system and
72@@ -349,7 +349,8 @@
73 self._update_state(context, instance_id, power_state.BUILDING)
74
75 self.driver.destroy(instance_ref)
76- instance_ref.image_id = image_id
77+ instance_ref.image_id = kwargs.get('image_id')
78+ instance_ref.injected_files = kwargs.get('injected_files', [])
79 self.driver.spawn(instance_ref)
80
81 self._update_image_id(context, instance_id, image_id)
82
83=== modified file 'nova/db/sqlalchemy/api.py'
84--- nova/db/sqlalchemy/api.py 2011-05-27 04:37:39 +0000
85+++ nova/db/sqlalchemy/api.py 2011-05-31 18:41:32 +0000
86@@ -771,24 +771,25 @@
87
88
89 ###################
90-
91-
92-@require_context
93-def instance_create(context, values):
94- """Create a new Instance record in the database.
95-
96- context - request context object
97- values - dict containing column values.
98- """
99- metadata = values.get('metadata')
100+def _metadata_refs(metadata_dict):
101 metadata_refs = []
102- if metadata:
103- for k, v in metadata.iteritems():
104+ if metadata_dict:
105+ for k, v in metadata_dict.iteritems():
106 metadata_ref = models.InstanceMetadata()
107 metadata_ref['key'] = k
108 metadata_ref['value'] = v
109 metadata_refs.append(metadata_ref)
110- values['metadata'] = metadata_refs
111+ return metadata_refs
112+
113+
114+@require_context
115+def instance_create(context, values):
116+ """Create a new Instance record in the database.
117+
118+ context - request context object
119+ values - dict containing column values.
120+ """
121+ values['metadata'] = _metadata_refs(values.get('metadata'))
122
123 instance_ref = models.Instance()
124 instance_ref.update(values)
125@@ -1010,6 +1011,11 @@
126 @require_context
127 def instance_update(context, instance_id, values):
128 session = get_session()
129+ metadata = values.get('metadata')
130+ if metadata is not None:
131+ instance_metadata_delete_all(context, instance_id)
132+ instance_metadata_update_or_create(context, instance_id,
133+ values.pop('metadata'))
134 with session.begin():
135 instance_ref = instance_get(context, instance_id, session=session)
136 instance_ref.update(values)
137@@ -2626,6 +2632,17 @@
138
139
140 @require_context
141+def instance_metadata_delete_all(context, instance_id):
142+ session = get_session()
143+ session.query(models.InstanceMetadata).\
144+ filter_by(instance_id=instance_id).\
145+ filter_by(deleted=False).\
146+ update({'deleted': True,
147+ 'deleted_at': datetime.datetime.utcnow(),
148+ 'updated_at': literal_column('updated_at')})
149+
150+
151+@require_context
152 def instance_metadata_get_item(context, instance_id, key):
153 session = get_session()
154
155@@ -2644,6 +2661,9 @@
156 @require_context
157 def instance_metadata_update_or_create(context, instance_id, metadata):
158 session = get_session()
159+
160+ original_metadata = instance_metadata_get(context, instance_id)
161+
162 meta_ref = None
163 for key, value in metadata.iteritems():
164 try:
165@@ -2655,4 +2675,5 @@
166 "instance_id": instance_id,
167 "deleted": 0})
168 meta_ref.save(session=session)
169+
170 return metadata
171
172=== modified file 'nova/tests/integrated/api/client.py'
173--- nova/tests/integrated/api/client.py 2011-03-28 23:54:17 +0000
174+++ nova/tests/integrated/api/client.py 2011-05-31 18:41:32 +0000
175@@ -152,7 +152,10 @@
176 def _decode_json(self, response):
177 body = response.read()
178 LOG.debug(_("Decoding JSON: %s") % (body))
179- return json.loads(body)
180+ if body:
181+ return json.loads(body)
182+ else:
183+ return ""
184
185 def api_get(self, relative_uri, **kwargs):
186 kwargs.setdefault('check_response_status', [200])
187@@ -166,7 +169,7 @@
188 headers['Content-Type'] = 'application/json'
189 kwargs['body'] = json.dumps(body)
190
191- kwargs.setdefault('check_response_status', [200])
192+ kwargs.setdefault('check_response_status', [200, 202])
193 response = self.api_request(relative_uri, **kwargs)
194 return self._decode_json(response)
195
196@@ -185,6 +188,9 @@
197 def post_server(self, server):
198 return self.api_post('/servers', server)['server']
199
200+ def post_server_action(self, server_id, data):
201+ return self.api_post('/servers/%s/action' % server_id, data)
202+
203 def delete_server(self, server_id):
204 return self.api_delete('/servers/%s' % server_id)
205
206
207=== modified file 'nova/tests/integrated/test_servers.py'
208--- nova/tests/integrated/test_servers.py 2011-04-15 19:36:52 +0000
209+++ nova/tests/integrated/test_servers.py 2011-05-31 18:41:32 +0000
210@@ -179,6 +179,112 @@
211 # Cleanup
212 self._delete_server(created_server_id)
213
214+ def test_create_and_rebuild_server(self):
215+ """Rebuild a server."""
216+
217+ # create a server with initially has no metadata
218+ server = self._build_minimal_create_server_request()
219+ server_post = {'server': server}
220+ created_server = self.api.post_server(server_post)
221+ LOG.debug("created_server: %s" % created_server)
222+ self.assertTrue(created_server['id'])
223+ created_server_id = created_server['id']
224+
225+ # rebuild the server with metadata
226+ post = {}
227+ post['rebuild'] = {
228+ "imageRef": "https://localhost/v1.1/32278/images/2",
229+ "name": "blah"
230+ }
231+
232+ self.api.post_server_action(created_server_id, post)
233+ LOG.debug("rebuilt server: %s" % created_server)
234+ self.assertTrue(created_server['id'])
235+
236+ found_server = self.api.get_server(created_server_id)
237+ self.assertEqual(created_server_id, found_server['id'])
238+ self.assertEqual({}, found_server.get('metadata'))
239+ self.assertEqual('blah', found_server.get('name'))
240+
241+ # Cleanup
242+ self._delete_server(created_server_id)
243+
244+ def test_create_and_rebuild_server_with_metadata(self):
245+ """Rebuild a server with metadata."""
246+
247+ # create a server with initially has no metadata
248+ server = self._build_minimal_create_server_request()
249+ server_post = {'server': server}
250+ created_server = self.api.post_server(server_post)
251+ LOG.debug("created_server: %s" % created_server)
252+ self.assertTrue(created_server['id'])
253+ created_server_id = created_server['id']
254+
255+ # rebuild the server with metadata
256+ post = {}
257+ post['rebuild'] = {
258+ "imageRef": "https://localhost/v1.1/32278/images/2",
259+ "name": "blah"
260+ }
261+
262+ metadata = {}
263+ for i in range(30):
264+ metadata['key_%s' % i] = 'value_%s' % i
265+
266+ post['rebuild']['metadata'] = metadata
267+
268+ self.api.post_server_action(created_server_id, post)
269+ LOG.debug("rebuilt server: %s" % created_server)
270+ self.assertTrue(created_server['id'])
271+
272+ found_server = self.api.get_server(created_server_id)
273+ self.assertEqual(created_server_id, found_server['id'])
274+ self.assertEqual(metadata, found_server.get('metadata'))
275+ self.assertEqual('blah', found_server.get('name'))
276+
277+ # Cleanup
278+ self._delete_server(created_server_id)
279+
280+ def test_create_and_rebuild_server_with_metadata_removal(self):
281+ """Rebuild a server with metadata."""
282+
283+ # create a server with initially has no metadata
284+ server = self._build_minimal_create_server_request()
285+ server_post = {'server': server}
286+
287+ metadata = {}
288+ for i in range(30):
289+ metadata['key_%s' % i] = 'value_%s' % i
290+
291+ server_post['server']['metadata'] = metadata
292+
293+ created_server = self.api.post_server(server_post)
294+ LOG.debug("created_server: %s" % created_server)
295+ self.assertTrue(created_server['id'])
296+ created_server_id = created_server['id']
297+
298+ # rebuild the server with metadata
299+ post = {}
300+ post['rebuild'] = {
301+ "imageRef": "https://localhost/v1.1/32278/images/2",
302+ "name": "blah"
303+ }
304+
305+ metadata = {}
306+ post['rebuild']['metadata'] = metadata
307+
308+ self.api.post_server_action(created_server_id, post)
309+ LOG.debug("rebuilt server: %s" % created_server)
310+ self.assertTrue(created_server['id'])
311+
312+ found_server = self.api.get_server(created_server_id)
313+ self.assertEqual(created_server_id, found_server['id'])
314+ self.assertEqual(metadata, found_server.get('metadata'))
315+ self.assertEqual('blah', found_server.get('name'))
316+
317+ # Cleanup
318+ self._delete_server(created_server_id)
319+
320
321 if __name__ == "__main__":
322 unittest.main()