Merge trunk, resolve conflicts, and move import of boto into conditional block to prevent import errors when running tests and not having boto installed.
This commit is contained in:
@@ -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`
|
||||
|
@@ -18,7 +18,8 @@
|
||||
# Glance documentation build configuration file, created by
|
||||
# sphinx-quickstart on Tue May 18 13:50:15 2010.
|
||||
#
|
||||
# This file is execfile()d with the current directory set to its containing dir.
|
||||
# This file is execfile()'d with the current directory set to it's containing
|
||||
# dir.
|
||||
#
|
||||
# Note that not all possible configuration values are present in this
|
||||
# autogenerated file.
|
||||
@@ -26,7 +27,8 @@
|
||||
# All configuration values have a default; values that are commented out
|
||||
# serve to show the default.
|
||||
|
||||
import sys, os
|
||||
import os
|
||||
import sys
|
||||
|
||||
# If extensions (or modules to document with autodoc) are in another directory,
|
||||
# add these directories to sys.path here. If the directory is relative to the
|
||||
@@ -36,19 +38,25 @@ sys.path.append([os.path.abspath('../glance'),
|
||||
os.path.abspath('../bin')
|
||||
])
|
||||
|
||||
# -- General configuration -----------------------------------------------------
|
||||
# -- General configuration ---------------------------------------------------
|
||||
|
||||
# Add any Sphinx extension module names here, as strings. They can be
|
||||
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
|
||||
extensions = ['sphinx.ext.autodoc',
|
||||
'sphinx.ext.coverage',
|
||||
'sphinx.ext.ifconfig',
|
||||
'sphinx.ext.intersphinx',
|
||||
'sphinx.ext.pngmath',
|
||||
'sphinx.ext.todo']
|
||||
|
||||
# Add any Sphinx extension module names here, as strings. They can be extensions
|
||||
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
|
||||
extensions = ['sphinx.ext.autodoc', 'sphinx.ext.intersphinx', 'sphinx.ext.todo', 'sphinx.ext.coverage', 'sphinx.ext.pngmath', 'sphinx.ext.ifconfig']
|
||||
todo_include_todos = True
|
||||
|
||||
# Add any paths that contain templates here, relative to this directory.
|
||||
templates_path = []
|
||||
if os.getenv('HUDSON_PUBLISH_DOCS'):
|
||||
templates_path = ['_ga', '_templates']
|
||||
templates_path = ['_ga', '_templates']
|
||||
else:
|
||||
templates_path = ['_templates']
|
||||
templates_path = ['_templates']
|
||||
|
||||
# The suffix of source filenames.
|
||||
source_suffix = '.rst'
|
||||
@@ -89,7 +97,7 @@ release = '1.0.2'
|
||||
# for source files.
|
||||
exclude_trees = []
|
||||
|
||||
# The reST default role (used for this markup: `text`) to use for all documents.
|
||||
# The reST default role (for this markup: `text`) to use for all documents.
|
||||
#default_role = None
|
||||
|
||||
# If true, '()' will be appended to :func: etc. cross-reference text.
|
||||
@@ -110,7 +118,7 @@ pygments_style = 'sphinx'
|
||||
modindex_common_prefix = ['glance.']
|
||||
|
||||
|
||||
# -- Options for HTML output ---------------------------------------------------
|
||||
# -- Options for HTML output -------------------------------------------------
|
||||
|
||||
# The theme to use for HTML and HTML Help pages. Major themes that come with
|
||||
# Sphinx are currently 'default' and 'sphinxdoc'.
|
||||
@@ -185,7 +193,7 @@ html_static_path = ['_static']
|
||||
htmlhelp_basename = 'glancedoc'
|
||||
|
||||
|
||||
# -- Options for LaTeX output --------------------------------------------------
|
||||
# -- Options for LaTeX output ------------------------------------------------
|
||||
|
||||
# The paper size ('letter' or 'a4').
|
||||
#latex_paper_size = 'letter'
|
||||
@@ -194,7 +202,8 @@ htmlhelp_basename = 'glancedoc'
|
||||
#latex_font_size = '10pt'
|
||||
|
||||
# Grouping the document tree into LaTeX files. List of tuples
|
||||
# (source start file, target name, title, author, documentclass [howto/manual]).
|
||||
# (source start file, target name, title, author,
|
||||
# documentclass [howto/manual]).
|
||||
latex_documents = [
|
||||
('index', 'Glance.tex', u'Glance Documentation',
|
||||
u'Glance Team', 'manual'),
|
||||
@@ -221,4 +230,3 @@ latex_documents = [
|
||||
intersphinx_mapping = {'python': ('http://docs.python.org/', None),
|
||||
'nova': ('http://nova.openstack.org', None),
|
||||
'swift': ('http://swift.openstack.org', None)}
|
||||
|
||||
|
@@ -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.
|
||||
|
@@ -120,11 +120,11 @@ class BaseClient(object):
|
||||
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 "
|
||||
@@ -203,12 +203,12 @@ class Client(BaseClient):
|
||||
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
|
||||
@@ -216,11 +216,9 @@ class Client(BaseClient):
|
||||
|
||||
: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
|
||||
@@ -242,17 +240,22 @@ class Client(BaseClient):
|
||||
|
||||
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):
|
||||
"""
|
||||
|
@@ -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))
|
||||
|
||||
|
||||
|
@@ -93,9 +93,13 @@ class RegistryClient(BaseClient):
|
||||
"""
|
||||
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):
|
||||
"""
|
||||
|
@@ -65,11 +65,11 @@ 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]
|
||||
@@ -77,19 +77,19 @@ def image_get(context, image_id):
|
||||
|
||||
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):
|
||||
@@ -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', {})
|
||||
|
||||
|
@@ -58,18 +58,18 @@ class ModelBase(object):
|
||||
"""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):
|
||||
@@ -77,10 +77,10 @@ class ModelBase(object):
|
||||
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]
|
||||
@@ -160,7 +160,7 @@ class Image(BASE, ModelBase):
|
||||
|
||||
@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
|
||||
|
||||
|
@@ -106,7 +106,7 @@ class ImageController(wsgi.Controller):
|
||||
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:
|
||||
|
172
glance/server.py
172
glance/server.py
@@ -162,17 +162,96 @@ class Controller(wsgi.Controller):
|
||||
util.inject_image_meta_into_headers(res, image)
|
||||
return req.get_response(res)
|
||||
|
||||
def _reserve(self, req):
|
||||
image_meta = util.get_image_meta_from_headers(req)
|
||||
image_meta['status'] = 'queued'
|
||||
|
||||
try:
|
||||
image_meta = registry.add_image_metadata(image_meta)
|
||||
return image_meta
|
||||
except exception.Duplicate:
|
||||
msg = "An image with identifier %s already exists"\
|
||||
% image_meta['id']
|
||||
logging.error(msg)
|
||||
raise HTTPConflict(msg, request=req)
|
||||
except exception.Invalid:
|
||||
raise HTTPBadRequest()
|
||||
|
||||
def _upload(self, req, image_meta):
|
||||
content_type = req.headers.get('content-type', 'notset')
|
||||
if content_type != 'application/octet-stream':
|
||||
raise HTTPBadRequest(
|
||||
"Content-Type must be application/octet-stream")
|
||||
|
||||
image_store = req.headers.get(
|
||||
'x-image-meta-store', FLAGS.default_store)
|
||||
|
||||
store = self.get_store_or_400(req, image_store)
|
||||
|
||||
image_meta['status'] = 'saving'
|
||||
registry.update_image_metadata(image_meta['id'], image_meta)
|
||||
|
||||
try:
|
||||
location = store.add(image_meta['id'], req.body)
|
||||
return location
|
||||
except exception.Duplicate, e:
|
||||
logging.error("Error adding image to store: %s", str(e))
|
||||
raise HTTPConflict(str(e), request=req)
|
||||
|
||||
def _activate(self, req, image_meta, location):
|
||||
image_meta['location'] = location
|
||||
image_meta['status'] = 'active'
|
||||
registry.update_image_metadata(image_meta['id'], image_meta)
|
||||
|
||||
def _kill(self, req, image_meta):
|
||||
image_meta['status'] = 'killed'
|
||||
registry.update_image_metadata(image_meta['id'], image_meta)
|
||||
|
||||
def _safe_kill(self, req, image_meta):
|
||||
"""Mark image killed without raising exceptions if it fails.
|
||||
|
||||
Since _kill is meant to be called from exceptions handlers, it should
|
||||
not raise itself, rather it should just log its error.
|
||||
"""
|
||||
try:
|
||||
self._kill(req, image_meta)
|
||||
except Exception, e:
|
||||
logging.error("Unable to kill image %s: %s",
|
||||
image_meta['id'], repr(e))
|
||||
|
||||
def _upload_and_activate(self, req, image_meta):
|
||||
try:
|
||||
location = self._upload(req, image_meta)
|
||||
self._activate(req, image_meta, location)
|
||||
except Exception, e:
|
||||
# NOTE(sirp): _safe_kill uses httplib which, in turn, uses
|
||||
# Eventlet's GreenSocket. Eventlet subsequently clears exceptions
|
||||
# by calling `sys.exc_clear()`. This is why we have to `raise e`
|
||||
# instead of `raise`
|
||||
self._safe_kill(req, image_meta)
|
||||
raise e
|
||||
|
||||
def create(self, req):
|
||||
"""
|
||||
Adds a new image to Glance. The body of the request may be a
|
||||
mime-encoded image data. Metadata about the image is sent via
|
||||
HTTP Headers.
|
||||
Adds a new image to Glance. Three scenarios exist when creating an
|
||||
image:
|
||||
|
||||
If the metadata about the image does not include a location
|
||||
to find the image, or if the image location is not valid,
|
||||
the request body *must* be encoded as application/octet-stream
|
||||
and be the image data itself, otherwise an HTTPBadRequest is
|
||||
returned.
|
||||
1. If the image data is available for upload, create can be passed the
|
||||
image data as the request body and the metadata as the request
|
||||
headers. The image will initially be 'queued', during upload it
|
||||
will be in the 'saving' status, and then 'killed' or 'active'
|
||||
depending on whether the upload completed successfully.
|
||||
|
||||
2. If the image data exists somewhere else, you can pass in the source
|
||||
using the x-image-meta-location header
|
||||
|
||||
3. If the image data is not available yet, but you'd like reserve a
|
||||
spot for it, you can omit the data and a record will be created in
|
||||
the 'queued' state. This exists primarily to maintain backwards
|
||||
compatibility with OpenStack/Rackspace API semantics.
|
||||
|
||||
The request body *must* be encoded as application/octet-stream,
|
||||
otherwise an HTTPBadRequest is returned.
|
||||
|
||||
Upon a successful save of the image data and metadata, a response
|
||||
containing metadata about the image is returned, including its
|
||||
@@ -184,62 +263,16 @@ class Controller(wsgi.Controller):
|
||||
and the request body is not application/octet-stream
|
||||
image data.
|
||||
"""
|
||||
image_meta = self._reserve(req)
|
||||
|
||||
# Verify the request and headers before we generate a new id
|
||||
|
||||
image_in_body = False
|
||||
image_store = None
|
||||
header_keys = [k.lower() for k in req.headers.keys()]
|
||||
if 'x-image-meta-location' not in header_keys:
|
||||
if ('content-type' not in header_keys or
|
||||
req.headers['content-type'] != 'application/octet-stream'):
|
||||
raise HTTPBadRequest("Image location was not specified in "
|
||||
"headers and the request body was not "
|
||||
"mime-encoded as application/"
|
||||
"octet-stream.", request=req)
|
||||
else:
|
||||
if 'x-image-meta-store' in header_keys:
|
||||
image_store = req.headers['x-image-meta-store']
|
||||
image_status = 'pending' # set to available when stored...
|
||||
image_in_body = True
|
||||
if req.body:
|
||||
self._upload_and_activate(req, image_meta)
|
||||
else:
|
||||
image_location = req.headers['x-image-meta-location']
|
||||
image_store = get_store_from_location(image_location)
|
||||
image_status = 'available'
|
||||
if 'x-image-meta-location' in req.headers:
|
||||
location = req.headers['x-image-meta-location']
|
||||
self._activate(req, image_meta, location)
|
||||
|
||||
# If image is the request body, validate that the requested
|
||||
# or default store is capable of storing the image data...
|
||||
if not image_store:
|
||||
image_store = FLAGS.default_store
|
||||
if image_in_body:
|
||||
store = self.get_store_or_400(req, image_store)
|
||||
|
||||
image_meta = util.get_image_meta_from_headers(req)
|
||||
|
||||
image_meta['status'] = image_status
|
||||
image_meta['store'] = image_store
|
||||
try:
|
||||
image_meta = registry.add_image_metadata(image_meta)
|
||||
|
||||
if image_in_body:
|
||||
try:
|
||||
location = store.add(image_meta['id'], req.body)
|
||||
except exception.Duplicate, e:
|
||||
logging.error("Error adding image to store: %s", str(e))
|
||||
return HTTPConflict(str(e), request=req)
|
||||
image_meta['status'] = 'available'
|
||||
image_meta['location'] = location
|
||||
registry.update_image_metadata(image_meta['id'], image_meta)
|
||||
|
||||
return dict(image=image_meta)
|
||||
|
||||
except exception.Duplicate:
|
||||
msg = "An image with identifier %s already exists"\
|
||||
% image_meta['id']
|
||||
logging.error(msg)
|
||||
return HTTPConflict(msg, request=req)
|
||||
except exception.Invalid:
|
||||
return HTTPBadRequest()
|
||||
return dict(image=image_meta)
|
||||
|
||||
def update(self, req, id):
|
||||
"""
|
||||
@@ -248,14 +281,21 @@ class Controller(wsgi.Controller):
|
||||
:param request: The WSGI/Webob Request object
|
||||
:param id: The opaque image identifier
|
||||
|
||||
:retval Returns the updated image information as a mapping,
|
||||
:retval Returns the updated image information as a mapping
|
||||
|
||||
"""
|
||||
image = self.get_image_meta_or_404(req, id)
|
||||
orig_image_meta = self.get_image_meta_or_404(req, id)
|
||||
new_image_meta = util.get_image_meta_from_headers(req)
|
||||
|
||||
image_data = json.loads(req.body)['image']
|
||||
updated_image = registry.update_image_metadata(id, image_data)
|
||||
return dict(image=updated_image)
|
||||
if req.body and (orig_image_meta['status'] != 'queued'):
|
||||
raise HTTPConflict("Cannot upload to an unqueued image")
|
||||
|
||||
image_meta = registry.update_image_metadata(id, new_image_meta)
|
||||
|
||||
if req.body:
|
||||
self._upload_and_activate(req, image_meta)
|
||||
|
||||
return dict(image=image_meta)
|
||||
|
||||
def delete(self, req, id):
|
||||
"""
|
||||
|
@@ -14,111 +14,3 @@
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import os
|
||||
import urlparse
|
||||
|
||||
from glance.common import exception
|
||||
|
||||
|
||||
# TODO(sirp): should this be moved out to common/utils.py ?
|
||||
def _file_iter(f, size):
|
||||
"""
|
||||
Return an iterator for a file-like object
|
||||
"""
|
||||
chunk = f.read(size)
|
||||
while chunk:
|
||||
yield chunk
|
||||
chunk = f.read(size)
|
||||
|
||||
|
||||
class BackendException(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class UnsupportedBackend(BackendException):
|
||||
pass
|
||||
|
||||
|
||||
class Backend(object):
|
||||
CHUNKSIZE = 4096
|
||||
|
||||
|
||||
class FilesystemBackend(Backend):
|
||||
@classmethod
|
||||
def get(cls, parsed_uri, expected_size, opener=lambda p: open(p, "rb")):
|
||||
""" Filesystem-based backend
|
||||
|
||||
file:///path/to/file.tar.gz.0
|
||||
"""
|
||||
#FIXME: must prevent attacks using ".." and "." paths
|
||||
with opener(parsed_uri.path) as f:
|
||||
return _file_iter(f, cls.CHUNKSIZE)
|
||||
|
||||
@classmethod
|
||||
def delete(cls, parsed_uri):
|
||||
"""
|
||||
Removes a file from the filesystem backend.
|
||||
|
||||
:param parsed_uri: Parsed pieces of URI in form of::
|
||||
file:///path/to/filename.ext
|
||||
|
||||
:raises NotFound if file does not exist
|
||||
:raises NotAuthorized if cannot delete because of permissions
|
||||
"""
|
||||
fn = parsed_uri.path
|
||||
if os.path.exists(fn):
|
||||
try:
|
||||
os.unlink(fn)
|
||||
except OSError:
|
||||
raise exception.NotAuthorized("You cannot delete file %s" % fn)
|
||||
else:
|
||||
raise exception.NotFound("File %s does not exist" % fn)
|
||||
|
||||
|
||||
def get_backend_class(backend):
|
||||
"""
|
||||
Returns the backend class as designated in the
|
||||
backend name
|
||||
|
||||
:param backend: Name of backend to create
|
||||
"""
|
||||
# NOTE(sirp): avoiding circular import
|
||||
from glance.store.backends.http import HTTPBackend
|
||||
from glance.store.backends.swift import SwiftBackend
|
||||
from glance.store.backends.s3 import S3Backend
|
||||
|
||||
BACKENDS = {
|
||||
"file": FilesystemBackend,
|
||||
"http": HTTPBackend,
|
||||
"https": HTTPBackend,
|
||||
"swift": SwiftBackend,
|
||||
"s3": S3Backend
|
||||
}
|
||||
|
||||
try:
|
||||
return BACKENDS[backend]
|
||||
except KeyError:
|
||||
raise UnsupportedBackend("No backend found for '%s'" % backend)
|
||||
|
||||
|
||||
def get_from_backend(uri, **kwargs):
|
||||
"""Yields chunks of data from backend specified by uri"""
|
||||
|
||||
parsed_uri = urlparse.urlparse(uri)
|
||||
scheme = parsed_uri.scheme
|
||||
|
||||
backend_class = get_backend_class(scheme)
|
||||
|
||||
return backend_class.get(parsed_uri, **kwargs)
|
||||
|
||||
|
||||
def delete_from_backend(uri, **kwargs):
|
||||
"""Removes chunks of data from backend specified by uri"""
|
||||
|
||||
parsed_uri = urlparse.urlparse(uri)
|
||||
scheme = parsed_uri.scheme
|
||||
|
||||
backend_class = get_backend_class(scheme)
|
||||
|
||||
return backend_class.delete(parsed_uri, **kwargs)
|
||||
|
@@ -114,7 +114,6 @@ class FilesystemBackend(glance.store.Backend):
|
||||
|
||||
:retval The location that was written, with file:// scheme prepended
|
||||
"""
|
||||
|
||||
datadir = FLAGS.filesystem_store_datadir
|
||||
|
||||
if not os.path.exists(datadir):
|
||||
|
@@ -15,11 +15,9 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
"""the s3 backend adapter"""
|
||||
"""The s3 backend adapter"""
|
||||
|
||||
from __future__ import absolute_import
|
||||
import glance.store
|
||||
import boto.s3.connection
|
||||
|
||||
|
||||
class S3Backend(glance.store.Backend):
|
||||
@@ -39,6 +37,7 @@ class S3Backend(glance.store.Backend):
|
||||
if conn_class:
|
||||
pass
|
||||
else:
|
||||
import boto.s3.connection
|
||||
conn_class = boto.s3.connection.S3Connection
|
||||
|
||||
(access_key, secret_key, host, bucket, obj) = \
|
||||
|
2
setup.py
2
setup.py
@@ -56,6 +56,6 @@ setup(
|
||||
'Programming Language :: Python :: 2.6',
|
||||
'Environment :: No Input/Output (Daemon)',
|
||||
],
|
||||
install_requires=[], # removed for better compat
|
||||
install_requires=[], # removed for better compat
|
||||
scripts=['bin/glance-api',
|
||||
'bin/glance-registry'])
|
||||
|
@@ -44,7 +44,7 @@ def stub_out_http_backend(stubs):
|
||||
"""Stubs out the httplib.HTTPRequest.getresponse to return
|
||||
faked-out data instead of grabbing actual contents of a resource
|
||||
|
||||
The stubbed getresponse() returns an iterator over
|
||||
The stubbed getresponse() returns an iterator over
|
||||
the data "I am a teapot, short and stout\n"
|
||||
|
||||
:param stubs: Set of stubout stubs
|
||||
@@ -155,6 +155,7 @@ def stub_out_swift_backend(stubs):
|
||||
"""
|
||||
class FakeSwiftAuth(object):
|
||||
pass
|
||||
|
||||
class FakeSwiftConnection(object):
|
||||
pass
|
||||
|
||||
@@ -173,8 +174,8 @@ def stub_out_swift_backend(stubs):
|
||||
|
||||
def chunk_it():
|
||||
for i in xrange(0, len(cls.DATA), cls.CHUNK_SIZE):
|
||||
yield cls.DATA[i:i+cls.CHUNK_SIZE]
|
||||
|
||||
yield cls.DATA[i:i + cls.CHUNK_SIZE]
|
||||
|
||||
return chunk_it()
|
||||
|
||||
fake_swift_backend = FakeSwiftBackend()
|
||||
@@ -316,7 +317,7 @@ def stub_out_registry_db_image_api(stubs):
|
||||
FIXTURES = [
|
||||
{'id': 1,
|
||||
'name': 'fake image #1',
|
||||
'status': 'available',
|
||||
'status': 'active',
|
||||
'type': 'kernel',
|
||||
'is_public': False,
|
||||
'created_at': datetime.datetime.utcnow(),
|
||||
@@ -328,7 +329,7 @@ def stub_out_registry_db_image_api(stubs):
|
||||
'properties': []},
|
||||
{'id': 2,
|
||||
'name': 'fake image #2',
|
||||
'status': 'available',
|
||||
'status': 'active',
|
||||
'type': 'kernel',
|
||||
'is_public': True,
|
||||
'created_at': datetime.datetime.utcnow(),
|
||||
@@ -339,7 +340,7 @@ def stub_out_registry_db_image_api(stubs):
|
||||
'location': "file:///tmp/glance-tests/2",
|
||||
'properties': []}]
|
||||
|
||||
VALID_STATUSES = ('available', 'disabled', 'pending')
|
||||
VALID_STATUSES = ('active', 'killed', 'queued', 'saving')
|
||||
|
||||
def __init__(self):
|
||||
self.images = FakeDatastore.FIXTURES
|
||||
@@ -354,7 +355,7 @@ def stub_out_registry_db_image_api(stubs):
|
||||
values['id'])
|
||||
|
||||
if 'status' not in values.keys():
|
||||
values['status'] = 'available'
|
||||
values['status'] = 'active'
|
||||
else:
|
||||
if not values['status'] in self.VALID_STATUSES:
|
||||
raise exception.Invalid("Invalid status '%s' for image" %
|
||||
@@ -362,25 +363,25 @@ def stub_out_registry_db_image_api(stubs):
|
||||
|
||||
values['deleted'] = False
|
||||
values['properties'] = values.get('properties', {})
|
||||
values['created_at'] = datetime.datetime.utcnow()
|
||||
values['created_at'] = datetime.datetime.utcnow()
|
||||
values['updated_at'] = datetime.datetime.utcnow()
|
||||
values['deleted_at'] = None
|
||||
|
||||
props = []
|
||||
|
||||
if 'properties' in values.keys():
|
||||
for k,v in values['properties'].iteritems():
|
||||
for k, v in values['properties'].iteritems():
|
||||
p = {}
|
||||
p['key'] = k
|
||||
p['value'] = v
|
||||
p['deleted'] = False
|
||||
p['created_at'] = datetime.datetime.utcnow()
|
||||
p['created_at'] = datetime.datetime.utcnow()
|
||||
p['updated_at'] = datetime.datetime.utcnow()
|
||||
p['deleted_at'] = None
|
||||
props.append(p)
|
||||
|
||||
values['properties'] = props
|
||||
|
||||
|
||||
self.next_id += 1
|
||||
self.images.append(values)
|
||||
return values
|
||||
@@ -390,12 +391,12 @@ def stub_out_registry_db_image_api(stubs):
|
||||
props = []
|
||||
|
||||
if 'properties' in values.keys():
|
||||
for k,v in values['properties'].iteritems():
|
||||
for k, v in values['properties'].iteritems():
|
||||
p = {}
|
||||
p['key'] = k
|
||||
p['value'] = v
|
||||
p['deleted'] = False
|
||||
p['created_at'] = datetime.datetime.utcnow()
|
||||
p['created_at'] = datetime.datetime.utcnow()
|
||||
p['updated_at'] = datetime.datetime.utcnow()
|
||||
p['deleted_at'] = None
|
||||
props.append(p)
|
||||
|
@@ -23,7 +23,7 @@ def make_swift_image():
|
||||
|
||||
# TODO(sirp): Create a testing account, and define gflags for
|
||||
# test_swift_username and test_swift_api_key
|
||||
USERNAME = "your user name here" # fill these out for testing
|
||||
USERNAME = "your user name here" # fill these out for testing
|
||||
API_KEY = "your api key here"
|
||||
#IMAGE_CHUNKS = [("filename", 123)] # filename, size in bytes
|
||||
IMAGE_CHUNKS = [("your test chunk here", 12345)]
|
||||
@@ -40,8 +40,8 @@ def make_swift_image():
|
||||
"swift://%s:%s@auth.api.rackspacecloud.com/v1.0/cloudservers/%s"
|
||||
) % (USERNAME, API_KEY, obj)
|
||||
|
||||
db.image_file_create(None,
|
||||
db.image_file_create(None,
|
||||
dict(image_id=image.id, location=location, size=size))
|
||||
|
||||
if __name__ == "__main__":
|
||||
make_swift_image() # NOTE: uncomment if you have a username and api_key
|
||||
make_swift_image() # NOTE: uncomment if you have a username and api_key
|
||||
|
@@ -19,13 +19,13 @@
|
||||
fakehttp/socket implementation
|
||||
|
||||
- TrackerSocket: an object which masquerades as a socket and responds to
|
||||
requests in a manner consistent with a *very* stupid CloudFS tracker.
|
||||
|
||||
requests in a manner consistent with a *very* stupid CloudFS tracker.
|
||||
|
||||
- CustomHTTPConnection: an object which subclasses httplib.HTTPConnection
|
||||
in order to replace it's socket with a TrackerSocket instance.
|
||||
|
||||
The unittests each have setup methods which create freerange connection
|
||||
instances that have had their HTTPConnection instances replaced by
|
||||
The unittests each have setup methods which create freerange connection
|
||||
instances that have had their HTTPConnection instances replaced by
|
||||
intances of CustomHTTPConnection.
|
||||
"""
|
||||
|
||||
@@ -54,21 +54,23 @@ class FakeSocket(object):
|
||||
def makefile(self, mode, flags):
|
||||
return self._wbuffer
|
||||
|
||||
|
||||
class TrackerSocket(FakeSocket):
|
||||
def write(self, data):
|
||||
self._wbuffer.write(data)
|
||||
|
||||
def read(self, length=-1):
|
||||
return self._rbuffer.read(length)
|
||||
|
||||
def _create_GET_account_content(self, path, args):
|
||||
if args.has_key('format') and args['format'] == 'json':
|
||||
if 'format' in args and args['format'] == 'json':
|
||||
containers = []
|
||||
containers.append('[\n');
|
||||
containers.append('[\n')
|
||||
containers.append('{"name":"container1","count":2,"bytes":78},\n')
|
||||
containers.append('{"name":"container2","count":1,"bytes":39},\n')
|
||||
containers.append('{"name":"container3","count":3,"bytes":117}\n')
|
||||
containers.append(']\n')
|
||||
elif args.has_key('format') and args['format'] == 'xml':
|
||||
elif 'format' in args and args['format'] == 'xml':
|
||||
containers = []
|
||||
containers.append('<?xml version="1.0" encoding="UTF-8"?>\n')
|
||||
containers.append('<account name="FakeAccount">\n')
|
||||
@@ -83,18 +85,18 @@ class TrackerSocket(FakeSocket):
|
||||
'<bytes>117</bytes></container>\n')
|
||||
containers.append('</account>\n')
|
||||
else:
|
||||
containers = ['container%s\n' % i for i in range(1,4)]
|
||||
containers = ['container%s\n' % i for i in range(1, 4)]
|
||||
return ''.join(containers)
|
||||
|
||||
def _create_GET_container_content(self, path, args):
|
||||
left = 0
|
||||
right = 9
|
||||
if args.has_key('offset'):
|
||||
if 'offset' in args:
|
||||
left = int(args['offset'])
|
||||
if args.has_key('limit'):
|
||||
if 'limit' in args:
|
||||
right = left + int(args['limit'])
|
||||
|
||||
if args.has_key('format') and args['format'] == 'json':
|
||||
if 'format' in args and args['format'] == 'json':
|
||||
objects = []
|
||||
objects.append('{"name":"object1",'
|
||||
'"hash":"4281c348eaf83e70ddce0e07221c3d28",'
|
||||
@@ -137,68 +139,68 @@ class TrackerSocket(FakeSocket):
|
||||
'"content_type":"application\/octet-stream",'
|
||||
'"last_modified":"2007-03-04 20:32:17"}')
|
||||
output = '[\n%s\n]\n' % (',\n'.join(objects[left:right]))
|
||||
elif args.has_key('format') and args['format'] == 'xml':
|
||||
elif 'format' in args and args['format'] == 'xml':
|
||||
objects = []
|
||||
objects.append('<object><name>object1</name>'
|
||||
'<hash>4281c348eaf83e70ddce0e07221c3d28</hash>'
|
||||
'<bytes>14</bytes>'
|
||||
'<content_type>application/octet-stream</content_type>'
|
||||
'<last_modified>2007-03-04 20:32:17</last_modified>'
|
||||
'</object>\n')
|
||||
'<hash>4281c348eaf83e70ddce0e07221c3d28</hash>'
|
||||
'<bytes>14</bytes>'
|
||||
'<content_type>application/octet-stream</content_type>'
|
||||
'<last_modified>2007-03-04 20:32:17</last_modified>'
|
||||
'</object>\n')
|
||||
objects.append('<object><name>object2</name>'
|
||||
'<hash>b039efe731ad111bc1b0ef221c3849d0</hash>'
|
||||
'<bytes>64</bytes>'
|
||||
'<content_type>application/octet-stream</content_type>'
|
||||
'<last_modified>2007-03-04 20:32:17</last_modified>'
|
||||
'</object>\n')
|
||||
'<hash>b039efe731ad111bc1b0ef221c3849d0</hash>'
|
||||
'<bytes>64</bytes>'
|
||||
'<content_type>application/octet-stream</content_type>'
|
||||
'<last_modified>2007-03-04 20:32:17</last_modified>'
|
||||
'</object>\n')
|
||||
objects.append('<object><name>object3</name>'
|
||||
'<hash>4281c348eaf83e70ddce0e07221c3d28</hash>'
|
||||
'<bytes>14</bytes>'
|
||||
'<content_type>application/octet-stream</content_type>'
|
||||
'<last_modified>2007-03-04 20:32:17</last_modified>'
|
||||
'</object>\n')
|
||||
'<hash>4281c348eaf83e70ddce0e07221c3d28</hash>'
|
||||
'<bytes>14</bytes>'
|
||||
'<content_type>application/octet-stream</content_type>'
|
||||
'<last_modified>2007-03-04 20:32:17</last_modified>'
|
||||
'</object>\n')
|
||||
objects.append('<object><name>object4</name>'
|
||||
'<hash>b039efe731ad111bc1b0ef221c3849d0</hash>'
|
||||
'<bytes>64</bytes>'
|
||||
'<content_type>application/octet-stream</content_type>'
|
||||
'<last_modified>2007-03-04 20:32:17</last_modified>'
|
||||
'</object>\n')
|
||||
'<hash>b039efe731ad111bc1b0ef221c3849d0</hash>'
|
||||
'<bytes>64</bytes>'
|
||||
'<content_type>application/octet-stream</content_type>'
|
||||
'<last_modified>2007-03-04 20:32:17</last_modified>'
|
||||
'</object>\n')
|
||||
objects.append('<object><name>object5</name>'
|
||||
'<hash>4281c348eaf83e70ddce0e07221c3d28</hash>'
|
||||
'<bytes>14</bytes>'
|
||||
'<content_type>application/octet-stream</content_type>'
|
||||
'<last_modified>2007-03-04 20:32:17</last_modified>'
|
||||
'</object>\n')
|
||||
'<hash>4281c348eaf83e70ddce0e07221c3d28</hash>'
|
||||
'<bytes>14</bytes>'
|
||||
'<content_type>application/octet-stream</content_type>'
|
||||
'<last_modified>2007-03-04 20:32:17</last_modified>'
|
||||
'</object>\n')
|
||||
objects.append('<object><name>object6</name>'
|
||||
'<hash>b039efe731ad111bc1b0ef221c3849d0</hash>'
|
||||
'<bytes>64</bytes>'
|
||||
'<content_type>application/octet-stream</content_type>'
|
||||
'<last_modified>2007-03-04 20:32:17</last_modified>'
|
||||
'</object>\n')
|
||||
'<hash>b039efe731ad111bc1b0ef221c3849d0</hash>'
|
||||
'<bytes>64</bytes>'
|
||||
'<content_type>application/octet-stream</content_type>'
|
||||
'<last_modified>2007-03-04 20:32:17</last_modified>'
|
||||
'</object>\n')
|
||||
objects.append('<object><name>object7</name>'
|
||||
'<hash>4281c348eaf83e70ddce0e07221c3d28</hash>'
|
||||
'<bytes>14</bytes>'
|
||||
'<content_type>application/octet-stream</content_type>'
|
||||
'<last_modified>2007-03-04 20:32:17</last_modified>'
|
||||
'</object>\n')
|
||||
'<hash>4281c348eaf83e70ddce0e07221c3d28</hash>'
|
||||
'<bytes>14</bytes>'
|
||||
'<content_type>application/octet-stream</content_type>'
|
||||
'<last_modified>2007-03-04 20:32:17</last_modified>'
|
||||
'</object>\n')
|
||||
objects.append('<object><name>object8</name>'
|
||||
'<hash>b039efe731ad111bc1b0ef221c3849d0</hash>'
|
||||
'<bytes>64</bytes>'
|
||||
'<content_type>application/octet-stream</content_type>'
|
||||
'<last_modified>2007-03-04 20:32:17</last_modified>'
|
||||
'</object>\n')
|
||||
'<hash>b039efe731ad111bc1b0ef221c3849d0</hash>'
|
||||
'<bytes>64</bytes>'
|
||||
'<content_type>application/octet-stream</content_type>'
|
||||
'<last_modified>2007-03-04 20:32:17</last_modified>'
|
||||
'</object>\n')
|
||||
objects = objects[left:right]
|
||||
objects.insert(0, '<?xml version="1.0" encoding="UTF-8"?>\n')
|
||||
objects.insert(1, '<container name="test_container_1"\n')
|
||||
objects.append('</container>\n')
|
||||
output = ''.join(objects)
|
||||
else:
|
||||
objects = ['object%s\n' % i for i in range(1,9)]
|
||||
objects = ['object%s\n' % i for i in range(1, 9)]
|
||||
objects = objects[left:right]
|
||||
output = ''.join(objects)
|
||||
|
||||
# prefix/path don't make much sense given our test data
|
||||
if args.has_key('prefix') or args.has_key('path'):
|
||||
if 'prefix' in args or 'path' in args:
|
||||
pass
|
||||
return output
|
||||
|
||||
|
@@ -15,6 +15,7 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import httplib
|
||||
import json
|
||||
import unittest
|
||||
|
||||
@@ -45,7 +46,7 @@ class TestRegistryAPI(unittest.TestCase):
|
||||
def test_get_root(self):
|
||||
"""Tests that the root registry API returns "index",
|
||||
which is a list of public images
|
||||
|
||||
|
||||
"""
|
||||
fixture = {'id': 2,
|
||||
'name': 'fake image #2'}
|
||||
@@ -57,13 +58,13 @@ class TestRegistryAPI(unittest.TestCase):
|
||||
images = res_dict['images']
|
||||
self.assertEquals(len(images), 1)
|
||||
|
||||
for k,v in fixture.iteritems():
|
||||
for k, v in fixture.iteritems():
|
||||
self.assertEquals(v, images[0][k])
|
||||
|
||||
def test_get_index(self):
|
||||
"""Tests that the /images registry API returns list of
|
||||
public images
|
||||
|
||||
|
||||
"""
|
||||
fixture = {'id': 2,
|
||||
'name': 'fake image #2'}
|
||||
@@ -75,19 +76,19 @@ class TestRegistryAPI(unittest.TestCase):
|
||||
images = res_dict['images']
|
||||
self.assertEquals(len(images), 1)
|
||||
|
||||
for k,v in fixture.iteritems():
|
||||
for k, v in fixture.iteritems():
|
||||
self.assertEquals(v, images[0][k])
|
||||
|
||||
def test_get_details(self):
|
||||
"""Tests that the /images/detail registry API returns
|
||||
a mapping containing a list of detailed image information
|
||||
|
||||
|
||||
"""
|
||||
fixture = {'id': 2,
|
||||
'name': 'fake image #2',
|
||||
'is_public': True,
|
||||
'type': 'kernel',
|
||||
'status': 'available'
|
||||
'status': 'active'
|
||||
}
|
||||
req = webob.Request.blank('/images/detail')
|
||||
res = req.get_response(rserver.API())
|
||||
@@ -97,7 +98,7 @@ class TestRegistryAPI(unittest.TestCase):
|
||||
images = res_dict['images']
|
||||
self.assertEquals(len(images), 1)
|
||||
|
||||
for k,v in fixture.iteritems():
|
||||
for k, v in fixture.iteritems():
|
||||
self.assertEquals(v, images[0][k])
|
||||
|
||||
def test_create_image(self):
|
||||
@@ -108,7 +109,7 @@ class TestRegistryAPI(unittest.TestCase):
|
||||
}
|
||||
|
||||
req = webob.Request.blank('/images')
|
||||
|
||||
|
||||
req.method = 'POST'
|
||||
req.body = json.dumps(dict(image=fixture))
|
||||
|
||||
@@ -118,14 +119,14 @@ class TestRegistryAPI(unittest.TestCase):
|
||||
|
||||
res_dict = json.loads(res.body)
|
||||
|
||||
for k,v in fixture.iteritems():
|
||||
for k, v in fixture.iteritems():
|
||||
self.assertEquals(v, res_dict['image'][k])
|
||||
|
||||
# Test ID auto-assigned properly
|
||||
self.assertEquals(3, res_dict['image']['id'])
|
||||
|
||||
# Test status was updated properly
|
||||
self.assertEquals('available', res_dict['image']['status'])
|
||||
self.assertEquals('active', res_dict['image']['status'])
|
||||
|
||||
def test_create_image_with_bad_status(self):
|
||||
"""Tests proper exception is raised if a bad status is set"""
|
||||
@@ -137,7 +138,7 @@ class TestRegistryAPI(unittest.TestCase):
|
||||
}
|
||||
|
||||
req = webob.Request.blank('/images')
|
||||
|
||||
|
||||
req.method = 'POST'
|
||||
req.body = json.dumps(dict(image=fixture))
|
||||
|
||||
@@ -154,7 +155,7 @@ class TestRegistryAPI(unittest.TestCase):
|
||||
}
|
||||
|
||||
req = webob.Request.blank('/images/2')
|
||||
|
||||
|
||||
req.method = 'PUT'
|
||||
req.body = json.dumps(dict(image=fixture))
|
||||
|
||||
@@ -164,7 +165,7 @@ class TestRegistryAPI(unittest.TestCase):
|
||||
|
||||
res_dict = json.loads(res.body)
|
||||
|
||||
for k,v in fixture.iteritems():
|
||||
for k, v in fixture.iteritems():
|
||||
self.assertEquals(v, res_dict['image'][k])
|
||||
|
||||
def test_update_image_not_existing(self):
|
||||
@@ -178,7 +179,7 @@ class TestRegistryAPI(unittest.TestCase):
|
||||
}
|
||||
|
||||
req = webob.Request.blank('/images/3')
|
||||
|
||||
|
||||
req.method = 'PUT'
|
||||
req.body = json.dumps(dict(image=fixture))
|
||||
|
||||
@@ -202,7 +203,7 @@ class TestRegistryAPI(unittest.TestCase):
|
||||
|
||||
# Delete image #2
|
||||
req = webob.Request.blank('/images/2')
|
||||
|
||||
|
||||
req.method = 'DELETE'
|
||||
|
||||
res = req.get_response(rserver.API())
|
||||
@@ -223,7 +224,7 @@ class TestRegistryAPI(unittest.TestCase):
|
||||
image"""
|
||||
|
||||
req = webob.Request.blank('/images/3')
|
||||
|
||||
|
||||
req.method = 'DELETE'
|
||||
|
||||
# TODO(jaypipes): Port Nova's Fault infrastructure
|
||||
@@ -251,16 +252,19 @@ class TestGlanceAPI(unittest.TestCase):
|
||||
self.stubs.UnsetAll()
|
||||
|
||||
def test_add_image_no_location_no_image_as_body(self):
|
||||
"""Tests raises BadRequest for no body and no loc header"""
|
||||
"""Tests creates a queued image for no body and no loc header"""
|
||||
fixture_headers = {'x-image-meta-store': 'file',
|
||||
'x-image-meta-name': 'fake image #3'}
|
||||
|
||||
req = webob.Request.blank("/images")
|
||||
req.method = 'POST'
|
||||
for k,v in fixture_headers.iteritems():
|
||||
for k, v in fixture_headers.iteritems():
|
||||
req.headers[k] = v
|
||||
res = req.get_response(server.API())
|
||||
self.assertEquals(res.status_int, webob.exc.HTTPBadRequest.code)
|
||||
self.assertEquals(res.status_int, httplib.OK)
|
||||
|
||||
res_body = json.loads(res.body)['image']
|
||||
self.assertEquals('queued', res_body['status'])
|
||||
|
||||
def test_add_image_bad_store(self):
|
||||
"""Tests raises BadRequest for invalid store header"""
|
||||
@@ -269,7 +273,7 @@ class TestGlanceAPI(unittest.TestCase):
|
||||
|
||||
req = webob.Request.blank("/images")
|
||||
req.method = 'POST'
|
||||
for k,v in fixture_headers.iteritems():
|
||||
for k, v in fixture_headers.iteritems():
|
||||
req.headers[k] = v
|
||||
|
||||
req.headers['Content-Type'] = 'application/octet-stream'
|
||||
@@ -284,7 +288,7 @@ class TestGlanceAPI(unittest.TestCase):
|
||||
|
||||
req = webob.Request.blank("/images")
|
||||
req.method = 'POST'
|
||||
for k,v in fixture_headers.iteritems():
|
||||
for k, v in fixture_headers.iteritems():
|
||||
req.headers[k] = v
|
||||
|
||||
req.headers['Content-Type'] = 'application/octet-stream'
|
||||
@@ -327,7 +331,8 @@ class TestGlanceAPI(unittest.TestCase):
|
||||
req = webob.Request.blank("/images/2")
|
||||
req.method = 'GET'
|
||||
res = req.get_response(server.API())
|
||||
self.assertEquals(res.status_int, webob.exc.HTTPNotFound.code, res.body)
|
||||
self.assertEquals(res.status_int, webob.exc.HTTPNotFound.code,
|
||||
res.body)
|
||||
|
||||
def test_delete_non_exists_image(self):
|
||||
req = webob.Request.blank("/images/42")
|
||||
|
@@ -33,7 +33,7 @@ FLAGS = flags.FLAGS
|
||||
|
||||
|
||||
class TestBadClients(unittest.TestCase):
|
||||
|
||||
|
||||
"""Test exceptions raised for bad clients"""
|
||||
|
||||
def test_bad_address(self):
|
||||
@@ -69,7 +69,7 @@ class TestRegistryClient(unittest.TestCase):
|
||||
images = self.client.get_images()
|
||||
self.assertEquals(len(images), 1)
|
||||
|
||||
for k,v in fixture.iteritems():
|
||||
for k, v in fixture.iteritems():
|
||||
self.assertEquals(v, images[0][k])
|
||||
|
||||
def test_get_image_details(self):
|
||||
@@ -78,7 +78,7 @@ class TestRegistryClient(unittest.TestCase):
|
||||
'name': 'fake image #2',
|
||||
'is_public': True,
|
||||
'type': 'kernel',
|
||||
'status': 'available',
|
||||
'status': 'active',
|
||||
'size': 19,
|
||||
'location': "file:///tmp/glance-tests/2",
|
||||
'properties': {}}
|
||||
@@ -87,7 +87,7 @@ class TestRegistryClient(unittest.TestCase):
|
||||
'name': 'fake image #2',
|
||||
'is_public': True,
|
||||
'type': 'kernel',
|
||||
'status': 'available',
|
||||
'status': 'active',
|
||||
'size': 19,
|
||||
'location': "file:///tmp/glance-tests/2",
|
||||
'properties': {}}
|
||||
@@ -95,7 +95,7 @@ class TestRegistryClient(unittest.TestCase):
|
||||
images = self.client.get_images_detailed()
|
||||
self.assertEquals(len(images), 1)
|
||||
|
||||
for k,v in expected.iteritems():
|
||||
for k, v in expected.iteritems():
|
||||
self.assertEquals(v, images[0][k])
|
||||
|
||||
def test_get_image(self):
|
||||
@@ -104,7 +104,7 @@ class TestRegistryClient(unittest.TestCase):
|
||||
'name': 'fake image #2',
|
||||
'is_public': True,
|
||||
'type': 'kernel',
|
||||
'status': 'available',
|
||||
'status': 'active',
|
||||
'size': 19,
|
||||
'location': "file:///tmp/glance-tests/2",
|
||||
'properties': {}}
|
||||
@@ -113,14 +113,14 @@ class TestRegistryClient(unittest.TestCase):
|
||||
'name': 'fake image #2',
|
||||
'is_public': True,
|
||||
'type': 'kernel',
|
||||
'status': 'available',
|
||||
'status': 'active',
|
||||
'size': 19,
|
||||
'location': "file:///tmp/glance-tests/2",
|
||||
'properties': {}}
|
||||
|
||||
data = self.client.get_image(2)
|
||||
|
||||
for k,v in expected.iteritems():
|
||||
for k, v in expected.iteritems():
|
||||
self.assertEquals(v, data[k])
|
||||
|
||||
def test_get_image_non_existing(self):
|
||||
@@ -138,7 +138,7 @@ class TestRegistryClient(unittest.TestCase):
|
||||
'size': 19,
|
||||
'location': "file:///tmp/glance-tests/acct/3.gz.0",
|
||||
}
|
||||
|
||||
|
||||
new_image = self.client.add_image(fixture)
|
||||
|
||||
# Test ID auto-assigned properly
|
||||
@@ -147,12 +147,12 @@ class TestRegistryClient(unittest.TestCase):
|
||||
# Test all other attributes set
|
||||
data = self.client.get_image(3)
|
||||
|
||||
for k,v in fixture.iteritems():
|
||||
for k, v in fixture.iteritems():
|
||||
self.assertEquals(v, data[k])
|
||||
|
||||
# Test status was updated properly
|
||||
self.assertTrue('status' in data.keys())
|
||||
self.assertEquals('available', data['status'])
|
||||
self.assertEquals('active', data['status'])
|
||||
|
||||
def test_add_image_with_properties(self):
|
||||
"""Tests that we can add image metadata with properties"""
|
||||
@@ -170,18 +170,18 @@ class TestRegistryClient(unittest.TestCase):
|
||||
'location': "file:///tmp/glance-tests/2",
|
||||
'properties': {'distro': 'Ubuntu 10.04 LTS'}
|
||||
}
|
||||
|
||||
|
||||
new_image = self.client.add_image(fixture)
|
||||
|
||||
# Test ID auto-assigned properly
|
||||
self.assertEquals(3, new_image['id'])
|
||||
|
||||
for k,v in expected.iteritems():
|
||||
for k, v in expected.iteritems():
|
||||
self.assertEquals(v, new_image[k])
|
||||
|
||||
# Test status was updated properly
|
||||
self.assertTrue('status' in new_image.keys())
|
||||
self.assertEquals('available', new_image['status'])
|
||||
self.assertEquals('active', new_image['status'])
|
||||
|
||||
def test_add_image_already_exists(self):
|
||||
"""Tests proper exception is raised if image with ID already exists"""
|
||||
@@ -224,7 +224,7 @@ class TestRegistryClient(unittest.TestCase):
|
||||
# Test all other attributes set
|
||||
data = self.client.get_image(2)
|
||||
|
||||
for k,v in fixture.iteritems():
|
||||
for k, v in fixture.iteritems():
|
||||
self.assertEquals(v, data[k])
|
||||
|
||||
def test_update_image_not_existing(self):
|
||||
@@ -293,7 +293,7 @@ class TestClient(unittest.TestCase):
|
||||
'name': 'fake image #2',
|
||||
'is_public': True,
|
||||
'type': 'kernel',
|
||||
'status': 'available',
|
||||
'status': 'active',
|
||||
'size': 19,
|
||||
'location': "file:///tmp/glance-tests/2",
|
||||
'properties': {}}
|
||||
@@ -304,7 +304,7 @@ class TestClient(unittest.TestCase):
|
||||
image_data += image_chunk
|
||||
|
||||
self.assertEquals(expected_image, image_data)
|
||||
for k,v in expected_meta.iteritems():
|
||||
for k, v in expected_meta.iteritems():
|
||||
self.assertEquals(v, meta[k])
|
||||
|
||||
def test_get_image_not_existing(self):
|
||||
@@ -321,7 +321,7 @@ class TestClient(unittest.TestCase):
|
||||
images = self.client.get_images()
|
||||
self.assertEquals(len(images), 1)
|
||||
|
||||
for k,v in fixture.iteritems():
|
||||
for k, v in fixture.iteritems():
|
||||
self.assertEquals(v, images[0][k])
|
||||
|
||||
def test_get_image_details(self):
|
||||
@@ -330,7 +330,7 @@ class TestClient(unittest.TestCase):
|
||||
'name': 'fake image #2',
|
||||
'is_public': True,
|
||||
'type': 'kernel',
|
||||
'status': 'available',
|
||||
'status': 'active',
|
||||
'size': 19,
|
||||
'location': "file:///tmp/glance-tests/2",
|
||||
'properties': {}}
|
||||
@@ -339,7 +339,7 @@ class TestClient(unittest.TestCase):
|
||||
'name': 'fake image #2',
|
||||
'is_public': True,
|
||||
'type': 'kernel',
|
||||
'status': 'available',
|
||||
'status': 'active',
|
||||
'size': 19,
|
||||
'location': "file:///tmp/glance-tests/2",
|
||||
'properties': {}}
|
||||
@@ -347,7 +347,7 @@ class TestClient(unittest.TestCase):
|
||||
images = self.client.get_images_detailed()
|
||||
self.assertEquals(len(images), 1)
|
||||
|
||||
for k,v in expected.iteritems():
|
||||
for k, v in expected.iteritems():
|
||||
self.assertEquals(v, images[0][k])
|
||||
|
||||
def test_get_image_meta(self):
|
||||
@@ -356,7 +356,7 @@ class TestClient(unittest.TestCase):
|
||||
'name': 'fake image #2',
|
||||
'is_public': True,
|
||||
'type': 'kernel',
|
||||
'status': 'available',
|
||||
'status': 'active',
|
||||
'size': 19,
|
||||
'location': "file:///tmp/glance-tests/2",
|
||||
'properties': {}}
|
||||
@@ -365,14 +365,14 @@ class TestClient(unittest.TestCase):
|
||||
'name': 'fake image #2',
|
||||
'is_public': True,
|
||||
'type': 'kernel',
|
||||
'status': 'available',
|
||||
'status': 'active',
|
||||
'size': 19,
|
||||
'location': "file:///tmp/glance-tests/2",
|
||||
'properties': {}}
|
||||
|
||||
data = self.client.get_image_meta(2)
|
||||
|
||||
for k,v in expected.iteritems():
|
||||
for k, v in expected.iteritems():
|
||||
self.assertEquals(v, data[k])
|
||||
|
||||
def test_get_image_non_existing(self):
|
||||
@@ -383,15 +383,14 @@ class TestClient(unittest.TestCase):
|
||||
42)
|
||||
|
||||
def test_add_image_without_location_or_raw_data(self):
|
||||
"""Tests client throws Invalid if missing both location and raw data"""
|
||||
"""Tests client returns image as queued"""
|
||||
fixture = {'name': 'fake public image',
|
||||
'is_public': True,
|
||||
'type': 'kernel'
|
||||
}
|
||||
|
||||
self.assertRaises(exception.Invalid,
|
||||
self.client.add_image,
|
||||
fixture)
|
||||
|
||||
image_meta = self.client.add_image(fixture)
|
||||
self.assertEquals('queued', image_meta['status'])
|
||||
|
||||
def test_add_image_basic(self):
|
||||
"""Tests that we can add image metadata and returns the new id"""
|
||||
@@ -402,20 +401,21 @@ class TestClient(unittest.TestCase):
|
||||
'location': "file:///tmp/glance-tests/2",
|
||||
}
|
||||
|
||||
new_id = self.client.add_image(fixture)
|
||||
new_image = self.client.add_image(fixture)
|
||||
new_image_id = new_image['id']
|
||||
|
||||
# Test ID auto-assigned properly
|
||||
self.assertEquals(3, new_id)
|
||||
self.assertEquals(3, new_image_id)
|
||||
|
||||
# Test all other attributes set
|
||||
data = self.client.get_image_meta(3)
|
||||
|
||||
for k,v in fixture.iteritems():
|
||||
for k, v in fixture.iteritems():
|
||||
self.assertEquals(v, data[k])
|
||||
|
||||
# Test status was updated properly
|
||||
self.assertTrue('status' in data.keys())
|
||||
self.assertEquals('available', data['status'])
|
||||
self.assertEquals('active', data['status'])
|
||||
|
||||
def test_add_image_with_properties(self):
|
||||
"""Tests that we can add image metadata with properties"""
|
||||
@@ -434,20 +434,21 @@ class TestClient(unittest.TestCase):
|
||||
'properties': {'distro': 'Ubuntu 10.04 LTS'}
|
||||
}
|
||||
|
||||
new_id = self.client.add_image(fixture)
|
||||
new_image = self.client.add_image(fixture)
|
||||
new_image_id = new_image['id']
|
||||
|
||||
# Test ID auto-assigned properly
|
||||
self.assertEquals(3, new_id)
|
||||
self.assertEquals(3, new_image_id)
|
||||
|
||||
# Test all other attributes set
|
||||
data = self.client.get_image_meta(3)
|
||||
|
||||
for k,v in expected.iteritems():
|
||||
for k, v in expected.iteritems():
|
||||
self.assertEquals(v, data[k])
|
||||
|
||||
# Test status was updated properly
|
||||
self.assertTrue('status' in data.keys())
|
||||
self.assertEquals('available', data['status'])
|
||||
self.assertTrue('status' in data)
|
||||
self.assertEquals('active', data['status'])
|
||||
|
||||
def test_add_image_already_exists(self):
|
||||
"""Tests proper exception is raised if image with ID already exists"""
|
||||
@@ -474,11 +475,8 @@ class TestClient(unittest.TestCase):
|
||||
'location': "file:///tmp/glance-tests/2",
|
||||
}
|
||||
|
||||
new_id = self.client.add_image(fixture)
|
||||
|
||||
data = self.client.get_image_meta(new_id)
|
||||
|
||||
self.assertEquals(data['status'], 'available')
|
||||
new_image = self.client.add_image(fixture)
|
||||
self.assertEquals(new_image['status'], 'active')
|
||||
|
||||
def test_add_image_with_image_data_as_string(self):
|
||||
"""Tests can add image by passing image data as string"""
|
||||
@@ -491,9 +489,9 @@ class TestClient(unittest.TestCase):
|
||||
|
||||
image_data_fixture = r"chunk0000remainder"
|
||||
|
||||
new_id = self.client.add_image(fixture, image_data_fixture)
|
||||
|
||||
self.assertEquals(3, new_id)
|
||||
new_image = self.client.add_image(fixture, image_data_fixture)
|
||||
new_image_id = new_image['id']
|
||||
self.assertEquals(3, new_image_id)
|
||||
|
||||
new_meta, new_image_chunks = self.client.get_image(3)
|
||||
|
||||
@@ -502,7 +500,7 @@ class TestClient(unittest.TestCase):
|
||||
new_image_data += image_chunk
|
||||
|
||||
self.assertEquals(image_data_fixture, new_image_data)
|
||||
for k,v in fixture.iteritems():
|
||||
for k, v in fixture.iteritems():
|
||||
self.assertEquals(v, new_meta[k])
|
||||
|
||||
def test_add_image_with_image_data_as_file(self):
|
||||
@@ -525,9 +523,9 @@ class TestClient(unittest.TestCase):
|
||||
tmp_file.write(image_data_fixture)
|
||||
tmp_file.close()
|
||||
|
||||
new_id = self.client.add_image(fixture, open(tmp_image_filepath))
|
||||
|
||||
self.assertEquals(3, new_id)
|
||||
new_image = self.client.add_image(fixture, open(tmp_image_filepath))
|
||||
new_image_id = new_image['id']
|
||||
self.assertEquals(3, new_image_id)
|
||||
|
||||
if os.path.exists(tmp_image_filepath):
|
||||
os.unlink(tmp_image_filepath)
|
||||
@@ -539,7 +537,7 @@ class TestClient(unittest.TestCase):
|
||||
new_image_data += image_chunk
|
||||
|
||||
self.assertEquals(image_data_fixture, new_image_data)
|
||||
for k,v in fixture.iteritems():
|
||||
for k, v in fixture.iteritems():
|
||||
self.assertEquals(v, new_meta[k])
|
||||
|
||||
def test_add_image_with_bad_store(self):
|
||||
@@ -570,7 +568,7 @@ class TestClient(unittest.TestCase):
|
||||
# Test all other attributes set
|
||||
data = self.client.get_image_meta(2)
|
||||
|
||||
for k,v in fixture.iteritems():
|
||||
for k, v in fixture.iteritems():
|
||||
self.assertEquals(v, data[k])
|
||||
|
||||
def test_update_image_not_existing(self):
|
||||
|
@@ -38,7 +38,7 @@ class TestImageController(unittest.TestCase):
|
||||
def test_get_root(self):
|
||||
"""Tests that the root registry API returns "index",
|
||||
which is a list of public images
|
||||
|
||||
|
||||
"""
|
||||
fixture = {'id': 2,
|
||||
'name': 'fake image #2'}
|
||||
@@ -50,13 +50,13 @@ class TestImageController(unittest.TestCase):
|
||||
images = res_dict['images']
|
||||
self.assertEquals(len(images), 1)
|
||||
|
||||
for k,v in fixture.iteritems():
|
||||
for k, v in fixture.iteritems():
|
||||
self.assertEquals(v, images[0][k])
|
||||
|
||||
def test_get_index(self):
|
||||
"""Tests that the /images registry API returns list of
|
||||
public images
|
||||
|
||||
|
||||
"""
|
||||
fixture = {'id': 2,
|
||||
'name': 'fake image #2'}
|
||||
@@ -68,19 +68,19 @@ class TestImageController(unittest.TestCase):
|
||||
images = res_dict['images']
|
||||
self.assertEquals(len(images), 1)
|
||||
|
||||
for k,v in fixture.iteritems():
|
||||
for k, v in fixture.iteritems():
|
||||
self.assertEquals(v, images[0][k])
|
||||
|
||||
def test_get_details(self):
|
||||
"""Tests that the /images/detail registry API returns
|
||||
a mapping containing a list of detailed image information
|
||||
|
||||
|
||||
"""
|
||||
fixture = {'id': 2,
|
||||
'name': 'fake image #2',
|
||||
'is_public': True,
|
||||
'type': 'kernel',
|
||||
'status': 'available'
|
||||
'status': 'active'
|
||||
}
|
||||
req = webob.Request.blank('/images/detail')
|
||||
res = req.get_response(server.API())
|
||||
@@ -90,7 +90,7 @@ class TestImageController(unittest.TestCase):
|
||||
images = res_dict['images']
|
||||
self.assertEquals(len(images), 1)
|
||||
|
||||
for k,v in fixture.iteritems():
|
||||
for k, v in fixture.iteritems():
|
||||
self.assertEquals(v, images[0][k])
|
||||
|
||||
def test_create_image(self):
|
||||
@@ -101,7 +101,7 @@ class TestImageController(unittest.TestCase):
|
||||
}
|
||||
|
||||
req = webob.Request.blank('/images')
|
||||
|
||||
|
||||
req.method = 'POST'
|
||||
req.body = json.dumps(dict(image=fixture))
|
||||
|
||||
@@ -111,14 +111,14 @@ class TestImageController(unittest.TestCase):
|
||||
|
||||
res_dict = json.loads(res.body)
|
||||
|
||||
for k,v in fixture.iteritems():
|
||||
for k, v in fixture.iteritems():
|
||||
self.assertEquals(v, res_dict['image'][k])
|
||||
|
||||
# Test ID auto-assigned properly
|
||||
self.assertEquals(3, res_dict['image']['id'])
|
||||
|
||||
# Test status was updated properly
|
||||
self.assertEquals('available', res_dict['image']['status'])
|
||||
self.assertEquals('active', res_dict['image']['status'])
|
||||
|
||||
def test_create_image_with_bad_status(self):
|
||||
"""Tests proper exception is raised if a bad status is set"""
|
||||
@@ -130,7 +130,7 @@ class TestImageController(unittest.TestCase):
|
||||
}
|
||||
|
||||
req = webob.Request.blank('/images')
|
||||
|
||||
|
||||
req.method = 'POST'
|
||||
req.body = json.dumps(dict(image=fixture))
|
||||
|
||||
@@ -147,7 +147,7 @@ class TestImageController(unittest.TestCase):
|
||||
}
|
||||
|
||||
req = webob.Request.blank('/images/2')
|
||||
|
||||
|
||||
req.method = 'PUT'
|
||||
req.body = json.dumps(dict(image=fixture))
|
||||
|
||||
@@ -157,7 +157,7 @@ class TestImageController(unittest.TestCase):
|
||||
|
||||
res_dict = json.loads(res.body)
|
||||
|
||||
for k,v in fixture.iteritems():
|
||||
for k, v in fixture.iteritems():
|
||||
self.assertEquals(v, res_dict['image'][k])
|
||||
|
||||
def test_update_image_not_existing(self):
|
||||
@@ -171,7 +171,7 @@ class TestImageController(unittest.TestCase):
|
||||
}
|
||||
|
||||
req = webob.Request.blank('/images/3')
|
||||
|
||||
|
||||
req.method = 'PUT'
|
||||
req.body = json.dumps(dict(image=fixture))
|
||||
|
||||
@@ -195,7 +195,7 @@ class TestImageController(unittest.TestCase):
|
||||
|
||||
# Delete image #2
|
||||
req = webob.Request.blank('/images/2')
|
||||
|
||||
|
||||
req.method = 'DELETE'
|
||||
|
||||
res = req.get_response(server.API())
|
||||
@@ -216,7 +216,7 @@ class TestImageController(unittest.TestCase):
|
||||
image"""
|
||||
|
||||
req = webob.Request.blank('/images/3')
|
||||
|
||||
|
||||
req.method = 'DELETE'
|
||||
|
||||
# TODO(jaypipes): Port Nova's Fault infrastructure
|
||||
|
@@ -28,7 +28,9 @@ from tests import stubs
|
||||
|
||||
Backend.CHUNKSIZE = 2
|
||||
|
||||
|
||||
class TestBackend(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
"""Establish a clean test environment"""
|
||||
self.stubs = stubout.StubOutForTesting()
|
||||
@@ -67,7 +69,7 @@ class TestHTTPBackend(TestBackend):
|
||||
|
||||
def test_http_get(self):
|
||||
url = "http://netloc/path/to/file.tar.gz"
|
||||
expected_returns = ['I ', 'am', ' a', ' t', 'ea', 'po', 't,', ' s',
|
||||
expected_returns = ['I ', 'am', ' a', ' t', 'ea', 'po', 't,', ' s',
|
||||
'ho', 'rt', ' a', 'nd', ' s', 'to', 'ut', '\n']
|
||||
fetcher = get_from_backend(url,
|
||||
expected_size=8)
|
||||
@@ -77,7 +79,7 @@ class TestHTTPBackend(TestBackend):
|
||||
|
||||
def test_https_get(self):
|
||||
url = "https://netloc/path/to/file.tar.gz"
|
||||
expected_returns = ['I ', 'am', ' a', ' t', 'ea', 'po', 't,', ' s',
|
||||
expected_returns = ['I ', 'am', ' a', ' t', 'ea', 'po', 't,', ' s',
|
||||
'ho', 'rt', ' a', 'nd', ' s', 'to', 'ut', '\n']
|
||||
fetcher = get_from_backend(url,
|
||||
expected_size=8)
|
||||
@@ -113,8 +115,8 @@ class TestSwiftBackend(TestBackend):
|
||||
|
||||
def test_get(self):
|
||||
|
||||
swift_uri = "swift://user:password@localhost/container1/file.tar.gz"
|
||||
expected_returns = ['I ', 'am', ' a', ' t', 'ea', 'po', 't,', ' s',
|
||||
swift_uri = "swift://user:pass@localhost/container1/file.tar.gz"
|
||||
expected_returns = ['I ', 'am', ' a', ' t', 'ea', 'po', 't,', ' s',
|
||||
'ho', 'rt', ' a', 'nd', ' s', 'to', 'ut', '\n']
|
||||
|
||||
fetcher = get_from_backend(swift_uri,
|
||||
@@ -129,12 +131,12 @@ class TestSwiftBackend(TestBackend):
|
||||
|
||||
swift_url = "swift://localhost/container1/file.tar.gz"
|
||||
|
||||
self.assertRaises(BackendException, get_from_backend,
|
||||
self.assertRaises(BackendException, get_from_backend,
|
||||
swift_url, expected_size=21)
|
||||
|
||||
def test_url_parsing(self):
|
||||
|
||||
swift_uri = "swift://user:password@localhost/v1.0/container1/file.tar.gz"
|
||||
swift_uri = "swift://user:pass@localhost/v1.0/container1/file.tar.gz"
|
||||
|
||||
parsed_uri = urlparse.urlparse(swift_uri)
|
||||
|
||||
@@ -142,7 +144,7 @@ class TestSwiftBackend(TestBackend):
|
||||
SwiftBackend._parse_swift_tokens(parsed_uri)
|
||||
|
||||
self.assertEqual(user, 'user')
|
||||
self.assertEqual(key, 'password')
|
||||
self.assertEqual(key, 'pass')
|
||||
self.assertEqual(authurl, 'https://localhost/v1.0')
|
||||
self.assertEqual(container, 'container1')
|
||||
self.assertEqual(obj, 'file.tar.gz')
|
||||
|
@@ -30,82 +30,88 @@ import sys
|
||||
ROOT = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
|
||||
VENV = os.path.join(ROOT, '.glance-venv')
|
||||
PIP_REQUIRES = os.path.join(ROOT, 'tools', 'pip-requires')
|
||||
TWISTED_NOVA='http://nova.openstack.org/Twisted-10.0.0Nova.tar.gz'
|
||||
TWISTED_NOVA = 'http://nova.openstack.org/Twisted-10.0.0Nova.tar.gz'
|
||||
|
||||
|
||||
def die(message, *args):
|
||||
print >>sys.stderr, message % args
|
||||
sys.exit(1)
|
||||
print >>sys.stderr, message % args
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def run_command(cmd, redirect_output=True, check_exit_code=True):
|
||||
"""
|
||||
Runs a command in an out-of-process shell, returning the
|
||||
output of that command. Working directory is ROOT.
|
||||
"""
|
||||
if redirect_output:
|
||||
stdout = subprocess.PIPE
|
||||
else:
|
||||
stdout = None
|
||||
"""
|
||||
Runs a command in an out-of-process shell, returning the
|
||||
output of that command. Working directory is ROOT.
|
||||
"""
|
||||
if redirect_output:
|
||||
stdout = subprocess.PIPE
|
||||
else:
|
||||
stdout = None
|
||||
|
||||
proc = subprocess.Popen(cmd, cwd=ROOT, stdout=stdout)
|
||||
output = proc.communicate()[0]
|
||||
if check_exit_code and proc.returncode != 0:
|
||||
die('Command "%s" failed.\n%s', ' '.join(cmd), output)
|
||||
return output
|
||||
proc = subprocess.Popen(cmd, cwd=ROOT, stdout=stdout)
|
||||
output = proc.communicate()[0]
|
||||
if check_exit_code and proc.returncode != 0:
|
||||
die('Command "%s" failed.\n%s', ' '.join(cmd), output)
|
||||
return output
|
||||
|
||||
|
||||
HAS_EASY_INSTALL = bool(run_command(['which', 'easy_install'], check_exit_code=False).strip())
|
||||
HAS_VIRTUALENV = bool(run_command(['which', 'virtualenv'], check_exit_code=False).strip())
|
||||
HAS_EASY_INSTALL = bool(run_command(['which', 'easy_install'],
|
||||
check_exit_code=False).strip())
|
||||
HAS_VIRTUALENV = bool(run_command(['which', 'virtualenv'],
|
||||
check_exit_code=False).strip())
|
||||
|
||||
|
||||
def check_dependencies():
|
||||
"""Make sure virtualenv is in the path."""
|
||||
"""Make sure virtualenv is in the path."""
|
||||
|
||||
if not HAS_VIRTUALENV:
|
||||
print 'not found.'
|
||||
# Try installing it via easy_install...
|
||||
if HAS_EASY_INSTALL:
|
||||
print 'Installing virtualenv via easy_install...',
|
||||
if not run_command(['which', 'easy_install']):
|
||||
die('ERROR: virtualenv not found.\n\nGlance development requires virtualenv,'
|
||||
' please install it using your favorite package management tool')
|
||||
print 'done.'
|
||||
print 'done.'
|
||||
if not HAS_VIRTUALENV:
|
||||
print 'not found.'
|
||||
# Try installing it via easy_install...
|
||||
if HAS_EASY_INSTALL:
|
||||
print 'Installing virtualenv via easy_install...',
|
||||
if not run_command(['which', 'easy_install']):
|
||||
die('ERROR: virtualenv not found.\n\n'
|
||||
'Glance development requires virtualenv, please install'
|
||||
' it using your favorite package management tool')
|
||||
print 'done.'
|
||||
print 'done.'
|
||||
|
||||
|
||||
def create_virtualenv(venv=VENV):
|
||||
"""Creates the virtual environment and installs PIP only into the
|
||||
virtual environment
|
||||
"""
|
||||
print 'Creating venv...',
|
||||
run_command(['virtualenv', '-q', '--no-site-packages', VENV])
|
||||
print 'done.'
|
||||
print 'Installing pip in virtualenv...',
|
||||
if not run_command(['tools/with_venv.sh', 'easy_install', 'pip']).strip():
|
||||
die("Failed to install pip.")
|
||||
print 'done.'
|
||||
"""Creates the virtual environment and installs PIP only into the
|
||||
virtual environment
|
||||
"""
|
||||
print 'Creating venv...',
|
||||
run_command(['virtualenv', '-q', '--no-site-packages', VENV])
|
||||
print 'done.'
|
||||
print 'Installing pip in virtualenv...',
|
||||
if not run_command(['tools/with_venv.sh', 'easy_install', 'pip']).strip():
|
||||
die("Failed to install pip.")
|
||||
print 'done.'
|
||||
|
||||
|
||||
def install_dependencies(venv=VENV):
|
||||
print 'Installing dependencies with pip (this can take a while)...'
|
||||
print 'Installing dependencies with pip (this can take a while)...'
|
||||
|
||||
# Install greenlet by hand - just listing it in the requires file does not
|
||||
# get it in stalled in the right order
|
||||
run_command(['tools/with_venv.sh', 'pip', 'install', '-E', venv, 'greenlet'],
|
||||
redirect_output=False)
|
||||
run_command(['tools/with_venv.sh', 'pip', 'install', '-E', venv, '-r', PIP_REQUIRES],
|
||||
redirect_output=False)
|
||||
run_command(['tools/with_venv.sh', 'pip', 'install', '-E', venv, TWISTED_NOVA],
|
||||
redirect_output=False)
|
||||
# Install greenlet by hand - just listing it in the requires file does not
|
||||
# get it in stalled in the right order
|
||||
venv_tool = 'tools/with_venv.sh'
|
||||
run_command([venv_tool, 'pip', 'install', '-E', venv, 'greenlet'],
|
||||
redirect_output=False)
|
||||
run_command([venv_tool, 'pip', 'install', '-E', venv, '-r', PIP_REQUIRES],
|
||||
redirect_output=False)
|
||||
run_command([venv_tool, 'pip', 'install', '-E', venv, TWISTED_NOVA],
|
||||
redirect_output=False)
|
||||
|
||||
# Tell the virtual env how to "import glance"
|
||||
pthfile = os.path.join(venv, "lib", "python2.6", "site-packages", "glance.pth")
|
||||
f = open(pthfile, 'w')
|
||||
f.write("%s\n" % ROOT)
|
||||
# Tell the virtual env how to "import glance"
|
||||
pthfile = os.path.join(venv, "lib", "python2.6", "site-packages",
|
||||
"glance.pth")
|
||||
f = open(pthfile, 'w')
|
||||
f.write("%s\n" % ROOT)
|
||||
|
||||
|
||||
def print_help():
|
||||
help = """
|
||||
help = """
|
||||
Glance development environment setup is complete.
|
||||
|
||||
Glance development uses virtualenv to track and manage Python dependencies
|
||||
@@ -114,7 +120,7 @@ def print_help():
|
||||
To activate the Glance virtualenv for the extent of your current shell session
|
||||
you can run:
|
||||
|
||||
$ source .glance-venv/bin/activate
|
||||
$ source .glance-venv/bin/activate
|
||||
|
||||
Or, if you prefer, you can run commands in the virtualenv on a case by case
|
||||
basis by running:
|
||||
@@ -122,15 +128,15 @@ def print_help():
|
||||
$ tools/with_venv.sh <your command>
|
||||
|
||||
Also, make test will automatically use the virtualenv.
|
||||
"""
|
||||
print help
|
||||
"""
|
||||
print help
|
||||
|
||||
|
||||
def main(argv):
|
||||
check_dependencies()
|
||||
create_virtualenv()
|
||||
install_dependencies()
|
||||
print_help()
|
||||
check_dependencies()
|
||||
create_virtualenv()
|
||||
install_dependencies()
|
||||
print_help()
|
||||
|
||||
if __name__ == '__main__':
|
||||
main(sys.argv)
|
||||
main(sys.argv)
|
||||
|
Reference in New Issue
Block a user