Skip to content

Commit

Permalink
This merge is in conjunction with lp:~rconradharris/nova/xs-snap-retu…
Browse files Browse the repository at this point in the history
…rn-image-id-before-snapshot

The patch does the following:

  * Image Create (POST) is broken up into 3 steps (reserve, upload and activate); reserve is used to allow the OpenStack API to return metadata about an image where the data has not been uploaded yet
  * Image Update (PUT) now takes image data as the request body and metadata as headers (just like POST); state is enforced so that image data can only be uploaded once.
  * Image statuses were changed to match the OpenStack API (queued, saving, active, killed); NOTE: preparing is not used
  * update_image and add_image client calls now return metadata instead of just the id
  • Loading branch information
rconradharris authored and Tarmac committed Jan 12, 2011
2 parents 5048e3f + 21cea9c commit 1f4a644
Show file tree
Hide file tree
Showing 16 changed files with 238 additions and 173 deletions.
4 changes: 4 additions & 0 deletions doc/source/client.rst
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,10 @@ The method signature is as follows::
The `image_meta` argument is a mapping containing various image metadata. The
`image_data` argument is the disk image data.

If the data is not yet available, but you would like to reserve a slot in
Glance to hold the image, you can create a 'queued' by omitting the
`image_data` parameter.

The list of metadata that `image_meta` can contain are listed below.

* `name`
Expand Down
16 changes: 16 additions & 0 deletions doc/source/glanceapi.rst
Original file line number Diff line number Diff line change
Expand Up @@ -292,3 +292,19 @@ The list of metadata headers that Glance accepts are listed below.
be attached to the image. However, keep in mind that the 8K limit on the
size of all HTTP headers sent in a request will effectively limit the number
of image properties.


Updating an Image
*****************

Glance will view as image metadata any HTTP header that it receives in a
`PUT` request where the header key is prefixed with the strings
`x-image-meta-` and `x-image-meta-property-`.

If an image was previously reserved, and thus is in the `queued` state, then
image data can be added by including it as the request body. If the image
already as data associated with it (e.g. not in the `queued` state), then
including a request body will result in a `409 Conflict` exception.

On success, the `PUT` request will return the image metadata encoded as `HTTP`
headers.
37 changes: 20 additions & 17 deletions glance/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,11 +120,11 @@ def do_request(self, method, action, body=None, headers=None):
elif status_code == httplib.NOT_FOUND:
raise exception.NotFound
elif status_code == httplib.CONFLICT:
raise exception.Duplicate
raise exception.Duplicate(res.read())
elif status_code == httplib.BAD_REQUEST:
raise exception.BadInputError
raise exception.BadInputError(res.read())
else:
raise Exception("Unknown error occurred! %d" % status_code)
raise Exception("Unknown error occurred! %s" % res.__dict__)

except (socket.error, IOError), e:
raise ClientConnectionError("Unable to connect to "
Expand Down Expand Up @@ -203,24 +203,22 @@ def get_image_meta(self, image_id):
image = util.get_image_meta_from_headers(res)
return image

def add_image(self, image_meta, image_data=None):
def add_image(self, image_meta=None, image_data=None):
"""
Tells Glance about an image's metadata as well
as optionally the image_data itself
:param image_meta: Mapping of information about the
:param image_meta: Optional Mapping of information about the
image
:param image_data: Optional string of raw image data
or file-like object that can be
used to read the image data
:retval The newly-stored image's metadata.
"""
if not image_data and 'location' not in image_meta.keys():
raise exception.Invalid("You must either specify a location "
"for the image or supply the actual "
"image data when adding an image to "
"Glance")
if image_meta is None:
image_meta = {}

if image_data:
if hasattr(image_data, 'read'):
# TODO(jaypipes): This is far from efficient. Implement
Expand All @@ -242,17 +240,22 @@ def add_image(self, image_meta, image_data=None):

res = self.do_request("POST", "/images", body, headers)
data = json.loads(res.read())
return data['image']['id']
return data['image']

def update_image(self, image_id, image_metadata):
def update_image(self, image_id, image_meta=None, image_data=None):
"""
Updates Glance's information about an image
"""
if 'image' not in image_metadata.keys():
image_metadata = dict(image=image_metadata)
body = json.dumps(image_metadata)
self.do_request("PUT", "/images/%s" % image_id, body)
return True
if image_meta:
if 'image' not in image_meta:
image_meta = dict(image=image_meta)

headers = util.image_meta_to_http_headers(image_meta or {})

body = image_data
res = self.do_request("PUT", "/images/%s" % image_id, body, headers)
data = json.loads(res.read())
return data['image']

def delete_image(self, image_id):
"""
Expand Down
3 changes: 1 addition & 2 deletions glance/common/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -141,8 +141,7 @@ def generate_uid(topic, size=8):

def generate_mac():
mac = [0x02, 0x16, 0x3e, random.randint(0x00, 0x7f),
random.randint(0x00, 0xff), random.randint(0x00, 0xff)
]
random.randint(0x00, 0xff), random.randint(0x00, 0xff)]
return ':'.join(map(lambda x: "%02x" % x, mac))


Expand Down
8 changes: 6 additions & 2 deletions glance/registry/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,9 +93,13 @@ def update_image(self, image_id, image_metadata):
"""
if 'image' not in image_metadata.keys():
image_metadata = dict(image=image_metadata)

body = json.dumps(image_metadata)
self.do_request("PUT", "/images/%s" % image_id, body)
return True

res = self.do_request("PUT", "/images/%s" % image_id, body)
data = json.loads(res.read())
image = data['image']
return image

def delete_image(self, image_id):
"""
Expand Down
32 changes: 17 additions & 15 deletions glance/registry/db/sqlalchemy/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,31 +65,31 @@ def image_destroy(_context, image_id):
def image_get(context, image_id):
session = get_session()
try:
return session.query(models.Image
).options(joinedload(models.Image.properties)
).filter_by(deleted=_deleted(context)
).filter_by(id=image_id
).one()
return session.query(models.Image).\
options(joinedload(models.Image.properties)).\
filter_by(deleted=_deleted(context)).\
filter_by(id=image_id).\
one()
except exc.NoResultFound:
new_exc = exception.NotFound("No model for id %s" % image_id)
raise new_exc.__class__, new_exc, sys.exc_info()[2]


def image_get_all(context):
session = get_session()
return session.query(models.Image
).options(joinedload(models.Image.properties)
).filter_by(deleted=_deleted(context)
).all()
return session.query(models.Image).\
options(joinedload(models.Image.properties)).\
filter_by(deleted=_deleted(context)).\
all()


def image_get_all_public(context, public):
session = get_session()
return session.query(models.Image
).options(joinedload(models.Image.properties)
).filter_by(deleted=_deleted(context)
).filter_by(is_public=public
).all()
return session.query(models.Image).\
options(joinedload(models.Image.properties)).\
filter_by(deleted=_deleted(context)).\
filter_by(is_public=public).\
all()


def image_get_by_str(context, str_id):
Expand Down Expand Up @@ -129,7 +129,9 @@ def _image_update(_context, values, image_id):
with session.begin():
_drop_protected_attrs(models.Image, values)

values['size'] = int(values['size'])
if 'size' in values:
values['size'] = int(values['size'])

values['is_public'] = bool(values.get('is_public', False))
properties = values.pop('properties', {})

Expand Down
22 changes: 11 additions & 11 deletions glance/registry/db/sqlalchemy/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,29 +58,29 @@ def all(cls, session=None, deleted=False):
"""Get all objects of this type"""
if not session:
session = get_session()
return session.query(cls
).filter_by(deleted=deleted
).all()
return session.query(cls).\
filter_by(deleted=deleted).\
all()

@classmethod
def count(cls, session=None, deleted=False):
"""Count objects of this type"""
if not session:
session = get_session()
return session.query(cls
).filter_by(deleted=deleted
).count()
return session.query(cls).\
filter_by(deleted=deleted).\
count()

@classmethod
def find(cls, obj_id, session=None, deleted=False):
"""Find object by id"""
if not session:
session = get_session()
try:
return session.query(cls
).filter_by(id=obj_id
).filter_by(deleted=deleted
).one()
return session.query(cls).\
filter_by(id=obj_id).\
filter_by(deleted=deleted).\
one()
except exc.NoResultFound:
new_exc = exception.NotFound("No model for id %s" % obj_id)
raise new_exc.__class__, new_exc, sys.exc_info()[2]
Expand Down Expand Up @@ -160,7 +160,7 @@ def validate_type(self, key, type):

@validates('status')
def validate_status(self, key, status):
if not status in ('available', 'pending', 'disabled'):
if not status in ('active', 'queued', 'killed', 'saving'):
raise exception.Invalid("Invalid status '%s' for image." % status)
return status

Expand Down
2 changes: 1 addition & 1 deletion glance/registry/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ def create(self, req):
image_data = json.loads(req.body)['image']

# Ensure the image has a status set
image_data.setdefault('status', 'available')
image_data.setdefault('status', 'active')

context = None
try:
Expand Down
Loading

0 comments on commit 1f4a644

Please sign in to comment.