summaryrefslogtreecommitdiffstats
path: root/nova/api
diff options
context:
space:
mode:
Diffstat (limited to 'nova/api')
-rw-r--r--nova/api/__init__.py42
-rw-r--r--nova/api/cloud.py20
-rw-r--r--nova/api/ec2/__init__.py9
-rw-r--r--nova/api/ec2/cloud.py99
-rw-r--r--nova/api/ec2/images.py123
-rw-r--r--nova/api/openstack/images.py13
-rw-r--r--nova/api/openstack/servers.py25
7 files changed, 129 insertions, 202 deletions
diff --git a/nova/api/__init__.py b/nova/api/__init__.py
index 8a1d9fe32..80f9f2109 100644
--- a/nova/api/__init__.py
+++ b/nova/api/__init__.py
@@ -15,15 +15,21 @@
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
-
"""
Root WSGI middleware for all API controllers.
+
+**Related Flags**
+
+:osapi_subdomain: subdomain running the OpenStack API (default: api)
+:ec2api_subdomain: subdomain running the EC2 API (default: ec2)
+
"""
import routes
import webob.dec
from nova import flags
+from nova import utils
from nova import wsgi
from nova.api import cloudpipe
from nova.api import ec2
@@ -35,37 +41,31 @@ flags.DEFINE_string('osapi_subdomain', 'api',
'subdomain running the OpenStack API')
flags.DEFINE_string('ec2api_subdomain', 'ec2',
'subdomain running the EC2 API')
-flags.DEFINE_string('FAKE_subdomain', None,
- 'set to api or ec2 to fake the subdomain of the host '
- 'for testing')
FLAGS = flags.FLAGS
class API(wsgi.Router):
"""Routes top-level requests to the appropriate controller."""
- def __init__(self):
- osapidomain = {'sub_domain': [FLAGS.osapi_subdomain]}
- ec2domain = {'sub_domain': [FLAGS.ec2api_subdomain]}
- # If someone wants to pretend they're hitting the OSAPI subdomain
- # on their local box, they can set FAKE_subdomain to 'api', which
- # removes subdomain restrictions from the OpenStack API routes below.
- if FLAGS.FAKE_subdomain == 'api':
- osapidomain = {}
- elif FLAGS.FAKE_subdomain == 'ec2':
- ec2domain = {}
+ def __init__(self, default_api):
+ osapi_subdomain = {'sub_domain': [FLAGS.osapi_subdomain]}
+ ec2api_subdomain = {'sub_domain': [FLAGS.ec2api_subdomain]}
+ if default_api == 'os':
+ osapi_subdomain = {}
+ elif default_api == 'ec2':
+ ec2api_subdomain = {}
mapper = routes.Mapper()
mapper.sub_domains = True
+
mapper.connect("/", controller=self.osapi_versions,
- conditions=osapidomain)
+ conditions=osapi_subdomain)
mapper.connect("/v1.0/{path_info:.*}", controller=openstack.API(),
- conditions=osapidomain)
+ conditions=osapi_subdomain)
mapper.connect("/", controller=self.ec2api_versions,
- conditions=ec2domain)
+ conditions=ec2api_subdomain)
mapper.connect("/services/{path_info:.*}", controller=ec2.API(),
- conditions=ec2domain)
- mapper.connect("/cloudpipe/{path_info:.*}", controller=cloudpipe.API())
+ conditions=ec2api_subdomain)
mrh = metadatarequesthandler.MetadataRequestHandler()
for s in ['/latest',
'/2009-04-04',
@@ -78,7 +78,9 @@ class API(wsgi.Router):
'/2007-01-19',
'/1.0']:
mapper.connect('%s/{path_info:.*}' % s, controller=mrh,
- conditions=ec2domain)
+ conditions=ec2api_subdomain)
+
+ mapper.connect("/cloudpipe/{path_info:.*}", controller=cloudpipe.API())
super(API, self).__init__(mapper)
@webob.dec.wsgify
diff --git a/nova/api/cloud.py b/nova/api/cloud.py
index aa84075dc..b8f15019f 100644
--- a/nova/api/cloud.py
+++ b/nova/api/cloud.py
@@ -36,3 +36,23 @@ def reboot(instance_id, context=None):
db.queue_get_for(context, FLAGS.compute_topic, host),
{"method": "reboot_instance",
"args": {"instance_id": instance_ref['id']}})
+
+
+def rescue(instance_id, context):
+ """Rescue the given instance."""
+ instance_ref = db.instance_get_by_internal_id(context, instance_id)
+ host = instance_ref['host']
+ rpc.cast(context,
+ db.queue_get_for(context, FLAGS.compute_topic, host),
+ {"method": "rescue_instance",
+ "args": {"instance_id": instance_ref['id']}})
+
+
+def unrescue(instance_id, context):
+ """Unrescue the given instance."""
+ instance_ref = db.instance_get_by_internal_id(context, instance_id)
+ host = instance_ref['host']
+ rpc.cast(context,
+ db.queue_get_for(context, FLAGS.compute_topic, host),
+ {"method": "unrescue_instance",
+ "args": {"instance_id": instance_ref['id']}})
diff --git a/nova/api/ec2/__init__.py b/nova/api/ec2/__init__.py
index 0df4d3710..a6ee16c33 100644
--- a/nova/api/ec2/__init__.py
+++ b/nova/api/ec2/__init__.py
@@ -15,8 +15,10 @@
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
+"""
+Starting point for routing EC2 requests.
-"""Starting point for routing EC2 requests"""
+"""
import logging
import routes
@@ -238,11 +240,12 @@ class Executor(wsgi.Application):
return self._error(req, type(ex).__name__, str(ex))
def _error(self, req, code, message):
+ logging.error("%s: %s", code, message)
resp = webob.Response()
resp.status = 400
resp.headers['Content-Type'] = 'text/xml'
- resp.body = ('<?xml version="1.0"?>\n'
+ resp.body = str('<?xml version="1.0"?>\n'
'<Response><Errors><Error><Code>%s</Code>'
'<Message>%s</Message></Error></Errors>'
- '<RequestID>?</RequestID></Response>') % (code, message)
+ '<RequestID>?</RequestID></Response>' % (code, message))
return resp
diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py
index 07229dd73..9327bf0d4 100644
--- a/nova/api/ec2/cloud.py
+++ b/nova/api/ec2/cloud.py
@@ -41,7 +41,7 @@ from nova import rpc
from nova import utils
from nova.compute.instance_types import INSTANCE_TYPES
from nova.api import cloud
-from nova.api.ec2 import images
+from nova.image.s3 import S3ImageService
FLAGS = flags.FLAGS
@@ -99,6 +99,8 @@ class CloudController(object):
"""
def __init__(self):
self.network_manager = utils.import_object(FLAGS.network_manager)
+ self.compute_manager = utils.import_object(FLAGS.compute_manager)
+ self.image_service = S3ImageService()
self.setup()
def __str__(self):
@@ -464,24 +466,31 @@ class CloudController(object):
return {'volumeSet': volumes}
def _format_volume(self, context, volume):
+ instance_ec2_id = None
+ instance_data = None
+ if volume.get('instance', None):
+ internal_id = volume['instance']['internal_id']
+ instance_ec2_id = internal_id_to_ec2_id(internal_id)
+ instance_data = '%s[%s]' % (instance_ec2_id,
+ volume['instance']['host'])
v = {}
v['volumeId'] = volume['ec2_id']
v['status'] = volume['status']
v['size'] = volume['size']
v['availabilityZone'] = volume['availability_zone']
v['createTime'] = volume['created_at']
- if context.user.is_admin():
+ if context.is_admin:
v['status'] = '%s (%s, %s, %s, %s)' % (
volume['status'],
volume['user_id'],
volume['host'],
- volume['instance_id'],
+ instance_data,
volume['mountpoint'])
if volume['attach_status'] == 'attached':
v['attachmentSet'] = [{'attachTime': volume['attach_time'],
'deleteOnTermination': False,
'device': volume['mountpoint'],
- 'instanceId': volume['instance_id'],
+ 'instanceId': instance_ec2_id,
'status': 'attached',
'volume_id': volume['ec2_id']}]
else:
@@ -516,7 +525,10 @@ class CloudController(object):
"args": {"topic": FLAGS.volume_topic,
"volume_id": volume_ref['id']}})
- return {'volumeSet': [self._format_volume(context, volume_ref)]}
+ # TODO(vish): Instance should be None at db layer instead of
+ # trying to lazy load, but for now we turn it into
+ # a dict to avoid an error.
+ return {'volumeSet': [self._format_volume(context, dict(volume_ref))]}
def attach_volume(self, context, volume_id, instance_id, device, **kwargs):
volume_ref = db.volume_get_by_ec2_id(context, volume_id)
@@ -668,7 +680,7 @@ class CloudController(object):
context.project_id)
for floating_ip_ref in iterator:
address = floating_ip_ref['address']
- instance_id = None
+ ec2_id = None
if (floating_ip_ref['fixed_ip']
and floating_ip_ref['fixed_ip']['instance']):
internal_id = floating_ip_ref['fixed_ip']['instance']['ec2_id']
@@ -706,8 +718,8 @@ class CloudController(object):
"args": {"floating_address": floating_ip_ref['address']}})
return {'releaseResponse': ["Address released."]}
- def associate_address(self, context, ec2_id, public_ip, **kwargs):
- internal_id = ec2_id_to_internal_id(ec2_id)
+ def associate_address(self, context, instance_id, public_ip, **kwargs):
+ internal_id = ec2_id_to_internal_id(instance_id)
instance_ref = db.instance_get_by_internal_id(context, internal_id)
fixed_address = db.instance_get_fixed_address(context,
instance_ref['id'])
@@ -774,7 +786,7 @@ class CloudController(object):
vpn = kwargs['image_id'] == FLAGS.vpn_image_id
if not vpn:
- image = images.get(context, kwargs['image_id'])
+ image = self.image_service.show(context, kwargs['image_id'])
# FIXME(ja): if image is vpn, this breaks
# get defaults from imagestore
@@ -787,8 +799,8 @@ class CloudController(object):
ramdisk_id = kwargs.get('ramdisk_id', ramdisk_id)
# make sure we have access to kernel and ramdisk
- images.get(context, kernel_id)
- images.get(context, ramdisk_id)
+ self.image_service.show(context, kernel_id)
+ self.image_service.show(context, ramdisk_id)
logging.debug("Going to run %s instances...", num_instances)
launch_time = time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime())
@@ -835,21 +847,21 @@ class CloudController(object):
elevated = context.elevated()
for num in range(num_instances):
- instance_ref = db.instance_create(context, base_options)
- inst_id = instance_ref['id']
- for security_group_id in security_groups:
- db.instance_add_security_group(elevated,
- inst_id,
- security_group_id)
+ instance_ref = self.compute_manager.create_instance(context,
+ security_groups,
+ mac_address=utils.generate_mac(),
+ launch_index=num,
+ **base_options)
+ inst_id = instance_ref['id']
- inst = {}
- inst['mac_address'] = utils.generate_mac()
- inst['launch_index'] = num
internal_id = instance_ref['internal_id']
ec2_id = internal_id_to_ec2_id(internal_id)
- inst['hostname'] = ec2_id
- db.instance_update(context, inst_id, inst)
+
+ self.compute_manager.update_instance(context,
+ inst_id,
+ hostname=ec2_id)
+
# TODO(vish): This probably should be done in the scheduler
# or in compute as a call. The network should be
# allocated after the host is assigned and setup
@@ -895,11 +907,12 @@ class CloudController(object):
id_str)
continue
now = datetime.datetime.utcnow()
- db.instance_update(context,
- instance_ref['id'],
- {'state_description': 'terminating',
- 'state': 0,
- 'terminated_at': now})
+ self.compute_manager.update_instance(context,
+ instance_ref['id'],
+ state_description='terminating',
+ state=0,
+ terminated_at=now)
+
# FIXME(ja): where should network deallocate occur?
address = db.instance_get_floating_address(context,
instance_ref['id'])
@@ -936,8 +949,21 @@ 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:
- cloud.reboot(id_str, context=context)
+ for ec2_id in instance_id:
+ internal_id = ec2_id_to_internal_id(ec2_id)
+ cloud.reboot(internal_id, context=context)
+ return True
+
+ def rescue_instance(self, context, instance_id, **kwargs):
+ """This is an extension to the normal ec2_api"""
+ internal_id = ec2_id_to_internal_id(instance_id)
+ cloud.rescue(internal_id, context=context)
+ return True
+
+ def unrescue_instance(self, context, instance_id, **kwargs):
+ """This is an extension to the normal ec2_api"""
+ internal_id = ec2_id_to_internal_id(instance_id)
+ cloud.unrescue(internal_id, context=context)
return True
def update_instance(self, context, ec2_id, **kwargs):
@@ -968,20 +994,17 @@ class CloudController(object):
return True
def describe_images(self, context, image_id=None, **kwargs):
- # The objectstore does its own authorization for describe
- imageSet = images.list(context, image_id)
+ imageSet = self.image_service.index(context, image_id)
return {'imagesSet': imageSet}
def deregister_image(self, context, image_id, **kwargs):
- # FIXME: should the objectstore be doing these authorization checks?
- images.deregister(context, image_id)
+ self.image_service.deregister(context, image_id)
return {'imageId': image_id}
def register_image(self, context, image_location=None, **kwargs):
- # FIXME: should the objectstore be doing these authorization checks?
if image_location is None and 'name' in kwargs:
image_location = kwargs['name']
- image_id = images.register(context, image_location)
+ image_id = self.image_service.register(context, image_location)
logging.debug("Registered %s as %s" % (image_location, image_id))
return {'imageId': image_id}
@@ -989,7 +1012,7 @@ class CloudController(object):
if attribute != 'launchPermission':
raise exception.ApiError('attribute not supported: %s' % attribute)
try:
- image = images.list(context, image_id)[0]
+ image = self.image_service.show(context, image_id)
except IndexError:
raise exception.ApiError('invalid id: %s' % image_id)
result = {'image_id': image_id, 'launchPermission': []}
@@ -1008,8 +1031,8 @@ class CloudController(object):
raise exception.ApiError('only group "all" is supported')
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)
+ return self.image_service.modify(context, image_id, operation_type)
def update_image(self, context, image_id, **kwargs):
- result = images.update(context, image_id, dict(kwargs))
+ result = self.image_service.update(context, image_id, dict(kwargs))
return result
diff --git a/nova/api/ec2/images.py b/nova/api/ec2/images.py
deleted file mode 100644
index 60f9008e9..000000000
--- a/nova/api/ec2/images.py
+++ /dev/null
@@ -1,123 +0,0 @@
-# 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.
-
-"""
-Proxy AMI-related calls from the cloud controller, to the running
-objectstore service.
-"""
-
-import json
-import urllib
-
-import boto.s3.connection
-
-from nova import exception
-from nova import flags
-from nova import utils
-from nova.auth import manager
-
-
-FLAGS = flags.FLAGS
-
-
-def modify(context, image_id, operation):
- conn(context).make_request(
- method='POST',
- bucket='_images',
- query_args=qs({'image_id': image_id, 'operation': 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 """
-
- image_id = utils.generate_uid('ami')
- conn(context).make_request(
- method='PUT',
- bucket='_images',
- query_args=qs({'image_location': image_location,
- 'image_id': image_id}))
-
- return image_id
-
-
-def list(context, filter_list=[]):
- """ return a list of all images that a user can see
-
- optionally filtered by a list of image_id """
-
- if FLAGS.connection_type == 'fake':
- return [{'imageId': 'bar'}]
-
- # FIXME: send along the list of only_images to check for
- response = conn(context).make_request(
- method='GET',
- bucket='_images')
-
- result = json.loads(response.read())
- if not filter_list is None:
- return [i for i in result if i['imageId'] in filter_list]
- return result
-
-
-def get(context, image_id):
- """return a image object if the context has permissions"""
- result = list(context, [image_id])
- if not result:
- raise exception.NotFound('Image %s could not be found' % image_id)
- image = result[0]
- return image
-
-
-def deregister(context, image_id):
- """ unregister an image """
- conn(context).make_request(
- method='DELETE',
- bucket='_images',
- query_args=qs({'image_id': image_id}))
-
-
-def conn(context):
- access = manager.AuthManager().get_access_key(context.user,
- context.project)
- secret = str(context.user.secret)
- calling = boto.s3.connection.OrdinaryCallingFormat()
- return boto.s3.connection.S3Connection(aws_access_key_id=access,
- aws_secret_access_key=secret,
- is_secure=False,
- calling_format=calling,
- port=FLAGS.s3_port,
- host=FLAGS.s3_host)
-
-
-def qs(params):
- pairs = []
- for key in params.keys():
- pairs.append(key + '=' + urllib.quote(params[key]))
- return '&'.join(pairs)
diff --git a/nova/api/openstack/images.py b/nova/api/openstack/images.py
index 5bc915e63..cdbdc9bdd 100644
--- a/nova/api/openstack/images.py
+++ b/nova/api/openstack/images.py
@@ -17,6 +17,7 @@
from webob import exc
+from nova import context
from nova import flags
from nova import utils
from nova import wsgi
@@ -46,19 +47,23 @@ class Controller(wsgi.Controller):
def detail(self, req):
"""Return all public images in detail."""
+ user_id = req.environ['nova.context']['user']['id']
+ ctxt = context.RequestContext(user_id, user_id)
try:
- images = self._service.detail()
+ images = self._service.detail(ctxt)
images = nova.api.openstack.limited(images, req)
except NotImplementedError:
# Emulate detail() using repeated calls to show()
- images = self._service.index()
+ images = self._service.index(ctxt)
images = nova.api.openstack.limited(images, req)
- images = [self._service.show(i['id']) for i in images]
+ images = [self._service.show(ctxt, i['id']) for i in images]
return dict(images=images)
def show(self, req, id):
"""Return data about the given image id."""
- return dict(image=self._service.show(id))
+ user_id = req.environ['nova.context']['user']['id']
+ ctxt = context.RequestContext(user_id, user_id)
+ return dict(image=self._service.show(ctxt, id))
def delete(self, req, id):
# Only public images are supported for now.
diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py
index ef773c3be..1d8aa2fa4 100644
--- a/nova/api/openstack/servers.py
+++ b/nova/api/openstack/servers.py
@@ -95,6 +95,7 @@ class Controller(wsgi.Controller):
db_driver = FLAGS.db_driver
self.db_driver = utils.import_object(db_driver)
self.network_manager = utils.import_object(FLAGS.network_manager)
+ self.compute_manager = utils.import_object(FLAGS.compute_manager)
super(Controller, self).__init__()
def index(self, req):
@@ -242,34 +243,30 @@ class Controller(wsgi.Controller):
inst['memory_mb'] = flavor['memory_mb']
inst['vcpus'] = flavor['vcpus']
inst['local_gb'] = flavor['local_gb']
-
- ref = self.db_driver.instance_create(ctxt, inst)
- inst['id'] = ref.internal_id
-
inst['mac_address'] = utils.generate_mac()
-
- #TODO(dietz) is this necessary?
inst['launch_index'] = 0
- inst['hostname'] = str(ref.internal_id)
- self.db_driver.instance_update(ctxt, inst['id'], inst)
+ ref = self.compute_manager.create_instance(ctxt, **inst)
+ inst['id'] = ref['internal_id']
+
+ inst['hostname'] = str(ref['internal_id'])
+ self.compute_manager.update_instance(ctxt, inst['id'], **inst)
- network_manager = utils.import_object(FLAGS.network_manager)
- address = network_manager.allocate_fixed_ip(ctxt,
- inst['id'])
+ address = self.network_manager.allocate_fixed_ip(ctxt,
+ inst['id'])
# TODO(vish): This probably should be done in the scheduler
# network is setup when host is assigned
- network_topic = self._get_network_topic(ctxt, network_manager)
+ network_topic = self._get_network_topic(ctxt)
rpc.call(ctxt,
network_topic,
{"method": "setup_fixed_ip",
"args": {"address": address}})
return inst
- def _get_network_topic(self, context, network_manager):
+ def _get_network_topic(self, context):
"""Retrieves the network host for a project"""
- network_ref = network_manager.get_network(context)
+ network_ref = self.network_manager.get_network(context)
host = network_ref['host']
if not host:
host = rpc.call(context,