Merge lp:~rconradharris/nova/xs-snap-return-image-id-before-snapshot into lp:~hudson-openstack/nova/trunk
- xs-snap-return-image-id-before-snapshot
- Merge into trunk
Status: | Merged |
---|---|
Approved by: | Jay Pipes |
Approved revision: | 532 |
Merged at revision: | 570 |
Proposed branch: | lp:~rconradharris/nova/xs-snap-return-image-id-before-snapshot |
Merge into: | lp:~hudson-openstack/nova/trunk |
Diff against target: |
684 lines (+148/-213) 11 files modified
nova/api/openstack/images.py (+18/-5) nova/compute/api.py (+36/-8) nova/compute/manager.py (+2/-2) nova/image/glance.py (+13/-144) nova/tests/api/openstack/fakes.py (+30/-32) nova/utils.py (+19/-2) nova/virt/libvirt_conn.py (+1/-1) nova/virt/xenapi/vm_utils.py (+15/-8) nova/virt/xenapi/vmops.py (+3/-3) nova/virt/xenapi_conn.py (+6/-2) plugins/xenserver/xenapi/etc/xapi.d/plugins/glance (+5/-6) |
To merge this branch: | bzr merge lp:~rconradharris/nova/xs-snap-return-image-id-before-snapshot |
Related bugs: | |
Related blueprints: |
XenServer Snapshots
(High)
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Jay Pipes (community) | Approve | ||
Thierry Carrez (community) | ffe | Approve | |
Soren Hansen (community) | Approve | ||
Eric Day (community) | Approve | ||
Review via email:
|
Commit message
Description of the change
The Openstack API requires image metadata to be returned immediately after an image-create call.
This is accomplished by having the ImageService create a 'queued' image in Glance.
When the image is subsequently uploaded, the image will go from 'queued' -> 'saving' -> 'queued'.
Related Future Work:
The ImageService needs to be cleaned up so that there is a canonical set of attributes (id, status, etc), and a canonical set of values ('queued', 'saving', etc). Right now, EC2 is fairly coupled to LocalImageService and S3ImageService while OpenStackAPI is coupled to GlanceImageService; ideally, we should be able mix-and-match from any of these.

Soren Hansen (soren) wrote : | # |
[2011-01-12 10:22:41] soren@lenny:
M nova/api/
M nova/compute/api.py
M nova/compute/
M nova/image/
M nova/utils.py
M nova/virt/
M nova/virt/
M nova/virt/
M nova/virt/
M plugins/
Text conflict in nova/image/
Text conflict in nova/virt/
2 conflicts encountered.

Soren Hansen (soren) wrote : | # |
compute.

Rick Harris (rconradharris) wrote : | # |
> compute.
Agreed. I'm doing it this way right now because Glance (as of yet) doesn't really support images-
Adding the image-instance relationships should definitely be part of Cactus (I don't think we should cram it into Bexar since there still needs to be some discussion on how best to do it).
For now, I've added a TODO so we don't forget. Soren, does that work for you for now?

Soren Hansen (soren) wrote : | # |
I'm not sure I understand. _is_public means everyone can see it, right? So if I have sensitive data on my instance, everyone will be able to see them by looking at these snapshots?

Eric Day (eday) wrote : | # |
26: Still broken, s/ComputeAPI/API/

Rick Harris (rconradharris) wrote : | # |
Eday: Oops, fixed now, thanks.
Soren: Right, is_public does mean everyone can see it, for now, until we build the ownership/sharing modeling into Glance (in Cactus). The idea with is_public=True was something like:
Nobody should be using this right now since we haven't addressed security (at all), let's just make it a little easier on the devs since the newly created snapshot will appear in the image-listing as they'd expect.
For now, I've changed it to is_public=False, so there shouldn't be anymore confusion.

Soren Hansen (soren) : | # |

Soren Hansen (soren) wrote : | # |
Rocking

OpenStack Infra (hudson-openstack) wrote : | # |
The attempt to merge lp:~rconradharris/nova/xs-snap-return-image-id-before-snapshot into lp:nova failed. Below is the output from the failed tests.
nova/image/
pass #raise NotImplementedError
^
Separate inline comments by at least two spaces.
An inline comment is a comment on the same line as a statement. Inline
comments should be separated by at least two spaces from the statement.
They should start with a # and a single space.
Okay: x = x + 1 # Increment x
Okay: x = x + 1 # Increment x
E261: x = x + 1 # Increment x
E262: x = x + 1 #Increment x
E262: x = x + 1 # Increment x

Rick Harris (rconradharris) wrote : | # |
Oops, I should have caught that. Pep-8 issue has been fixed. Full Pep-8 suite passes.

OpenStack Infra (hudson-openstack) wrote : | # |
The attempt to merge lp:~rconradharris/nova/xs-snap-return-image-id-before-snapshot into lp:nova failed. Below is the output from the failed tests.
TrialTestCase
runTest ok
Failure
runTest ERROR
APITest
test_
Failure
runTest ERROR
TestFaults
test_
test_raise ok
test_
Failure
runTest ERROR
runTest ERROR
LimiterTest
test_minute ok
test_
test_second ok
test_
test_
WSGIAppProxyTest
test_200 ok
test_403 ok
test_failure ok
WSGIAppTest
test_escaping ok
test_good_urls ok
test_
test_
test_
Failure
runTest ERROR
SharedIpGroupsTest
test_
test_
test_
Test
test_ec2 ok
test_ec2_root ok
test_metadata ok
test_not_found ok
test_openstack ok
test_
SerializerTest
test_basic ok
test_
test_
test_
test_
Test
test_controller ok
test_debug *******
wsgi.multithread = False
SCRIPT_NAME =
webob.adhoc_attrs = {'response': <Response at 0x49343d0 200 OK>}
wsgi.input = <cStringIO.StringI object at 0x4920e40>
REQUEST_METHOD = GET
HTTP_HOST = localhost:80
PATH_INFO = /
S...

Thierry Carrez (ttx) wrote : | # |
This needs some work to fix the test failures, but is in good shape. Let's see if we can get it in before Monday.

Jay Pipes (jaypipes) wrote : | # |
It's really just waiting on packaging for glance..

Ewan Mellor (ewanmellor) wrote : | # |
> It's really just waiting on packaging for glance..
Glance is on PyPI now, so I think you just need to add glance to pip-requires, and this branch will merge.

Soren Hansen (soren) wrote : | # |
Packaged glance (based on work from Monty). Installed it on Hudson. Retrying.

OpenStack Infra (hudson-openstack) wrote : | # |
Attempt to merge into lp:nova failed due to conflicts:
text conflict in nova/compute/api.py

Jay Pipes (jaypipes) wrote : | # |
Alrighty, let's try this again :)

OpenStack Infra (hudson-openstack) wrote : | # |
The attempt to merge lp:~rconradharris/nova/xs-snap-return-image-id-before-snapshot into lp:nova failed. Below is the output from the failed tests.
nova/compute/
^
JCR: Trailing whitespace is superfluous.
FBM: Except when it occurs as part of a blank line (i.e. the line is
nothing but whitespace). According to Python docs[1] a line with only
whitespace is considered a blank line, and is to be ignored. However,
matching a blank line to its indentation level avoids mistakenly
pasting code into the standard Python interpreter.
[1] http://
The warning returned varies on whether the line itself is blank, for easier
filtering for those who want to indent their blank lines.
Okay: spam(1)
W291: spam(1)\s
W293: class Foo(object):\n \n bang = 12
nova/compute/
:retval: A dict containing image metadata
JCR: Trailing whitespace is superfluous.
FBM: Except when it occurs as part of a blank line (i.e. the line is
nothing but whitespace). According to Python docs[1] a line with only
whitespace is considered a blank line, and is to be ignored. However,
matching a blank line to its indentation level avoids mistakenly
pasting code into the standard Python interpreter.
[1] http://
The warning returned varies on whether the line itself is blank, for easier
filtering for those who want to indent their blank lines.
Okay: spam(1)
W291: spam(1)\s
W293: class Foo(object):\n \n bang = 12

Jay Pipes (jaypipes) wrote : | # |
pep8 fixes approved.

OpenStack Infra (hudson-openstack) wrote : | # |
There are additional revisions which have not been approved in review. Please seek review and approval of these new revisions.

Jay Pipes (jaypipes) : | # |
Preview Diff
1 | === modified file 'nova/api/openstack/images.py' |
2 | --- nova/api/openstack/images.py 2011-01-06 02:23:23 +0000 |
3 | +++ nova/api/openstack/images.py 2011-01-17 17:34:42 +0000 |
4 | @@ -78,7 +78,14 @@ |
5 | 'decrypting': 'preparing', |
6 | 'untarring': 'saving', |
7 | 'available': 'active'} |
8 | - item['status'] = status_mapping[item['status']] |
9 | + try: |
10 | + item['status'] = status_mapping[item['status']] |
11 | + except KeyError: |
12 | + # TODO(sirp): Performing translation of status (if necessary) here for |
13 | + # now. Perhaps this should really be done in EC2 API and |
14 | + # S3ImageService |
15 | + pass |
16 | + |
17 | return item |
18 | |
19 | |
20 | @@ -92,9 +99,11 @@ |
21 | |
22 | |
23 | def _convert_image_id_to_hash(image): |
24 | - image_id = abs(hash(image['imageId'])) |
25 | - image['imageId'] = image_id |
26 | - image['id'] = image_id |
27 | + if 'imageId' in image: |
28 | + # Convert EC2-style ID (i-blah) to Rackspace-style (int) |
29 | + image_id = abs(hash(image['imageId'])) |
30 | + image['imageId'] = image_id |
31 | + image['id'] = image_id |
32 | |
33 | |
34 | class Controller(wsgi.Controller): |
35 | @@ -147,7 +156,11 @@ |
36 | env = self._deserialize(req.body, req) |
37 | instance_id = env["image"]["serverId"] |
38 | name = env["image"]["name"] |
39 | - return compute.API().snapshot(context, instance_id, name) |
40 | + |
41 | + image_meta = compute.API().snapshot( |
42 | + context, instance_id, name) |
43 | + |
44 | + return dict(image=image_meta) |
45 | |
46 | def update(self, req, id): |
47 | # Users may not modify public images, and that's all that |
48 | |
49 | === modified file 'nova/compute/api.py' |
50 | --- nova/compute/api.py 2011-01-15 01:48:48 +0000 |
51 | +++ nova/compute/api.py 2011-01-17 17:34:42 +0000 |
52 | @@ -335,27 +335,55 @@ |
53 | project_id) |
54 | return self.db.instance_get_all(context) |
55 | |
56 | - def _cast_compute_message(self, method, context, instance_id, host=None): |
57 | - """Generic handler for RPC casts to compute.""" |
58 | + def _cast_compute_message(self, method, context, instance_id, host=None, |
59 | + params=None): |
60 | + """Generic handler for RPC casts to compute. |
61 | + |
62 | + :param params: Optional dictionary of arguments to be passed to the |
63 | + compute worker |
64 | + |
65 | + :retval None |
66 | + """ |
67 | + if not params: |
68 | + params = {} |
69 | if not host: |
70 | instance = self.get(context, instance_id) |
71 | host = instance['host'] |
72 | queue = self.db.queue_get_for(context, FLAGS.compute_topic, host) |
73 | - kwargs = {'method': method, 'args': {'instance_id': instance_id}} |
74 | + params['instance_id'] = instance_id |
75 | + kwargs = {'method': method, 'args': params} |
76 | rpc.cast(context, queue, kwargs) |
77 | |
78 | - def _call_compute_message(self, method, context, instance_id, host=None): |
79 | - """Generic handler for RPC calls to compute.""" |
80 | + def _call_compute_message(self, method, context, instance_id, host=None, |
81 | + params=None): |
82 | + """Generic handler for RPC calls to compute. |
83 | + |
84 | + :param params: Optional dictionary of arguments to be passed to the |
85 | + compute worker |
86 | + |
87 | + :retval: Result returned by compute worker |
88 | + """ |
89 | + if not params: |
90 | + params = {} |
91 | if not host: |
92 | instance = self.get(context, instance_id) |
93 | host = instance["host"] |
94 | queue = self.db.queue_get_for(context, FLAGS.compute_topic, host) |
95 | - kwargs = {"method": method, "args": {"instance_id": instance_id}} |
96 | + params['instance_id'] = instance_id |
97 | + kwargs = {'method': method, 'args': params} |
98 | return rpc.call(context, queue, kwargs) |
99 | |
100 | def snapshot(self, context, instance_id, name): |
101 | - """Snapshot the given instance.""" |
102 | - self._cast_compute_message('snapshot_instance', context, instance_id) |
103 | + """Snapshot the given instance. |
104 | + |
105 | + :retval: A dict containing image metadata |
106 | + """ |
107 | + data = {'name': name, 'is_public': False} |
108 | + image_meta = self.image_service.create(context, data) |
109 | + params = {'image_id': image_meta['id']} |
110 | + self._cast_compute_message('snapshot_instance', context, instance_id, |
111 | + params=params) |
112 | + return image_meta |
113 | |
114 | def reboot(self, context, instance_id): |
115 | """Reboot the given instance.""" |
116 | |
117 | === modified file 'nova/compute/manager.py' |
118 | --- nova/compute/manager.py 2011-01-13 16:51:31 +0000 |
119 | +++ nova/compute/manager.py 2011-01-17 17:34:42 +0000 |
120 | @@ -294,7 +294,7 @@ |
121 | self._update_state(context, instance_id) |
122 | |
123 | @exception.wrap_exception |
124 | - def snapshot_instance(self, context, instance_id, name): |
125 | + def snapshot_instance(self, context, instance_id, image_id): |
126 | """Snapshot an instance on this server.""" |
127 | context = context.elevated() |
128 | instance_ref = self.db.instance_get(context, instance_id) |
129 | @@ -311,7 +311,7 @@ |
130 | 'instance: %s (state: %s excepted: %s)'), |
131 | instance_id, instance_ref['state'], power_state.RUNNING) |
132 | |
133 | - self.driver.snapshot(instance_ref, name) |
134 | + self.driver.snapshot(instance_ref, image_id) |
135 | |
136 | @exception.wrap_exception |
137 | @checks_instance_lock |
138 | |
139 | === modified file 'nova/image/glance.py' |
140 | --- nova/image/glance.py 2011-01-07 14:46:17 +0000 |
141 | +++ nova/image/glance.py 2011-01-17 17:34:42 +0000 |
142 | @@ -14,9 +14,9 @@ |
143 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
144 | # License for the specific language governing permissions and limitations |
145 | # under the License. |
146 | - |
147 | """Implementation of an image service that uses Glance as the backend""" |
148 | |
149 | +from __future__ import absolute_import |
150 | import httplib |
151 | import json |
152 | import urlparse |
153 | @@ -24,171 +24,40 @@ |
154 | from nova import exception |
155 | from nova import flags |
156 | from nova import log as logging |
157 | +from nova import utils |
158 | from nova.image import service |
159 | |
160 | |
161 | LOG = logging.getLogger('nova.image.glance') |
162 | |
163 | FLAGS = flags.FLAGS |
164 | -flags.DEFINE_string('glance_teller_address', 'http://127.0.0.1', |
165 | - 'IP address or URL where Glance\'s Teller service resides') |
166 | -flags.DEFINE_string('glance_teller_port', '9191', |
167 | - 'Port for Glance\'s Teller service') |
168 | -flags.DEFINE_string('glance_parallax_address', 'http://127.0.0.1', |
169 | - 'IP address or URL where Glance\'s Parallax service ' |
170 | - 'resides') |
171 | -flags.DEFINE_string('glance_parallax_port', '9292', |
172 | - 'Port for Glance\'s Parallax service') |
173 | - |
174 | - |
175 | -class TellerClient(object): |
176 | - |
177 | - def __init__(self): |
178 | - self.address = FLAGS.glance_teller_address |
179 | - self.port = FLAGS.glance_teller_port |
180 | - url = urlparse.urlparse(self.address) |
181 | - self.netloc = url.netloc |
182 | - self.connection_type = {'http': httplib.HTTPConnection, |
183 | - 'https': httplib.HTTPSConnection}[url.scheme] |
184 | - |
185 | - |
186 | -class ParallaxClient(object): |
187 | - |
188 | - def __init__(self): |
189 | - self.address = FLAGS.glance_parallax_address |
190 | - self.port = FLAGS.glance_parallax_port |
191 | - url = urlparse.urlparse(self.address) |
192 | - self.netloc = url.netloc |
193 | - self.connection_type = {'http': httplib.HTTPConnection, |
194 | - 'https': httplib.HTTPSConnection}[url.scheme] |
195 | - |
196 | - def get_image_index(self): |
197 | - """ |
198 | - Returns a list of image id/name mappings from Parallax |
199 | - """ |
200 | - try: |
201 | - c = self.connection_type(self.netloc, self.port) |
202 | - c.request("GET", "images") |
203 | - res = c.getresponse() |
204 | - if res.status == 200: |
205 | - # Parallax returns a JSONified dict(images=image_list) |
206 | - data = json.loads(res.read())['images'] |
207 | - return data |
208 | - else: |
209 | - LOG.warn(_("Parallax returned HTTP error %d from " |
210 | - "request for /images"), res.status_int) |
211 | - return [] |
212 | - finally: |
213 | - c.close() |
214 | - |
215 | - def get_image_details(self): |
216 | - """ |
217 | - Returns a list of detailed image data mappings from Parallax |
218 | - """ |
219 | - try: |
220 | - c = self.connection_type(self.netloc, self.port) |
221 | - c.request("GET", "images/detail") |
222 | - res = c.getresponse() |
223 | - if res.status == 200: |
224 | - # Parallax returns a JSONified dict(images=image_list) |
225 | - data = json.loads(res.read())['images'] |
226 | - return data |
227 | - else: |
228 | - LOG.warn(_("Parallax returned HTTP error %d from " |
229 | - "request for /images/detail"), res.status_int) |
230 | - return [] |
231 | - finally: |
232 | - c.close() |
233 | - |
234 | - def get_image_metadata(self, image_id): |
235 | - """ |
236 | - Returns a mapping of image metadata from Parallax |
237 | - """ |
238 | - try: |
239 | - c = self.connection_type(self.netloc, self.port) |
240 | - c.request("GET", "images/%s" % image_id) |
241 | - res = c.getresponse() |
242 | - if res.status == 200: |
243 | - # Parallax returns a JSONified dict(image=image_info) |
244 | - data = json.loads(res.read())['image'] |
245 | - return data |
246 | - else: |
247 | - # TODO(jaypipes): log the error? |
248 | - return None |
249 | - finally: |
250 | - c.close() |
251 | - |
252 | - def add_image_metadata(self, image_metadata): |
253 | - """ |
254 | - Tells parallax about an image's metadata |
255 | - """ |
256 | - try: |
257 | - c = self.connection_type(self.netloc, self.port) |
258 | - body = json.dumps(image_metadata) |
259 | - c.request("POST", "images", body) |
260 | - res = c.getresponse() |
261 | - if res.status == 200: |
262 | - # Parallax returns a JSONified dict(image=image_info) |
263 | - data = json.loads(res.read())['image'] |
264 | - return data['id'] |
265 | - else: |
266 | - # TODO(jaypipes): log the error? |
267 | - return None |
268 | - finally: |
269 | - c.close() |
270 | - |
271 | - def update_image_metadata(self, image_id, image_metadata): |
272 | - """ |
273 | - Updates Parallax's information about an image |
274 | - """ |
275 | - try: |
276 | - c = self.connection_type(self.netloc, self.port) |
277 | - body = json.dumps(image_metadata) |
278 | - c.request("PUT", "images/%s" % image_id, body) |
279 | - res = c.getresponse() |
280 | - return res.status == 200 |
281 | - finally: |
282 | - c.close() |
283 | - |
284 | - def delete_image_metadata(self, image_id): |
285 | - """ |
286 | - Deletes Parallax's information about an image |
287 | - """ |
288 | - try: |
289 | - c = self.connection_type(self.netloc, self.port) |
290 | - c.request("DELETE", "images/%s" % image_id) |
291 | - res = c.getresponse() |
292 | - return res.status == 200 |
293 | - finally: |
294 | - c.close() |
295 | + |
296 | +GlanceClient = utils.import_class('glance.client.Client') |
297 | |
298 | |
299 | class GlanceImageService(service.BaseImageService): |
300 | """Provides storage and retrieval of disk image objects within Glance.""" |
301 | |
302 | def __init__(self): |
303 | - self.teller = TellerClient() |
304 | - self.parallax = ParallaxClient() |
305 | + self.client = GlanceClient(FLAGS.glance_host, FLAGS.glance_port) |
306 | |
307 | def index(self, context): |
308 | """ |
309 | - Calls out to Parallax for a list of images available |
310 | + Calls out to Glance for a list of images available |
311 | """ |
312 | - images = self.parallax.get_image_index() |
313 | - return images |
314 | + return self.client.get_images() |
315 | |
316 | def detail(self, context): |
317 | """ |
318 | - Calls out to Parallax for a list of detailed image information |
319 | + Calls out to Glance for a list of detailed image information |
320 | """ |
321 | - images = self.parallax.get_image_details() |
322 | - return images |
323 | + return self.client.get_images_detailed() |
324 | |
325 | def show(self, context, id): |
326 | """ |
327 | Returns a dict containing image data for the given opaque image id. |
328 | """ |
329 | - image = self.parallax.get_image_metadata(id) |
330 | + image = self.client.get_image_meta(id) |
331 | if image: |
332 | return image |
333 | raise exception.NotFound |
334 | @@ -200,7 +69,7 @@ |
335 | :raises AlreadyExists if the image already exist. |
336 | |
337 | """ |
338 | - return self.parallax.add_image_metadata(data) |
339 | + return self.client.add_image(image_meta=data) |
340 | |
341 | def update(self, context, image_id, data): |
342 | """Replace the contents of the given image with the new data. |
343 | @@ -208,7 +77,7 @@ |
344 | :raises NotFound if the image does not exist. |
345 | |
346 | """ |
347 | - self.parallax.update_image_metadata(image_id, data) |
348 | + return self.client.update_image(image_id, data) |
349 | |
350 | def delete(self, context, image_id): |
351 | """ |
352 | @@ -217,7 +86,7 @@ |
353 | :raises NotFound if the image does not exist. |
354 | |
355 | """ |
356 | - self.parallax.delete_image_metadata(image_id) |
357 | + return self.client.delete_image(image_id) |
358 | |
359 | def delete_all(self): |
360 | """ |
361 | |
362 | === modified file 'nova/tests/api/openstack/fakes.py' |
363 | --- nova/tests/api/openstack/fakes.py 2011-01-10 02:08:54 +0000 |
364 | +++ nova/tests/api/openstack/fakes.py 2011-01-17 17:34:42 +0000 |
365 | @@ -23,6 +23,8 @@ |
366 | import webob |
367 | import webob.dec |
368 | |
369 | +from glance import client as glance_client |
370 | + |
371 | from nova import auth |
372 | from nova import context |
373 | from nova import exception as exc |
374 | @@ -116,64 +118,60 @@ |
375 | stubs.Set(nova.compute.API, 'snapshot', snapshot) |
376 | |
377 | |
378 | -def stub_out_glance(stubs, initial_fixtures=[]): |
379 | +def stub_out_glance(stubs, initial_fixtures=None): |
380 | |
381 | - class FakeParallaxClient: |
382 | + class FakeGlanceClient: |
383 | |
384 | def __init__(self, initial_fixtures): |
385 | - self.fixtures = initial_fixtures |
386 | + self.fixtures = initial_fixtures or [] |
387 | |
388 | - def fake_get_image_index(self): |
389 | + def fake_get_images(self): |
390 | return [dict(id=f['id'], name=f['name']) |
391 | for f in self.fixtures] |
392 | |
393 | - def fake_get_image_details(self): |
394 | + def fake_get_images_detailed(self): |
395 | return self.fixtures |
396 | |
397 | - def fake_get_image_metadata(self, image_id): |
398 | + def fake_get_image_meta(self, image_id): |
399 | for f in self.fixtures: |
400 | if f['id'] == image_id: |
401 | return f |
402 | return None |
403 | |
404 | - def fake_add_image_metadata(self, image_data): |
405 | + def fake_add_image(self, image_meta): |
406 | id = ''.join(random.choice(string.letters) for _ in range(20)) |
407 | - image_data['id'] = id |
408 | - self.fixtures.append(image_data) |
409 | + image_meta['id'] = id |
410 | + self.fixtures.append(image_meta) |
411 | return id |
412 | |
413 | - def fake_update_image_metadata(self, image_id, image_data): |
414 | - f = self.fake_get_image_metadata(image_id) |
415 | + def fake_update_image(self, image_id, image_meta): |
416 | + f = self.fake_get_image_meta(image_id) |
417 | if not f: |
418 | raise exc.NotFound |
419 | |
420 | - f.update(image_data) |
421 | + f.update(image_meta) |
422 | |
423 | - def fake_delete_image_metadata(self, image_id): |
424 | - f = self.fake_get_image_metadata(image_id) |
425 | + def fake_delete_image(self, image_id): |
426 | + f = self.fake_get_image_meta(image_id) |
427 | if not f: |
428 | raise exc.NotFound |
429 | |
430 | self.fixtures.remove(f) |
431 | |
432 | - def fake_delete_all(self): |
433 | - self.fixtures = [] |
434 | - |
435 | - fake_parallax_client = FakeParallaxClient(initial_fixtures) |
436 | - stubs.Set(nova.image.glance.ParallaxClient, 'get_image_index', |
437 | - fake_parallax_client.fake_get_image_index) |
438 | - stubs.Set(nova.image.glance.ParallaxClient, 'get_image_details', |
439 | - fake_parallax_client.fake_get_image_details) |
440 | - stubs.Set(nova.image.glance.ParallaxClient, 'get_image_metadata', |
441 | - fake_parallax_client.fake_get_image_metadata) |
442 | - stubs.Set(nova.image.glance.ParallaxClient, 'add_image_metadata', |
443 | - fake_parallax_client.fake_add_image_metadata) |
444 | - stubs.Set(nova.image.glance.ParallaxClient, 'update_image_metadata', |
445 | - fake_parallax_client.fake_update_image_metadata) |
446 | - stubs.Set(nova.image.glance.ParallaxClient, 'delete_image_metadata', |
447 | - fake_parallax_client.fake_delete_image_metadata) |
448 | - stubs.Set(nova.image.glance.GlanceImageService, 'delete_all', |
449 | - fake_parallax_client.fake_delete_all) |
450 | + ##def fake_delete_all(self): |
451 | + ## self.fixtures = [] |
452 | + |
453 | + GlanceClient = glance_client.Client |
454 | + fake = FakeGlanceClient(initial_fixtures) |
455 | + |
456 | + stubs.Set(GlanceClient, 'get_images', fake.fake_get_images) |
457 | + stubs.Set(GlanceClient, 'get_images_detailed', |
458 | + fake.fake_get_images_detailed) |
459 | + stubs.Set(GlanceClient, 'get_image_meta', fake.fake_get_image_meta) |
460 | + stubs.Set(GlanceClient, 'add_image', fake.fake_add_image) |
461 | + stubs.Set(GlanceClient, 'update_image', fake.fake_update_image) |
462 | + stubs.Set(GlanceClient, 'delete_image', fake.fake_delete_image) |
463 | + #stubs.Set(GlanceClient, 'delete_all', fake.fake_delete_all) |
464 | |
465 | |
466 | class FakeToken(object): |
467 | |
468 | === modified file 'nova/utils.py' |
469 | --- nova/utils.py 2011-01-15 01:48:48 +0000 |
470 | +++ nova/utils.py 2011-01-17 17:34:42 +0000 |
471 | @@ -334,6 +334,20 @@ |
472 | return getattr(backend, key) |
473 | |
474 | |
475 | +class LoopingCallDone(Exception): |
476 | + """The poll-function passed to LoopingCall can raise this exception to |
477 | + break out of the loop normally. This is somewhat analogous to |
478 | + StopIteration. |
479 | + |
480 | + An optional return-value can be included as the argument to the exception; |
481 | + this return-value will be returned by LoopingCall.wait() |
482 | + """ |
483 | + |
484 | + def __init__(self, retvalue=True): |
485 | + """:param retvalue: Value that LoopingCall.wait() should return""" |
486 | + self.retvalue = retvalue |
487 | + |
488 | + |
489 | class LoopingCall(object): |
490 | def __init__(self, f=None, *args, **kw): |
491 | self.args = args |
492 | @@ -352,12 +366,15 @@ |
493 | while self._running: |
494 | self.f(*self.args, **self.kw) |
495 | greenthread.sleep(interval) |
496 | + except LoopingCallDone, e: |
497 | + self.stop() |
498 | + done.send(e.retvalue) |
499 | except Exception: |
500 | logging.exception('in looping call') |
501 | done.send_exception(*sys.exc_info()) |
502 | return |
503 | - |
504 | - done.send(True) |
505 | + else: |
506 | + done.send(True) |
507 | |
508 | self.done = done |
509 | |
510 | |
511 | === modified file 'nova/virt/libvirt_conn.py' |
512 | --- nova/virt/libvirt_conn.py 2011-01-15 01:54:36 +0000 |
513 | +++ nova/virt/libvirt_conn.py 2011-01-17 17:34:42 +0000 |
514 | @@ -295,7 +295,7 @@ |
515 | virt_dom.detachDevice(xml) |
516 | |
517 | @exception.wrap_exception |
518 | - def snapshot(self, instance, name): |
519 | + def snapshot(self, instance, image_id): |
520 | """ Create snapshot from a running VM instance """ |
521 | raise NotImplementedError( |
522 | _("Instance snapshotting is not supported for libvirt" |
523 | |
524 | === modified file 'nova/virt/xenapi/vm_utils.py' |
525 | --- nova/virt/xenapi/vm_utils.py 2011-01-11 06:47:35 +0000 |
526 | +++ nova/virt/xenapi/vm_utils.py 2011-01-17 17:34:42 +0000 |
527 | @@ -236,14 +236,15 @@ |
528 | return template_vm_ref, [template_vdi_uuid, parent_uuid] |
529 | |
530 | @classmethod |
531 | - def upload_image(cls, session, instance_id, vdi_uuids, image_name): |
532 | + def upload_image(cls, session, instance_id, vdi_uuids, image_id): |
533 | """ Requests that the Glance plugin bundle the specified VDIs and |
534 | push them into Glance using the specified human-friendly name. |
535 | """ |
536 | - LOG.debug(_("Asking xapi to upload %s as '%s'"), vdi_uuids, image_name) |
537 | + logging.debug(_("Asking xapi to upload %s as ID %s"), |
538 | + vdi_uuids, image_id) |
539 | |
540 | params = {'vdi_uuids': vdi_uuids, |
541 | - 'image_name': image_name, |
542 | + 'image_id': image_id, |
543 | 'glance_host': FLAGS.glance_host, |
544 | 'glance_port': FLAGS.glance_port} |
545 | |
546 | @@ -424,9 +425,16 @@ |
547 | * parent_vhd |
548 | snapshot |
549 | """ |
550 | - #TODO(sirp): we need to timeout this req after a while |
551 | + max_attempts = FLAGS.xenapi_vhd_coalesce_max_attempts |
552 | + attempts = {'counter': 0} |
553 | |
554 | def _poll_vhds(): |
555 | + attempts['counter'] += 1 |
556 | + if attempts['counter'] > max_attempts: |
557 | + msg = (_("VHD coalesce attempts exceeded (%d > %d), giving up...") |
558 | + % (attempts['counter'], max_attempts)) |
559 | + raise exception.Error(msg) |
560 | + |
561 | scan_sr(session, instance_id, sr_ref) |
562 | parent_uuid = get_vhd_parent_uuid(session, vdi_ref) |
563 | if original_parent_uuid and (parent_uuid != original_parent_uuid): |
564 | @@ -434,13 +442,12 @@ |
565 | "waiting for coalesce..."), parent_uuid, |
566 | original_parent_uuid) |
567 | else: |
568 | - done.send(parent_uuid) |
569 | + # Breakout of the loop (normally) and return the parent_uuid |
570 | + raise utils.LoopingCallDone(parent_uuid) |
571 | |
572 | - done = event.Event() |
573 | loop = utils.LoopingCall(_poll_vhds) |
574 | loop.start(FLAGS.xenapi_vhd_coalesce_poll_interval, now=True) |
575 | - parent_uuid = done.wait() |
576 | - loop.stop() |
577 | + parent_uuid = loop.wait() |
578 | return parent_uuid |
579 | |
580 | |
581 | |
582 | === modified file 'nova/virt/xenapi/vmops.py' |
583 | --- nova/virt/xenapi/vmops.py 2011-01-13 16:53:13 +0000 |
584 | +++ nova/virt/xenapi/vmops.py 2011-01-17 17:34:42 +0000 |
585 | @@ -161,11 +161,11 @@ |
586 | raise Exception(_('Instance not present %s') % instance_name) |
587 | return vm |
588 | |
589 | - def snapshot(self, instance, name): |
590 | + def snapshot(self, instance, image_id): |
591 | """ Create snapshot from a running VM instance |
592 | |
593 | :param instance: instance to be snapshotted |
594 | - :param name: name/label to be given to the snapshot |
595 | + :param image_id: id of image to upload to |
596 | |
597 | Steps involved in a XenServer snapshot: |
598 | |
599 | @@ -201,7 +201,7 @@ |
600 | try: |
601 | # call plugin to ship snapshot off to glance |
602 | VMHelper.upload_image( |
603 | - self._session, instance.id, template_vdi_uuids, name) |
604 | + self._session, instance.id, template_vdi_uuids, image_id) |
605 | finally: |
606 | self._destroy(instance, template_vm_ref, shutdown=False) |
607 | |
608 | |
609 | === modified file 'nova/virt/xenapi_conn.py' |
610 | --- nova/virt/xenapi_conn.py 2011-01-12 19:22:01 +0000 |
611 | +++ nova/virt/xenapi_conn.py 2011-01-17 17:34:42 +0000 |
612 | @@ -93,6 +93,10 @@ |
613 | 5.0, |
614 | 'The interval used for polling of coalescing vhds.' |
615 | ' Used only if connection_type=xenapi.') |
616 | +flags.DEFINE_integer('xenapi_vhd_coalesce_max_attempts', |
617 | + 5, |
618 | + 'Max number of times to poll for VHD to coalesce.' |
619 | + ' Used only if connection_type=xenapi.') |
620 | flags.DEFINE_string('target_host', |
621 | None, |
622 | 'iSCSI Target Host') |
623 | @@ -141,9 +145,9 @@ |
624 | """Create VM instance""" |
625 | self._vmops.spawn(instance) |
626 | |
627 | - def snapshot(self, instance, name): |
628 | + def snapshot(self, instance, image_id): |
629 | """ Create snapshot from a running VM instance """ |
630 | - self._vmops.snapshot(instance, name) |
631 | + self._vmops.snapshot(instance, image_id) |
632 | |
633 | def reboot(self, instance): |
634 | """Reboot VM instance""" |
635 | |
636 | === modified file 'plugins/xenserver/xenapi/etc/xapi.d/plugins/glance' |
637 | --- plugins/xenserver/xenapi/etc/xapi.d/plugins/glance 2010-12-22 19:01:33 +0000 |
638 | +++ plugins/xenserver/xenapi/etc/xapi.d/plugins/glance 2011-01-17 17:34:42 +0000 |
639 | @@ -45,24 +45,24 @@ |
640 | def put_vdis(session, args): |
641 | params = pickle.loads(exists(args, 'params')) |
642 | vdi_uuids = params["vdi_uuids"] |
643 | - image_name = params["image_name"] |
644 | + image_id = params["image_id"] |
645 | glance_host = params["glance_host"] |
646 | glance_port = params["glance_port"] |
647 | |
648 | sr_path = get_sr_path(session) |
649 | #FIXME(sirp): writing to a temp file until Glance supports chunked-PUTs |
650 | - tmp_file = "%s.tar.gz" % os.path.join('/tmp', image_name) |
651 | + tmp_file = "%s.tar.gz" % os.path.join('/tmp', str(image_id)) |
652 | tar_cmd = ['tar', '-zcf', tmp_file, '--directory=%s' % sr_path] |
653 | paths = [ "%s.vhd" % vdi_uuid for vdi_uuid in vdi_uuids ] |
654 | tar_cmd.extend(paths) |
655 | logging.debug("Bundling image with cmd: %s", tar_cmd) |
656 | subprocess.call(tar_cmd) |
657 | logging.debug("Writing to test file %s", tmp_file) |
658 | - put_bundle_in_glance(tmp_file, image_name, glance_host, glance_port) |
659 | + put_bundle_in_glance(tmp_file, image_id, glance_host, glance_port) |
660 | return "" # FIXME(sirp): return anything useful here? |
661 | |
662 | |
663 | -def put_bundle_in_glance(tmp_file, image_name, glance_host, glance_port): |
664 | +def put_bundle_in_glance(tmp_file, image_id, glance_host, glance_port): |
665 | size = os.path.getsize(tmp_file) |
666 | basename = os.path.basename(tmp_file) |
667 | |
668 | @@ -72,7 +72,6 @@ |
669 | 'x-image-meta-store': 'file', |
670 | 'x-image-meta-is_public': 'True', |
671 | 'x-image-meta-type': 'raw', |
672 | - 'x-image-meta-name': image_name, |
673 | 'x-image-meta-size': size, |
674 | 'content-length': size, |
675 | 'content-type': 'application/octet-stream', |
676 | @@ -80,7 +79,7 @@ |
677 | conn = httplib.HTTPConnection(glance_host, glance_port) |
678 | #NOTE(sirp): httplib under python2.4 won't accept a file-like object |
679 | # to request |
680 | - conn.putrequest('POST', '/images') |
681 | + conn.putrequest('PUT', '/images/%s' % image_id) |
682 | |
683 | for header, value in headers.iteritems(): |
684 | conn.putheader(header, value) |
26: you want compute.API() here, compute_ api.ComputeAPI( ) was the old name
42: Not needed, use self.image_service that was made in constructor