Merge yagni-db-adapter and resolve conflicts.

This commit is contained in:
jaypipes@gmail.com 2011-01-31 13:40:08 -05:00
commit 8cd4bc1899
7 changed files with 148 additions and 321 deletions

View File

@ -21,11 +21,7 @@
DB abstraction for Nova and Glance
"""
from glance.registry.db.api import *
from glance.registry.db import models
# attributes common to all models
BASE_MODEL_ATTRS = set(['id', 'created_at', 'updated_at', 'deleted_at',
'deleted'])
IMAGE_ATTRS = BASE_MODEL_ATTRS | set(['name', 'type', 'status', 'size',
'is_public', 'location'])
models.register_models()

View File

@ -21,46 +21,27 @@
Defines interface for DB access
"""
from sqlalchemy.orm import joinedload
from glance.common import exception
from glance.common import utils
from glance.registry.db.sqlalchemy import api
from glance.common.db.sqlalchemy.session import get_session
from glance.registry.db import models
IMPL = api
# attributes common to all models
BASE_MODEL_ATTRS = set(['id', 'created_at', 'updated_at', 'deleted_at',
'deleted'])
IMAGE_ATTRS = BASE_MODEL_ATTRS | set(['name', 'type', 'status', 'size',
'is_public', 'location'])
###################
def image_create(context, values):
"""Create an image from the values dictionary."""
return IMPL.image_create(context, values)
def image_destroy(context, image_id):
"""Destroy the image or raise if it does not exist."""
return IMPL.image_destroy(context, image_id)
def image_get(context, image_id):
"""Get an image or raise if it does not exist."""
return IMPL.image_get(context, image_id)
def image_get_all(context):
"""Get all images."""
return IMPL.image_get_all(context)
def image_get_all_public(context, public=True):
"""Get all public images."""
return IMPL.image_get_all_public(context, public=public)
def image_get_by_str(context, str_id):
"""Get an image by string id."""
return IMPL.image_get_by_str(context, str_id)
return _image_update(context, values, None)
def image_update(context, image_id, values):
@ -69,20 +50,131 @@ def image_update(context, image_id, values):
Raises NotFound if image does not exist.
"""
return IMPL.image_update(context, image_id, values)
return _image_update(context, values, image_id)
###################
def image_destroy(context, image_id):
"""Destroy the image or raise if it does not exist."""
session = get_session()
with session.begin():
image_ref = image_get(context, image_id, session=session)
image_ref.delete(session=session)
def image_file_create(context, values):
"""Create an image file from the values dictionary."""
return IMPL.image_file_create(context, values)
def image_get(context, image_id, session=None):
"""Get an image or raise if it does not exist."""
session = session or get_session()
try:
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_public(context):
"""Get all public images."""
session = get_session()
return session.query(models.Image).\
options(joinedload(models.Image.properties)).\
filter_by(deleted=_deleted(context)).\
filter_by(is_public=True).\
all()
def image_property_create(context, values):
"""Create an image property from the values dictionary."""
return IMPL.image_property_create(context, values)
def _drop_protected_attrs(model_class, values):
"""Removed protected attributes from values dictionary using the models
__protected_attributes__ field.
"""
for attr in model_class.__protected_attributes__:
if attr in values:
del values[attr]
def _image_update(context, values, image_id):
"""Used internally by image_create and image_update
:param context: Request context
:param values: A dict of attributes to set
:param image_id: If None, create the image, otherwise, find and update it
"""
session = get_session()
with session.begin():
_drop_protected_attrs(models.Image, values)
if 'size' in values:
values['size'] = int(values['size'])
values['is_public'] = bool(values.get('is_public', False))
properties = values.pop('properties', {})
if image_id:
image_ref = image_get(context, image_id, session=session)
else:
image_ref = models.Image()
image_ref.update(values)
image_ref.save(session=session)
_set_properties_for_image(context, image_ref, properties, session)
return image_get(context, image_ref.id)
def _set_properties_for_image(context, image_ref, properties, session=None):
"""
Create or update a set of image_properties for a given image
:param context: Request context
:param image_ref: An Image object
:param properties: A dict of properties to set
:param session: A SQLAlchemy session to use (if present)
"""
orig_properties = {}
for prop_ref in image_ref.properties:
orig_properties[prop_ref.key] = prop_ref
for key, value in properties.iteritems():
prop_values = {'image_id': image_ref.id,
'key': key,
'value': value}
if key in orig_properties:
prop_ref = orig_properties[key]
image_property_update(context, prop_ref, prop_values,
session=session)
else:
image_property_create(context, prop_values, session=session)
def image_property_create(context, values, session=None):
"""Create an ImageProperty object"""
prop_ref = models.ImageProperty()
return _image_property_update(context, prop_ref, values, session=session)
def image_property_update(context, prop_ref, values, session=None):
"""Update an ImageProperty object"""
return _image_property_update(context, prop_ref, values, session=session)
def _image_property_update(context, prop_ref, values, session=None):
"""Used internally by image_property_create and image_property_update
"""
_drop_protected_attrs(models.ImageProperty, values)
prop_ref.update(values)
prop_ref.save(session=session)
return prop_ref
# pylint: disable-msg=C0111
def _deleted(context):
"""Calculates whether to include deleted objects based on context.
Currently just looks for a flag called deleted in the context dict.
"""
if not hasattr(context, 'get'):
return False
return context.get('deleted', False)

View File

@ -30,18 +30,15 @@ from sqlalchemy import UniqueConstraint
from sqlalchemy.ext.declarative import declarative_base
from glance.common.db.sqlalchemy.session import get_session, get_engine
from glance.common import exception
BASE = declarative_base()
#TODO(sirp): ModelBase should be moved out so Glance and Nova can share it
class ModelBase(object):
"""Base class for Nova and Glance Models"""
__table_args__ = {'mysql_engine': 'InnoDB'}
__table_initialized__ = False
__prefix__ = 'none'
__protected_attributes__ = set([
"created_at", "updated_at", "deleted_at", "deleted"])
@ -50,53 +47,9 @@ class ModelBase(object):
deleted_at = Column(DateTime)
deleted = Column(Boolean, default=False)
@classmethod
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()
@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()
@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()
except exc.NoResultFound:
new_exc = exception.NotFound("No model for id %s" % obj_id)
raise new_exc.__class__, new_exc, sys.exc_info()[2]
@classmethod
def find_by_str(cls, str_id, session=None, deleted=False):
"""Find object by str_id"""
int_id = int(str_id.rpartition('-')[2])
return cls.find(int_id, session=session, deleted=deleted)
@property
def str_id(self):
"""Get string id of object (generally prefix + '-' + id)"""
return "%s-%s" % (self.__prefix__, self.id)
def save(self, session=None):
"""Save this object"""
if not session:
session = get_session()
session = session or get_session()
session.add(self)
session.flush()
@ -138,7 +91,6 @@ class ModelBase(object):
class Image(BASE, ModelBase):
"""Represents an image in the datastore"""
__tablename__ = 'images'
__prefix__ = 'img'
id = Column(Integer, primary_key=True)
name = Column(String(255))
@ -165,7 +117,6 @@ class Image(BASE, ModelBase):
class ImageProperty(BASE, ModelBase):
"""Represents an image properties in the datastore"""
__tablename__ = 'image_properties'
__prefix__ = 'img-prop'
__table_args__ = (UniqueConstraint('image_id', 'key'), {})
id = Column(Integer, primary_key=True)

View File

@ -1,26 +0,0 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2010 United States Government as represented by the
# Administrator of the National Aeronautics and Space Administration.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""
SQLAlchemy models for glance data
"""
from glance.registry.db.sqlalchemy import models
models.register_models()

View File

@ -1,186 +0,0 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2010 United States Government as represented by the
# Administrator of the National Aeronautics and Space Administration.
# Copyright 2011 OpenStack LLC.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""
Implementation of SQLAlchemy backend
"""
import sys
from glance.common import db
from glance.common import exception
from glance.common.db.sqlalchemy.session import get_session
from glance.registry.db.sqlalchemy import models
from sqlalchemy.orm import exc
#from sqlalchemy.orm import joinedload_all
# TODO(sirp): add back eager loading
from sqlalchemy.orm import joinedload
from sqlalchemy.sql import func
# NOTE(vish): disabling docstring pylint because the docstrings are
# in the interface definition
# pylint: disable-msg=C0111
def _deleted(context):
"""Calculates whether to include deleted objects based on context.
Currently just looks for a flag called deleted in the context dict.
"""
if not hasattr(context, 'get'):
return False
return context.get('deleted', False)
###################
def image_create(_context, values):
return _image_update(_context, values, None)
def image_destroy(_context, image_id):
session = get_session()
with session.begin():
image_ref = models.Image.find(image_id, session=session)
image_ref.delete(session=session)
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()
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()
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()
def image_get_by_str(context, str_id):
return models.Image.find_by_str(str_id, deleted=_deleted(context))
def image_update(_context, image_id, values):
return _image_update(_context, values, image_id)
###################
def image_property_create(_context, values, session=None):
"""Create an ImageProperty object"""
prop_ref = models.ImageProperty()
return _image_property_update(_context, prop_ref, values, session=session)
def image_property_update(_context, prop_ref, values, session=None):
"""Update an ImageProperty object"""
return _image_property_update(_context, prop_ref, values, session=session)
def _image_property_update(_context, prop_ref, values, session=None):
"""Used internally by image_property_create and image_property_update
"""
_drop_protected_attrs(models.ImageProperty, values)
prop_ref.update(values)
prop_ref.save(session=session)
return prop_ref
def _drop_protected_attrs(model_class, values):
"""Removed protected attributes from values dictionary using the models
__protected_attributes__ field.
"""
for attr in model_class.__protected_attributes__:
if attr in values:
del values[attr]
def _image_update(_context, values, image_id):
"""Used internally by image_create and image_update
:param _context: Request context
:param values: A dict of attributes to set
:param image_id: If None, create the image, otherwise, find and update it
"""
session = get_session()
with session.begin():
_drop_protected_attrs(models.Image, values)
if 'size' in values:
values['size'] = int(values['size'])
values['is_public'] = bool(values.get('is_public', False))
properties = values.pop('properties', {})
if image_id:
image_ref = models.Image.find(image_id, session=session)
else:
image_ref = models.Image()
image_ref.update(values)
image_ref.save(session=session)
_set_properties_for_image(_context, image_ref, properties, session)
return image_get(_context, image_ref.id)
def _set_properties_for_image(_context, image_ref, properties, session=None):
"""
Create or update a set of image_properties for a given image
:param _context: Request context
:param image_ref: An Image object
:param properties: A dict of properties to set
:param session: A SQLAlchemy session to use (if present)
"""
orig_properties = {}
for prop_ref in image_ref.properties:
orig_properties[prop_ref.key] = prop_ref
for key, value in properties.iteritems():
prop_values = {'image_id': image_ref.id,
'key': key,
'value': value}
if key in orig_properties:
prop_ref = orig_properties[key]
image_property_update(_context, prop_ref, prop_values,
session=session)
else:
image_property_create(_context, prop_values, session=session)

View File

@ -24,7 +24,7 @@ from webob import exc
from glance.common import wsgi
from glance.common import exception
import glance.registry.db as db
from glance.registry.db import api as db_api
class Controller(wsgi.Controller):
@ -46,7 +46,7 @@ class Controller(wsgi.Controller):
{'id': image_id, 'name': image_name}
"""
images = db.image_get_all_public(None)
images = db_api.image_get_all_public(None)
image_dicts = [dict(id=i['id'],
name=i['name'],
type=i['type'],
@ -65,14 +65,14 @@ class Controller(wsgi.Controller):
all image model fields.
"""
images = db.image_get_all_public(None)
images = db_api.image_get_all_public(None)
image_dicts = [make_image_dict(i) for i in images]
return dict(images=image_dicts)
def show(self, req, id):
"""Return data about the given image id."""
try:
image = db.image_get(None, id)
image = db_api.image_get(None, id)
except exception.NotFound:
raise exc.HTTPNotFound()
@ -90,7 +90,7 @@ class Controller(wsgi.Controller):
"""
context = None
try:
db.image_destroy(context, id)
db_api.image_destroy(context, id)
except exception.NotFound:
return exc.HTTPNotFound()
@ -113,7 +113,7 @@ class Controller(wsgi.Controller):
context = None
try:
image_data = db.image_create(context, image_data)
image_data = db_api.image_create(context, image_data)
return dict(image=make_image_dict(image_data))
except exception.Duplicate:
return exc.HTTPConflict()
@ -135,7 +135,7 @@ class Controller(wsgi.Controller):
context = None
try:
updated_image = db.image_update(context, id, image_data)
updated_image = db_api.image_update(context, id, image_data)
return dict(image=make_image_dict(updated_image))
except exception.NotFound:
return exc.HTTPNotFound()
@ -169,7 +169,7 @@ def make_image_dict(image):
properties = dict((p['key'], p['value'])
for p in image['properties'] if not p['deleted'])
image_dict = _fetch_attrs(image, db.IMAGE_ATTRS)
image_dict = _fetch_attrs(image, db_api.IMAGE_ATTRS)
image_dict['properties'] = properties
return image_dict

View File

@ -34,7 +34,7 @@ import glance.store
import glance.store.filesystem
import glance.store.http
import glance.store.swift
import glance.registry.db.sqlalchemy.api
import glance.registry.db.api
FAKE_FILESYSTEM_ROOTDIR = os.path.join('/tmp', 'glance-tests')
@ -441,18 +441,18 @@ def stub_out_registry_db_image_api(stubs):
else:
return images[0]
def image_get_all_public(self, _context, public):
def image_get_all_public(self, _context, public=True):
return [f for f in self.images
if f['is_public'] == public]
fake_datastore = FakeDatastore()
stubs.Set(glance.registry.db.sqlalchemy.api, 'image_create',
stubs.Set(glance.registry.db.api, 'image_create',
fake_datastore.image_create)
stubs.Set(glance.registry.db.sqlalchemy.api, 'image_update',
stubs.Set(glance.registry.db.api, 'image_update',
fake_datastore.image_update)
stubs.Set(glance.registry.db.sqlalchemy.api, 'image_destroy',
stubs.Set(glance.registry.db.api, 'image_destroy',
fake_datastore.image_destroy)
stubs.Set(glance.registry.db.sqlalchemy.api, 'image_get',
stubs.Set(glance.registry.db.api, 'image_get',
fake_datastore.image_get)
stubs.Set(glance.registry.db.sqlalchemy.api, 'image_get_all_public',
stubs.Set(glance.registry.db.api, 'image_get_all_public',
fake_datastore.image_get_all_public)