summaryrefslogtreecommitdiffstats
path: root/nova/api
diff options
context:
space:
mode:
authorVishvananda Ishaya <vishvananda@gmail.com>2011-02-22 13:36:18 -0800
committerVishvananda Ishaya <vishvananda@gmail.com>2011-02-22 13:36:18 -0800
commit6685226189bd91964dccddab23b7c11b256a6a7b (patch)
tree90e71968998244a62cdc26ec4a15710db40b9fc3 /nova/api
parent2f4258d99e8d97ec70645cd2df2f4e54dc869e89 (diff)
parentbd0ca93866b48a7a65de8b97ab0ac0ac9c737f73 (diff)
merged trunk
Diffstat (limited to 'nova/api')
-rw-r--r--nova/api/direct.py12
-rw-r--r--nova/api/ec2/__init__.py76
-rw-r--r--nova/api/ec2/admin.py63
-rw-r--r--nova/api/ec2/apirequest.py14
-rw-r--r--nova/api/ec2/cloud.py108
-rw-r--r--nova/api/openstack/__init__.py12
-rw-r--r--nova/api/openstack/auth.py1
-rw-r--r--nova/api/openstack/common.py35
-rw-r--r--nova/api/openstack/servers.py72
-rw-r--r--nova/api/openstack/zones.py80
10 files changed, 350 insertions, 123 deletions
diff --git a/nova/api/direct.py b/nova/api/direct.py
index 81b3ae202..208b6d086 100644
--- a/nova/api/direct.py
+++ b/nova/api/direct.py
@@ -142,9 +142,15 @@ class Reflection(object):
if argspec[2]:
args_out.insert(0, ('**%s' % argspec[2],))
+ if f.__doc__:
+ short_doc = f.__doc__.split('\n')[0]
+ doc = f.__doc__
+ else:
+ short_doc = doc = _('not available')
+
methods['/%s/%s' % (route, k)] = {
- 'short_doc': f.__doc__.split('\n')[0],
- 'doc': f.__doc__,
+ 'short_doc': short_doc,
+ 'doc': doc,
'name': k,
'args': list(reversed(args_out))}
@@ -196,6 +202,8 @@ class ServiceWrapper(wsgi.Controller):
# TODO(termie): do some basic normalization on methods
method = getattr(self.service_handle, action)
+ # NOTE(vish): make sure we have no unicode keys for py2.6.
+ params = dict([(str(k), v) for (k, v) in params.iteritems()])
result = method(context, **params)
if type(result) is dict or type(result) is list:
return self._serialize(result, req)
diff --git a/nova/api/ec2/__init__.py b/nova/api/ec2/__init__.py
index 238cb0f38..5adc2c075 100644
--- a/nova/api/ec2/__init__.py
+++ b/nova/api/ec2/__init__.py
@@ -20,8 +20,6 @@ Starting point for routing EC2 requests.
"""
-import datetime
-import routes
import webob
import webob.dec
import webob.exc
@@ -33,6 +31,7 @@ from nova import log as logging
from nova import utils
from nova import wsgi
from nova.api.ec2 import apirequest
+from nova.api.ec2 import cloud
from nova.auth import manager
@@ -56,23 +55,20 @@ class RequestLogging(wsgi.Middleware):
@webob.dec.wsgify
def __call__(self, req):
+ start = utils.utcnow()
rv = req.get_response(self.application)
- self.log_request_completion(rv, req)
+ self.log_request_completion(rv, req, start)
return rv
- def log_request_completion(self, response, request):
+ def log_request_completion(self, response, request, start):
controller = request.environ.get('ec2.controller', None)
if controller:
controller = controller.__class__.__name__
action = request.environ.get('ec2.action', None)
ctxt = request.environ.get('ec2.context', None)
- seconds = 'X'
- microseconds = 'X'
- if ctxt:
- delta = datetime.datetime.utcnow() - \
- ctxt.timestamp
- seconds = delta.seconds
- microseconds = delta.microseconds
+ delta = utils.utcnow() - start
+ seconds = delta.seconds
+ microseconds = delta.microseconds
LOG.info(
"%s.%ss %s %s %s %s:%s %s [%s] %s %s",
seconds,
@@ -131,9 +127,11 @@ class Lockout(wsgi.Middleware):
# NOTE(vish): To use incr, failures has to be a string.
self.mc.set(failures_key, '1', time=FLAGS.lockout_window * 60)
elif failures >= FLAGS.lockout_attempts:
- LOG.warn(_('Access key %s has had %d failed authentications'
- ' and will be locked out for %d minutes.'),
- access_key, failures, FLAGS.lockout_minutes)
+ lock_mins = FLAGS.lockout_minutes
+ msg = _('Access key %(access_key)s has had %(failures)d'
+ ' failed authentications and will be locked out'
+ ' for %(lock_mins)d minutes.') % locals()
+ LOG.warn(msg)
self.mc.set(failures_key, str(failures),
time=FLAGS.lockout_minutes * 60)
return res
@@ -168,7 +166,7 @@ class Authenticate(wsgi.Middleware):
req.path)
# Be explicit for what exceptions are 403, the rest bubble as 500
except (exception.NotFound, exception.NotAuthorized) as ex:
- LOG.audit(_("Authentication Failure: %s"), str(ex))
+ LOG.audit(_("Authentication Failure: %s"), unicode(ex))
raise webob.exc.HTTPForbidden()
# Authenticated!
@@ -179,8 +177,10 @@ class Authenticate(wsgi.Middleware):
project=project,
remote_address=remote_address)
req.environ['ec2.context'] = ctxt
- LOG.audit(_('Authenticated Request For %s:%s)'), user.name,
- project.name, context=req.environ['ec2.context'])
+ uname = user.name
+ pname = project.name
+ msg = _('Authenticated Request For %(uname)s:%(pname)s)') % locals()
+ LOG.audit(msg, context=req.environ['ec2.context'])
return self.application
@@ -206,10 +206,11 @@ class Requestify(wsgi.Middleware):
LOG.debug(_('action: %s'), action)
for key, value in args.items():
- LOG.debug(_('arg: %s\t\tval: %s'), key, value)
+ LOG.debug(_('arg: %(key)s\t\tval: %(value)s') % locals())
# Success!
- api_request = apirequest.APIRequest(self.controller, action, args)
+ api_request = apirequest.APIRequest(self.controller, action,
+ req.params['Version'], args)
req.environ['ec2.request'] = api_request
req.environ['ec2.action_args'] = args
return self.application
@@ -227,7 +228,7 @@ class Authorizer(wsgi.Middleware):
super(Authorizer, self).__init__(application)
self.action_roles = {
'CloudController': {
- 'DescribeAvailabilityzones': ['all'],
+ 'DescribeAvailabilityZones': ['all'],
'DescribeRegions': ['all'],
'DescribeSnapshots': ['all'],
'DescribeKeyPairs': ['all'],
@@ -277,8 +278,8 @@ class Authorizer(wsgi.Middleware):
if self._matches_any_role(context, allowed_roles):
return self.application
else:
- LOG.audit(_("Unauthorized request for controller=%s "
- "and action=%s"), controller, action, context=context)
+ LOG.audit(_('Unauthorized request for controller=%(controller)s '
+ 'and action=%(action)s') % locals(), context=context)
raise webob.exc.HTTPUnauthorized()
def _matches_any_role(self, context, roles):
@@ -289,7 +290,7 @@ class Authorizer(wsgi.Middleware):
return True
if 'none' in roles:
return False
- return any(context.project.has_role(context.user.id, role)
+ return any(context.project.has_role(context.user_id, role)
for role in roles)
@@ -309,18 +310,32 @@ class Executor(wsgi.Application):
result = None
try:
result = api_request.invoke(context)
+ except exception.InstanceNotFound as ex:
+ LOG.info(_('InstanceNotFound raised: %s'), unicode(ex),
+ context=context)
+ ec2_id = cloud.id_to_ec2_id(ex.instance_id)
+ message = _('Instance %s not found') % ec2_id
+ return self._error(req, context, type(ex).__name__, message)
+ except exception.VolumeNotFound as ex:
+ LOG.info(_('VolumeNotFound raised: %s'), unicode(ex),
+ context=context)
+ ec2_id = cloud.id_to_ec2_id(ex.volume_id, 'vol-%08x')
+ message = _('Volume %s not found') % ec2_id
+ return self._error(req, context, type(ex).__name__, message)
except exception.NotFound as ex:
- LOG.info(_('NotFound raised: %s'), str(ex), context=context)
- return self._error(req, context, type(ex).__name__, str(ex))
+ LOG.info(_('NotFound raised: %s'), unicode(ex), context=context)
+ return self._error(req, context, type(ex).__name__, unicode(ex))
except exception.ApiError as ex:
- LOG.exception(_('ApiError raised: %s'), str(ex), context=context)
+ LOG.exception(_('ApiError raised: %s'), unicode(ex),
+ context=context)
if ex.code:
- return self._error(req, context, ex.code, str(ex))
+ return self._error(req, context, ex.code, unicode(ex))
else:
- return self._error(req, context, type(ex).__name__, str(ex))
+ return self._error(req, context, type(ex).__name__,
+ unicode(ex))
except Exception as ex:
extra = {'environment': req.environ}
- LOG.exception(_('Unexpected error raised: %s'), str(ex),
+ LOG.exception(_('Unexpected error raised: %s'), unicode(ex),
extra=extra, context=context)
return self._error(req,
context,
@@ -343,7 +358,8 @@ class Executor(wsgi.Application):
'<Response><Errors><Error><Code>%s</Code>'
'<Message>%s</Message></Error></Errors>'
'<RequestID>%s</RequestID></Response>' %
- (code, message, context.request_id))
+ (utils.utf8(code), utils.utf8(message),
+ utils.utf8(context.request_id)))
return resp
diff --git a/nova/api/ec2/admin.py b/nova/api/ec2/admin.py
index 758b612e8..735951082 100644
--- a/nova/api/ec2/admin.py
+++ b/nova/api/ec2/admin.py
@@ -26,6 +26,7 @@ from nova import db
from nova import exception
from nova import log as logging
from nova.auth import manager
+from nova.compute import instance_types
LOG = logging.getLogger('nova.api.ec2.admin')
@@ -62,6 +63,14 @@ def host_dict(host):
return {}
+def instance_dict(name, inst):
+ return {'name': name,
+ 'memory_mb': inst['memory_mb'],
+ 'vcpus': inst['vcpus'],
+ 'disk_gb': inst['local_gb'],
+ 'flavor_id': inst['flavorid']}
+
+
class AdminController(object):
"""
API Controller for users, hosts, nodes, and workers.
@@ -70,6 +79,10 @@ class AdminController(object):
def __str__(self):
return 'AdminController'
+ def describe_instance_types(self, _context, **_kwargs):
+ return {'instanceTypeSet': [instance_dict(n, v) for n, v in
+ instance_types.INSTANCE_TYPES.iteritems()]}
+
def describe_user(self, _context, name, **_kwargs):
"""Returns user data, including access and secret keys."""
return user_dict(manager.AuthManager().get_user(name))
@@ -111,19 +124,23 @@ class AdminController(object):
"""Add or remove a role for a user and project."""
if operation == 'add':
if project:
- LOG.audit(_("Adding role %s to user %s for project %s"), role,
- user, project, context=context)
+ msg = _("Adding role %(role)s to user %(user)s"
+ " for project %(project)s") % locals()
+ LOG.audit(msg, context=context)
else:
- LOG.audit(_("Adding sitewide role %s to user %s"), role, user,
- context=context)
+ msg = _("Adding sitewide role %(role)s to"
+ " user %(user)s") % locals()
+ LOG.audit(msg, context=context)
manager.AuthManager().add_role(user, role, project)
elif operation == 'remove':
if project:
- LOG.audit(_("Removing role %s from user %s for project %s"),
- role, user, project, context=context)
+ msg = _("Removing role %(role)s from user %(user)s"
+ " for project %(project)s") % locals()
+ LOG.audit(msg, context=context)
else:
- LOG.audit(_("Removing sitewide role %s from user %s"), role,
- user, context=context)
+ msg = _("Removing sitewide role %(role)s"
+ " from user %(user)s") % locals()
+ LOG.audit(msg, context=context)
manager.AuthManager().remove_role(user, role, project)
else:
raise exception.ApiError(_('operation must be add or remove'))
@@ -139,8 +156,9 @@ class AdminController(object):
project = name
project = manager.AuthManager().get_project(project)
user = manager.AuthManager().get_user(name)
- LOG.audit(_("Getting x509 for user: %s on project: %s"), name,
- project, context=context)
+ msg = _("Getting x509 for user: %(name)s"
+ " on project: %(project)s") % locals()
+ LOG.audit(msg, context=context)
return user_dict(user, base64.b64encode(project.get_credentials(user)))
def describe_project(self, context, name, **kwargs):
@@ -156,8 +174,9 @@ class AdminController(object):
def register_project(self, context, name, manager_user, description=None,
member_users=None, **kwargs):
"""Creates a new project"""
- LOG.audit(_("Create project %s managed by %s"), name, manager_user,
- context=context)
+ msg = _("Create project %(name)s managed by"
+ " %(manager_user)s") % locals()
+ LOG.audit(msg, context=context)
return project_dict(
manager.AuthManager().create_project(
name,
@@ -165,6 +184,17 @@ class AdminController(object):
description=None,
member_users=None))
+ def modify_project(self, context, name, manager_user, description=None,
+ **kwargs):
+ """Modifies a project"""
+ msg = _("Modify project: %(name)s managed by"
+ " %(manager_user)s") % locals()
+ LOG.audit(msg, context=context)
+ manager.AuthManager().modify_project(name,
+ manager_user=manager_user,
+ description=description)
+ return True
+
def deregister_project(self, context, name):
"""Permanently deletes a project."""
LOG.audit(_("Delete project: %s"), name, context=context)
@@ -181,12 +211,13 @@ class AdminController(object):
**kwargs):
"""Add or remove a user from a project."""
if operation == 'add':
- LOG.audit(_("Adding user %s to project %s"), user, project,
- context=context)
+ msg = _("Adding user %(user)s to project %(project)s") % locals()
+ LOG.audit(msg, context=context)
manager.AuthManager().add_to_project(user, project)
elif operation == 'remove':
- LOG.audit(_("Removing user %s from project %s"), user, project,
- context=context)
+ msg = _("Removing user %(user)s from"
+ " project %(project)s") % locals()
+ LOG.audit(msg, context=context)
manager.AuthManager().remove_from_project(user, project)
else:
raise exception.ApiError(_('operation must be add or remove'))
diff --git a/nova/api/ec2/apirequest.py b/nova/api/ec2/apirequest.py
index 78576470a..00b527d62 100644
--- a/nova/api/ec2/apirequest.py
+++ b/nova/api/ec2/apirequest.py
@@ -20,6 +20,7 @@
APIRequest class
"""
+import datetime
import re
# TODO(termie): replace minidom with etree
from xml.dom import minidom
@@ -83,9 +84,10 @@ def _try_convert(value):
class APIRequest(object):
- def __init__(self, controller, action, args):
+ def __init__(self, controller, action, version, args):
self.controller = controller
self.action = action
+ self.version = version
self.args = args
def invoke(self, context):
@@ -93,8 +95,10 @@ class APIRequest(object):
method = getattr(self.controller,
_camelcase_to_underscore(self.action))
except AttributeError:
- _error = _('Unsupported API request: controller = %s,'
- 'action = %s') % (self.controller, self.action)
+ controller = self.controller
+ action = self.action
+ _error = _('Unsupported API request: controller = %(controller)s,'
+ ' action = %(action)s') % locals()
LOG.exception(_error)
# TODO: Raise custom exception, trap in apiserver,
# and reraise as 400 error.
@@ -130,7 +134,7 @@ class APIRequest(object):
response_el = xml.createElement(self.action + 'Response')
response_el.setAttribute('xmlns',
- 'http://ec2.amazonaws.com/doc/2009-11-30/')
+ 'http://ec2.amazonaws.com/doc/%s/' % self.version)
request_id_el = xml.createElement('requestId')
request_id_el.appendChild(xml.createTextNode(request_id))
response_el.appendChild(request_id_el)
@@ -168,6 +172,8 @@ class APIRequest(object):
self._render_dict(xml, data_el, data.__dict__)
elif isinstance(data, bool):
data_el.appendChild(xml.createTextNode(str(data).lower()))
+ elif isinstance(data, datetime.datetime):
+ data_el.appendChild(xml.createTextNode(data.isoformat()))
elif data != None:
data_el.appendChild(xml.createTextNode(str(data)))
diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py
index 57d41ed67..882cdcfc9 100644
--- a/nova/api/ec2/cloud.py
+++ b/nova/api/ec2/cloud.py
@@ -59,7 +59,7 @@ def _gen_key(context, user_id, key_name):
# creation before creating key_pair
try:
db.key_pair_get(context, user_id, key_name)
- raise exception.Duplicate("The key_pair %s already exists"
+ raise exception.Duplicate(_("The key_pair %s already exists")
% key_name)
except exception.NotFound:
pass
@@ -133,7 +133,7 @@ class CloudController(object):
return result
def _get_availability_zone_by_host(self, context, host):
- services = db.service_get_all_by_host(context, host)
+ services = db.service_get_all_by_host(context.elevated(), host)
if len(services) > 0:
return services[0]['availability_zone']
return 'unknown zone'
@@ -252,18 +252,18 @@ class CloudController(object):
regions = []
for region in FLAGS.region_list:
name, _sep, host = region.partition('=')
- endpoint = '%s://%s:%s%s' % (FLAGS.ec2_prefix,
+ endpoint = '%s://%s:%s%s' % (FLAGS.ec2_scheme,
host,
FLAGS.ec2_port,
- FLAGS.ec2_suffix)
+ FLAGS.ec2_path)
regions.append({'regionName': name,
'regionEndpoint': endpoint})
else:
regions = [{'regionName': 'nova',
- 'regionEndpoint': '%s://%s:%s%s' % (FLAGS.ec2_prefix,
+ 'regionEndpoint': '%s://%s:%s%s' % (FLAGS.ec2_scheme,
FLAGS.ec2_host,
FLAGS.ec2_port,
- FLAGS.ec2_suffix)}]
+ FLAGS.ec2_path)}]
return {'regionInfo': regions}
def describe_snapshots(self,
@@ -282,7 +282,7 @@ class CloudController(object):
'description': 'fixme'}]}
def describe_key_pairs(self, context, key_name=None, **kwargs):
- key_pairs = db.key_pair_get_all_by_user(context, context.user.id)
+ key_pairs = db.key_pair_get_all_by_user(context, context.user_id)
if not key_name is None:
key_pairs = [x for x in key_pairs if x['name'] in key_name]
@@ -290,7 +290,7 @@ class CloudController(object):
for key_pair in key_pairs:
# filter out the vpn keys
suffix = FLAGS.vpn_key_suffix
- if context.user.is_admin() or \
+ if context.is_admin or \
not key_pair['name'].endswith(suffix):
result.append({
'keyName': key_pair['name'],
@@ -301,7 +301,7 @@ class CloudController(object):
def create_key_pair(self, context, key_name, **kwargs):
LOG.audit(_("Create key pair %s"), key_name, context=context)
- data = _gen_key(context, context.user.id, key_name)
+ data = _gen_key(context, context.user_id, key_name)
return {'keyName': key_name,
'keyFingerprint': data['fingerprint'],
'keyMaterial': data['private_key']}
@@ -310,7 +310,7 @@ class CloudController(object):
def delete_key_pair(self, context, key_name, **kwargs):
LOG.audit(_("Delete key pair %s"), key_name, context=context)
try:
- db.key_pair_destroy(context, context.user.id, key_name)
+ db.key_pair_destroy(context, context.user_id, key_name)
except exception.NotFound:
# aws returns true even if the key doesn't exist
pass
@@ -318,7 +318,7 @@ class CloudController(object):
def describe_security_groups(self, context, group_name=None, **kwargs):
self.compute_api.ensure_default_security_group(context)
- if context.user.is_admin():
+ if context.is_admin:
groups = db.security_group_get_all(context)
else:
groups = db.security_group_get_by_project(context,
@@ -327,7 +327,9 @@ class CloudController(object):
if not group_name is None:
groups = [g for g in groups if g.name in group_name]
- return {'securityGroupInfo': groups}
+ return {'securityGroupInfo':
+ list(sorted(groups,
+ key=lambda k: (k['ownerId'], k['groupName'])))}
def _format_security_group(self, context, group):
g = {}
@@ -492,7 +494,7 @@ class CloudController(object):
if db.security_group_exists(context, context.project_id, group_name):
raise exception.ApiError(_('group %s already exists') % group_name)
- group = {'user_id': context.user.id,
+ group = {'user_id': context.user_id,
'project_id': context.project_id,
'name': group_name,
'description': group_description}
@@ -512,8 +514,11 @@ class CloudController(object):
def get_console_output(self, context, instance_id, **kwargs):
LOG.audit(_("Get console output for instance %s"), instance_id,
context=context)
- # instance_id is passed in as a list of instances
- ec2_id = instance_id[0]
+ # instance_id may be passed in as a list of instances
+ if type(instance_id) == list:
+ ec2_id = instance_id[0]
+ else:
+ ec2_id = instance_id
instance_id = ec2_id_to_id(ec2_id)
output = self.compute_api.get_console_output(
context, instance_id=instance_id)
@@ -529,11 +534,14 @@ class CloudController(object):
def describe_volumes(self, context, volume_id=None, **kwargs):
if volume_id:
- volume_id = [ec2_id_to_id(x) for x in volume_id]
- volumes = self.volume_api.get_all(context)
- # NOTE(vish): volume_id is an optional list of volume ids to filter by.
- volumes = [self._format_volume(context, v) for v in volumes
- if volume_id is None or v['id'] in volume_id]
+ volumes = []
+ for ec2_id in volume_id:
+ internal_id = ec2_id_to_id(ec2_id)
+ volume = self.volume_api.get(context, internal_id)
+ volumes.append(volume)
+ else:
+ volumes = self.volume_api.get_all(context)
+ volumes = [self._format_volume(context, v) for v in volumes]
return {'volumeSet': volumes}
def _format_volume(self, context, volume):
@@ -601,8 +609,9 @@ class CloudController(object):
def attach_volume(self, context, volume_id, instance_id, device, **kwargs):
volume_id = ec2_id_to_id(volume_id)
instance_id = ec2_id_to_id(instance_id)
- LOG.audit(_("Attach volume %s to instance %s at %s"), volume_id,
- instance_id, device, context=context)
+ msg = _("Attach volume %(volume_id)s to instance %(instance_id)s"
+ " at %(device)s") % locals()
+ LOG.audit(msg, context=context)
self.compute_api.attach_volume(context,
instance_id=instance_id,
volume_id=volume_id,
@@ -657,12 +666,15 @@ class CloudController(object):
reservations = {}
# NOTE(vish): instance_id is an optional list of ids to filter by
if instance_id:
- instance_id = [ec2_id_to_id(x) for x in instance_id]
- instances = [self.compute_api.get(context, x) for x in instance_id]
+ instances = []
+ for ec2_id in instance_id:
+ internal_id = ec2_id_to_id(ec2_id)
+ instance = self.compute_api.get(context, internal_id)
+ instances.append(instance)
else:
instances = self.compute_api.get_all(context, **kwargs)
for instance in instances:
- if not context.user.is_admin():
+ if not context.is_admin:
if instance['image_id'] == FLAGS.vpn_image_id:
continue
i = {}
@@ -690,7 +702,7 @@ class CloudController(object):
i['dnsName'] = i['publicDnsName'] or i['privateDnsName']
i['keyName'] = instance['key_name']
- if context.user.is_admin():
+ if context.is_admin:
i['keyName'] = '%s (%s, %s)' % (i['keyName'],
instance['project_id'],
instance['host'])
@@ -707,7 +719,12 @@ class CloudController(object):
r = {}
r['reservationId'] = instance['reservation_id']
r['ownerId'] = instance['project_id']
- r['groupSet'] = self._convert_to_set([], 'groups')
+ security_group_names = []
+ if instance.get('security_groups'):
+ for security_group in instance['security_groups']:
+ security_group_names.append(security_group['name'])
+ r['groupSet'] = self._convert_to_set(security_group_names,
+ 'groupId')
r['instancesSet'] = []
reservations[instance['reservation_id']] = r
reservations[instance['reservation_id']]['instancesSet'].append(i)
@@ -719,7 +736,7 @@ class CloudController(object):
def format_addresses(self, context):
addresses = []
- if context.user.is_admin():
+ if context.is_admin:
iterator = db.floating_ip_get_all(context)
else:
iterator = db.floating_ip_get_all_by_project(context,
@@ -729,11 +746,11 @@ class CloudController(object):
ec2_id = None
if (floating_ip_ref['fixed_ip']
and floating_ip_ref['fixed_ip']['instance']):
- instance_id = floating_ip_ref['fixed_ip']['instance']['ec2_id']
+ instance_id = floating_ip_ref['fixed_ip']['instance']['id']
ec2_id = id_to_ec2_id(instance_id)
address_rv = {'public_ip': address,
'instance_id': ec2_id}
- if context.user.is_admin():
+ if context.is_admin:
details = "%s (%s)" % (address_rv['instance_id'],
floating_ip_ref['project_id'])
address_rv['instance_id'] = details
@@ -751,8 +768,8 @@ class CloudController(object):
return {'releaseResponse': ["Address released."]}
def associate_address(self, context, instance_id, public_ip, **kwargs):
- LOG.audit(_("Associate address %s to instance %s"), public_ip,
- instance_id, context=context)
+ LOG.audit(_("Associate address %(public_ip)s to"
+ " instance %(instance_id)s") % locals(), context=context)
instance_id = ec2_id_to_id(instance_id)
self.compute_api.associate_floating_ip(context,
instance_id=instance_id,
@@ -824,11 +841,26 @@ class CloudController(object):
self.compute_api.update(context, instance_id=instance_id, **kwargs)
return True
+ def _format_image(self, context, image):
+ """Convert from format defined by BaseImageService to S3 format."""
+ i = {}
+ i['imageId'] = image.get('id')
+ i['kernelId'] = image.get('kernel_id')
+ i['ramdiskId'] = image.get('ramdisk_id')
+ i['imageOwnerId'] = image.get('owner_id')
+ i['imageLocation'] = image.get('location')
+ i['imageState'] = image.get('status')
+ i['type'] = image.get('type')
+ i['isPublic'] = image.get('is_public')
+ i['architecture'] = image.get('architecture')
+ return i
+
def describe_images(self, context, image_id=None, **kwargs):
- # Note: image_id is a list!
+ # NOTE: image_id is a list!
images = self.image_service.index(context)
if image_id:
- images = filter(lambda x: x['imageId'] in image_id, images)
+ images = filter(lambda x: x['id'] in image_id, images)
+ images = [self._format_image(context, i) for i in images]
return {'imagesSet': images}
def deregister_image(self, context, image_id, **kwargs):
@@ -840,8 +872,9 @@ class CloudController(object):
if image_location is None and 'name' in kwargs:
image_location = kwargs['name']
image_id = self.image_service.register(context, image_location)
- LOG.audit(_("Registered image %s with id %s"), image_location,
- image_id, context=context)
+ msg = _("Registered image %(image_location)s with"
+ " id %(image_id)s") % locals()
+ LOG.audit(msg, context=context)
return {'imageId': image_id}
def describe_image_attribute(self, context, image_id, attribute, **kwargs):
@@ -850,6 +883,9 @@ class CloudController(object):
% attribute)
try:
image = self.image_service.show(context, image_id)
+ image = self._format_image(context,
+ self.image_service.show(context,
+ image_id))
except IndexError:
raise exception.ApiError(_('invalid id: %s') % image_id)
result = {'image_id': image_id, 'launchPermission': []}
diff --git a/nova/api/openstack/__init__.py b/nova/api/openstack/__init__.py
index f2caac483..d0b18eced 100644
--- a/nova/api/openstack/__init__.py
+++ b/nova/api/openstack/__init__.py
@@ -34,13 +34,11 @@ from nova.api.openstack import flavors
from nova.api.openstack import images
from nova.api.openstack import servers
from nova.api.openstack import shared_ip_groups
+from nova.api.openstack import zones
LOG = logging.getLogger('nova.api.openstack')
FLAGS = flags.FLAGS
-flags.DEFINE_string('os_krm_mapping_file',
- 'krm_mapping.json',
- 'Location of OpenStack Flavor/OS:EC2 Kernel/Ramdisk/Machine JSON file.')
flags.DEFINE_bool('allow_admin_api',
False,
'When True, this API service will accept admin operations.')
@@ -54,8 +52,8 @@ class FaultWrapper(wsgi.Middleware):
try:
return req.get_response(self.application)
except Exception as ex:
- LOG.exception(_("Caught error: %s"), str(ex))
- exc = webob.exc.HTTPInternalServerError(explanation=str(ex))
+ LOG.exception(_("Caught error: %s"), unicode(ex))
+ exc = webob.exc.HTTPInternalServerError(explanation=unicode(ex))
return faults.Fault(exc)
@@ -82,6 +80,10 @@ class APIRouter(wsgi.Router):
server_members["actions"] = "GET"
server_members['suspend'] = 'POST'
server_members['resume'] = 'POST'
+ server_members['reset_network'] = 'POST'
+
+ mapper.resource("zone", "zones", controller=zones.Controller(),
+ collection={'detail': 'GET'})
mapper.resource("server", "servers", controller=servers.Controller(),
collection={'detail': 'GET'},
diff --git a/nova/api/openstack/auth.py b/nova/api/openstack/auth.py
index 1dfdd5318..473071738 100644
--- a/nova/api/openstack/auth.py
+++ b/nova/api/openstack/auth.py
@@ -19,6 +19,7 @@ import datetime
import hashlib
import json
import time
+import logging
import webob.exc
import webob.dec
diff --git a/nova/api/openstack/common.py b/nova/api/openstack/common.py
index 037ed47a0..1dc3767e2 100644
--- a/nova/api/openstack/common.py
+++ b/nova/api/openstack/common.py
@@ -18,22 +18,29 @@
from nova import exception
-def limited(items, req):
- """Return a slice of items according to requested offset and limit.
-
- items - a sliceable
- req - wobob.Request possibly containing offset and limit GET variables.
- offset is where to start in the list, and limit is the maximum number
- of items to return.
+def limited(items, request, max_limit=1000):
+ """
+ Return a slice of items according to requested offset and limit.
- If limit is not specified, 0, or > 1000, defaults to 1000.
+ @param items: A sliceable entity
+ @param request: `webob.Request` possibly containing 'offset' and 'limit'
+ GET variables. 'offset' is where to start in the list,
+ and 'limit' is the maximum number of items to return. If
+ 'limit' is not specified, 0, or > max_limit, we default
+ to max_limit.
+ @kwarg max_limit: The maximum number of items to return from 'items'
"""
+ try:
+ offset = int(request.GET.get('offset', 0))
+ except ValueError:
+ offset = 0
+
+ try:
+ limit = int(request.GET.get('limit', max_limit))
+ except ValueError:
+ limit = max_limit
- offset = int(req.GET.get('offset', 0))
- limit = int(req.GET.get('limit', 0))
- if not limit:
- limit = 1000
- limit = min(1000, limit)
+ limit = min(max_limit, limit or max_limit)
range_end = offset + limit
return items[offset:range_end]
@@ -54,7 +61,7 @@ def get_image_id_from_image_hash(image_service, context, image_hash):
except NotImplementedError:
items = image_service.index(context)
for image in items:
- image_id = image['imageId']
+ image_id = image['id']
if abs(hash(image_id)) == int(image_hash):
return image_id
raise exception.NotFound(image_hash)
diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py
index 8cbcebed2..ce9601ecb 100644
--- a/nova/api/openstack/servers.py
+++ b/nova/api/openstack/servers.py
@@ -1,5 +1,3 @@
-# vim: tabstop=4 shiftwidth=4 softtabstop=4
-
# Copyright 2010 OpenStack LLC.
# All Rights Reserved.
#
@@ -64,6 +62,22 @@ def _translate_detail_keys(inst):
inst_dict['status'] = power_mapping[inst_dict['status']]
inst_dict['addresses'] = dict(public=[], private=[])
+
+ # grab single private fixed ip
+ try:
+ private_ip = inst['fixed_ip']['address']
+ if private_ip:
+ inst_dict['addresses']['private'].append(private_ip)
+ except KeyError:
+ LOG.debug(_("Failed to read private ip"))
+
+ # grab all public floating ips
+ try:
+ for floating in inst['fixed_ip']['floating_ips']:
+ inst_dict['addresses']['public'].append(floating['address'])
+ except KeyError:
+ LOG.debug(_("Failed to read public ip(s)"))
+
inst_dict['metadata'] = {}
inst_dict['hostId'] = ''
@@ -124,17 +138,23 @@ class Controller(wsgi.Controller):
return faults.Fault(exc.HTTPNotFound())
return exc.HTTPAccepted()
- def _get_kernel_ramdisk_from_image(self, image_id):
- mapping_filename = FLAGS.os_krm_mapping_file
-
- with open(mapping_filename) as f:
- mapping = json.load(f)
- if image_id in mapping:
- return mapping[image_id]
+ def _get_kernel_ramdisk_from_image(self, req, image_id):
+ """
+ Machine images are associated with Kernels and Ramdisk images via
+ metadata stored in Glance as 'image_properties'
+ """
+ def lookup(param):
+ _image_id = image_id
+ try:
+ return image['properties'][param]
+ except KeyError:
+ raise exception.NotFound(
+ _("%(param)s property not found for image %(_image_id)s") %
+ locals())
- raise exception.NotFound(
- _("No entry for image '%s' in mapping file '%s'") %
- (image_id, mapping_filename))
+ image_id = str(image_id)
+ image = self._image_service.show(req.environ['nova.context'], image_id)
+ return lookup('kernel_id'), lookup('ramdisk_id')
def create(self, req):
""" Creates a new server for a given user """
@@ -142,11 +162,16 @@ class Controller(wsgi.Controller):
if not env:
return faults.Fault(exc.HTTPUnprocessableEntity())
- key_pair = auth_manager.AuthManager.get_key_pairs(
- req.environ['nova.context'])[0]
+ key_pairs = auth_manager.AuthManager.get_key_pairs(
+ req.environ['nova.context'])
+ if not key_pairs:
+ raise exception.NotFound(_("No keypairs defined"))
+ key_pair = key_pairs[0]
+
image_id = common.get_image_id_from_image_hash(self._image_service,
req.environ['nova.context'], env['server']['imageId'])
- kernel_id, ramdisk_id = self._get_kernel_ramdisk_from_image(image_id)
+ kernel_id, ramdisk_id = self._get_kernel_ramdisk_from_image(
+ req, image_id)
instances = self.compute_api.create(
req.environ['nova.context'],
instance_types.get_by_flavor_id(env['server']['flavorId']),
@@ -156,7 +181,8 @@ class Controller(wsgi.Controller):
display_name=env['server']['name'],
display_description=env['server']['name'],
key_name=key_pair['name'],
- key_data=key_pair['public_key'])
+ key_data=key_pair['public_key'],
+ onset_files=env.get('onset_files', []))
return _translate_keys(instances[0])
def update(self, req, id):
@@ -242,6 +268,20 @@ class Controller(wsgi.Controller):
return faults.Fault(exc.HTTPUnprocessableEntity())
return exc.HTTPAccepted()
+ def reset_network(self, req, id):
+ """
+ Reset networking on an instance (admin only).
+
+ """
+ context = req.environ['nova.context']
+ try:
+ self.compute_api.reset_network(context, id)
+ except:
+ readable = traceback.format_exc()
+ LOG.exception(_("Compute.api::reset_network %s"), readable)
+ return faults.Fault(exc.HTTPUnprocessableEntity())
+ return exc.HTTPAccepted()
+
def pause(self, req, id):
""" Permit Admins to Pause the server. """
ctxt = req.environ['nova.context']
diff --git a/nova/api/openstack/zones.py b/nova/api/openstack/zones.py
new file mode 100644
index 000000000..830464ffd
--- /dev/null
+++ b/nova/api/openstack/zones.py
@@ -0,0 +1,80 @@
+# Copyright 2010 OpenStack LLC.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import common
+import logging
+
+from nova import flags
+from nova import wsgi
+from nova import db
+
+
+FLAGS = flags.FLAGS
+
+
+def _filter_keys(item, keys):
+ """
+ Filters all model attributes except for keys
+ item is a dict
+
+ """
+ return dict((k, v) for k, v in item.iteritems() if k in keys)
+
+
+def _scrub_zone(zone):
+ return _filter_keys(zone, ('id', 'api_url'))
+
+
+class Controller(wsgi.Controller):
+
+ _serialization_metadata = {
+ 'application/xml': {
+ "attributes": {
+ "zone": ["id", "api_url"]}}}
+
+ def index(self, req):
+ """Return all zones in brief"""
+ items = db.zone_get_all(req.environ['nova.context'])
+ items = common.limited(items, req)
+ items = [_scrub_zone(item) for item in items]
+ return dict(zones=items)
+
+ def detail(self, req):
+ """Return all zones in detail"""
+ return self.index(req)
+
+ def show(self, req, id):
+ """Return data about the given zone id"""
+ zone_id = int(id)
+ zone = db.zone_get(req.environ['nova.context'], zone_id)
+ return dict(zone=_scrub_zone(zone))
+
+ def delete(self, req, id):
+ zone_id = int(id)
+ db.zone_delete(req.environ['nova.context'], zone_id)
+ return {}
+
+ def create(self, req):
+ context = req.environ['nova.context']
+ env = self._deserialize(req.body, req)
+ zone = db.zone_create(context, env["zone"])
+ return dict(zone=_scrub_zone(zone))
+
+ def update(self, req, id):
+ context = req.environ['nova.context']
+ env = self._deserialize(req.body, req)
+ zone_id = int(id)
+ zone = db.zone_update(context, zone_id, env["zone"])
+ return dict(zone=_scrub_zone(zone))