Merge lp:~yolanda.robla/ubuntu/precise/glance/essex-sru into lp:ubuntu/precise-updates/glance

Proposed by Yolanda Robla
Status: Rejected
Rejected by: James Page
Proposed branch: lp:~yolanda.robla/ubuntu/precise/glance/essex-sru
Merge into: lp:ubuntu/precise-updates/glance
Diff against target: 1573 lines (+309/-1085)
21 files modified
.gitignore (+0/-11)
.gitreview (+0/-5)
.mailmap (+0/-19)
.pc/CVE-2012-4573.patch/glance/api/v1/images.py (+0/-973)
.pc/applied-patches (+0/-1)
.pc/fix_migration_012_foreign_keys.patch/Authors (+1/-0)
Authors (+1/-0)
PKG-INFO (+15/-0)
debian/changelog (+18/-0)
debian/glance-api.logrotate (+1/-1)
debian/glance-registry.logrotate (+1/-1)
debian/patches/CVE-2012-4573.patch (+0/-35)
debian/patches/fix_migration_012_foreign_keys.patch (+22/-26)
debian/patches/series (+0/-1)
glance.egg-info/PKG-INFO (+15/-0)
glance.egg-info/SOURCES.txt (+217/-0)
glance.egg-info/dependency_links.txt (+1/-0)
glance.egg-info/top_level.txt (+1/-0)
glance/vcsversion.py (+7/-0)
setup.cfg (+8/-11)
tools/pip-requires (+1/-1)
To merge this branch: bzr merge lp:~yolanda.robla/ubuntu/precise/glance/essex-sru
Reviewer Review Type Date Requested Status
James Page Disapprove
Review via email: mp+140451@code.launchpad.net

This proposal supersedes a proposal from 2012-12-18.

To post a comment you must log in.
Revision history for this message
James Page (james-page) wrote : Posted in a previous version of this proposal

Yolanda

Specifically what was the pep8 issue that was causing the build to fail?

[ Yolanda Robla ]
* debian/rules: skipping pep8 tests to allow building

review: Needs Information
Revision history for this message
James Page (james-page) wrote :

As discussed with Yolanda on IRC, this update is pretty much no-change as the security issue is already patched and the jenkins problem does not effect Ubuntu.

review: Disapprove

Unmerged revisions

53. By Yolanda Robla

removed skipping pep8 tests
fix typos in changelog

52. By Yolanda Robla

[ Adam Gandelman ]
* debian/glance-{registry, api}.logrotate: Fix incorrect logfile
  locations. (LP: #1049314)

[ Yolanda Robla ]
* debian/rules: skipping pep8 tests to allow building

[ Yolanda Robla Mota ]
* Resynchronize with stable/essex (efd7e75b):
  - [efd7e75] Non-admin users can cause public glance images to be deleted
    from the backend storage repository (CVE-2012-4573)
  - [e6be061] Jenkins jobs fail because of incompatibility between sqlalchemy-
    migrate and the newest sqlalchemy-0.8.0b1 (LP: #1073569)

* Dropped patches, superseeded by snapshot:
  - debian/patches/CVE-2012-4573.patch: [efd7e75]

51. By Jamie Strandboge

* SECURITY UPDATE: deletion of arbitrary public and shared images via
  authenticated user
  - debian/patches/CVE-2012-4573.patch: adjust glance/api/v1/images.py to
    ensure image is owned by user before delayed_deletion
  - CVE-2012-4573

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== removed file '.gitignore'
--- .gitignore 2012-06-24 03:14:33 +0000
+++ .gitignore 1970-01-01 00:00:00 +0000
@@ -1,11 +0,0 @@
1*.pyc
2*.swp
3*.log
4.glance-venv
5.venv
6.tox
7build
8dist
9glance.egg-info
10glance/vcsversion.py
11tests.sqlite
120
=== removed file '.gitreview'
--- .gitreview 2012-06-24 03:14:33 +0000
+++ .gitreview 1970-01-01 00:00:00 +0000
@@ -1,5 +0,0 @@
1[gerrit]
2host=review.openstack.org
3port=29418
4project=openstack/glance.git
5defaultbranch=stable/essex
60
=== removed file '.mailmap'
--- .mailmap 2012-06-24 03:14:33 +0000
+++ .mailmap 1970-01-01 00:00:00 +0000
@@ -1,19 +0,0 @@
1# Format is:
2# <preferred e-mail> <other e-mail 1>
3# <preferred e-mail> <other e-mail 2>
4<adam.gandelman@canonical.com> <adamg@canonical.com>
5<brian.waldon@rackspace.com> <bcwaldon@gmail.com>
6<corywright@gmail.com> <cory.wright@rackspace.com>
7<dprince@redhat.com> <dan.prince@rackspace.com>
8<jsuh@isi.edu> <jsuh@bespin>
9<josh@jk0.org> <josh.kearney@rackspace.com>
10<rconradharris@gmail.com> <rick.harris@rackspace.com>
11<rconradharris@gmail.com> <rick@quasar.racklabs.com>
12<rick@openstack.org> <rclark@chat-blanc>
13<soren.hansen@rackspace.com> <soren@linux2go.dk>
14<soren.hansen@rackspace.com> <soren@openstack.org>
15<jeblair@hp.com> <corvus@gnu.org>
16<jeblair@hp.com> <james.blair@rackspace.com>
17<chris@pistoncloud.com> <chris@slicehost.com>
18<ken.pepple@gmail.com> <ken.pepple@rabbityard.com>
19<P@draigBrady.com> <pbrady@redhat.com>
200
=== removed directory '.pc/CVE-2012-4573.patch'
=== removed directory '.pc/CVE-2012-4573.patch/glance'
=== removed directory '.pc/CVE-2012-4573.patch/glance/api'
=== removed directory '.pc/CVE-2012-4573.patch/glance/api/v1'
=== removed file '.pc/CVE-2012-4573.patch/glance/api/v1/images.py'
--- .pc/CVE-2012-4573.patch/glance/api/v1/images.py 2012-11-08 07:19:39 +0000
+++ .pc/CVE-2012-4573.patch/glance/api/v1/images.py 1970-01-01 00:00:00 +0000
@@ -1,973 +0,0 @@
1# vim: tabstop=4 shiftwidth=4 softtabstop=4
2
3# Copyright 2010 OpenStack LLC.
4# All Rights Reserved.
5#
6# Licensed under the Apache License, Version 2.0 (the "License"); you may
7# not use this file except in compliance with the License. You may obtain
8# a copy of the License at
9#
10# http://www.apache.org/licenses/LICENSE-2.0
11#
12# Unless required by applicable law or agreed to in writing, software
13# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
14# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
15# License for the specific language governing permissions and limitations
16# under the License.
17
18"""
19/images endpoint for Glance v1 API
20"""
21
22import errno
23import logging
24import sys
25import traceback
26
27from webob.exc import (HTTPError,
28 HTTPNotFound,
29 HTTPConflict,
30 HTTPBadRequest,
31 HTTPForbidden,
32 HTTPRequestEntityTooLarge,
33 HTTPServiceUnavailable,
34 )
35
36from glance.api import policy
37import glance.api.v1
38from glance.api.v1 import controller
39from glance.api.v1 import filters
40from glance.common import cfg
41from glance.common import exception
42from glance.common import wsgi
43from glance.common import utils
44import glance.store
45import glance.store.filesystem
46import glance.store.http
47import glance.store.rbd
48import glance.store.s3
49import glance.store.swift
50from glance.store import (get_from_backend,
51 get_size_from_backend,
52 schedule_delete_from_backend,
53 get_store_from_location,
54 get_store_from_scheme)
55from glance import registry
56from glance import notifier
57
58
59logger = logging.getLogger(__name__)
60SUPPORTED_PARAMS = glance.api.v1.SUPPORTED_PARAMS
61SUPPORTED_FILTERS = glance.api.v1.SUPPORTED_FILTERS
62
63
64# 1 PiB, which is a *huge* image by anyone's measure. This is just to protect
65# against client programming errors (or DoS attacks) in the image metadata.
66# We have a known limit of 1 << 63 in the database -- images.size is declared
67# as a BigInteger.
68IMAGE_SIZE_CAP = 1 << 50
69
70
71class Controller(controller.BaseController):
72 """
73 WSGI controller for images resource in Glance v1 API
74
75 The images resource API is a RESTful web service for image data. The API
76 is as follows::
77
78 GET /images -- Returns a set of brief metadata about images
79 GET /images/detail -- Returns a set of detailed metadata about
80 images
81 HEAD /images/<ID> -- Return metadata about an image with id <ID>
82 GET /images/<ID> -- Return image data for image with id <ID>
83 POST /images -- Store image data and return metadata about the
84 newly-stored image
85 PUT /images/<ID> -- Update image metadata and/or upload image
86 data for a previously-reserved image
87 DELETE /images/<ID> -- Delete the image with id <ID>
88 """
89
90 default_store_opt = cfg.StrOpt('default_store', default='file')
91
92 def __init__(self, conf):
93 self.conf = conf
94 self.conf.register_opt(self.default_store_opt)
95 glance.store.create_stores(conf)
96 self.verify_store_or_exit(self.conf.default_store)
97 self.notifier = notifier.Notifier(conf)
98 registry.configure_registry_client(conf)
99 self.policy = policy.Enforcer(conf)
100
101 def _enforce(self, req, action):
102 """Authorize an action against our policies"""
103 try:
104 self.policy.enforce(req.context, action, {})
105 except exception.Forbidden:
106 raise HTTPForbidden()
107
108 def index(self, req):
109 """
110 Returns the following information for all public, available images:
111
112 * id -- The opaque image identifier
113 * name -- The name of the image
114 * disk_format -- The disk image format
115 * container_format -- The "container" format of the image
116 * checksum -- MD5 checksum of the image data
117 * size -- Size of image data in bytes
118
119 :param req: The WSGI/Webob Request object
120 :retval The response body is a mapping of the following form::
121
122 {'images': [
123 {'id': <ID>,
124 'name': <NAME>,
125 'disk_format': <DISK_FORMAT>,
126 'container_format': <DISK_FORMAT>,
127 'checksum': <CHECKSUM>
128 'size': <SIZE>}, ...
129 ]}
130 """
131 self._enforce(req, 'get_images')
132 params = self._get_query_params(req)
133 try:
134 images = registry.get_images_list(req.context, **params)
135 except exception.Invalid, e:
136 raise HTTPBadRequest(explanation="%s" % e)
137
138 return dict(images=images)
139
140 def detail(self, req):
141 """
142 Returns detailed information for all public, available images
143
144 :param req: The WSGI/Webob Request object
145 :retval The response body is a mapping of the following form::
146
147 {'images': [
148 {'id': <ID>,
149 'name': <NAME>,
150 'size': <SIZE>,
151 'disk_format': <DISK_FORMAT>,
152 'container_format': <CONTAINER_FORMAT>,
153 'checksum': <CHECKSUM>,
154 'min_disk': <MIN_DISK>,
155 'min_ram': <MIN_RAM>,
156 'store': <STORE>,
157 'status': <STATUS>,
158 'created_at': <TIMESTAMP>,
159 'updated_at': <TIMESTAMP>,
160 'deleted_at': <TIMESTAMP>|<NONE>,
161 'properties': {'distro': 'Ubuntu 10.04 LTS', ...}}, ...
162 ]}
163 """
164 self._enforce(req, 'get_images')
165 params = self._get_query_params(req)
166 try:
167 images = registry.get_images_detail(req.context, **params)
168 # Strip out the Location attribute. Temporary fix for
169 # LP Bug #755916. This information is still coming back
170 # from the registry, since the API server still needs access
171 # to it, however we do not return this potential security
172 # information to the API end user...
173 for image in images:
174 del image['location']
175 except exception.Invalid, e:
176 raise HTTPBadRequest(explanation="%s" % e)
177 return dict(images=images)
178
179 def _get_query_params(self, req):
180 """
181 Extracts necessary query params from request.
182
183 :param req: the WSGI Request object
184 :retval dict of parameters that can be used by registry client
185 """
186 params = {'filters': self._get_filters(req)}
187
188 for PARAM in SUPPORTED_PARAMS:
189 if PARAM in req.params:
190 params[PARAM] = req.params.get(PARAM)
191 return params
192
193 def _get_filters(self, req):
194 """
195 Return a dictionary of query param filters from the request
196
197 :param req: the Request object coming from the wsgi layer
198 :retval a dict of key/value filters
199 """
200 query_filters = {}
201 for param in req.params:
202 if param in SUPPORTED_FILTERS or param.startswith('property-'):
203 query_filters[param] = req.params.get(param)
204 if not filters.validate(param, query_filters[param]):
205 raise HTTPBadRequest('Bad value passed to filter %s '
206 'got %s' % (param,
207 query_filters[param]))
208 return query_filters
209
210 def meta(self, req, id):
211 """
212 Returns metadata about an image in the HTTP headers of the
213 response object
214
215 :param req: The WSGI/Webob Request object
216 :param id: The opaque image identifier
217 :retval similar to 'show' method but without image_data
218
219 :raises HTTPNotFound if image metadata is not available to user
220 """
221 self._enforce(req, 'get_image')
222 image_meta = self.get_image_meta_or_404(req, id)
223 del image_meta['location']
224 return {
225 'image_meta': image_meta
226 }
227
228 @staticmethod
229 def _validate_source(source, req):
230 """
231 External sources (as specified via the location or copy-from headers)
232 are supported only over non-local store types, i.e. S3, Swift, HTTP.
233 Note the absence of file:// for security reasons, see LP bug #942118.
234 If the above constraint is violated, we reject with 400 "Bad Request".
235 """
236 if source:
237 for scheme in ['s3', 'swift', 'http']:
238 if source.lower().startswith(scheme):
239 return source
240 msg = _("External sourcing not supported for store %s") % source
241 logger.error(msg)
242 raise HTTPBadRequest(msg, request=req, content_type="text/plain")
243
244 @staticmethod
245 def _copy_from(req):
246 return req.headers.get('x-glance-api-copy-from')
247
248 @staticmethod
249 def _external_source(image_meta, req):
250 source = image_meta.get('location', Controller._copy_from(req))
251 return Controller._validate_source(source, req)
252
253 @staticmethod
254 def _get_from_store(where):
255 try:
256 image_data, image_size = get_from_backend(where)
257 except exception.NotFound, e:
258 raise HTTPNotFound(explanation="%s" % e)
259 image_size = int(image_size) if image_size else None
260 return image_data, image_size
261
262 def show(self, req, id):
263 """
264 Returns an iterator that can be used to retrieve an image's
265 data along with the image metadata.
266
267 :param req: The WSGI/Webob Request object
268 :param id: The opaque image identifier
269
270 :raises HTTPNotFound if image is not available to user
271 """
272 self._enforce(req, 'get_image')
273 image_meta = self.get_active_image_meta_or_404(req, id)
274
275 if image_meta.get('size') == 0:
276 image_iterator = iter([])
277 else:
278 image_iterator, size = self._get_from_store(image_meta['location'])
279 image_meta['size'] = size or image_meta['size']
280
281 del image_meta['location']
282 return {
283 'image_iterator': image_iterator,
284 'image_meta': image_meta,
285 }
286
287 def _reserve(self, req, image_meta):
288 """
289 Adds the image metadata to the registry and assigns
290 an image identifier if one is not supplied in the request
291 headers. Sets the image's status to `queued`.
292
293 :param req: The WSGI/Webob Request object
294 :param id: The opaque image identifier
295 :param image_meta: The image metadata
296
297 :raises HTTPConflict if image already exists
298 :raises HTTPBadRequest if image metadata is not valid
299 """
300 location = self._external_source(image_meta, req)
301
302 image_meta['status'] = ('active' if image_meta.get('size') == 0
303 else 'queued')
304
305 if location:
306 store = get_store_from_location(location)
307 # check the store exists before we hit the registry, but we
308 # don't actually care what it is at this point
309 self.get_store_or_400(req, store)
310
311 # retrieve the image size from remote store (if not provided)
312 image_meta['size'] = self._get_size(image_meta, location)
313 else:
314 # Ensure that the size attribute is set to zero for directly
315 # uploadable images (if not provided). The size will be set
316 # to a non-zero value during upload
317 image_meta['size'] = image_meta.get('size', 0)
318
319 try:
320 image_meta = registry.add_image_metadata(req.context, image_meta)
321 return image_meta
322 except exception.Duplicate:
323 msg = (_("An image with identifier %s already exists")
324 % image_meta['id'])
325 logger.error(msg)
326 raise HTTPConflict(msg, request=req, content_type="text/plain")
327 except exception.Invalid, e:
328 msg = (_("Failed to reserve image. Got error: %(e)s") % locals())
329 for line in msg.split('\n'):
330 logger.error(line)
331 raise HTTPBadRequest(msg, request=req, content_type="text/plain")
332 except exception.Forbidden:
333 msg = _("Forbidden to reserve image.")
334 logger.error(msg)
335 raise HTTPForbidden(msg, request=req, content_type="text/plain")
336
337 def _upload(self, req, image_meta):
338 """
339 Uploads the payload of the request to a backend store in
340 Glance. If the `x-image-meta-store` header is set, Glance
341 will attempt to use that store, if not, Glance will use the
342 store set by the flag `default_store`.
343
344 :param req: The WSGI/Webob Request object
345 :param image_meta: Mapping of metadata about image
346
347 :raises HTTPConflict if image already exists
348 :retval The location where the image was stored
349 """
350
351 copy_from = self._copy_from(req)
352 if copy_from:
353 image_data, image_size = self._get_from_store(copy_from)
354 image_meta['size'] = image_size or image_meta['size']
355 else:
356 try:
357 req.get_content_type('application/octet-stream')
358 except exception.InvalidContentType:
359 self._safe_kill(req, image_meta['id'])
360 msg = _("Content-Type must be application/octet-stream")
361 logger.error(msg)
362 raise HTTPBadRequest(explanation=msg)
363
364 image_data = req.body_file
365
366 if req.content_length:
367 image_size = int(req.content_length)
368 elif 'x-image-meta-size' in req.headers:
369 image_size = int(req.headers['x-image-meta-size'])
370 else:
371 logger.debug(_("Got request with no content-length and no "
372 "x-image-meta-size header"))
373 image_size = 0
374
375 store_name = req.headers.get('x-image-meta-store',
376 self.conf.default_store)
377
378 store = self.get_store_or_400(req, store_name)
379
380 image_id = image_meta['id']
381 logger.debug(_("Setting image %s to status 'saving'"), image_id)
382 registry.update_image_metadata(req.context, image_id,
383 {'status': 'saving'})
384 try:
385 logger.debug(_("Uploading image data for image %(image_id)s "
386 "to %(store_name)s store"), locals())
387
388 if image_size > IMAGE_SIZE_CAP:
389 max_image_size = IMAGE_SIZE_CAP
390 msg = _("Denying attempt to upload image larger than "
391 "%(max_image_size)d. Supplied image size was "
392 "%(image_size)d") % locals()
393 logger.warn(msg)
394 raise HTTPBadRequest(msg, request=req)
395
396 location, size, checksum = store.add(image_meta['id'],
397 image_data,
398 image_size)
399
400 # Verify any supplied checksum value matches checksum
401 # returned from store when adding image
402 supplied_checksum = image_meta.get('checksum')
403 if supplied_checksum and supplied_checksum != checksum:
404 msg = _("Supplied checksum (%(supplied_checksum)s) and "
405 "checksum generated from uploaded image "
406 "(%(checksum)s) did not match. Setting image "
407 "status to 'killed'.") % locals()
408 logger.error(msg)
409 self._safe_kill(req, image_id)
410 raise HTTPBadRequest(msg, content_type="text/plain",
411 request=req)
412
413 # Update the database with the checksum returned
414 # from the backend store
415 logger.debug(_("Updating image %(image_id)s data. "
416 "Checksum set to %(checksum)s, size set "
417 "to %(size)d"), locals())
418 update_data = {'checksum': checksum,
419 'size': size}
420 image_meta = registry.update_image_metadata(req.context,
421 image_id,
422 update_data)
423 self.notifier.info('image.upload', image_meta)
424
425 return location
426
427 except exception.Duplicate, e:
428 msg = _("Attempt to upload duplicate image: %s") % e
429 logger.error(msg)
430 self._safe_kill(req, image_id)
431 self.notifier.error('image.upload', msg)
432 raise HTTPConflict(msg, request=req)
433
434 except exception.Forbidden, e:
435 msg = _("Forbidden upload attempt: %s") % e
436 logger.error(msg)
437 self._safe_kill(req, image_id)
438 self.notifier.error('image.upload', msg)
439 raise HTTPForbidden(msg, request=req, content_type="text/plain")
440
441 except exception.StorageFull, e:
442 msg = _("Image storage media is full: %s") % e
443 logger.error(msg)
444 self._safe_kill(req, image_id)
445 self.notifier.error('image.upload', msg)
446 raise HTTPRequestEntityTooLarge(msg, request=req,
447 content_type='text/plain')
448
449 except exception.StorageWriteDenied, e:
450 msg = _("Insufficient permissions on image storage media: %s") % e
451 logger.error(msg)
452 self._safe_kill(req, image_id)
453 self.notifier.error('image.upload', msg)
454 raise HTTPServiceUnavailable(msg, request=req,
455 content_type='text/plain')
456
457 except HTTPError, e:
458 self._safe_kill(req, image_id)
459 self.notifier.error('image.upload', e.explanation)
460 raise
461
462 except Exception, e:
463 tb_info = traceback.format_exc()
464 logger.error(tb_info)
465
466 self._safe_kill(req, image_id)
467
468 msg = _("Error uploading image: (%(class_name)s): "
469 "%(exc)s") % ({'class_name': e.__class__.__name__,
470 'exc': str(e)})
471
472 self.notifier.error('image.upload', msg)
473 raise HTTPBadRequest(msg, request=req)
474
475 def _activate(self, req, image_id, location):
476 """
477 Sets the image status to `active` and the image's location
478 attribute.
479
480 :param req: The WSGI/Webob Request object
481 :param image_id: Opaque image identifier
482 :param location: Location of where Glance stored this image
483 """
484 image_meta = {}
485 image_meta['location'] = location
486 image_meta['status'] = 'active'
487
488 try:
489 return registry.update_image_metadata(req.context,
490 image_id,
491 image_meta)
492 except exception.Invalid, e:
493 msg = (_("Failed to activate image. Got error: %(e)s")
494 % locals())
495 for line in msg.split('\n'):
496 logger.error(line)
497 self.notifier.error('image.update', msg)
498 raise HTTPBadRequest(msg, request=req, content_type="text/plain")
499
500 def _kill(self, req, image_id):
501 """
502 Marks the image status to `killed`.
503
504 :param req: The WSGI/Webob Request object
505 :param image_id: Opaque image identifier
506 """
507 registry.update_image_metadata(req.context, image_id,
508 {'status': 'killed'})
509
510 def _safe_kill(self, req, image_id):
511 """
512 Mark image killed without raising exceptions if it fails.
513
514 Since _kill is meant to be called from exceptions handlers, it should
515 not raise itself, rather it should just log its error.
516
517 :param req: The WSGI/Webob Request object
518 :param image_id: Opaque image identifier
519 """
520 try:
521 self._kill(req, image_id)
522 except Exception, e:
523 logger.error(_("Unable to kill image %(id)s: "
524 "%(exc)s") % ({'id': image_id,
525 'exc': repr(e)}))
526
527 def _upload_and_activate(self, req, image_meta):
528 """
529 Safely uploads the image data in the request payload
530 and activates the image in the registry after a successful
531 upload.
532
533 :param req: The WSGI/Webob Request object
534 :param image_meta: Mapping of metadata about image
535
536 :retval Mapping of updated image data
537 """
538 image_id = image_meta['id']
539 # This is necessary because of a bug in Webob 1.0.2 - 1.0.7
540 # See: https://bitbucket.org/ianb/webob/
541 # issue/12/fix-for-issue-6-broke-chunked-transfer
542 req.is_body_readable = True
543 location = self._upload(req, image_meta)
544 return self._activate(req, image_id, location)
545
546 def _get_size(self, image_meta, location):
547 # retrieve the image size from remote store (if not provided)
548 return image_meta.get('size', 0) or get_size_from_backend(location)
549
550 def _handle_source(self, req, image_id, image_meta, image_data):
551 if image_data or self._copy_from(req):
552 image_meta = self._upload_and_activate(req, image_meta)
553 else:
554 location = image_meta.get('location')
555 if location:
556 image_meta = self._activate(req, image_id, location)
557 return image_meta
558
559 def create(self, req, image_meta, image_data):
560 """
561 Adds a new image to Glance. Four scenarios exist when creating an
562 image:
563
564 1. If the image data is available directly for upload, create can be
565 passed the image data as the request body and the metadata as the
566 request headers. The image will initially be 'queued', during
567 upload it will be in the 'saving' status, and then 'killed' or
568 'active' depending on whether the upload completed successfully.
569
570 2. If the image data exists somewhere else, you can upload indirectly
571 from the external source using the x-glance-api-copy-from header.
572 Once the image is uploaded, the external store is not subsequently
573 consulted, i.e. the image content is served out from the configured
574 glance image store. State transitions are as for option #1.
575
576 3. If the image data exists somewhere else, you can reference the
577 source using the x-image-meta-location header. The image content
578 will be served out from the external store, i.e. is never uploaded
579 to the configured glance image store.
580
581 4. If the image data is not available yet, but you'd like reserve a
582 spot for it, you can omit the data and a record will be created in
583 the 'queued' state. This exists primarily to maintain backwards
584 compatibility with OpenStack/Rackspace API semantics.
585
586 The request body *must* be encoded as application/octet-stream,
587 otherwise an HTTPBadRequest is returned.
588
589 Upon a successful save of the image data and metadata, a response
590 containing metadata about the image is returned, including its
591 opaque identifier.
592
593 :param req: The WSGI/Webob Request object
594 :param image_meta: Mapping of metadata about image
595 :param image_data: Actual image data that is to be stored
596
597 :raises HTTPBadRequest if x-image-meta-location is missing
598 and the request body is not application/octet-stream
599 image data.
600 """
601 self._enforce(req, 'add_image')
602 if image_meta.get('is_public'):
603 self._enforce(req, 'publicize_image')
604 if req.context.read_only:
605 msg = _("Read-only access")
606 logger.debug(msg)
607 raise HTTPForbidden(msg, request=req,
608 content_type="text/plain")
609
610 image_meta = self._reserve(req, image_meta)
611 id = image_meta['id']
612
613 image_meta = self._handle_source(req, id, image_meta, image_data)
614
615 # Prevent client from learning the location, as it
616 # could contain security credentials
617 image_meta.pop('location', None)
618
619 return {'image_meta': image_meta}
620
621 def update(self, req, id, image_meta, image_data):
622 """
623 Updates an existing image with the registry.
624
625 :param request: The WSGI/Webob Request object
626 :param id: The opaque image identifier
627
628 :retval Returns the updated image information as a mapping
629 """
630 self._enforce(req, 'modify_image')
631 if image_meta.get('is_public'):
632 self._enforce(req, 'publicize_image')
633 if req.context.read_only:
634 msg = _("Read-only access")
635 logger.debug(msg)
636 raise HTTPForbidden(msg, request=req,
637 content_type="text/plain")
638
639 orig_image_meta = self.get_image_meta_or_404(req, id)
640 orig_status = orig_image_meta['status']
641
642 # The default behaviour for a PUT /images/<IMAGE_ID> is to
643 # override any properties that were previously set. This, however,
644 # leads to a number of issues for the common use case where a caller
645 # registers an image with some properties and then almost immediately
646 # uploads an image file along with some more properties. Here, we
647 # check for a special header value to be false in order to force
648 # properties NOT to be purged. However we also disable purging of
649 # properties if an image file is being uploaded...
650 purge_props = req.headers.get('x-glance-registry-purge-props', True)
651 purge_props = (utils.bool_from_string(purge_props) and
652 image_data is None)
653
654 if image_data is not None and orig_status != 'queued':
655 raise HTTPConflict(_("Cannot upload to an unqueued image"))
656
657 # Only allow the Location|Copy-From fields to be modified if the
658 # image is in queued status, which indicates that the user called
659 # POST /images but originally supply neither a Location|Copy-From
660 # field NOR image data
661 location = self._external_source(image_meta, req)
662 reactivating = orig_status != 'queued' and location
663 activating = orig_status == 'queued' and (location or image_data)
664
665 if reactivating:
666 msg = _("Attempted to update Location field for an image "
667 "not in queued status.")
668 raise HTTPBadRequest(msg, request=req, content_type="text/plain")
669
670 try:
671 if location:
672 image_meta['size'] = self._get_size(image_meta, location)
673
674 image_meta = registry.update_image_metadata(req.context,
675 id,
676 image_meta,
677 purge_props)
678
679 if activating:
680 image_meta = self._handle_source(req, id, image_meta,
681 image_data)
682 except exception.Invalid, e:
683 msg = (_("Failed to update image metadata. Got error: %(e)s")
684 % locals())
685 for line in msg.split('\n'):
686 logger.error(line)
687 self.notifier.error('image.update', msg)
688 raise HTTPBadRequest(msg, request=req, content_type="text/plain")
689 except exception.NotFound, e:
690 msg = ("Failed to find image to update: %(e)s" % locals())
691 for line in msg.split('\n'):
692 logger.info(line)
693 self.notifier.info('image.update', msg)
694 raise HTTPNotFound(msg, request=req, content_type="text/plain")
695 except exception.Forbidden, e:
696 msg = ("Forbidden to update image: %(e)s" % locals())
697 for line in msg.split('\n'):
698 logger.info(line)
699 self.notifier.info('image.update', msg)
700 raise HTTPForbidden(msg, request=req, content_type="text/plain")
701 else:
702 self.notifier.info('image.update', image_meta)
703
704 # Prevent client from learning the location, as it
705 # could contain security credentials
706 image_meta.pop('location', None)
707
708 return {'image_meta': image_meta}
709
710 def delete(self, req, id):
711 """
712 Deletes the image and all its chunks from the Glance
713
714 :param req: The WSGI/Webob Request object
715 :param id: The opaque image identifier
716
717 :raises HttpBadRequest if image registry is invalid
718 :raises HttpNotFound if image or any chunk is not available
719 :raises HttpUnauthorized if image or any chunk is not
720 deleteable by the requesting user
721 """
722 self._enforce(req, 'delete_image')
723 if req.context.read_only:
724 msg = _("Read-only access")
725 logger.debug(msg)
726 raise HTTPForbidden(msg, request=req,
727 content_type="text/plain")
728
729 image = self.get_image_meta_or_404(req, id)
730 if image['protected']:
731 msg = _("Image is protected")
732 logger.debug(msg)
733 raise HTTPForbidden(msg, request=req,
734 content_type="text/plain")
735
736 # The image's location field may be None in the case
737 # of a saving or queued image, therefore don't ask a backend
738 # to delete the image if the backend doesn't yet store it.
739 # See https://bugs.launchpad.net/glance/+bug/747799
740 try:
741 if image['location']:
742 schedule_delete_from_backend(image['location'], self.conf,
743 req.context, id)
744 registry.delete_image_metadata(req.context, id)
745 except exception.NotFound, e:
746 msg = ("Failed to find image to delete: %(e)s" % locals())
747 for line in msg.split('\n'):
748 logger.info(line)
749 self.notifier.info('image.delete', msg)
750 raise HTTPNotFound(msg, request=req, content_type="text/plain")
751 except exception.Forbidden, e:
752 msg = ("Forbidden to delete image: %(e)s" % locals())
753 for line in msg.split('\n'):
754 logger.info(line)
755 self.notifier.info('image.delete', msg)
756 raise HTTPForbidden(msg, request=req, content_type="text/plain")
757 else:
758 self.notifier.info('image.delete', id)
759
760 def get_store_or_400(self, request, store_name):
761 """
762 Grabs the storage backend for the supplied store name
763 or raises an HTTPBadRequest (400) response
764
765 :param request: The WSGI/Webob Request object
766 :param store_name: The backend store name
767
768 :raises HTTPNotFound if store does not exist
769 """
770 try:
771 return get_store_from_scheme(store_name)
772 except exception.UnknownScheme:
773 msg = (_("Requested store %s not available on this Glance server")
774 % store_name)
775 logger.error(msg)
776 raise HTTPBadRequest(msg, request=request,
777 content_type='text/plain')
778
779 def verify_store_or_exit(self, store_name):
780 """
781 Verifies availability of the storage backend for the
782 given store name or exits
783
784 :param store_name: The backend store name
785 """
786 try:
787 get_store_from_scheme(store_name)
788 except exception.UnknownScheme:
789 msg = (_("Default store %s not available on this Glance server\n")
790 % store_name)
791 logger.error(msg)
792 # message on stderr will only be visible if started directly via
793 # bin/glance-api, as opposed to being daemonized by glance-control
794 sys.stderr.write(msg)
795 sys.exit(255)
796
797
798class ImageDeserializer(wsgi.JSONRequestDeserializer):
799 """Handles deserialization of specific controller method requests."""
800
801 def _deserialize(self, request):
802 result = {}
803 try:
804 result['image_meta'] = utils.get_image_meta_from_headers(request)
805 except exception.Invalid:
806 image_size_str = request.headers['x-image-meta-size']
807 msg = _("Incoming image size of %s was not convertible to "
808 "an integer.") % image_size_str
809 raise HTTPBadRequest(msg, request=request)
810
811 image_meta = result['image_meta']
812 if 'size' in image_meta:
813 incoming_image_size = image_meta['size']
814 if incoming_image_size > IMAGE_SIZE_CAP:
815 max_image_size = IMAGE_SIZE_CAP
816 msg = _("Denying attempt to upload image larger than "
817 "%(max_image_size)d. Supplied image size was "
818 "%(incoming_image_size)d") % locals()
819 logger.warn(msg)
820 raise HTTPBadRequest(msg, request=request)
821
822 data = request.body_file if self.has_body(request) else None
823 result['image_data'] = data
824 return result
825
826 def create(self, request):
827 return self._deserialize(request)
828
829 def update(self, request):
830 return self._deserialize(request)
831
832
833class ImageSerializer(wsgi.JSONResponseSerializer):
834 """Handles serialization of specific controller method responses."""
835
836 def __init__(self, conf):
837 self.conf = conf
838 self.notifier = notifier.Notifier(conf)
839
840 def _inject_location_header(self, response, image_meta):
841 location = self._get_image_location(image_meta)
842 response.headers['Location'] = location
843
844 def _inject_checksum_header(self, response, image_meta):
845 response.headers['ETag'] = image_meta['checksum']
846
847 def _inject_image_meta_headers(self, response, image_meta):
848 """
849 Given a response and mapping of image metadata, injects
850 the Response with a set of HTTP headers for the image
851 metadata. Each main image metadata field is injected
852 as a HTTP header with key 'x-image-meta-<FIELD>' except
853 for the properties field, which is further broken out
854 into a set of 'x-image-meta-property-<KEY>' headers
855
856 :param response: The Webob Response object
857 :param image_meta: Mapping of image metadata
858 """
859 headers = utils.image_meta_to_http_headers(image_meta)
860
861 for k, v in headers.items():
862 response.headers[k] = v
863
864 def _get_image_location(self, image_meta):
865 """Build a relative url to reach the image defined by image_meta."""
866 return "/v1/images/%s" % image_meta['id']
867
868 def meta(self, response, result):
869 image_meta = result['image_meta']
870 self._inject_image_meta_headers(response, image_meta)
871 self._inject_location_header(response, image_meta)
872 self._inject_checksum_header(response, image_meta)
873 return response
874
875 def image_send_notification(self, bytes_written, expected_size,
876 image_meta, request):
877 """Send an image.send message to the notifier."""
878 try:
879 context = request.context
880 payload = {
881 'bytes_sent': bytes_written,
882 'image_id': image_meta['id'],
883 'owner_id': image_meta['owner'],
884 'receiver_tenant_id': context.tenant,
885 'receiver_user_id': context.user,
886 'destination_ip': request.remote_addr,
887 }
888 if bytes_written != expected_size:
889 self.notifier.error('image.send', payload)
890 else:
891 self.notifier.info('image.send', payload)
892 except Exception, err:
893 msg = _("An error occurred during image.send"
894 " notification: %(err)s") % locals()
895 logger.error(msg)
896
897 def show(self, response, result):
898 image_meta = result['image_meta']
899 image_id = image_meta['id']
900
901 # We use a secondary iterator here to wrap the
902 # iterator coming back from the store driver in
903 # order to check for disconnections from the backend
904 # storage connections and log an error if the size of
905 # the transferred image is not the same as the expected
906 # size of the image file. See LP Bug #882585.
907 def checked_iter(image_id, expected_size, image_iter):
908 bytes_written = 0
909
910 def notify_image_sent_hook(env):
911 self.image_send_notification(bytes_written, expected_size,
912 image_meta, response.request)
913
914 # Add hook to process after response is fully sent
915 if 'eventlet.posthooks' in response.request.environ:
916 response.request.environ['eventlet.posthooks'].append(
917 (notify_image_sent_hook, (), {}))
918
919 try:
920 for chunk in image_iter:
921 yield chunk
922 bytes_written += len(chunk)
923 except Exception, err:
924 msg = _("An error occurred reading from backend storage "
925 "for image %(image_id): %(err)s") % locals()
926 logger.error(msg)
927 raise
928
929 if expected_size != bytes_written:
930 msg = _("Backend storage for image %(image_id)s "
931 "disconnected after writing only %(bytes_written)d "
932 "bytes") % locals()
933 logger.error(msg)
934 raise IOError(errno.EPIPE, _("Corrupt image download for "
935 "image %(image_id)s") % locals())
936
937 image_iter = result['image_iterator']
938 # image_meta['size'] is a str
939 expected_size = int(image_meta['size'])
940 response.app_iter = checked_iter(image_id, expected_size, image_iter)
941 # Using app_iter blanks content-length, so we set it here...
942 response.headers['Content-Length'] = image_meta['size']
943 response.headers['Content-Type'] = 'application/octet-stream'
944
945 self._inject_image_meta_headers(response, image_meta)
946 self._inject_location_header(response, image_meta)
947 self._inject_checksum_header(response, image_meta)
948
949 return response
950
951 def update(self, response, result):
952 image_meta = result['image_meta']
953 response.body = self.to_json(dict(image=image_meta))
954 response.headers['Content-Type'] = 'application/json'
955 self._inject_location_header(response, image_meta)
956 self._inject_checksum_header(response, image_meta)
957 return response
958
959 def create(self, response, result):
960 image_meta = result['image_meta']
961 response.status = 201
962 response.headers['Content-Type'] = 'application/json'
963 response.body = self.to_json(dict(image=image_meta))
964 self._inject_location_header(response, image_meta)
965 self._inject_checksum_header(response, image_meta)
966 return response
967
968
969def create_resource(conf):
970 """Images resource factory method"""
971 deserializer = ImageDeserializer()
972 serializer = ImageSerializer(conf)
973 return wsgi.Resource(Controller(conf), deserializer, serializer)
9740
=== modified file '.pc/applied-patches'
--- .pc/applied-patches 2012-11-08 07:19:39 +0000
+++ .pc/applied-patches 2012-12-18 14:12:30 +0000
@@ -3,4 +3,3 @@
3disable-network-for-docs.patch3disable-network-for-docs.patch
4disable_db_table_auto_create.patch4disable_db_table_auto_create.patch
5fix_migration_012_foreign_keys.patch5fix_migration_012_foreign_keys.patch
6CVE-2012-4573.patch
76
=== modified file '.pc/fix_migration_012_foreign_keys.patch/Authors'
--- .pc/fix_migration_012_foreign_keys.patch/Authors 2012-06-24 03:14:33 +0000
+++ .pc/fix_migration_012_foreign_keys.patch/Authors 2012-12-18 14:12:30 +0000
@@ -54,6 +54,7 @@
54Rick Harris <rconradharris@gmail.com>54Rick Harris <rconradharris@gmail.com>
55Reynolds Chin <benzwt@gmail.com>55Reynolds Chin <benzwt@gmail.com>
56Russell Bryant <rbryant@redhat.com>56Russell Bryant <rbryant@redhat.com>
57Sean Dague <sdague@linux.vnet.ibm.com>
57Soren Hansen <soren.hansen@rackspace.com>58Soren Hansen <soren.hansen@rackspace.com>
58Stuart McLaren <stuart.mclaren@hp.com>59Stuart McLaren <stuart.mclaren@hp.com>
59Taku Fukushima <tfukushima@dcl.info.waseda.ac.jp>60Taku Fukushima <tfukushima@dcl.info.waseda.ac.jp>
6061
=== modified file 'Authors'
--- Authors 2012-06-24 03:14:33 +0000
+++ Authors 2012-12-18 14:12:30 +0000
@@ -55,6 +55,7 @@
55Reynolds Chin <benzwt@gmail.com>55Reynolds Chin <benzwt@gmail.com>
56Russell Bryant <rbryant@redhat.com>56Russell Bryant <rbryant@redhat.com>
57Sam Morrison <sorrison@gmail.com>57Sam Morrison <sorrison@gmail.com>
58Sean Dague <sdague@linux.vnet.ibm.com>
58Soren Hansen <soren.hansen@rackspace.com>59Soren Hansen <soren.hansen@rackspace.com>
59Stuart McLaren <stuart.mclaren@hp.com>60Stuart McLaren <stuart.mclaren@hp.com>
60Taku Fukushima <tfukushima@dcl.info.waseda.ac.jp>61Taku Fukushima <tfukushima@dcl.info.waseda.ac.jp>
6162
=== added file 'PKG-INFO'
--- PKG-INFO 1970-01-01 00:00:00 +0000
+++ PKG-INFO 2012-12-18 14:12:30 +0000
@@ -0,0 +1,15 @@
1Metadata-Version: 1.1
2Name: glance
3Version: 2012.1.3
4Summary: The Glance project provides services for discovering, registering, and retrieving virtual machine images
5Home-page: http://glance.openstack.org/
6Author: OpenStack
7Author-email: openstack@lists.launchpad.net
8License: Apache License (2.0)
9Description: UNKNOWN
10Platform: UNKNOWN
11Classifier: Development Status :: 4 - Beta
12Classifier: License :: OSI Approved :: Apache Software License
13Classifier: Operating System :: POSIX :: Linux
14Classifier: Programming Language :: Python :: 2.6
15Classifier: Environment :: No Input/Output (Daemon)
016
=== modified file 'debian/changelog'
--- debian/changelog 2012-11-08 07:19:39 +0000
+++ debian/changelog 2012-12-18 14:12:30 +0000
@@ -1,3 +1,21 @@
1glance (2012.1.4+stable-20121217-efd7e75b-0ubuntu1) precise-proposed; urgency=low
2
3 [ Adam Gandelman ]
4 * debian/glance-{registry, api}.logrotate: Fix incorrect logfile
5 locations. (LP: #1049314)
6
7 [ Yolanda Robla Mota ]
8 * Resynchronize with stable/essex (efd7e75b):
9 - [efd7e75] Non-admin users can cause public glance images to be deleted
10 from the backend storage repository (CVE-2012-4573)
11 - [e6be061] Jenkins jobs fail because of incompatibility between sqlalchemy-
12 migrate and the newest sqlalchemy-0.8.0b1 (LP: #1073569)
13
14 * Dropped patches, superseeded by snapshot:
15 - debian/patches/CVE-2012-4573.patch: [efd7e75]
16
17 -- Yolanda Robla Mota <yolanda.robla@canonical.com> Mon, 17 Dec 2012 10:56:46 +0000
18
1glance (2012.1.3+stable~20120821-120fcf-0ubuntu1.2) precise-security; urgency=low19glance (2012.1.3+stable~20120821-120fcf-0ubuntu1.2) precise-security; urgency=low
220
3 * SECURITY UPDATE: deletion of arbitrary public and shared images via21 * SECURITY UPDATE: deletion of arbitrary public and shared images via
422
=== modified file 'debian/glance-api.logrotate'
--- debian/glance-api.logrotate 2012-01-13 10:50:40 +0000
+++ debian/glance-api.logrotate 2012-12-18 14:12:30 +0000
@@ -1,4 +1,4 @@
1/var/log/glance/glance-api.log {1/var/log/glance/api.log {
2 daily2 daily
3 missingok3 missingok
4 compress4 compress
55
=== modified file 'debian/glance-registry.logrotate'
--- debian/glance-registry.logrotate 2012-01-13 10:50:40 +0000
+++ debian/glance-registry.logrotate 2012-12-18 14:12:30 +0000
@@ -1,4 +1,4 @@
1/var/log/glance/glance-api.log {1/var/log/glance/registry.log {
2 daily2 daily
3 missingok3 missingok
4 compress4 compress
55
=== removed file 'debian/patches/CVE-2012-4573.patch'
--- debian/patches/CVE-2012-4573.patch 2012-11-08 07:19:39 +0000
+++ debian/patches/CVE-2012-4573.patch 1970-01-01 00:00:00 +0000
@@ -1,35 +0,0 @@
1From efd7e75b1f419a52c7103c7840e24af8e5deb29d Mon Sep 17 00:00:00 2001
2From: Brian Waldon <bcwaldon@gmail.com>
3Date: Wed, 7 Nov 2012 10:06:43 -0500
4Subject: [PATCH] Ensure image owned by user before delayed_deletion
5
6Fixes bug 1065187.
7
8Change-Id: Icf2f117a094c712bad645ef5f297e9f7da994c84
9---
10 glance/api/v1/images.py | 9 +++++++++
11 1 file changed, 9 insertions(+)
12
13diff --git a/glance/api/v1/images.py b/glance/api/v1/images.py
14index 9bedf20..1a8eac8 100644
15--- a/glance/api/v1/images.py
16+++ b/glance/api/v1/images.py
17@@ -727,6 +727,15 @@ class Controller(controller.BaseController):
18 content_type="text/plain")
19
20 image = self.get_image_meta_or_404(req, id)
21+
22+ if not (req.context.is_admin
23+ or image['owner'] == None
24+ or image['owner'] == req.context.owner):
25+ msg = _("Unable to delete image you do not own")
26+ logger.debug(msg)
27+ raise HTTPForbidden(msg, request=req,
28+ content_type="text/plain")
29+
30 if image['protected']:
31 msg = _("Image is protected")
32 logger.debug(msg)
33--
341.7.9.5
35
360
=== modified file 'debian/patches/fix_migration_012_foreign_keys.patch'
--- debian/patches/fix_migration_012_foreign_keys.patch 2012-04-12 15:02:08 +0000
+++ debian/patches/fix_migration_012_foreign_keys.patch 2012-12-18 14:12:30 +0000
@@ -1,5 +1,3 @@
1From: 4e35808f94db8c17a20608e137e2940195821fac
2Author: Sam Morrison <sorrison@gmail.com>
3Date: Mon Apr 2 11:22:10 2012 +10001Date: Mon Apr 2 11:22:10 2012 +1000
4Bug: https://bugs.launchpad.net/glance/+bug/9769082Bug: https://bugs.launchpad.net/glance/+bug/976908
5Origin, upstream: https://review.openstack.org/#change,60613Origin, upstream: https://review.openstack.org/#change,6061
@@ -8,25 +6,21 @@
86
9* fix bug 9769087* fix bug 976908
108
11Change-Id: I0248f825396d08688238e6d2ef37c8fcb49e8c9d9Change-Id: I0248f825396d08688238e6d2ef37c8fcb49e8c9
1210diff -Naurp glance-2012.1.3.orig/Authors glance-2012.1.3/Authors
1311--- glance-2012.1.3.orig/Authors 2012-11-26 15:19:40.000000000 -0600
14diff --git a/Authors b/Authors12+++ glance-2012.1.3/Authors 2012-11-26 15:42:42.691087411 -0600
15index 8158c2a..d9c9302 10064413@@ -54,6 +54,7 @@ Rick Clark <rick@openstack.org>
16--- a/Authors
17+++ b/Authors
18@@ -52,6 +52,7 @@ Rick Clark <rick@openstack.org>
19 Rick Harris <rconradharris@gmail.com>14 Rick Harris <rconradharris@gmail.com>
20 Reynolds Chin <benzwt@gmail.com>15 Reynolds Chin <benzwt@gmail.com>
21 Russell Bryant <rbryant@redhat.com>16 Russell Bryant <rbryant@redhat.com>
22+Sam Morrison <sorrison@gmail.com>17+Sam Morrison <sorrison@gmail.com>
18 Sean Dague <sdague@linux.vnet.ibm.com>
23 Soren Hansen <soren.hansen@rackspace.com>19 Soren Hansen <soren.hansen@rackspace.com>
24 Stuart McLaren <stuart.mclaren@hp.com>20 Stuart McLaren <stuart.mclaren@hp.com>
25 Taku Fukushima <tfukushima@dcl.info.waseda.ac.jp>21diff -Naurp glance-2012.1.3.orig/glance/registry/db/migrate_repo/versions/012_id_to_uuid.py glance-2012.1.3/glance/registry/db/migrate_repo/versions/012_id_to_uuid.py
26diff --git a/glance/registry/db/migrate_repo/versions/012_id_to_uuid.py b/glance/registry/db/migrate_repo/versions/012_id_to_uuid.py22--- glance-2012.1.3.orig/glance/registry/db/migrate_repo/versions/012_id_to_uuid.py 2012-11-26 15:19:40.000000000 -0600
27index fe6bf86..8db18c1 10064423+++ glance-2012.1.3/glance/registry/db/migrate_repo/versions/012_id_to_uuid.py 2012-11-26 15:41:59.231087390 -0600
28--- a/glance/registry/db/migrate_repo/versions/012_id_to_uuid.py
29+++ b/glance/registry/db/migrate_repo/versions/012_id_to_uuid.py
30@@ -225,18 +225,24 @@ def _get_table(table_name, metadata):24@@ -225,18 +225,24 @@ def _get_table(table_name, metadata):
31 25
32 def _get_foreign_keys(t_images, t_image_members, t_image_properties):26 def _get_foreign_keys(t_images, t_image_members, t_image_properties):
@@ -36,27 +30,29 @@
36+ foreign_keys = []30+ foreign_keys = []
37+ if t_image_members.foreign_keys:31+ if t_image_members.foreign_keys:
38+ img_members_fk_name = list(t_image_members.foreign_keys)[0].name32+ img_members_fk_name = list(t_image_members.foreign_keys)[0].name
39 33+
40- fk1 = migrate.ForeignKeyConstraint([t_image_members.c.image_id],
41- [t_images.c.id],
42- name=image_members_fk_name)
43+ fk1 = migrate.ForeignKeyConstraint([t_image_members.c.image_id],34+ fk1 = migrate.ForeignKeyConstraint([t_image_members.c.image_id],
44+ [t_images.c.id],35+ [t_images.c.id],
45+ name=img_members_fk_name)36+ name=img_members_fk_name)
46+ foreign_keys.append(fk1)37+ foreign_keys.append(fk1)
47 38+
48- fk2 = migrate.ForeignKeyConstraint([t_image_properties.c.image_id],
49- [t_images.c.id],
50- name=image_properties_fk_name)
51+ if t_image_properties.foreign_keys:39+ if t_image_properties.foreign_keys:
52+ img_properties_fk_name = list(t_image_properties.foreign_keys)[0].name40+ img_properties_fk_name = list(t_image_properties.foreign_keys)[0].name
53 41+
54- return fk1, fk2
55+ fk2 = migrate.ForeignKeyConstraint([t_image_properties.c.image_id],42+ fk2 = migrate.ForeignKeyConstraint([t_image_properties.c.image_id],
56+ [t_images.c.id],43+ [t_images.c.id],
57+ name=img_properties_fk_name)44+ name=img_properties_fk_name)
58+ foreign_keys.append(fk2)45+ foreign_keys.append(fk2)
59+46
47- fk1 = migrate.ForeignKeyConstraint([t_image_members.c.image_id],
48- [t_images.c.id],
49- name=image_members_fk_name)
50-
51- fk2 = migrate.ForeignKeyConstraint([t_image_properties.c.image_id],
52- [t_images.c.id],
53- name=image_properties_fk_name)
54-
55- return fk1, fk2
60+ return foreign_keys56+ return foreign_keys
61 57
62 58
6359
=== modified file 'debian/patches/series'
--- debian/patches/series 2012-11-08 07:19:39 +0000
+++ debian/patches/series 2012-12-18 14:12:30 +0000
@@ -3,4 +3,3 @@
3disable-network-for-docs.patch3disable-network-for-docs.patch
4disable_db_table_auto_create.patch4disable_db_table_auto_create.patch
5fix_migration_012_foreign_keys.patch5fix_migration_012_foreign_keys.patch
6CVE-2012-4573.patch
76
=== added directory 'glance.egg-info'
=== added file 'glance.egg-info/PKG-INFO'
--- glance.egg-info/PKG-INFO 1970-01-01 00:00:00 +0000
+++ glance.egg-info/PKG-INFO 2012-12-18 14:12:30 +0000
@@ -0,0 +1,15 @@
1Metadata-Version: 1.1
2Name: glance
3Version: 2012.1.3
4Summary: The Glance project provides services for discovering, registering, and retrieving virtual machine images
5Home-page: http://glance.openstack.org/
6Author: OpenStack
7Author-email: openstack@lists.launchpad.net
8License: Apache License (2.0)
9Description: UNKNOWN
10Platform: UNKNOWN
11Classifier: Development Status :: 4 - Beta
12Classifier: License :: OSI Approved :: Apache Software License
13Classifier: Operating System :: POSIX :: Linux
14Classifier: Programming Language :: Python :: 2.6
15Classifier: Environment :: No Input/Output (Daemon)
016
=== added file 'glance.egg-info/SOURCES.txt'
--- glance.egg-info/SOURCES.txt 1970-01-01 00:00:00 +0000
+++ glance.egg-info/SOURCES.txt 2012-12-18 14:12:30 +0000
@@ -0,0 +1,217 @@
1Authors
2HACKING.rst
3LICENSE
4MANIFEST.in
5README.rst
6babel.cfg
7pylintrc
8run_tests.py
9run_tests.sh
10setup.cfg
11setup.py
12tox.ini
13bin/glance
14bin/glance-api
15bin/glance-cache-cleaner
16bin/glance-cache-manage
17bin/glance-cache-prefetcher
18bin/glance-cache-pruner
19bin/glance-control
20bin/glance-manage
21bin/glance-registry
22bin/glance-scrubber
23doc/source/architecture.rst
24doc/source/authentication.rst
25doc/source/cache.rst
26doc/source/client.rst
27doc/source/conf.py
28doc/source/configuring.rst
29doc/source/controllingservers.rst
30doc/source/formats.rst
31doc/source/glance.rst
32doc/source/glanceapi.rst
33doc/source/identifiers.rst
34doc/source/index.rst
35doc/source/installing.rst
36doc/source/notifications.rst
37doc/source/policies.rst
38doc/source/statuses.rst
39doc/source/_static/basic.css
40doc/source/_static/default.css
41doc/source/_static/jquery.tweet.js
42doc/source/_static/tweaks.css
43doc/source/_templates/.placeholder
44doc/source/_theme/layout.html
45doc/source/_theme/theme.conf
46doc/source/man/glance.rst
47doc/source/man/glanceapi.rst
48doc/source/man/glancecachecleaner.rst
49doc/source/man/glancecachemanage.rst
50doc/source/man/glancecacheprefetcher.rst
51doc/source/man/glancecachepruner.rst
52doc/source/man/glancecontrol.rst
53doc/source/man/glancemanage.rst
54doc/source/man/glanceregistry.rst
55doc/source/man/glancescrubber.rst
56etc/glance-api-paste.ini
57etc/glance-api.conf
58etc/glance-cache-paste.ini
59etc/glance-cache.conf
60etc/glance-registry-paste.ini
61etc/glance-registry.conf
62etc/glance-scrubber-paste.ini
63etc/glance-scrubber.conf
64etc/logging.cnf.sample
65etc/policy.json
66glance/__init__.py
67glance/client.py
68glance/vcsversion.py
69glance/version.py
70glance.egg-info/PKG-INFO
71glance.egg-info/SOURCES.txt
72glance.egg-info/dependency_links.txt
73glance.egg-info/top_level.txt
74glance/api/__init__.py
75glance/api/cached_images.py
76glance/api/policy.py
77glance/api/versions.py
78glance/api/middleware/__init__.py
79glance/api/middleware/cache.py
80glance/api/middleware/cache_manage.py
81glance/api/middleware/version_negotiation.py
82glance/api/v1/__init__.py
83glance/api/v1/controller.py
84glance/api/v1/filters.py
85glance/api/v1/images.py
86glance/api/v1/members.py
87glance/api/v1/router.py
88glance/common/__init__.py
89glance/common/animation.py
90glance/common/auth.py
91glance/common/cfg.py
92glance/common/client.py
93glance/common/config.py
94glance/common/context.py
95glance/common/crypt.py
96glance/common/exception.py
97glance/common/policy.py
98glance/common/utils.py
99glance/common/wsgi.py
100glance/image_cache/__init__.py
101glance/image_cache/cleaner.py
102glance/image_cache/prefetcher.py
103glance/image_cache/pruner.py
104glance/image_cache/queue_image.py
105glance/image_cache/drivers/__init__.py
106glance/image_cache/drivers/base.py
107glance/image_cache/drivers/sqlite.py
108glance/image_cache/drivers/xattr.py
109glance/locale/__init__.py
110glance/locale/glance.pot
111glance/notifier/__init__.py
112glance/notifier/notify_kombu.py
113glance/notifier/notify_log.py
114glance/notifier/notify_noop.py
115glance/notifier/notify_qpid.py
116glance/notifier/strategy.py
117glance/registry/__init__.py
118glance/registry/client.py
119glance/registry/context.py
120glance/registry/api/__init__.py
121glance/registry/api/v1/__init__.py
122glance/registry/api/v1/images.py
123glance/registry/api/v1/members.py
124glance/registry/db/__init__.py
125glance/registry/db/api.py
126glance/registry/db/migration.py
127glance/registry/db/models.py
128glance/registry/db/migrate_repo/README
129glance/registry/db/migrate_repo/__init__.py
130glance/registry/db/migrate_repo/manage.py
131glance/registry/db/migrate_repo/migrate.cfg
132glance/registry/db/migrate_repo/schema.py
133glance/registry/db/migrate_repo/versions/001_add_images_table.py
134glance/registry/db/migrate_repo/versions/002_add_image_properties_table.py
135glance/registry/db/migrate_repo/versions/003_add_disk_format.py
136glance/registry/db/migrate_repo/versions/003_sqlite_downgrade.sql
137glance/registry/db/migrate_repo/versions/003_sqlite_upgrade.sql
138glance/registry/db/migrate_repo/versions/004_add_checksum.py
139glance/registry/db/migrate_repo/versions/005_size_big_integer.py
140glance/registry/db/migrate_repo/versions/006_key_to_name.py
141glance/registry/db/migrate_repo/versions/006_mysql_downgrade.sql
142glance/registry/db/migrate_repo/versions/006_mysql_upgrade.sql
143glance/registry/db/migrate_repo/versions/006_sqlite_downgrade.sql
144glance/registry/db/migrate_repo/versions/006_sqlite_upgrade.sql
145glance/registry/db/migrate_repo/versions/007_add_owner.py
146glance/registry/db/migrate_repo/versions/008_add_image_members_table.py
147glance/registry/db/migrate_repo/versions/009_add_mindisk_and_minram.py
148glance/registry/db/migrate_repo/versions/010_default_update_at.py
149glance/registry/db/migrate_repo/versions/011_make_mindisk_and_minram_notnull.py
150glance/registry/db/migrate_repo/versions/012_id_to_uuid.py
151glance/registry/db/migrate_repo/versions/013_add_protected.py
152glance/registry/db/migrate_repo/versions/013_sqlite_downgrade.sql
153glance/registry/db/migrate_repo/versions/__init__.py
154glance/store/__init__.py
155glance/store/base.py
156glance/store/filesystem.py
157glance/store/http.py
158glance/store/location.py
159glance/store/rbd.py
160glance/store/s3.py
161glance/store/scrubber.py
162glance/store/swift.py
163glance/tests/__init__.py
164glance/tests/logcapture.py
165glance/tests/stubs.py
166glance/tests/utils.py
167glance/tests/etc/policy.json
168glance/tests/functional/__init__.py
169glance/tests/functional/store_utils.py
170glance/tests/functional/test_api.py
171glance/tests/functional/test_bin_glance.py
172glance/tests/functional/test_bin_glance_cache_manage.py
173glance/tests/functional/test_cache_middleware.py
174glance/tests/functional/test_client_exceptions.py
175glance/tests/functional/test_client_redirects.py
176glance/tests/functional/test_copy_to_file.py
177glance/tests/functional/test_logging.py
178glance/tests/functional/test_misc.py
179glance/tests/functional/test_multiprocessing.py
180glance/tests/functional/test_rbd.py
181glance/tests/functional/test_respawn.py
182glance/tests/functional/test_s3.py
183glance/tests/functional/test_scrubber.py
184glance/tests/functional/test_sqlite.py
185glance/tests/functional/test_ssl.py
186glance/tests/functional/test_swift.py
187glance/tests/unit/__init__.py
188glance/tests/unit/base.py
189glance/tests/unit/test_api.py
190glance/tests/unit/test_auth.py
191glance/tests/unit/test_cfg.py
192glance/tests/unit/test_clients.py
193glance/tests/unit/test_config.py
194glance/tests/unit/test_context.py
195glance/tests/unit/test_db.py
196glance/tests/unit/test_filesystem_store.py
197glance/tests/unit/test_http_store.py
198glance/tests/unit/test_image_cache.py
199glance/tests/unit/test_migrations.conf
200glance/tests/unit/test_migrations.py
201glance/tests/unit/test_misc.py
202glance/tests/unit/test_notifier.py
203glance/tests/unit/test_s3_store.py
204glance/tests/unit/test_skip_examples.py
205glance/tests/unit/test_store_location.py
206glance/tests/unit/test_swift_store.py
207glance/tests/unit/test_utils.py
208glance/tests/unit/test_versions.py
209glance/tests/unit/test_wsgi.py
210glance/tests/var/ca.crt
211glance/tests/var/certificate.crt
212glance/tests/var/privatekey.key
213tools/install_venv.py
214tools/migrate_image_owners.py
215tools/pip-requires
216tools/test-requires
217tools/with_venv.sh
0\ No newline at end of file218\ No newline at end of file
1219
=== added file 'glance.egg-info/dependency_links.txt'
--- glance.egg-info/dependency_links.txt 1970-01-01 00:00:00 +0000
+++ glance.egg-info/dependency_links.txt 2012-12-18 14:12:30 +0000
@@ -0,0 +1,1 @@
1
02
=== added file 'glance.egg-info/top_level.txt'
--- glance.egg-info/top_level.txt 1970-01-01 00:00:00 +0000
+++ glance.egg-info/top_level.txt 2012-12-18 14:12:30 +0000
@@ -0,0 +1,1 @@
1glance
02
=== added file 'glance/vcsversion.py'
--- glance/vcsversion.py 1970-01-01 00:00:00 +0000
+++ glance/vcsversion.py 2012-12-18 14:12:30 +0000
@@ -0,0 +1,7 @@
1
2# This file is automatically generated by setup.py, So don't edit it. :)
3version_info = {
4 'branch_nick': 'stable/essex',
5 'revision_id': 'efd7e75b1f419a52c7103c7840e24af8e5deb29d',
6 'revno': 1464
7}
08
=== modified file 'setup.cfg'
--- setup.cfg 2012-06-24 03:14:33 +0000
+++ setup.cfg 2012-12-18 14:12:30 +0000
@@ -23,14 +23,11 @@
23output_file = glance/locale/glance.pot23output_file = glance/locale/glance.pot
2424
25[nosetests]25[nosetests]
26# NOTE(jkoelker) To run the test suite under nose install the following26verbosity = 2
27# coverage http://pypi.python.org/pypi/coverage27detailed-errors = 1
28# tissue http://pypi.python.org/pypi/tissue (pep8 checker)28with-openstack = 1
29# openstack-nose https://github.com/jkoelker/openstack-nose29openstack-red = 0.05
30verbosity=230openstack-yellow = 0.025
31detailed-errors=131openstack-show-elapsed = 1
32with-openstack=132openstack-color = 1
33openstack-red=0.0533
34openstack-yellow=0.025
35openstack-show-elapsed=1
36openstack-color=1
3734
=== modified file 'tools/pip-requires'
--- tools/pip-requires 2012-06-24 03:14:33 +0000
+++ tools/pip-requires 2012-12-18 14:12:30 +0000
@@ -3,7 +3,7 @@
3# package to get the right headers...3# package to get the right headers...
4greenlet>=0.3.14greenlet>=0.3.1
55
6SQLAlchemy>=0.76SQLAlchemy>=0.7,<=0.7.9
7anyjson7anyjson
8eventlet>=0.9.128eventlet>=0.9.12
9PasteDeploy9PasteDeploy

Subscribers

People subscribed via source and target branches

to all changes: