summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSoren Hansen <soren.hansen@rackspace.com>2010-10-12 07:21:25 +0200
committerSoren Hansen <soren.hansen@rackspace.com>2010-10-12 07:21:25 +0200
commite8a1cb4dd50b20359a9389c79564310a5d35c7bc (patch)
tree7b86cbec8c1acaba097749b2db6d56eb8ca39f0c
parentd13d4343435d1e6f597c480c3c8d13ba6b47796f (diff)
parented5d6ca32e2996e8218a5e6d70b9952619ef6564 (diff)
downloadnova-e8a1cb4dd50b20359a9389c79564310a5d35c7bc.tar.gz
nova-e8a1cb4dd50b20359a9389c79564310a5d35c7bc.tar.xz
nova-e8a1cb4dd50b20359a9389c79564310a5d35c7bc.zip
Merge trunk.
-rw-r--r--nova/api/__init__.py30
-rw-r--r--nova/api/cloud.py2
-rw-r--r--nova/api/ec2/cloud.py61
-rw-r--r--nova/api/ec2/images.py3
-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.py6
-rw-r--r--nova/db/api.py4
-rw-r--r--nova/db/sqlalchemy/api.py28
-rw-r--r--nova/db/sqlalchemy/models.py4
-rw-r--r--nova/fakerabbit.py14
-rw-r--r--nova/flags.py4
-rw-r--r--nova/image/service.py235
-rw-r--r--nova/objectstore/image.py10
-rw-r--r--nova/rpc.py9
-rw-r--r--nova/server.py6
-rw-r--r--nova/tests/api/__init__.py10
-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.py205
-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.py141
-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.py148
-rw-r--r--nova/tests/api/rackspace/test_images.py39
-rw-r--r--nova/tests/bundle/1mb.manifest.xml2
-rw-r--r--nova/tests/bundle/1mb.no_kernel_or_ramdisk.manifest.xml1
-rw-r--r--nova/tests/cloud_unittest.py34
-rw-r--r--nova/tests/objectstore_unittest.py29
-rw-r--r--nova/utils.py10
-rw-r--r--nova/virt/images.py1
-rwxr-xr-xrun_tests.sh25
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