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,11 +38,17 @@ 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.
|
||||
@@ -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)
|
||||
|
||||
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:
|
||||
|
170
glance/server.py
170
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,63 +263,17 @@ 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)
|
||||
if req.body:
|
||||
self._upload_and_activate(req, image_meta)
|
||||
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
|
||||
else:
|
||||
image_location = req.headers['x-image-meta-location']
|
||||
image_store = get_store_from_location(image_location)
|
||||
image_status = 'available'
|
||||
|
||||
# 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)
|
||||
if 'x-image-meta-location' in req.headers:
|
||||
location = req.headers['x-image-meta-location']
|
||||
self._activate(req, image_meta, location)
|
||||
|
||||
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()
|
||||
|
||||
def update(self, req, id):
|
||||
"""
|
||||
Updates an existing image with the registry.
|
||||
@@ -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) = \
|
||||
|
@@ -155,6 +155,7 @@ def stub_out_swift_backend(stubs):
|
||||
"""
|
||||
class FakeSwiftAuth(object):
|
||||
pass
|
||||
|
||||
class FakeSwiftConnection(object):
|
||||
pass
|
||||
|
||||
@@ -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" %
|
||||
|
@@ -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')
|
||||
@@ -89,12 +91,12 @@ class TrackerSocket(FakeSocket):
|
||||
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,7 +139,7 @@ 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>'
|
||||
@@ -198,7 +200,7 @@ class TrackerSocket(FakeSocket):
|
||||
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
|
||||
|
||||
@@ -87,7 +88,7 @@ class TestRegistryAPI(unittest.TestCase):
|
||||
'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())
|
||||
@@ -125,7 +126,7 @@ class TestRegistryAPI(unittest.TestCase):
|
||||
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"""
|
||||
@@ -251,7 +252,7 @@ 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'}
|
||||
|
||||
@@ -260,7 +261,10 @@ class TestGlanceAPI(unittest.TestCase):
|
||||
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"""
|
||||
@@ -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")
|
||||
|
@@ -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': {}}
|
||||
@@ -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,7 +113,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': {}}
|
||||
@@ -152,7 +152,7 @@ class TestRegistryClient(unittest.TestCase):
|
||||
|
||||
# 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"""
|
||||
@@ -181,7 +181,7 @@ class TestRegistryClient(unittest.TestCase):
|
||||
|
||||
# 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"""
|
||||
@@ -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': {}}
|
||||
@@ -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': {}}
|
||||
@@ -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,7 +365,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': {}}
|
||||
@@ -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,10 +401,11 @@ 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)
|
||||
@@ -415,7 +415,7 @@ class TestClient(unittest.TestCase):
|
||||
|
||||
# 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,10 +434,11 @@ 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)
|
||||
@@ -446,8 +447,8 @@ class TestClient(unittest.TestCase):
|
||||
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)
|
||||
|
||||
@@ -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)
|
||||
|
@@ -80,7 +80,7 @@ class TestImageController(unittest.TestCase):
|
||||
'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())
|
||||
@@ -118,7 +118,7 @@ class TestImageController(unittest.TestCase):
|
||||
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"""
|
||||
|
@@ -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()
|
||||
@@ -113,7 +115,7 @@ class TestSwiftBackend(TestBackend):
|
||||
|
||||
def test_get(self):
|
||||
|
||||
swift_uri = "swift://user:password@localhost/container1/file.tar.gz"
|
||||
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']
|
||||
|
||||
@@ -134,7 +136,7 @@ class TestSwiftBackend(TestBackend):
|
||||
|
||||
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')
|
||||
|
@@ -32,6 +32,7 @@ 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'
|
||||
|
||||
|
||||
def die(message, *args):
|
||||
print >>sys.stderr, message % args
|
||||
sys.exit(1)
|
||||
@@ -54,8 +55,10 @@ def run_command(cmd, redirect_output=True, check_exit_code=True):
|
||||
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():
|
||||
@@ -67,8 +70,9 @@ def check_dependencies():
|
||||
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')
|
||||
die('ERROR: virtualenv not found.\n\n'
|
||||
'Glance development requires virtualenv, please install'
|
||||
' it using your favorite package management tool')
|
||||
print 'done.'
|
||||
print 'done.'
|
||||
|
||||
@@ -91,15 +95,17 @@ def install_dependencies(venv=VENV):
|
||||
|
||||
# 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'],
|
||||
venv_tool = 'tools/with_venv.sh'
|
||||
run_command([venv_tool, 'pip', 'install', '-E', venv, 'greenlet'],
|
||||
redirect_output=False)
|
||||
run_command(['tools/with_venv.sh', 'pip', 'install', '-E', venv, '-r', PIP_REQUIRES],
|
||||
run_command([venv_tool, 'pip', 'install', '-E', venv, '-r', PIP_REQUIRES],
|
||||
redirect_output=False)
|
||||
run_command(['tools/with_venv.sh', 'pip', 'install', '-E', venv, TWISTED_NOVA],
|
||||
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")
|
||||
pthfile = os.path.join(venv, "lib", "python2.6", "site-packages",
|
||||
"glance.pth")
|
||||
f = open(pthfile, 'w')
|
||||
f.write("%s\n" % ROOT)
|
||||
|
||||
|
Reference in New Issue
Block a user