summaryrefslogtreecommitdiffstats
path: root/nova/api
diff options
context:
space:
mode:
authorTodd Willey <todd@ansolabs.com>2011-06-11 14:46:08 -0400
committerTodd Willey <todd@ansolabs.com>2011-06-11 14:46:08 -0400
commit85bfd9592f8a49d2a730e64f9bf58e395d8965c7 (patch)
tree471c3fb453abd68b025d69df39bf7f635fdb2add /nova/api
parent0bcb15317fede5c17c77c187e1cd9a68a0c8030c (diff)
parent91e34d37d2907295e892e96ca2c3039c7fbe14bf (diff)
Merge and resolve.
Diffstat (limited to 'nova/api')
-rw-r--r--nova/api/direct.py2
-rw-r--r--nova/api/ec2/__init__.py1
-rw-r--r--nova/api/ec2/admin.py6
-rw-r--r--nova/api/ec2/cloud.py55
-rw-r--r--nova/api/openstack/__init__.py2
-rw-r--r--nova/api/openstack/auth.py22
-rw-r--r--nova/api/openstack/common.py74
-rw-r--r--nova/api/openstack/contrib/__init__.py2
-rw-r--r--nova/api/openstack/extensions.py4
-rw-r--r--nova/api/openstack/image_metadata.py3
-rw-r--r--nova/api/openstack/images.py113
-rw-r--r--nova/api/openstack/limits.py2
-rw-r--r--nova/api/openstack/ratelimiting/__init__.py2
-rw-r--r--nova/api/openstack/servers.py61
-rw-r--r--nova/api/openstack/versions.py2
-rw-r--r--nova/api/openstack/views/limits.py9
-rw-r--r--nova/api/openstack/views/servers.py16
-rw-r--r--nova/api/openstack/wsgi.py2
-rw-r--r--nova/api/openstack/zones.py28
19 files changed, 231 insertions, 175 deletions
diff --git a/nova/api/direct.py b/nova/api/direct.py
index ea20042a7..ec79151b1 100644
--- a/nova/api/direct.py
+++ b/nova/api/direct.py
@@ -324,7 +324,7 @@ class Limited(object):
def __init__(self, proxy):
self._proxy = proxy
- if not self.__doc__:
+ if not self.__doc__: # pylint: disable=E0203
self.__doc__ = proxy.__doc__
if not self._allowed:
self._allowed = []
diff --git a/nova/api/ec2/__init__.py b/nova/api/ec2/__init__.py
index 1915d007d..890d57fe7 100644
--- a/nova/api/ec2/__init__.py
+++ b/nova/api/ec2/__init__.py
@@ -242,6 +242,7 @@ class Authorizer(wsgi.Middleware):
'CreateKeyPair': ['all'],
'DeleteKeyPair': ['all'],
'DescribeSecurityGroups': ['all'],
+ 'ImportPublicKey': ['all'],
'AuthorizeSecurityGroupIngress': ['netadmin'],
'RevokeSecurityGroupIngress': ['netadmin'],
'CreateSecurityGroup': ['netadmin'],
diff --git a/nova/api/ec2/admin.py b/nova/api/ec2/admin.py
index 1d4cffff3..fcf7f674c 100644
--- a/nova/api/ec2/admin.py
+++ b/nova/api/ec2/admin.py
@@ -311,7 +311,7 @@ class AdminController(object):
* Volume Count
"""
services = db.service_get_all(context, False)
- now = datetime.datetime.utcnow()
+ now = utils.utcnow()
hosts = []
rv = []
for host in [service['host'] for service in services]:
@@ -332,10 +332,6 @@ class AdminController(object):
now))
return {'hosts': rv}
- def describe_host(self, _context, name, **_kwargs):
- """Returns status info for single node."""
- return host_dict(db.host_get(name))
-
def _provider_fw_rule_exists(self, context, rule):
# TODO(todd): we call this repeatedly, can we filter by protocol?
for old_rule in db.provider_fw_rule_get_all(context):
diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py
index 79cc3b3bf..e1c65ae40 100644
--- a/nova/api/ec2/cloud.py
+++ b/nova/api/ec2/cloud.py
@@ -23,7 +23,6 @@ datastore.
"""
import base64
-import datetime
import IPy
import os
import urllib
@@ -40,6 +39,7 @@ from nova import flags
from nova import ipv6
from nova import log as logging
from nova import network
+from nova import rpc
from nova import utils
from nova import volume
from nova.api.ec2 import ec2utils
@@ -137,6 +137,13 @@ class CloudController(object):
return services[0]['availability_zone']
return 'unknown zone'
+ def _get_image_state(self, image):
+ # NOTE(vish): fallback status if image_state isn't set
+ state = image.get('status')
+ if state == 'active':
+ state = 'available'
+ return image['properties'].get('image_state', state)
+
def get_metadata(self, address):
ctxt = context.get_admin_context()
instance_ref = self.compute_api.get_all(ctxt, fixed_ip=address)
@@ -159,7 +166,7 @@ class CloudController(object):
floating_ip = db.instance_get_floating_address(ctxt,
instance_ref['id'])
ec2_id = ec2utils.id_to_ec2_id(instance_ref['id'])
- image_ec2_id = self.image_ec2_id(instance_ref['image_id'])
+ image_ec2_id = self.image_ec2_id(instance_ref['image_ref'])
data = {
'user-data': base64.b64decode(instance_ref['user_data']),
'meta-data': {
@@ -235,7 +242,7 @@ class CloudController(object):
'zoneState': 'available'}]}
services = db.service_get_all(context, False)
- now = datetime.datetime.utcnow()
+ now = utils.utcnow()
hosts = []
for host in [service['host'] for service in services]:
if not host in hosts:
@@ -595,7 +602,7 @@ class CloudController(object):
instance_id = ec2utils.ec2_id_to_id(ec2_id)
output = self.compute_api.get_console_output(
context, instance_id=instance_id)
- now = datetime.datetime.utcnow()
+ now = utils.utcnow()
return {"InstanceId": ec2_id,
"Timestamp": now,
"output": base64.b64encode(output)}
@@ -774,13 +781,13 @@ class CloudController(object):
instances = self.compute_api.get_all(context, **kwargs)
for instance in instances:
if not context.is_admin:
- if instance['image_id'] == str(FLAGS.vpn_image_id):
+ if instance['image_ref'] == str(FLAGS.vpn_image_id):
continue
i = {}
instance_id = instance['id']
ec2_id = ec2utils.id_to_ec2_id(instance_id)
i['instanceId'] = ec2_id
- i['imageId'] = self.image_ec2_id(instance['image_id'])
+ i['imageId'] = self.image_ec2_id(instance['image_ref'])
i['instanceState'] = {
'code': instance['state'],
'name': instance['state_description']}
@@ -866,8 +873,14 @@ class CloudController(object):
def allocate_address(self, context, **kwargs):
LOG.audit(_("Allocate address"), context=context)
- public_ip = self.network_api.allocate_floating_ip(context)
- return {'publicIp': public_ip}
+ try:
+ public_ip = self.network_api.allocate_floating_ip(context)
+ return {'publicIp': public_ip}
+ except rpc.RemoteError as ex:
+ if ex.exc_type == 'NoMoreAddresses':
+ raise exception.NoMoreFloatingIps()
+ else:
+ raise
def release_address(self, context, public_ip, **kwargs):
LOG.audit(_("Release address %s"), public_ip, context=context)
@@ -896,10 +909,20 @@ class CloudController(object):
if kwargs.get('ramdisk_id'):
ramdisk = self._get_image(context, kwargs['ramdisk_id'])
kwargs['ramdisk_id'] = ramdisk['id']
+ image = self._get_image(context, kwargs['image_id'])
+
+ if image:
+ image_state = self._get_image_state(image)
+ else:
+ raise exception.ImageNotFound(image_id=kwargs['image_id'])
+
+ if image_state != 'available':
+ raise exception.ApiError(_('Image must be available'))
+
instances = self.compute_api.create(context,
instance_type=instance_types.get_instance_type_by_name(
kwargs.get('instance_type', None)),
- image_id=self._get_image(context, kwargs['image_id'])['id'],
+ image_href=self._get_image(context, kwargs['image_id'])['id'],
min_count=int(kwargs.get('min_count', max_count)),
max_count=max_count,
kernel_id=kwargs.get('kernel_id'),
@@ -975,7 +998,12 @@ class CloudController(object):
def image_ec2_id(image_id, image_type='ami'):
"""Returns image ec2_id using id and three letter type."""
template = image_type + '-%08x'
- return ec2utils.id_to_ec2_id(int(image_id), template=template)
+ try:
+ return ec2utils.id_to_ec2_id(int(image_id), template=template)
+ except ValueError:
+ #TODO(wwolf): once we have ec2_id -> glance_id mapping
+ # in place, this wont be necessary
+ return "ami-00000000"
def _get_image(self, context, ec2_id):
try:
@@ -1006,11 +1034,8 @@ class CloudController(object):
get('image_location'), name)
else:
i['imageLocation'] = image['properties'].get('image_location')
- # NOTE(vish): fallback status if image_state isn't set
- state = image.get('status')
- if state == 'active':
- state = 'available'
- i['imageState'] = image['properties'].get('image_state', state)
+
+ i['imageState'] = self._get_image_state(image)
i['displayName'] = name
i['description'] = image.get('description')
display_mapping = {'aki': 'kernel',
diff --git a/nova/api/openstack/__init__.py b/nova/api/openstack/__init__.py
index d8fb5265b..c116e4220 100644
--- a/nova/api/openstack/__init__.py
+++ b/nova/api/openstack/__init__.py
@@ -101,7 +101,7 @@ class APIRouter(base_wsgi.Router):
mapper.resource("zone", "zones",
controller=zones.create_resource(),
collection={'detail': 'GET', 'info': 'GET',
- 'select': 'GET'})
+ 'select': 'POST'})
mapper.resource("user", "users",
controller=users.create_resource(),
diff --git a/nova/api/openstack/auth.py b/nova/api/openstack/auth.py
index 6c6ee22a2..7c3e683d6 100644
--- a/nova/api/openstack/auth.py
+++ b/nova/api/openstack/auth.py
@@ -13,9 +13,8 @@
# 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 datetime
+# under the License.
-import datetime
import hashlib
import time
@@ -50,19 +49,22 @@ class AuthMiddleware(wsgi.Middleware):
if not self.has_authentication(req):
return self.authenticate(req)
user = self.get_user_by_authentication(req)
- accounts = self.auth.get_projects(user=user)
if not user:
token = req.headers["X-Auth-Token"]
msg = _("%(user)s could not be found with token '%(token)s'")
LOG.warn(msg % locals())
return faults.Fault(webob.exc.HTTPUnauthorized())
- if accounts:
- #we are punting on this til auth is settled,
- #and possibly til api v1.1 (mdragon)
- account = accounts[0]
- else:
- return faults.Fault(webob.exc.HTTPUnauthorized())
+ try:
+ account = req.headers["X-Auth-Project-Id"]
+ except KeyError:
+ # FIXME(usrleon): It needed only for compatibility
+ # while osapi clients don't use this header
+ accounts = self.auth.get_projects(user=user)
+ if accounts:
+ account = accounts[0]
+ else:
+ return faults.Fault(webob.exc.HTTPUnauthorized())
if not self.auth.is_admin(user) and \
not self.auth.is_project_member(user, account):
@@ -127,7 +129,7 @@ class AuthMiddleware(wsgi.Middleware):
except exception.NotFound:
return None
if token:
- delta = datetime.datetime.utcnow() - token['created_at']
+ delta = utils.utcnow() - token['created_at']
if delta.days >= 2:
self.db.auth_token_destroy(ctxt, token['token_hash'])
else:
diff --git a/nova/api/openstack/common.py b/nova/api/openstack/common.py
index bb1a96812..ce7e2805c 100644
--- a/nova/api/openstack/common.py
+++ b/nova/api/openstack/common.py
@@ -35,6 +35,37 @@ XML_NS_V10 = 'http://docs.rackspacecloud.com/servers/api/v1.0'
XML_NS_V11 = 'http://docs.openstack.org/compute/api/v1.1'
+def get_pagination_params(request):
+ """Return marker, limit tuple from request.
+
+ :param request: `wsgi.Request` possibly containing 'marker' and 'limit'
+ GET variables. 'marker' is the id of the last element
+ the client has seen, and 'limit' is the maximum number
+ of items to return. If 'limit' is not specified, 0, or
+ > max_limit, we default to max_limit. Negative values
+ for either marker or limit will cause
+ exc.HTTPBadRequest() exceptions to be raised.
+
+ """
+ try:
+ marker = int(request.GET.get('marker', 0))
+ except ValueError:
+ raise webob.exc.HTTPBadRequest(_('marker param must be an integer'))
+
+ try:
+ limit = int(request.GET.get('limit', 0))
+ except ValueError:
+ raise webob.exc.HTTPBadRequest(_('limit param must be an integer'))
+
+ if limit < 0:
+ raise webob.exc.HTTPBadRequest(_('limit param must be positive'))
+
+ if marker < 0:
+ raise webob.exc.HTTPBadRequest(_('marker param must be positive'))
+
+ return(marker, limit)
+
+
def limited(items, request, max_limit=FLAGS.osapi_max_limit):
"""
Return a slice of items according to requested offset and limit.
@@ -71,19 +102,10 @@ def limited(items, request, max_limit=FLAGS.osapi_max_limit):
def limited_by_marker(items, request, max_limit=FLAGS.osapi_max_limit):
"""Return a slice of items according to the requested marker and limit."""
+ (marker, limit) = get_pagination_params(request)
- try:
- marker = int(request.GET.get('marker', 0))
- except ValueError:
- raise webob.exc.HTTPBadRequest(_('marker param must be an integer'))
-
- try:
- limit = int(request.GET.get('limit', max_limit))
- except ValueError:
- raise webob.exc.HTTPBadRequest(_('limit param must be an integer'))
-
- if limit < 0:
- raise webob.exc.HTTPBadRequest(_('limit param must be positive'))
+ if limit == 0:
+ limit = max_limit
limit = min(max_limit, limit)
start_index = 0
@@ -99,34 +121,6 @@ def limited_by_marker(items, request, max_limit=FLAGS.osapi_max_limit):
return items[start_index:range_end]
-def get_image_id_from_image_hash(image_service, context, image_hash):
- """Given an Image ID Hash, return an objectstore Image ID.
-
- image_service - reference to objectstore compatible image service.
- context - security context for image service requests.
- image_hash - hash of the image ID.
- """
-
- # FIX(sandy): This is terribly inefficient. It pulls all images
- # from objectstore in order to find the match. ObjectStore
- # should have a numeric counterpart to the string ID.
- try:
- items = image_service.detail(context)
- except NotImplementedError:
- items = image_service.index(context)
- for image in items:
- image_id = image['id']
- try:
- if abs(hash(image_id)) == int(image_hash):
- return image_id
- except ValueError:
- msg = _("Requested image_id has wrong format: %s,"
- "should have numerical format") % image_id
- LOG.error(msg)
- raise Exception(msg)
- raise exception.ImageNotFound(image_id=image_hash)
-
-
def get_id_from_href(href):
"""Return the id portion of a url as an int.
diff --git a/nova/api/openstack/contrib/__init__.py b/nova/api/openstack/contrib/__init__.py
index b42a1d89d..acb5eb280 100644
--- a/nova/api/openstack/contrib/__init__.py
+++ b/nova/api/openstack/contrib/__init__.py
@@ -13,7 +13,7 @@
# 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 datetime
+# under the License.
"""Contrib contains extensions that are shipped with nova.
diff --git a/nova/api/openstack/extensions.py b/nova/api/openstack/extensions.py
index 881b61733..54e17e23d 100644
--- a/nova/api/openstack/extensions.py
+++ b/nova/api/openstack/extensions.py
@@ -137,7 +137,7 @@ class ActionExtensionResource(wsgi.Resource):
def __init__(self, application):
controller = ActionExtensionController(application)
- super(ActionExtensionResource, self).__init__(controller)
+ wsgi.Resource.__init__(self, controller)
def add_action(self, action_name, handler):
self.controller.add_action(action_name, handler)
@@ -164,7 +164,7 @@ class RequestExtensionResource(wsgi.Resource):
def __init__(self, application):
controller = RequestExtensionController(application)
- super(RequestExtensionResource, self).__init__(controller)
+ wsgi.Resource.__init__(self, controller)
def add_handler(self, handler):
self.controller.add_handler(handler)
diff --git a/nova/api/openstack/image_metadata.py b/nova/api/openstack/image_metadata.py
index 88e10168d..ebfe2bde9 100644
--- a/nova/api/openstack/image_metadata.py
+++ b/nova/api/openstack/image_metadata.py
@@ -18,6 +18,7 @@
from webob import exc
from nova import flags
+from nova import image
from nova import quota
from nova import utils
from nova.api.openstack import faults
@@ -31,7 +32,7 @@ class Controller(object):
"""The image metadata API controller for the Openstack API"""
def __init__(self):
- self.image_service = utils.import_object(FLAGS.image_service)
+ self.image_service = image.get_default_image_service()
def _get_metadata(self, context, image_id, image=None):
if not image:
diff --git a/nova/api/openstack/images.py b/nova/api/openstack/images.py
index d6107a2d0..5ffd8e96a 100644
--- a/nova/api/openstack/images.py
+++ b/nova/api/openstack/images.py
@@ -18,6 +18,7 @@ import webob.exc
from nova import compute
from nova import exception
from nova import flags
+import nova.image
from nova import log
from nova import utils
from nova.api.openstack import common
@@ -40,35 +41,11 @@ class Controller(object):
:param compute_service: `nova.compute.api:API`
:param image_service: `nova.image.service:BaseImageService`
- """
- _default_service = utils.import_object(flags.FLAGS.image_service)
-
- self._compute_service = compute_service or compute.API()
- self._image_service = image_service or _default_service
-
- def index(self, req):
- """Return an index listing of images available to the request.
- :param req: `wsgi.Request` object
"""
- context = req.environ['nova.context']
- filters = self._get_filters(req)
- images = self._image_service.index(context, filters)
- images = common.limited(images, req)
- builder = self.get_builder(req).build
- return dict(images=[builder(image, detail=False) for image in images])
-
- def detail(self, req):
- """Return a detailed index listing of images available to the request.
-
- :param req: `wsgi.Request` object.
- """
- context = req.environ['nova.context']
- filters = self._get_filters(req)
- images = self._image_service.detail(context, filters)
- images = common.limited(images, req)
- builder = self.get_builder(req).build
- return dict(images=[builder(image, detail=True) for image in images])
+ self._compute_service = compute_service or compute.API()
+ self._image_service = image_service or \
+ nova.image.get_default_image_service()
def _get_filters(self, req):
"""
@@ -88,22 +65,16 @@ class Controller(object):
"""Return detailed information about a specific image.
:param req: `wsgi.Request` object
- :param id: Image identifier (integer)
+ :param id: Image identifier
"""
context = req.environ['nova.context']
try:
- image_id = int(id)
- except ValueError:
+ image = self._image_service.show(context, id)
+ except (exception.NotFound, exception.InvalidImageRef):
explanation = _("Image not found.")
raise faults.Fault(webob.exc.HTTPNotFound(explanation=explanation))
- try:
- image = self._image_service.show(context, image_id)
- except exception.NotFound:
- explanation = _("Image '%d' not found.") % (image_id)
- raise faults.Fault(webob.exc.HTTPNotFound(explanation=explanation))
-
return dict(image=self.get_builder(req).build(image, detail=True))
def delete(self, req, id):
@@ -112,9 +83,8 @@ class Controller(object):
:param req: `wsgi.Request` object
:param id: Image identifier (integer)
"""
- image_id = id
context = req.environ['nova.context']
- self._image_service.delete(context, image_id)
+ self._image_service.delete(context, id)
return webob.exc.HTTPNoContent()
def create(self, req, body):
@@ -129,7 +99,7 @@ class Controller(object):
raise webob.exc.HTTPBadRequest()
try:
- server_id = body["image"]["serverId"]
+ server_id = self._server_id_from_req_data(body)
image_name = body["image"]["name"]
except KeyError:
raise webob.exc.HTTPBadRequest()
@@ -141,6 +111,9 @@ class Controller(object):
"""Indicates that you must use a Controller subclass."""
raise NotImplementedError
+ def _server_id_from_req_data(self, data):
+ raise NotImplementedError()
+
class ControllerV10(Controller):
"""Version 1.0 specific controller logic."""
@@ -150,6 +123,35 @@ class ControllerV10(Controller):
base_url = request.application_url
return images_view.ViewBuilderV10(base_url)
+ def index(self, req):
+ """Return an index listing of images available to the request.
+
+ :param req: `wsgi.Request` object
+
+ """
+ context = req.environ['nova.context']
+ filters = self._get_filters(req)
+ images = self._image_service.index(context, filters)
+ images = common.limited(images, req)
+ builder = self.get_builder(req).build
+ return dict(images=[builder(image, detail=False) for image in images])
+
+ def detail(self, req):
+ """Return a detailed index listing of images available to the request.
+
+ :param req: `wsgi.Request` object.
+
+ """
+ context = req.environ['nova.context']
+ filters = self._get_filters(req)
+ images = self._image_service.detail(context, filters)
+ images = common.limited(images, req)
+ builder = self.get_builder(req).build
+ return dict(images=[builder(image, detail=True) for image in images])
+
+ def _server_id_from_req_data(self, data):
+ return data['image']['serverId']
+
class ControllerV11(Controller):
"""Version 1.1 specific controller logic."""
@@ -159,6 +161,37 @@ class ControllerV11(Controller):
base_url = request.application_url
return images_view.ViewBuilderV11(base_url)
+ def index(self, req):
+ """Return an index listing of images available to the request.
+
+ :param req: `wsgi.Request` object
+
+ """
+ context = req.environ['nova.context']
+ filters = self._get_filters(req)
+ (marker, limit) = common.get_pagination_params(req)
+ images = self._image_service.index(
+ context, filters=filters, marker=marker, limit=limit)
+ builder = self.get_builder(req).build
+ return dict(images=[builder(image, detail=False) for image in images])
+
+ def detail(self, req):
+ """Return a detailed index listing of images available to the request.
+
+ :param req: `wsgi.Request` object.
+
+ """
+ context = req.environ['nova.context']
+ filters = self._get_filters(req)
+ (marker, limit) = common.get_pagination_params(req)
+ images = self._image_service.detail(
+ context, filters=filters, marker=marker, limit=limit)
+ builder = self.get_builder(req).build
+ return dict(images=[builder(image, detail=True) for image in images])
+
+ def _server_id_from_req_data(self, data):
+ return data['image']['serverRef']
+
def create_resource(version='1.0'):
controller = {
@@ -174,7 +207,7 @@ def create_resource(version='1.0'):
metadata = {
"attributes": {
"image": ["id", "name", "updated", "created", "status",
- "serverId", "progress"],
+ "serverId", "progress", "serverRef"],
"link": ["rel", "type", "href"],
},
}
diff --git a/nova/api/openstack/limits.py b/nova/api/openstack/limits.py
index 4d46b92df..dc2bc6bbc 100644
--- a/nova/api/openstack/limits.py
+++ b/nova/api/openstack/limits.py
@@ -11,7 +11,7 @@
# 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 datetime
+# under the License.
"""
Module dedicated functions/classes dealing with rate limiting requests.
diff --git a/nova/api/openstack/ratelimiting/__init__.py b/nova/api/openstack/ratelimiting/__init__.py
index 88ffc3246..9ede548c2 100644
--- a/nova/api/openstack/ratelimiting/__init__.py
+++ b/nova/api/openstack/ratelimiting/__init__.py
@@ -13,7 +13,7 @@
# 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 datetime
+# under the License.
"""Rate limiting of arbitrary actions."""
diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py
index 0f7dde389..9cf5e8721 100644
--- a/nova/api/openstack/servers.py
+++ b/nova/api/openstack/servers.py
@@ -22,6 +22,7 @@ from xml.dom import minidom
from nova import compute
from nova import exception
from nova import flags
+import nova.image
from nova import log as logging
from nova import quota
from nova import utils
@@ -51,13 +52,21 @@ class Controller(object):
def index(self, req):
""" Returns a list of server names and ids for a given user """
- return self._items(req, is_detail=False)
+ try:
+ servers = self._items(req, is_detail=False)
+ except exception.Invalid as err:
+ return exc.HTTPBadRequest(str(err))
+ return servers
def detail(self, req):
""" Returns a list of server details for a given user """
- return self._items(req, is_detail=True)
+ try:
+ servers = self._items(req, is_detail=True)
+ except exception.Invalid as err:
+ return exc.HTTPBadRequest(str(err))
+ return servers
- def _image_id_from_req_data(self, data):
+ def _image_ref_from_req_data(self, data):
raise NotImplementedError()
def _flavor_id_from_req_data(self, data):
@@ -121,18 +130,19 @@ class Controller(object):
key_name = key_pair['name']
key_data = key_pair['public_key']
- requested_image_id = self._image_id_from_req_data(body)
+ image_href = self._image_ref_from_req_data(body)
try:
- image_id = common.get_image_id_from_image_hash(self._image_service,
- context, requested_image_id)
+ image_service, image_id = nova.image.get_image_service(image_href)
+ kernel_id, ramdisk_id = self._get_kernel_ramdisk_from_image(
+ req, image_service, image_id)
+ images = set([str(x['id']) for x in image_service.index(context)])
+ assert str(image_id) in images
except:
- msg = _("Can not find requested image")
+ msg = _("Cannot find requested image %s") % image_href
return faults.Fault(exc.HTTPBadRequest(msg))
- kernel_id, ramdisk_id = self._get_kernel_ramdisk_from_image(
- req, image_id)
-
personality = body['server'].get('personality')
+
injected_files = []
if personality:
injected_files = self._get_injected_files(personality)
@@ -143,6 +153,7 @@ class Controller(object):
msg = _("Server name is not defined")
return exc.HTTPBadRequest(msg)
+ zone_blob = body['server'].get('blob')
name = body['server']['name']
self._validate_server_name(name)
name = name.strip()
@@ -153,7 +164,7 @@ class Controller(object):
(inst,) = self.compute_api.create(
context,
inst_type,
- image_id,
+ image_href,
kernel_id=kernel_id,
ramdisk_id=ramdisk_id,
display_name=name,
@@ -162,12 +173,16 @@ class Controller(object):
key_data=key_data,
metadata=body['server'].get('metadata', {}),
injected_files=injected_files,
- admin_password=password)
+ admin_password=password,
+ zone_blob=zone_blob)
except quota.QuotaError as error:
self._handle_quota_error(error)
+ except exception.ImageNotFound as error:
+ msg = _("Can not find requested image")
+ return faults.Fault(exc.HTTPBadRequest(msg))
inst['instance_type'] = inst_type
- inst['image_id'] = requested_image_id
+ inst['image_ref'] = image_href
builder = self._get_view_builder(req)
server = builder.build(inst, is_detail=True)
@@ -505,17 +520,15 @@ class Controller(object):
error=item.error))
return dict(actions=actions)
- def _get_kernel_ramdisk_from_image(self, req, image_id):
+ def _get_kernel_ramdisk_from_image(self, req, image_service, image_id):
"""Fetch an image from the ImageService, then if present, return the
associated kernel and ramdisk image IDs.
"""
context = req.environ['nova.context']
- image_meta = self._image_service.show(context, image_id)
+ image_meta = image_service.show(context, image_id)
# NOTE(sirp): extracted to a separate method to aid unit-testing, the
# new method doesn't need a request obj or an ImageService stub
- kernel_id, ramdisk_id = self._do_get_kernel_ramdisk_from_image(
- image_meta)
- return kernel_id, ramdisk_id
+ return self._do_get_kernel_ramdisk_from_image(image_meta)
@staticmethod
def _do_get_kernel_ramdisk_from_image(image_meta):
@@ -546,7 +559,7 @@ class Controller(object):
class ControllerV10(Controller):
- def _image_id_from_req_data(self, data):
+ def _image_ref_from_req_data(self, data):
return data['server']['imageId']
def _flavor_id_from_req_data(self, data):
@@ -604,9 +617,8 @@ class ControllerV10(Controller):
class ControllerV11(Controller):
- def _image_id_from_req_data(self, data):
- href = data['server']['imageRef']
- return common.get_id_from_href(href)
+ def _image_ref_from_req_data(self, data):
+ return data['server']['imageRef']
def _flavor_id_from_req_data(self, data):
href = data['server']['flavorRef']
@@ -686,13 +698,12 @@ class ControllerV11(Controller):
instance_id = int(instance_id)
try:
- image_ref = info["rebuild"]["imageRef"]
+ image_href = info["rebuild"]["imageRef"]
except (KeyError, TypeError):
msg = _("Could not parse imageRef from request.")
LOG.debug(msg)
return faults.Fault(exc.HTTPBadRequest(explanation=msg))
- image_id = common.get_id_from_href(image_ref)
personalities = info["rebuild"].get("personality", [])
metadata = info["rebuild"].get("metadata")
name = info["rebuild"].get("name")
@@ -702,7 +713,7 @@ class ControllerV11(Controller):
self._decode_personalities(personalities)
try:
- self.compute_api.rebuild(context, instance_id, image_id, name,
+ self.compute_api.rebuild(context, instance_id, image_href, name,
metadata, personalities)
except exception.BuildInProgress:
msg = _("Instance %d is currently being rebuilt.") % instance_id
diff --git a/nova/api/openstack/versions.py b/nova/api/openstack/versions.py
index 9db160102..4c682302f 100644
--- a/nova/api/openstack/versions.py
+++ b/nova/api/openstack/versions.py
@@ -35,7 +35,7 @@ class Versions(wsgi.Resource):
'application/xml': wsgi.XMLDictSerializer(metadata=metadata),
}
- super(Versions, self).__init__(None, serializers=serializers)
+ wsgi.Resource.__init__(self, None, serializers=serializers)
def dispatch(self, request, *args):
"""Respond to a request for all OpenStack API versions."""
diff --git a/nova/api/openstack/views/limits.py b/nova/api/openstack/views/limits.py
index e21c9f2fd..934b4921a 100644
--- a/nova/api/openstack/views/limits.py
+++ b/nova/api/openstack/views/limits.py
@@ -29,9 +29,6 @@ class ViewBuilder(object):
def _build_rate_limit(self, rate_limit):
raise NotImplementedError()
- def _build_absolute_limits(self, absolute_limit):
- raise NotImplementedError()
-
def build(self, rate_limits, absolute_limits):
rate_limits = self._build_rate_limits(rate_limits)
absolute_limits = self._build_absolute_limits(absolute_limits)
@@ -67,12 +64,6 @@ class ViewBuilder(object):
limits[name] = value
return limits
- def _build_rate_limits(self, rate_limits):
- raise NotImplementedError()
-
- def _build_rate_limit(self, rate_limit):
- raise NotImplementedError()
-
class ViewBuilderV10(ViewBuilder):
"""Openstack API v1.0 limits view builder."""
diff --git a/nova/api/openstack/views/servers.py b/nova/api/openstack/views/servers.py
index 0be468edc..b2352e3fd 100644
--- a/nova/api/openstack/views/servers.py
+++ b/nova/api/openstack/views/servers.py
@@ -18,6 +18,7 @@
import hashlib
import os
+from nova import exception
from nova.compute import power_state
import nova.compute
import nova.context
@@ -112,8 +113,11 @@ class ViewBuilderV10(ViewBuilder):
"""Model an Openstack API V1.0 server response."""
def _build_image(self, response, inst):
- if 'image_id' in dict(inst):
- response['imageId'] = inst['image_id']
+ if 'image_ref' in dict(inst):
+ image_ref = inst['image_ref']
+ if str(image_ref).startswith('http'):
+ raise exception.ListingImageRefsNotSupported()
+ response['imageId'] = int(image_ref)
def _build_flavor(self, response, inst):
if 'instance_type' in dict(inst):
@@ -130,9 +134,11 @@ class ViewBuilderV11(ViewBuilder):
self.base_url = base_url
def _build_image(self, response, inst):
- if "image_id" in dict(inst):
- image_id = inst.get("image_id")
- response["imageRef"] = self.image_builder.generate_href(image_id)
+ if 'image_ref' in dict(inst):
+ image_href = inst['image_ref']
+ if str(image_href).isdigit():
+ image_href = int(image_href)
+ response['imageRef'] = image_href
def _build_flavor(self, response, inst):
if "instance_type" in dict(inst):
diff --git a/nova/api/openstack/wsgi.py b/nova/api/openstack/wsgi.py
index ddf4e6fa9..6760735c4 100644
--- a/nova/api/openstack/wsgi.py
+++ b/nova/api/openstack/wsgi.py
@@ -225,7 +225,7 @@ class XMLDictSerializer(DictSerializer):
if not xmlns and self.xmlns:
node.setAttribute('xmlns', self.xmlns)
- return node.toprettyxml(indent=' ')
+ return node.toprettyxml(indent=' ', encoding='utf-8')
def _to_xml_node(self, doc, metadata, nodename, data):
"""Recursive method to convert data members to XML nodes."""
diff --git a/nova/api/openstack/zones.py b/nova/api/openstack/zones.py
index 8061b3b67..b2f7898cb 100644
--- a/nova/api/openstack/zones.py
+++ b/nova/api/openstack/zones.py
@@ -27,9 +27,6 @@ from nova.scheduler import api
FLAGS = flags.FLAGS
-flags.DEFINE_string('build_plan_encryption_key',
- None,
- '128bit (hex) encryption key for scheduler build plans.')
LOG = logging.getLogger('nova.api.openstack.zones')
@@ -53,6 +50,14 @@ def _scrub_zone(zone):
'deleted', 'deleted_at', 'updated_at'))
+def check_encryption_key(func):
+ def wrapped(*args, **kwargs):
+ if not FLAGS.build_plan_encryption_key:
+ raise exception.Error(_("--build_plan_encryption_key not set"))
+ return func(*args, **kwargs)
+ return wrapped
+
+
class Controller(object):
def index(self, req):
@@ -103,19 +108,13 @@ class Controller(object):
zone = api.zone_update(context, zone_id, body["zone"])
return dict(zone=_scrub_zone(zone))
- def select(self, req):
+ @check_encryption_key
+ def select(self, req, body):
"""Returns a weighted list of costs to create instances
of desired capabilities."""
ctx = req.environ['nova.context']
- qs = req.environ['QUERY_STRING']
- param_dict = urlparse.parse_qs(qs)
- param_dict.pop("fresh", None)
- # parse_qs returns a dict where the values are lists,
- # since query strings can have multiple values for the
- # same key. We need to convert that to single values.
- for key in param_dict:
- param_dict[key] = param_dict[key][0]
- build_plan = api.select(ctx, specs=param_dict)
+ specs = json.loads(body)
+ build_plan = api.select(ctx, specs=specs)
cooked = self._scrub_build_plan(build_plan)
return {"weights": cooked}
@@ -123,9 +122,6 @@ class Controller(object):
"""Remove all the confidential data and return a sanitized
version of the build plan. Include an encrypted full version
of the weighting entry so we can get back to it later."""
- if not FLAGS.build_plan_encryption_key:
- raise exception.FlagNotSet(flag='build_plan_encryption_key')
-
encryptor = crypto.encryptor(FLAGS.build_plan_encryption_key)
cooked = []
for entry in build_plan: