summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorVishvananda Ishaya <vishvananda@yahoo.com>2010-09-28 16:54:57 -0700
committerVishvananda Ishaya <vishvananda@yahoo.com>2010-09-28 16:54:57 -0700
commitbd5816698316f64a2df700ed361b66e533eb9a31 (patch)
tree6a5b111ae15e2b9d7ca607864803bc2b6b1cfc8f
parent669cf475d11700064aa16f959077d0512e6b1531 (diff)
parent2b65cf963b8afbc4703f79d3057e5c19f2894baa (diff)
downloadnova-bd5816698316f64a2df700ed361b66e533eb9a31.tar.gz
nova-bd5816698316f64a2df700ed361b66e533eb9a31.tar.xz
nova-bd5816698316f64a2df700ed361b66e533eb9a31.zip
fixed merge conflicts
-rwxr-xr-xbin/nova-manage58
-rw-r--r--nova/adminclient.py73
-rw-r--r--nova/api/ec2/cloud.py44
-rw-r--r--nova/api/rackspace/__init__.py18
-rw-r--r--nova/api/rackspace/_id_translator.py2
-rw-r--r--nova/api/rackspace/auth.py8
-rw-r--r--nova/api/rackspace/backup_schedules.py (renamed from nova/api/rackspace/base.py)26
-rw-r--r--nova/api/rackspace/flavors.py4
-rw-r--r--nova/api/rackspace/images.py9
-rw-r--r--nova/api/rackspace/servers.py203
-rw-r--r--nova/api/rackspace/sharedipgroups.py4
-rw-r--r--nova/compute/manager.py10
-rw-r--r--nova/db/api.py15
-rw-r--r--nova/db/sqlalchemy/api.py67
-rw-r--r--nova/db/sqlalchemy/models.py16
-rw-r--r--nova/network/linux_net.py2
-rw-r--r--nova/network/manager.py2
-rw-r--r--nova/rpc.py45
-rw-r--r--nova/test.py54
-rw-r--r--nova/tests/access_unittest.py2
-rw-r--r--nova/tests/api/rackspace/auth.py4
-rw-r--r--nova/tests/api/rackspace/flavors.py15
-rw-r--r--nova/tests/api/rackspace/servers.py150
-rw-r--r--nova/tests/api/rackspace/test_helper.py60
-rw-r--r--nova/tests/api/test_helper.py1
-rw-r--r--nova/tests/api/wsgi_test.py57
-rw-r--r--nova/tests/auth_unittest.py3
-rw-r--r--nova/tests/cloud_unittest.py10
-rw-r--r--nova/tests/objectstore_unittest.py2
-rw-r--r--nova/tests/rpc_unittest.py17
-rw-r--r--nova/volume/manager.py10
-rw-r--r--nova/wsgi.py72
-rw-r--r--pylintrc3
-rw-r--r--setup.py2
34 files changed, 848 insertions, 220 deletions
diff --git a/bin/nova-manage b/bin/nova-manage
index baa1cb4db..fa6a49f36 100755
--- a/bin/nova-manage
+++ b/bin/nova-manage
@@ -135,15 +135,48 @@ class VpnCommands(object):
class ShellCommands(object):
- def run(self):
- "Runs a Python interactive interpreter. Tries to use IPython, if it's available."
- try:
- import IPython
- # Explicitly pass an empty list as arguments, because otherwise IPython
- # would use sys.argv from this script.
- shell = IPython.Shell.IPShell(argv=[])
- shell.mainloop()
- except ImportError:
+ def bpython(self):
+ """Runs a bpython shell.
+
+ Falls back to Ipython/python shell if unavailable"""
+ self.run('bpython')
+
+ def ipython(self):
+ """Runs an Ipython shell.
+
+ Falls back to Python shell if unavailable"""
+ self.run('ipython')
+
+ def python(self):
+ """Runs a python shell.
+
+ Falls back to Python shell if unavailable"""
+ self.run('python')
+
+ def run(self, shell=None):
+ """Runs a Python interactive interpreter.
+
+ args: [shell=bpython]"""
+ if not shell:
+ shell = 'bpython'
+
+ if shell == 'bpython':
+ try:
+ import bpython
+ bpython.embed()
+ except ImportError:
+ shell = 'ipython'
+ if shell == 'ipython':
+ try:
+ import IPython
+ # Explicitly pass an empty list as arguments, because otherwise IPython
+ # would use sys.argv from this script.
+ shell = IPython.Shell.IPShell(argv=[])
+ shell.mainloop()
+ except ImportError:
+ shell = 'python'
+
+ if shell == 'python':
import code
try: # Try activating rlcompleter, because it's handy.
import readline
@@ -156,6 +189,11 @@ class ShellCommands(object):
readline.parse_and_bind("tab:complete")
code.interact()
+ def script(self, path):
+ """Runs the script from the specifed path with flags set properly.
+ arguments: path"""
+ exec(compile(open(path).read(), path, 'exec'), locals(), globals())
+
class RoleCommands(object):
"""Class for managing roles."""
@@ -316,7 +354,7 @@ class FloatingIpCommands(object):
for floating_ip in floating_ips:
instance = None
if floating_ip['fixed_ip']:
- instance = floating_ip['fixed_ip']['instance']['str_id']
+ instance = floating_ip['fixed_ip']['instance']['ec2_id']
print "%s\t%s\t%s" % (floating_ip['host'],
floating_ip['address'],
instance)
diff --git a/nova/adminclient.py b/nova/adminclient.py
index 0ca32b1e5..fc9fcfde0 100644
--- a/nova/adminclient.py
+++ b/nova/adminclient.py
@@ -20,11 +20,17 @@ Nova User API client library.
"""
import base64
-
import boto
+import httplib
from boto.ec2.regioninfo import RegionInfo
+DEFAULT_CLC_URL='http://127.0.0.1:8773'
+DEFAULT_REGION='nova'
+DEFAULT_ACCESS_KEY='admin'
+DEFAULT_SECRET_KEY='admin'
+
+
class UserInfo(object):
"""
Information about a Nova user, as parsed through SAX
@@ -68,13 +74,13 @@ class UserRole(object):
def __init__(self, connection=None):
self.connection = connection
self.role = None
-
+
def __repr__(self):
return 'UserRole:%s' % self.role
def startElement(self, name, attrs, connection):
return None
-
+
def endElement(self, name, value, connection):
if name == 'role':
self.role = value
@@ -128,20 +134,20 @@ class ProjectMember(object):
def __init__(self, connection=None):
self.connection = connection
self.memberId = None
-
+
def __repr__(self):
return 'ProjectMember:%s' % self.memberId
def startElement(self, name, attrs, connection):
return None
-
+
def endElement(self, name, value, connection):
if name == 'member':
self.memberId = value
else:
setattr(self, name, str(value))
-
+
class HostInfo(object):
"""
Information about a Nova Host, as parsed through SAX:
@@ -171,35 +177,56 @@ class HostInfo(object):
class NovaAdminClient(object):
- def __init__(self, clc_ip='127.0.0.1', region='nova', access_key='admin',
- secret_key='admin', **kwargs):
- self.clc_ip = clc_ip
+ def __init__(self, clc_url=DEFAULT_CLC_URL, region=DEFAULT_REGION,
+ access_key=DEFAULT_ACCESS_KEY, secret_key=DEFAULT_SECRET_KEY,
+ **kwargs):
+ parts = self.split_clc_url(clc_url)
+
+ self.clc_url = clc_url
self.region = region
self.access = access_key
self.secret = secret_key
self.apiconn = boto.connect_ec2(aws_access_key_id=access_key,
aws_secret_access_key=secret_key,
- is_secure=False,
- region=RegionInfo(None, region, clc_ip),
- port=8773,
+ is_secure=parts['is_secure'],
+ region=RegionInfo(None,
+ region,
+ parts['ip']),
+ port=parts['port'],
path='/services/Admin',
**kwargs)
self.apiconn.APIVersion = 'nova'
- def connection_for(self, username, project, **kwargs):
+ def connection_for(self, username, project, clc_url=None, region=None,
+ **kwargs):
"""
Returns a boto ec2 connection for the given username.
"""
+ if not clc_url:
+ clc_url = self.clc_url
+ if not region:
+ region = self.region
+ parts = self.split_clc_url(clc_url)
user = self.get_user(username)
access_key = '%s:%s' % (user.accesskey, project)
- return boto.connect_ec2(
- aws_access_key_id=access_key,
- aws_secret_access_key=user.secretkey,
- is_secure=False,
- region=RegionInfo(None, self.region, self.clc_ip),
- port=8773,
- path='/services/Cloud',
- **kwargs)
+ return boto.connect_ec2(aws_access_key_id=access_key,
+ aws_secret_access_key=user.secretkey,
+ is_secure=parts['is_secure'],
+ region=RegionInfo(None,
+ self.region,
+ parts['ip']),
+ port=parts['port'],
+ path='/services/Cloud',
+ **kwargs)
+
+ def split_clc_url(self, clc_url):
+ """
+ Splits a cloud controller endpoint url.
+ """
+ parts = httplib.urlsplit(clc_url)
+ is_secure = parts.scheme == 'https'
+ ip, port = parts.netloc.split(':')
+ return {'ip': ip, 'port': int(port), 'is_secure': is_secure}
def get_users(self):
""" grabs the list of all users """
@@ -289,7 +316,7 @@ class NovaAdminClient(object):
if project.projectname != None:
return project
-
+
def create_project(self, projectname, manager_user, description=None,
member_users=None):
"""
@@ -322,7 +349,7 @@ class NovaAdminClient(object):
Adds a user to a project.
"""
return self.modify_project_member(user, project, operation='add')
-
+
def remove_project_member(self, user, project):
"""
Removes a user from a project.
diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py
index b7d260eb4..f3bd4f9d9 100644
--- a/nova/api/ec2/cloud.py
+++ b/nova/api/ec2/cloud.py
@@ -103,7 +103,7 @@ class CloudController(object):
result = {}
for instance in db.instance_get_all_by_project(None, project_id):
if instance['fixed_ip']:
- line = '%s slots=%d' % (instance['fixed_ip']['str_id'],
+ line = '%s slots=%d' % (instance['fixed_ip']['address'],
INSTANCE_TYPES[instance['instance_type']]['vcpus'])
key = str(instance['key_name'])
if key in result:
@@ -143,7 +143,7 @@ class CloudController(object):
},
'hostname': hostname,
'instance-action': 'none',
- 'instance-id': instance_ref['str_id'],
+ 'instance-id': instance_ref['ec2_id'],
'instance-type': instance_ref['instance_type'],
'local-hostname': hostname,
'local-ipv4': address,
@@ -245,7 +245,7 @@ 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_str(context, instance_id[0])
+ instance_ref = db.instance_get_by_ec2_id(context, instance_id[0])
return rpc.call('%s.%s' % (FLAGS.compute_topic,
instance_ref['host']),
{"method": "get_console_output",
@@ -264,7 +264,7 @@ class CloudController(object):
def _format_volume(self, context, volume):
v = {}
- v['volumeId'] = volume['str_id']
+ v['volumeId'] = volume['ec2_id']
v['status'] = volume['status']
v['size'] = volume['size']
v['availabilityZone'] = volume['availability_zone']
@@ -282,7 +282,7 @@ class CloudController(object):
'device': volume['mountpoint'],
'instanceId': volume['instance_id'],
'status': 'attached',
- 'volume_id': volume['str_id']}]
+ 'volume_id': volume['ec2_id']}]
else:
v['attachmentSet'] = [{}]
return v
@@ -314,13 +314,13 @@ class CloudController(object):
def attach_volume(self, context, volume_id, instance_id, device, **kwargs):
- volume_ref = db.volume_get_by_str(context, volume_id)
+ volume_ref = db.volume_get_by_ec2_id(context, volume_id)
# TODO(vish): abstract status checking?
if volume_ref['status'] != "available":
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_str(context, instance_id)
+ instance_ref = db.instance_get_by_ec2_id(context, instance_id)
host = instance_ref['host']
rpc.cast(db.queue_get_for(context, FLAGS.compute_topic, host),
{"method": "attach_volume",
@@ -336,7 +336,7 @@ class CloudController(object):
'volumeId': volume_ref['id']}
def detach_volume(self, context, volume_id, **kwargs):
- volume_ref = db.volume_get_by_str(context, volume_id)
+ volume_ref = db.volume_get_by_ec2_id(context, volume_id)
instance_ref = db.volume_get_instance(context, volume_ref['id'])
if not instance_ref:
raise exception.ApiError("Volume isn't attached to anything!")
@@ -356,7 +356,7 @@ class CloudController(object):
db.volume_detached(context)
return {'attachTime': volume_ref['attach_time'],
'device': volume_ref['mountpoint'],
- 'instanceId': instance_ref['str_id'],
+ 'instanceId': instance_ref['ec2_id'],
'requestId': context.request_id,
'status': volume_ref['attach_status'],
'volumeId': volume_ref['id']}
@@ -395,7 +395,7 @@ class CloudController(object):
if instance['image_id'] == FLAGS.vpn_image_id:
continue
i = {}
- i['instanceId'] = instance['str_id']
+ i['instanceId'] = instance['ec2_id']
i['imageId'] = instance['image_id']
i['instanceState'] = {
'code': instance['state'],
@@ -404,10 +404,10 @@ class CloudController(object):
fixed_addr = None
floating_addr = None
if instance['fixed_ip']:
- fixed_addr = instance['fixed_ip']['str_id']
+ fixed_addr = instance['fixed_ip']['address']
if instance['fixed_ip']['floating_ips']:
fixed = instance['fixed_ip']
- floating_addr = fixed['floating_ips'][0]['str_id']
+ floating_addr = fixed['floating_ips'][0]['address']
i['privateDnsName'] = fixed_addr
i['publicDnsName'] = floating_addr
i['dnsName'] = i['publicDnsName'] or i['privateDnsName']
@@ -442,11 +442,11 @@ class CloudController(object):
iterator = db.floating_ip_get_all_by_project(context,
context.project.id)
for floating_ip_ref in iterator:
- address = floating_ip_ref['str_id']
+ address = floating_ip_ref['address']
instance_id = None
if (floating_ip_ref['fixed_ip']
and floating_ip_ref['fixed_ip']['instance']):
- instance_id = floating_ip_ref['fixed_ip']['instance']['str_id']
+ instance_id = floating_ip_ref['fixed_ip']['instance']['ec2_id']
address_rv = {'public_ip': address,
'instance_id': instance_id}
if context.user.is_admin():
@@ -477,11 +477,11 @@ class CloudController(object):
rpc.cast(network_topic,
{"method": "deallocate_floating_ip",
"args": {"context": None,
- "floating_address": floating_ip_ref['str_id']}})
+ "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_str(context, instance_id)
+ instance_ref = db.instance_get_by_ec2_id(context, instance_id)
fixed_address = db.instance_get_fixed_address(context,
instance_ref['id'])
floating_ip_ref = db.floating_ip_get_by_address(context, public_ip)
@@ -489,7 +489,7 @@ class CloudController(object):
rpc.cast(network_topic,
{"method": "associate_floating_ip",
"args": {"context": None,
- "floating_address": floating_ip_ref['str_id'],
+ "floating_address": floating_ip_ref['address'],
"fixed_address": fixed_address}})
return {'associateResponse': ["Address associated."]}
@@ -499,7 +499,7 @@ class CloudController(object):
rpc.cast(network_topic,
{"method": "disassociate_floating_ip",
"args": {"context": None,
- "floating_address": floating_ip_ref['str_id']}})
+ "floating_address": floating_ip_ref['address']}})
return {'disassociateResponse': ["Address disassociated."]}
def _get_network_topic(self, context):
@@ -590,7 +590,7 @@ class CloudController(object):
inst = {}
inst['mac_address'] = utils.generate_mac()
inst['launch_index'] = num
- inst['hostname'] = instance_ref['str_id']
+ inst['hostname'] = instance_ref['ec2_id']
db.instance_update(context, inst_id, inst)
address = self.network_manager.allocate_fixed_ip(context,
inst_id,
@@ -619,7 +619,7 @@ class CloudController(object):
for id_str in instance_id:
logging.debug("Going to try and terminate %s" % id_str)
try:
- instance_ref = db.instance_get_by_str(context, id_str)
+ instance_ref = db.instance_get_by_ec2_id(context, id_str)
except exception.NotFound:
logging.warning("Instance %s was not found during terminate"
% id_str)
@@ -665,7 +665,7 @@ class CloudController(object):
def reboot_instances(self, context, instance_id, **kwargs):
"""instance_id is a list of instance ids"""
for id_str in instance_id:
- instance_ref = db.instance_get_by_str(context, id_str)
+ instance_ref = db.instance_get_by_ec2_id(context, id_str)
host = instance_ref['host']
rpc.cast(db.queue_get_for(context, FLAGS.compute_topic, host),
{"method": "reboot_instance",
@@ -675,7 +675,7 @@ class CloudController(object):
def delete_volume(self, context, volume_id, **kwargs):
# TODO: return error if not authorized
- volume_ref = db.volume_get_by_str(context, volume_id)
+ volume_ref = db.volume_get_by_ec2_id(context, volume_id)
if volume_ref['status'] != "available":
raise exception.ApiError("Volume status must be available")
now = datetime.datetime.utcnow()
diff --git a/nova/api/rackspace/__init__.py b/nova/api/rackspace/__init__.py
index c24d08585..98802663f 100644
--- a/nova/api/rackspace/__init__.py
+++ b/nova/api/rackspace/__init__.py
@@ -31,6 +31,7 @@ import webob
from nova import flags
from nova import utils
from nova import wsgi
+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
@@ -67,8 +68,10 @@ class AuthMiddleware(wsgi.Middleware):
if not user:
return webob.exc.HTTPUnauthorized()
- context = {'user': user}
- req.environ['nova.context'] = context
+
+ if not req.environ.has_key('nova.context'):
+ req.environ['nova.context'] = {}
+ req.environ['nova.context']['user'] = user
return self.application
class RateLimitingMiddleware(wsgi.Middleware):
@@ -145,11 +148,20 @@ class APIRouter(wsgi.Router):
def __init__(self):
mapper = routes.Mapper()
- mapper.resource("server", "servers", controller=servers.Controller())
+ mapper.resource("server", "servers", controller=servers.Controller(),
+ collection={ 'detail': 'GET'},
+ member={'action':'POST'})
+
+ mapper.resource("backup_schedule", "backup_schedules",
+ controller=backup_schedules.Controller(),
+ parent_resource=dict(member_name='server',
+ collection_name = 'servers'))
+
mapper.resource("image", "images", controller=images.Controller(),
collection={'detail': 'GET'})
mapper.resource("flavor", "flavors", controller=flavors.Controller(),
collection={'detail': 'GET'})
mapper.resource("sharedipgroup", "sharedipgroups",
controller=sharedipgroups.Controller())
+
super(APIRouter, self).__init__(mapper)
diff --git a/nova/api/rackspace/_id_translator.py b/nova/api/rackspace/_id_translator.py
index aec5fb6a5..333aa8434 100644
--- a/nova/api/rackspace/_id_translator.py
+++ b/nova/api/rackspace/_id_translator.py
@@ -37,6 +37,6 @@ class RackspaceAPIIdTranslator(object):
# every int id be used.)
return int(self._store.hget(self._fwd_key, str(opaque_id)))
- def from_rs_id(self, strategy_name, rs_id):
+ def from_rs_id(self, rs_id):
"""Convert a Rackspace id to a strategy-specific one."""
return self._store.hget(self._rev_key, rs_id)
diff --git a/nova/api/rackspace/auth.py b/nova/api/rackspace/auth.py
index ce5a967eb..8bfb0753e 100644
--- a/nova/api/rackspace/auth.py
+++ b/nova/api/rackspace/auth.py
@@ -1,13 +1,15 @@
import datetime
+import hashlib
import json
import time
+
import webob.exc
import webob.dec
-import hashlib
-from nova import flags
+
from nova import auth
-from nova import manager
from nova import db
+from nova import flags
+from nova import manager
from nova import utils
FLAGS = flags.FLAGS
diff --git a/nova/api/rackspace/base.py b/nova/api/rackspace/backup_schedules.py
index dd2c6543c..46da778ee 100644
--- a/nova/api/rackspace/base.py
+++ b/nova/api/rackspace/backup_schedules.py
@@ -15,16 +15,24 @@
# License for the specific language governing permissions and limitations
# under the License.
-from nova import wsgi
+import time
+from webob import exc
+from nova import wsgi
+from nova.api.rackspace import _id_translator
+import nova.image.service
class Controller(wsgi.Controller):
- """TODO(eday): Base controller for all rackspace controllers. What is this
- for? Is this just Rackspace specific? """
+ def __init__(self):
+ pass
+
+ def index(self, req, server_id):
+ return exc.HTTPNotFound()
+
+ def create(self, req, server_id):
+ """ No actual update method required, since the existing API allows
+ both create and update through a POST """
+ return exc.HTTPNotFound()
- @classmethod
- def render(cls, instance):
- if isinstance(instance, list):
- return {cls.entity_name: cls.render(instance)}
- else:
- return {"TODO": "TODO"}
+ def delete(self, req, server_id):
+ return exc.HTTPNotFound()
diff --git a/nova/api/rackspace/flavors.py b/nova/api/rackspace/flavors.py
index 60b35c939..3bcf170e5 100644
--- a/nova/api/rackspace/flavors.py
+++ b/nova/api/rackspace/flavors.py
@@ -15,11 +15,11 @@
# License for the specific language governing permissions and limitations
# under the License.
-from nova.api.rackspace import base
from nova.compute import instance_types
+from nova import wsgi
from webob import exc
-class Controller(base.Controller):
+class Controller(wsgi.Controller):
"""Flavor controller for the Rackspace API."""
_serialization_metadata = {
diff --git a/nova/api/rackspace/images.py b/nova/api/rackspace/images.py
index 2f3e928b9..11b058dec 100644
--- a/nova/api/rackspace/images.py
+++ b/nova/api/rackspace/images.py
@@ -15,12 +15,13 @@
# License for the specific language governing permissions and limitations
# under the License.
-import nova.image.service
-from nova.api.rackspace import base
-from nova.api.rackspace import _id_translator
from webob import exc
-class Controller(base.Controller):
+from nova import wsgi
+from nova.api.rackspace import _id_translator
+import nova.image.service
+
+class Controller(wsgi.Controller):
_serialization_metadata = {
'application/xml': {
diff --git a/nova/api/rackspace/servers.py b/nova/api/rackspace/servers.py
index 1815f7523..4ab04bde7 100644
--- a/nova/api/rackspace/servers.py
+++ b/nova/api/rackspace/servers.py
@@ -14,67 +14,194 @@
# 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 time
-from nova import db
+from webob import exc
+
from nova import flags
from nova import rpc
from nova import utils
-from nova.api.rackspace import base
+from nova import wsgi
+from nova.api.rackspace import _id_translator
+from nova.compute import power_state
+import nova.image.service
FLAGS = flags.FLAGS
-class Controller(base.Controller):
- entity_name = 'servers'
- def index(self, **kwargs):
- instances = []
- for inst in db.instance_get_all(None):
- instances.append(instance_details(inst))
- def show(self, **kwargs):
- instance_id = kwargs['id']
- return db.instance_get(None, instance_id)
+def translator_instance():
+ """ Helper method for initializing the image id translator """
+ service = nova.image.service.ImageService.load()
+ return _id_translator.RackspaceAPIIdTranslator(
+ "image", service.__class__.__name__)
- def delete(self, **kwargs):
- instance_id = kwargs['id']
- instance = db.instance_get(None, instance_id)
- if not instance:
- raise ServerNotFound("The requested server was not found")
- instance.destroy()
- return True
+def _filter_params(inst_dict):
+ """ Extracts all updatable parameters for a server update request """
+ keys = ['name', 'adminPass']
+ new_attrs = {}
+ for k in keys:
+ if inst_dict.has_key(k):
+ new_attrs[k] = inst_dict[k]
+ return new_attrs
+
+def _entity_list(entities):
+ """ Coerces a list of servers into proper dictionary format """
+ return dict(servers=entities)
+
+def _entity_detail(inst):
+ """ Maps everything to Rackspace-like attributes for return"""
+ power_mapping = {
+ power_state.NOSTATE: 'build',
+ power_state.RUNNING: 'active',
+ power_state.BLOCKED: 'active',
+ power_state.PAUSED: 'suspended',
+ power_state.SHUTDOWN: 'active',
+ power_state.SHUTOFF: 'active',
+ power_state.CRASHED: 'error'
+ }
+ inst_dict = {}
+
+ mapped_keys = dict(status='state', imageId='image_id',
+ flavorId='instance_type', name='server_name', id='id')
+
+ for k, v in mapped_keys.iteritems():
+ inst_dict[k] = inst[v]
+
+ inst_dict['status'] = power_mapping[inst_dict['status']]
+ inst_dict['addresses'] = dict(public=[], private=[])
+ inst_dict['metadata'] = {}
+ inst_dict['hostId'] = ''
+
+ return dict(server=inst_dict)
+
+def _entity_inst(inst):
+ """ Filters all model attributes save for id and name """
+ return dict(server=dict(id=inst['id'], name=inst['server_name']))
+
+class Controller(wsgi.Controller):
+ """ The Server API controller for the Openstack API """
+
+
+ _serialization_metadata = {
+ 'application/xml': {
+ "attributes": {
+ "server": [ "id", "imageId", "name", "flavorId", "hostId",
+ "status", "progress", "progress" ]
+ }
+ }
+ }
+
+ def __init__(self, db_driver=None):
+ if not db_driver:
+ db_driver = FLAGS.db_driver
+ self.db_driver = utils.import_object(db_driver)
+ super(Controller, self).__init__()
+
+ def index(self, req):
+ """ Returns a list of server names and ids for a given user """
+ user_id = req.environ['nova.context']['user']['id']
+ instance_list = self.db_driver.instance_get_all_by_user(None, user_id)
+ res = [_entity_inst(inst)['server'] for inst in instance_list]
+ return _entity_list(res)
+
+ def detail(self, req):
+ """ Returns a list of server details for a given user """
+ user_id = req.environ['nova.context']['user']['id']
+ res = [_entity_detail(inst)['server'] for inst in
+ self.db_driver.instance_get_all_by_user(None, user_id)]
+ return _entity_list(res)
+
+ def show(self, req, id):
+ """ Returns server details by server id """
+ user_id = req.environ['nova.context']['user']['id']
+ inst = self.db_driver.instance_get(None, id)
+ if inst:
+ if inst.user_id == user_id:
+ return _entity_detail(inst)
+ raise exc.HTTPNotFound()
+
+ def delete(self, req, id):
+ """ Destroys a server """
+ user_id = req.environ['nova.context']['user']['id']
+ instance = self.db_driver.instance_get(None, id)
+ if instance and instance['user_id'] == user_id:
+ self.db_driver.instance_destroy(None, id)
+ return exc.HTTPAccepted()
+ return exc.HTTPNotFound()
+
+ def create(self, req):
+ """ Creates a new server for a given user """
+ if not req.environ.has_key('inst_dict'):
+ return exc.HTTPUnprocessableEntity()
+
+ inst = self._build_server_instance(req)
- def create(self, **kwargs):
- inst = self.build_server_instance(kwargs['server'])
rpc.cast(
FLAGS.compute_topic, {
"method": "run_instance",
"args": {"instance_id": inst['id']}})
+ return _entity_inst(inst)
- def update(self, **kwargs):
- instance_id = kwargs['id']
- instance = db.instance_get(None, instance_id)
+ def update(self, req, id):
+ """ Updates the server name or password """
+ if not req.environ.has_key('inst_dict'):
+ return exc.HTTPUnprocessableEntity()
+
+ instance = self.db_driver.instance_get(None, id)
if not instance:
- raise ServerNotFound("The requested server was not found")
- instance.update(kwargs['server'])
- instance.save()
+ return exc.HTTPNotFound()
+
+ attrs = req.environ['nova.context'].get('model_attributes', None)
+ if attrs:
+ self.db_driver.instance_update(None, id, _filter_params(attrs))
+ return exc.HTTPNoContent()
+
+ def action(self, req, id):
+ """ multi-purpose method used to reboot, rebuild, and
+ resize a server """
+ if not req.environ.has_key('inst_dict'):
+ return exc.HTTPUnprocessableEntity()
- def build_server_instance(self, env):
+ def _build_server_instance(self, req):
"""Build instance data structure and save it to the data store."""
- reservation = utils.generate_uid('r')
ltime = time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime())
inst = {}
- inst['name'] = env['server']['name']
- inst['image_id'] = env['server']['imageId']
+
+ env = req.environ['inst_dict']
+
+ image_id = env['server']['imageId']
+ opaque_id = translator_instance().from_rs_id(image_id)
+
+ inst['name'] = env['server']['server_name']
+ inst['image_id'] = opaque_id
inst['instance_type'] = env['server']['flavorId']
- inst['user_id'] = env['user']['id']
- inst['project_id'] = env['project']['id']
- inst['reservation_id'] = reservation
+
+ user_id = req.environ['nova.context']['user']['id']
+ inst['user_id'] = user_id
+
inst['launch_time'] = ltime
inst['mac_address'] = utils.generate_mac()
- inst_id = db.instance_create(None, inst)['id']
- address = self.network_manager.allocate_fixed_ip(None, inst_id)
- # key_data, key_name, ami_launch_index
- # TODO(todd): key data or root password
- inst.save()
+
+ inst['project_id'] = env['project']['id']
+ inst['reservation_id'] = reservation
+ reservation = utils.generate_uid('r')
+
+ address = self.network.allocate_ip(
+ inst['user_id'],
+ inst['project_id'],
+ mac=inst['mac_address'])
+
+ inst['private_dns_name'] = str(address)
+ inst['bridge_name'] = network.BridgedNetwork.get_network_for_project(
+ inst['user_id'],
+ inst['project_id'],
+ 'default')['bridge_name']
+
+ ref = self.db_driver.instance_create(None, inst)
+ inst['id'] = ref.id
+
return inst
+
+
diff --git a/nova/api/rackspace/sharedipgroups.py b/nova/api/rackspace/sharedipgroups.py
index 986f11434..4d2d0ede1 100644
--- a/nova/api/rackspace/sharedipgroups.py
+++ b/nova/api/rackspace/sharedipgroups.py
@@ -15,4 +15,6 @@
# License for the specific language governing permissions and limitations
# under the License.
-class Controller(object): pass
+from nova import wsgi
+
+class Controller(wsgi.Controller): pass
diff --git a/nova/compute/manager.py b/nova/compute/manager.py
index 24538e4f1..f370ede8b 100644
--- a/nova/compute/manager.py
+++ b/nova/compute/manager.py
@@ -67,7 +67,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['str_id'] in self.driver.list_instances():
+ if instance_ref['ec2_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']
@@ -129,7 +129,7 @@ class ComputeManager(manager.Manager):
raise exception.Error(
'trying to reboot a non-running'
'instance: %s (state: %s excepted: %s)' %
- (instance_ref['str_id'],
+ (instance_ref['ec2_id'],
instance_ref['state'],
power_state.RUNNING))
@@ -151,7 +151,7 @@ class ComputeManager(manager.Manager):
if FLAGS.connection_type == 'libvirt':
fname = os.path.abspath(os.path.join(FLAGS.instances_path,
- instance_ref['str_id'],
+ instance_ref['ec2_id'],
'console.log'))
with open(fname, 'r') as f:
output = f.read()
@@ -174,7 +174,7 @@ class ComputeManager(manager.Manager):
instance_ref = self.db.instance_get(context, instance_id)
dev_path = yield self.volume_manager.setup_compute_volume(context,
volume_id)
- yield self.driver.attach_volume(instance_ref['str_id'],
+ yield self.driver.attach_volume(instance_ref['ec2_id'],
dev_path,
mountpoint)
self.db.volume_attached(context, volume_id, instance_id, mountpoint)
@@ -189,7 +189,7 @@ class ComputeManager(manager.Manager):
volume_id)
instance_ref = self.db.instance_get(context, instance_id)
volume_ref = self.db.volume_get(context, volume_id)
- yield self.driver.detach_volume(instance_ref['str_id'],
+ yield self.driver.detach_volume(instance_ref['ec2_id'],
volume_ref['mountpoint'])
self.db.volume_detached(context, volume_id)
defer.returnValue(True)
diff --git a/nova/db/api.py b/nova/db/api.py
index 2c8bca764..3d633dd0f 100644
--- a/nova/db/api.py
+++ b/nova/db/api.py
@@ -256,6 +256,9 @@ def instance_get_all(context):
"""Get all instances."""
return IMPL.instance_get_all(context)
+def instance_get_all_by_user(context, user_id):
+ """Get all instances."""
+ return IMPL.instance_get_all(context, user_id)
def instance_get_all_by_project(context, project_id):
"""Get all instance belonging to a project."""
@@ -277,9 +280,9 @@ def instance_get_floating_address(context, instance_id):
return IMPL.instance_get_floating_address(context, instance_id)
-def instance_get_by_str(context, str_id):
- """Get an instance by string id."""
- return IMPL.instance_get_by_str(context, str_id)
+def instance_get_by_ec2_id(context, ec2_id):
+ """Get an instance by ec2 id."""
+ return IMPL.instance_get_by_ec2_id(context, ec2_id)
def instance_is_vpn(context, instance_id):
@@ -542,9 +545,9 @@ def volume_get_all_by_project(context, project_id):
return IMPL.volume_get_all_by_project(context, project_id)
-def volume_get_by_str(context, str_id):
- """Get a volume by string id."""
- return IMPL.volume_get_by_str(context, str_id)
+def volume_get_by_ec2_id(context, ec2_id):
+ """Get a volume by ec2 id."""
+ return IMPL.volume_get_by_ec2_id(context, ec2_id)
def volume_get_shelf_and_blade(context, volume_id):
diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py
index f55e063da..05d38d73d 100644
--- a/nova/db/sqlalchemy/api.py
+++ b/nova/db/sqlalchemy/api.py
@@ -24,11 +24,12 @@ import sys
from nova import db
from nova import exception
from nova import flags
+from nova import utils
from nova.db.sqlalchemy import models
from nova.db.sqlalchemy.session import get_session
from sqlalchemy import or_
from sqlalchemy.orm import joinedload_all
-from sqlalchemy.sql import func
+from sqlalchemy.sql import exists, func
FLAGS = flags.FLAGS
@@ -63,6 +64,7 @@ def service_get_all_by_topic(context, topic):
session = get_session()
return session.query(models.Service
).filter_by(deleted=False
+ ).filter_by(disabled=False
).filter_by(topic=topic
).all()
@@ -72,6 +74,7 @@ def _service_get_all_topic_subquery(_context, session, topic, subq, label):
return session.query(models.Service, func.coalesce(sort_value, 0)
).filter_by(topic=topic
).filter_by(deleted=False
+ ).filter_by(disabled=False
).outerjoin((subq, models.Service.host == subq.c.host)
).order_by(sort_value
).all()
@@ -377,9 +380,15 @@ def instance_create(_context, values):
instance_ref = models.Instance()
for (key, value) in values.iteritems():
instance_ref[key] = value
- instance_ref.save()
- return instance_ref
+ 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
+ instance_ref.save(session=session)
+ return instance_ref
def instance_data_get_for_project(_context, project_id):
session = get_session()
@@ -410,6 +419,13 @@ def instance_get_all(context):
).filter_by(deleted=_deleted(context)
).all()
+def instance_get_all_by_user(context, user_id):
+ session = get_session()
+ return session.query(models.Instance
+ ).options(joinedload_all('fixed_ip.floating_ips')
+ ).filter_by(deleted=_deleted(context)
+ ).filter_by(user_id=user_id
+ ).all()
def instance_get_all_by_project(context, project_id):
session = get_session()
@@ -429,8 +445,22 @@ def instance_get_all_by_reservation(_context, reservation_id):
).all()
-def instance_get_by_str(context, str_id):
- return models.Instance.find_by_str(str_id, deleted=_deleted(context))
+def instance_get_by_ec2_id(context, ec2_id):
+ session = get_session()
+ instance_ref = session.query(models.Instance
+ ).filter_by(ec2_id=ec2_id
+ ).filter_by(deleted=_deleted(context)
+ ).first()
+ if not instance_ref:
+ raise exception.NotFound('Instance %s not found' % (ec2_id))
+
+ return instance_ref
+
+
+def instance_ec2_id_exists(context, ec2_id, session=None):
+ if not session:
+ session = get_session()
+ return session.query(exists().where(models.Instance.id==ec2_id)).one()[0]
def instance_get_fixed_address(_context, instance_id):
@@ -790,7 +820,14 @@ def volume_create(_context, values):
volume_ref = models.Volume()
for (key, value) in values.iteritems():
volume_ref[key] = value
- volume_ref.save()
+
+ session = get_session()
+ with session.begin():
+ while volume_ref.ec2_id == None:
+ ec2_id = utils.generate_uid(volume_ref.__prefix__)
+ if not volume_ec2_id_exists(_context, ec2_id, session=session):
+ volume_ref.ec2_id = ec2_id
+ volume_ref.save(session=session)
return volume_ref
@@ -843,8 +880,22 @@ def volume_get_all_by_project(context, project_id):
).all()
-def volume_get_by_str(context, str_id):
- return models.Volume.find_by_str(str_id, deleted=_deleted(context))
+def volume_get_by_ec2_id(context, ec2_id):
+ session = get_session()
+ volume_ref = session.query(models.Volume
+ ).filter_by(ec2_id=ec2_id
+ ).filter_by(deleted=_deleted(context)
+ ).first()
+ if not volume_ref:
+ raise exception.NotFound('Volume %s not found' % (ec2_id))
+
+ return volume_ref
+
+
+def volume_ec2_id_exists(context, ec2_id, session=None):
+ if not session:
+ session = get_session()
+ return session.query(exists().where(models.Volume.id==ec2_id)).one()[0]
def volume_get_instance(_context, volume_id):
diff --git a/nova/db/sqlalchemy/models.py b/nova/db/sqlalchemy/models.py
index f6ba7953f..3d2460c39 100644
--- a/nova/db/sqlalchemy/models.py
+++ b/nova/db/sqlalchemy/models.py
@@ -126,6 +126,7 @@ class NovaBase(object):
# __tablename__ = 'images'
# __prefix__ = 'ami'
# id = Column(Integer, primary_key=True)
+# ec2_id = Column(String(12), unique=True)
# user_id = Column(String(255))
# project_id = Column(String(255))
# image_type = Column(String(255))
@@ -173,6 +174,7 @@ class Service(BASE, NovaBase):
binary = Column(String(255))
topic = Column(String(255))
report_count = Column(Integer, nullable=False, default=0)
+ disabled = Column(Boolean, default=False)
@classmethod
def find_by_args(cls, host, binary, session=None, deleted=False):
@@ -195,6 +197,7 @@ class Instance(BASE, NovaBase):
__tablename__ = 'instances'
__prefix__ = 'i'
id = Column(Integer, primary_key=True)
+ ec2_id = Column(String(10), unique=True)
user_id = Column(String(255))
project_id = Column(String(255))
@@ -209,12 +212,14 @@ class Instance(BASE, NovaBase):
@property
def name(self):
- return self.str_id
+ return self.ec2_id
image_id = Column(String(255))
kernel_id = Column(String(255))
ramdisk_id = Column(String(255))
+ server_name = Column(String(255))
+
# image_id = Column(Integer, ForeignKey('images.id'), nullable=True)
# kernel_id = Column(Integer, ForeignKey('images.id'), nullable=True)
# ramdisk_id = Column(Integer, ForeignKey('images.id'), nullable=True)
@@ -265,6 +270,7 @@ class Volume(BASE, NovaBase):
__tablename__ = 'volumes'
__prefix__ = 'vol'
id = Column(Integer, primary_key=True)
+ ec2_id = Column(String(12), unique=True)
user_id = Column(String(255))
project_id = Column(String(255))
@@ -398,8 +404,8 @@ class NetworkIndex(BASE, NovaBase):
uselist=False))
class AuthToken(BASE, NovaBase):
- """Represents an authorization token for all API transactions. Fields
- are a string representing the actual token and a user id for mapping
+ """Represents an authorization token for all API transactions. Fields
+ are a string representing the actual token and a user id for mapping
to the actual user"""
__tablename__ = 'auth_tokens'
token_hash = Column(String(255), primary_key=True)
@@ -454,10 +460,6 @@ class FloatingIp(BASE, NovaBase):
project_id = Column(String(255))
host = Column(String(255)) # , ForeignKey('hosts.id'))
- @property
- def str_id(self):
- return self.address
-
@classmethod
def find_by_str(cls, str_id, session=None, deleted=False):
if not session:
diff --git a/nova/network/linux_net.py b/nova/network/linux_net.py
index 621ae54ea..b5346218a 100644
--- a/nova/network/linux_net.py
+++ b/nova/network/linux_net.py
@@ -177,7 +177,7 @@ def _host_dhcp(fixed_ip_ref):
instance_ref = fixed_ip_ref['instance']
return "%s,%s.novalocal,%s" % (instance_ref['mac_address'],
instance_ref['hostname'],
- fixed_ip_ref['str_id'])
+ fixed_ip_ref['address'])
def _execute(cmd, *args, **kwargs):
diff --git a/nova/network/manager.py b/nova/network/manager.py
index f4a7d16c1..28c20e994 100644
--- a/nova/network/manager.py
+++ b/nova/network/manager.py
@@ -270,7 +270,7 @@ class VlanManager(NetworkManager):
raise exception.Error("IP %s leased to bad mac %s vs %s" %
(address, instance_ref['mac_address'], mac))
self.db.fixed_ip_update(context,
- fixed_ip_ref['str_id'],
+ fixed_ip_ref['address'],
{'leased': True})
def release_fixed_ip(self, context, mac, address):
diff --git a/nova/rpc.py b/nova/rpc.py
index 6363335ea..fe52ad35f 100644
--- a/nova/rpc.py
+++ b/nova/rpc.py
@@ -84,19 +84,6 @@ class Consumer(messaging.Consumer):
self.failed_connection = False
super(Consumer, self).__init__(*args, **kwargs)
- # TODO(termie): it would be nice to give these some way of automatically
- # cleaning up after themselves
- def attach_to_tornado(self, io_inst=None):
- """Attach a callback to tornado that fires 10 times a second"""
- from tornado import ioloop
- if io_inst is None:
- io_inst = ioloop.IOLoop.instance()
-
- injected = ioloop.PeriodicCallback(
- lambda: self.fetch(enable_callbacks=True), 100, io_loop=io_inst)
- injected.start()
- return injected
-
def fetch(self, no_ack=None, auto_ack=None, enable_callbacks=False):
"""Wraps the parent fetch with some logic for failed connections"""
# TODO(vish): the logic for failed connections and logging should be
@@ -124,6 +111,7 @@ class Consumer(messaging.Consumer):
"""Attach a callback to twisted that fires 10 times a second"""
loop = task.LoopingCall(self.fetch, enable_callbacks=True)
loop.start(interval=0.1)
+ return loop
class Publisher(messaging.Publisher):
@@ -294,6 +282,37 @@ def call(topic, msg):
return wait_msg.result
+def call_twisted(topic, msg):
+ """Sends a message on a topic and wait for a response"""
+ LOG.debug("Making asynchronous call...")
+ msg_id = uuid.uuid4().hex
+ msg.update({'_msg_id': msg_id})
+ LOG.debug("MSG_ID is %s" % (msg_id))
+
+ conn = Connection.instance()
+ d = defer.Deferred()
+ consumer = DirectConsumer(connection=conn, msg_id=msg_id)
+
+ def deferred_receive(data, message):
+ """Acks message and callbacks or errbacks"""
+ message.ack()
+ if data['failure']:
+ return d.errback(RemoteError(*data['failure']))
+ else:
+ return d.callback(data['result'])
+
+ consumer.register_callback(deferred_receive)
+ injected = consumer.attach_to_twisted()
+
+ # clean up after the injected listened and return x
+ d.addCallback(lambda x: injected.stop() and x or x)
+
+ publisher = TopicPublisher(connection=conn, topic=topic)
+ publisher.send(msg)
+ publisher.close()
+ return d
+
+
def cast(topic, msg):
"""Sends a message on a topic without waiting for a response"""
LOG.debug("Making asynchronous cast...")
diff --git a/nova/test.py b/nova/test.py
index c392c8a84..1f4b33272 100644
--- a/nova/test.py
+++ b/nova/test.py
@@ -33,6 +33,7 @@ from twisted.trial import unittest
from nova import fakerabbit
from nova import flags
+from nova import rpc
FLAGS = flags.FLAGS
@@ -62,19 +63,29 @@ class TrialTestCase(unittest.TestCase):
self.mox = mox.Mox()
self.stubs = stubout.StubOutForTesting()
self.flag_overrides = {}
+ self.injected = []
+ self._monkeyPatchAttach()
def tearDown(self): # pylint: disable-msg=C0103
"""Runs after each test method to finalize/tear down test environment"""
- super(TrialTestCase, self).tearDown()
self.reset_flags()
self.mox.UnsetStubs()
self.stubs.UnsetAll()
self.stubs.SmartUnsetAll()
self.mox.VerifyAll()
+
+ rpc.Consumer.attach_to_twisted = self.originalAttach
+ for x in self.injected:
+ try:
+ x.stop()
+ except AssertionError:
+ pass
if FLAGS.fake_rabbit:
fakerabbit.reset_all()
+ super(TrialTestCase, self).tearDown()
+
def flags(self, **kw):
"""Override flag variables for a test"""
for k, v in kw.iteritems():
@@ -90,16 +101,51 @@ class TrialTestCase(unittest.TestCase):
for k, v in self.flag_overrides.iteritems():
setattr(FLAGS, k, v)
+ def run(self, result=None):
+ test_method = getattr(self, self._testMethodName)
+ setattr(self,
+ self._testMethodName,
+ self._maybeInlineCallbacks(test_method, result))
+ rv = super(TrialTestCase, self).run(result)
+ setattr(self, self._testMethodName, test_method)
+ return rv
+
+ def _maybeInlineCallbacks(self, func, result):
+ def _wrapped():
+ g = func()
+ if isinstance(g, defer.Deferred):
+ return g
+ if not hasattr(g, 'send'):
+ return defer.succeed(g)
+
+ inlined = defer.inlineCallbacks(func)
+ d = inlined()
+ return d
+ _wrapped.func_name = func.func_name
+ return _wrapped
+
+ def _monkeyPatchAttach(self):
+ self.originalAttach = rpc.Consumer.attach_to_twisted
+ def _wrapped(innerSelf):
+ rv = self.originalAttach(innerSelf)
+ self.injected.append(rv)
+ return rv
+
+ _wrapped.func_name = self.originalAttach.func_name
+ rpc.Consumer.attach_to_twisted = _wrapped
+
class BaseTestCase(TrialTestCase):
# TODO(jaypipes): Can this be moved into the TrialTestCase class?
- """Base test case class for all unit tests."""
+ """Base test case class for all unit tests.
+
+ DEPRECATED: This is being removed once Tornado is gone, use TrialTestCase.
+ """
def setUp(self): # pylint: disable-msg=C0103
"""Run before each test method to initialize test environment"""
super(BaseTestCase, self).setUp()
# TODO(termie): we could possibly keep a more global registry of
# the injected listeners... this is fine for now though
- self.injected = []
self.ioloop = ioloop.IOLoop.instance()
self._waiting = None
@@ -109,8 +155,6 @@ class BaseTestCase(TrialTestCase):
def tearDown(self):# pylint: disable-msg=C0103
"""Runs after each test method to finalize/tear down test environment"""
super(BaseTestCase, self).tearDown()
- for x in self.injected:
- x.stop()
if FLAGS.fake_rabbit:
fakerabbit.reset_all()
diff --git a/nova/tests/access_unittest.py b/nova/tests/access_unittest.py
index c8a49d2ca..4b40ffd0a 100644
--- a/nova/tests/access_unittest.py
+++ b/nova/tests/access_unittest.py
@@ -31,7 +31,7 @@ FLAGS = flags.FLAGS
class Context(object):
pass
-class AccessTestCase(test.BaseTestCase):
+class AccessTestCase(test.TrialTestCase):
def setUp(self):
super(AccessTestCase, self).setUp()
um = manager.AuthManager()
diff --git a/nova/tests/api/rackspace/auth.py b/nova/tests/api/rackspace/auth.py
index 429c22ad2..a6e10970f 100644
--- a/nova/tests/api/rackspace/auth.py
+++ b/nova/tests/api/rackspace/auth.py
@@ -15,8 +15,8 @@ class Test(unittest.TestCase):
'__init__', test_helper.fake_auth_init)
test_helper.FakeAuthManager.auth_data = {}
test_helper.FakeAuthDatabase.data = {}
- self.stubs.Set(nova.api.rackspace, 'RateLimitingMiddleware',
- test_helper.FakeRateLimiter)
+ test_helper.stub_out_rate_limiting(self.stubs)
+ test_helper.stub_for_testing(self.stubs)
def tearDown(self):
self.stubs.UnsetAll()
diff --git a/nova/tests/api/rackspace/flavors.py b/nova/tests/api/rackspace/flavors.py
index fb8ba94a5..7bd1ea1c4 100644
--- a/nova/tests/api/rackspace/flavors.py
+++ b/nova/tests/api/rackspace/flavors.py
@@ -16,19 +16,32 @@
# under the License.
import unittest
+import stubout
+import nova.api
from nova.api.rackspace import flavors
+from nova.tests.api.rackspace import test_helper
from nova.tests.api.test_helper import *
class FlavorsTest(unittest.TestCase):
def setUp(self):
self.stubs = stubout.StubOutForTesting()
+ test_helper.FakeAuthManager.auth_data = {}
+ test_helper.FakeAuthDatabase.data = {}
+ test_helper.stub_for_testing(self.stubs)
+ test_helper.stub_out_rate_limiting(self.stubs)
+ test_helper.stub_out_auth(self.stubs)
def tearDown(self):
self.stubs.UnsetAll()
def test_get_flavor_list(self):
- pass
+ req = webob.Request.blank('/v1.0/flavors')
+ res = req.get_response(nova.api.API())
+ print res
def test_get_flavor_by_id(self):
pass
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/nova/tests/api/rackspace/servers.py b/nova/tests/api/rackspace/servers.py
index 6d628e78a..9fd8e5e88 100644
--- a/nova/tests/api/rackspace/servers.py
+++ b/nova/tests/api/rackspace/servers.py
@@ -15,44 +15,168 @@
# License for the specific language governing permissions and limitations
# under the License.
+import json
import unittest
+import stubout
+
+from nova import db
+from nova import flags
+import nova.api.rackspace
from nova.api.rackspace import servers
+import nova.db.api
+from nova.db.sqlalchemy.models import Instance
from nova.tests.api.test_helper import *
+from nova.tests.api.rackspace import test_helper
+
+FLAGS = flags.FLAGS
+
+def return_server(context, id):
+ return stub_instance(id)
+
+def return_servers(context, user_id=1):
+ return [stub_instance(i, user_id) for i in xrange(5)]
+
+
+def stub_instance(id, user_id=1):
+ return Instance(
+ id=id, state=0, image_id=10, server_name='server%s'%id,
+ user_id=user_id
+ )
class ServersTest(unittest.TestCase):
def setUp(self):
self.stubs = stubout.StubOutForTesting()
+ test_helper.FakeAuthManager.auth_data = {}
+ test_helper.FakeAuthDatabase.data = {}
+ test_helper.stub_for_testing(self.stubs)
+ test_helper.stub_out_rate_limiting(self.stubs)
+ test_helper.stub_out_auth(self.stubs)
+ self.stubs.Set(nova.db.api, 'instance_get_all', return_servers)
+ self.stubs.Set(nova.db.api, 'instance_get', return_server)
+ self.stubs.Set(nova.db.api, 'instance_get_all_by_user',
+ return_servers)
def tearDown(self):
self.stubs.UnsetAll()
- def test_get_server_list(self):
- pass
-
- def test_create_instance(self):
- pass
-
def test_get_server_by_id(self):
- pass
+ 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']['name'], 'server1')
def test_get_backup_schedule(self):
pass
- def test_get_server_details(self):
+ def test_get_server_list(self):
+ req = webob.Request.blank('/v1.0/servers')
+ res = req.get_response(nova.api.API())
+ res_dict = json.loads(res.body)
+
+ i = 0
+ for s in res_dict['servers']:
+ self.assertEqual(s['id'], i)
+ self.assertEqual(s['name'], 'server%d'%i)
+ self.assertEqual(s.get('imageId', None), None)
+ i += 1
+
+ #def test_create_instance(self):
+ # test_helper.stub_out_image_translator(self.stubs)
+ # body = dict(server=dict(
+ # name='server_test', imageId=2, flavorId=2, metadata={},
+ # personality = {}
+ # ))
+ # req = webob.Request.blank('/v1.0/servers')
+ # req.method = 'POST'
+ # req.body = json.dumps(body)
+
+ # res = req.get_response(nova.api.API())
+
+ # print res
+ def test_update_server_password(self):
pass
- def test_get_server_ips(self):
+ def test_update_server_name(self):
pass
+ def test_create_backup_schedules(self):
+ req = webob.Request.blank('/v1.0/servers/1/backup_schedules')
+ req.method = 'POST'
+ res = req.get_response(nova.api.API())
+ self.assertEqual(res.status, '404 Not Found')
+
+ def test_delete_backup_schedules(self):
+ req = webob.Request.blank('/v1.0/servers/1/backup_schedules')
+ req.method = 'DELETE'
+ res = req.get_response(nova.api.API())
+ self.assertEqual(res.status, '404 Not Found')
+
+ def test_get_server_backup_schedules(self):
+ req = webob.Request.blank('/v1.0/servers/1/backup_schedules')
+ res = req.get_response(nova.api.API())
+ self.assertEqual(res.status, '404 Not Found')
+
+ def test_get_all_server_details(self):
+ req = webob.Request.blank('/v1.0/servers/detail')
+ res = req.get_response(nova.api.API())
+ res_dict = json.loads(res.body)
+
+ i = 0
+ for s in res_dict['servers']:
+ self.assertEqual(s['id'], i)
+ self.assertEqual(s['name'], 'server%d'%i)
+ self.assertEqual(s['imageId'], 10)
+ i += 1
+
def test_server_reboot(self):
- pass
+ body = dict(server=dict(
+ name='server_test', imageId=2, flavorId=2, metadata={},
+ personality = {}
+ ))
+ req = webob.Request.blank('/v1.0/servers/1/action')
+ req.method = 'POST'
+ req.content_type= 'application/json'
+ req.body = json.dumps(body)
+ res = req.get_response(nova.api.API())
def test_server_rebuild(self):
- pass
+ body = dict(server=dict(
+ name='server_test', imageId=2, flavorId=2, metadata={},
+ personality = {}
+ ))
+ req = webob.Request.blank('/v1.0/servers/1/action')
+ req.method = 'POST'
+ req.content_type= 'application/json'
+ req.body = json.dumps(body)
+ res = req.get_response(nova.api.API())
def test_server_resize(self):
- pass
+ body = dict(server=dict(
+ name='server_test', imageId=2, flavorId=2, metadata={},
+ personality = {}
+ ))
+ req = webob.Request.blank('/v1.0/servers/1/action')
+ req.method = 'POST'
+ req.content_type= 'application/json'
+ req.body = json.dumps(body)
+ res = req.get_response(nova.api.API())
def test_delete_server_instance(self):
- pass
+ req = webob.Request.blank('/v1.0/servers/1')
+ req.method = 'DELETE'
+
+ self.server_delete_called = False
+ def instance_destroy_mock(context, id):
+ self.server_delete_called = True
+
+ self.stubs.Set(nova.db.api, 'instance_destroy',
+ instance_destroy_mock)
+
+ res = req.get_response(nova.api.API())
+ self.assertEqual(res.status, '202 Accepted')
+ self.assertEqual(self.server_delete_called, True)
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/nova/tests/api/rackspace/test_helper.py b/nova/tests/api/rackspace/test_helper.py
index be14e2de8..aa7fb382c 100644
--- a/nova/tests/api/rackspace/test_helper.py
+++ b/nova/tests/api/rackspace/test_helper.py
@@ -1,8 +1,17 @@
+import datetime
+import json
+
import webob
import webob.dec
-import datetime
-from nova.wsgi import Router
+
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.wsgi import Router
+
+FLAGS = flags.FLAGS
class Context(object):
pass
@@ -24,6 +33,53 @@ def fake_auth_init(self):
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_image_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_for_testing(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 = {}
diff --git a/nova/tests/api/test_helper.py b/nova/tests/api/test_helper.py
index 8151a4af6..d0a2cc027 100644
--- a/nova/tests/api/test_helper.py
+++ b/nova/tests/api/test_helper.py
@@ -1,4 +1,5 @@
import webob.dec
+from nova import wsgi
class APIStub(object):
"""Class to verify request and mark it was called."""
diff --git a/nova/tests/api/wsgi_test.py b/nova/tests/api/wsgi_test.py
index 786dc1bce..9425b01d0 100644
--- a/nova/tests/api/wsgi_test.py
+++ b/nova/tests/api/wsgi_test.py
@@ -91,6 +91,57 @@ class Test(unittest.TestCase):
result = webob.Request.blank('/test/123').get_response(Router())
self.assertNotEqual(result.body, "123")
- def test_serializer(self):
- # TODO(eday): Placeholder for serializer testing.
- pass
+
+class SerializerTest(unittest.TestCase):
+
+ def match(self, url, accept, expect):
+ input_dict = dict(servers=dict(a=(2,3)))
+ expected_xml = '<servers><a>(2,3)</a></servers>'
+ expected_json = '{"servers":{"a":[2,3]}}'
+ req = webob.Request.blank(url, headers=dict(Accept=accept))
+ result = wsgi.Serializer(req.environ).to_content_type(input_dict)
+ result = result.replace('\n', '').replace(' ', '')
+ if expect == 'xml':
+ self.assertEqual(result, expected_xml)
+ elif expect == 'json':
+ self.assertEqual(result, expected_json)
+ else:
+ raise "Bad expect value"
+
+ def test_basic(self):
+ self.match('/servers/4.json', None, expect='json')
+ self.match('/servers/4', 'application/json', expect='json')
+ self.match('/servers/4', 'application/xml', expect='xml')
+ self.match('/servers/4.xml', None, expect='xml')
+
+ def test_defaults_to_json(self):
+ self.match('/servers/4', None, expect='json')
+ self.match('/servers/4', 'text/html', expect='json')
+
+ def test_suffix_takes_precedence_over_accept_header(self):
+ self.match('/servers/4.xml', 'application/json', expect='xml')
+ self.match('/servers/4.xml.', 'application/json', expect='json')
+
+ def test_deserialize(self):
+ xml = """
+ <a a1="1" a2="2">
+ <bs><b>1</b><b>2</b><b>3</b><b><c c1="1"/></b></bs>
+ <d><e>1</e></d>
+ <f>1</f>
+ </a>
+ """.strip()
+ as_dict = dict(a={
+ 'a1': '1',
+ 'a2': '2',
+ 'bs': ['1', '2', '3', {'c': dict(c1='1')}],
+ 'd': {'e': '1'},
+ 'f': '1'})
+ metadata = {'application/xml': dict(plurals={'bs': 'b', 'ts': 't'})}
+ serializer = wsgi.Serializer({}, metadata)
+ self.assertEqual(serializer.deserialize(xml), as_dict)
+
+ def test_deserialize_empty_xml(self):
+ xml = """<a></a>"""
+ as_dict = {"a": {}}
+ serializer = wsgi.Serializer({})
+ self.assertEqual(serializer.deserialize(xml), as_dict)
diff --git a/nova/tests/auth_unittest.py b/nova/tests/auth_unittest.py
index 3235dea39..f8be00613 100644
--- a/nova/tests/auth_unittest.py
+++ b/nova/tests/auth_unittest.py
@@ -29,7 +29,8 @@ from nova.api.ec2 import cloud
FLAGS = flags.FLAGS
-class AuthTestCase(test.BaseTestCase):
+class AuthTestCase(test.TrialTestCase):
+ flush_db = False
def setUp(self):
super(AuthTestCase, self).setUp()
self.flags(connection_type='fake')
diff --git a/nova/tests/cloud_unittest.py b/nova/tests/cloud_unittest.py
index 756ce519e..e8ff42fc5 100644
--- a/nova/tests/cloud_unittest.py
+++ b/nova/tests/cloud_unittest.py
@@ -41,7 +41,7 @@ from nova.api.ec2 import cloud
FLAGS = flags.FLAGS
-class CloudTestCase(test.BaseTestCase):
+class CloudTestCase(test.TrialTestCase):
def setUp(self):
super(CloudTestCase, self).setUp()
self.flags(connection_type='fake')
@@ -55,9 +55,9 @@ class CloudTestCase(test.BaseTestCase):
# set up a service
self.compute = utils.import_class(FLAGS.compute_manager)
self.compute_consumer = rpc.AdapterConsumer(connection=self.conn,
- topic=FLAGS.compute_topic,
- proxy=self.compute)
- self.injected.append(self.compute_consumer.attach_to_tornado(self.ioloop))
+ topic=FLAGS.compute_topic,
+ proxy=self.compute)
+ self.compute_consumer.attach_to_twisted()
self.manager = manager.AuthManager()
self.user = self.manager.create_user('admin', 'admin', 'admin', True)
@@ -68,7 +68,7 @@ class CloudTestCase(test.BaseTestCase):
def tearDown(self):
self.manager.delete_project(self.project)
self.manager.delete_user(self.user)
- super(CloudTestCase, self).setUp()
+ super(CloudTestCase, self).tearDown()
def _create_key(self, name):
# NOTE(vish): create depends on pool, so just call helper directly
diff --git a/nova/tests/objectstore_unittest.py b/nova/tests/objectstore_unittest.py
index dece4b5d5..b5970d405 100644
--- a/nova/tests/objectstore_unittest.py
+++ b/nova/tests/objectstore_unittest.py
@@ -53,7 +53,7 @@ os.makedirs(os.path.join(OSS_TEMPDIR, 'images'))
os.makedirs(os.path.join(OSS_TEMPDIR, 'buckets'))
-class ObjectStoreTestCase(test.BaseTestCase):
+class ObjectStoreTestCase(test.TrialTestCase):
"""Test objectstore API directly."""
def setUp(self): # pylint: disable-msg=C0103
diff --git a/nova/tests/rpc_unittest.py b/nova/tests/rpc_unittest.py
index e12a28fbc..9652841f2 100644
--- a/nova/tests/rpc_unittest.py
+++ b/nova/tests/rpc_unittest.py
@@ -30,7 +30,7 @@ from nova import test
FLAGS = flags.FLAGS
-class RpcTestCase(test.BaseTestCase):
+class RpcTestCase(test.TrialTestCase):
"""Test cases for rpc"""
def setUp(self): # pylint: disable-msg=C0103
super(RpcTestCase, self).setUp()
@@ -39,14 +39,13 @@ class RpcTestCase(test.BaseTestCase):
self.consumer = rpc.AdapterConsumer(connection=self.conn,
topic='test',
proxy=self.receiver)
-
- self.injected.append(self.consumer.attach_to_tornado(self.ioloop))
+ self.consumer.attach_to_twisted()
def test_call_succeed(self):
"""Get a value through rpc call"""
value = 42
- result = yield rpc.call('test', {"method": "echo",
- "args": {"value": value}})
+ result = yield rpc.call_twisted('test', {"method": "echo",
+ "args": {"value": value}})
self.assertEqual(value, result)
def test_call_exception(self):
@@ -57,12 +56,12 @@ class RpcTestCase(test.BaseTestCase):
to an int in the test.
"""
value = 42
- self.assertFailure(rpc.call('test', {"method": "fail",
- "args": {"value": value}}),
+ self.assertFailure(rpc.call_twisted('test', {"method": "fail",
+ "args": {"value": value}}),
rpc.RemoteError)
try:
- yield rpc.call('test', {"method": "fail",
- "args": {"value": value}})
+ yield rpc.call_twisted('test', {"method": "fail",
+ "args": {"value": value}})
self.fail("should have thrown rpc.RemoteError")
except rpc.RemoteError as exc:
self.assertEqual(int(exc.value), value)
diff --git a/nova/volume/manager.py b/nova/volume/manager.py
index 034763512..8508f27b2 100644
--- a/nova/volume/manager.py
+++ b/nova/volume/manager.py
@@ -77,7 +77,7 @@ class AOEManager(manager.Manager):
size = volume_ref['size']
logging.debug("volume %s: creating lv of size %sG", volume_id, size)
- yield self.driver.create_volume(volume_ref['str_id'], size)
+ yield self.driver.create_volume(volume_ref['ec2_id'], size)
logging.debug("volume %s: allocating shelf & blade", volume_id)
self._ensure_blades(context)
@@ -87,7 +87,7 @@ class AOEManager(manager.Manager):
logging.debug("volume %s: exporting shelf %s & blade %s", volume_id,
shelf_id, blade_id)
- yield self.driver.create_export(volume_ref['str_id'],
+ yield self.driver.create_export(volume_ref['ec2_id'],
shelf_id,
blade_id)
@@ -111,10 +111,10 @@ class AOEManager(manager.Manager):
raise exception.Error("Volume is not local to this node")
shelf_id, blade_id = self.db.volume_get_shelf_and_blade(context,
volume_id)
- yield self.driver.remove_export(volume_ref['str_id'],
+ yield self.driver.remove_export(volume_ref['ec2_id'],
shelf_id,
blade_id)
- yield self.driver.delete_volume(volume_ref['str_id'])
+ yield self.driver.delete_volume(volume_ref['ec2_id'])
self.db.volume_destroy(context, volume_id)
defer.returnValue(True)
@@ -125,7 +125,7 @@ class AOEManager(manager.Manager):
Returns path to device.
"""
volume_ref = self.db.volume_get(context, volume_id)
- yield self.driver.discover_volume(volume_ref['str_id'])
+ yield self.driver.discover_volume(volume_ref['ec2_id'])
shelf_id, blade_id = self.db.volume_get_shelf_and_blade(context,
volume_id)
defer.returnValue("/dev/etherd/e%s.%s" % (shelf_id, blade_id))
diff --git a/nova/wsgi.py b/nova/wsgi.py
index 8a4e2a9f4..da9374542 100644
--- a/nova/wsgi.py
+++ b/nova/wsgi.py
@@ -21,14 +21,17 @@
Utility methods for working with WSGI servers
"""
+import json
import logging
import sys
+from xml.dom import minidom
import eventlet
import eventlet.wsgi
eventlet.patcher.monkey_patch(all=False, socket=True)
import routes
import routes.middleware
+import webob
import webob.dec
import webob.exc
@@ -230,7 +233,7 @@ class Controller(object):
class Serializer(object):
"""
- Serializes a dictionary to a Content Type specified by a WSGI environment.
+ Serializes and deserializes dictionaries to certain MIME types.
"""
def __init__(self, environ, metadata=None):
@@ -239,31 +242,74 @@ class Serializer(object):
'metadata' is an optional dict mapping MIME types to information
needed to serialize a dictionary to that type.
"""
- self.environ = environ
self.metadata = metadata or {}
- self._methods = {
- 'application/json': self._to_json,
- 'application/xml': self._to_xml}
+ req = webob.Request(environ)
+ suffix = req.path_info.split('.')[-1].lower()
+ if suffix == 'json':
+ self.handler = self._to_json
+ elif suffix == 'xml':
+ self.handler = self._to_xml
+ elif 'application/json' in req.accept:
+ self.handler = self._to_json
+ elif 'application/xml' in req.accept:
+ self.handler = self._to_xml
+ else:
+ self.handler = self._to_json # default
def to_content_type(self, data):
"""
- Serialize a dictionary into a string. The format of the string
- will be decided based on the Content Type requested in self.environ:
- by Accept: header, or by URL suffix.
+ Serialize a dictionary into a string.
+
+ The format of the string will be decided based on the Content Type
+ requested in self.environ: by Accept: header, or by URL suffix.
+ """
+ return self.handler(data)
+
+ def deserialize(self, datastring):
+ """
+ Deserialize a string to a dictionary.
+
+ The string must be in the format of a supported MIME type.
"""
- mimetype = 'application/xml'
- # TODO(gundlach): determine mimetype from request
- return self._methods.get(mimetype, repr)(data)
+ datastring = datastring.strip()
+ is_xml = (datastring[0] == '<')
+ if not is_xml:
+ return json.loads(datastring)
+ return self._from_xml(datastring)
+
+ def _from_xml(self, datastring):
+ xmldata = self.metadata.get('application/xml', {})
+ plurals = set(xmldata.get('plurals', {}))
+ node = minidom.parseString(datastring).childNodes[0]
+ return {node.nodeName: self._from_xml_node(node, plurals)}
+
+ def _from_xml_node(self, node, listnames):
+ """
+ Convert a minidom node to a simple Python type.
+
+ listnames is a collection of names of XML nodes whose subnodes should
+ be considered list items.
+ """
+ if len(node.childNodes) == 1 and node.childNodes[0].nodeType == 3:
+ return node.childNodes[0].nodeValue
+ elif node.nodeName in listnames:
+ return [self._from_xml_node(n, listnames) for n in node.childNodes]
+ else:
+ result = dict()
+ for attr in node.attributes.keys():
+ result[attr] = node.attributes[attr].nodeValue
+ for child in node.childNodes:
+ if child.nodeType != node.TEXT_NODE:
+ result[child.nodeName] = self._from_xml_node(child, listnames)
+ return result
def _to_json(self, data):
- import json
return json.dumps(data)
def _to_xml(self, data):
metadata = self.metadata.get('application/xml', {})
# We expect data to contain a single key which is the XML root.
root_key = data.keys()[0]
- from xml.dom import minidom
doc = minidom.Document()
node = self._to_xml_node(doc, metadata, root_key, data[root_key])
return node.toprettyxml(indent=' ')
diff --git a/pylintrc b/pylintrc
index 6702ca895..024802835 100644
--- a/pylintrc
+++ b/pylintrc
@@ -1,7 +1,8 @@
[Messages Control]
# W0511: TODOs in code comments are fine.
# W0142: *args and **kwargs are fine.
-disable-msg=W0511,W0142
+# W0622: Redefining id is fine.
+disable-msg=W0511,W0142,W0622
[Basic]
# Variable names can be 1 to 31 characters long, with lowercase and underscores
diff --git a/setup.py b/setup.py
index 1767b00f4..d420d3559 100644
--- a/setup.py
+++ b/setup.py
@@ -54,5 +54,5 @@ setup(name='nova',
'bin/nova-manage',
'bin/nova-network',
'bin/nova-objectstore',
- 'bin/nova-api-new',
+ 'bin/nova-scheduler',
'bin/nova-volume'])