summaryrefslogtreecommitdiffstats
path: root/nova/api
diff options
context:
space:
mode:
authorJenkins <jenkins@review.openstack.org>2011-09-27 20:01:30 +0000
committerGerrit Code Review <review@openstack.org>2011-09-27 20:01:30 +0000
commita2646129bc9dbd9dec57bdde7f510e0ea7bbddea (patch)
tree041886cfdef32a0bb26cc51399e1e08d0c7b5f39 /nova/api
parent7dba1d9aa989760b190f1cf3bad2ed22bb2e2fc5 (diff)
parent0fab78825ef06310926181f6f97d377058b56b97 (diff)
Merge "compute_api create*() and schedulers refactoring"
Diffstat (limited to 'nova/api')
-rw-r--r--nova/api/ec2/cloud.py10
-rw-r--r--nova/api/openstack/__init__.py3
-rw-r--r--nova/api/openstack/contrib/createserverext.py3
-rw-r--r--nova/api/openstack/contrib/volumes.py43
-rw-r--r--nova/api/openstack/contrib/zones.py50
-rw-r--r--nova/api/openstack/create_instance_helper.py606
-rw-r--r--nova/api/openstack/servers.py616
-rw-r--r--nova/api/openstack/zones.py48
8 files changed, 652 insertions, 727 deletions
diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py
index 3c8cd513f..a71ab8cf0 100644
--- a/nova/api/ec2/cloud.py
+++ b/nova/api/ec2/cloud.py
@@ -1385,7 +1385,7 @@ class CloudController(object):
if image_state != 'available':
raise exception.ApiError(_('Image must be available'))
- instances = self.compute_api.create(context,
+ (instances, resv_id) = self.compute_api.create(context,
instance_type=instance_types.get_instance_type_by_name(
kwargs.get('instance_type', None)),
image_href=self._get_image(context, kwargs['image_id'])['id'],
@@ -1400,9 +1400,11 @@ class CloudController(object):
security_group=kwargs.get('security_group'),
availability_zone=kwargs.get('placement', {}).get(
'AvailabilityZone'),
- block_device_mapping=kwargs.get('block_device_mapping', {}))
- return self._format_run_instances(context,
- reservation_id=instances[0]['reservation_id'])
+ block_device_mapping=kwargs.get('block_device_mapping', {}),
+ # NOTE(comstud): Unfortunately, EC2 requires that the
+ # instance DB entries have been created..
+ wait_for_instances=True)
+ return self._format_run_instances(context, resv_id)
def _do_instance(self, action, context, ec2_id):
instance_id = ec2utils.ec2_id_to_id(ec2_id)
diff --git a/nova/api/openstack/__init__.py b/nova/api/openstack/__init__.py
index 3b74fefc9..b517eae2c 100644
--- a/nova/api/openstack/__init__.py
+++ b/nova/api/openstack/__init__.py
@@ -139,8 +139,7 @@ class APIRouter(base_wsgi.Router):
controller=zones.create_resource(version),
collection={'detail': 'GET',
'info': 'GET',
- 'select': 'POST',
- 'boot': 'POST'})
+ 'select': 'POST'})
mapper.connect("versions", "/",
controller=versions.create_resource(version),
diff --git a/nova/api/openstack/contrib/createserverext.py b/nova/api/openstack/contrib/createserverext.py
index af7f37f13..1e13e10d4 100644
--- a/nova/api/openstack/contrib/createserverext.py
+++ b/nova/api/openstack/contrib/createserverext.py
@@ -15,7 +15,6 @@
# under the License
from nova import utils
-from nova.api.openstack import create_instance_helper as helper
from nova.api.openstack import extensions
from nova.api.openstack import servers
from nova.api.openstack import wsgi
@@ -66,7 +65,7 @@ class Createserverext(extensions.ExtensionDescriptor):
}
body_deserializers = {
- 'application/xml': helper.ServerXMLDeserializerV11(),
+ 'application/xml': servers.ServerXMLDeserializerV11(),
}
serializer = wsgi.ResponseSerializer(body_serializers,
diff --git a/nova/api/openstack/contrib/volumes.py b/nova/api/openstack/contrib/volumes.py
index 9d4254f1f..6c79a0538 100644
--- a/nova/api/openstack/contrib/volumes.py
+++ b/nova/api/openstack/contrib/volumes.py
@@ -334,47 +334,8 @@ class VolumeAttachmentController(object):
class BootFromVolumeController(servers.ControllerV11):
"""The boot from volume API controller for the Openstack API."""
- def _create_instance(self, context, instance_type, image_href, **kwargs):
- try:
- return self.compute_api.create(context, instance_type,
- image_href, **kwargs)
- except quota.QuotaError as error:
- self.helper._handle_quota_error(error)
- except exception.ImageNotFound as error:
- msg = _("Can not find requested image")
- raise faults.Fault(exc.HTTPBadRequest(explanation=msg))
-
- def create(self, req, body):
- """ Creates a new server for a given user """
- extra_values = None
- try:
-
- def get_kwargs(context, instance_type, image_href, **kwargs):
- kwargs['context'] = context
- kwargs['instance_type'] = instance_type
- kwargs['image_href'] = image_href
- return kwargs
-
- extra_values, kwargs = self.helper.create_instance(req, body,
- get_kwargs)
-
- block_device_mapping = body['server'].get('block_device_mapping')
- kwargs['block_device_mapping'] = block_device_mapping
-
- instances = self._create_instance(**kwargs)
- except faults.Fault, f:
- return f
-
- # We can only return 1 instance via the API, if we happen to
- # build more than one... instances is a list, so we'll just
- # use the first one..
- inst = instances[0]
- for key in ['instance_type', 'image_ref']:
- inst[key] = extra_values[key]
-
- server = self._build_view(req, inst, is_detail=True)
- server['server']['adminPass'] = extra_values['password']
- return server
+ def _get_block_device_mapping(self, data):
+ return data.get('block_device_mapping')
class Volumes(extensions.ExtensionDescriptor):
diff --git a/nova/api/openstack/contrib/zones.py b/nova/api/openstack/contrib/zones.py
new file mode 100644
index 000000000..5901e321e
--- /dev/null
+++ b/nova/api/openstack/contrib/zones.py
@@ -0,0 +1,50 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# 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.
+
+"""The zones extension."""
+
+
+from nova import flags
+from nova import log as logging
+from nova.api.openstack import extensions
+
+
+LOG = logging.getLogger("nova.api.zones")
+FLAGS = flags.FLAGS
+
+
+class Zones(extensions.ExtensionDescriptor):
+ def get_name(self):
+ return "Zones"
+
+ def get_alias(self):
+ return "os-zones"
+
+ def get_description(self):
+ return """Enables zones-related functionality such as adding
+child zones, listing child zones, getting the capabilities of the
+local zone, and returning build plans to parent zones' schedulers"""
+
+ def get_namespace(self):
+ return "http://docs.openstack.org/ext/zones/api/v1.1"
+
+ def get_updated(self):
+ return "2011-09-21T00:00:00+00:00"
+
+ def get_resources(self):
+ # Nothing yet.
+ return []
diff --git a/nova/api/openstack/create_instance_helper.py b/nova/api/openstack/create_instance_helper.py
deleted file mode 100644
index dde9187cd..000000000
--- a/nova/api/openstack/create_instance_helper.py
+++ /dev/null
@@ -1,606 +0,0 @@
-# Copyright 2011 OpenStack LLC.
-# Copyright 2011 Piston Cloud Computing, Inc.
-# 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
-
-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 common
-from nova.api.openstack import wsgi
-from nova.rpc.common import RemoteError
-
-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 exc.HTTPUnprocessableEntity()
-
- if not 'server' in body:
- raise exc.HTTPUnprocessableEntity()
-
- context = req.environ['nova.context']
- server_dict = body['server']
- password = self.controller._get_server_admin_password(server_dict)
-
- if not 'name' in server_dict:
- msg = _("Server name is not defined")
- raise exc.HTTPBadRequest(explanation=msg)
-
- name = server_dict['name']
- self._validate_server_name(name)
- name = name.strip()
-
- image_href = self.controller._image_ref_from_req_data(body)
- # If the image href was generated by nova api, strip image_href
- # down to an id and use the default glance connection params
-
- if str(image_href).startswith(req.application_url):
- image_href = image_href.split('/').pop()
- try:
- image_service, image_id = nova.image.get_image_service(context,
- 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 Exception, e:
- msg = _("Cannot find requested image %(image_href)s: %(e)s" %
- locals())
- raise exc.HTTPBadRequest(explanation=msg)
-
- personality = server_dict.get('personality')
- config_drive = server_dict.get('config_drive')
-
- injected_files = []
- if personality:
- injected_files = self._get_injected_files(personality)
-
- sg_names = []
- security_groups = server_dict.get('security_groups')
- if security_groups is not None:
- sg_names = [sg['name'] for sg in security_groups if sg.get('name')]
- if not sg_names:
- sg_names.append('default')
-
- sg_names = list(set(sg_names))
-
- requested_networks = server_dict.get('networks')
- if requested_networks is not None:
- requested_networks = self._get_requested_networks(
- requested_networks)
-
- try:
- flavor_id = self.controller._flavor_id_from_req_data(body)
- except ValueError as error:
- msg = _("Invalid flavorRef provided.")
- raise exc.HTTPBadRequest(explanation=msg)
-
- zone_blob = server_dict.get('blob')
-
- # optional openstack extensions:
- key_name = server_dict.get('key_name')
- user_data = server_dict.get('user_data')
- self._validate_user_data(user_data)
-
- availability_zone = server_dict.get('availability_zone')
- name = server_dict['name']
- self._validate_server_name(name)
- name = name.strip()
-
- reservation_id = server_dict.get('reservation_id')
- min_count = server_dict.get('min_count')
- max_count = server_dict.get('max_count')
- # min_count and max_count are optional. If they exist, they come
- # in as strings. We want to default 'min_count' to 1, and default
- # 'max_count' to be 'min_count'.
- min_count = int(min_count) if min_count else 1
- max_count = int(max_count) if max_count else min_count
- if min_count > max_count:
- min_count = max_count
-
- try:
- inst_type = \
- instance_types.get_instance_type_by_flavor_id(flavor_id)
- extra_values = {
- 'instance_type': inst_type,
- 'image_ref': image_href,
- 'config_drive': config_drive,
- '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,
- metadata=server_dict.get('metadata', {}),
- access_ip_v4=server_dict.get('accessIPv4'),
- access_ip_v6=server_dict.get('accessIPv6'),
- injected_files=injected_files,
- admin_password=password,
- zone_blob=zone_blob,
- reservation_id=reservation_id,
- min_count=min_count,
- max_count=max_count,
- requested_networks=requested_networks,
- security_group=sg_names,
- user_data=user_data,
- availability_zone=availability_zone,
- config_drive=config_drive,))
- except quota.QuotaError as error:
- self._handle_quota_error(error)
- except exception.InstanceTypeMemoryTooSmall as error:
- raise exc.HTTPBadRequest(explanation=unicode(error))
- except exception.InstanceTypeDiskTooSmall as error:
- raise exc.HTTPBadRequest(explanation=unicode(error))
- except exception.ImageNotFound as error:
- msg = _("Can not find requested image")
- raise exc.HTTPBadRequest(explanation=msg)
- except exception.FlavorNotFound as error:
- msg = _("Invalid flavorRef provided.")
- raise exc.HTTPBadRequest(explanation=msg)
- except exception.KeypairNotFound as error:
- msg = _("Invalid key_name provided.")
- raise exc.HTTPBadRequest(explanation=msg)
- except exception.SecurityGroupNotFound as error:
- raise exc.HTTPBadRequest(explanation=unicode(error))
- except RemoteError as err:
- msg = "%(err_type)s: %(err_msg)s" % \
- {'err_type': err.exc_type, 'err_msg': err.value}
- raise 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.HTTPRequestEntityTooLarge(explanation=error.message,
- headers={'Retry-After': 0})
- if error.code == "OnsetFilePathLimitExceeded":
- expl = _("Personality file path too long")
- raise exc.HTTPRequestEntityTooLarge(explanation=error.message,
- headers={'Retry-After': 0})
- if error.code == "OnsetFileContentLimitExceeded":
- expl = _("Personality file content too long")
- raise exc.HTTPRequestEntityTooLarge(explanation=error.message,
- headers={'Retry-After': 0})
- if error.code == "InstanceLimitExceeded":
- expl = _("Instance quotas have been exceeded")
- raise exc.HTTPRequestEntityTooLarge(explanation=error.message,
- headers={'Retry-After': 0})
- # 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 = ServerXMLDeserializer()
- 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_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
- 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:
- ramdisk_id = None
-
- 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(FLAGS.password_length)
-
- 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(FLAGS.password_length)
- if not isinstance(password, basestring) or password == '':
- msg = _("Invalid adminPass")
- raise exc.HTTPBadRequest(explanation=msg)
- return password
-
- def _get_requested_networks(self, requested_networks):
- """
- Create a list of requested networks from the networks attribute
- """
- networks = []
- for network in requested_networks:
- try:
- network_uuid = network['uuid']
-
- if not utils.is_uuid_like(network_uuid):
- msg = _("Bad networks format: network uuid is not in"
- " proper format (%s)") % network_uuid
- raise exc.HTTPBadRequest(explanation=msg)
-
- #fixed IP address is optional
- #if the fixed IP address is not provided then
- #it will use one of the available IP address from the network
- address = network.get('fixed_ip', None)
- if address is not None and not utils.is_valid_ipv4(address):
- msg = _("Invalid fixed IP address (%s)") % address
- raise exc.HTTPBadRequest(explanation=msg)
- # check if the network id is already present in the list,
- # we don't want duplicate networks to be passed
- # at the boot time
- for id, ip in networks:
- if id == network_uuid:
- expl = _("Duplicate networks (%s) are not allowed")\
- % network_uuid
- raise exc.HTTPBadRequest(explanation=expl)
-
- networks.append((network_uuid, address))
- except KeyError as key:
- expl = _('Bad network format: missing %s') % key
- raise exc.HTTPBadRequest(explanation=expl)
- except TypeError:
- expl = _('Bad networks format')
- raise exc.HTTPBadRequest(explanation=expl)
-
- return networks
-
- def _validate_user_data(self, user_data):
- """Check if the user_data is encoded properly"""
- if not user_data:
- return
- try:
- user_data = base64.b64decode(user_data)
- except TypeError:
- expl = _('Userdata content cannot be decoded')
- raise exc.HTTPBadRequest(explanation=expl)
-
-
-class ServerXMLDeserializer(wsgi.XMLDeserializer):
- """
- Deserializer to handle xml-formatted server create requests.
-
- Handles standard server attributes as well as optional metadata
- and personality attributes
- """
-
- metadata_deserializer = common.MetadataXMLDeserializer()
-
- def create(self, string):
- """Deserialize an xml-formatted server create request"""
- dom = minidom.parseString(string)
- server = self._extract_server(dom)
- return {'body': {'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')
-
- attributes = ["name", "imageId", "flavorId", "adminPass"]
- for attr in attributes:
- if server_node.getAttribute(attr):
- server[attr] = server_node.getAttribute(attr)
-
- metadata_node = self.find_first_child_named(server_node, "metadata")
- server["metadata"] = self.metadata_deserializer.extract_metadata(
- metadata_node)
-
- server["personality"] = self._extract_personality(server_node)
-
- return server
-
- def _extract_personality(self, server_node):
- """Marshal the personality attribute of a parsed request"""
- node = self.find_first_child_named(server_node, "personality")
- personality = []
- if node is not None:
- for file_node in self.find_children_named(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
-
-
-class ServerXMLDeserializerV11(wsgi.MetadataXMLDeserializer):
- """
- Deserializer to handle xml-formatted server create requests.
-
- Handles standard server attributes as well as optional metadata
- and personality attributes
- """
-
- metadata_deserializer = common.MetadataXMLDeserializer()
-
- def action(self, string):
- dom = minidom.parseString(string)
- action_node = dom.childNodes[0]
- action_name = action_node.tagName
-
- action_deserializer = {
- 'createImage': self._action_create_image,
- 'createBackup': self._action_create_backup,
- 'changePassword': self._action_change_password,
- 'reboot': self._action_reboot,
- 'rebuild': self._action_rebuild,
- 'resize': self._action_resize,
- 'confirmResize': self._action_confirm_resize,
- 'revertResize': self._action_revert_resize,
- }.get(action_name, self.default)
-
- action_data = action_deserializer(action_node)
-
- return {'body': {action_name: action_data}}
-
- def _action_create_image(self, node):
- return self._deserialize_image_action(node, ('name',))
-
- def _action_create_backup(self, node):
- attributes = ('name', 'backup_type', 'rotation')
- return self._deserialize_image_action(node, attributes)
-
- def _action_change_password(self, node):
- if not node.hasAttribute("adminPass"):
- raise AttributeError("No adminPass was specified in request")
- return {"adminPass": node.getAttribute("adminPass")}
-
- def _action_reboot(self, node):
- if not node.hasAttribute("type"):
- raise AttributeError("No reboot type was specified in request")
- return {"type": node.getAttribute("type")}
-
- def _action_rebuild(self, node):
- rebuild = {}
- if node.hasAttribute("name"):
- rebuild['name'] = node.getAttribute("name")
-
- metadata_node = self.find_first_child_named(node, "metadata")
- if metadata_node is not None:
- rebuild["metadata"] = self.extract_metadata(metadata_node)
-
- personality = self._extract_personality(node)
- if personality is not None:
- rebuild["personality"] = personality
-
- if not node.hasAttribute("imageRef"):
- raise AttributeError("No imageRef was specified in request")
- rebuild["imageRef"] = node.getAttribute("imageRef")
-
- return rebuild
-
- def _action_resize(self, node):
- if not node.hasAttribute("flavorRef"):
- raise AttributeError("No flavorRef was specified in request")
- return {"flavorRef": node.getAttribute("flavorRef")}
-
- def _action_confirm_resize(self, node):
- return None
-
- def _action_revert_resize(self, node):
- return None
-
- def _deserialize_image_action(self, node, allowed_attributes):
- data = {}
- for attribute in allowed_attributes:
- value = node.getAttribute(attribute)
- if value:
- data[attribute] = value
- metadata_node = self.find_first_child_named(node, 'metadata')
- if metadata_node is not None:
- metadata = self.metadata_deserializer.extract_metadata(
- metadata_node)
- data['metadata'] = metadata
- return data
-
- def create(self, string):
- """Deserialize an xml-formatted server create request"""
- dom = minidom.parseString(string)
- server = self._extract_server(dom)
- return {'body': {'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')
-
- attributes = ["name", "imageRef", "flavorRef", "adminPass",
- "accessIPv4", "accessIPv6"]
- for attr in attributes:
- if server_node.getAttribute(attr):
- server[attr] = server_node.getAttribute(attr)
-
- metadata_node = self.find_first_child_named(server_node, "metadata")
- if metadata_node is not None:
- server["metadata"] = self.extract_metadata(metadata_node)
-
- personality = self._extract_personality(server_node)
- if personality is not None:
- server["personality"] = personality
-
- networks = self._extract_networks(server_node)
- if networks is not None:
- server["networks"] = networks
-
- security_groups = self._extract_security_groups(server_node)
- if security_groups is not None:
- server["security_groups"] = security_groups
-
- return server
-
- def _extract_personality(self, server_node):
- """Marshal the personality attribute of a parsed request"""
- node = self.find_first_child_named(server_node, "personality")
- if node is not None:
- personality = []
- for file_node in self.find_children_named(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
- else:
- return None
-
- def _extract_networks(self, server_node):
- """Marshal the networks attribute of a parsed request"""
- node = self.find_first_child_named(server_node, "networks")
- if node is not None:
- networks = []
- for network_node in self.find_children_named(node,
- "network"):
- item = {}
- if network_node.hasAttribute("uuid"):
- item["uuid"] = network_node.getAttribute("uuid")
- if network_node.hasAttribute("fixed_ip"):
- item["fixed_ip"] = network_node.getAttribute("fixed_ip")
- networks.append(item)
- return networks
- else:
- return None
-
- def _extract_security_groups(self, server_node):
- """Marshal the security_groups attribute of a parsed request"""
- node = self.find_first_child_named(server_node, "security_groups")
- if node is not None:
- security_groups = []
- for sg_node in self.find_children_named(node, "security_group"):
- item = {}
- name_node = self.find_first_child_named(sg_node, "name")
- if name_node:
- item["name"] = self.extract_text(name_node)
- security_groups.append(item)
- return security_groups
- else:
- return None
diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py
index e92ab9bda..4a2d0bcbc 100644
--- a/nova/api/openstack/servers.py
+++ b/nova/api/openstack/servers.py
@@ -1,4 +1,5 @@
# Copyright 2010 OpenStack LLC.
+# Copyright 2011 Piston Cloud Computing, Inc
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
@@ -21,18 +22,21 @@ from novaclient import exceptions as novaclient_exceptions
from lxml import etree
from webob import exc
import webob
+from xml.dom import minidom
from nova import compute
from nova import network
from nova import db
from nova import exception
from nova import flags
+from nova import image
from nova import log as logging
from nova import utils
+from nova import quota
from nova.api.openstack import common
-from nova.api.openstack import create_instance_helper as helper
from nova.api.openstack import ips
from nova.api.openstack import wsgi
+from nova.compute import instance_types
from nova.scheduler import api as scheduler_api
import nova.api.openstack
import nova.api.openstack.views.addresses
@@ -40,6 +44,7 @@ import nova.api.openstack.views.flavors
import nova.api.openstack.views.images
import nova.api.openstack.views.servers
from nova.api.openstack import xmlutil
+from nova.rpc import common as rpc_common
LOG = logging.getLogger('nova.api.openstack.servers')
@@ -73,7 +78,6 @@ class Controller(object):
def __init__(self):
self.compute_api = compute.API()
self.network_api = network.API()
- self.helper = helper.CreateInstanceHelper(self)
def index(self, req):
""" Returns a list of server names and ids for a given user """
@@ -107,11 +111,23 @@ class Controller(object):
def _action_rebuild(self, info, request, instance_id):
raise NotImplementedError()
+ def _get_block_device_mapping(self, data):
+ """Get block_device_mapping from 'server' dictionary.
+ Overidden by volumes controller.
+ """
+ return None
+
def _get_networks_for_instance(self, req, instance):
return ips._get_networks_for_instance(req.environ['nova.context'],
self.network_api,
instance)
+ def _get_block_device_mapping(self, data):
+ """Get block_device_mapping from 'server' dictionary.
+ Overidden by volumes controller.
+ """
+ return None
+
def _get_servers(self, req, is_detail):
"""Returns a list of servers, taking into account any search
options specified.
@@ -163,6 +179,181 @@ class Controller(object):
limited_list = self._limit_items(instance_list, req)
return self._build_list(req, limited_list, is_detail=is_detail)
+ def _handle_quota_error(self, error):
+ """
+ Reraise quota errors as api-specific http exceptions
+ """
+
+ code_mappings = {
+ "OnsetFileLimitExceeded":
+ _("Personality file limit exceeded"),
+ "OnsetFilePathLimitExceeded":
+ _("Personality file path too long"),
+ "OnsetFileContentLimitExceeded":
+ _("Personality file content too long"),
+ "InstanceLimitExceeded":
+ _("Instance quotas have been exceeded")}
+
+ expl = code_mappings.get(error.code)
+ if expl:
+ raise exc.HTTPRequestEntityTooLarge(explanation=expl,
+ headers={'Retry-After': 0})
+ # 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 = ServerXMLDeserializer()
+ 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_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
+ 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:
+ ramdisk_id = None
+
+ 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(FLAGS.password_length)
+
+ 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(FLAGS.password_length)
+ if not isinstance(password, basestring) or password == '':
+ msg = _("Invalid adminPass")
+ raise exc.HTTPBadRequest(explanation=msg)
+ return password
+
+ def _get_requested_networks(self, requested_networks):
+ """
+ Create a list of requested networks from the networks attribute
+ """
+ networks = []
+ for network in requested_networks:
+ try:
+ network_uuid = network['uuid']
+
+ if not utils.is_uuid_like(network_uuid):
+ msg = _("Bad networks format: network uuid is not in"
+ " proper format (%s)") % network_uuid
+ raise exc.HTTPBadRequest(explanation=msg)
+
+ #fixed IP address is optional
+ #if the fixed IP address is not provided then
+ #it will use one of the available IP address from the network
+ address = network.get('fixed_ip', None)
+ if address is not None and not utils.is_valid_ipv4(address):
+ msg = _("Invalid fixed IP address (%s)") % address
+ raise exc.HTTPBadRequest(explanation=msg)
+ # check if the network id is already present in the list,
+ # we don't want duplicate networks to be passed
+ # at the boot time
+ for id, ip in networks:
+ if id == network_uuid:
+ expl = _("Duplicate networks (%s) are not allowed")\
+ % network_uuid
+ raise exc.HTTPBadRequest(explanation=expl)
+
+ networks.append((network_uuid, address))
+ except KeyError as key:
+ expl = _('Bad network format: missing %s') % key
+ raise exc.HTTPBadRequest(explanation=expl)
+ except TypeError:
+ expl = _('Bad networks format')
+ raise exc.HTTPBadRequest(explanation=expl)
+
+ return networks
+
+ def _validate_user_data(self, user_data):
+ """Check if the user_data is encoded properly"""
+ if not user_data:
+ return
+ try:
+ user_data = base64.b64decode(user_data)
+ except TypeError:
+ expl = _('Userdata content cannot be decoded')
+ raise exc.HTTPBadRequest(explanation=expl)
+
@novaclient_exception_converter
@scheduler_api.redirect_handler
def show(self, req, id):
@@ -180,22 +371,172 @@ class Controller(object):
def create(self, req, body):
""" Creates a new server for a given user """
- if 'server' in body:
- body['server']['key_name'] = self._get_key_name(req, body)
- extra_values = None
- extra_values, instances = self.helper.create_instance(
- req, body, self.compute_api.create)
+ if not body:
+ raise exc.HTTPUnprocessableEntity()
+
+ if not 'server' in body:
+ raise exc.HTTPUnprocessableEntity()
+
+ body['server']['key_name'] = self._get_key_name(req, body)
+
+ context = req.environ['nova.context']
+ server_dict = body['server']
+ password = self._get_server_admin_password(server_dict)
+
+ if not 'name' in server_dict:
+ msg = _("Server name is not defined")
+ raise exc.HTTPBadRequest(explanation=msg)
+
+ name = server_dict['name']
+ self._validate_server_name(name)
+ name = name.strip()
- # We can only return 1 instance via the API, if we happen to
- # build more than one... instances is a list, so we'll just
- # use the first one..
- inst = instances[0]
- for key in ['instance_type', 'image_ref']:
- inst[key] = extra_values[key]
+ image_href = self._image_ref_from_req_data(body)
+ # If the image href was generated by nova api, strip image_href
+ # down to an id and use the default glance connection params
- server = self._build_view(req, inst, is_detail=True)
- server['server']['adminPass'] = extra_values['password']
+ if str(image_href).startswith(req.application_url):
+ image_href = image_href.split('/').pop()
+ try:
+ image_service, image_id = image.get_image_service(context,
+ 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 Exception, e:
+ msg = _("Cannot find requested image %(image_href)s: %(e)s" %
+ locals())
+ raise exc.HTTPBadRequest(explanation=msg)
+
+ personality = server_dict.get('personality')
+ config_drive = server_dict.get('config_drive')
+
+ injected_files = []
+ if personality:
+ injected_files = self._get_injected_files(personality)
+
+ sg_names = []
+ security_groups = server_dict.get('security_groups')
+ if security_groups is not None:
+ sg_names = [sg['name'] for sg in security_groups if sg.get('name')]
+ if not sg_names:
+ sg_names.append('default')
+
+ sg_names = list(set(sg_names))
+
+ requested_networks = server_dict.get('networks')
+ if requested_networks is not None:
+ requested_networks = self._get_requested_networks(
+ requested_networks)
+
+ try:
+ flavor_id = self._flavor_id_from_req_data(body)
+ except ValueError as error:
+ msg = _("Invalid flavorRef provided.")
+ raise exc.HTTPBadRequest(explanation=msg)
+
+ zone_blob = server_dict.get('blob')
+
+ # optional openstack extensions:
+ key_name = server_dict.get('key_name')
+ user_data = server_dict.get('user_data')
+ self._validate_user_data(user_data)
+
+ availability_zone = server_dict.get('availability_zone')
+ name = server_dict['name']
+ self._validate_server_name(name)
+ name = name.strip()
+
+ block_device_mapping = self._get_block_device_mapping(server_dict)
+
+ # Only allow admins to specify their own reservation_ids
+ # This is really meant to allow zones to work.
+ reservation_id = server_dict.get('reservation_id')
+ if all([reservation_id is not None,
+ reservation_id != '',
+ not context.is_admin]):
+ reservation_id = None
+
+ ret_resv_id = server_dict.get('return_reservation_id', False)
+
+ min_count = server_dict.get('min_count')
+ max_count = server_dict.get('max_count')
+ # min_count and max_count are optional. If they exist, they come
+ # in as strings. We want to default 'min_count' to 1, and default
+ # 'max_count' to be 'min_count'.
+ min_count = int(min_count) if min_count else 1
+ max_count = int(max_count) if max_count else min_count
+ if min_count > max_count:
+ min_count = max_count
+
+ try:
+ inst_type = \
+ instance_types.get_instance_type_by_flavor_id(flavor_id)
+
+ (instances, resv_id) = self.compute_api.create(context,
+ inst_type,
+ image_id,
+ kernel_id=kernel_id,
+ ramdisk_id=ramdisk_id,
+ display_name=name,
+ display_description=name,
+ key_name=key_name,
+ metadata=server_dict.get('metadata', {}),
+ access_ip_v4=server_dict.get('accessIPv4'),
+ access_ip_v6=server_dict.get('accessIPv6'),
+ injected_files=injected_files,
+ admin_password=password,
+ zone_blob=zone_blob,
+ reservation_id=reservation_id,
+ min_count=min_count,
+ max_count=max_count,
+ requested_networks=requested_networks,
+ security_group=sg_names,
+ user_data=user_data,
+ availability_zone=availability_zone,
+ config_drive=config_drive,
+ block_device_mapping=block_device_mapping,
+ wait_for_instances=not ret_resv_id)
+ except quota.QuotaError as error:
+ self._handle_quota_error(error)
+ except exception.InstanceTypeMemoryTooSmall as error:
+ raise exc.HTTPBadRequest(explanation=unicode(error))
+ except exception.InstanceTypeDiskTooSmall as error:
+ raise exc.HTTPBadRequest(explanation=unicode(error))
+ except exception.ImageNotFound as error:
+ msg = _("Can not find requested image")
+ raise exc.HTTPBadRequest(explanation=msg)
+ except exception.FlavorNotFound as error:
+ msg = _("Invalid flavorRef provided.")
+ raise exc.HTTPBadRequest(explanation=msg)
+ except exception.KeypairNotFound as error:
+ msg = _("Invalid key_name provided.")
+ raise exc.HTTPBadRequest(explanation=msg)
+ except exception.SecurityGroupNotFound as error:
+ raise exc.HTTPBadRequest(explanation=unicode(error))
+ except rpc_common.RemoteError as err:
+ msg = "%(err_type)s: %(err_msg)s" % \
+ {'err_type': err.exc_type, 'err_msg': err.value}
+ raise exc.HTTPBadRequest(explanation=msg)
+ # Let the caller deal with unhandled exceptions.
+
+ # If the caller wanted a reservation_id, return it
+ if ret_resv_id:
+ return {'reservation_id': resv_id}
+
+ # Instances is a list
+ instance = instances[0]
+ if not instance.get('_is_precooked', False):
+ instance['instance_type'] = inst_type
+ instance['image_ref'] = image_href
+
+ server = self._build_view(req, instance, is_detail=True)
+ if '_is_precooked' in server['server']:
+ del server['server']['_is_precooked']
+ else:
+ server['server']['adminPass'] = password
return server
def _delete(self, context, id):
@@ -218,7 +559,7 @@ class Controller(object):
if 'name' in body['server']:
name = body['server']['name']
- self.helper._validate_server_name(name)
+ self._validate_server_name(name)
update_dict['display_name'] = name.strip()
if 'accessIPv4' in body['server']:
@@ -290,17 +631,17 @@ class Controller(object):
except KeyError as missing_key:
msg = _("createBackup entity requires %s attribute") % missing_key
- raise webob.exc.HTTPBadRequest(explanation=msg)
+ raise exc.HTTPBadRequest(explanation=msg)
except TypeError:
msg = _("Malformed createBackup entity")
- raise webob.exc.HTTPBadRequest(explanation=msg)
+ raise exc.HTTPBadRequest(explanation=msg)
try:
rotation = int(rotation)
except ValueError:
msg = _("createBackup attribute 'rotation' must be an integer")
- raise webob.exc.HTTPBadRequest(explanation=msg)
+ raise exc.HTTPBadRequest(explanation=msg)
# preserve link to server in image properties
server_ref = os.path.join(req.application_url,
@@ -315,7 +656,7 @@ class Controller(object):
props.update(metadata)
except ValueError:
msg = _("Invalid metadata")
- raise webob.exc.HTTPBadRequest(explanation=msg)
+ raise exc.HTTPBadRequest(explanation=msg)
image = self.compute_api.backup(context,
instance_id,
@@ -696,7 +1037,7 @@ class ControllerV10(Controller):
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)
+ return self._get_server_admin_password_old_style(server)
def _get_server_search_options(self):
"""Return server search options allowed by non-admin"""
@@ -884,11 +1225,11 @@ class ControllerV11(Controller):
except KeyError:
msg = _("createImage entity requires name attribute")
- raise webob.exc.HTTPBadRequest(explanation=msg)
+ raise exc.HTTPBadRequest(explanation=msg)
except TypeError:
msg = _("Malformed createImage entity")
- raise webob.exc.HTTPBadRequest(explanation=msg)
+ raise exc.HTTPBadRequest(explanation=msg)
# preserve link to server in image properties
server_ref = os.path.join(req.application_url,
@@ -903,7 +1244,7 @@ class ControllerV11(Controller):
props.update(metadata)
except ValueError:
msg = _("Invalid metadata")
- raise webob.exc.HTTPBadRequest(explanation=msg)
+ raise exc.HTTPBadRequest(explanation=msg)
image = self.compute_api.snapshot(context,
instance_id,
@@ -923,7 +1264,7 @@ class ControllerV11(Controller):
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)
+ return self._get_server_admin_password_new_style(server)
def _get_server_search_options(self):
"""Return server search options allowed by non-admin"""
@@ -1068,6 +1409,227 @@ class ServerXMLSerializer(wsgi.XMLDictSerializer):
return self._to_xml(server)
+class ServerXMLDeserializer(wsgi.XMLDeserializer):
+ """
+ Deserializer to handle xml-formatted server create requests.
+
+ Handles standard server attributes as well as optional metadata
+ and personality attributes
+ """
+
+ metadata_deserializer = common.MetadataXMLDeserializer()
+
+ def create(self, string):
+ """Deserialize an xml-formatted server create request"""
+ dom = minidom.parseString(string)
+ server = self._extract_server(dom)
+ return {'body': {'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')
+
+ attributes = ["name", "imageId", "flavorId", "adminPass"]
+ for attr in attributes:
+ if server_node.getAttribute(attr):
+ server[attr] = server_node.getAttribute(attr)
+
+ metadata_node = self.find_first_child_named(server_node, "metadata")
+ server["metadata"] = self.metadata_deserializer.extract_metadata(
+ metadata_node)
+
+ server["personality"] = self._extract_personality(server_node)
+
+ return server
+
+ def _extract_personality(self, server_node):
+ """Marshal the personality attribute of a parsed request"""
+ node = self.find_first_child_named(server_node, "personality")
+ personality = []
+ if node is not None:
+ for file_node in self.find_children_named(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
+
+
+class ServerXMLDeserializerV11(wsgi.MetadataXMLDeserializer):
+ """
+ Deserializer to handle xml-formatted server create requests.
+
+ Handles standard server attributes as well as optional metadata
+ and personality attributes
+ """
+
+ metadata_deserializer = common.MetadataXMLDeserializer()
+
+ def action(self, string):
+ dom = minidom.parseString(string)
+ action_node = dom.childNodes[0]
+ action_name = action_node.tagName
+
+ action_deserializer = {
+ 'createImage': self._action_create_image,
+ 'createBackup': self._action_create_backup,
+ 'changePassword': self._action_change_password,
+ 'reboot': self._action_reboot,
+ 'rebuild': self._action_rebuild,
+ 'resize': self._action_resize,
+ 'confirmResize': self._action_confirm_resize,
+ 'revertResize': self._action_revert_resize,
+ }.get(action_name, self.default)
+
+ action_data = action_deserializer(action_node)
+
+ return {'body': {action_name: action_data}}
+
+ def _action_create_image(self, node):
+ return self._deserialize_image_action(node, ('name',))
+
+ def _action_create_backup(self, node):
+ attributes = ('name', 'backup_type', 'rotation')
+ return self._deserialize_image_action(node, attributes)
+
+ def _action_change_password(self, node):
+ if not node.hasAttribute("adminPass"):
+ raise AttributeError("No adminPass was specified in request")
+ return {"adminPass": node.getAttribute("adminPass")}
+
+ def _action_reboot(self, node):
+ if not node.hasAttribute("type"):
+ raise AttributeError("No reboot type was specified in request")
+ return {"type": node.getAttribute("type")}
+
+ def _action_rebuild(self, node):
+ rebuild = {}
+ if node.hasAttribute("name"):
+ rebuild['name'] = node.getAttribute("name")
+
+ metadata_node = self.find_first_child_named(node, "metadata")
+ if metadata_node is not None:
+ rebuild["metadata"] = self.extract_metadata(metadata_node)
+
+ personality = self._extract_personality(node)
+ if personality is not None:
+ rebuild["personality"] = personality
+
+ if not node.hasAttribute("imageRef"):
+ raise AttributeError("No imageRef was specified in request")
+ rebuild["imageRef"] = node.getAttribute("imageRef")
+
+ return rebuild
+
+ def _action_resize(self, node):
+ if not node.hasAttribute("flavorRef"):
+ raise AttributeError("No flavorRef was specified in request")
+ return {"flavorRef": node.getAttribute("flavorRef")}
+
+ def _action_confirm_resize(self, node):
+ return None
+
+ def _action_revert_resize(self, node):
+ return None
+
+ def _deserialize_image_action(self, node, allowed_attributes):
+ data = {}
+ for attribute in allowed_attributes:
+ value = node.getAttribute(attribute)
+ if value:
+ data[attribute] = value
+ metadata_node = self.find_first_child_named(node, 'metadata')
+ if metadata_node is not None:
+ metadata = self.metadata_deserializer.extract_metadata(
+ metadata_node)
+ data['metadata'] = metadata
+ return data
+
+ def create(self, string):
+ """Deserialize an xml-formatted server create request"""
+ dom = minidom.parseString(string)
+ server = self._extract_server(dom)
+ return {'body': {'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')
+
+ attributes = ["name", "imageRef", "flavorRef", "adminPass",
+ "accessIPv4", "accessIPv6"]
+ for attr in attributes:
+ if server_node.getAttribute(attr):
+ server[attr] = server_node.getAttribute(attr)
+
+ metadata_node = self.find_first_child_named(server_node, "metadata")
+ if metadata_node is not None:
+ server["metadata"] = self.extract_metadata(metadata_node)
+
+ personality = self._extract_personality(server_node)
+ if personality is not None:
+ server["personality"] = personality
+
+ networks = self._extract_networks(server_node)
+ if networks is not None:
+ server["networks"] = networks
+
+ security_groups = self._extract_security_groups(server_node)
+ if security_groups is not None:
+ server["security_groups"] = security_groups
+
+ return server
+
+ def _extract_personality(self, server_node):
+ """Marshal the personality attribute of a parsed request"""
+ node = self.find_first_child_named(server_node, "personality")
+ if node is not None:
+ personality = []
+ for file_node in self.find_children_named(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
+ else:
+ return None
+
+ def _extract_networks(self, server_node):
+ """Marshal the networks attribute of a parsed request"""
+ node = self.find_first_child_named(server_node, "networks")
+ if node is not None:
+ networks = []
+ for network_node in self.find_children_named(node,
+ "network"):
+ item = {}
+ if network_node.hasAttribute("uuid"):
+ item["uuid"] = network_node.getAttribute("uuid")
+ if network_node.hasAttribute("fixed_ip"):
+ item["fixed_ip"] = network_node.getAttribute("fixed_ip")
+ networks.append(item)
+ return networks
+ else:
+ return None
+
+ def _extract_security_groups(self, server_node):
+ """Marshal the security_groups attribute of a parsed request"""
+ node = self.find_first_child_named(server_node, "security_groups")
+ if node is not None:
+ security_groups = []
+ for sg_node in self.find_children_named(node, "security_group"):
+ item = {}
+ name_node = self.find_first_child_named(sg_node, "name")
+ if name_node:
+ item["name"] = self.extract_text(name_node)
+ security_groups.append(item)
+ return security_groups
+ else:
+ return None
+
+
def create_resource(version='1.0'):
controller = {
'1.0': ControllerV10,
@@ -1107,8 +1669,8 @@ def create_resource(version='1.0'):
}
xml_deserializer = {
- '1.0': helper.ServerXMLDeserializer(),
- '1.1': helper.ServerXMLDeserializerV11(),
+ '1.0': ServerXMLDeserializer(),
+ '1.1': ServerXMLDeserializerV11(),
}[version]
body_deserializers = {
diff --git a/nova/api/openstack/zones.py b/nova/api/openstack/zones.py
index 998498e82..95e9eaa51 100644
--- a/nova/api/openstack/zones.py
+++ b/nova/api/openstack/zones.py
@@ -25,8 +25,8 @@ 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 servers
from nova.api.openstack import wsgi
@@ -67,7 +67,6 @@ class Controller(object):
def __init__(self):
self.compute_api = compute.API()
- self.helper = helper.CreateInstanceHelper(self)
def index(self, req):
"""Return all zones in brief"""
@@ -120,18 +119,6 @@ class Controller(object):
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
- extra_values, result = self.helper.create_instance(req, body,
- self.compute_api.create_all_at_once)
-
- 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
@@ -155,37 +142,8 @@ 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(Controller):
- """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]()
-
metadata = {
"attributes": {
"zone": ["id", "api_url", "name", "capabilities"],
@@ -199,8 +157,8 @@ def create_resource(version):
serializer = wsgi.ResponseSerializer(body_serializers)
body_deserializers = {
- 'application/xml': helper.ServerXMLDeserializer(),
+ 'application/xml': servers.ServerXMLDeserializer(),
}
deserializer = wsgi.RequestDeserializer(body_deserializers)
- return wsgi.Resource(controller, deserializer, serializer)
+ return wsgi.Resource(Controller(), deserializer, serializer)