From 0ef621d47eeea421820a2191de53dee9e83d8c44 Mon Sep 17 00:00:00 2001 From: "jaypipes@gmail.com" <> Date: Fri, 1 Oct 2010 16:06:14 -0400 Subject: Adds BaseImageService and flag to control image service loading. Adds unit test for local image service. --- nova/image/service.py | 114 ++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 102 insertions(+), 12 deletions(-) (limited to 'nova/image') diff --git a/nova/image/service.py b/nova/image/service.py index 1a7a258b7..4bceab6ee 100644 --- a/nova/image/service.py +++ b/nova/image/service.py @@ -20,34 +20,117 @@ import os.path import random import string -class ImageService(object): - """Provides storage and retrieval of disk image objects.""" +from nova import utils +from nova import flags - @staticmethod - def load(): - """Factory method to return image service.""" - #TODO(gundlach): read from config. - class_ = LocalImageService - return class_() + +FLAGS = flags.FLAGS + + +flags.DEFINE_string('glance_teller_address', '127.0.0.1', + 'IP address or URL where Glance\'s Teller service resides') +flags.DEFINE_string('glance_teller_port', '9191', + 'Port for Glance\'s Teller service') +flags.DEFINE_string('glance_parallax_address', '127.0.0.1', + 'IP address or URL where Glance\'s Parallax service resides') +flags.DEFINE_string('glance_parallax_port', '9191', + 'Port for Glance\'s Parallax service') + + +class BaseImageService(object): + + """Base class for providing image search and retrieval services""" def index(self): """ Return a dict from opaque image id to image data. """ + raise NotImplementedError def show(self, id): """ Returns a dict containing image data for the given opaque image id. """ + raise NotImplementedError + + def create(self, data): + """ + Store the image data and return the new image id. + + :raises AlreadyExists if the image already exist. + """ + raise NotImplementedError + + def update(self, image_id, data): + """Replace the contents of the given image with the new data. -class GlanceImageService(ImageService): + :raises NotFound if the image does not exist. + + """ + raise NotImplementedError + + def delete(self, image_id): + """ + Delete the given image. + + :raises NotFound if the image does not exist. + + """ + raise NotImplementedError + + +class GlanceImageService(BaseImageService): + """Provides storage and retrieval of disk image objects within Glance.""" - # TODO(gundlach): once Glance has an API, build this. - pass + def index(self): + """ + Calls out to Parallax for a list of images available + """ + raise NotImplementedError + + def show(self, id): + """ + Returns a dict containing image data for the given opaque image id. + """ + raise NotImplementedError + + def create(self, data): + """ + Store the image data and return the new image id. + + :raises AlreadyExists if the image already exist. + + """ + raise NotImplementedError + + def update(self, image_id, data): + """Replace the contents of the given image with the new data. + + :raises NotFound if the image does not exist. + + """ + raise NotImplementedError + + def delete(self, image_id): + """ + Delete the given image. + + :raises NotFound if the image does not exist. + + """ + raise NotImplementedError + + def delete_all(self): + """ + Clears out all images + """ + pass + + +class LocalImageService(BaseImageService): -class LocalImageService(ImageService): """Image service storing images to local disk.""" def __init__(self): @@ -88,3 +171,10 @@ class LocalImageService(ImageService): Delete the given image. Raises OSError if the image does not exist. """ os.unlink(self._path_to(image_id)) + + def delete_all(self): + """ + Clears out all images in local directory + """ + for f in os.listdir(self._path): + os.unlink(self._path_to(f)) -- cgit From 32bd6c198a4ed96768649f58628e22fb25a95855 Mon Sep 17 00:00:00 2001 From: "jaypipes@gmail.com" <> Date: Mon, 4 Oct 2010 16:47:08 -0400 Subject: Adds ParallaxClient and TellerClient plumbing for GlanceImageService. Adds stubs FakeParallaxClient and unit tests for LocalImageService and GlanceImageService. --- nova/image/service.py | 99 +++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 92 insertions(+), 7 deletions(-) (limited to 'nova/image') diff --git a/nova/image/service.py b/nova/image/service.py index 4bceab6ee..3b6d3b6e3 100644 --- a/nova/image/service.py +++ b/nova/image/service.py @@ -16,9 +16,14 @@ # under the License. import cPickle as pickle +import httplib +import json import os.path import random import string +import urlparse + +import webob.exc from nova import utils from nova import flags @@ -27,11 +32,11 @@ from nova import flags FLAGS = flags.FLAGS -flags.DEFINE_string('glance_teller_address', '127.0.0.1', +flags.DEFINE_string('glance_teller_address', 'http://127.0.0.1', 'IP address or URL where Glance\'s Teller service resides') flags.DEFINE_string('glance_teller_port', '9191', 'Port for Glance\'s Teller service') -flags.DEFINE_string('glance_parallax_address', '127.0.0.1', +flags.DEFINE_string('glance_parallax_address', 'http://127.0.0.1', 'IP address or URL where Glance\'s Parallax service resides') flags.DEFINE_string('glance_parallax_port', '9191', 'Port for Glance\'s Parallax service') @@ -80,21 +85,101 @@ class BaseImageService(object): raise NotImplementedError +class TellerClient(object): + + def __init__(self): + self.address = FLAGS.glance_teller_address + self.port = FLAGS.glance_teller_port + url = urlparse.urlparse(self.address) + self.netloc = url.netloc + self.connection_type = {'http': httplib.HTTPConnection, + 'https': httplib.HTTPSConnection}[url.scheme] + + +class ParallaxClient(object): + + def __init__(self): + self.address = FLAGS.glance_parallax_address + self.port = FLAGS.glance_parallax_port + url = urlparse.urlparse(self.address) + self.netloc = url.netloc + self.connection_type = {'http': httplib.HTTPConnection, + 'https': httplib.HTTPSConnection}[url.scheme] + + def get_images(self): + """ + Returns a list of image data mappings from Parallax + """ + try: + c = self.connection_type(self.netloc, self.port) + c.request("GET", "images") + res = c.getresponse() + if res.status == 200: + data = json.loads(res.read()) + return data + else: + # TODO(jaypipes): return or raise HTTP error? + return [] + finally: + c.close() + + def get_image_metadata(self, image_id): + """ + Returns a mapping of image metadata from Parallax + """ + try: + c = self.connection_type(self.netloc, self.port) + c.request("GET", "images/%s" % image_id) + res = c.getresponse() + if res.status == 200: + data = json.loads(res.read()) + return data + else: + # TODO(jaypipes): return or raise HTTP error? + return [] + finally: + c.close() + + def add_image_metadata(self, image_metadata): + """ + Tells parallax about an image's metadata + """ + pass + + def update_image_metadata(self, image_id, image_metadata): + """ + Updates Parallax's information about an image + """ + pass + + def delete_image_metadata(self, image_id): + """ + Deletes Parallax's information about an image + """ + pass + + class GlanceImageService(BaseImageService): """Provides storage and retrieval of disk image objects within Glance.""" + def __init__(self): + self.teller = TellerClient() + self.parallax = ParallaxClient() + def index(self): """ Calls out to Parallax for a list of images available """ - raise NotImplementedError + images = self.parallax.get_images() + return images def show(self, id): """ Returns a dict containing image data for the given opaque image id. """ - raise NotImplementedError + image = self.parallax.get_image_metadata(id) + return image def create(self, data): """ @@ -103,7 +188,7 @@ class GlanceImageService(BaseImageService): :raises AlreadyExists if the image already exist. """ - raise NotImplementedError + return self.parallax.add_image_metadata(data) def update(self, image_id, data): """Replace the contents of the given image with the new data. @@ -111,7 +196,7 @@ class GlanceImageService(BaseImageService): :raises NotFound if the image does not exist. """ - raise NotImplementedError + self.parallax.update_image_metadata(image_id, data) def delete(self, image_id): """ @@ -120,7 +205,7 @@ class GlanceImageService(BaseImageService): :raises NotFound if the image does not exist. """ - raise NotImplementedError + self.parallax.delete_image_metadata(image_id) def delete_all(self): """ -- cgit From bf727292794026694c37b84201172b933b41ad2d Mon Sep 17 00:00:00 2001 From: "jaypipes@gmail.com" <> Date: Mon, 4 Oct 2010 17:32:01 -0400 Subject: Update Parallax default port number to match Glance --- nova/image/service.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova/image') diff --git a/nova/image/service.py b/nova/image/service.py index 3b6d3b6e3..66be669dd 100644 --- a/nova/image/service.py +++ b/nova/image/service.py @@ -38,7 +38,7 @@ flags.DEFINE_string('glance_teller_port', '9191', 'Port for Glance\'s Teller service') flags.DEFINE_string('glance_parallax_address', 'http://127.0.0.1', 'IP address or URL where Glance\'s Parallax service resides') -flags.DEFINE_string('glance_parallax_port', '9191', +flags.DEFINE_string('glance_parallax_port', '9292', 'Port for Glance\'s Parallax service') -- cgit From b61f4ceff6ea5dbb4d9c63b9f7345c0b31785984 Mon Sep 17 00:00:00 2001 From: "jaypipes@gmail.com" <> Date: Tue, 5 Oct 2010 13:29:27 -0400 Subject: Adds unit test for calling show() on a non-existing image. Changes return from real Parallax service per sirp's recommendation for actual returned dict() values. --- nova/image/service.py | 36 +++++++++++++++++++++++++++--------- 1 file changed, 27 insertions(+), 9 deletions(-) (limited to 'nova/image') diff --git a/nova/image/service.py b/nova/image/service.py index 66be669dd..2e570e8a4 100644 --- a/nova/image/service.py +++ b/nova/image/service.py @@ -18,6 +18,7 @@ import cPickle as pickle import httplib import json +import logging import os.path import random import string @@ -27,6 +28,7 @@ import webob.exc from nova import utils from nova import flags +from nova import exception FLAGS = flags.FLAGS @@ -55,6 +57,8 @@ class BaseImageService(object): def show(self, id): """ Returns a dict containing image data for the given opaque image id. + + :raises NotFound if the image does not exist """ raise NotImplementedError @@ -115,10 +119,12 @@ class ParallaxClient(object): c.request("GET", "images") res = c.getresponse() if res.status == 200: - data = json.loads(res.read()) + # Parallax returns a JSONified dict(images=image_list) + data = json.loads(res.read())['images'] return data else: - # TODO(jaypipes): return or raise HTTP error? + logging.warn("Parallax returned HTTP error %d from " + "request for /images", res.status_int) return [] finally: c.close() @@ -132,11 +138,12 @@ class ParallaxClient(object): c.request("GET", "images/%s" % image_id) res = c.getresponse() if res.status == 200: - data = json.loads(res.read()) + # Parallax returns a JSONified dict(image=image_info) + data = json.loads(res.read())['image'] return data else: - # TODO(jaypipes): return or raise HTTP error? - return [] + # TODO(jaypipes): log the error? + return None finally: c.close() @@ -179,7 +186,9 @@ class GlanceImageService(BaseImageService): Returns a dict containing image data for the given opaque image id. """ image = self.parallax.get_image_metadata(id) - return image + if image: + return image + raise exception.NotFound def create(self, data): """ @@ -236,7 +245,10 @@ class LocalImageService(BaseImageService): return [ self.show(id) for id in self._ids() ] def show(self, id): - return pickle.load(open(self._path_to(id))) + try: + return pickle.load(open(self._path_to(id))) + except IOError: + raise exception.NotFound def create(self, data): """ @@ -249,13 +261,19 @@ class LocalImageService(BaseImageService): def update(self, image_id, data): """Replace the contents of the given image with the new data.""" - pickle.dump(data, open(self._path_to(image_id), 'w')) + try: + pickle.dump(data, open(self._path_to(image_id), 'w')) + except IOError: + raise exception.NotFound def delete(self, image_id): """ Delete the given image. Raises OSError if the image does not exist. """ - os.unlink(self._path_to(image_id)) + try: + os.unlink(self._path_to(image_id)) + except IOError: + raise exception.NotFound def delete_all(self): """ -- cgit