diff options
| author | Soren Hansen <soren.hansen@rackspace.com> | 2010-10-12 07:21:25 +0200 |
|---|---|---|
| committer | Soren Hansen <soren.hansen@rackspace.com> | 2010-10-12 07:21:25 +0200 |
| commit | e8a1cb4dd50b20359a9389c79564310a5d35c7bc (patch) | |
| tree | 7b86cbec8c1acaba097749b2db6d56eb8ca39f0c | |
| parent | d13d4343435d1e6f597c480c3c8d13ba6b47796f (diff) | |
| parent | ed5d6ca32e2996e8218a5e6d70b9952619ef6564 (diff) | |
| download | nova-e8a1cb4dd50b20359a9389c79564310a5d35c7bc.tar.gz nova-e8a1cb4dd50b20359a9389c79564310a5d35c7bc.tar.xz nova-e8a1cb4dd50b20359a9389c79564310a5d35c7bc.zip | |
Merge trunk.
| -rw-r--r-- | nova/api/__init__.py | 30 | ||||
| -rw-r--r-- | nova/api/cloud.py | 2 | ||||
| -rw-r--r-- | nova/api/ec2/cloud.py | 61 | ||||
| -rw-r--r-- | nova/api/ec2/images.py | 3 | ||||
| -rw-r--r-- | nova/api/openstack/__init__.py (renamed from nova/api/rackspace/__init__.py) | 26 | ||||
| -rw-r--r-- | nova/api/openstack/_id_translator.py (renamed from nova/api/rackspace/_id_translator.py) | 0 | ||||
| -rw-r--r-- | nova/api/openstack/auth.py (renamed from nova/api/rackspace/auth.py) | 4 | ||||
| -rw-r--r-- | nova/api/openstack/backup_schedules.py (renamed from nova/api/rackspace/backup_schedules.py) | 3 | ||||
| -rw-r--r-- | nova/api/openstack/context.py (renamed from nova/api/rackspace/context.py) | 0 | ||||
| -rw-r--r-- | nova/api/openstack/faults.py (renamed from nova/api/rackspace/faults.py) | 0 | ||||
| -rw-r--r-- | nova/api/openstack/flavors.py (renamed from nova/api/rackspace/flavors.py) | 8 | ||||
| -rw-r--r-- | nova/api/openstack/images.py (renamed from nova/api/rackspace/images.py) | 23 | ||||
| -rw-r--r-- | nova/api/openstack/notes.txt (renamed from nova/api/rackspace/notes.txt) | 4 | ||||
| -rw-r--r-- | nova/api/openstack/ratelimiting/__init__.py (renamed from nova/api/rackspace/ratelimiting/__init__.py) | 0 | ||||
| -rw-r--r-- | nova/api/openstack/servers.py (renamed from nova/api/rackspace/servers.py) | 73 | ||||
| -rw-r--r-- | nova/api/openstack/sharedipgroups.py (renamed from nova/api/rackspace/sharedipgroups.py) | 0 | ||||
| -rw-r--r-- | nova/compute/manager.py | 6 | ||||
| -rw-r--r-- | nova/db/api.py | 4 | ||||
| -rw-r--r-- | nova/db/sqlalchemy/api.py | 28 | ||||
| -rw-r--r-- | nova/db/sqlalchemy/models.py | 4 | ||||
| -rw-r--r-- | nova/fakerabbit.py | 14 | ||||
| -rw-r--r-- | nova/flags.py | 4 | ||||
| -rw-r--r-- | nova/image/service.py | 235 | ||||
| -rw-r--r-- | nova/objectstore/image.py | 10 | ||||
| -rw-r--r-- | nova/rpc.py | 9 | ||||
| -rw-r--r-- | nova/server.py | 6 | ||||
| -rw-r--r-- | nova/tests/api/__init__.py | 10 | ||||
| -rw-r--r-- | nova/tests/api/openstack/__init__.py (renamed from nova/tests/api/rackspace/__init__.py) | 4 | ||||
| -rw-r--r-- | nova/tests/api/openstack/fakes.py | 205 | ||||
| -rw-r--r-- | nova/tests/api/openstack/test_auth.py (renamed from nova/tests/api/rackspace/test_auth.py) | 8 | ||||
| -rw-r--r-- | nova/tests/api/openstack/test_faults.py (renamed from nova/tests/api/rackspace/test_faults.py) | 2 | ||||
| -rw-r--r-- | nova/tests/api/openstack/test_flavors.py (renamed from nova/tests/api/rackspace/test_flavors.py) | 4 | ||||
| -rw-r--r-- | nova/tests/api/openstack/test_images.py | 141 | ||||
| -rw-r--r-- | nova/tests/api/openstack/test_ratelimiting.py (renamed from nova/api/rackspace/ratelimiting/tests.py) | 2 | ||||
| -rw-r--r-- | nova/tests/api/openstack/test_servers.py (renamed from nova/tests/api/rackspace/test_servers.py) | 17 | ||||
| -rw-r--r-- | nova/tests/api/openstack/test_sharedipgroups.py (renamed from nova/tests/api/rackspace/test_sharedipgroups.py) | 2 | ||||
| -rw-r--r-- | nova/tests/api/rackspace/fakes.py | 148 | ||||
| -rw-r--r-- | nova/tests/api/rackspace/test_images.py | 39 | ||||
| -rw-r--r-- | nova/tests/bundle/1mb.manifest.xml | 2 | ||||
| -rw-r--r-- | nova/tests/bundle/1mb.no_kernel_or_ramdisk.manifest.xml | 1 | ||||
| -rw-r--r-- | nova/tests/cloud_unittest.py | 34 | ||||
| -rw-r--r-- | nova/tests/objectstore_unittest.py | 29 | ||||
| -rw-r--r-- | nova/utils.py | 10 | ||||
| -rw-r--r-- | nova/virt/images.py | 1 | ||||
| -rwxr-xr-x | run_tests.sh | 25 |
45 files changed, 842 insertions, 399 deletions
diff --git a/nova/api/__init__.py b/nova/api/__init__.py index 744abd621..8ec7094d7 100644 --- a/nova/api/__init__.py +++ b/nova/api/__init__.py @@ -27,16 +27,16 @@ from nova import flags from nova import wsgi from nova.api import cloudpipe from nova.api import ec2 -from nova.api import rackspace +from nova.api import openstack from nova.api.ec2 import metadatarequesthandler -flags.DEFINE_string('rsapi_subdomain', 'rs', - 'subdomain running the RS API') +flags.DEFINE_string('osapi_subdomain', 'api', + 'subdomain running the OpenStack API') flags.DEFINE_string('ec2api_subdomain', 'ec2', 'subdomain running the EC2 API') flags.DEFINE_string('FAKE_subdomain', None, - 'set to rs or ec2 to fake the subdomain of the host for testing') + 'set to api or ec2 to fake the subdomain of the host for testing') FLAGS = flags.FLAGS @@ -44,21 +44,21 @@ class API(wsgi.Router): """Routes top-level requests to the appropriate controller.""" def __init__(self): - rsdomain = {'sub_domain': [FLAGS.rsapi_subdomain]} + osapidomain = {'sub_domain': [FLAGS.osapi_subdomain]} ec2domain = {'sub_domain': [FLAGS.ec2api_subdomain]} - # If someone wants to pretend they're hitting the RS subdomain - # on their local box, they can set FAKE_subdomain to 'rs', which - # removes subdomain restrictions from the RS routes below. - if FLAGS.FAKE_subdomain == 'rs': - rsdomain = {} + # If someone wants to pretend they're hitting the OSAPI subdomain + # on their local box, they can set FAKE_subdomain to 'api', which + # removes subdomain restrictions from the OpenStack API routes below. + if FLAGS.FAKE_subdomain == 'api': + osapidomain = {} elif FLAGS.FAKE_subdomain == 'ec2': ec2domain = {} mapper = routes.Mapper() mapper.sub_domains = True - mapper.connect("/", controller=self.rsapi_versions, - conditions=rsdomain) - mapper.connect("/v1.0/{path_info:.*}", controller=rackspace.API(), - conditions=rsdomain) + mapper.connect("/", controller=self.osapi_versions, + conditions=osapidomain) + mapper.connect("/v1.0/{path_info:.*}", controller=openstack.API(), + conditions=osapidomain) mapper.connect("/", controller=self.ec2api_versions, conditions=ec2domain) @@ -81,7 +81,7 @@ class API(wsgi.Router): super(API, self).__init__(mapper) @webob.dec.wsgify - def rsapi_versions(self, req): + def osapi_versions(self, req): """Respond to a request for all OpenStack API versions.""" response = { "versions": [ diff --git a/nova/api/cloud.py b/nova/api/cloud.py index 345677d4f..57e94a17a 100644 --- a/nova/api/cloud.py +++ b/nova/api/cloud.py @@ -34,7 +34,7 @@ def reboot(instance_id, context=None): #TODO(gundlach) not actually sure what context is used for by ec2 here -- I think we can just remove it and use None all the time. """ - instance_ref = db.instance_get_by_ec2_id(None, instance_id) + instance_ref = db.instance_get_by_internal_id(None, instance_id) host = instance_ref['host'] rpc.cast(db.queue_get_for(context, FLAGS.compute_topic, host), {"method": "reboot_instance", diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index 4cd4c78ae..555518448 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -75,6 +75,20 @@ def _gen_key(context, user_id, key_name): return {'private_key': private_key, 'fingerprint': fingerprint} +def ec2_id_to_internal_id(ec2_id): + """Convert an ec2 ID (i-[base 36 number]) to an internal id (int)""" + return int(ec2_id[2:], 36) + + +def internal_id_to_ec2_id(internal_id): + """Convert an internal ID (int) to an ec2 ID (i-[base 36 number])""" + digits = [] + while internal_id != 0: + internal_id, remainder = divmod(internal_id, 36) + digits.append('0123456789abcdefghijklmnopqrstuvwxyz'[remainder]) + return "i-%s" % ''.join(reversed(digits)) + + class CloudController(object): """ CloudController provides the critical dispatch between inbound API calls through the endpoint and messages @@ -156,7 +170,7 @@ class CloudController(object): }, 'hostname': hostname, 'instance-action': 'none', - 'instance-id': instance_ref['ec2_id'], + 'instance-id': internal_id_to_ec2_id(instance_ref['internal_id']), 'instance-type': instance_ref['instance_type'], 'local-hostname': hostname, 'local-ipv4': address, @@ -435,7 +449,9 @@ class CloudController(object): def get_console_output(self, context, instance_id, **kwargs): # instance_id is passed in as a list of instances - instance_ref = db.instance_get_by_ec2_id(context, instance_id[0]) + ec2_id = instance_id[0] + internal_id = ec2_id_to_internal_id(ec2_id) + instance_ref = db.instance_get_by_internal_id(context, internal_id) return rpc.call('%s.%s' % (FLAGS.compute_topic, instance_ref['host']), {"method": "get_console_output", @@ -515,7 +531,8 @@ class CloudController(object): raise exception.ApiError("Volume status must be available") if volume_ref['attach_status'] == "attached": raise exception.ApiError("Volume is already attached") - instance_ref = db.instance_get_by_ec2_id(context, instance_id) + internal_id = ec2_id_to_internal_id(instance_id) + instance_ref = db.instance_get_by_internal_id(context, internal_id) host = instance_ref['host'] rpc.cast(db.queue_get_for(context, FLAGS.compute_topic, host), {"method": "attach_volume", @@ -549,9 +566,11 @@ class CloudController(object): # If the instance doesn't exist anymore, # then we need to call detach blind db.volume_detached(context) + internal_id = instance_ref['internal_id'] + ec2_id = internal_id_to_ec2_id(internal_id) return {'attachTime': volume_ref['attach_time'], 'device': volume_ref['mountpoint'], - 'instanceId': instance_ref['ec2_id'], + 'instanceId': internal_id, 'requestId': context.request_id, 'status': volume_ref['attach_status'], 'volumeId': volume_ref['id']} @@ -600,7 +619,9 @@ class CloudController(object): if instance['image_id'] == FLAGS.vpn_image_id: continue i = {} - i['instanceId'] = instance['ec2_id'] + internal_id = instance['internal_id'] + ec2_id = internal_id_to_ec2_id(internal_id) + i['instanceId'] = ec2_id i['imageId'] = instance['image_id'] i['instanceState'] = { 'code': instance['state'], @@ -653,9 +674,10 @@ class CloudController(object): instance_id = None if (floating_ip_ref['fixed_ip'] and floating_ip_ref['fixed_ip']['instance']): - instance_id = floating_ip_ref['fixed_ip']['instance']['ec2_id'] + internal_id = floating_ip_ref['fixed_ip']['instance']['ec2_id'] + ec2_id = internal_id_to_ec2_id(internal_id) address_rv = {'public_ip': address, - 'instance_id': instance_id} + 'instance_id': ec2_id} if context.user.is_admin(): details = "%s (%s)" % (address_rv['instance_id'], floating_ip_ref['project_id']) @@ -687,8 +709,9 @@ class CloudController(object): "floating_address": floating_ip_ref['address']}}) return {'releaseResponse': ["Address released."]} - def associate_address(self, context, instance_id, public_ip, **kwargs): - instance_ref = db.instance_get_by_ec2_id(context, instance_id) + def associate_address(self, context, ec2_id, public_ip, **kwargs): + internal_id = ec2_id_to_internal_id(ec2_id) + instance_ref = db.instance_get_by_internal_id(context, internal_id) fixed_address = db.instance_get_fixed_address(context, instance_ref['id']) floating_ip_ref = db.floating_ip_get_by_address(context, public_ip) @@ -824,7 +847,9 @@ class CloudController(object): inst = {} inst['mac_address'] = utils.generate_mac() inst['launch_index'] = num - inst['hostname'] = instance_ref['ec2_id'] + internal_id = instance_ref['internal_id'] + ec2_id = internal_id_to_ec2_id(internal_id) + inst['hostname'] = ec2_id db.instance_update(context, inst_id, inst) address = self.network_manager.allocate_fixed_ip(context, inst_id, @@ -849,11 +874,18 @@ class CloudController(object): def terminate_instances(self, context, instance_id, **kwargs): + """Terminate each instance in instance_id, which is a list of ec2 ids. + + instance_id is a kwarg so its name cannot be modified. + """ + ec2_id_list = instance_id logging.debug("Going to start terminating instances") - for id_str in instance_id: + for id_str in ec2_id_list: + internal_id = ec2_id_to_internal_id(id_str) logging.debug("Going to try and terminate %s" % id_str) try: - instance_ref = db.instance_get_by_ec2_id(context, id_str) + instance_ref = db.instance_get_by_internal_id(context, + internal_id) except exception.NotFound: logging.warning("Instance %s was not found during terminate" % id_str) @@ -902,7 +934,7 @@ class CloudController(object): cloud.reboot(id_str, context=context) return True - def update_instance(self, context, instance_id, **kwargs): + def update_instance(self, context, ec2_id, **kwargs): updatable_fields = ['display_name', 'display_description'] changes = {} for field in updatable_fields: @@ -910,7 +942,8 @@ class CloudController(object): changes[field] = kwargs[field] if changes: db_context = {} - inst = db.instance_get_by_ec2_id(db_context, instance_id) + internal_id = ec2_id_to_internal_id(ec2_id) + inst = db.instance_get_by_internal_id(db_context, internal_id) db.instance_update(db_context, inst['id'], kwargs) return True diff --git a/nova/api/ec2/images.py b/nova/api/ec2/images.py index cb54cdda2..f0a43dad6 100644 --- a/nova/api/ec2/images.py +++ b/nova/api/ec2/images.py @@ -69,6 +69,9 @@ def list(context, filter_list=[]): optionally filtered by a list of image_id """ + if FLAGS.connection_type == 'fake': + return [{ 'imageId' : 'bar'}] + # FIXME: send along the list of only_images to check for response = conn(context).make_request( method='GET', diff --git a/nova/api/rackspace/__init__.py b/nova/api/openstack/__init__.py index 89a4693ad..5e81ba2bd 100644 --- a/nova/api/rackspace/__init__.py +++ b/nova/api/openstack/__init__.py @@ -17,7 +17,7 @@ # under the License. """ -WSGI middleware for Rackspace API controllers. +WSGI middleware for OpenStack API controllers. """ import json @@ -31,30 +31,30 @@ import webob from nova import flags from nova import utils from nova import wsgi -from nova.api.rackspace import faults -from nova.api.rackspace import backup_schedules -from nova.api.rackspace import flavors -from nova.api.rackspace import images -from nova.api.rackspace import ratelimiting -from nova.api.rackspace import servers -from nova.api.rackspace import sharedipgroups +from nova.api.openstack import faults +from nova.api.openstack import backup_schedules +from nova.api.openstack import flavors +from nova.api.openstack import images +from nova.api.openstack import ratelimiting +from nova.api.openstack import servers +from nova.api.openstack import sharedipgroups from nova.auth import manager FLAGS = flags.FLAGS flags.DEFINE_string('nova_api_auth', - 'nova.api.rackspace.auth.BasicApiAuthManager', - 'The auth mechanism to use for the Rackspace API implemenation') + 'nova.api.openstack.auth.BasicApiAuthManager', + 'The auth mechanism to use for the OpenStack API implemenation') class API(wsgi.Middleware): - """WSGI entry point for all Rackspace API requests.""" + """WSGI entry point for all OpenStack API requests.""" def __init__(self): app = AuthMiddleware(RateLimitingMiddleware(APIRouter())) super(API, self).__init__(app) class AuthMiddleware(wsgi.Middleware): - """Authorize the rackspace API request or return an HTTP Forbidden.""" + """Authorize the openstack API request or return an HTTP Forbidden.""" def __init__(self, application): self.auth_driver = utils.import_class(FLAGS.nova_api_auth)() @@ -145,7 +145,7 @@ class RateLimitingMiddleware(wsgi.Middleware): class APIRouter(wsgi.Router): """ - Routes requests on the Rackspace API to the appropriate controller + Routes requests on the OpenStack API to the appropriate controller and method. """ diff --git a/nova/api/rackspace/_id_translator.py b/nova/api/openstack/_id_translator.py index 333aa8434..333aa8434 100644 --- a/nova/api/rackspace/_id_translator.py +++ b/nova/api/openstack/_id_translator.py diff --git a/nova/api/rackspace/auth.py b/nova/api/openstack/auth.py index c45156ebd..4c909293e 100644 --- a/nova/api/rackspace/auth.py +++ b/nova/api/openstack/auth.py @@ -11,7 +11,7 @@ from nova import db from nova import flags from nova import manager from nova import utils -from nova.api.rackspace import faults +from nova.api.openstack import faults FLAGS = flags.FLAGS @@ -19,7 +19,7 @@ class Context(object): pass class BasicApiAuthManager(object): - """ Implements a somewhat rudimentary version of Rackspace Auth""" + """ Implements a somewhat rudimentary version of OpenStack Auth""" def __init__(self, host=None, db_driver=None): if not host: diff --git a/nova/api/rackspace/backup_schedules.py b/nova/api/openstack/backup_schedules.py index cb83023bc..76ad6ef87 100644 --- a/nova/api/rackspace/backup_schedules.py +++ b/nova/api/openstack/backup_schedules.py @@ -19,8 +19,7 @@ import time from webob import exc from nova import wsgi -from nova.api.rackspace import _id_translator -from nova.api.rackspace import faults +from nova.api.openstack import faults import nova.image.service class Controller(wsgi.Controller): diff --git a/nova/api/rackspace/context.py b/nova/api/openstack/context.py index 77394615b..77394615b 100644 --- a/nova/api/rackspace/context.py +++ b/nova/api/openstack/context.py diff --git a/nova/api/rackspace/faults.py b/nova/api/openstack/faults.py index 32e5c866f..32e5c866f 100644 --- a/nova/api/rackspace/faults.py +++ b/nova/api/openstack/faults.py diff --git a/nova/api/rackspace/flavors.py b/nova/api/openstack/flavors.py index 916449854..793984a5d 100644 --- a/nova/api/rackspace/flavors.py +++ b/nova/api/openstack/flavors.py @@ -17,13 +17,13 @@ from webob import exc -from nova.api.rackspace import faults +from nova.api.openstack import faults from nova.compute import instance_types from nova import wsgi -import nova.api.rackspace +import nova.api.openstack class Controller(wsgi.Controller): - """Flavor controller for the Rackspace API.""" + """Flavor controller for the OpenStack API.""" _serialization_metadata = { 'application/xml': { @@ -41,7 +41,7 @@ class Controller(wsgi.Controller): def detail(self, req): """Return all flavors in detail.""" items = [self.show(req, id)['flavor'] for id in self._all_ids()] - items = nova.api.rackspace.limited(items, req) + items = nova.api.openstack.limited(items, req) return dict(flavors=items) def show(self, req, id): diff --git a/nova/api/rackspace/images.py b/nova/api/openstack/images.py index 4a7dd489c..aa438739c 100644 --- a/nova/api/rackspace/images.py +++ b/nova/api/openstack/images.py @@ -17,11 +17,15 @@ from webob import exc +from nova import flags +from nova import utils from nova import wsgi -from nova.api.rackspace import _id_translator -import nova.api.rackspace +import nova.api.openstack import nova.image.service -from nova.api.rackspace import faults +from nova.api.openstack import faults + + +FLAGS = flags.FLAGS class Controller(wsgi.Controller): @@ -35,9 +39,7 @@ class Controller(wsgi.Controller): } def __init__(self): - self._service = nova.image.service.ImageService.load() - self._id_translator = _id_translator.RackspaceAPIIdTranslator( - "image", self._service.__class__.__name__) + self._service = utils.import_object(FLAGS.image_service) def index(self, req): """Return all public images in brief.""" @@ -47,17 +49,12 @@ class Controller(wsgi.Controller): def detail(self, req): """Return all public images in detail.""" data = self._service.index() - data = nova.api.rackspace.limited(data, req) - for img in data: - img['id'] = self._id_translator.to_rs_id(img['id']) + data = nova.api.openstack.limited(data, req) return dict(images=data) def show(self, req, id): """Return data about the given image id.""" - opaque_id = self._id_translator.from_rs_id(id) - img = self._service.show(opaque_id) - img['id'] = id - return dict(image=img) + return dict(image=self._service.show(id)) def delete(self, req, id): # Only public images are supported for now. diff --git a/nova/api/rackspace/notes.txt b/nova/api/openstack/notes.txt index e133bf5ea..2330f1002 100644 --- a/nova/api/rackspace/notes.txt +++ b/nova/api/openstack/notes.txt @@ -10,11 +10,11 @@ image ids are URIs. LocalImageService(ImageService): image ids are random strings. -RackspaceAPITranslationStore: +OpenstackAPITranslationStore: translates RS server/images/flavor/etc ids into formats required by a given ImageService strategy. -api.rackspace.images.Controller: +api.openstack.images.Controller: uses an ImageService strategy behind the scenes to do its fetching; it just converts int image id into a strategy-specific image id. diff --git a/nova/api/rackspace/ratelimiting/__init__.py b/nova/api/openstack/ratelimiting/__init__.py index f843bac0f..f843bac0f 100644 --- a/nova/api/rackspace/ratelimiting/__init__.py +++ b/nova/api/openstack/ratelimiting/__init__.py diff --git a/nova/api/rackspace/servers.py b/nova/api/openstack/servers.py index 11efd8aef..5d1ed9822 100644 --- a/nova/api/rackspace/servers.py +++ b/nova/api/openstack/servers.py @@ -25,30 +25,15 @@ from nova import rpc from nova import utils from nova import wsgi from nova.api import cloud -from nova.api.rackspace import _id_translator -from nova.api.rackspace import context -from nova.api.rackspace import faults +from nova.api.openstack import context +from nova.api.openstack import faults from nova.compute import instance_types from nova.compute import power_state -import nova.api.rackspace +import nova.api.openstack import nova.image.service FLAGS = flags.FLAGS -flags.DEFINE_string('rs_network_manager', 'nova.network.manager.FlatManager', - 'Networking for rackspace') - -def _instance_id_translator(): - """ Helper method for initializing an id translator for Rackspace instance - ids """ - return _id_translator.RackspaceAPIIdTranslator( "instance", 'nova') - -def _image_service(): - """ Helper method for initializing the image id translator """ - service = nova.image.service.ImageService.load() - return (service, _id_translator.RackspaceAPIIdTranslator( - "image", service.__class__.__name__)) - def _filter_params(inst_dict): """ Extracts all updatable parameters for a server update request """ keys = dict(name='name', admin_pass='adminPass') @@ -63,7 +48,7 @@ def _entity_list(entities): return dict(servers=entities) def _entity_detail(inst): - """ Maps everything to Rackspace-like attributes for return""" + """ Maps everything to valid attributes for return""" power_mapping = { power_state.NOSTATE: 'build', power_state.RUNNING: 'active', @@ -93,7 +78,7 @@ def _entity_inst(inst): return dict(server=dict(id=inst['id'], name=inst['server_name'])) class Controller(wsgi.Controller): - """ The Server API controller for the Openstack API """ + """ The Server API controller for the OpenStack API """ _serialization_metadata = { 'application/xml': { @@ -125,17 +110,14 @@ class Controller(wsgi.Controller): """ user_id = req.environ['nova.context']['user']['id'] instance_list = self.db_driver.instance_get_all_by_user(None, user_id) - limited_list = nova.api.rackspace.limited(instance_list, req) + limited_list = nova.api.openstack.limited(instance_list, req) res = [entity_maker(inst)['server'] for inst in limited_list] return _entity_list(res) def show(self, req, id): """ Returns server details by server id """ - inst_id_trans = _instance_id_translator() - inst_id = inst_id_trans.from_rs_id(id) - user_id = req.environ['nova.context']['user']['id'] - inst = self.db_driver.instance_get_by_ec2_id(None, inst_id) + inst = self.db_driver.instance_get_by_internal_id(None, int(id)) if inst: if inst.user_id == user_id: return _entity_detail(inst) @@ -143,11 +125,8 @@ class Controller(wsgi.Controller): def delete(self, req, id): """ Destroys a server """ - inst_id_trans = _instance_id_translator() - inst_id = inst_id_trans.from_rs_id(id) - user_id = req.environ['nova.context']['user']['id'] - instance = self.db_driver.instance_get_by_ec2_id(None, inst_id) + instance = self.db_driver.instance_get_by_internal_id(None, int(id)) if instance and instance['user_id'] == user_id: self.db_driver.instance_destroy(None, id) return faults.Fault(exc.HTTPAccepted()) @@ -160,10 +139,10 @@ class Controller(wsgi.Controller): if not env: return faults.Fault(exc.HTTPUnprocessableEntity()) - try: - inst = self._build_server_instance(req, env) - except Exception, e: - return faults.Fault(exc.HTTPUnprocessableEntity()) + #try: + inst = self._build_server_instance(req, env) + #except Exception, e: + # return faults.Fault(exc.HTTPUnprocessableEntity()) rpc.cast( FLAGS.compute_topic, { @@ -173,8 +152,6 @@ class Controller(wsgi.Controller): def update(self, req, id): """ Updates the server name or password """ - inst_id_trans = _instance_id_translator() - inst_id = inst_id_trans.from_rs_id(id) user_id = req.environ['nova.context']['user']['id'] inst_dict = self._deserialize(req.body, req) @@ -182,32 +159,33 @@ class Controller(wsgi.Controller): if not inst_dict: return faults.Fault(exc.HTTPUnprocessableEntity()) - instance = self.db_driver.instance_get_by_ec2_id(None, inst_id) + instance = self.db_driver.instance_get_by_internal_id(None, int(id)) if not instance or instance.user_id != user_id: return faults.Fault(exc.HTTPNotFound()) - self.db_driver.instance_update(None, id, + self.db_driver.instance_update(None, int(id), _filter_params(inst_dict['server'])) return faults.Fault(exc.HTTPNoContent()) def action(self, req, id): """ multi-purpose method used to reboot, rebuild, and resize a server """ + user_id = req.environ['nova.context']['user']['id'] input_dict = self._deserialize(req.body, req) try: reboot_type = input_dict['reboot']['type'] except Exception: raise faults.Fault(webob.exc.HTTPNotImplemented()) - opaque_id = _instance_id_translator().from_rs_id(id) - cloud.reboot(opaque_id) + inst_ref = self.db.instance_get_by_internal_id(None, int(id)) + if not inst_ref or (inst_ref and not inst_ref.user_id == user_id): + return faults.Fault(exc.HTTPUnprocessableEntity()) + cloud.reboot(id) def _build_server_instance(self, req, env): """Build instance data structure and save it to the data store.""" ltime = time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime()) inst = {} - inst_id_trans = _instance_id_translator() - user_id = req.environ['nova.context']['user']['id'] flavor_id = env['server']['flavorId'] @@ -218,16 +196,15 @@ class Controller(wsgi.Controller): image_id = env['server']['imageId'] - img_service, image_id_trans = _image_service() + img_service = utils.import_object(FLAGS.image_service) - opaque_image_id = image_id_trans.to_rs_id(image_id) - image = img_service.show(opaque_image_id) + image = img_service.show(image_id) if not image: raise Exception, "Image not found" inst['server_name'] = env['server']['name'] - inst['image_id'] = opaque_image_id + inst['image_id'] = image_id inst['user_id'] = user_id inst['launch_time'] = ltime inst['mac_address'] = utils.generate_mac() @@ -258,7 +235,7 @@ class Controller(wsgi.Controller): inst['local_gb'] = flavor['local_gb'] ref = self.db_driver.instance_create(None, inst) - inst['id'] = inst_id_trans.to_rs_id(ref.ec2_id) + inst['id'] = ref.internal_id # TODO(dietz): this isn't explicitly necessary, but the networking # calls depend on an object with a project_id property, and therefore @@ -270,10 +247,10 @@ class Controller(wsgi.Controller): #TODO(dietz) is this necessary? inst['launch_index'] = 0 - inst['hostname'] = ref.ec2_id + inst['hostname'] = str(ref.internal_id) self.db_driver.instance_update(None, inst['id'], inst) - network_manager = utils.import_object(FLAGS.rs_network_manager) + network_manager = utils.import_object(FLAGS.network_manager) address = network_manager.allocate_fixed_ip(api_context, inst['id']) diff --git a/nova/api/rackspace/sharedipgroups.py b/nova/api/openstack/sharedipgroups.py index 4d2d0ede1..4d2d0ede1 100644 --- a/nova/api/rackspace/sharedipgroups.py +++ b/nova/api/openstack/sharedipgroups.py diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 02ac3cb4c..d9e19ec9e 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -72,7 +72,7 @@ class ComputeManager(manager.Manager): def run_instance(self, context, instance_id, **_kwargs): """Launch a new instance with specified options.""" instance_ref = self.db.instance_get(context, instance_id) - if instance_ref['ec2_id'] in self.driver.list_instances(): + if instance_ref['internal_id'] in self.driver.list_instances(): raise exception.Error("Instance has already been created") logging.debug("instance %s: starting...", instance_id) project_id = instance_ref['project_id'] @@ -134,7 +134,7 @@ class ComputeManager(manager.Manager): raise exception.Error( 'trying to reboot a non-running' 'instance: %s (state: %s excepted: %s)' % - (instance_ref['ec2_id'], + (instance_ref['internal_id'], instance_ref['state'], power_state.RUNNING)) @@ -156,7 +156,7 @@ class ComputeManager(manager.Manager): if FLAGS.connection_type == 'libvirt': fname = os.path.abspath(os.path.join(FLAGS.instances_path, - instance_ref['ec2_id'], + instance_ref['internal_id'], 'console.log')) with open(fname, 'r') as f: output = f.read() diff --git a/nova/db/api.py b/nova/db/api.py index d9c715da7..4d3ff3871 100644 --- a/nova/db/api.py +++ b/nova/db/api.py @@ -280,9 +280,9 @@ def instance_get_floating_address(context, instance_id): return IMPL.instance_get_floating_address(context, instance_id) -def instance_get_by_ec2_id(context, ec2_id): +def instance_get_by_internal_id(context, internal_id): """Get an instance by ec2 id.""" - return IMPL.instance_get_by_ec2_id(context, ec2_id) + return IMPL.instance_get_by_internal_id(context, internal_id) def instance_is_vpn(context, instance_id): diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index e0f99d1a7..50d802774 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -453,7 +453,6 @@ def fixed_ip_create(_context, values): fixed_ip_ref.save() return fixed_ip_ref['address'] - @require_context def fixed_ip_disassociate(context, address): session = get_session() @@ -464,7 +463,6 @@ def fixed_ip_disassociate(context, address): fixed_ip_ref.instance = None fixed_ip_ref.save(session=session) - @require_admin_context def fixed_ip_disassociate_all_by_timeout(_context, host, time): session = get_session() @@ -528,6 +526,9 @@ def fixed_ip_update(context, address, values): ################### +#TODO(gundlach): instance_create and volume_create are nearly identical +#and should be refactored. I expect there are other copy-and-paste +#functions between the two of them as well. @require_context def instance_create(context, values): instance_ref = models.Instance() @@ -536,10 +537,11 @@ def instance_create(context, values): session = get_session() with session.begin(): - while instance_ref.ec2_id == None: - ec2_id = utils.generate_uid(instance_ref.__prefix__) - if not instance_ec2_id_exists(context, ec2_id, session=session): - instance_ref.ec2_id = ec2_id + while instance_ref.internal_id == None: + internal_id = utils.generate_uid(instance_ref.__prefix__) + if not instance_internal_id_exists(context, internal_id, + session=session): + instance_ref.internal_id = internal_id instance_ref.save(session=session) return instance_ref @@ -645,33 +647,35 @@ def instance_get_all_by_reservation(context, reservation_id): @require_context -def instance_get_by_ec2_id(context, ec2_id): +def instance_get_by_internal_id(context, internal_id): session = get_session() if is_admin_context(context): result = session.query(models.Instance ).options(joinedload('security_groups') - ).filter_by(ec2_id=ec2_id + ).filter_by(internal_id=internal_id ).filter_by(deleted=can_read_deleted(context) ).first() elif is_user_context(context): result = session.query(models.Instance ).options(joinedload('security_groups') ).filter_by(project_id=context.project.id - ).filter_by(ec2_id=ec2_id + ).filter_by(internal_id=internal_id ).filter_by(deleted=False ).first() if not result: - raise exception.NotFound('Instance %s not found' % (ec2_id)) + raise exception.NotFound('Instance %s not found' % (internal_id)) return result @require_context -def instance_ec2_id_exists(context, ec2_id, session=None): +def instance_internal_id_exists(context, internal_id, session=None): if not session: session = get_session() - return session.query(exists().where(models.Instance.id==ec2_id)).one()[0] + return session.query( + exists().where(models.Instance.internal_id==internal_id) + ).one()[0] @require_context diff --git a/nova/db/sqlalchemy/models.py b/nova/db/sqlalchemy/models.py index 65c117590..584214deb 100644 --- a/nova/db/sqlalchemy/models.py +++ b/nova/db/sqlalchemy/models.py @@ -152,7 +152,7 @@ class Instance(BASE, NovaBase): __tablename__ = 'instances' __prefix__ = 'i' id = Column(Integer, primary_key=True) - ec2_id = Column(String(10), unique=True) + internal_id = Column(Integer, unique=True) admin_pass = Column(String(255)) @@ -169,7 +169,7 @@ class Instance(BASE, NovaBase): @property def name(self): - return self.ec2_id + return self.internal_id image_id = Column(String(255)) kernel_id = Column(String(255)) diff --git a/nova/fakerabbit.py b/nova/fakerabbit.py index 068025249..835973810 100644 --- a/nova/fakerabbit.py +++ b/nova/fakerabbit.py @@ -22,6 +22,7 @@ import logging import Queue as queue from carrot.backends import base +from eventlet import greenthread class Message(base.BaseMessage): @@ -38,6 +39,7 @@ class Exchange(object): def publish(self, message, routing_key=None): logging.debug('(%s) publish (key: %s) %s', self.name, routing_key, message) + routing_key = routing_key.split('.')[0] if routing_key in self._routes: for f in self._routes[routing_key]: logging.debug('Publishing to route %s', f) @@ -94,6 +96,18 @@ class Backend(object): self._exchanges[exchange].bind(self._queues[queue].push, routing_key) + def declare_consumer(self, queue, callback, *args, **kwargs): + self.current_queue = queue + self.current_callback = callback + + def consume(self, *args, **kwargs): + while True: + item = self.get(self.current_queue) + if item: + self.current_callback(item) + raise StopIteration() + greenthread.sleep(0) + def get(self, queue, no_ack=False): if not queue in self._queues or not self._queues[queue].size(): return None diff --git a/nova/flags.py b/nova/flags.py index c32cdd7a4..ab80e83fb 100644 --- a/nova/flags.py +++ b/nova/flags.py @@ -222,6 +222,10 @@ DEFINE_string('volume_manager', 'nova.volume.manager.AOEManager', DEFINE_string('scheduler_manager', 'nova.scheduler.manager.SchedulerManager', 'Manager for scheduler') +# The service to use for image search and retrieval +DEFINE_string('image_service', 'nova.image.service.LocalImageService', + 'The service to use for retrieving and searching for images.') + DEFINE_string('host', socket.gethostname(), 'name of this node') diff --git a/nova/image/service.py b/nova/image/service.py index 1a7a258b7..5276e1312 100644 --- a/nova/image/service.py +++ b/nova/image/service.py @@ -16,39 +16,218 @@ # under the License. import cPickle as pickle +import httplib +import json +import logging import os.path import random import string +import urlparse -class ImageService(object): - """Provides storage and retrieval of disk image objects.""" +import webob.exc - @staticmethod - def load(): - """Factory method to return image service.""" - #TODO(gundlach): read from config. - class_ = LocalImageService - return class_() +from nova import utils +from nova import flags +from nova import exception + + +FLAGS = flags.FLAGS + + +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', 'http://127.0.0.1', + 'IP address or URL where Glance\'s Parallax service resides') +flags.DEFINE_string('glance_parallax_port', '9292', + '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. + + :raises NotFound if the image does not exist + """ + 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 + + +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: + # Parallax returns a JSONified dict(images=image_list) + data = json.loads(res.read())['images'] + return data + else: + logging.warn("Parallax returned HTTP error %d from " + "request for /images", res.status_int) + 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: + # Parallax returns a JSONified dict(image=image_info) + data = json.loads(res.read())['image'] + return data + else: + # TODO(jaypipes): log the error? + return None + 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(ImageService): +class GlanceImageService(BaseImageService): + """Provides storage and retrieval of disk image objects within Glance.""" - # TODO(gundlach): once Glance has an API, build this. - pass + def __init__(self): + self.teller = TellerClient() + self.parallax = ParallaxClient() + + def index(self): + """ + Calls out to Parallax for a list of images available + """ + images = self.parallax.get_images() + return images + + def show(self, id): + """ + Returns a dict containing image data for the given opaque image id. + """ + image = self.parallax.get_image_metadata(id) + if image: + return image + raise exception.NotFound -class LocalImageService(ImageService): - """Image service storing images to local disk.""" + def create(self, data): + """ + Store the image data and return the new image id. + + :raises AlreadyExists if the image already exist. + + """ + return self.parallax.add_image_metadata(data) + + 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. + + """ + self.parallax.update_image_metadata(image_id, data) + + def delete(self, image_id): + """ + Delete the given image. + + :raises NotFound if the image does not exist. + + """ + self.parallax.delete_image_metadata(image_id) + + def delete_all(self): + """ + Clears out all images + """ + pass + + +class LocalImageService(BaseImageService): + + """Image service storing images to local disk. + + It assumes that image_ids are integers.""" def __init__(self): self._path = "/tmp/nova/images" @@ -57,34 +236,50 @@ class LocalImageService(ImageService): except OSError: # exists pass - def _path_to(self, image_id=''): - return os.path.join(self._path, image_id) + def _path_to(self, image_id): + return os.path.join(self._path, str(image_id)) def _ids(self): """The list of all image ids.""" - return os.listdir(self._path) + return [int(i) for i in os.listdir(self._path)] def index(self): 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): """ Store the image data and return the new image id. """ - id = ''.join(random.choice(string.letters) for _ in range(20)) + id = random.randint(0, 2**32-1) data['id'] = id self.update(id, data) return id 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): + """ + Clears out all images in local directory + """ + for id in self._ids(): + os.unlink(self._path_to(id)) diff --git a/nova/objectstore/image.py b/nova/objectstore/image.py index def1b8167..c01b041bb 100644 --- a/nova/objectstore/image.py +++ b/nova/objectstore/image.py @@ -191,14 +191,14 @@ class Image(object): if kernel_id == 'true': image_type = 'kernel' except: - pass + kernel_id = None try: ramdisk_id = manifest.find("machine_configuration/ramdisk_id").text if ramdisk_id == 'true': image_type = 'ramdisk' except: - pass + ramdisk_id = None info = { 'imageId': image_id, @@ -209,6 +209,12 @@ class Image(object): 'imageType' : image_type } + if kernel_id: + info['kernelId'] = kernel_id + + if ramdisk_id: + info['ramdiskId'] = ramdisk_id + def write_state(state): info['imageState'] = state with open(os.path.join(image_path, 'info.json'), "w") as f: diff --git a/nova/rpc.py b/nova/rpc.py index fe52ad35f..447ad3b93 100644 --- a/nova/rpc.py +++ b/nova/rpc.py @@ -28,6 +28,7 @@ import uuid from carrot import connection as carrot_connection from carrot import messaging +from eventlet import greenthread from twisted.internet import defer from twisted.internet import task @@ -107,6 +108,14 @@ class Consumer(messaging.Consumer): logging.exception("Failed to fetch message from queue") self.failed_connection = True + def attach_to_eventlet(self): + """Only needed for unit tests!""" + def fetch_repeatedly(): + while True: + self.fetch(enable_callbacks=True) + greenthread.sleep(0.1) + greenthread.spawn(fetch_repeatedly) + def attach_to_twisted(self): """Attach a callback to twisted that fires 10 times a second""" loop = task.LoopingCall(self.fetch, enable_callbacks=True) diff --git a/nova/server.py b/nova/server.py index d4563bfe0..c58a15041 100644 --- a/nova/server.py +++ b/nova/server.py @@ -106,6 +106,7 @@ def serve(name, main): def daemonize(args, name, main): """Does the work of daemonizing the process""" logging.getLogger('amqplib').setLevel(logging.WARN) + files_to_keep = [] if FLAGS.daemonize: logger = logging.getLogger() formatter = logging.Formatter( @@ -114,12 +115,14 @@ def daemonize(args, name, main): syslog = logging.handlers.SysLogHandler(address='/dev/log') syslog.setFormatter(formatter) logger.addHandler(syslog) + files_to_keep.append(syslog.socket) else: if not FLAGS.logfile: FLAGS.logfile = '%s.log' % name logfile = logging.FileHandler(FLAGS.logfile) logfile.setFormatter(formatter) logger.addHandler(logfile) + files_to_keep.append(logfile.stream) stdin, stdout, stderr = None, None, None else: stdin, stdout, stderr = sys.stdin, sys.stdout, sys.stderr @@ -139,6 +142,7 @@ def daemonize(args, name, main): stdout=stdout, stderr=stderr, uid=FLAGS.uid, - gid=FLAGS.gid + gid=FLAGS.gid, + files_preserve=files_to_keep ): main(args) diff --git a/nova/tests/api/__init__.py b/nova/tests/api/__init__.py index ec76aa827..f051e2390 100644 --- a/nova/tests/api/__init__.py +++ b/nova/tests/api/__init__.py @@ -44,9 +44,9 @@ class Test(unittest.TestCase): req = webob.Request.blank(url, environ_keys) return req.get_response(api.API()) - def test_rackspace(self): - self.stubs.Set(api.rackspace, 'API', APIStub) - result = self._request('/v1.0/cloud', 'rs') + def test_openstack(self): + self.stubs.Set(api.openstack, 'API', APIStub) + result = self._request('/v1.0/cloud', 'api') self.assertEqual(result.body, "/cloud") def test_ec2(self): @@ -56,12 +56,12 @@ class Test(unittest.TestCase): def test_not_found(self): self.stubs.Set(api.ec2, 'API', APIStub) - self.stubs.Set(api.rackspace, 'API', APIStub) + self.stubs.Set(api.openstack, 'API', APIStub) result = self._request('/test/cloud', 'ec2') self.assertNotEqual(result.body, "/cloud") def test_query_api_versions(self): - result = self._request('/', 'rs') + result = self._request('/', 'api') self.assertTrue('CURRENT' in result.body) def test_metadata(self): diff --git a/nova/tests/api/rackspace/__init__.py b/nova/tests/api/openstack/__init__.py index 1834f91b1..b534897f5 100644 --- a/nova/tests/api/rackspace/__init__.py +++ b/nova/tests/api/openstack/__init__.py @@ -17,8 +17,8 @@ import unittest -from nova.api.rackspace import limited -from nova.api.rackspace import RateLimitingMiddleware +from nova.api.openstack import limited +from nova.api.openstack import RateLimitingMiddleware from nova.tests.api.fakes import APIStub from webob import Request diff --git a/nova/tests/api/openstack/fakes.py b/nova/tests/api/openstack/fakes.py new file mode 100644 index 000000000..34bc1f2a9 --- /dev/null +++ b/nova/tests/api/openstack/fakes.py @@ -0,0 +1,205 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2010 OpenStack LLC. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import datetime +import json +import random +import string + +import webob +import webob.dec + +from nova import auth +from nova import utils +from nova import flags +from nova import exception as exc +import nova.api.openstack.auth +from nova.image import service +from nova.wsgi import Router + + +FLAGS = flags.FLAGS + + +class Context(object): + pass + + +class FakeRouter(Router): + def __init__(self): + pass + + @webob.dec.wsgify + def __call__(self, req): + res = webob.Response() + res.status = '200' + res.headers['X-Test-Success'] = 'True' + return res + + +def fake_auth_init(self): + self.db = FakeAuthDatabase() + self.context = Context() + self.auth = FakeAuthManager() + self.host = 'foo' + + +@webob.dec.wsgify +def fake_wsgi(self, req): + req.environ['nova.context'] = dict(user=dict(id=1)) + if req.body: + req.environ['inst_dict'] = json.loads(req.body) + return self.application + + +def stub_out_key_pair_funcs(stubs): + def key_pair(context, user_id): + return [dict(name='key', public_key='public_key')] + stubs.Set(nova.db.api, 'key_pair_get_all_by_user', + key_pair) + + +def stub_out_image_service(stubs): + def fake_image_show(meh, id): + return dict(kernelId=1, ramdiskId=1) + + stubs.Set(nova.image.service.LocalImageService, 'show', fake_image_show) + +def stub_out_auth(stubs): + def fake_auth_init(self, app): + self.application = app + + stubs.Set(nova.api.openstack.AuthMiddleware, + '__init__', fake_auth_init) + stubs.Set(nova.api.openstack.AuthMiddleware, + '__call__', fake_wsgi) + + +def stub_out_rate_limiting(stubs): + def fake_rate_init(self, app): + super(nova.api.openstack.RateLimitingMiddleware, self).__init__(app) + self.application = app + + stubs.Set(nova.api.openstack.RateLimitingMiddleware, + '__init__', fake_rate_init) + + stubs.Set(nova.api.openstack.RateLimitingMiddleware, + '__call__', fake_wsgi) + + +def stub_out_networking(stubs): + def get_my_ip(): + return '127.0.0.1' + stubs.Set(nova.utils, 'get_my_ip', get_my_ip) + FLAGS.FAKE_subdomain = 'api' + + +def stub_out_glance(stubs): + + class FakeParallaxClient: + + def __init__(self): + self.fixtures = {} + + def fake_get_images(self): + return self.fixtures + + def fake_get_image_metadata(self, image_id): + for k, f in self.fixtures.iteritems(): + if k == image_id: + return f + return None + + def fake_add_image_metadata(self, image_data): + id = ''.join(random.choice(string.letters) for _ in range(20)) + image_data['id'] = id + self.fixtures[id] = image_data + return id + + def fake_update_image_metadata(self, image_id, image_data): + + if image_id not in self.fixtures.keys(): + raise exc.NotFound + + self.fixtures[image_id].update(image_data) + + def fake_delete_image_metadata(self, image_id): + + if image_id not in self.fixtures.keys(): + raise exc.NotFound + + del self.fixtures[image_id] + + def fake_delete_all(self): + self.fixtures = {} + + fake_parallax_client = FakeParallaxClient() + stubs.Set(nova.image.service.ParallaxClient, 'get_images', + fake_parallax_client.fake_get_images) + stubs.Set(nova.image.service.ParallaxClient, 'get_image_metadata', + fake_parallax_client.fake_get_image_metadata) + stubs.Set(nova.image.service.ParallaxClient, 'add_image_metadata', + fake_parallax_client.fake_add_image_metadata) + stubs.Set(nova.image.service.ParallaxClient, 'update_image_metadata', + fake_parallax_client.fake_update_image_metadata) + stubs.Set(nova.image.service.ParallaxClient, 'delete_image_metadata', + fake_parallax_client.fake_delete_image_metadata) + stubs.Set(nova.image.service.GlanceImageService, 'delete_all', + fake_parallax_client.fake_delete_all) + + +class FakeAuthDatabase(object): + data = {} + + @staticmethod + def auth_get_token(context, token_hash): + return FakeAuthDatabase.data.get(token_hash, None) + + @staticmethod + def auth_create_token(context, token): + token['created_at'] = datetime.datetime.now() + FakeAuthDatabase.data[token['token_hash']] = token + + @staticmethod + def auth_destroy_token(context, token): + if FakeAuthDatabase.data.has_key(token['token_hash']): + del FakeAuthDatabase.data['token_hash'] + + +class FakeAuthManager(object): + auth_data = {} + + def add_user(self, key, user): + FakeAuthManager.auth_data[key] = user + + def get_user(self, uid): + for k, v in FakeAuthManager.auth_data.iteritems(): + if v['uid'] == uid: + return v + return None + + def get_user_from_access_key(self, key): + return FakeAuthManager.auth_data.get(key, None) + + +class FakeRateLimiter(object): + def __init__(self, application): + self.application = application + + @webob.dec.wsgify + def __call__(self, req): + return self.application diff --git a/nova/tests/api/rackspace/test_auth.py b/nova/tests/api/openstack/test_auth.py index 374cfe42b..d2ba80243 100644 --- a/nova/tests/api/rackspace/test_auth.py +++ b/nova/tests/api/openstack/test_auth.py @@ -6,14 +6,14 @@ import webob import webob.dec import nova.api -import nova.api.rackspace.auth +import nova.api.openstack.auth from nova import auth -from nova.tests.api.rackspace import fakes +from nova.tests.api.openstack import fakes class Test(unittest.TestCase): def setUp(self): self.stubs = stubout.StubOutForTesting() - self.stubs.Set(nova.api.rackspace.auth.BasicApiAuthManager, + self.stubs.Set(nova.api.openstack.auth.BasicApiAuthManager, '__init__', fakes.fake_auth_init) fakes.FakeAuthManager.auth_data = {} fakes.FakeAuthDatabase.data = {} @@ -55,7 +55,7 @@ class Test(unittest.TestCase): self.assertEqual(result.headers['X-Storage-Url'], "") token = result.headers['X-Auth-Token'] - self.stubs.Set(nova.api.rackspace, 'APIRouter', + self.stubs.Set(nova.api.openstack, 'APIRouter', fakes.FakeRouter) req = webob.Request.blank('/v1.0/fake') req.headers['X-Auth-Token'] = token diff --git a/nova/tests/api/rackspace/test_faults.py b/nova/tests/api/openstack/test_faults.py index b2931bc98..70a811469 100644 --- a/nova/tests/api/rackspace/test_faults.py +++ b/nova/tests/api/openstack/test_faults.py @@ -3,7 +3,7 @@ import webob import webob.dec import webob.exc -from nova.api.rackspace import faults +from nova.api.openstack import faults class TestFaults(unittest.TestCase): diff --git a/nova/tests/api/rackspace/test_flavors.py b/nova/tests/api/openstack/test_flavors.py index affdd2406..8dd4d1f29 100644 --- a/nova/tests/api/rackspace/test_flavors.py +++ b/nova/tests/api/openstack/test_flavors.py @@ -21,8 +21,8 @@ import stubout import webob import nova.api -from nova.api.rackspace import flavors -from nova.tests.api.rackspace import fakes +from nova.api.openstack import flavors +from nova.tests.api.openstack import fakes class FlavorsTest(unittest.TestCase): diff --git a/nova/tests/api/openstack/test_images.py b/nova/tests/api/openstack/test_images.py new file mode 100644 index 000000000..505fea3e2 --- /dev/null +++ b/nova/tests/api/openstack/test_images.py @@ -0,0 +1,141 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2010 OpenStack LLC. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import logging +import unittest + +import stubout + +from nova import exception +from nova import utils +from nova.api.openstack import images +from nova.tests.api.openstack import fakes + + +class BaseImageServiceTests(): + + """Tasks to test for all image services""" + + def test_create(self): + + fixture = {'name': 'test image', + 'updated': None, + 'created': None, + 'status': None, + 'serverId': None, + 'progress': None} + + num_images = len(self.service.index()) + + id = self.service.create(fixture) + + self.assertNotEquals(None, id) + self.assertEquals(num_images + 1, len(self.service.index())) + + def test_create_and_show_non_existing_image(self): + + fixture = {'name': 'test image', + 'updated': None, + 'created': None, + 'status': None, + 'serverId': None, + 'progress': None} + + num_images = len(self.service.index()) + + id = self.service.create(fixture) + + self.assertNotEquals(None, id) + + self.assertRaises(exception.NotFound, + self.service.show, + 'bad image id') + + def test_update(self): + + fixture = {'name': 'test image', + 'updated': None, + 'created': None, + 'status': None, + 'serverId': None, + 'progress': None} + + id = self.service.create(fixture) + + fixture['status'] = 'in progress' + + self.service.update(id, fixture) + new_image_data = self.service.show(id) + self.assertEquals('in progress', new_image_data['status']) + + def test_delete(self): + + fixtures = [ + {'name': 'test image 1', + 'updated': None, + 'created': None, + 'status': None, + 'serverId': None, + 'progress': None}, + {'name': 'test image 2', + 'updated': None, + 'created': None, + 'status': None, + 'serverId': None, + 'progress': None}] + + ids = [] + for fixture in fixtures: + new_id = self.service.create(fixture) + ids.append(new_id) + + num_images = len(self.service.index()) + self.assertEquals(2, num_images) + + self.service.delete(ids[0]) + + num_images = len(self.service.index()) + self.assertEquals(1, num_images) + + +class LocalImageServiceTest(unittest.TestCase, + BaseImageServiceTests): + + """Tests the local image service""" + + def setUp(self): + self.stubs = stubout.StubOutForTesting() + self.service = utils.import_object('nova.image.service.LocalImageService') + + def tearDown(self): + self.service.delete_all() + self.stubs.UnsetAll() + + +class GlanceImageServiceTest(unittest.TestCase, + BaseImageServiceTests): + + """Tests the local image service""" + + def setUp(self): + self.stubs = stubout.StubOutForTesting() + fakes.stub_out_glance(self.stubs) + self.service = utils.import_object('nova.image.service.GlanceImageService') + + def tearDown(self): + self.service.delete_all() + self.stubs.UnsetAll() diff --git a/nova/api/rackspace/ratelimiting/tests.py b/nova/tests/api/openstack/test_ratelimiting.py index 4c9510917..ad9e67454 100644 --- a/nova/api/rackspace/ratelimiting/tests.py +++ b/nova/tests/api/openstack/test_ratelimiting.py @@ -4,7 +4,7 @@ import time import unittest import webob -import nova.api.rackspace.ratelimiting as ratelimiting +import nova.api.openstack.ratelimiting as ratelimiting class LimiterTest(unittest.TestCase): diff --git a/nova/tests/api/rackspace/test_servers.py b/nova/tests/api/openstack/test_servers.py index 9c1860879..d1ee533b6 100644 --- a/nova/tests/api/rackspace/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -23,16 +23,17 @@ import webob from nova import db from nova import flags -import nova.api.rackspace -from nova.api.rackspace import servers +import nova.api.openstack +from nova.api.openstack import servers import nova.db.api from nova.db.sqlalchemy.models import Instance import nova.rpc -from nova.tests.api.rackspace import fakes +from nova.tests.api.openstack import fakes FLAGS = flags.FLAGS +FLAGS.verbose = True def return_server(context, id): return stub_instance(id) @@ -57,11 +58,10 @@ class ServersTest(unittest.TestCase): fakes.stub_out_networking(self.stubs) fakes.stub_out_rate_limiting(self.stubs) fakes.stub_out_auth(self.stubs) - fakes.stub_out_id_translator(self.stubs) fakes.stub_out_key_pair_funcs(self.stubs) fakes.stub_out_image_service(self.stubs) self.stubs.Set(nova.db.api, 'instance_get_all', return_servers) - self.stubs.Set(nova.db.api, 'instance_get_by_ec2_id', return_server) + self.stubs.Set(nova.db.api, 'instance_get_by_internal_id', return_server) self.stubs.Set(nova.db.api, 'instance_get_all_by_user', return_servers) @@ -72,7 +72,7 @@ class ServersTest(unittest.TestCase): req = webob.Request.blank('/v1.0/servers/1') res = req.get_response(nova.api.API()) res_dict = json.loads(res.body) - self.assertEqual(res_dict['server']['id'], '1') + self.assertEqual(res_dict['server']['id'], 1) self.assertEqual(res_dict['server']['name'], 'server1') def test_get_server_list(self): @@ -93,7 +93,7 @@ class ServersTest(unittest.TestCase): def instance_create(context, inst): class Foo(object): - ec2_id = 1 + internal_id = 1 return Foo() def fake_method(*args, **kwargs): @@ -112,10 +112,9 @@ class ServersTest(unittest.TestCase): self.stubs.Set(nova.db.api, 'instance_update', server_update) self.stubs.Set(nova.db.api, 'queue_get_for', queue_get_for) - self.stubs.Set(nova.network.manager.FlatManager, 'allocate_fixed_ip', + self.stubs.Set(nova.network.manager.VlanManager, 'allocate_fixed_ip', fake_method) - fakes.stub_out_id_translator(self.stubs) body = dict(server=dict( name='server_test', imageId=2, flavorId=2, metadata={}, personality = {} diff --git a/nova/tests/api/rackspace/test_sharedipgroups.py b/nova/tests/api/openstack/test_sharedipgroups.py index 31ce967d0..d199951d8 100644 --- a/nova/tests/api/rackspace/test_sharedipgroups.py +++ b/nova/tests/api/openstack/test_sharedipgroups.py @@ -19,7 +19,7 @@ import unittest import stubout -from nova.api.rackspace import sharedipgroups +from nova.api.openstack import sharedipgroups class SharedIpGroupsTest(unittest.TestCase): diff --git a/nova/tests/api/rackspace/fakes.py b/nova/tests/api/rackspace/fakes.py deleted file mode 100644 index 2c4447920..000000000 --- a/nova/tests/api/rackspace/fakes.py +++ /dev/null @@ -1,148 +0,0 @@ -import datetime -import json - -import webob -import webob.dec - -from nova import auth -from nova import utils -from nova import flags -import nova.api.rackspace.auth -import nova.api.rackspace._id_translator -from nova.image import service -from nova.wsgi import Router - - -FLAGS = flags.FLAGS - - -class Context(object): - pass - - -class FakeRouter(Router): - def __init__(self): - pass - - @webob.dec.wsgify - def __call__(self, req): - res = webob.Response() - res.status = '200' - res.headers['X-Test-Success'] = 'True' - return res - - -def fake_auth_init(self): - self.db = FakeAuthDatabase() - self.context = Context() - self.auth = FakeAuthManager() - self.host = 'foo' - - -@webob.dec.wsgify -def fake_wsgi(self, req): - req.environ['nova.context'] = dict(user=dict(id=1)) - if req.body: - req.environ['inst_dict'] = json.loads(req.body) - return self.application - - -def stub_out_key_pair_funcs(stubs): - def key_pair(context, user_id): - return [dict(name='key', public_key='public_key')] - stubs.Set(nova.db.api, 'key_pair_get_all_by_user', - key_pair) - - -def stub_out_image_service(stubs): - def fake_image_show(meh, id): - return dict(kernelId=1, ramdiskId=1) - - stubs.Set(nova.image.service.LocalImageService, 'show', fake_image_show) - - -def stub_out_id_translator(stubs): - class FakeTranslator(object): - def __init__(self, id_type, service_name): - pass - - def to_rs_id(self, id): - return id - - def from_rs_id(self, id): - return id - - stubs.Set(nova.api.rackspace._id_translator, - 'RackspaceAPIIdTranslator', FakeTranslator) - - -def stub_out_auth(stubs): - def fake_auth_init(self, app): - self.application = app - - stubs.Set(nova.api.rackspace.AuthMiddleware, - '__init__', fake_auth_init) - stubs.Set(nova.api.rackspace.AuthMiddleware, - '__call__', fake_wsgi) - - -def stub_out_rate_limiting(stubs): - def fake_rate_init(self, app): - super(nova.api.rackspace.RateLimitingMiddleware, self).__init__(app) - self.application = app - - stubs.Set(nova.api.rackspace.RateLimitingMiddleware, - '__init__', fake_rate_init) - - stubs.Set(nova.api.rackspace.RateLimitingMiddleware, - '__call__', fake_wsgi) - - -def stub_out_networking(stubs): - def get_my_ip(): - return '127.0.0.1' - stubs.Set(nova.utils, 'get_my_ip', get_my_ip) - FLAGS.FAKE_subdomain = 'rs' - - -class FakeAuthDatabase(object): - data = {} - - @staticmethod - def auth_get_token(context, token_hash): - return FakeAuthDatabase.data.get(token_hash, None) - - @staticmethod - def auth_create_token(context, token): - token['created_at'] = datetime.datetime.now() - FakeAuthDatabase.data[token['token_hash']] = token - - @staticmethod - def auth_destroy_token(context, token): - if FakeAuthDatabase.data.has_key(token['token_hash']): - del FakeAuthDatabase.data['token_hash'] - - -class FakeAuthManager(object): - auth_data = {} - - def add_user(self, key, user): - FakeAuthManager.auth_data[key] = user - - def get_user(self, uid): - for k, v in FakeAuthManager.auth_data.iteritems(): - if v['uid'] == uid: - return v - return None - - def get_user_from_access_key(self, key): - return FakeAuthManager.auth_data.get(key, None) - - -class FakeRateLimiter(object): - def __init__(self, application): - self.application = application - - @webob.dec.wsgify - def __call__(self, req): - return self.application diff --git a/nova/tests/api/rackspace/test_images.py b/nova/tests/api/rackspace/test_images.py deleted file mode 100644 index 489e35052..000000000 --- a/nova/tests/api/rackspace/test_images.py +++ /dev/null @@ -1,39 +0,0 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# Copyright 2010 OpenStack LLC. -# All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -import unittest - -import stubout - -from nova.api.rackspace import images - - -class ImagesTest(unittest.TestCase): - def setUp(self): - self.stubs = stubout.StubOutForTesting() - - def tearDown(self): - self.stubs.UnsetAll() - - def test_get_image_list(self): - pass - - def test_delete_image(self): - pass - - def test_create_image(self): - pass diff --git a/nova/tests/bundle/1mb.manifest.xml b/nova/tests/bundle/1mb.manifest.xml index dc3315957..01648a544 100644 --- a/nova/tests/bundle/1mb.manifest.xml +++ b/nova/tests/bundle/1mb.manifest.xml @@ -1 +1 @@ -<?xml version="1.0" ?><manifest><version>2007-10-10</version><bundler><name>euca-tools</name><version>1.2</version><release>31337</release></bundler><machine_configuration><architecture>x86_64</architecture></machine_configuration><image><name>1mb</name><user>42</user><type>machine</type><digest algorithm="SHA1">da39a3ee5e6b4b0d3255bfef95601890afd80709</digest><size>1048576</size><bundled_size>1136</bundled_size><ec2_encrypted_key algorithm="AES-128-CBC">33a2ea00dc64083dd9a10eb5e233635b42a7beb1670ab75452087d9de74c60aba1cd27c136fda56f62beb581de128fb1f10d072b9e556fd25e903107a57827c21f6ee8a93a4ff55b11311fcef217e3eefb07e81f71e88216f43b4b54029c1f2549f2925a839a73947d2d5aeecec4a62ece4af9156d557ae907978298296d9915</ec2_encrypted_key><user_encrypted_key algorithm="AES-128-CBC">4c11147fd8caf92447e90ce339928933d7579244c2f8ffb07cc0ea35f8738da8b90eff6c7a49671a84500e993e9462e4c36d5c19c0b3a2b397d035b4c0cce742b58e12552175d81d129b0425e9f71ebacb9aeb539fa9dd2ac36749fb82876f6902e5fb24b6ec19f35ec4c20acd50437fd30966e99c4d9a0647577970a8fa3023</user_encrypted_key><ec2_encrypted_iv>14bd082c9715f071160c69bbfb070f51d2ba1076775f1d988ccde150e515088156b248e4b5a64e46c4fe064feeeedfe14511f7fde478a51acb89f9b2f6c84b60593e5c3f792ba6b01fed9bf2158fdac03086374883b39d13a3ca74497eeaaf579fc3f26effc73bfd9446a2a8c4061f0874bfaca058905180e22d3d8881551cb3</ec2_encrypted_iv><user_encrypted_iv>8f7606f19f00e4e19535dd234b66b31b77e9c7bad3885d9c9efa75c863631fd4f82a009e17d789066d9cc6032a436f05384832f6d9a3283d3e63eab04fa0da5c8c87db9b17e854e842c3fb416507d067a266b44538125ce732e486098e8ebd1ca91fa3079f007fce7d14957a9b7e57282407ead3c6eb68fe975df3d83190021b</user_encrypted_iv><parts count="2"><part index="0"><filename>1mb.part.0</filename><digest algorithm="SHA1">c4413423cf7a57e71187e19bfd5cd4b514a64283</digest></part><part index="1"><filename>1mb.part.1</filename><digest algorithm="SHA1">9d4262e6589393d09a11a0332af169887bc2e57d</digest></part></parts></image><signature>4e00b5ba28114dda4a9df7eeae94be847ec46117a09a1cbe41e578660642f0660dda1776b39fb3bf826b6cfec019e2a5e9c566728d186b7400ebc989a30670eb1db26ce01e68bd9d3f31290370077a85b81c66b63c1e0d5499bac115c06c17a21a81b6d3a67ebbce6c17019095af7ab07f3796c708cc843e58efc12ddc788c5e</signature></manifest>
\ No newline at end of file +<?xml version="1.0" ?><manifest><version>2007-10-10</version><bundler><name>euca-tools</name><version>1.2</version><release>31337</release></bundler><machine_configuration><architecture>x86_64</architecture><kernel_id>aki-test</kernel_id><ramdisk_id>ari-test</ramdisk_id></machine_configuration><image><name>1mb</name><user>42</user><type>machine</type><digest algorithm="SHA1">da39a3ee5e6b4b0d3255bfef95601890afd80709</digest><size>1048576</size><bundled_size>1136</bundled_size><ec2_encrypted_key algorithm="AES-128-CBC">33a2ea00dc64083dd9a10eb5e233635b42a7beb1670ab75452087d9de74c60aba1cd27c136fda56f62beb581de128fb1f10d072b9e556fd25e903107a57827c21f6ee8a93a4ff55b11311fcef217e3eefb07e81f71e88216f43b4b54029c1f2549f2925a839a73947d2d5aeecec4a62ece4af9156d557ae907978298296d9915</ec2_encrypted_key><user_encrypted_key algorithm="AES-128-CBC">4c11147fd8caf92447e90ce339928933d7579244c2f8ffb07cc0ea35f8738da8b90eff6c7a49671a84500e993e9462e4c36d5c19c0b3a2b397d035b4c0cce742b58e12552175d81d129b0425e9f71ebacb9aeb539fa9dd2ac36749fb82876f6902e5fb24b6ec19f35ec4c20acd50437fd30966e99c4d9a0647577970a8fa3023</user_encrypted_key><ec2_encrypted_iv>14bd082c9715f071160c69bbfb070f51d2ba1076775f1d988ccde150e515088156b248e4b5a64e46c4fe064feeeedfe14511f7fde478a51acb89f9b2f6c84b60593e5c3f792ba6b01fed9bf2158fdac03086374883b39d13a3ca74497eeaaf579fc3f26effc73bfd9446a2a8c4061f0874bfaca058905180e22d3d8881551cb3</ec2_encrypted_iv><user_encrypted_iv>8f7606f19f00e4e19535dd234b66b31b77e9c7bad3885d9c9efa75c863631fd4f82a009e17d789066d9cc6032a436f05384832f6d9a3283d3e63eab04fa0da5c8c87db9b17e854e842c3fb416507d067a266b44538125ce732e486098e8ebd1ca91fa3079f007fce7d14957a9b7e57282407ead3c6eb68fe975df3d83190021b</user_encrypted_iv><parts count="2"><part index="0"><filename>1mb.part.0</filename><digest algorithm="SHA1">c4413423cf7a57e71187e19bfd5cd4b514a64283</digest></part><part index="1"><filename>1mb.part.1</filename><digest algorithm="SHA1">9d4262e6589393d09a11a0332af169887bc2e57d</digest></part></parts></image><signature>4e00b5ba28114dda4a9df7eeae94be847ec46117a09a1cbe41e578660642f0660dda1776b39fb3bf826b6cfec019e2a5e9c566728d186b7400ebc989a30670eb1db26ce01e68bd9d3f31290370077a85b81c66b63c1e0d5499bac115c06c17a21a81b6d3a67ebbce6c17019095af7ab07f3796c708cc843e58efc12ddc788c5e</signature></manifest> diff --git a/nova/tests/bundle/1mb.no_kernel_or_ramdisk.manifest.xml b/nova/tests/bundle/1mb.no_kernel_or_ramdisk.manifest.xml new file mode 100644 index 000000000..73d7ace00 --- /dev/null +++ b/nova/tests/bundle/1mb.no_kernel_or_ramdisk.manifest.xml @@ -0,0 +1 @@ +<?xml version="1.0" ?><manifest><version>2007-10-10</version><bundler><name>euca-tools</name><version>1.2</version><release>31337</release></bundler><machine_configuration><architecture>x86_64</architecture></machine_configuration><image><name>1mb</name><user>42</user><type>machine</type><digest algorithm="SHA1">da39a3ee5e6b4b0d3255bfef95601890afd80709</digest><size>1048576</size><bundled_size>1136</bundled_size><ec2_encrypted_key algorithm="AES-128-CBC">33a2ea00dc64083dd9a10eb5e233635b42a7beb1670ab75452087d9de74c60aba1cd27c136fda56f62beb581de128fb1f10d072b9e556fd25e903107a57827c21f6ee8a93a4ff55b11311fcef217e3eefb07e81f71e88216f43b4b54029c1f2549f2925a839a73947d2d5aeecec4a62ece4af9156d557ae907978298296d9915</ec2_encrypted_key><user_encrypted_key algorithm="AES-128-CBC">4c11147fd8caf92447e90ce339928933d7579244c2f8ffb07cc0ea35f8738da8b90eff6c7a49671a84500e993e9462e4c36d5c19c0b3a2b397d035b4c0cce742b58e12552175d81d129b0425e9f71ebacb9aeb539fa9dd2ac36749fb82876f6902e5fb24b6ec19f35ec4c20acd50437fd30966e99c4d9a0647577970a8fa3023</user_encrypted_key><ec2_encrypted_iv>14bd082c9715f071160c69bbfb070f51d2ba1076775f1d988ccde150e515088156b248e4b5a64e46c4fe064feeeedfe14511f7fde478a51acb89f9b2f6c84b60593e5c3f792ba6b01fed9bf2158fdac03086374883b39d13a3ca74497eeaaf579fc3f26effc73bfd9446a2a8c4061f0874bfaca058905180e22d3d8881551cb3</ec2_encrypted_iv><user_encrypted_iv>8f7606f19f00e4e19535dd234b66b31b77e9c7bad3885d9c9efa75c863631fd4f82a009e17d789066d9cc6032a436f05384832f6d9a3283d3e63eab04fa0da5c8c87db9b17e854e842c3fb416507d067a266b44538125ce732e486098e8ebd1ca91fa3079f007fce7d14957a9b7e57282407ead3c6eb68fe975df3d83190021b</user_encrypted_iv><parts count="2"><part index="0"><filename>1mb.part.0</filename><digest algorithm="SHA1">c4413423cf7a57e71187e19bfd5cd4b514a64283</digest></part><part index="1"><filename>1mb.part.1</filename><digest algorithm="SHA1">9d4262e6589393d09a11a0332af169887bc2e57d</digest></part></parts></image><signature>4e00b5ba28114dda4a9df7eeae94be847ec46117a09a1cbe41e578660642f0660dda1776b39fb3bf826b6cfec019e2a5e9c566728d186b7400ebc989a30670eb1db26ce01e68bd9d3f31290370077a85b81c66b63c1e0d5499bac115c06c17a21a81b6d3a67ebbce6c17019095af7ab07f3796c708cc843e58efc12ddc788c5e</signature></manifest> diff --git a/nova/tests/cloud_unittest.py b/nova/tests/cloud_unittest.py index ae7dea1db..8e5881edb 100644 --- a/nova/tests/cloud_unittest.py +++ b/nova/tests/cloud_unittest.py @@ -16,6 +16,7 @@ # License for the specific language governing permissions and limitations # under the License. +from base64 import b64decode import json import logging from M2Crypto import BIO @@ -63,11 +64,17 @@ class CloudTestCase(test.TrialTestCase): self.cloud = cloud.CloudController() # set up a service - self.compute = utils.import_class(FLAGS.compute_manager) + self.compute = utils.import_class(FLAGS.compute_manager)() self.compute_consumer = rpc.AdapterConsumer(connection=self.conn, topic=FLAGS.compute_topic, proxy=self.compute) - self.compute_consumer.attach_to_twisted() + self.compute_consumer.attach_to_eventlet() + self.network = utils.import_class(FLAGS.network_manager)() + self.network_consumer = rpc.AdapterConsumer(connection=self.conn, + topic=FLAGS.network_topic, + proxy=self.network) + self.network_consumer.attach_to_eventlet() + self.manager = manager.AuthManager() self.user = self.manager.create_user('admin', 'admin', 'admin', True) @@ -85,15 +92,17 @@ class CloudTestCase(test.TrialTestCase): return cloud._gen_key(self.context, self.context.user.id, name) def test_console_output(self): - if FLAGS.connection_type == 'fake': - logging.debug("Can't test instances without a real virtual env.") - return - instance_id = 'foo' - inst = yield self.compute.run_instance(instance_id) - output = yield self.cloud.get_console_output(self.context, [instance_id]) - logging.debug(output) - self.assert_(output) - rv = yield self.compute.terminate_instance(instance_id) + image_id = FLAGS.default_image + instance_type = FLAGS.default_instance_type + max_count = 1 + kwargs = {'image_id': image_id, + 'instance_type': instance_type, + 'max_count': max_count } + rv = yield self.cloud.run_instances(self.context, **kwargs) + instance_id = rv['instancesSet'][0]['instanceId'] + output = yield self.cloud.get_console_output(context=self.context, instance_id=[instance_id]) + self.assertEquals(b64decode(output['output']), 'FAKE CONSOLE OUTPUT') + rv = yield self.cloud.terminate_instances(self.context, [instance_id]) def test_key_generation(self): @@ -236,7 +245,8 @@ class CloudTestCase(test.TrialTestCase): def test_update_of_instance_display_fields(self): inst = db.instance_create({}, {}) - self.cloud.update_instance(self.context, inst['ec2_id'], + ec2_id = cloud.internal_id_to_ec2_id(inst['internal_id']) + self.cloud.update_instance(self.context, ec2_id, display_name='c00l 1m4g3') inst = db.instance_get({}, inst['id']) self.assertEqual('c00l 1m4g3', inst['display_name']) diff --git a/nova/tests/objectstore_unittest.py b/nova/tests/objectstore_unittest.py index 1d6b9e826..872f1ab23 100644 --- a/nova/tests/objectstore_unittest.py +++ b/nova/tests/objectstore_unittest.py @@ -133,13 +133,22 @@ class ObjectStoreTestCase(test.TrialTestCase): self.assertRaises(NotFound, objectstore.bucket.Bucket, 'new_bucket') def test_images(self): + self.do_test_images('1mb.manifest.xml', True, + 'image_bucket1', 'i-testing1') + + def test_images_no_kernel_or_ramdisk(self): + self.do_test_images('1mb.no_kernel_or_ramdisk.manifest.xml', + False, 'image_bucket2', 'i-testing2') + + def do_test_images(self, manifest_file, expect_kernel_and_ramdisk, + image_bucket, image_name): "Test the image API." self.context.user = self.auth_manager.get_user('user1') self.context.project = self.auth_manager.get_project('proj1') # create a bucket for our bundle - objectstore.bucket.Bucket.create('image_bucket', self.context) - bucket = objectstore.bucket.Bucket('image_bucket') + objectstore.bucket.Bucket.create(image_bucket, self.context) + bucket = objectstore.bucket.Bucket(image_bucket) # upload an image manifest/parts bundle_path = os.path.join(os.path.dirname(__file__), 'bundle') @@ -147,18 +156,28 @@ class ObjectStoreTestCase(test.TrialTestCase): bucket[os.path.basename(path)] = open(path, 'rb').read() # register an image - image.Image.register_aws_image('i-testing', - 'image_bucket/1mb.manifest.xml', + image.Image.register_aws_image(image_name, + '%s/%s' % (image_bucket, manifest_file), self.context) # verify image - my_img = image.Image('i-testing') + my_img = image.Image(image_name) result_image_file = os.path.join(my_img.path, 'image') self.assertEqual(os.stat(result_image_file).st_size, 1048576) sha = hashlib.sha1(open(result_image_file).read()).hexdigest() self.assertEqual(sha, '3b71f43ff30f4b15b5cd85dd9e95ebc7e84eb5a3') + if expect_kernel_and_ramdisk: + # Verify the default kernel and ramdisk are set + self.assertEqual(my_img.metadata['kernelId'], 'aki-test') + self.assertEqual(my_img.metadata['ramdiskId'], 'ari-test') + else: + # Verify that the default kernel and ramdisk (the one from FLAGS) + # doesn't get embedded in the metadata + self.assertFalse('kernelId' in my_img.metadata) + self.assertFalse('ramdiskId' in my_img.metadata) + # verify image permissions self.context.user = self.auth_manager.get_user('user2') self.context.project = self.auth_manager.get_project('proj2') diff --git a/nova/utils.py b/nova/utils.py index d18dd9843..b1699bda8 100644 --- a/nova/utils.py +++ b/nova/utils.py @@ -126,7 +126,15 @@ def runthis(prompt, cmd, check_exit_code = True): def generate_uid(topic, size=8): - return '%s-%s' % (topic, ''.join([random.choice('01234567890abcdefghijklmnopqrstuvwxyz') for x in xrange(size)])) + if topic == "i": + # Instances have integer internal ids. + #TODO(gundlach): We should make this more than 32 bits, but we need to + #figure out how to make the DB happy with 64 bit integers. + return random.randint(0, 2**32-1) + else: + characters = '01234567890abcdefghijklmnopqrstuvwxyz' + choices = [random.choice(characters) for x in xrange(size)] + return '%s-%s' % (topic, ''.join(choices)) def generate_mac(): diff --git a/nova/virt/images.py b/nova/virt/images.py index a60bcc4c1..dc50764d9 100644 --- a/nova/virt/images.py +++ b/nova/virt/images.py @@ -29,6 +29,7 @@ from nova import flags from nova import process from nova.auth import manager from nova.auth import signer +from nova.objectstore import image FLAGS = flags.FLAGS diff --git a/run_tests.sh b/run_tests.sh index 6ea40d95e..ec727d094 100755 --- a/run_tests.sh +++ b/run_tests.sh @@ -6,6 +6,7 @@ function usage { echo "" echo " -V, --virtual-env Always use virtualenv. Install automatically if not present" echo " -N, --no-virtual-env Don't use virtualenv. Run tests in local environment" + echo " -f, --force Force a clean re-build of the virtual environment. Useful when dependencies have been added." echo " -h, --help Print this usage message" echo "" echo "Note: with no options specified, the script will try to run the tests in a virtual environment," @@ -14,20 +15,12 @@ function usage { exit } -function process_options { - array=$1 - elements=${#array[@]} - for (( x=0;x<$elements;x++)); do - process_option ${array[${x}]} - done -} - function process_option { - option=$1 - case $option in + case "$1" in -h|--help) usage;; -V|--virtual-env) let always_venv=1; let never_venv=0;; -N|--no-virtual-env) let always_venv=0; let never_venv=1;; + -f|--force) let force=1;; esac } @@ -35,9 +28,11 @@ venv=.nova-venv with_venv=tools/with_venv.sh always_venv=0 never_venv=0 -options=("$@") +force=0 -process_options $options +for arg in "$@"; do + process_option $arg +done if [ $never_venv -eq 1 ]; then # Just run the test suites in current environment @@ -45,6 +40,12 @@ if [ $never_venv -eq 1 ]; then exit fi +# Remove the virtual environment if --force used +if [ $force -eq 1 ]; then + echo "Cleaning virtualenv..." + rm -rf ${venv} +fi + if [ -e ${venv} ]; then ${with_venv} python run_tests.py $@ else |
