summaryrefslogtreecommitdiffstats
path: root/nova/api
diff options
context:
space:
mode:
Diffstat (limited to 'nova/api')
-rw-r--r--nova/api/ec2/__init__.py18
-rw-r--r--nova/api/ec2/admin.py111
-rw-r--r--nova/api/ec2/apirequest.py9
-rw-r--r--nova/api/ec2/cloud.py43
-rw-r--r--nova/api/openstack/__init__.py1
-rw-r--r--nova/api/openstack/auth.py11
-rw-r--r--nova/api/openstack/backup_schedules.py1
-rw-r--r--nova/api/openstack/images.py2
-rw-r--r--nova/api/openstack/servers.py122
-rw-r--r--nova/api/openstack/shared_ip_groups.py2
-rw-r--r--nova/api/openstack/zones.py1
11 files changed, 232 insertions, 89 deletions
diff --git a/nova/api/ec2/__init__.py b/nova/api/ec2/__init__.py
index 1a06b3f01..5adc2c075 100644
--- a/nova/api/ec2/__init__.py
+++ b/nova/api/ec2/__init__.py
@@ -20,7 +20,6 @@ Starting point for routing EC2 requests.
"""
-import datetime
import webob
import webob.dec
import webob.exc
@@ -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,
@@ -294,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)
diff --git a/nova/api/ec2/admin.py b/nova/api/ec2/admin.py
index 735951082..e2a05fce1 100644
--- a/nova/api/ec2/admin.py
+++ b/nova/api/ec2/admin.py
@@ -21,14 +21,18 @@ Admin API controller, exposed through http via the api worker.
"""
import base64
+import datetime
from nova import db
from nova import exception
+from nova import flags
from nova import log as logging
+from nova import utils
from nova.auth import manager
from nova.compute import instance_types
+FLAGS = flags.FLAGS
LOG = logging.getLogger('nova.api.ec2.admin')
@@ -55,12 +59,25 @@ def project_dict(project):
return {}
-def host_dict(host):
+def host_dict(host, compute_service, instances, volume_service, volumes, now):
"""Convert a host model object to a result dict"""
- if host:
- return host.state
- else:
- return {}
+ rv = {'hostanme': host, 'instance_count': len(instances),
+ 'volume_count': len(volumes)}
+ if compute_service:
+ latest = compute_service['updated_at'] or compute_service['created_at']
+ delta = now - latest
+ if delta.seconds <= FLAGS.service_down_time:
+ rv['compute'] = 'up'
+ else:
+ rv['compute'] = 'down'
+ if volume_service:
+ latest = volume_service['updated_at'] or volume_service['created_at']
+ delta = now - latest
+ if delta.seconds <= FLAGS.service_down_time:
+ rv['volume'] = 'up'
+ else:
+ rv['volume'] = 'down'
+ return rv
def instance_dict(name, inst):
@@ -71,6 +88,25 @@ def instance_dict(name, inst):
'flavor_id': inst['flavorid']}
+def vpn_dict(project, vpn_instance):
+ rv = {'project_id': project.id,
+ 'public_ip': project.vpn_ip,
+ 'public_port': project.vpn_port}
+ if vpn_instance:
+ rv['instance_id'] = vpn_instance['ec2_id']
+ rv['created_at'] = utils.isotime(vpn_instance['created_at'])
+ address = vpn_instance.get('fixed_ip', None)
+ if address:
+ rv['internal_ip'] = address['address']
+ if utils.vpn_ping(project.vpn_ip, project.vpn_port):
+ rv['state'] = 'running'
+ else:
+ rv['state'] = 'down'
+ else:
+ rv['state'] = 'pending'
+ return rv
+
+
class AdminController(object):
"""
API Controller for users, hosts, nodes, and workers.
@@ -223,19 +259,68 @@ class AdminController(object):
raise exception.ApiError(_('operation must be add or remove'))
return True
+ def _vpn_for(self, context, project_id):
+ """Get the VPN instance for a project ID."""
+ for instance in db.instance_get_all_by_project(context, project_id):
+ if (instance['image_id'] == FLAGS.vpn_image_id
+ and not instance['state_description'] in
+ ['shutting_down', 'shutdown']):
+ return instance
+
+ def start_vpn(self, context, project):
+ instance = self._vpn_for(context, project)
+ if not instance:
+ # NOTE(vish) import delayed because of __init__.py
+ from nova.cloudpipe import pipelib
+ pipe = pipelib.CloudPipe()
+ try:
+ pipe.launch_vpn_instance(project)
+ except db.NoMoreNetworks:
+ raise exception.ApiError("Unable to claim IP for VPN instance"
+ ", ensure it isn't running, and try "
+ "again in a few minutes")
+ instance = self._vpn_for(context, project)
+ return {'instance_id': instance['ec2_id']}
+
+ def describe_vpns(self, context):
+ vpns = []
+ for project in manager.AuthManager().get_projects():
+ instance = self._vpn_for(context, project.id)
+ vpns.append(vpn_dict(project, instance))
+ return {'items': vpns}
+
# FIXME(vish): these host commands don't work yet, perhaps some of the
# required data can be retrieved from service objects?
- def describe_hosts(self, _context, **_kwargs):
+ def describe_hosts(self, context, **_kwargs):
"""Returns status info for all nodes. Includes:
- * Disk Space
- * Instance List
- * RAM used
- * CPU used
- * DHCP servers running
- * Iptables / bridges
+ * Hostname
+ * Compute (up, down, None)
+ * Instance count
+ * Volume (up, down, None)
+ * Volume Count
"""
- return {'hostSet': [host_dict(h) for h in db.host_get_all()]}
+ services = db.service_get_all(context)
+ now = datetime.datetime.utcnow()
+ hosts = []
+ rv = []
+ for host in [service['host'] for service in services]:
+ if not host in hosts:
+ hosts.append(host)
+ for host in hosts:
+ compute = [s for s in services if s['host'] == host \
+ and s['binary'] == 'nova-compute']
+ if compute:
+ compute = compute[0]
+ instances = db.instance_get_all_by_host(context, host)
+ volume = [s for s in services if s['host'] == host \
+ and s['binary'] == 'nova-volume']
+ if volume:
+ volume = volume[0]
+ volumes = db.volume_get_all_by_host(context, host)
+ rv.append(host_dict(host, compute, instances, volume, volumes,
+ now))
+ return {'hosts': rv}
def describe_host(self, _context, name, **_kwargs):
"""Returns status info for single node."""
diff --git a/nova/api/ec2/apirequest.py b/nova/api/ec2/apirequest.py
index 7e72d67fb..2b1acba5a 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
@@ -45,6 +46,11 @@ def _underscore_to_xmlcase(str):
return res[:1].lower() + res[1:]
+def _database_to_isoformat(datetimeobj):
+ """Return a xs:dateTime parsable string from datatime"""
+ return datetimeobj.strftime("%Y-%m-%dT%H:%M:%SZ")
+
+
def _try_convert(value):
"""Return a non-string if possible"""
if value == 'None':
@@ -171,6 +177,9 @@ 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(_database_to_isoformat(data)))
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 6919cd8d2..844ccbe5e 100644
--- a/nova/api/ec2/cloud.py
+++ b/nova/api/ec2/cloud.py
@@ -198,8 +198,9 @@ class CloudController(object):
return self._describe_availability_zones(context, **kwargs)
def _describe_availability_zones(self, context, **kwargs):
- enabled_services = db.service_get_all(context)
- disabled_services = db.service_get_all(context, True)
+ ctxt = context.elevated()
+ enabled_services = db.service_get_all(ctxt)
+ disabled_services = db.service_get_all(ctxt, True)
available_zones = []
for zone in [service.availability_zone for service
in enabled_services]:
@@ -282,7 +283,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 +291,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 +302,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 +311,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,14 +319,19 @@ 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 group_name:
+ groups = []
+ for name in group_name:
+ group = db.security_group_get_by_name(context,
+ context.project_id,
+ name)
+ groups.append(group)
+ elif context.is_admin:
groups = db.security_group_get_all(context)
else:
groups = db.security_group_get_by_project(context,
context.project_id)
groups = [self._format_security_group(context, g) for g in groups]
- if not group_name is None:
- groups = [g for g in groups if g.name in group_name]
return {'securityGroupInfo':
list(sorted(groups,
@@ -494,7 +500,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}
@@ -529,8 +535,9 @@ class CloudController(object):
def get_ajax_console(self, context, instance_id, **kwargs):
ec2_id = instance_id[0]
- internal_id = ec2_id_to_id(ec2_id)
- return self.compute_api.get_ajax_console(context, internal_id)
+ instance_id = ec2_id_to_id(ec2_id)
+ return self.compute_api.get_ajax_console(context,
+ instance_id=instance_id)
def describe_volumes(self, context, volume_id=None, **kwargs):
if volume_id:
@@ -669,12 +676,13 @@ class CloudController(object):
instances = []
for ec2_id in instance_id:
internal_id = ec2_id_to_id(ec2_id)
- instance = self.compute_api.get(context, internal_id)
+ instance = self.compute_api.get(context,
+ instance_id=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 = {}
@@ -702,7 +710,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'])
@@ -736,7 +744,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,
@@ -750,7 +758,7 @@ class CloudController(object):
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
@@ -882,7 +890,6 @@ class CloudController(object):
raise exception.ApiError(_('attribute not supported: %s')
% attribute)
try:
- image = self.image_service.show(context, image_id)
image = self._format_image(context,
self.image_service.show(context,
image_id))
diff --git a/nova/api/openstack/__init__.py b/nova/api/openstack/__init__.py
index b38f9d9a2..39ead9e6b 100644
--- a/nova/api/openstack/__init__.py
+++ b/nova/api/openstack/__init__.py
@@ -81,6 +81,7 @@ class APIRouter(wsgi.Router):
server_members['suspend'] = 'POST'
server_members['resume'] = 'POST'
server_members['reset_network'] = 'POST'
+ server_members['inject_network_info'] = 'POST'
mapper.resource("zone", "zones", controller=zones.Controller(),
collection={'detail': 'GET', 'info': 'GET'}),
diff --git a/nova/api/openstack/auth.py b/nova/api/openstack/auth.py
index 473071738..6011e6115 100644
--- a/nova/api/openstack/auth.py
+++ b/nova/api/openstack/auth.py
@@ -19,7 +19,6 @@ import datetime
import hashlib
import json
import time
-import logging
import webob.exc
import webob.dec
@@ -27,6 +26,7 @@ import webob.dec
from nova import auth
from nova import context
from nova import db
+from nova import exception
from nova import flags
from nova import manager
from nova import utils
@@ -104,11 +104,14 @@ class AuthMiddleware(wsgi.Middleware):
2 days ago.
"""
ctxt = context.get_admin_context()
- token = self.db.auth_get_token(ctxt, token_hash)
+ try:
+ token = self.db.auth_token_get(ctxt, token_hash)
+ except exception.NotFound:
+ return None
if token:
delta = datetime.datetime.now() - token.created_at
if delta.days >= 2:
- self.db.auth_destroy_token(ctxt, token)
+ self.db.auth_token_destroy(ctxt, token.token_hash)
else:
return self.auth.get_user(token.user_id)
return None
@@ -132,6 +135,6 @@ class AuthMiddleware(wsgi.Middleware):
token_dict['server_management_url'] = req.url
token_dict['storage_url'] = ''
token_dict['user_id'] = user.id
- token = self.db.auth_create_token(ctxt, token_dict)
+ token = self.db.auth_token_create(ctxt, token_dict)
return token, user
return None, None
diff --git a/nova/api/openstack/backup_schedules.py b/nova/api/openstack/backup_schedules.py
index 197125d86..7abb5f884 100644
--- a/nova/api/openstack/backup_schedules.py
+++ b/nova/api/openstack/backup_schedules.py
@@ -15,7 +15,6 @@
# License for the specific language governing permissions and limitations
# under the License.
-import logging
import time
from webob import exc
diff --git a/nova/api/openstack/images.py b/nova/api/openstack/images.py
index 9d56bc508..cf85a496f 100644
--- a/nova/api/openstack/images.py
+++ b/nova/api/openstack/images.py
@@ -15,8 +15,6 @@
# License for the specific language governing permissions and limitations
# under the License.
-import logging
-
from webob import exc
from nova import compute
diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py
index 009ef6db1..69273ad7b 100644
--- a/nova/api/openstack/servers.py
+++ b/nova/api/openstack/servers.py
@@ -13,6 +13,7 @@
# License for the specific language governing permissions and limitations
# under the License.
+import hashlib
import json
import traceback
@@ -33,7 +34,6 @@ import nova.api.openstack
LOG = logging.getLogger('server')
-LOG.setLevel(logging.DEBUG)
FLAGS = flags.FLAGS
@@ -51,7 +51,8 @@ def _translate_detail_keys(inst):
power_state.PAUSED: 'paused',
power_state.SHUTDOWN: 'active',
power_state.SHUTOFF: 'active',
- power_state.CRASHED: 'error'}
+ power_state.CRASHED: 'error',
+ power_state.FAILED: 'error'}
inst_dict = {}
mapped_keys = dict(status='state', imageId='image_id',
@@ -64,22 +65,22 @@ def _translate_detail_keys(inst):
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"))
+ private_ips = utils.get_from_path(inst, 'fixed_ip/address')
+ inst_dict['addresses']['private'] = private_ips
# 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)"))
+ public_ips = utils.get_from_path(inst, 'fixed_ip/floating_ips/address')
+ inst_dict['addresses']['public'] = public_ips
+
+ # Return the metadata as a dictionary
+ metadata = {}
+ for item in inst['metadata']:
+ metadata[item['key']] = item['value']
+ inst_dict['metadata'] = metadata
- inst_dict['metadata'] = {}
inst_dict['hostId'] = ''
+ if inst['host']:
+ inst_dict['hostId'] = hashlib.sha224(inst['host']).hexdigest()
return dict(server=inst_dict)
@@ -138,38 +139,35 @@ class Controller(wsgi.Controller):
return faults.Fault(exc.HTTPNotFound())
return exc.HTTPAccepted()
- 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())
-
- 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 """
env = self._deserialize(req.body, req)
if not env:
return faults.Fault(exc.HTTPUnprocessableEntity())
- key_pair = auth_manager.AuthManager.get_key_pairs(
- req.environ['nova.context'])[0]
+ context = req.environ['nova.context']
+ key_pairs = auth_manager.AuthManager.get_key_pairs(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'])
+ context, env['server']['imageId'])
kernel_id, ramdisk_id = self._get_kernel_ramdisk_from_image(
req, image_id)
+
+ # Metadata is a list, not a Dictionary, because we allow duplicate keys
+ # (even though JSON can't encode this)
+ # In future, we may not allow duplicate keys.
+ # However, the CloudServers API is not definitive on this front,
+ # and we want to be compatible.
+ metadata = []
+ if env['server'].get('metadata'):
+ for k, v in env['server']['metadata'].items():
+ metadata.append({'key': k, 'value': v})
+
instances = self.compute_api.create(
- req.environ['nova.context'],
+ context,
instance_types.get_by_flavor_id(env['server']['flavorId']),
image_id,
kernel_id=kernel_id,
@@ -177,7 +175,9 @@ 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'],
+ metadata=metadata,
+ onset_files=env.get('onset_files', []))
return _translate_keys(instances[0])
def update(self, req, id):
@@ -277,6 +277,20 @@ class Controller(wsgi.Controller):
return faults.Fault(exc.HTTPUnprocessableEntity())
return exc.HTTPAccepted()
+ def inject_network_info(self, req, id):
+ """
+ Inject network info for an instance (admin only).
+
+ """
+ context = req.environ['nova.context']
+ try:
+ self.compute_api.inject_network_info(context, id)
+ except:
+ readable = traceback.format_exc()
+ LOG.exception(_("Compute.api::inject_network_info %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']
@@ -348,3 +362,37 @@ class Controller(wsgi.Controller):
action=item.action,
error=item.error))
return dict(actions=actions)
+
+ def _get_kernel_ramdisk_from_image(self, req, image_id):
+ """Retrevies kernel and ramdisk IDs from Glance
+
+ Only 'machine' (ami) type use kernel and ramdisk outside of the
+ image.
+ """
+ # FIXME(sirp): Since we're retrieving the kernel_id from an
+ # image_property, this means only Glance is supported.
+ # The BaseImageService needs to expose a consistent way of accessing
+ # kernel_id and ramdisk_id
+ image = self._image_service.show(req.environ['nova.context'], image_id)
+
+ if image['status'] != 'active':
+ raise exception.Invalid(
+ _("Cannot build from image %(image_id)s, status not active") %
+ locals())
+
+ if image['type'] != 'machine':
+ return None, None
+
+ try:
+ kernel_id = image['properties']['kernel_id']
+ except KeyError:
+ raise exception.NotFound(
+ _("Kernel not found for image %(image_id)s") % locals())
+
+ try:
+ ramdisk_id = image['properties']['ramdisk_id']
+ except KeyError:
+ raise exception.NotFound(
+ _("Ramdisk not found for image %(image_id)s") % locals())
+
+ return kernel_id, ramdisk_id
diff --git a/nova/api/openstack/shared_ip_groups.py b/nova/api/openstack/shared_ip_groups.py
index bd3cc23a8..5d78f9377 100644
--- a/nova/api/openstack/shared_ip_groups.py
+++ b/nova/api/openstack/shared_ip_groups.py
@@ -15,8 +15,6 @@
# License for the specific language governing permissions and limitations
# under the License.
-import logging
-
from webob import exc
from nova import wsgi
diff --git a/nova/api/openstack/zones.py b/nova/api/openstack/zones.py
index 99be0ba02..989b3a235 100644
--- a/nova/api/openstack/zones.py
+++ b/nova/api/openstack/zones.py
@@ -14,7 +14,6 @@
# under the License.
import common
-import logging
from nova import flags
from nova import wsgi