summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rwxr-xr-xbin/nova-manage73
-rw-r--r--nova/adminclient.py73
-rw-r--r--nova/api/ec2/__init__.py2
-rw-r--r--nova/api/ec2/cloud.py104
-rw-r--r--nova/api/ec2/images.py8
-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/auth/ldapdriver.py16
-rw-r--r--nova/auth/manager.py6
-rw-r--r--nova/compute/manager.py10
-rw-r--r--nova/db/api.py43
-rw-r--r--nova/db/sqlalchemy/api.py109
-rw-r--r--nova/db/sqlalchemy/models.py27
-rw-r--r--nova/flags.py2
-rw-r--r--nova/manager.py7
-rw-r--r--nova/network/linux_net.py79
-rw-r--r--nova/network/manager.py15
-rw-r--r--nova/objectstore/handler.py21
-rw-r--r--nova/objectstore/image.py10
-rw-r--r--nova/rpc.py45
-rw-r--r--nova/service.py1
-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.py409
-rw-r--r--nova/tests/cloud_unittest.py86
-rw-r--r--nova/tests/objectstore_unittest.py8
-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
-rwxr-xr-xtools/setup_iptables.sh158
44 files changed, 1607 insertions, 426 deletions
diff --git a/bin/nova-manage b/bin/nova-manage
index baa1cb4db..7a9a4c3d1 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."""
@@ -228,6 +266,19 @@ class UserCommands(object):
for user in self.manager.get_users():
print user.name
+ def modify(self, name, access_key, secret_key, is_admin):
+ """update a users keys & admin flag
+ arguments: accesskey secretkey admin
+ leave any field blank to ignore it, admin should be 'T', 'F', or blank
+ """
+ if not is_admin:
+ is_admin = None
+ elif is_admin.upper()[0] == 'T':
+ is_admin = True
+ else:
+ is_admin = False
+ print "is_admin: %r" % is_admin
+ self.manager.modify_user(name, access_key, secret_key, is_admin)
class ProjectCommands(object):
"""Class for managing projects."""
@@ -253,7 +304,7 @@ class ProjectCommands(object):
def environment(self, project_id, user_id, filename='novarc'):
"""Exports environment variables to an sourcable file
arguments: project_id user_id [filename='novarc]"""
- rc = self.manager.get_environment_rc(project_id, user_id)
+ rc = self.manager.get_environment_rc(user_id, project_id)
with open(filename, 'w') as f:
f.write(rc)
@@ -316,7 +367,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/__init__.py b/nova/api/ec2/__init__.py
index d46e194f4..8111ef023 100644
--- a/nova/api/ec2/__init__.py
+++ b/nova/api/ec2/__init__.py
@@ -160,12 +160,14 @@ class Authorizer(wsgi.Middleware):
'RunInstances': ['projectmanager', 'sysadmin'],
'TerminateInstances': ['projectmanager', 'sysadmin'],
'RebootInstances': ['projectmanager', 'sysadmin'],
+ 'UpdateInstance': ['projectmanager', 'sysadmin'],
'DeleteVolume': ['projectmanager', 'sysadmin'],
'DescribeImages': ['all'],
'DeregisterImage': ['projectmanager', 'sysadmin'],
'RegisterImage': ['projectmanager', 'sysadmin'],
'DescribeImageAttribute': ['all'],
'ModifyImageAttribute': ['projectmanager', 'sysadmin'],
+ 'UpdateImage': ['projectmanager', 'sysadmin'],
},
'AdminController': {
# All actions have the same permission: ['none'] (the default)
diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py
index d54562ec6..ca3f71036 100644
--- a/nova/api/ec2/cloud.py
+++ b/nova/api/ec2/cloud.py
@@ -104,9 +104,9 @@ class CloudController(object):
def _get_mpi_data(self, project_id):
result = {}
- for instance in db.instance_get_by_project(None, project_id):
+ 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:
@@ -155,7 +155,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,
@@ -410,7 +410,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",
@@ -421,7 +421,7 @@ class CloudController(object):
if context.user.is_admin():
volumes = db.volume_get_all(context)
else:
- volumes = db.volume_get_by_project(context, context.project.id)
+ volumes = db.volume_get_all_by_project(context, context.project.id)
volumes = [self._format_volume(context, v) for v in volumes]
@@ -429,7 +429,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']
@@ -447,9 +447,12 @@ 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'] = [{}]
+
+ v['display_name'] = volume['display_name']
+ v['display_description'] = volume['display_description']
return v
def create_volume(self, context, size, **kwargs):
@@ -467,6 +470,8 @@ class CloudController(object):
vol['availability_zone'] = FLAGS.storage_availability_zone
vol['status'] = "creating"
vol['attach_status'] = "detached"
+ vol['display_name'] = kwargs.get('display_name')
+ vol['display_description'] = kwargs.get('display_description')
volume_ref = db.volume_create(context, vol)
rpc.cast(FLAGS.scheduler_topic,
@@ -479,13 +484,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",
@@ -501,7 +506,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!")
@@ -521,7 +526,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']}
@@ -533,6 +538,16 @@ class CloudController(object):
lst = [lst]
return [{label: x} for x in lst]
+ def update_volume(self, context, volume_id, **kwargs):
+ updatable_fields = ['display_name', 'display_description']
+ changes = {}
+ for field in updatable_fields:
+ if field in kwargs:
+ changes[field] = kwargs[field]
+ if changes:
+ db.volume_update(context, volume_id, kwargs)
+ return True
+
def describe_instances(self, context, **kwargs):
return self._format_describe_instances(context)
@@ -547,20 +562,20 @@ class CloudController(object):
def _format_instances(self, context, reservation_id=None):
reservations = {}
if reservation_id:
- instances = db.instance_get_by_reservation(context,
- reservation_id)
+ instances = db.instance_get_all_by_reservation(context,
+ reservation_id)
else:
if context.user.is_admin():
instances = db.instance_get_all(context)
else:
- instances = db.instance_get_by_project(context,
- context.project.id)
+ instances = db.instance_get_all_by_project(context,
+ context.project.id)
for instance in instances:
if not context.user.is_admin():
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'],
@@ -569,10 +584,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']
@@ -585,6 +600,8 @@ class CloudController(object):
i['instanceType'] = instance['instance_type']
i['launchTime'] = instance['created_at']
i['amiLaunchIndex'] = instance['launch_index']
+ i['displayName'] = instance['display_name']
+ i['displayDescription'] = instance['display_description']
if not reservations.has_key(instance['reservation_id']):
r = {}
r['reservationId'] = instance['reservation_id']
@@ -604,14 +621,14 @@ class CloudController(object):
if context.user.is_admin():
iterator = db.floating_ip_get_all(context)
else:
- iterator = db.floating_ip_get_by_project(context,
- context.project.id)
+ 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():
@@ -642,19 +659,20 @@ 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)
- fixed_ip_ref = db.fixed_ip_get_by_instance(context, instance_ref['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)
network_topic = self._get_network_topic(context)
rpc.cast(network_topic,
{"method": "associate_floating_ip",
"args": {"context": None,
- "floating_address": floating_ip_ref['str_id'],
- "fixed_address": fixed_ip_ref['str_id']}})
+ "floating_address": floating_ip_ref['address'],
+ "fixed_address": fixed_address}})
return {'associateResponse': ["Address associated."]}
def disassociate_address(self, context, public_ip, **kwargs):
@@ -663,7 +681,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):
@@ -763,6 +781,10 @@ class CloudController(object):
type_data = INSTANCE_TYPES[instance_type]
base_options['instance_type'] = instance_type
+ base_options['display_name'] = kwargs.get('display_name')
+ base_options['display_description'] = kwargs.get('display_description')
+
+ type_data = INSTANCE_TYPES[instance_type]
base_options['memory_mb'] = type_data['memory_mb']
base_options['vcpus'] = type_data['vcpus']
base_options['local_gb'] = type_data['local_gb']
@@ -778,7 +800,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,
@@ -807,7 +829,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)
@@ -829,7 +851,7 @@ class CloudController(object):
rpc.cast(network_topic,
{"method": "disassociate_floating_ip",
"args": {"context": None,
- "address": address}})
+ "floating_address": address}})
address = db.instance_get_fixed_address(context,
instance_ref['id'])
@@ -853,7 +875,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",
@@ -861,9 +883,21 @@ class CloudController(object):
"instance_id": instance_ref['id']}})
return True
+ def update_instance(self, context, instance_id, **kwargs):
+ updatable_fields = ['display_name', 'display_description']
+ changes = {}
+ for field in updatable_fields:
+ if field in kwargs:
+ changes[field] = kwargs[field]
+ if changes:
+ db_context = {}
+ inst = db.instance_get_by_ec2_id(db_context, instance_id)
+ db.instance_update(db_context, inst['id'], kwargs)
+ return True
+
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()
@@ -916,3 +950,7 @@ class CloudController(object):
if not operation_type in ['add', 'remove']:
raise exception.ApiError('operation_type must be add or remove')
return images.modify(context, image_id, operation_type)
+
+ def update_image(self, context, image_id, **kwargs):
+ result = images.update(context, image_id, dict(kwargs))
+ return result
diff --git a/nova/api/ec2/images.py b/nova/api/ec2/images.py
index 4579cd81a..cb54cdda2 100644
--- a/nova/api/ec2/images.py
+++ b/nova/api/ec2/images.py
@@ -43,6 +43,14 @@ def modify(context, image_id, operation):
return True
+def update(context, image_id, attributes):
+ """update an image's attributes / info.json"""
+ attributes.update({"image_id": image_id})
+ conn(context).make_request(
+ method='POST',
+ bucket='_images',
+ query_args=qs(attributes))
+ return True
def register(context, image_location):
""" rpc call to register a new image based from a manifest """
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/auth/ldapdriver.py b/nova/auth/ldapdriver.py
index 021851ebf..640ea169e 100644
--- a/nova/auth/ldapdriver.py
+++ b/nova/auth/ldapdriver.py
@@ -256,8 +256,7 @@ class LdapDriver(object):
if not self.__user_exists(uid):
raise exception.NotFound("User %s doesn't exist" % uid)
self.__remove_from_all(uid)
- self.conn.delete_s('uid=%s,%s' % (uid,
- FLAGS.ldap_user_subtree))
+ self.conn.delete_s(self.__uid_to_dn(uid))
def delete_project(self, project_id):
"""Delete a project"""
@@ -265,6 +264,19 @@ class LdapDriver(object):
self.__delete_roles(project_dn)
self.__delete_group(project_dn)
+ def modify_user(self, uid, access_key=None, secret_key=None, admin=None):
+ """Modify an existing project"""
+ if not access_key and not secret_key and admin is None:
+ return
+ attr = []
+ if access_key:
+ attr.append((self.ldap.MOD_REPLACE, 'accessKey', access_key))
+ if secret_key:
+ attr.append((self.ldap.MOD_REPLACE, 'secretKey', secret_key))
+ if admin is not None:
+ attr.append((self.ldap.MOD_REPLACE, 'isAdmin', str(admin).upper()))
+ self.conn.modify_s(self.__uid_to_dn(uid), attr)
+
def __user_exists(self, uid):
"""Check if user exists"""
return self.get_user(uid) != None
diff --git a/nova/auth/manager.py b/nova/auth/manager.py
index bea4c7933..e2e035d37 100644
--- a/nova/auth/manager.py
+++ b/nova/auth/manager.py
@@ -632,6 +632,12 @@ class AuthManager(object):
with self.driver() as drv:
drv.delete_user(uid)
+ def modify_user(self, user, access_key=None, secret_key=None, admin=None):
+ """Modify credentials for a user"""
+ uid = User.safe_id(user)
+ with self.driver() as drv:
+ drv.modify_user(uid, access_key, secret_key, admin)
+
def get_credentials(self, user, project=None):
"""Get credential zip for user in project"""
if not isinstance(user, User):
diff --git a/nova/compute/manager.py b/nova/compute/manager.py
index 94cea1c50..02ac3cb4c 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['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']
@@ -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['str_id'],
+ (instance_ref['ec2_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['str_id'],
+ instance_ref['ec2_id'],
'console.log'))
with open(fname, 'r') as f:
output = f.read()
@@ -179,7 +179,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)
@@ -194,7 +194,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 5e033b59d..9ce3dfb2a 100644
--- a/nova/db/api.py
+++ b/nova/db/api.py
@@ -161,10 +161,15 @@ def floating_ip_get_all(context):
def floating_ip_get_all_by_host(context, host):
- """Get all floating ips."""
+ """Get all floating ips by host."""
return IMPL.floating_ip_get_all_by_host(context, host)
+def floating_ip_get_all_by_project(context, project_id):
+ """Get all floating ips by project."""
+ return IMPL.floating_ip_get_all_by_project(context, project_id)
+
+
def floating_ip_get_by_address(context, address):
"""Get a floating ip by address or raise if it doesn't exist."""
return IMPL.floating_ip_get_by_address(context, address)
@@ -251,15 +256,18 @@ 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_by_project(context, project_id):
+def instance_get_all_by_project(context, project_id):
"""Get all instance belonging to a project."""
- return IMPL.instance_get_by_project(context, project_id)
+ return IMPL.instance_get_all_by_project(context, project_id)
-def instance_get_by_reservation(context, reservation_id):
+def instance_get_all_by_reservation(context, reservation_id):
"""Get all instance belonging to a reservation."""
- return IMPL.instance_get_by_reservation(context, reservation_id)
+ return IMPL.instance_get_all_by_reservation(context, reservation_id)
def instance_get_fixed_address(context, instance_id):
@@ -272,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):
@@ -398,9 +406,12 @@ def network_index_count(context):
return IMPL.network_index_count(context)
-def network_index_create(context, values):
- """Create a network index from the values dict"""
- return IMPL.network_index_create(context, values)
+def network_index_create_safe(context, values):
+ """Create a network index from the values dict
+
+ The index is not returned. If the create violates the unique
+ constraints because the index already exists, no exception is raised."""
+ return IMPL.network_index_create_safe(context, values)
def network_set_cidr(context, network_id, cidr):
@@ -537,14 +548,14 @@ def volume_get_instance(context, volume_id):
return IMPL.volume_get_instance(context, volume_id)
-def volume_get_by_project(context, project_id):
+def volume_get_all_by_project(context, project_id):
"""Get all volumes belonging to a project."""
- return IMPL.volume_get_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 07ea5d145..013e8ab16 100644
--- a/nova/db/sqlalchemy/api.py
+++ b/nova/db/sqlalchemy/api.py
@@ -19,13 +19,19 @@
Implementation of SQLAlchemy backend
"""
+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 eagerload, joinedload_all
+from sqlalchemy.exc import IntegrityError
+from sqlalchemy.orm import eagerload
+from sqlalchemy.orm import joinedload_all
+from sqlalchemy.sql import exists
from sqlalchemy.sql import func
FLAGS = flags.FLAGS
@@ -61,6 +67,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()
@@ -70,6 +77,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()
@@ -163,6 +171,7 @@ def floating_ip_allocate_address(_context, host, project_id):
floating_ip_ref = session.query(models.FloatingIp
).filter_by(host=host
).filter_by(fixed_ip_id=None
+ ).filter_by(project_id=None
).filter_by(deleted=False
).with_lockmode('update'
).first()
@@ -250,6 +259,14 @@ def floating_ip_get_all_by_host(_context, host):
).filter_by(deleted=False
).all()
+def floating_ip_get_all_by_project(_context, project_id):
+ session = get_session()
+ return session.query(models.FloatingIp
+ ).options(joinedload_all('fixed_ip.instance')
+ ).filter_by(project_id=project_id
+ ).filter_by(deleted=False
+ ).all()
+
def floating_ip_get_by_address(_context, address):
return models.FloatingIp.find_by_str(address)
@@ -325,7 +342,17 @@ def fixed_ip_disassociate(_context, address):
def fixed_ip_get_by_address(_context, address):
- return models.FixedIp.find_by_str(address)
+ session = get_session()
+ with session.begin():
+ try:
+ return session.query(models.FixedIp
+ ).options(joinedload_all('instance')
+ ).filter_by(address=address
+ ).filter_by(deleted=False
+ ).one()
+ except exc.NoResultFound:
+ new_exc = exception.NotFound("No model for address %s" % address)
+ raise new_exc.__class__, new_exc, sys.exc_info()[2]
def fixed_ip_get_instance(_context, address):
@@ -356,9 +383,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()
@@ -390,8 +423,15 @@ 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_by_project(context, project_id):
+def instance_get_all_by_project(context, project_id):
session = get_session()
return session.query(models.Instance
).options(joinedload_all('fixed_ip.floating_ips')
@@ -400,7 +440,7 @@ def instance_get_by_project(context, project_id):
).all()
-def instance_get_by_reservation(_context, reservation_id):
+def instance_get_all_by_reservation(_context, reservation_id):
session = get_session()
return session.query(models.Instance
).options(joinedload_all('fixed_ip.floating_ips')
@@ -409,8 +449,22 @@ def instance_get_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):
@@ -582,6 +636,7 @@ def network_get(_context, network_id):
def network_get_associated_fixed_ips(_context, network_id):
session = get_session()
return session.query(models.FixedIp
+ ).options(joinedload_all('instance')
).filter_by(network_id=network_id
).filter(models.FixedIp.instance_id != None
).filter_by(deleted=False
@@ -619,11 +674,14 @@ def network_index_count(_context):
return models.NetworkIndex.count()
-def network_index_create(_context, values):
+def network_index_create_safe(_context, values):
network_index_ref = models.NetworkIndex()
for (key, value) in values.iteritems():
network_index_ref[key] = value
- network_index_ref.save()
+ try:
+ network_index_ref.save()
+ except IntegrityError:
+ pass
def network_set_host(_context, network_id, host_id):
@@ -711,7 +769,7 @@ def auth_create_token(_context, token):
tk[k] = v
tk.save()
return tk
-
+
###################
@@ -780,7 +838,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
@@ -825,7 +890,7 @@ def volume_get_all(context):
return models.Volume.all(deleted=_deleted(context))
-def volume_get_by_project(context, project_id):
+def volume_get_all_by_project(context, project_id):
session = get_session()
return session.query(models.Volume
).filter_by(project_id=project_id
@@ -833,8 +898,22 @@ def volume_get_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 9ce146f1d..d4caf0b52 100644
--- a/nova/db/sqlalchemy/models.py
+++ b/nova/db/sqlalchemy/models.py
@@ -130,6 +130,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))
@@ -177,6 +178,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):
@@ -199,6 +201,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))
@@ -213,12 +216,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)
@@ -237,7 +242,6 @@ class Instance(BASE, NovaBase):
vcpus = Column(Integer)
local_gb = Column(Integer)
-
hostname = Column(String(255))
host = Column(String(255)) # , ForeignKey('hosts.id'))
@@ -251,6 +255,10 @@ class Instance(BASE, NovaBase):
scheduled_at = Column(DateTime)
launched_at = Column(DateTime)
terminated_at = Column(DateTime)
+
+ display_name = Column(String(255))
+ display_description = Column(String(255))
+
# TODO(vish): see Ewan's email about state improvements, probably
# should be in a driver base class or some such
# vmstate_state = running, halted, suspended, paused
@@ -268,6 +276,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))
@@ -286,6 +295,10 @@ class Volume(BASE, NovaBase):
launched_at = Column(DateTime)
terminated_at = Column(DateTime)
+ display_name = Column(String(255))
+ display_description = Column(String(255))
+
+
class Quota(BASE, NovaBase):
"""Represents quota overrides for a project"""
__tablename__ = 'quotas'
@@ -447,14 +460,14 @@ class NetworkIndex(BASE, NovaBase):
"""
__tablename__ = 'network_indexes'
id = Column(Integer, primary_key=True)
- index = Column(Integer)
+ index = Column(Integer, unique=True)
network_id = Column(Integer, ForeignKey('networks.id'), nullable=True)
network = relationship(Network, backref=backref('network_index',
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)
@@ -509,10 +522,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/flags.py b/nova/flags.py
index 6a1c14490..c32cdd7a4 100644
--- a/nova/flags.py
+++ b/nova/flags.py
@@ -188,6 +188,8 @@ DEFINE_string('rabbit_userid', 'guest', 'rabbit userid')
DEFINE_string('rabbit_password', 'guest', 'rabbit password')
DEFINE_string('rabbit_virtual_host', '/', 'rabbit virtual host')
DEFINE_string('control_exchange', 'nova', 'the main exchange to connect to')
+DEFINE_string('cc_host', '127.0.0.1', 'ip of api server')
+DEFINE_integer('cc_port', 8773, 'cloud controller port')
DEFINE_string('ec2_url', 'http://127.0.0.1:8773/services/Cloud',
'Url to ec2 api server')
diff --git a/nova/manager.py b/nova/manager.py
index e9aa50c56..94e4ae959 100644
--- a/nova/manager.py
+++ b/nova/manager.py
@@ -37,3 +37,10 @@ class Manager(object):
if not db_driver:
db_driver = FLAGS.db_driver
self.db = utils.import_object(db_driver) # pylint: disable-msg=C0103
+
+ def init_host(self):
+ """Do any initialization that needs to be run if this is a standalone service.
+
+ Child classes should override this method.
+ """
+ pass
diff --git a/nova/network/linux_net.py b/nova/network/linux_net.py
index 41aeb5da7..709195ba4 100644
--- a/nova/network/linux_net.py
+++ b/nova/network/linux_net.py
@@ -36,13 +36,34 @@ flags.DEFINE_string('dhcpbridge_flagfile',
flags.DEFINE_string('networks_path', utils.abspath('../networks'),
'Location to keep network config files')
flags.DEFINE_string('public_interface', 'vlan1',
- 'Interface for public IP addresses')
+ 'Interface for public IP addresses')
flags.DEFINE_string('bridge_dev', 'eth0',
- 'network device for bridges')
-
+ 'network device for bridges')
+flags.DEFINE_string('routing_source_ip', '127.0.0.1',
+ 'Public IP of network host')
+flags.DEFINE_bool('use_nova_chains', False,
+ 'use the nova_ routing chains instead of default')
DEFAULT_PORTS = [("tcp", 80), ("tcp", 22), ("udp", 1194), ("tcp", 443)]
+def init_host():
+ """Basic networking setup goes here"""
+ # NOTE(devcamcar): Cloud public DNAT entries, CloudPipe port
+ # forwarding entries and a default DNAT entry.
+ _confirm_rule("PREROUTING", "-t nat -s 0.0.0.0/0 "
+ "-d 169.254.169.254/32 -p tcp -m tcp --dport 80 -j DNAT "
+ "--to-destination %s:%s" % (FLAGS.cc_host, FLAGS.cc_port))
+
+ # NOTE(devcamcar): Cloud public SNAT entries and the default
+ # SNAT rule for outbound traffic.
+ _confirm_rule("POSTROUTING", "-t nat -s %s "
+ "-j SNAT --to-source %s"
+ % (FLAGS.private_range, FLAGS.routing_source_ip))
+
+ _confirm_rule("POSTROUTING", "-t nat -s %s -j MASQUERADE" %
+ FLAGS.private_range)
+ _confirm_rule("POSTROUTING", "-t nat -s %(range)s -d %(range)s -j ACCEPT" %
+ {'range': FLAGS.private_range})
def bind_floating_ip(floating_ip):
"""Bind ip to public interface"""
@@ -58,37 +79,37 @@ def unbind_floating_ip(floating_ip):
def ensure_vlan_forward(public_ip, port, private_ip):
"""Sets up forwarding rules for vlan"""
- _confirm_rule("FORWARD -d %s -p udp --dport 1194 -j ACCEPT" % private_ip)
- _confirm_rule(
- "PREROUTING -t nat -d %s -p udp --dport %s -j DNAT --to %s:1194"
+ _confirm_rule("FORWARD", "-d %s -p udp --dport 1194 -j ACCEPT" %
+ private_ip)
+ _confirm_rule("PREROUTING",
+ "-t nat -d %s -p udp --dport %s -j DNAT --to %s:1194"
% (public_ip, port, private_ip))
def ensure_floating_forward(floating_ip, fixed_ip):
"""Ensure floating ip forwarding rule"""
- _confirm_rule("PREROUTING -t nat -d %s -j DNAT --to %s"
+ _confirm_rule("PREROUTING", "-t nat -d %s -j DNAT --to %s"
% (floating_ip, fixed_ip))
- _confirm_rule("POSTROUTING -t nat -s %s -j SNAT --to %s"
+ _confirm_rule("POSTROUTING", "-t nat -s %s -j SNAT --to %s"
% (fixed_ip, floating_ip))
# TODO(joshua): Get these from the secgroup datastore entries
- _confirm_rule("FORWARD -d %s -p icmp -j ACCEPT"
+ _confirm_rule("FORWARD", "-d %s -p icmp -j ACCEPT"
% (fixed_ip))
for (protocol, port) in DEFAULT_PORTS:
- _confirm_rule(
- "FORWARD -d %s -p %s --dport %s -j ACCEPT"
+ _confirm_rule("FORWARD","-d %s -p %s --dport %s -j ACCEPT"
% (fixed_ip, protocol, port))
def remove_floating_forward(floating_ip, fixed_ip):
"""Remove forwarding for floating ip"""
- _remove_rule("PREROUTING -t nat -d %s -j DNAT --to %s"
+ _remove_rule("PREROUTING", "-t nat -d %s -j DNAT --to %s"
% (floating_ip, fixed_ip))
- _remove_rule("POSTROUTING -t nat -s %s -j SNAT --to %s"
+ _remove_rule("POSTROUTING", "-t nat -s %s -j SNAT --to %s"
% (fixed_ip, floating_ip))
- _remove_rule("FORWARD -d %s -p icmp -j ACCEPT"
+ _remove_rule("FORWARD", "-d %s -p icmp -j ACCEPT"
% (fixed_ip))
for (protocol, port) in DEFAULT_PORTS:
- _remove_rule("FORWARD -d %s -p %s --dport %s -j ACCEPT"
+ _remove_rule("FORWARD", "-d %s -p %s --dport %s -j ACCEPT"
% (fixed_ip, protocol, port))
@@ -124,16 +145,18 @@ def ensure_bridge(bridge, interface, net_attrs=None):
net_attrs['gateway'],
net_attrs['broadcast'],
net_attrs['netmask']))
- _confirm_rule("FORWARD --in-interface %s -j ACCEPT" % bridge)
else:
_execute("sudo ifconfig %s up" % bridge)
+ _confirm_rule("FORWARD", "--in-interface %s -j ACCEPT" % bridge)
+ _confirm_rule("FORWARD", "--out-interface %s -j ACCEPT" % bridge)
def get_dhcp_hosts(context, network_id):
"""Get a string containing a network's hosts config in dnsmasq format"""
hosts = []
- for fixed_ip in db.network_get_associated_fixed_ips(context, network_id):
- hosts.append(_host_dhcp(fixed_ip['str_id']))
+ for fixed_ip_ref in db.network_get_associated_fixed_ips(context,
+ network_id):
+ hosts.append(_host_dhcp(fixed_ip_ref))
return '\n'.join(hosts)
@@ -171,12 +194,12 @@ def update_dhcp(context, network_id):
_execute(command, addl_env=env)
-def _host_dhcp(address):
+def _host_dhcp(fixed_ip_ref):
"""Return a host string for an address"""
- instance_ref = db.fixed_ip_get_instance(None, address)
+ instance_ref = fixed_ip_ref['instance']
return "%s,%s.novalocal,%s" % (instance_ref['mac_address'],
instance_ref['hostname'],
- address)
+ fixed_ip_ref['address'])
def _execute(cmd, *args, **kwargs):
@@ -194,15 +217,19 @@ def _device_exists(device):
return not err
-def _confirm_rule(cmd):
+def _confirm_rule(chain, cmd):
"""Delete and re-add iptables rule"""
- _execute("sudo iptables --delete %s" % (cmd), check_exit_code=False)
- _execute("sudo iptables -I %s" % (cmd))
+ if FLAGS.use_nova_chains:
+ chain = "nova_%s" % chain.lower()
+ _execute("sudo iptables --delete %s %s" % (chain, cmd), check_exit_code=False)
+ _execute("sudo iptables -I %s %s" % (chain, cmd))
-def _remove_rule(cmd):
+def _remove_rule(chain, cmd):
"""Remove iptables rule"""
- _execute("sudo iptables --delete %s" % (cmd))
+ if FLAGS.use_nova_chains:
+ chain = "%S" % chain.lower()
+ _execute("sudo iptables --delete %s %s" % (chain, cmd))
def _dnsmasq_cmd(net):
diff --git a/nova/network/manager.py b/nova/network/manager.py
index e2dc846a6..8f1924ac9 100644
--- a/nova/network/manager.py
+++ b/nova/network/manager.py
@@ -217,6 +217,13 @@ class FlatManager(NetworkManager):
class VlanManager(NetworkManager):
"""Vlan network with dhcp"""
+
+ def init_host(self):
+ """Do any initialization that needs to be run if this is a
+ standalone service.
+ """
+ self.driver.init_host()
+
def allocate_fixed_ip(self, context, instance_id, *args, **kwargs):
"""Gets a fixed ip from the pool"""
network_ref = self.db.project_get_network(context, context.project.id)
@@ -261,7 +268,7 @@ class VlanManager(NetworkManager):
if not fixed_ip_ref['allocated']:
logging.warn("IP %s leased that was already deallocated", address)
return
- instance_ref = self.db.fixed_ip_get_instance(context, address)
+ instance_ref = fixed_ip_ref['instance']
if not instance_ref:
raise exception.Error("IP %s leased that isn't associated" %
address)
@@ -269,7 +276,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):
@@ -279,7 +286,7 @@ class VlanManager(NetworkManager):
if not fixed_ip_ref['leased']:
logging.warn("IP %s released that was not leased", address)
return
- instance_ref = self.db.fixed_ip_get_instance(context, address)
+ instance_ref = fixed_ip_ref['instance']
if not instance_ref:
raise exception.Error("IP %s released that isn't associated" %
address)
@@ -342,7 +349,7 @@ class VlanManager(NetworkManager):
This could use a manage command instead of keying off of a flag"""
if not self.db.network_index_count(context):
for index in range(FLAGS.num_networks):
- self.db.network_index_create(context, {'index': index})
+ self.db.network_index_create_safe(context, {'index': index})
def _on_set_network_host(self, context, network_id):
"""Called when this host becomes the host for a project"""
diff --git a/nova/objectstore/handler.py b/nova/objectstore/handler.py
index aabf6831f..dfee64aca 100644
--- a/nova/objectstore/handler.py
+++ b/nova/objectstore/handler.py
@@ -352,6 +352,8 @@ class ImagesResource(resource.Resource):
m[u'imageType'] = m['type']
elif 'imageType' in m:
m[u'type'] = m['imageType']
+ if 'displayName' not in m:
+ m[u'displayName'] = u''
return m
request.write(json.dumps([decorate(i.metadata) for i in images]))
@@ -382,16 +384,25 @@ class ImagesResource(resource.Resource):
def render_POST(self, request): # pylint: disable-msg=R0201
"""Update image attributes: public/private"""
+ # image_id required for all requests
image_id = get_argument(request, 'image_id', u'')
- operation = get_argument(request, 'operation', u'')
-
image_object = image.Image(image_id)
-
if not image_object.is_authorized(request.context):
+ logging.debug("not authorized for render_POST in images")
raise exception.NotAuthorized
- image_object.set_public(operation=='add')
-
+ operation = get_argument(request, 'operation', u'')
+ if operation:
+ # operation implies publicity toggle
+ logging.debug("handling publicity toggle")
+ image_object.set_public(operation=='add')
+ else:
+ # other attributes imply update
+ logging.debug("update user fields")
+ clean_args = {}
+ for arg in request.args.keys():
+ clean_args[arg] = request.args[arg][0]
+ image_object.update_user_editable_fields(clean_args)
return ''
def render_DELETE(self, request): # pylint: disable-msg=R0201
diff --git a/nova/objectstore/image.py b/nova/objectstore/image.py
index f3c02a425..def1b8167 100644
--- a/nova/objectstore/image.py
+++ b/nova/objectstore/image.py
@@ -82,6 +82,16 @@ class Image(object):
with open(os.path.join(self.path, 'info.json'), 'w') as f:
json.dump(md, f)
+ def update_user_editable_fields(self, args):
+ """args is from the request parameters, so requires extra cleaning"""
+ fields = {'display_name': 'displayName', 'description': 'description'}
+ info = self.metadata
+ for field in fields.keys():
+ if field in args:
+ info[fields[field]] = args[field]
+ with open(os.path.join(self.path, 'info.json'), 'w') as f:
+ json.dump(info, f)
+
@staticmethod
def all():
images = []
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/service.py b/nova/service.py
index 870dd6ceb..dcd2a09ef 100644
--- a/nova/service.py
+++ b/nova/service.py
@@ -50,6 +50,7 @@ class Service(object, service.Service):
self.topic = topic
manager_class = utils.import_class(manager)
self.manager = manager_class(host=host, *args, **kwargs)
+ self.manager.init_host()
self.model_disconnected = False
super(Service, self).__init__(*args, **kwargs)
try:
diff --git a/nova/test.py b/nova/test.py
index 5ed0c73d3..08e1dea2d 100644
--- a/nova/test.py
+++ b/nova/test.py
@@ -34,6 +34,7 @@ from twisted.trial import unittest
from nova import db
from nova import fakerabbit
from nova import flags
+from nova import rpc
FLAGS = flags.FLAGS
@@ -63,20 +64,30 @@ 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()
db.security_group_destroy_all(None)
+ super(TrialTestCase, self).tearDown()
+
def flags(self, **kw):
"""Override flag variables for a test"""
for k, v in kw.iteritems():
@@ -92,16 +103,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
@@ -111,8 +157,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..1955bb417 100644
--- a/nova/tests/auth_unittest.py
+++ b/nova/tests/auth_unittest.py
@@ -28,25 +28,71 @@ from nova.api.ec2 import cloud
FLAGS = flags.FLAGS
-
-class AuthTestCase(test.BaseTestCase):
+class user_generator(object):
+ def __init__(self, manager, **user_state):
+ if 'name' not in user_state:
+ user_state['name'] = 'test1'
+ self.manager = manager
+ self.user = manager.create_user(**user_state)
+
+ def __enter__(self):
+ return self.user
+
+ def __exit__(self, value, type, trace):
+ self.manager.delete_user(self.user)
+
+class project_generator(object):
+ def __init__(self, manager, **project_state):
+ if 'name' not in project_state:
+ project_state['name'] = 'testproj'
+ if 'manager_user' not in project_state:
+ project_state['manager_user'] = 'test1'
+ self.manager = manager
+ self.project = manager.create_project(**project_state)
+
+ def __enter__(self):
+ return self.project
+
+ def __exit__(self, value, type, trace):
+ self.manager.delete_project(self.project)
+
+class user_and_project_generator(object):
+ def __init__(self, manager, user_state={}, project_state={}):
+ self.manager = manager
+ if 'name' not in user_state:
+ user_state['name'] = 'test1'
+ if 'name' not in project_state:
+ project_state['name'] = 'testproj'
+ if 'manager_user' not in project_state:
+ project_state['manager_user'] = 'test1'
+ self.user = manager.create_user(**user_state)
+ self.project = manager.create_project(**project_state)
+
+ def __enter__(self):
+ return (self.user, self.project)
+
+ def __exit__(self, value, type, trace):
+ self.manager.delete_user(self.user)
+ self.manager.delete_project(self.project)
+
+class AuthManagerTestCase(test.TrialTestCase):
def setUp(self):
- super(AuthTestCase, self).setUp()
+ super(AuthManagerTestCase, self).setUp()
self.flags(connection_type='fake')
self.manager = manager.AuthManager()
- def test_001_can_create_users(self):
- self.manager.create_user('test1', 'access', 'secret')
- self.manager.create_user('test2')
-
- def test_002_can_get_user(self):
- user = self.manager.get_user('test1')
+ def test_create_and_find_user(self):
+ with user_generator(self.manager):
+ self.assert_(self.manager.get_user('test1'))
- def test_003_can_retreive_properties(self):
- user = self.manager.get_user('test1')
- self.assertEqual('test1', user.id)
- self.assertEqual('access', user.access)
- self.assertEqual('secret', user.secret)
+ def test_create_and_find_with_properties(self):
+ with user_generator(self.manager, name="herbert", secret="classified",
+ access="private-party"):
+ u = self.manager.get_user('herbert')
+ self.assertEqual('herbert', u.id)
+ self.assertEqual('herbert', u.name)
+ self.assertEqual('classified', u.secret)
+ self.assertEqual('private-party', u.access)
def test_004_signature_is_valid(self):
#self.assertTrue(self.manager.authenticate( **boto.generate_url ... ? ? ? ))
@@ -63,133 +109,216 @@ class AuthTestCase(test.BaseTestCase):
'export S3_URL="http://127.0.0.1:3333/"\n' +
'export EC2_USER_ID="test1"\n')
- def test_010_can_list_users(self):
- users = self.manager.get_users()
- logging.warn(users)
- self.assertTrue(filter(lambda u: u.id == 'test1', users))
-
- def test_101_can_add_user_role(self):
- self.assertFalse(self.manager.has_role('test1', 'itsec'))
- self.manager.add_role('test1', 'itsec')
- self.assertTrue(self.manager.has_role('test1', 'itsec'))
-
- def test_199_can_remove_user_role(self):
- self.assertTrue(self.manager.has_role('test1', 'itsec'))
- self.manager.remove_role('test1', 'itsec')
- self.assertFalse(self.manager.has_role('test1', 'itsec'))
-
- def test_201_can_create_project(self):
- project = self.manager.create_project('testproj', 'test1', 'A test project', ['test1'])
- self.assertTrue(filter(lambda p: p.name == 'testproj', self.manager.get_projects()))
- self.assertEqual(project.name, 'testproj')
- self.assertEqual(project.description, 'A test project')
- self.assertEqual(project.project_manager_id, 'test1')
- self.assertTrue(project.has_member('test1'))
-
- def test_202_user1_is_project_member(self):
- self.assertTrue(self.manager.get_user('test1').is_project_member('testproj'))
-
- def test_203_user2_is_not_project_member(self):
- self.assertFalse(self.manager.get_user('test2').is_project_member('testproj'))
-
- def test_204_user1_is_project_manager(self):
- self.assertTrue(self.manager.get_user('test1').is_project_manager('testproj'))
-
- def test_205_user2_is_not_project_manager(self):
- self.assertFalse(self.manager.get_user('test2').is_project_manager('testproj'))
-
- def test_206_can_add_user_to_project(self):
- self.manager.add_to_project('test2', 'testproj')
- self.assertTrue(self.manager.get_project('testproj').has_member('test2'))
-
- def test_207_can_remove_user_from_project(self):
- self.manager.remove_from_project('test2', 'testproj')
- self.assertFalse(self.manager.get_project('testproj').has_member('test2'))
-
- def test_208_can_remove_add_user_with_role(self):
- self.manager.add_to_project('test2', 'testproj')
- self.manager.add_role('test2', 'developer', 'testproj')
- self.manager.remove_from_project('test2', 'testproj')
- self.assertFalse(self.manager.has_role('test2', 'developer', 'testproj'))
- self.manager.add_to_project('test2', 'testproj')
- self.manager.remove_from_project('test2', 'testproj')
-
- def test_209_can_generate_x509(self):
- # MUST HAVE RUN CLOUD SETUP BY NOW
- self.cloud = cloud.CloudController()
- self.cloud.setup()
- _key, cert_str = self.manager._generate_x509_cert('test1', 'testproj')
- logging.debug(cert_str)
-
- # Need to verify that it's signed by the right intermediate CA
- full_chain = crypto.fetch_ca(project_id='testproj', chain=True)
- int_cert = crypto.fetch_ca(project_id='testproj', chain=False)
- cloud_cert = crypto.fetch_ca()
- logging.debug("CA chain:\n\n =====\n%s\n\n=====" % full_chain)
- signed_cert = X509.load_cert_string(cert_str)
- chain_cert = X509.load_cert_string(full_chain)
- int_cert = X509.load_cert_string(int_cert)
- cloud_cert = X509.load_cert_string(cloud_cert)
- self.assertTrue(signed_cert.verify(chain_cert.get_pubkey()))
- self.assertTrue(signed_cert.verify(int_cert.get_pubkey()))
-
- if not FLAGS.use_intermediate_ca:
- self.assertTrue(signed_cert.verify(cloud_cert.get_pubkey()))
- else:
- self.assertFalse(signed_cert.verify(cloud_cert.get_pubkey()))
-
- def test_210_can_add_project_role(self):
- project = self.manager.get_project('testproj')
- self.assertFalse(project.has_role('test1', 'sysadmin'))
- self.manager.add_role('test1', 'sysadmin')
- self.assertFalse(project.has_role('test1', 'sysadmin'))
- project.add_role('test1', 'sysadmin')
- self.assertTrue(project.has_role('test1', 'sysadmin'))
-
- def test_211_can_list_project_roles(self):
- project = self.manager.get_project('testproj')
- user = self.manager.get_user('test1')
- self.manager.add_role(user, 'netadmin', project)
- roles = self.manager.get_user_roles(user)
- self.assertTrue('sysadmin' in roles)
- self.assertFalse('netadmin' in roles)
- project_roles = self.manager.get_user_roles(user, project)
- self.assertTrue('sysadmin' in project_roles)
- self.assertTrue('netadmin' in project_roles)
- # has role should be false because global role is missing
- self.assertFalse(self.manager.has_role(user, 'netadmin', project))
-
-
- def test_212_can_remove_project_role(self):
- project = self.manager.get_project('testproj')
- self.assertTrue(project.has_role('test1', 'sysadmin'))
- project.remove_role('test1', 'sysadmin')
- self.assertFalse(project.has_role('test1', 'sysadmin'))
- self.manager.remove_role('test1', 'sysadmin')
- self.assertFalse(project.has_role('test1', 'sysadmin'))
-
- def test_214_can_retrieve_project_by_user(self):
- project = self.manager.create_project('testproj2', 'test2', 'Another test project', ['test2'])
- self.assert_(len(self.manager.get_projects()) > 1)
- self.assertEqual(len(self.manager.get_projects('test2')), 1)
-
- def test_220_can_modify_project(self):
- self.manager.modify_project('testproj', 'test2', 'new description')
- project = self.manager.get_project('testproj')
- self.assertEqual(project.project_manager_id, 'test2')
- self.assertEqual(project.description, 'new description')
-
- def test_299_can_delete_project(self):
- self.manager.delete_project('testproj')
- self.assertFalse(filter(lambda p: p.name == 'testproj', self.manager.get_projects()))
- self.manager.delete_project('testproj2')
-
- def test_999_can_delete_users(self):
+ def test_can_list_users(self):
+ with user_generator(self.manager):
+ with user_generator(self.manager, name="test2"):
+ users = self.manager.get_users()
+ self.assert_(filter(lambda u: u.id == 'test1', users))
+ self.assert_(filter(lambda u: u.id == 'test2', users))
+ self.assert_(not filter(lambda u: u.id == 'test3', users))
+
+ def test_can_add_and_remove_user_role(self):
+ with user_generator(self.manager):
+ self.assertFalse(self.manager.has_role('test1', 'itsec'))
+ self.manager.add_role('test1', 'itsec')
+ self.assertTrue(self.manager.has_role('test1', 'itsec'))
+ self.manager.remove_role('test1', 'itsec')
+ self.assertFalse(self.manager.has_role('test1', 'itsec'))
+
+ def test_can_create_and_get_project(self):
+ with user_and_project_generator(self.manager) as (u,p):
+ self.assert_(self.manager.get_user('test1'))
+ self.assert_(self.manager.get_user('test1'))
+ self.assert_(self.manager.get_project('testproj'))
+
+ def test_can_list_projects(self):
+ with user_and_project_generator(self.manager):
+ with project_generator(self.manager, name="testproj2"):
+ projects = self.manager.get_projects()
+ self.assert_(filter(lambda p: p.name == 'testproj', projects))
+ self.assert_(filter(lambda p: p.name == 'testproj2', projects))
+ self.assert_(not filter(lambda p: p.name == 'testproj3',
+ projects))
+
+ def test_can_create_and_get_project_with_attributes(self):
+ with user_generator(self.manager):
+ with project_generator(self.manager, description='A test project'):
+ project = self.manager.get_project('testproj')
+ self.assertEqual('A test project', project.description)
+
+ def test_can_create_project_with_manager(self):
+ with user_and_project_generator(self.manager) as (user, project):
+ self.assertEqual('test1', project.project_manager_id)
+ self.assertTrue(self.manager.is_project_manager(user, project))
+
+ def test_create_project_assigns_manager_to_members(self):
+ with user_and_project_generator(self.manager) as (user, project):
+ self.assertTrue(self.manager.is_project_member(user, project))
+
+ def test_no_extra_project_members(self):
+ with user_generator(self.manager, name='test2') as baduser:
+ with user_and_project_generator(self.manager) as (user, project):
+ self.assertFalse(self.manager.is_project_member(baduser,
+ project))
+
+ def test_no_extra_project_managers(self):
+ with user_generator(self.manager, name='test2') as baduser:
+ with user_and_project_generator(self.manager) as (user, project):
+ self.assertFalse(self.manager.is_project_manager(baduser,
+ project))
+
+ def test_can_add_user_to_project(self):
+ with user_generator(self.manager, name='test2') as user:
+ with user_and_project_generator(self.manager) as (_user, project):
+ self.manager.add_to_project(user, project)
+ project = self.manager.get_project('testproj')
+ self.assertTrue(self.manager.is_project_member(user, project))
+
+ def test_can_remove_user_from_project(self):
+ with user_generator(self.manager, name='test2') as user:
+ with user_and_project_generator(self.manager) as (_user, project):
+ self.manager.add_to_project(user, project)
+ project = self.manager.get_project('testproj')
+ self.assertTrue(self.manager.is_project_member(user, project))
+ self.manager.remove_from_project(user, project)
+ project = self.manager.get_project('testproj')
+ self.assertFalse(self.manager.is_project_member(user, project))
+
+ def test_can_add_remove_user_with_role(self):
+ with user_generator(self.manager, name='test2') as user:
+ with user_and_project_generator(self.manager) as (_user, project):
+ # NOTE(todd): after modifying users you must reload project
+ self.manager.add_to_project(user, project)
+ project = self.manager.get_project('testproj')
+ self.manager.add_role(user, 'developer', project)
+ self.assertTrue(self.manager.is_project_member(user, project))
+ self.manager.remove_from_project(user, project)
+ project = self.manager.get_project('testproj')
+ self.assertFalse(self.manager.has_role(user, 'developer',
+ project))
+ self.assertFalse(self.manager.is_project_member(user, project))
+
+ def test_can_generate_x509(self):
+ # NOTE(todd): this doesn't assert against the auth manager
+ # so it probably belongs in crypto_unittest
+ # but I'm leaving it where I found it.
+ with user_and_project_generator(self.manager) as (user, project):
+ # NOTE(todd): Should mention why we must setup controller first
+ # (somebody please clue me in)
+ cloud_controller = cloud.CloudController()
+ cloud_controller.setup()
+ _key, cert_str = self.manager._generate_x509_cert('test1',
+ 'testproj')
+ logging.debug(cert_str)
+
+ # Need to verify that it's signed by the right intermediate CA
+ full_chain = crypto.fetch_ca(project_id='testproj', chain=True)
+ int_cert = crypto.fetch_ca(project_id='testproj', chain=False)
+ cloud_cert = crypto.fetch_ca()
+ logging.debug("CA chain:\n\n =====\n%s\n\n=====" % full_chain)
+ signed_cert = X509.load_cert_string(cert_str)
+ chain_cert = X509.load_cert_string(full_chain)
+ int_cert = X509.load_cert_string(int_cert)
+ cloud_cert = X509.load_cert_string(cloud_cert)
+ self.assertTrue(signed_cert.verify(chain_cert.get_pubkey()))
+ self.assertTrue(signed_cert.verify(int_cert.get_pubkey()))
+ if not FLAGS.use_intermediate_ca:
+ self.assertTrue(signed_cert.verify(cloud_cert.get_pubkey()))
+ else:
+ self.assertFalse(signed_cert.verify(cloud_cert.get_pubkey()))
+
+ def test_adding_role_to_project_is_ignored_unless_added_to_user(self):
+ with user_and_project_generator(self.manager) as (user, project):
+ self.assertFalse(self.manager.has_role(user, 'sysadmin', project))
+ self.manager.add_role(user, 'sysadmin', project)
+ # NOTE(todd): it will still show up in get_user_roles(u, project)
+ self.assertFalse(self.manager.has_role(user, 'sysadmin', project))
+ self.manager.add_role(user, 'sysadmin')
+ self.assertTrue(self.manager.has_role(user, 'sysadmin', project))
+
+ def test_add_user_role_doesnt_infect_project_roles(self):
+ with user_and_project_generator(self.manager) as (user, project):
+ self.assertFalse(self.manager.has_role(user, 'sysadmin', project))
+ self.manager.add_role(user, 'sysadmin')
+ self.assertFalse(self.manager.has_role(user, 'sysadmin', project))
+
+ def test_can_list_user_roles(self):
+ with user_and_project_generator(self.manager) as (user, project):
+ self.manager.add_role(user, 'sysadmin')
+ roles = self.manager.get_user_roles(user)
+ self.assertTrue('sysadmin' in roles)
+ self.assertFalse('netadmin' in roles)
+
+ def test_can_list_project_roles(self):
+ with user_and_project_generator(self.manager) as (user, project):
+ self.manager.add_role(user, 'sysadmin')
+ self.manager.add_role(user, 'sysadmin', project)
+ self.manager.add_role(user, 'netadmin', project)
+ project_roles = self.manager.get_user_roles(user, project)
+ self.assertTrue('sysadmin' in project_roles)
+ self.assertTrue('netadmin' in project_roles)
+ # has role should be false user-level role is missing
+ self.assertFalse(self.manager.has_role(user, 'netadmin', project))
+
+ def test_can_remove_user_roles(self):
+ with user_and_project_generator(self.manager) as (user, project):
+ self.manager.add_role(user, 'sysadmin')
+ self.assertTrue(self.manager.has_role(user, 'sysadmin'))
+ self.manager.remove_role(user, 'sysadmin')
+ self.assertFalse(self.manager.has_role(user, 'sysadmin'))
+
+ def test_removing_user_role_hides_it_from_project(self):
+ with user_and_project_generator(self.manager) as (user, project):
+ self.manager.add_role(user, 'sysadmin')
+ self.manager.add_role(user, 'sysadmin', project)
+ self.assertTrue(self.manager.has_role(user, 'sysadmin', project))
+ self.manager.remove_role(user, 'sysadmin')
+ self.assertFalse(self.manager.has_role(user, 'sysadmin', project))
+
+ def test_can_remove_project_role_but_keep_user_role(self):
+ with user_and_project_generator(self.manager) as (user, project):
+ self.manager.add_role(user, 'sysadmin')
+ self.manager.add_role(user, 'sysadmin', project)
+ self.assertTrue(self.manager.has_role(user, 'sysadmin'))
+ self.manager.remove_role(user, 'sysadmin', project)
+ self.assertFalse(self.manager.has_role(user, 'sysadmin', project))
+ self.assertTrue(self.manager.has_role(user, 'sysadmin'))
+
+ def test_can_retrieve_project_by_user(self):
+ with user_and_project_generator(self.manager) as (user, project):
+ self.assertEqual(1, len(self.manager.get_projects('test1')))
+
+ def test_can_modify_project(self):
+ with user_and_project_generator(self.manager):
+ with user_generator(self.manager, name='test2'):
+ self.manager.modify_project('testproj', 'test2', 'new desc')
+ project = self.manager.get_project('testproj')
+ self.assertEqual('test2', project.project_manager_id)
+ self.assertEqual('new desc', project.description)
+
+ def test_can_delete_project(self):
+ with user_generator(self.manager):
+ self.manager.create_project('testproj', 'test1')
+ self.assert_(self.manager.get_project('testproj'))
+ self.manager.delete_project('testproj')
+ projectlist = self.manager.get_projects()
+ self.assert_(not filter(lambda p: p.name == 'testproj',
+ projectlist))
+
+ def test_can_delete_user(self):
+ self.manager.create_user('test1')
+ self.assert_(self.manager.get_user('test1'))
self.manager.delete_user('test1')
- users = self.manager.get_users()
- self.assertFalse(filter(lambda u: u.id == 'test1', users))
- self.manager.delete_user('test2')
- self.assertEqual(self.manager.get_user('test2'), None)
+ userlist = self.manager.get_users()
+ self.assert_(not filter(lambda u: u.id == 'test1', userlist))
+
+ def test_can_modify_users(self):
+ with user_generator(self.manager):
+ self.manager.modify_user('test1', 'access', 'secret', True)
+ user = self.manager.get_user('test1')
+ self.assertEqual('access', user.access)
+ self.assertEqual('secret', user.secret)
+ self.assertTrue(user.is_admin())
if __name__ == "__main__":
diff --git a/nova/tests/cloud_unittest.py b/nova/tests/cloud_unittest.py
index 756ce519e..ae7dea1db 100644
--- a/nova/tests/cloud_unittest.py
+++ b/nova/tests/cloud_unittest.py
@@ -16,10 +16,13 @@
# License for the specific language governing permissions and limitations
# under the License.
+import json
import logging
from M2Crypto import BIO
from M2Crypto import RSA
+import os
import StringIO
+import tempfile
import time
from twisted.internet import defer
@@ -36,15 +39,22 @@ from nova.auth import manager
from nova.compute import power_state
from nova.api.ec2 import context
from nova.api.ec2 import cloud
+from nova.objectstore import image
FLAGS = flags.FLAGS
-class CloudTestCase(test.BaseTestCase):
+# Temp dirs for working with image attributes through the cloud controller
+# (stole this from objectstore_unittest.py)
+OSS_TEMPDIR = tempfile.mkdtemp(prefix='test_oss-')
+IMAGES_PATH = os.path.join(OSS_TEMPDIR, 'images')
+os.makedirs(IMAGES_PATH)
+
+class CloudTestCase(test.TrialTestCase):
def setUp(self):
super(CloudTestCase, self).setUp()
- self.flags(connection_type='fake')
+ self.flags(connection_type='fake', images_path=IMAGES_PATH)
self.conn = rpc.Connection.instance()
logging.getLogger().setLevel(logging.DEBUG)
@@ -55,9 +65,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 +78,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
@@ -191,3 +201,67 @@ class CloudTestCase(test.BaseTestCase):
#for i in xrange(4):
# data = self.cloud.get_metadata(instance(i)['private_dns_name'])
# self.assert_(data['meta-data']['ami-id'] == 'ami-%s' % i)
+
+ @staticmethod
+ def _fake_set_image_description(ctxt, image_id, description):
+ from nova.objectstore import handler
+ class req:
+ pass
+ request = req()
+ request.context = ctxt
+ request.args = {'image_id': [image_id],
+ 'description': [description]}
+
+ resource = handler.ImagesResource()
+ resource.render_POST(request)
+
+ def test_user_editable_image_endpoint(self):
+ pathdir = os.path.join(FLAGS.images_path, 'ami-testing')
+ os.mkdir(pathdir)
+ info = {'isPublic': False}
+ with open(os.path.join(pathdir, 'info.json'), 'w') as f:
+ json.dump(info, f)
+ img = image.Image('ami-testing')
+ # self.cloud.set_image_description(self.context, 'ami-testing',
+ # 'Foo Img')
+ # NOTE(vish): Above won't work unless we start objectstore or create
+ # a fake version of api/ec2/images.py conn that can
+ # call methods directly instead of going through boto.
+ # for now, just cheat and call the method directly
+ self._fake_set_image_description(self.context, 'ami-testing',
+ 'Foo Img')
+ self.assertEqual('Foo Img', img.metadata['description'])
+ self._fake_set_image_description(self.context, 'ami-testing', '')
+ self.assertEqual('', img.metadata['description'])
+
+ def test_update_of_instance_display_fields(self):
+ inst = db.instance_create({}, {})
+ self.cloud.update_instance(self.context, inst['ec2_id'],
+ display_name='c00l 1m4g3')
+ inst = db.instance_get({}, inst['id'])
+ self.assertEqual('c00l 1m4g3', inst['display_name'])
+ db.instance_destroy({}, inst['id'])
+
+ def test_update_of_instance_wont_update_private_fields(self):
+ inst = db.instance_create({}, {})
+ self.cloud.update_instance(self.context, inst['id'],
+ mac_address='DE:AD:BE:EF')
+ inst = db.instance_get({}, inst['id'])
+ self.assertEqual(None, inst['mac_address'])
+ db.instance_destroy({}, inst['id'])
+
+ def test_update_of_volume_display_fields(self):
+ vol = db.volume_create({}, {})
+ self.cloud.update_volume(self.context, vol['id'],
+ display_name='c00l v0lum3')
+ vol = db.volume_get({}, vol['id'])
+ self.assertEqual('c00l v0lum3', vol['display_name'])
+ db.volume_destroy({}, vol['id'])
+
+ def test_update_of_volume_wont_update_private_fields(self):
+ vol = db.volume_create({}, {})
+ self.cloud.update_volume(self.context, vol['id'],
+ mountpoint='/not/here')
+ vol = db.volume_get({}, vol['id'])
+ self.assertEqual(None, vol['mountpoint'])
+ db.volume_destroy({}, vol['id'])
diff --git a/nova/tests/objectstore_unittest.py b/nova/tests/objectstore_unittest.py
index 6d4023210..1d6b9e826 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
@@ -164,6 +164,12 @@ class ObjectStoreTestCase(test.BaseTestCase):
self.context.project = self.auth_manager.get_project('proj2')
self.assertFalse(my_img.is_authorized(self.context))
+ # change user-editable fields
+ my_img.update_user_editable_fields({'display_name': 'my cool image'})
+ self.assertEqual('my cool image', my_img.metadata['displayName'])
+ my_img.update_user_editable_fields({'display_name': ''})
+ self.assert_(not my_img.metadata['displayName'])
+
class TestHTTPChannel(http.HTTPChannel):
"""Dummy site required for twisted.web"""
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'])
diff --git a/tools/setup_iptables.sh b/tools/setup_iptables.sh
new file mode 100755
index 000000000..673353eb4
--- /dev/null
+++ b/tools/setup_iptables.sh
@@ -0,0 +1,158 @@
+#!/usr/bin/env bash
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2010 United States Government as represented by the
+# Administrator of the National Aeronautics and Space Administration.
+# 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.
+
+# NOTE(vish): This script sets up some reasonable defaults for iptables and
+# creates nova-specific chains. If you use this script you should
+# run nova-network and nova-compute with --use_nova_chains=True
+
+# NOTE(vish): If you run nova-api on a different port, make sure to change
+# the port here
+API_PORT=${API_PORT:-"8773"}
+if [ -n "$1" ]; then
+ CMD=$1
+else
+ CMD="all"
+fi
+
+if [ -n "$2" ]; then
+ IP=$2
+else
+ # NOTE(vish): This will just get the first ip in the list, so if you
+ # have more than one eth device set up, this will fail, and
+ # you should explicitly pass in the ip of the instance
+ IP=`ifconfig | grep -m 1 'inet addr:'| cut -d: -f2 | awk '{print $1}'`
+fi
+
+if [ -n "$3" ]; then
+ PRIVATE_RANGE=$3
+else
+ PRIVATE_RANGE="10.0.0.0/12"
+fi
+
+
+if [ -n "$4" ]; then
+ # NOTE(vish): Management IP is the ip over which to allow ssh traffic. It
+ # will also allow traffic to nova-api
+ MGMT_IP=$4
+else
+ MGMT_IP="$IP"
+fi
+if [ "$CMD" == "clear" ]; then
+ iptables -P INPUT ACCEPT
+ iptables -P FORWARD ACCEPT
+ iptables -P OUTPUT ACCEPT
+ iptables -F
+ iptables -t nat -F
+ iptables -F nova_input
+ iptables -F nova_output
+ iptables -F nova_forward
+ iptables -t nat -F nova_input
+ iptables -t nat -F nova_output
+ iptables -t nat -F nova_forward
+ iptables -t nat -X
+ iptables -X
+fi
+
+if [ "$CMD" == "base" ] || [ "$CMD" == "all" ]; then
+ iptables -P INPUT DROP
+ iptables -A INPUT -m state --state INVALID -j DROP
+ iptables -A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
+ iptables -A INPUT -m tcp -p tcp -d $MGMT_IP --dport 22 -j ACCEPT
+ iptables -A INPUT -m udp -p udp --dport 123 -j ACCEPT
+ iptables -N nova_input
+ iptables -A INPUT -j nova_input
+ iptables -A INPUT -p icmp -j ACCEPT
+ iptables -A INPUT -p tcp -j REJECT --reject-with tcp-reset
+ iptables -A INPUT -j REJECT --reject-with icmp-port-unreachable
+
+ iptables -P FORWARD DROP
+ iptables -A FORWARD -m state --state INVALID -j DROP
+ iptables -A FORWARD -m state --state RELATED,ESTABLISHED -j ACCEPT
+ iptables -A FORWARD -p tcp -m tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu
+ iptables -N nova_forward
+ iptables -A FORWARD -j nova_forward
+
+ # NOTE(vish): DROP on output is too restrictive for now. We need to add
+ # in a bunch of more specific output rules to use it.
+ # iptables -P OUTPUT DROP
+ iptables -A OUTPUT -m state --state INVALID -j DROP
+ iptables -A OUTPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
+ iptables -N nova_output
+ iptables -A OUTPUT -j nova_output
+
+ iptables -t nat -N nova_prerouting
+ iptables -t nat -A PREROUTING -j nova_prerouting
+
+ iptables -t nat -N nova_postrouting
+ iptables -t nat -A POSTROUTING -j nova_postrouting
+
+ iptables -t nat -N nova_output
+ iptables -t nat -A OUTPUT -j nova_output
+fi
+
+if [ "$CMD" == "ganglia" ] || [ "$CMD" == "all" ]; then
+ iptables -A nova_input -m tcp -p tcp -d $IP --dport 8649 -j ACCEPT
+ iptables -A nova_input -m udp -p udp -d $IP --dport 8649 -j ACCEPT
+fi
+
+if [ "$CMD" == "web" ] || [ "$CMD" == "all" ]; then
+ # NOTE(vish): This opens up ports for web access, allowing web-based
+ # dashboards to work.
+ iptables -A nova_input -m tcp -p tcp -d $IP --dport 80 -j ACCEPT
+ iptables -A nova_input -m tcp -p tcp -d $IP --dport 443 -j ACCEPT
+fi
+
+if [ "$CMD" == "objectstore" ] || [ "$CMD" == "all" ]; then
+ iptables -A nova_input -m tcp -p tcp -d $IP --dport 3333 -j ACCEPT
+fi
+
+if [ "$CMD" == "api" ] || [ "$CMD" == "all" ]; then
+ iptables -A nova_input -m tcp -p tcp -d $IP --dport $API_PORT -j ACCEPT
+ if [ "$IP" != "$MGMT_IP" ]; then
+ iptables -A nova_input -m tcp -p tcp -d $MGMT_IP --dport $API_PORT -j ACCEPT
+ fi
+fi
+
+if [ "$CMD" == "redis" ] || [ "$CMD" == "all" ]; then
+ iptables -A nova_input -m tcp -p tcp -d $IP --dport 6379 -j ACCEPT
+fi
+
+if [ "$CMD" == "mysql" ] || [ "$CMD" == "all" ]; then
+ iptables -A nova_input -m tcp -p tcp -d $IP --dport 3306 -j ACCEPT
+fi
+
+if [ "$CMD" == "rabbitmq" ] || [ "$CMD" == "all" ]; then
+ iptables -A nova_input -m tcp -p tcp -d $IP --dport 4369 -j ACCEPT
+ iptables -A nova_input -m tcp -p tcp -d $IP --dport 5672 -j ACCEPT
+ iptables -A nova_input -m tcp -p tcp -d $IP --dport 53284 -j ACCEPT
+fi
+
+if [ "$CMD" == "dnsmasq" ] || [ "$CMD" == "all" ]; then
+ # NOTE(vish): this could theoretically be setup per network
+ # for each host, but it seems like overkill
+ iptables -A nova_input -m tcp -p tcp -s $PRIVATE_RANGE --dport 53 -j ACCEPT
+ iptables -A nova_input -m udp -p udp -s $PRIVATE_RANGE --dport 53 -j ACCEPT
+ iptables -A nova_input -m udp -p udp --dport 67 -j ACCEPT
+fi
+
+if [ "$CMD" == "ldap" ] || [ "$CMD" == "all" ]; then
+ iptables -A nova_input -m tcp -p tcp -d $IP --dport 389 -j ACCEPT
+fi
+
+