summaryrefslogtreecommitdiffstats
path: root/nova/api
diff options
context:
space:
mode:
authorTodd Willey <todd@ansolabs.com>2011-06-23 14:34:42 -0400
committerTodd Willey <todd@ansolabs.com>2011-06-23 14:34:42 -0400
commitf43832dff403405889dffc30824c0c3316335246 (patch)
treef90835d9e69d597a9eddd5001848807f91fbb07d /nova/api
parent0a6aeacfedfd5e666e109b54c5c03908eeb47c31 (diff)
parent6e2ebfa1dc29e50f74f1b337d1b5349bc3c78cdc (diff)
Merge provider-fw-rules-list.
Diffstat (limited to 'nova/api')
-rw-r--r--nova/api/ec2/admin.py4
-rw-r--r--nova/api/ec2/apirequest.py78
-rw-r--r--nova/api/ec2/cloud.py61
-rw-r--r--nova/api/ec2/ec2utils.py94
-rw-r--r--nova/api/openstack/__init__.py70
-rw-r--r--nova/api/openstack/common.py2
-rw-r--r--nova/api/openstack/contrib/volumes.py6
-rw-r--r--nova/api/openstack/create_instance_helper.py343
-rw-r--r--nova/api/openstack/extensions.py13
-rw-r--r--nova/api/openstack/image_metadata.py49
-rw-r--r--nova/api/openstack/ips.py19
-rw-r--r--nova/api/openstack/limits.py2
-rw-r--r--nova/api/openstack/notes.txt3
-rw-r--r--nova/api/openstack/server_metadata.py49
-rw-r--r--nova/api/openstack/servers.py295
-rw-r--r--nova/api/openstack/views/servers.py15
-rw-r--r--nova/api/openstack/wsgi.py47
-rw-r--r--nova/api/openstack/zones.py68
18 files changed, 770 insertions, 448 deletions
diff --git a/nova/api/ec2/admin.py b/nova/api/ec2/admin.py
index da982d8dc..b8fc8f114 100644
--- a/nova/api/ec2/admin.py
+++ b/nova/api/ec2/admin.py
@@ -22,7 +22,7 @@ Admin API controller, exposed through http via the api worker.
import base64
import datetime
-import IPy
+import netaddr
import urllib
from nova import compute
@@ -346,7 +346,7 @@ class AdminController(object):
cidr, context=context)
cidr = urllib.unquote(cidr).decode()
# raise if invalid
- IPy.IP(cidr)
+ netaddr.IPNetwork(cidr)
rule = {'cidr': cidr}
tcp_rule = rule.copy()
tcp_rule.update({'protocol': 'tcp', 'from_port': 1, 'to_port': 65535})
diff --git a/nova/api/ec2/apirequest.py b/nova/api/ec2/apirequest.py
index 6672e60bb..7d78c5cfa 100644
--- a/nova/api/ec2/apirequest.py
+++ b/nova/api/ec2/apirequest.py
@@ -21,22 +21,15 @@ APIRequest class
"""
import datetime
-import re
# TODO(termie): replace minidom with etree
from xml.dom import minidom
from nova import log as logging
+from nova.api.ec2 import ec2utils
LOG = logging.getLogger("nova.api.request")
-_c2u = re.compile('(((?<=[a-z])[A-Z])|([A-Z](?![A-Z]|$)))')
-
-
-def _camelcase_to_underscore(str):
- return _c2u.sub(r'_\1', str).lower().strip('_')
-
-
def _underscore_to_camelcase(str):
return ''.join([x[:1].upper() + x[1:] for x in str.split('_')])
@@ -51,59 +44,6 @@ def _database_to_isoformat(datetimeobj):
return datetimeobj.strftime("%Y-%m-%dT%H:%M:%SZ")
-def _try_convert(value):
- """Return a non-string from a string or unicode, if possible.
-
- ============= =====================================================
- When value is returns
- ============= =====================================================
- zero-length ''
- 'None' None
- 'True' True
- 'False' False
- '0', '-0' 0
- 0xN, -0xN int from hex (postitive) (N is any number)
- 0bN, -0bN int from binary (positive) (N is any number)
- * try conversion to int, float, complex, fallback value
-
- """
- if len(value) == 0:
- return ''
- if value == 'None':
- return None
- if value == 'True':
- return True
- if value == 'False':
- return False
- valueneg = value[1:] if value[0] == '-' else value
- if valueneg == '0':
- return 0
- if valueneg == '':
- return value
- if valueneg[0] == '0':
- if valueneg[1] in 'xX':
- return int(value, 16)
- elif valueneg[1] in 'bB':
- return int(value, 2)
- else:
- try:
- return int(value, 8)
- except ValueError:
- pass
- try:
- return int(value)
- except ValueError:
- pass
- try:
- return float(value)
- except ValueError:
- pass
- try:
- return complex(value)
- except ValueError:
- return value
-
-
class APIRequest(object):
def __init__(self, controller, action, version, args):
self.controller = controller
@@ -114,7 +54,7 @@ class APIRequest(object):
def invoke(self, context):
try:
method = getattr(self.controller,
- _camelcase_to_underscore(self.action))
+ ec2utils.camelcase_to_underscore(self.action))
except AttributeError:
controller = self.controller
action = self.action
@@ -125,19 +65,7 @@ class APIRequest(object):
# and reraise as 400 error.
raise Exception(_error)
- args = {}
- for key, value in self.args.items():
- parts = key.split(".")
- key = _camelcase_to_underscore(parts[0])
- if isinstance(value, str) or isinstance(value, unicode):
- # NOTE(vish): Automatically convert strings back
- # into their respective values
- value = _try_convert(value)
- if len(parts) > 1:
- d = args.get(key, {})
- d[parts[1]] = value
- value = d
- args[key] = value
+ args = ec2utils.dict_from_dotted_str(self.args.items())
for key in args.keys():
# NOTE(vish): Turn numeric dict keys into lists
diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py
index e1c65ae40..97875f1f5 100644
--- a/nova/api/ec2/cloud.py
+++ b/nova/api/ec2/cloud.py
@@ -909,6 +909,25 @@ class CloudController(object):
if kwargs.get('ramdisk_id'):
ramdisk = self._get_image(context, kwargs['ramdisk_id'])
kwargs['ramdisk_id'] = ramdisk['id']
+ for bdm in kwargs.get('block_device_mapping', []):
+ # NOTE(yamahata)
+ # BlockDevicedMapping.<N>.DeviceName
+ # BlockDevicedMapping.<N>.Ebs.SnapshotId
+ # BlockDevicedMapping.<N>.Ebs.VolumeSize
+ # BlockDevicedMapping.<N>.Ebs.DeleteOnTermination
+ # BlockDevicedMapping.<N>.VirtualName
+ # => remove .Ebs and allow volume id in SnapshotId
+ ebs = bdm.pop('ebs', None)
+ if ebs:
+ ec2_id = ebs.pop('snapshot_id')
+ id = ec2utils.ec2_id_to_id(ec2_id)
+ if ec2_id.startswith('snap-'):
+ bdm['snapshot_id'] = id
+ elif ec2_id.startswith('vol-'):
+ bdm['volume_id'] = id
+ ebs.setdefault('delete_on_termination', True)
+ bdm.update(ebs)
+
image = self._get_image(context, kwargs['image_id'])
if image:
@@ -933,37 +952,54 @@ class CloudController(object):
user_data=kwargs.get('user_data'),
security_group=kwargs.get('security_group'),
availability_zone=kwargs.get('placement', {}).get(
- 'AvailabilityZone'))
+ 'AvailabilityZone'),
+ block_device_mapping=kwargs.get('block_device_mapping', {}))
return self._format_run_instances(context,
instances[0]['reservation_id'])
+ def _do_instance(self, action, context, ec2_id):
+ instance_id = ec2utils.ec2_id_to_id(ec2_id)
+ action(context, instance_id=instance_id)
+
+ def _do_instances(self, action, context, instance_id):
+ for ec2_id in instance_id:
+ self._do_instance(action, context, ec2_id)
+
def terminate_instances(self, context, instance_id, **kwargs):
"""Terminate each instance in instance_id, which is a list of ec2 ids.
instance_id is a kwarg so its name cannot be modified."""
LOG.debug(_("Going to start terminating instances"))
- for ec2_id in instance_id:
- instance_id = ec2utils.ec2_id_to_id(ec2_id)
- self.compute_api.delete(context, instance_id=instance_id)
+ self._do_instances(self.compute_api.delete, context, instance_id)
return True
def reboot_instances(self, context, instance_id, **kwargs):
"""instance_id is a list of instance ids"""
LOG.audit(_("Reboot instance %r"), instance_id, context=context)
- for ec2_id in instance_id:
- instance_id = ec2utils.ec2_id_to_id(ec2_id)
- self.compute_api.reboot(context, instance_id=instance_id)
+ self._do_instances(self.compute_api.reboot, context, instance_id)
+ return True
+
+ def stop_instances(self, context, instance_id, **kwargs):
+ """Stop each instances in instance_id.
+ Here instance_id is a list of instance ids"""
+ LOG.debug(_("Going to stop instances"))
+ self._do_instances(self.compute_api.stop, context, instance_id)
+ return True
+
+ def start_instances(self, context, instance_id, **kwargs):
+ """Start each instances in instance_id.
+ Here instance_id is a list of instance ids"""
+ LOG.debug(_("Going to start instances"))
+ self._do_instances(self.compute_api.start, context, instance_id)
return True
def rescue_instance(self, context, instance_id, **kwargs):
"""This is an extension to the normal ec2_api"""
- instance_id = ec2utils.ec2_id_to_id(instance_id)
- self.compute_api.rescue(context, instance_id=instance_id)
+ self._do_instance(self.compute_api.rescue, contect, instnace_id)
return True
def unrescue_instance(self, context, instance_id, **kwargs):
"""This is an extension to the normal ec2_api"""
- instance_id = ec2utils.ec2_id_to_id(instance_id)
- self.compute_api.unrescue(context, instance_id=instance_id)
+ self._do_instance(self.compute_api.unrescue, context, instance_id)
return True
def update_instance(self, context, instance_id, **kwargs):
@@ -974,7 +1010,8 @@ class CloudController(object):
changes[field] = kwargs[field]
if changes:
instance_id = ec2utils.ec2_id_to_id(instance_id)
- self.compute_api.update(context, instance_id=instance_id, **kwargs)
+ self.compute_api.update(context, instance_id=instance_id,
+ **changes)
return True
@staticmethod
diff --git a/nova/api/ec2/ec2utils.py b/nova/api/ec2/ec2utils.py
index 163aa4ed2..222e1de1e 100644
--- a/nova/api/ec2/ec2utils.py
+++ b/nova/api/ec2/ec2utils.py
@@ -16,6 +16,8 @@
# License for the specific language governing permissions and limitations
# under the License.
+import re
+
from nova import exception
@@ -30,3 +32,95 @@ def ec2_id_to_id(ec2_id):
def id_to_ec2_id(instance_id, template='i-%08x'):
"""Convert an instance ID (int) to an ec2 ID (i-[base 16 number])"""
return template % instance_id
+
+
+_c2u = re.compile('(((?<=[a-z])[A-Z])|([A-Z](?![A-Z]|$)))')
+
+
+def camelcase_to_underscore(str):
+ return _c2u.sub(r'_\1', str).lower().strip('_')
+
+
+def _try_convert(value):
+ """Return a non-string from a string or unicode, if possible.
+
+ ============= =====================================================
+ When value is returns
+ ============= =====================================================
+ zero-length ''
+ 'None' None
+ 'True' True case insensitive
+ 'False' False case insensitive
+ '0', '-0' 0
+ 0xN, -0xN int from hex (postitive) (N is any number)
+ 0bN, -0bN int from binary (positive) (N is any number)
+ * try conversion to int, float, complex, fallback value
+
+ """
+ if len(value) == 0:
+ return ''
+ if value == 'None':
+ return None
+ lowered_value = value.lower()
+ if lowered_value == 'true':
+ return True
+ if lowered_value == 'false':
+ return False
+ valueneg = value[1:] if value[0] == '-' else value
+ if valueneg == '0':
+ return 0
+ if valueneg == '':
+ return value
+ if valueneg[0] == '0':
+ if valueneg[1] in 'xX':
+ return int(value, 16)
+ elif valueneg[1] in 'bB':
+ return int(value, 2)
+ else:
+ try:
+ return int(value, 8)
+ except ValueError:
+ pass
+ try:
+ return int(value)
+ except ValueError:
+ pass
+ try:
+ return float(value)
+ except ValueError:
+ pass
+ try:
+ return complex(value)
+ except ValueError:
+ return value
+
+
+def dict_from_dotted_str(items):
+ """parse multi dot-separated argument into dict.
+ EBS boot uses multi dot-separeted arguments like
+ BlockDeviceMapping.1.DeviceName=snap-id
+ Convert the above into
+ {'block_device_mapping': {'1': {'device_name': snap-id}}}
+ """
+ args = {}
+ for key, value in items:
+ parts = key.split(".")
+ key = camelcase_to_underscore(parts[0])
+ if isinstance(value, str) or isinstance(value, unicode):
+ # NOTE(vish): Automatically convert strings back
+ # into their respective values
+ value = _try_convert(value)
+
+ if len(parts) > 1:
+ d = args.get(key, {})
+ args[key] = d
+ for k in parts[1:-1]:
+ k = camelcase_to_underscore(k)
+ v = d.get(k, {})
+ d[k] = v
+ d = v
+ d[camelcase_to_underscore(parts[-1])] = value
+ else:
+ args[key] = value
+
+ return args
diff --git a/nova/api/openstack/__init__.py b/nova/api/openstack/__init__.py
index c116e4220..f24017df0 100644
--- a/nova/api/openstack/__init__.py
+++ b/nova/api/openstack/__init__.py
@@ -81,7 +81,9 @@ class APIRouter(base_wsgi.Router):
self._setup_routes(mapper)
super(APIRouter, self).__init__(mapper)
- def _setup_routes(self, mapper):
+ def _setup_routes(self, mapper, version):
+ """Routes common to all versions."""
+
server_members = self.server_members
server_members['action'] = 'POST'
if FLAGS.allow_admin_api:
@@ -98,11 +100,6 @@ class APIRouter(base_wsgi.Router):
server_members['reset_network'] = 'POST'
server_members['inject_network_info'] = 'POST'
- mapper.resource("zone", "zones",
- controller=zones.create_resource(),
- collection={'detail': 'GET', 'info': 'GET',
- 'select': 'POST'})
-
mapper.resource("user", "users",
controller=users.create_resource(),
collection={'detail': 'GET'})
@@ -111,10 +108,33 @@ class APIRouter(base_wsgi.Router):
controller=accounts.create_resource(),
collection={'detail': 'GET'})
+ mapper.resource("zone", "zones",
+ controller=zones.create_resource(version),
+ collection={'detail': 'GET',
+ 'info': 'GET',
+ 'select': 'POST',
+ 'boot': 'POST'})
+
mapper.resource("console", "consoles",
- controller=consoles.create_resource(),
- parent_resource=dict(member_name='server',
- collection_name='servers'))
+ controller=consoles.create_resource(),
+ parent_resource=dict(member_name='server',
+ collection_name='servers'))
+
+ mapper.resource("server", "servers",
+ controller=servers.create_resource(version),
+ collection={'detail': 'GET'},
+ member=self.server_members)
+
+ mapper.resource("image", "images",
+ controller=images.create_resource(version),
+ collection={'detail': 'GET'})
+
+ mapper.resource("limit", "limits",
+ controller=limits.create_resource(version))
+
+ mapper.resource("flavor", "flavors",
+ controller=flavors.create_resource(version),
+ collection={'detail': 'GET'})
super(APIRouter, self).__init__(mapper)
@@ -123,20 +143,11 @@ class APIRouterV10(APIRouter):
"""Define routes specific to OpenStack API V1.0."""
def _setup_routes(self, mapper):
- super(APIRouterV10, self)._setup_routes(mapper)
- mapper.resource("server", "servers",
- controller=servers.create_resource('1.0'),
- collection={'detail': 'GET'},
- member=self.server_members)
-
+ super(APIRouterV10, self)._setup_routes(mapper, '1.0')
mapper.resource("image", "images",
controller=images.create_resource('1.0'),
collection={'detail': 'GET'})
- mapper.resource("flavor", "flavors",
- controller=flavors.create_resource('1.0'),
- collection={'detail': 'GET'})
-
mapper.resource("shared_ip_group", "shared_ip_groups",
collection={'detail': 'GET'},
controller=shared_ip_groups.create_resource())
@@ -146,9 +157,6 @@ class APIRouterV10(APIRouter):
parent_resource=dict(member_name='server',
collection_name='servers'))
- mapper.resource("limit", "limits",
- controller=limits.create_resource('1.0'))
-
mapper.resource("ip", "ips", controller=ips.create_resource(),
collection=dict(public='GET', private='GET'),
parent_resource=dict(member_name='server',
@@ -159,16 +167,7 @@ class APIRouterV11(APIRouter):
"""Define routes specific to OpenStack API V1.1."""
def _setup_routes(self, mapper):
- super(APIRouterV11, self)._setup_routes(mapper)
- mapper.resource("server", "servers",
- controller=servers.create_resource('1.1'),
- collection={'detail': 'GET'},
- member=self.server_members)
-
- mapper.resource("image", "images",
- controller=images.create_resource('1.1'),
- collection={'detail': 'GET'})
-
+ super(APIRouterV11, self)._setup_routes(mapper, '1.1')
mapper.resource("image_meta", "meta",
controller=image_metadata.create_resource(),
parent_resource=dict(member_name='image',
@@ -178,10 +177,3 @@ class APIRouterV11(APIRouter):
controller=server_metadata.create_resource(),
parent_resource=dict(member_name='server',
collection_name='servers'))
-
- mapper.resource("flavor", "flavors",
- controller=flavors.create_resource('1.1'),
- collection={'detail': 'GET'})
-
- mapper.resource("limit", "limits",
- controller=limits.create_resource('1.1'))
diff --git a/nova/api/openstack/common.py b/nova/api/openstack/common.py
index ce7e2805c..4da7ec0ef 100644
--- a/nova/api/openstack/common.py
+++ b/nova/api/openstack/common.py
@@ -26,8 +26,6 @@ from nova import log as logging
LOG = logging.getLogger('nova.api.openstack.common')
-
-
FLAGS = flags.FLAGS
diff --git a/nova/api/openstack/contrib/volumes.py b/nova/api/openstack/contrib/volumes.py
index feabdce89..e5e2c5b50 100644
--- a/nova/api/openstack/contrib/volumes.py
+++ b/nova/api/openstack/contrib/volumes.py
@@ -301,7 +301,7 @@ class Volumes(extensions.ExtensionDescriptor):
return "Volumes"
def get_alias(self):
- return "VOLUMES"
+ return "os-volumes"
def get_description(self):
return "Volumes support"
@@ -317,12 +317,12 @@ class Volumes(extensions.ExtensionDescriptor):
# NOTE(justinsb): No way to provide singular name ('volume')
# Does this matter?
- res = extensions.ResourceExtension('volumes',
+ res = extensions.ResourceExtension('os-volumes',
VolumeController(),
collection_actions={'detail': 'GET'})
resources.append(res)
- res = extensions.ResourceExtension('volume_attachments',
+ res = extensions.ResourceExtension('os-volume_attachments',
VolumeAttachmentController(),
parent=dict(
member_name='server',
diff --git a/nova/api/openstack/create_instance_helper.py b/nova/api/openstack/create_instance_helper.py
new file mode 100644
index 000000000..436e524c1
--- /dev/null
+++ b/nova/api/openstack/create_instance_helper.py
@@ -0,0 +1,343 @@
+# Copyright 2011 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 base64
+import re
+import webob
+
+from webob import exc
+from xml.dom import minidom
+
+from nova import exception
+from nova import flags
+from nova import log as logging
+import nova.image
+from nova import quota
+from nova import utils
+
+from nova.compute import instance_types
+from nova.api.openstack import faults
+from nova.api.openstack import wsgi
+from nova.auth import manager as auth_manager
+
+
+LOG = logging.getLogger('nova.api.openstack.create_instance_helper')
+FLAGS = flags.FLAGS
+
+
+class CreateFault(exception.NovaException):
+ message = _("Invalid parameters given to create_instance.")
+
+ def __init__(self, fault):
+ self.fault = fault
+ super(CreateFault, self).__init__()
+
+
+class CreateInstanceHelper(object):
+ """This is the base class for OS API Controllers that
+ are capable of creating instances (currently Servers and Zones).
+
+ Once we stabilize the Zones portion of the API we may be able
+ to move this code back into servers.py
+ """
+
+ def __init__(self, controller):
+ """We need the image service to create an instance."""
+ self.controller = controller
+ self._image_service = utils.import_object(FLAGS.image_service)
+ super(CreateInstanceHelper, self).__init__()
+
+ def create_instance(self, req, body, create_method):
+ """Creates a new server for the given user. The approach
+ used depends on the create_method. For example, the standard
+ POST /server call uses compute.api.create(), while
+ POST /zones/server uses compute.api.create_all_at_once().
+
+ The problem is, both approaches return different values (i.e.
+ [instance dicts] vs. reservation_id). So the handling of the
+ return type from this method is left to the caller.
+ """
+ if not body:
+ raise faults.Fault(exc.HTTPUnprocessableEntity())
+
+ context = req.environ['nova.context']
+
+ password = self.controller._get_server_admin_password(body['server'])
+
+ key_name = None
+ key_data = None
+ key_pairs = auth_manager.AuthManager.get_key_pairs(context)
+ if key_pairs:
+ key_pair = key_pairs[0]
+ key_name = key_pair['name']
+ key_data = key_pair['public_key']
+
+ image_href = self.controller._image_ref_from_req_data(body)
+ try:
+ image_service, image_id = nova.image.get_image_service(image_href)
+ kernel_id, ramdisk_id = self._get_kernel_ramdisk_from_image(
+ req, image_id)
+ images = set([str(x['id']) for x in image_service.index(context)])
+ assert str(image_id) in images
+ except Exception, e:
+ msg = _("Cannot find requested image %(image_href)s: %(e)s" %
+ locals())
+ raise faults.Fault(exc.HTTPBadRequest(explanation=msg))
+
+ personality = body['server'].get('personality')
+
+ injected_files = []
+ if personality:
+ injected_files = self._get_injected_files(personality)
+
+ flavor_id = self.controller._flavor_id_from_req_data(body)
+
+ if not 'name' in body['server']:
+ msg = _("Server name is not defined")
+ raise exc.HTTPBadRequest(explanation=msg)
+
+ zone_blob = body['server'].get('blob')
+ name = body['server']['name']
+ self._validate_server_name(name)
+ name = name.strip()
+
+ reservation_id = body['server'].get('reservation_id')
+
+ try:
+ inst_type = \
+ instance_types.get_instance_type_by_flavor_id(flavor_id)
+ extra_values = {
+ 'instance_type': inst_type,
+ 'image_ref': image_href,
+ 'password': password}
+
+ return (extra_values,
+ create_method(context,
+ inst_type,
+ image_id,
+ kernel_id=kernel_id,
+ ramdisk_id=ramdisk_id,
+ display_name=name,
+ display_description=name,
+ key_name=key_name,
+ key_data=key_data,
+ metadata=body['server'].get('metadata', {}),
+ injected_files=injected_files,
+ admin_password=password,
+ zone_blob=zone_blob,
+ reservation_id=reservation_id))
+ except quota.QuotaError as error:
+ self._handle_quota_error(error)
+ except exception.ImageNotFound as error:
+ msg = _("Can not find requested image")
+ raise faults.Fault(exc.HTTPBadRequest(explanation=msg))
+
+ # Let the caller deal with unhandled exceptions.
+
+ def _handle_quota_error(self, error):
+ """
+ Reraise quota errors as api-specific http exceptions
+ """
+ if error.code == "OnsetFileLimitExceeded":
+ expl = _("Personality file limit exceeded")
+ raise exc.HTTPBadRequest(explanation=expl)
+ if error.code == "OnsetFilePathLimitExceeded":
+ expl = _("Personality file path too long")
+ raise exc.HTTPBadRequest(explanation=expl)
+ if error.code == "OnsetFileContentLimitExceeded":
+ expl = _("Personality file content too long")
+ raise exc.HTTPBadRequest(explanation=expl)
+ # if the original error is okay, just reraise it
+ raise error
+
+ def _deserialize_create(self, request):
+ """
+ Deserialize a create request
+
+ Overrides normal behavior in the case of xml content
+ """
+ if request.content_type == "application/xml":
+ deserializer = ServerCreateRequestXMLDeserializer()
+ return deserializer.deserialize(request.body)
+ else:
+ return self._deserialize(request.body, request.get_content_type())
+
+ def _validate_server_name(self, value):
+ if not isinstance(value, basestring):
+ msg = _("Server name is not a string or unicode")
+ raise exc.HTTPBadRequest(explanation=msg)
+
+ if value.strip() == '':
+ msg = _("Server name is an empty string")
+ raise exc.HTTPBadRequest(explanation=msg)
+
+ def _get_kernel_ramdisk_from_image(self, req, 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)
+ # 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
+
+ @staticmethod
+ def _do_get_kernel_ramdisk_from_image(image_meta):
+ """Given an ImageService image_meta, return kernel and ramdisk image
+ ids if present.
+
+ This is only valid for `ami` style images.
+ """
+ image_id = image_meta['id']
+ if image_meta['status'] != 'active':
+ raise exception.ImageUnacceptable(image_id=image_id,
+ reason=_("status is not active"))
+
+ if image_meta.get('container_format') != 'ami':
+ return None, None
+
+ try:
+ kernel_id = image_meta['properties']['kernel_id']
+ except KeyError:
+ raise exception.KernelNotFoundForImage(image_id=image_id)
+
+ try:
+ ramdisk_id = image_meta['properties']['ramdisk_id']
+ except KeyError:
+ raise exception.RamdiskNotFoundForImage(image_id=image_id)
+
+ return kernel_id, ramdisk_id
+
+ def _get_injected_files(self, personality):
+ """
+ Create a list of injected files from the personality attribute
+
+ At this time, injected_files must be formatted as a list of
+ (file_path, file_content) pairs for compatibility with the
+ underlying compute service.
+ """
+ injected_files = []
+
+ for item in personality:
+ try:
+ path = item['path']
+ contents = item['contents']
+ except KeyError as key:
+ expl = _('Bad personality format: missing %s') % key
+ raise exc.HTTPBadRequest(explanation=expl)
+ except TypeError:
+ expl = _('Bad personality format')
+ raise exc.HTTPBadRequest(explanation=expl)
+ try:
+ contents = base64.b64decode(contents)
+ except TypeError:
+ expl = _('Personality content for %s cannot be decoded') % path
+ raise exc.HTTPBadRequest(explanation=expl)
+ injected_files.append((path, contents))
+ return injected_files
+
+ def _get_server_admin_password_old_style(self, server):
+ """ Determine the admin password for a server on creation """
+ return utils.generate_password(16)
+
+ def _get_server_admin_password_new_style(self, server):
+ """ Determine the admin password for a server on creation """
+ password = server.get('adminPass')
+
+ if password is None:
+ return utils.generate_password(16)
+ if not isinstance(password, basestring) or password == '':
+ msg = _("Invalid adminPass")
+ raise exc.HTTPBadRequest(explanation=msg)
+ return password
+
+
+class ServerXMLDeserializer(wsgi.XMLDeserializer):
+ """
+ Deserializer to handle xml-formatted server create requests.
+
+ Handles standard server attributes as well as optional metadata
+ and personality attributes
+ """
+
+ def create(self, string):
+ """Deserialize an xml-formatted server create request"""
+ dom = minidom.parseString(string)
+ server = self._extract_server(dom)
+ return {'server': server}
+
+ def _extract_server(self, node):
+ """Marshal the server attribute of a parsed request"""
+ server = {}
+ server_node = self._find_first_child_named(node, 'server')
+ for attr in ["name", "imageId", "flavorId", "imageRef", "flavorRef"]:
+ if server_node.getAttribute(attr):
+ server[attr] = server_node.getAttribute(attr)
+ metadata = self._extract_metadata(server_node)
+ if metadata is not None:
+ server["metadata"] = metadata
+ personality = self._extract_personality(server_node)
+ if personality is not None:
+ server["personality"] = personality
+ return server
+
+ def _extract_metadata(self, server_node):
+ """Marshal the metadata attribute of a parsed request"""
+ metadata_node = self._find_first_child_named(server_node, "metadata")
+ if metadata_node is None:
+ return None
+ metadata = {}
+ for meta_node in self._find_children_named(metadata_node, "meta"):
+ key = meta_node.getAttribute("key")
+ metadata[key] = self._extract_text(meta_node)
+ return metadata
+
+ def _extract_personality(self, server_node):
+ """Marshal the personality attribute of a parsed request"""
+ personality_node = \
+ self._find_first_child_named(server_node, "personality")
+ if personality_node is None:
+ return None
+ personality = []
+ for file_node in self._find_children_named(personality_node, "file"):
+ item = {}
+ if file_node.hasAttribute("path"):
+ item["path"] = file_node.getAttribute("path")
+ item["contents"] = self._extract_text(file_node)
+ personality.append(item)
+ return personality
+
+ def _find_first_child_named(self, parent, name):
+ """Search a nodes children for the first child with a given name"""
+ for node in parent.childNodes:
+ if node.nodeName == name:
+ return node
+ return None
+
+ def _find_children_named(self, parent, name):
+ """Return all of a nodes children who have the given name"""
+ for node in parent.childNodes:
+ if node.nodeName == name:
+ yield node
+
+ def _extract_text(self, node):
+ """Get the text field contained by the given node"""
+ if len(node.childNodes) == 1:
+ child = node.childNodes[0]
+ if child.nodeType == child.TEXT_NODE:
+ return child.nodeValue
+ return ""
diff --git a/nova/api/openstack/extensions.py b/nova/api/openstack/extensions.py
index 54e17e23d..da06ecd15 100644
--- a/nova/api/openstack/extensions.py
+++ b/nova/api/openstack/extensions.py
@@ -374,6 +374,8 @@ class ExtensionManager(object):
LOG.debug(_('Ext updated: %s'), extension.get_updated())
except AttributeError as ex:
LOG.exception(_("Exception loading extension: %s"), unicode(ex))
+ return False
+ return True
def _load_all_extensions(self):
"""Load extensions from the configured path.
@@ -412,15 +414,16 @@ class ExtensionManager(object):
'file': ext_path})
continue
new_ext = new_ext_class()
- self._check_extension(new_ext)
- self._add_extension(new_ext)
+ self.add_extension(new_ext)
+
+ def add_extension(self, ext):
+ # Do nothing if the extension doesn't check out
+ if not self._check_extension(ext):
+ return
- def _add_extension(self, ext):
alias = ext.get_alias()
LOG.audit(_('Loaded extension: %s'), alias)
- self._check_extension(ext)
-
if alias in self.extensions:
raise exception.Error("Found duplicate extension: %s" % alias)
self.extensions[alias] = ext
diff --git a/nova/api/openstack/image_metadata.py b/nova/api/openstack/image_metadata.py
index ebfe2bde9..691264553 100644
--- a/nova/api/openstack/image_metadata.py
+++ b/nova/api/openstack/image_metadata.py
@@ -16,6 +16,7 @@
# under the License.
from webob import exc
+from xml.dom import minidom
from nova import flags
from nova import image
@@ -103,9 +104,55 @@ class Controller(object):
self.image_service.update(context, image_id, img, None)
+class ImageMetadataXMLSerializer(wsgi.XMLDictSerializer):
+ def __init__(self):
+ xmlns = wsgi.XMLNS_V11
+ super(ImageMetadataXMLSerializer, self).__init__(xmlns=xmlns)
+
+ def _meta_item_to_xml(self, doc, key, value):
+ node = doc.createElement('meta')
+ node.setAttribute('key', key)
+ text = doc.createTextNode(value)
+ node.appendChild(text)
+ return node
+
+ def _meta_list_to_xml(self, xml_doc, meta_items):
+ container_node = xml_doc.createElement('metadata')
+ for (key, value) in meta_items:
+ item_node = self._meta_item_to_xml(xml_doc, key, value)
+ container_node.appendChild(item_node)
+ return container_node
+
+ def _meta_list_to_xml_string(self, metadata_dict):
+ xml_doc = minidom.Document()
+ items = metadata_dict['metadata'].items()
+ container_node = self._meta_list_to_xml(xml_doc, items)
+ self._add_xmlns(container_node)
+ return container_node.toprettyxml(indent=' ')
+
+ def index(self, metadata_dict):
+ return self._meta_list_to_xml_string(metadata_dict)
+
+ def create(self, metadata_dict):
+ return self._meta_list_to_xml_string(metadata_dict)
+
+ def _meta_item_to_xml_string(self, meta_item_dict):
+ xml_doc = minidom.Document()
+ item_key, item_value = meta_item_dict.items()[0]
+ item_node = self._meta_item_to_xml(xml_doc, item_key, item_value)
+ self._add_xmlns(item_node)
+ return item_node.toprettyxml(indent=' ')
+
+ def show(self, meta_item_dict):
+ return self._meta_item_to_xml_string(meta_item_dict['meta'])
+
+ def update(self, meta_item_dict):
+ return self._meta_item_to_xml_string(meta_item_dict['meta'])
+
+
def create_resource():
serializers = {
- 'application/xml': wsgi.XMLDictSerializer(xmlns=wsgi.XMLNS_V11),
+ 'application/xml': ImageMetadataXMLSerializer(),
}
return wsgi.Resource(Controller(), serializers=serializers)
diff --git a/nova/api/openstack/ips.py b/nova/api/openstack/ips.py
index abea71830..71646b6d3 100644
--- a/nova/api/openstack/ips.py
+++ b/nova/api/openstack/ips.py
@@ -32,25 +32,24 @@ class Controller(object):
self.compute_api = nova.compute.API()
self.builder = nova.api.openstack.views.addresses.ViewBuilderV10()
- def index(self, req, server_id):
+ def _get_instance(self, req, server_id):
try:
- instance = self.compute_api.get(req.environ['nova.context'], id)
+ instance = self.compute_api.get(
+ req.environ['nova.context'], server_id)
except nova.exception.NotFound:
return faults.Fault(exc.HTTPNotFound())
+ return instance
+
+ def index(self, req, server_id):
+ instance = self._get_instance(req, server_id)
return {'addresses': self.builder.build(instance)}
def public(self, req, server_id):
- try:
- instance = self.compute_api.get(req.environ['nova.context'], id)
- except nova.exception.NotFound:
- return faults.Fault(exc.HTTPNotFound())
+ instance = self._get_instance(req, server_id)
return {'public': self.builder.build_public_parts(instance)}
def private(self, req, server_id):
- try:
- instance = self.compute_api.get(req.environ['nova.context'], id)
- except nova.exception.NotFound:
- return faults.Fault(exc.HTTPNotFound())
+ instance = self._get_instance(req, server_id)
return {'private': self.builder.build_private_parts(instance)}
def show(self, req, server_id, id):
diff --git a/nova/api/openstack/limits.py b/nova/api/openstack/limits.py
index dc2bc6bbc..fede96e33 100644
--- a/nova/api/openstack/limits.py
+++ b/nova/api/openstack/limits.py
@@ -99,7 +99,7 @@ def create_resource(version='1.0'):
serializers = {
'application/xml': wsgi.XMLDictSerializer(xmlns=xmlns,
- metadata=metadata)
+ metadata=metadata),
}
return wsgi.Resource(controller, serializers=serializers)
diff --git a/nova/api/openstack/notes.txt b/nova/api/openstack/notes.txt
index 2330f1002..4e95bffc8 100644
--- a/nova/api/openstack/notes.txt
+++ b/nova/api/openstack/notes.txt
@@ -7,9 +7,6 @@ image ids.
GlanceImageService(ImageService):
image ids are URIs.
-LocalImageService(ImageService):
-image ids are random strings.
-
OpenstackAPITranslationStore:
translates RS server/images/flavor/etc ids into formats required
by a given ImageService strategy.
diff --git a/nova/api/openstack/server_metadata.py b/nova/api/openstack/server_metadata.py
index b38b84a2a..8a314de22 100644
--- a/nova/api/openstack/server_metadata.py
+++ b/nova/api/openstack/server_metadata.py
@@ -18,9 +18,10 @@
from webob import exc
from nova import compute
-from nova import quota
from nova.api.openstack import faults
from nova.api.openstack import wsgi
+from nova import exception
+from nova import quota
class Controller(object):
@@ -37,23 +38,39 @@ class Controller(object):
meta_dict[key] = value
return dict(metadata=meta_dict)
+ def _check_body(self, body):
+ if body == None or body == "":
+ expl = _('No Request Body')
+ raise exc.HTTPBadRequest(explanation=expl)
+
def index(self, req, server_id):
""" Returns the list of metadata for a given instance """
context = req.environ['nova.context']
- return self._get_metadata(context, server_id)
+ try:
+ return self._get_metadata(context, server_id)
+ except exception.InstanceNotFound:
+ msg = _('Server %(server_id)s does not exist') % locals()
+ raise exc.HTTPNotFound(explanation=msg)
def create(self, req, server_id, body):
+ self._check_body(body)
context = req.environ['nova.context']
metadata = body.get('metadata')
try:
self.compute_api.update_or_create_instance_metadata(context,
server_id,
metadata)
+ except exception.InstanceNotFound:
+ msg = _('Server %(server_id)s does not exist') % locals()
+ raise exc.HTTPNotFound(explanation=msg)
+
except quota.QuotaError as error:
self._handle_quota_error(error)
- return req.body
+
+ return body
def update(self, req, server_id, id, body):
+ self._check_body(body)
context = req.environ['nova.context']
if not id in body:
expl = _('Request body and URI mismatch')
@@ -65,24 +82,38 @@ class Controller(object):
self.compute_api.update_or_create_instance_metadata(context,
server_id,
body)
+ except exception.InstanceNotFound:
+ msg = _('Server %(server_id)s does not exist') % locals()
+ raise exc.HTTPNotFound(explanation=msg)
+
except quota.QuotaError as error:
self._handle_quota_error(error)
- return req.body
+ return body
def show(self, req, server_id, id):
""" Return a single metadata item """
context = req.environ['nova.context']
- data = self._get_metadata(context, server_id)
- if id in data['metadata']:
+ try:
+ data = self._get_metadata(context, server_id)
+ except exception.InstanceNotFound:
+ msg = _('Server %(server_id)s does not exist') % locals()
+ raise exc.HTTPNotFound(explanation=msg)
+
+ try:
return {id: data['metadata'][id]}
- else:
- return faults.Fault(exc.HTTPNotFound())
+ except KeyError:
+ msg = _("metadata item %s was not found" % (id))
+ raise exc.HTTPNotFound(explanation=msg)
def delete(self, req, server_id, id):
""" Deletes an existing metadata """
context = req.environ['nova.context']
- self.compute_api.delete_instance_metadata(context, server_id, id)
+ try:
+ self.compute_api.delete_instance_metadata(context, server_id, id)
+ except exception.InstanceNotFound:
+ msg = _('Server %(server_id)s does not exist') % locals()
+ raise exc.HTTPNotFound(explanation=msg)
def _handle_quota_error(self, error):
"""Reraise quota errors as api-specific http exceptions."""
diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py
index 9cf5e8721..b82a6de19 100644
--- a/nova/api/openstack/servers.py
+++ b/nova/api/openstack/servers.py
@@ -17,24 +17,20 @@ import base64
import traceback
from webob import exc
-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
from nova.api.openstack import common
+from nova.api.openstack import create_instance_helper as helper
from nova.api.openstack import faults
import nova.api.openstack.views.addresses
import nova.api.openstack.views.flavors
import nova.api.openstack.views.images
import nova.api.openstack.views.servers
from nova.api.openstack import wsgi
-from nova.auth import manager as auth_manager
-from nova.compute import instance_types
import nova.api.openstack
from nova.scheduler import api as scheduler_api
@@ -48,14 +44,14 @@ class Controller(object):
def __init__(self):
self.compute_api = compute.API()
- self._image_service = utils.import_object(FLAGS.image_service)
+ self.helper = helper.CreateInstanceHelper(self)
def index(self, req):
""" Returns a list of server names and ids for a given user """
try:
servers = self._items(req, is_detail=False)
except exception.Invalid as err:
- return exc.HTTPBadRequest(str(err))
+ return exc.HTTPBadRequest(explanation=str(err))
return servers
def detail(self, req):
@@ -63,15 +59,9 @@ class Controller(object):
try:
servers = self._items(req, is_detail=True)
except exception.Invalid as err:
- return exc.HTTPBadRequest(str(err))
+ return exc.HTTPBadRequest(explanation=str(err))
return servers
- def _image_ref_from_req_data(self, data):
- raise NotImplementedError()
-
- def _flavor_id_from_req_data(self, data):
- raise NotImplementedError()
-
def _get_view_builder(self, req):
raise NotImplementedError()
@@ -86,7 +76,10 @@ class Controller(object):
builder - the response model builder
"""
- instance_list = self.compute_api.get_all(req.environ['nova.context'])
+ reservation_id = req.str_GET.get('reservation_id')
+ instance_list = self.compute_api.get_all(
+ req.environ['nova.context'],
+ reservation_id=reservation_id)
limited_list = self._limit_items(instance_list, req)
builder = self._get_view_builder(req)
servers = [builder.build(inst, is_detail)['server']
@@ -115,128 +108,25 @@ class Controller(object):
def create(self, req, body):
""" Creates a new server for a given user """
- if not body:
- return faults.Fault(exc.HTTPUnprocessableEntity())
-
- context = req.environ['nova.context']
-
- password = self._get_server_admin_password(body['server'])
-
- key_name = None
- key_data = None
- key_pairs = auth_manager.AuthManager.get_key_pairs(context)
- if key_pairs:
- key_pair = key_pairs[0]
- key_name = key_pair['name']
- key_data = key_pair['public_key']
-
- image_href = self._image_ref_from_req_data(body)
+ extra_values = None
+ result = None
try:
- 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 = _("Cannot find requested image %s") % image_href
- return faults.Fault(exc.HTTPBadRequest(msg))
-
- personality = body['server'].get('personality')
+ extra_values, result = self.helper.create_instance(
+ req, body, self.compute_api.create)
+ except faults.Fault, f:
+ return f
- injected_files = []
- if personality:
- injected_files = self._get_injected_files(personality)
+ instances = result
- flavor_id = self._flavor_id_from_req_data(body)
-
- if not 'name' in body['server']:
- 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()
-
- try:
- inst_type = \
- instance_types.get_instance_type_by_flavor_id(flavor_id)
- (inst,) = self.compute_api.create(
- context,
- inst_type,
- image_href,
- kernel_id=kernel_id,
- ramdisk_id=ramdisk_id,
- display_name=name,
- display_description=name,
- key_name=key_name,
- key_data=key_data,
- metadata=body['server'].get('metadata', {}),
- injected_files=injected_files,
- 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_ref'] = image_href
+ (inst, ) = instances
+ for key in ['instance_type', 'image_ref']:
+ inst[key] = extra_values[key]
builder = self._get_view_builder(req)
server = builder.build(inst, is_detail=True)
- server['server']['adminPass'] = password
+ server['server']['adminPass'] = extra_values['password']
return server
- def _get_injected_files(self, personality):
- """
- Create a list of injected files from the personality attribute
-
- At this time, injected_files must be formatted as a list of
- (file_path, file_content) pairs for compatibility with the
- underlying compute service.
- """
- injected_files = []
-
- for item in personality:
- try:
- path = item['path']
- contents = item['contents']
- except KeyError as key:
- expl = _('Bad personality format: missing %s') % key
- raise exc.HTTPBadRequest(explanation=expl)
- except TypeError:
- expl = _('Bad personality format')
- raise exc.HTTPBadRequest(explanation=expl)
- try:
- contents = base64.b64decode(contents)
- except TypeError:
- expl = _('Personality content for %s cannot be decoded') % path
- raise exc.HTTPBadRequest(explanation=expl)
- injected_files.append((path, contents))
- return injected_files
-
- def _handle_quota_error(self, error):
- """
- Reraise quota errors as api-specific http exceptions
- """
- if error.code == "OnsetFileLimitExceeded":
- expl = _("Personality file limit exceeded")
- raise exc.HTTPBadRequest(explanation=expl)
- if error.code == "OnsetFilePathLimitExceeded":
- expl = _("Personality file path too long")
- raise exc.HTTPBadRequest(explanation=expl)
- if error.code == "OnsetFileContentLimitExceeded":
- expl = _("Personality file content too long")
- raise exc.HTTPBadRequest(explanation=expl)
- # if the original error is okay, just reraise it
- raise error
-
- def _get_server_admin_password(self, server):
- """ Determine the admin password for a server on creation """
- return utils.generate_password(16)
-
@scheduler_api.redirect_handler
def update(self, req, id, body):
""" Updates the server name or password """
@@ -251,7 +141,7 @@ class Controller(object):
if 'name' in body['server']:
name = body['server']['name']
- self._validate_server_name(name)
+ self.helper._validate_server_name(name)
update_dict['display_name'] = name.strip()
self._parse_update(ctxt, id, body, update_dict)
@@ -263,15 +153,6 @@ class Controller(object):
return exc.HTTPNoContent()
- def _validate_server_name(self, value):
- if not isinstance(value, basestring):
- msg = _("Server name is not a string or unicode")
- raise exc.HTTPBadRequest(msg)
-
- if value.strip() == '':
- msg = _("Server name is an empty string")
- raise exc.HTTPBadRequest(msg)
-
def _parse_update(self, context, id, inst_dict, update_dict):
pass
@@ -520,45 +401,9 @@ class Controller(object):
error=item.error))
return dict(actions=actions)
- 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 = 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
- return self._do_get_kernel_ramdisk_from_image(image_meta)
-
- @staticmethod
- def _do_get_kernel_ramdisk_from_image(image_meta):
- """Given an ImageService image_meta, return kernel and ramdisk image
- ids if present.
-
- This is only valid for `ami` style images.
- """
- image_id = image_meta['id']
- if image_meta['status'] != 'active':
- raise exception.ImageUnacceptable(image_id=image_id,
- reason=_("status is not active"))
-
- if image_meta.get('container_format') != 'ami':
- return None, None
-
- try:
- kernel_id = image_meta['properties']['kernel_id']
- except KeyError:
- raise exception.KernelNotFoundForImage(image_id=image_id)
-
- try:
- ramdisk_id = image_meta['properties']['ramdisk_id']
- except KeyError:
- raise exception.RamdiskNotFoundForImage(image_id=image_id)
-
- return kernel_id, ramdisk_id
-
class ControllerV10(Controller):
+
def _image_ref_from_req_data(self, data):
return data['server']['imageId']
@@ -615,6 +460,10 @@ class ControllerV10(Controller):
response.empty_body = True
return response
+ def _get_server_admin_password(self, server):
+ """ Determine the admin password for a server on creation """
+ return self.helper._get_server_admin_password_old_style(server)
+
class ControllerV11(Controller):
def _image_ref_from_req_data(self, data):
@@ -639,11 +488,11 @@ class ControllerV11(Controller):
if (not 'changePassword' in input_dict
or not 'adminPass' in input_dict['changePassword']):
msg = _("No adminPass was specified")
- return exc.HTTPBadRequest(msg)
+ return exc.HTTPBadRequest(explanation=msg)
password = input_dict['changePassword']['adminPass']
if not isinstance(password, basestring) or password == '':
msg = _("Invalid adminPass")
- return exc.HTTPBadRequest(msg)
+ return exc.HTTPBadRequest(explanation=msg)
self.compute_api.set_admin_password(context, id, password)
return exc.HTTPAccepted()
@@ -724,92 +573,12 @@ class ControllerV11(Controller):
response.empty_body = True
return response
+ def get_default_xmlns(self, req):
+ return common.XML_NS_V11
+
def _get_server_admin_password(self, server):
""" Determine the admin password for a server on creation """
- password = server.get('adminPass')
- if password is None:
- return utils.generate_password(16)
- if not isinstance(password, basestring) or password == '':
- msg = _("Invalid adminPass")
- raise exc.HTTPBadRequest(msg)
- return password
-
-
-class ServerXMLDeserializer(wsgi.XMLDeserializer):
- """
- Deserializer to handle xml-formatted server create requests.
-
- Handles standard server attributes as well as optional metadata
- and personality attributes
- """
-
- def create(self, string):
- """Deserialize an xml-formatted server create request"""
- dom = minidom.parseString(string)
- server = self._extract_server(dom)
- return {'server': server}
-
- def _extract_server(self, node):
- """Marshal the server attribute of a parsed request"""
- server = {}
- server_node = self._find_first_child_named(node, 'server')
- for attr in ["name", "imageId", "flavorId", "imageRef", "flavorRef"]:
- if server_node.getAttribute(attr):
- server[attr] = server_node.getAttribute(attr)
- metadata = self._extract_metadata(server_node)
- if metadata is not None:
- server["metadata"] = metadata
- personality = self._extract_personality(server_node)
- if personality is not None:
- server["personality"] = personality
- return server
-
- def _extract_metadata(self, server_node):
- """Marshal the metadata attribute of a parsed request"""
- metadata_node = self._find_first_child_named(server_node, "metadata")
- if metadata_node is None:
- return None
- metadata = {}
- for meta_node in self._find_children_named(metadata_node, "meta"):
- key = meta_node.getAttribute("key")
- metadata[key] = self._extract_text(meta_node)
- return metadata
-
- def _extract_personality(self, server_node):
- """Marshal the personality attribute of a parsed request"""
- personality_node = \
- self._find_first_child_named(server_node, "personality")
- if personality_node is None:
- return None
- personality = []
- for file_node in self._find_children_named(personality_node, "file"):
- item = {}
- if file_node.hasAttribute("path"):
- item["path"] = file_node.getAttribute("path")
- item["contents"] = self._extract_text(file_node)
- personality.append(item)
- return personality
-
- def _find_first_child_named(self, parent, name):
- """Search a nodes children for the first child with a given name"""
- for node in parent.childNodes:
- if node.nodeName == name:
- return node
- return None
-
- def _find_children_named(self, parent, name):
- """Return all of a nodes children who have the given name"""
- for node in parent.childNodes:
- if node.nodeName == name:
- yield node
-
- def _extract_text(self, node):
- """Get the text field contained by the given node"""
- if len(node.childNodes) == 1:
- child = node.childNodes[0]
- if child.nodeType == child.TEXT_NODE:
- return child.nodeValue
- return ""
+ return self.helper._get_server_admin_password_new_style(server)
def create_resource(version='1.0'):
@@ -845,7 +614,7 @@ def create_resource(version='1.0'):
}
deserializers = {
- 'application/xml': ServerXMLDeserializer(),
+ 'application/xml': helper.ServerXMLDeserializer(),
}
return wsgi.Resource(controller, serializers=serializers,
diff --git a/nova/api/openstack/views/servers.py b/nova/api/openstack/views/servers.py
index b2352e3fd..cbfa5aae7 100644
--- a/nova/api/openstack/views/servers.py
+++ b/nova/api/openstack/views/servers.py
@@ -42,12 +42,15 @@ class ViewBuilder(object):
def build(self, inst, is_detail):
"""Return a dict that represenst a server."""
- if is_detail:
- server = self._build_detail(inst)
+ if inst.get('_is_precooked', False):
+ server = dict(server=inst)
else:
- server = self._build_simple(inst)
+ if is_detail:
+ server = self._build_detail(inst)
+ else:
+ server = self._build_simple(inst)
- self._build_extra(server, inst)
+ self._build_extra(server, inst)
return server
@@ -72,13 +75,14 @@ class ViewBuilder(object):
}
inst_dict = {
- 'id': int(inst['id']),
+ 'id': inst['id'],
'name': inst['display_name'],
'addresses': self.addresses_builder.build(inst),
'status': power_mapping[inst.get('state')]}
ctxt = nova.context.get_admin_context()
compute_api = nova.compute.API()
+
if compute_api.has_finished_migration(ctxt, inst['id']):
inst_dict['status'] = 'RESIZE-CONFIRM'
@@ -95,6 +99,7 @@ class ViewBuilder(object):
self._build_image(inst_dict, inst)
self._build_flavor(inst_dict, inst)
+ inst_dict['uuid'] = inst['uuid']
return dict(server=inst_dict)
def _build_image(self, response, inst):
diff --git a/nova/api/openstack/wsgi.py b/nova/api/openstack/wsgi.py
index 6760735c4..5d24b4cca 100644
--- a/nova/api/openstack/wsgi.py
+++ b/nova/api/openstack/wsgi.py
@@ -2,7 +2,9 @@
import json
import webob
from xml.dom import minidom
+from xml.parsers import expat
+import faults
from nova import exception
from nova import log as logging
from nova import utils
@@ -60,7 +62,7 @@ class TextDeserializer(object):
def deserialize(self, datastring, action='default'):
"""Find local deserialization method and parse request body."""
- action_method = getattr(self, action, self.default)
+ action_method = getattr(self, str(action), self.default)
return action_method(datastring)
def default(self, datastring):
@@ -71,7 +73,11 @@ class TextDeserializer(object):
class JSONDeserializer(TextDeserializer):
def default(self, datastring):
- return utils.loads(datastring)
+ try:
+ return utils.loads(datastring)
+ except ValueError:
+ raise exception.MalformedRequestBody(
+ reason=_("malformed JSON in request body"))
class XMLDeserializer(TextDeserializer):
@@ -86,8 +92,13 @@ class XMLDeserializer(TextDeserializer):
def default(self, datastring):
plurals = set(self.metadata.get('plurals', {}))
- node = minidom.parseString(datastring).childNodes[0]
- return {node.nodeName: self._from_xml_node(node, plurals)}
+
+ try:
+ node = minidom.parseString(datastring).childNodes[0]
+ return {node.nodeName: self._from_xml_node(node, plurals)}
+ except expat.ExpatError:
+ raise exception.MalformedRequestBody(
+ reason=_("malformed XML in request body"))
def _from_xml_node(self, node, listnames):
"""Convert a minidom node to a simple Python type.
@@ -189,7 +200,7 @@ class DictSerializer(object):
def serialize(self, data, action='default'):
"""Find local serialization method and encode response body."""
- action_method = getattr(self, action, self.default)
+ action_method = getattr(self, str(action), self.default)
return action_method(data)
def default(self, data):
@@ -221,12 +232,14 @@ class XMLDictSerializer(DictSerializer):
doc = minidom.Document()
node = self._to_xml_node(doc, self.metadata, root_key, data[root_key])
- xmlns = node.getAttribute('xmlns')
- if not xmlns and self.xmlns:
- node.setAttribute('xmlns', self.xmlns)
+ self._add_xmlns(node)
return node.toprettyxml(indent=' ', encoding='utf-8')
+ def _add_xmlns(self, node):
+ if self.xmlns is not None:
+ node.setAttribute('xmlns', self.xmlns)
+
def _to_xml_node(self, doc, metadata, nodename, data):
"""Recursive method to convert data members to XML nodes."""
result = doc.createElement(nodename)
@@ -296,7 +309,7 @@ class ResponseSerializer(object):
}
self.serializers.update(serializers or {})
- def serialize(self, response_data, content_type):
+ def serialize(self, response_data, content_type, action='default'):
"""Serialize a dict into a string and wrap in a wsgi.Request object.
:param response_data: dict produced by the Controller
@@ -307,7 +320,7 @@ class ResponseSerializer(object):
response.headers['Content-Type'] = content_type
serializer = self.get_serializer(content_type)
- response.body = serializer.serialize(response_data)
+ response.body = serializer.serialize(response_data, action)
return response
@@ -352,22 +365,26 @@ class Resource(wsgi.Application):
action, action_args, accept = self.deserializer.deserialize(
request)
except exception.InvalidContentType:
- return webob.exc.HTTPBadRequest(_("Unsupported Content-Type"))
+ msg = _("Unsupported Content-Type")
+ return webob.exc.HTTPBadRequest(explanation=msg)
+ except exception.MalformedRequestBody:
+ msg = _("Malformed request body")
+ return faults.Fault(webob.exc.HTTPBadRequest(explanation=msg))
action_result = self.dispatch(request, action, action_args)
#TODO(bcwaldon): find a more elegant way to pass through non-dict types
if type(action_result) is dict:
- response = self.serializer.serialize(action_result, accept)
+ response = self.serializer.serialize(action_result, accept, action)
else:
response = action_result
try:
msg_dict = dict(url=request.url, status=response.status_int)
msg = _("%(url)s returned with HTTP %(status)d") % msg_dict
- except AttributeError:
- msg_dict = dict(url=request.url)
- msg = _("%(url)s returned a fault")
+ except AttributeError, e:
+ msg_dict = dict(url=request.url, e=e)
+ msg = _("%(url)s returned a fault: %(e)s" % msg_dict)
LOG.debug(msg)
diff --git a/nova/api/openstack/zones.py b/nova/api/openstack/zones.py
index b2f7898cb..8864f825b 100644
--- a/nova/api/openstack/zones.py
+++ b/nova/api/openstack/zones.py
@@ -21,9 +21,14 @@ from nova import db
from nova import exception
from nova import flags
from nova import log as logging
+
+from nova.compute import api as compute
+from nova.scheduler import api
+
+from nova.api.openstack import create_instance_helper as helper
from nova.api.openstack import common
+from nova.api.openstack import faults
from nova.api.openstack import wsgi
-from nova.scheduler import api
FLAGS = flags.FLAGS
@@ -59,6 +64,11 @@ def check_encryption_key(func):
class Controller(object):
+ """Controller for Zone resources."""
+
+ def __init__(self):
+ self.compute_api = compute.API()
+ self.helper = helper.CreateInstanceHelper(self)
def index(self, req):
"""Return all zones in brief"""
@@ -93,21 +103,39 @@ class Controller(object):
return dict(zone=_scrub_zone(zone))
def delete(self, req, id):
+ """Delete a child zone entry."""
zone_id = int(id)
api.zone_delete(req.environ['nova.context'], zone_id)
return {}
def create(self, req, body):
+ """Create a child zone entry."""
context = req.environ['nova.context']
zone = api.zone_create(context, body["zone"])
return dict(zone=_scrub_zone(zone))
def update(self, req, id, body):
+ """Update a child zone entry."""
context = req.environ['nova.context']
zone_id = int(id)
zone = api.zone_update(context, zone_id, body["zone"])
return dict(zone=_scrub_zone(zone))
+ def boot(self, req, body):
+ """Creates a new server for a given user while being Zone aware.
+
+ Returns a reservation ID (a UUID).
+ """
+ result = None
+ try:
+ extra_values, result = self.helper.create_instance(req, body,
+ self.compute_api.create_all_at_once)
+ except faults.Fault, f:
+ return f
+
+ reservation_id = result
+ return {'reservation_id': reservation_id}
+
@check_encryption_key
def select(self, req, body):
"""Returns a weighted list of costs to create instances
@@ -131,8 +159,37 @@ class Controller(object):
blob=cipher_text))
return cooked
+ def _image_ref_from_req_data(self, data):
+ return data['server']['imageId']
+
+ def _flavor_id_from_req_data(self, data):
+ return data['server']['flavorId']
+
+ def _get_server_admin_password(self, server):
+ """ Determine the admin password for a server on creation """
+ return self.helper._get_server_admin_password_old_style(server)
+
+
+class ControllerV11(object):
+ """Controller for 1.1 Zone resources."""
+
+ def _get_server_admin_password(self, server):
+ """ Determine the admin password for a server on creation """
+ return self.helper._get_server_admin_password_new_style(server)
+
+ def _image_ref_from_req_data(self, data):
+ return data['server']['imageRef']
+
+ def _flavor_id_from_req_data(self, data):
+ return data['server']['flavorRef']
+
+
+def create_resource(version):
+ controller = {
+ '1.0': Controller,
+ '1.1': ControllerV11,
+ }[version]()
-def create_resource():
metadata = {
"attributes": {
"zone": ["id", "api_url", "name", "capabilities"],
@@ -144,4 +201,9 @@ def create_resource():
metadata=metadata),
}
- return wsgi.Resource(Controller(), serializers=serializers)
+ deserializers = {
+ 'application/xml': helper.ServerXMLDeserializer(),
+ }
+
+ return wsgi.Resource(controller, serializers=serializers,
+ deserializers=deserializers)