summaryrefslogtreecommitdiffstats
path: root/nova/api
diff options
context:
space:
mode:
authorChris Behrens <cbehrens@codestud.com>2011-09-21 09:00:18 +0000
committerChris Behrens <cbehrens@codestud.com>2011-09-21 09:00:18 +0000
commita63ca427e75c73611dd129bc7b5a625f6c06fa44 (patch)
treed80bef65fe63bb45fbf201f0c0475d3d699fa045 /nova/api
parent4d0bb8730a076b44d0a37fd0770c743b834e5751 (diff)
parentd9752d46554ffa87360bfd740177b40871cfbea6 (diff)
downloadnova-a63ca427e75c73611dd129bc7b5a625f6c06fa44.tar.gz
nova-a63ca427e75c73611dd129bc7b5a625f6c06fa44.tar.xz
nova-a63ca427e75c73611dd129bc7b5a625f6c06fa44.zip
Merged trunk
Diffstat (limited to 'nova/api')
-rw-r--r--nova/api/auth.py31
-rw-r--r--nova/api/ec2/__init__.py51
-rw-r--r--nova/api/ec2/cloud.py68
-rw-r--r--nova/api/ec2/ec2utils.py2
-rw-r--r--nova/api/openstack/common.py93
-rw-r--r--nova/api/openstack/contrib/deferred_delete.py76
-rw-r--r--nova/api/openstack/contrib/flavorextradata.py46
-rw-r--r--nova/api/openstack/contrib/rescue.py13
-rw-r--r--nova/api/openstack/contrib/virtual_interfaces.py16
-rw-r--r--nova/api/openstack/contrib/volumes.py3
-rw-r--r--nova/api/openstack/create_instance_helper.py7
-rw-r--r--nova/api/openstack/flavors.py69
-rw-r--r--nova/api/openstack/image_metadata.py58
-rw-r--r--nova/api/openstack/images.py137
-rw-r--r--nova/api/openstack/ips.py59
-rw-r--r--nova/api/openstack/limits.py78
-rw-r--r--nova/api/openstack/schemas/v1.1/addresses.rng14
-rw-r--r--nova/api/openstack/schemas/v1.1/flavor.rng14
-rw-r--r--nova/api/openstack/schemas/v1.1/flavors.rng6
-rw-r--r--nova/api/openstack/schemas/v1.1/flavors_index.rng12
-rw-r--r--nova/api/openstack/schemas/v1.1/image.rng30
-rw-r--r--nova/api/openstack/schemas/v1.1/images.rng6
-rw-r--r--nova/api/openstack/schemas/v1.1/images_index.rng12
-rw-r--r--nova/api/openstack/schemas/v1.1/limits.rng28
-rw-r--r--nova/api/openstack/schemas/v1.1/metadata.rng9
-rw-r--r--nova/api/openstack/schemas/v1.1/server.rng6
-rw-r--r--nova/api/openstack/servers.py267
-rw-r--r--nova/api/openstack/versions.py246
-rw-r--r--nova/api/openstack/views/flavors.py3
-rw-r--r--nova/api/openstack/views/images.py41
-rw-r--r--nova/api/openstack/views/servers.py7
-rw-r--r--nova/api/openstack/views/versions.py2
-rw-r--r--nova/api/openstack/wsgi.py50
33 files changed, 849 insertions, 711 deletions
diff --git a/nova/api/auth.py b/nova/api/auth.py
index cd0d38b3f..a94f28739 100644
--- a/nova/api/auth.py
+++ b/nova/api/auth.py
@@ -43,34 +43,3 @@ class InjectContext(wsgi.Middleware):
def __call__(self, req):
req.environ['nova.context'] = self.context
return self.application
-
-
-class KeystoneContext(wsgi.Middleware):
- """Make a request context from keystone headers"""
-
- @webob.dec.wsgify(RequestClass=wsgi.Request)
- def __call__(self, req):
- try:
- user_id = req.headers['X_USER']
- except KeyError:
- return webob.exc.HTTPUnauthorized()
- # get the roles
- roles = [r.strip() for r in req.headers.get('X_ROLE', '').split(',')]
- project_id = req.headers['X_TENANT']
- # Get the auth token
- auth_token = req.headers.get('X_AUTH_TOKEN',
- req.headers.get('X_STORAGE_TOKEN'))
-
- # Build a context, including the auth_token...
- remote_address = getattr(req, 'remote_address', '127.0.0.1')
- remote_address = req.remote_addr
- if FLAGS.use_forwarded_for:
- remote_address = req.headers.get('X-Forwarded-For', remote_address)
- ctx = context.RequestContext(user_id,
- project_id,
- roles=roles,
- auth_token=auth_token,
- remote_address=remote_address)
-
- req.environ['nova.context'] = ctx
- return self.application
diff --git a/nova/api/ec2/__init__.py b/nova/api/ec2/__init__.py
index 144aa3811..8dcb44bba 100644
--- a/nova/api/ec2/__init__.py
+++ b/nova/api/ec2/__init__.py
@@ -46,9 +46,6 @@ flags.DEFINE_integer('lockout_minutes', 15,
'Number of minutes to lockout if triggered.')
flags.DEFINE_integer('lockout_window', 15,
'Number of minutes for lockout window.')
-flags.DEFINE_string('keystone_ec2_url',
- 'http://localhost:5000/v2.0/ec2tokens',
- 'URL to get token from ec2 request.')
flags.DECLARE('use_forwarded_for', 'nova.api.auth')
@@ -142,54 +139,6 @@ class Lockout(wsgi.Middleware):
return res
-class ToToken(wsgi.Middleware):
- """Authenticate an EC2 request with keystone and convert to token."""
-
- @webob.dec.wsgify(RequestClass=wsgi.Request)
- def __call__(self, req):
- # Read request signature and access id.
- try:
- signature = req.params['Signature']
- access = req.params['AWSAccessKeyId']
- except KeyError:
- raise webob.exc.HTTPBadRequest()
-
- # Make a copy of args for authentication and signature verification.
- auth_params = dict(req.params)
- # Not part of authentication args
- auth_params.pop('Signature')
-
- # Authenticate the request.
- creds = {'ec2Credentials': {'access': access,
- 'signature': signature,
- 'host': req.host,
- 'verb': req.method,
- 'path': req.path,
- 'params': auth_params,
- }}
- creds_json = utils.dumps(creds)
- headers = {'Content-Type': 'application/json'}
- o = urlparse(FLAGS.keystone_ec2_url)
- if o.scheme == "http":
- conn = httplib.HTTPConnection(o.netloc)
- else:
- conn = httplib.HTTPSConnection(o.netloc)
- conn.request('POST', o.path, body=creds_json, headers=headers)
- response = conn.getresponse().read()
- conn.close()
-
- # NOTE(vish): We could save a call to keystone by
- # having keystone return token, tenant,
- # user, and roles from this call.
- result = utils.loads(response)
- # TODO(vish): check for errors
-
- token_id = result['auth']['token']['id']
- # Authenticated!
- req.headers['X-Auth-Token'] = token_id
- return self.application
-
-
class NoAuth(wsgi.Middleware):
"""Add user:project as 'nova.context' to WSGI environ."""
diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py
index 049ca6f93..68d39042f 100644
--- a/nova/api/ec2/cloud.py
+++ b/nova/api/ec2/cloud.py
@@ -89,6 +89,7 @@ _STATE_DESCRIPTION_MAP = {
vm_states.BUILDING: 'pending',
vm_states.REBUILDING: 'pending',
vm_states.DELETED: 'terminated',
+ vm_states.SOFT_DELETE: 'terminated',
vm_states.STOPPED: 'stopped',
vm_states.MIGRATING: 'migrate',
vm_states.RESIZING: 'resize',
@@ -272,11 +273,23 @@ class CloudController(object):
mappings = {}
mappings['ami'] = block_device.strip_dev(root_device_name)
mappings['root'] = root_device_name
-
- # 'ephemeralN' and 'swap'
+ default_local_device = instance_ref.get('default_local_device')
+ if default_local_device:
+ mappings['ephemeral0'] = default_local_device
+ default_swap_device = instance_ref.get('default_swap_device')
+ if default_swap_device:
+ mappings['swap'] = default_swap_device
+ ebs_devices = []
+
+ # 'ephemeralN', 'swap' and ebs
for bdm in db.block_device_mapping_get_all_by_instance(
ctxt, instance_ref['id']):
- if (bdm['volume_id'] or bdm['snapshot_id'] or bdm['no_device']):
+ if bdm['no_device']:
+ continue
+
+ # ebs volume case
+ if (bdm['volume_id'] or bdm['snapshot_id']):
+ ebs_devices.append(bdm['device_name'])
continue
virtual_name = bdm['virtual_name']
@@ -286,6 +299,16 @@ class CloudController(object):
if block_device.is_swap_or_ephemeral(virtual_name):
mappings[virtual_name] = bdm['device_name']
+ # NOTE(yamahata): I'm not sure how ebs device should be numbered.
+ # Right now sort by device name for deterministic
+ # result.
+ if ebs_devices:
+ nebs = 0
+ ebs_devices.sort()
+ for ebs in ebs_devices:
+ mappings['ebs%d' % nebs] = ebs
+ nebs += 1
+
return mappings
def get_metadata(self, address):
@@ -304,11 +327,6 @@ class CloudController(object):
instance_ref = db.instance_get(ctxt, instance_ref[0]['id'])
mpi = self._get_mpi_data(ctxt, instance_ref['project_id'])
- if instance_ref['key_name']:
- keys = {'0': {'_name': instance_ref['key_name'],
- 'openssh-key': instance_ref['key_data']}}
- else:
- keys = ''
hostname = instance_ref['hostname']
host = instance_ref['host']
availability_zone = self._get_availability_zone_by_host(ctxt, host)
@@ -336,11 +354,16 @@ class CloudController(object):
'placement': {'availability-zone': availability_zone},
'public-hostname': hostname,
'public-ipv4': floating_ip or '',
- 'public-keys': keys,
'reservation-id': instance_ref['reservation_id'],
'security-groups': security_groups,
'mpi': mpi}}
+ # public-keys should be in meta-data only if user specified one
+ if instance_ref['key_name']:
+ data['meta-data']['public-keys'] = {
+ '0': {'_name': instance_ref['key_name'],
+ 'openssh-key': instance_ref['key_data']}}
+
for image_type in ['kernel', 'ramdisk']:
if instance_ref.get('%s_id' % image_type):
ec2_id = self.image_ec2_id(instance_ref['%s_id' % image_type],
@@ -572,18 +595,31 @@ class CloudController(object):
g['ipPermissions'] = []
for rule in group.rules:
r = {}
- r['ipProtocol'] = rule.protocol
- r['fromPort'] = rule.from_port
- r['toPort'] = rule.to_port
r['groups'] = []
r['ipRanges'] = []
if rule.group_id:
source_group = db.security_group_get(context, rule.group_id)
r['groups'] += [{'groupName': source_group.name,
'userId': source_group.project_id}]
+ if rule.protocol:
+ r['ipProtocol'] = rule.protocol
+ r['fromPort'] = rule.from_port
+ r['toPort'] = rule.to_port
+ g['ipPermissions'] += [dict(r)]
+ else:
+ for protocol, min_port, max_port in (('icmp', -1, -1),
+ ('tcp', 1, 65535),
+ ('udp', 1, 65536)):
+ r['ipProtocol'] = protocol
+ r['fromPort'] = min_port
+ r['toPort'] = max_port
+ g['ipPermissions'] += [dict(r)]
else:
+ r['ipProtocol'] = rule.protocol
+ r['fromPort'] = rule.from_port
+ r['toPort'] = rule.to_port
r['ipRanges'] += [{'cidrIp': rule.cidr}]
- g['ipPermissions'] += [r]
+ g['ipPermissions'] += [r]
return g
def _rule_args_to_dict(self, context, kwargs):
@@ -1200,8 +1236,10 @@ class CloudController(object):
instances.append(instance)
else:
try:
+ # always filter out deleted instances
+ search_opts['deleted'] = False
instances = self.compute_api.get_all(context,
- search_opts=search_opts)
+ search_opts=search_opts)
except exception.NotFound:
instances = []
for instance in instances:
@@ -1465,7 +1503,7 @@ class CloudController(object):
return image
def _format_image(self, image):
- """Convert from format defined by BaseImageService to S3 format."""
+ """Convert from format defined by GlanceImageService to S3 format."""
i = {}
image_type = self._image_type(image.get('container_format'))
ec2_id = self.image_ec2_id(image.get('id'), image_type)
diff --git a/nova/api/ec2/ec2utils.py b/nova/api/ec2/ec2utils.py
index bcdf2ba78..aac8f9a1e 100644
--- a/nova/api/ec2/ec2utils.py
+++ b/nova/api/ec2/ec2utils.py
@@ -116,7 +116,7 @@ def dict_from_dotted_str(items):
args = {}
for key, value in items:
parts = key.split(".")
- key = camelcase_to_underscore(parts[0])
+ key = str(camelcase_to_underscore(parts[0]))
if isinstance(value, str) or isinstance(value, unicode):
# NOTE(vish): Automatically convert strings back
# into their respective values
diff --git a/nova/api/openstack/common.py b/nova/api/openstack/common.py
index d743a66ef..3ef9bdee5 100644
--- a/nova/api/openstack/common.py
+++ b/nova/api/openstack/common.py
@@ -16,6 +16,7 @@
# under the License.
import functools
+from lxml import etree
import re
import urlparse
from xml.dom import minidom
@@ -27,6 +28,7 @@ from nova import flags
from nova import log as logging
from nova import quota
from nova.api.openstack import wsgi
+from nova.api.openstack import xmlutil
from nova.compute import vm_states
from nova.compute import task_states
@@ -76,6 +78,9 @@ _STATE_MAP = {
vm_states.DELETED: {
'default': 'DELETED',
},
+ vm_states.SOFT_DELETE: {
+ 'default': 'DELETED',
+ },
}
@@ -185,30 +190,16 @@ def limited_by_marker(items, request, max_limit=FLAGS.osapi_max_limit):
def get_id_from_href(href):
- """Return the id portion of a url as an int.
+ """Return the id or uuid portion of a url.
Given: 'http://www.foo.com/bar/123?q=4'
- Returns: 123
+ Returns: '123'
- In order to support local hrefs, the href argument can be just an id:
- Given: '123'
- Returns: 123
+ Given: 'http://www.foo.com/bar/abc123?q=4'
+ Returns: 'abc123'
"""
- LOG.debug(_("Attempting to treat %(href)s as an integer ID.") % locals())
-
- try:
- return int(href)
- except ValueError:
- pass
-
- LOG.debug(_("Attempting to treat %(href)s as a URL.") % locals())
-
- try:
- return int(urlparse.urlsplit(href).path.split('/')[-1])
- except ValueError as error:
- LOG.debug(_("Failed to parse ID from %(href)s: %(error)s") % locals())
- raise
+ return urlparse.urlsplit("%s" % href).path.split('/')[-1]
def remove_version_from_href(href):
@@ -308,54 +299,48 @@ class MetadataHeadersSerializer(wsgi.ResponseHeadersSerializer):
class MetadataXMLSerializer(wsgi.XMLDictSerializer):
+
+ NSMAP = {None: xmlutil.XMLNS_V11}
+
def __init__(self, xmlns=wsgi.XMLNS_V11):
super(MetadataXMLSerializer, self).__init__(xmlns=xmlns)
- def _meta_item_to_xml(self, doc, key, value):
- node = doc.createElement('meta')
- doc.appendChild(node)
- node.setAttribute('key', '%s' % key)
- text = doc.createTextNode('%s' % 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)
- xml_doc.appendChild(container_node)
- self._add_xmlns(container_node)
- return xml_doc.toxml('UTF-8')
+ def populate_metadata(self, metadata_elem, meta_dict):
+ for (key, value) in meta_dict.items():
+ elem = etree.SubElement(metadata_elem, 'meta')
+ elem.set('key', str(key))
+ elem.text = value
+
+ def _populate_meta_item(self, meta_elem, meta_item_dict):
+ """Populate a meta xml element from a dict."""
+ (key, value) = meta_item_dict.items()[0]
+ meta_elem.set('key', str(key))
+ meta_elem.text = value
def index(self, metadata_dict):
- return self._meta_list_to_xml_string(metadata_dict)
+ metadata = etree.Element('metadata', nsmap=self.NSMAP)
+ self.populate_metadata(metadata, metadata_dict.get('metadata', {}))
+ return self._to_xml(metadata)
def create(self, metadata_dict):
- return self._meta_list_to_xml_string(metadata_dict)
+ metadata = etree.Element('metadata', nsmap=self.NSMAP)
+ self.populate_metadata(metadata, metadata_dict.get('metadata', {}))
+ return self._to_xml(metadata)
def update_all(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)
- xml_doc.appendChild(item_node)
- self._add_xmlns(item_node)
- return xml_doc.toxml('UTF-8')
+ metadata = etree.Element('metadata', nsmap=self.NSMAP)
+ self.populate_metadata(metadata, metadata_dict.get('metadata', {}))
+ return self._to_xml(metadata)
def show(self, meta_item_dict):
- return self._meta_item_to_xml_string(meta_item_dict['meta'])
+ meta = etree.Element('meta', nsmap=self.NSMAP)
+ self._populate_meta_item(meta, meta_item_dict['meta'])
+ return self._to_xml(meta)
def update(self, meta_item_dict):
- return self._meta_item_to_xml_string(meta_item_dict['meta'])
+ meta = etree.Element('meta', nsmap=self.NSMAP)
+ self._populate_meta_item(meta, meta_item_dict['meta'])
+ return self._to_xml(meta)
def default(self, *args, **kwargs):
return ''
diff --git a/nova/api/openstack/contrib/deferred_delete.py b/nova/api/openstack/contrib/deferred_delete.py
new file mode 100644
index 000000000..13ee5511e
--- /dev/null
+++ b/nova/api/openstack/contrib/deferred_delete.py
@@ -0,0 +1,76 @@
+# 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 deferred instance delete extension."""
+
+import webob
+from webob import exc
+
+from nova import compute
+from nova import exception
+from nova import log as logging
+from nova.api.openstack import common
+from nova.api.openstack import extensions
+from nova.api.openstack import faults
+from nova.api.openstack import servers
+
+
+LOG = logging.getLogger("nova.api.contrib.deferred-delete")
+
+
+class Deferred_delete(extensions.ExtensionDescriptor):
+ def __init__(self):
+ super(Deferred_delete, self).__init__()
+ self.compute_api = compute.API()
+
+ def _restore(self, input_dict, req, instance_id):
+ """Restore a previously deleted instance."""
+
+ context = req.environ["nova.context"]
+ self.compute_api.restore(context, instance_id)
+ return webob.Response(status_int=202)
+
+ def _force_delete(self, input_dict, req, instance_id):
+ """Force delete of instance before deferred cleanup."""
+
+ context = req.environ["nova.context"]
+ self.compute_api.force_delete(context, instance_id)
+ return webob.Response(status_int=202)
+
+ def get_name(self):
+ return "DeferredDelete"
+
+ def get_alias(self):
+ return "os-deferred-delete"
+
+ def get_description(self):
+ return "Instance deferred delete"
+
+ def get_namespace(self):
+ return "http://docs.openstack.org/ext/deferred-delete/api/v1.1"
+
+ def get_updated(self):
+ return "2011-09-01T00:00:00+00:00"
+
+ def get_actions(self):
+ """Return the actions the extension adds, as required by contract."""
+ actions = [
+ extensions.ActionExtension("servers", "restore",
+ self._restore),
+ extensions.ActionExtension("servers", "forceDelete",
+ self._force_delete),
+ ]
+
+ return actions
diff --git a/nova/api/openstack/contrib/flavorextradata.py b/nova/api/openstack/contrib/flavorextradata.py
new file mode 100644
index 000000000..d0554c7b4
--- /dev/null
+++ b/nova/api/openstack/contrib/flavorextradata.py
@@ -0,0 +1,46 @@
+# Copyright 2011 Canonical Ltd.
+# 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 Flavor extra data extension
+Openstack API version 1.1 lists "name", "ram", "disk", "vcpus" as flavor
+attributes. This extension adds to that list:
+ rxtx_cap
+ rxtx_quota
+ swap
+"""
+
+from nova.api.openstack import extensions
+
+
+class Flavorextradata(extensions.ExtensionDescriptor):
+ """The Flavor extra data extension for the OpenStack API."""
+
+ def get_name(self):
+ return "FlavorExtraData"
+
+ def get_alias(self):
+ return "os-flavor-extra-data"
+
+ def get_description(self):
+ return "Provide additional data for flavors"
+
+ def get_namespace(self):
+ return "http://docs.openstack.org/ext/flavor_extra_data/api/v1.1"
+
+ def get_updated(self):
+ return "2011-09-14T00:00:00+00:00"
+
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
diff --git a/nova/api/openstack/contrib/rescue.py b/nova/api/openstack/contrib/rescue.py
index 3de128895..2e5dbab73 100644
--- a/nova/api/openstack/contrib/rescue.py
+++ b/nova/api/openstack/contrib/rescue.py
@@ -18,11 +18,14 @@ import webob
from webob import exc
from nova import compute
+from nova import flags
from nova import log as logging
+from nova import utils
from nova.api.openstack import extensions as exts
from nova.api.openstack import faults
+FLAGS = flags.FLAGS
LOG = logging.getLogger("nova.api.contrib.rescue")
@@ -30,7 +33,7 @@ def wrap_errors(fn):
""""Ensure errors are not passed along."""
def wrapped(*args):
try:
- fn(*args)
+ return fn(*args)
except Exception, e:
return faults.Fault(exc.HTTPInternalServerError())
return wrapped
@@ -46,9 +49,13 @@ class Rescue(exts.ExtensionDescriptor):
def _rescue(self, input_dict, req, instance_id):
"""Rescue an instance."""
context = req.environ["nova.context"]
- self.compute_api.rescue(context, instance_id)
+ if input_dict['rescue'] and 'adminPass' in input_dict['rescue']:
+ password = input_dict['rescue']['adminPass']
+ else:
+ password = utils.generate_password(FLAGS.password_length)
+ self.compute_api.rescue(context, instance_id, rescue_password=password)
- return webob.Response(status_int=202)
+ return {'adminPass': password}
@wrap_errors
def _unrescue(self, input_dict, req, instance_id):
diff --git a/nova/api/openstack/contrib/virtual_interfaces.py b/nova/api/openstack/contrib/virtual_interfaces.py
index dab61efc8..1981cd372 100644
--- a/nova/api/openstack/contrib/virtual_interfaces.py
+++ b/nova/api/openstack/contrib/virtual_interfaces.py
@@ -15,15 +15,10 @@
"""The virtual interfaces extension."""
-from webob import exc
-import webob
-
-from nova import compute
-from nova import exception
from nova import log as logging
+from nova import network
from nova.api.openstack import common
from nova.api.openstack import extensions
-from nova.api.openstack import faults
from nova.api.openstack import wsgi
@@ -50,19 +45,14 @@ class ServerVirtualInterfaceController(object):
"""
def __init__(self):
- self.compute_api = compute.API()
+ self.network_api = network.API()
super(ServerVirtualInterfaceController, self).__init__()
def _items(self, req, server_id, entity_maker):
"""Returns a list of VIFs, transformed through entity_maker."""
context = req.environ['nova.context']
- try:
- instance = self.compute_api.get(context, server_id)
- except exception.NotFound:
- return faults.Fault(exc.HTTPNotFound())
-
- vifs = instance['virtual_interfaces']
+ vifs = self.network_api.get_vifs_by_instance(context, server_id)
limited_list = common.limited(vifs, req)
res = [entity_maker(context, vif) for vif in limited_list]
return {'virtual_interfaces': res}
diff --git a/nova/api/openstack/contrib/volumes.py b/nova/api/openstack/contrib/volumes.py
index d62225e58..9d4254f1f 100644
--- a/nova/api/openstack/contrib/volumes.py
+++ b/nova/api/openstack/contrib/volumes.py
@@ -372,8 +372,7 @@ class BootFromVolumeController(servers.ControllerV11):
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 = self._build_view(req, inst, is_detail=True)
server['server']['adminPass'] = extra_values['password']
return server
diff --git a/nova/api/openstack/create_instance_helper.py b/nova/api/openstack/create_instance_helper.py
index 67e669c17..79f17e27f 100644
--- a/nova/api/openstack/create_instance_helper.py
+++ b/nova/api/openstack/create_instance_helper.py
@@ -92,7 +92,8 @@ class CreateInstanceHelper(object):
if str(image_href).startswith(req.application_url):
image_href = image_href.split('/').pop()
try:
- image_service, image_id = nova.image.get_image_service(image_href)
+ 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)])
@@ -316,14 +317,14 @@ class CreateInstanceHelper(object):
def _get_server_admin_password_old_style(self, server):
""" Determine the admin password for a server on creation """
- return utils.generate_password(16)
+ 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(16)
+ return utils.generate_password(FLAGS.password_length)
if not isinstance(password, basestring) or password == '':
msg = _("Invalid adminPass")
raise exc.HTTPBadRequest(explanation=msg)
diff --git a/nova/api/openstack/flavors.py b/nova/api/openstack/flavors.py
index fd36060da..8a310c900 100644
--- a/nova/api/openstack/flavors.py
+++ b/nova/api/openstack/flavors.py
@@ -16,12 +16,13 @@
# under the License.
import webob
-import xml.dom.minidom as minidom
+from lxml import etree
from nova import db
from nova import exception
from nova.api.openstack import views
from nova.api.openstack import wsgi
+from nova.api.openstack import xmlutil
class Controller(object):
@@ -78,48 +79,48 @@ class ControllerV11(Controller):
class FlavorXMLSerializer(wsgi.XMLDictSerializer):
+ NSMAP = {None: xmlutil.XMLNS_V11, 'atom': xmlutil.XMLNS_ATOM}
+
def __init__(self):
super(FlavorXMLSerializer, self).__init__(xmlns=wsgi.XMLNS_V11)
- def _flavor_to_xml(self, xml_doc, flavor, detailed):
- flavor_node = xml_doc.createElement('flavor')
- flavor_node.setAttribute('id', str(flavor['id']))
- flavor_node.setAttribute('name', flavor['name'])
+ def _populate_flavor(self, flavor_elem, flavor_dict, detailed=False):
+ """Populate a flavor xml element from a dict."""
+ flavor_elem.set('name', flavor_dict['name'])
+ flavor_elem.set('id', str(flavor_dict['id']))
if detailed:
- flavor_node.setAttribute('ram', str(flavor['ram']))
- flavor_node.setAttribute('disk', str(flavor['disk']))
-
- link_nodes = self._create_link_nodes(xml_doc, flavor['links'])
- for link_node in link_nodes:
- flavor_node.appendChild(link_node)
- return flavor_node
+ flavor_elem.set('ram', str(flavor_dict['ram']))
+ flavor_elem.set('disk', str(flavor_dict['disk']))
- def _flavors_list_to_xml(self, xml_doc, flavors, detailed):
- container_node = xml_doc.createElement('flavors')
+ for attr in ("vcpus", "swap", "rxtx_quota", "rxtx_cap"):
+ flavor_elem.set(attr, str(flavor_dict.get(attr, "")))
- for flavor in flavors:
- item_node = self._flavor_to_xml(xml_doc, flavor, detailed)
- container_node.appendChild(item_node)
- return container_node
+ for link in flavor_dict.get('links', []):
+ elem = etree.SubElement(flavor_elem,
+ '{%s}link' % xmlutil.XMLNS_ATOM)
+ elem.set('rel', link['rel'])
+ elem.set('href', link['href'])
+ return flavor_elem
def show(self, flavor_container):
- xml_doc = minidom.Document()
- flavor = flavor_container['flavor']
- node = self._flavor_to_xml(xml_doc, flavor, True)
- return self.to_xml_string(node, True)
-
- def detail(self, flavors_container):
- xml_doc = minidom.Document()
- flavors = flavors_container['flavors']
- node = self._flavors_list_to_xml(xml_doc, flavors, True)
- return self.to_xml_string(node, True)
-
- def index(self, flavors_container):
- xml_doc = minidom.Document()
- flavors = flavors_container['flavors']
- node = self._flavors_list_to_xml(xml_doc, flavors, False)
- return self.to_xml_string(node, True)
+ flavor = etree.Element('flavor', nsmap=self.NSMAP)
+ self._populate_flavor(flavor, flavor_container['flavor'], True)
+ return self._to_xml(flavor)
+
+ def detail(self, flavors_dict):
+ flavors = etree.Element('flavors', nsmap=self.NSMAP)
+ for flavor_dict in flavors_dict['flavors']:
+ flavor = etree.SubElement(flavors, 'flavor')
+ self._populate_flavor(flavor, flavor_dict, True)
+ return self._to_xml(flavors)
+
+ def index(self, flavors_dict):
+ flavors = etree.Element('flavors', nsmap=self.NSMAP)
+ for flavor_dict in flavors_dict['flavors']:
+ flavor = etree.SubElement(flavors, 'flavor')
+ self._populate_flavor(flavor, flavor_dict, False)
+ return self._to_xml(flavors)
def create_resource(version='1.0'):
diff --git a/nova/api/openstack/image_metadata.py b/nova/api/openstack/image_metadata.py
index 4d615ea96..adb6bee4b 100644
--- a/nova/api/openstack/image_metadata.py
+++ b/nova/api/openstack/image_metadata.py
@@ -17,6 +17,7 @@
from webob import exc
+from nova import exception
from nova import flags
from nova import image
from nova import utils
@@ -33,21 +34,22 @@ class Controller(object):
def __init__(self):
self.image_service = image.get_default_image_service()
- def _get_metadata(self, context, image_id, image=None):
- if not image:
- image = self.image_service.show(context, image_id)
- metadata = image.get('properties', {})
- return metadata
+ def _get_image(self, context, image_id):
+ try:
+ return self.image_service.show(context, image_id)
+ except exception.NotFound:
+ msg = _("Image not found.")
+ raise exc.HTTPNotFound(explanation=msg)
def index(self, req, image_id):
"""Returns the list of metadata for a given instance"""
context = req.environ['nova.context']
- metadata = self._get_metadata(context, image_id)
+ metadata = self._get_image(context, image_id)['properties']
return dict(metadata=metadata)
def show(self, req, image_id, id):
context = req.environ['nova.context']
- metadata = self._get_metadata(context, image_id)
+ metadata = self._get_image(context, image_id)['properties']
if id in metadata:
return {'meta': {id: metadata[id]}}
else:
@@ -55,15 +57,13 @@ class Controller(object):
def create(self, req, image_id, body):
context = req.environ['nova.context']
- img = self.image_service.show(context, image_id)
- metadata = self._get_metadata(context, image_id, img)
+ image = self._get_image(context, image_id)
if 'metadata' in body:
for key, value in body['metadata'].iteritems():
- metadata[key] = value
- common.check_img_metadata_quota_limit(context, metadata)
- img['properties'] = metadata
- self.image_service.update(context, image_id, img, None)
- return dict(metadata=metadata)
+ image['properties'][key] = value
+ common.check_img_metadata_quota_limit(context, image['properties'])
+ self.image_service.update(context, image_id, image, None)
+ return dict(metadata=image['properties'])
def update(self, req, image_id, id, body):
context = req.environ['nova.context']
@@ -80,32 +80,30 @@ class Controller(object):
if len(meta) > 1:
expl = _('Request body contains too many items')
raise exc.HTTPBadRequest(explanation=expl)
- img = self.image_service.show(context, image_id)
- metadata = self._get_metadata(context, image_id, img)
- metadata[id] = meta[id]
- common.check_img_metadata_quota_limit(context, metadata)
- img['properties'] = metadata
- self.image_service.update(context, image_id, img, None)
+
+ image = self._get_image(context, image_id)
+ image['properties'][id] = meta[id]
+ common.check_img_metadata_quota_limit(context, image['properties'])
+ self.image_service.update(context, image_id, image, None)
return dict(meta=meta)
def update_all(self, req, image_id, body):
context = req.environ['nova.context']
- img = self.image_service.show(context, image_id)
+ image = self._get_image(context, image_id)
metadata = body.get('metadata', {})
common.check_img_metadata_quota_limit(context, metadata)
- img['properties'] = metadata
- self.image_service.update(context, image_id, img, None)
+ image['properties'] = metadata
+ self.image_service.update(context, image_id, image, None)
return dict(metadata=metadata)
def delete(self, req, image_id, id):
context = req.environ['nova.context']
- img = self.image_service.show(context, image_id)
- metadata = self._get_metadata(context, image_id)
- if not id in metadata:
- raise exc.HTTPNotFound()
- metadata.pop(id)
- img['properties'] = metadata
- self.image_service.update(context, image_id, img, None)
+ image = self._get_image(context, image_id)
+ if not id in image['properties']:
+ msg = _("Invalid metadata key")
+ raise exc.HTTPNotFound(explanation=msg)
+ image['properties'].pop(id)
+ self.image_service.update(context, image_id, image, None)
def create_resource():
diff --git a/nova/api/openstack/images.py b/nova/api/openstack/images.py
index 1c8fc10c9..d579ae716 100644
--- a/nova/api/openstack/images.py
+++ b/nova/api/openstack/images.py
@@ -16,8 +16,8 @@
import urlparse
import os.path
+from lxml import etree
import webob.exc
-from xml.dom import minidom
from nova import compute
from nova import exception
@@ -29,6 +29,7 @@ from nova.api.openstack import image_metadata
from nova.api.openstack import servers
from nova.api.openstack.views import images as images_view
from nova.api.openstack import wsgi
+from nova.api.openstack import xmlutil
LOG = log.getLogger('nova.api.openstack.images')
@@ -50,7 +51,7 @@ class Controller(object):
"""Initialize new `ImageController`.
:param compute_service: `nova.compute.api:API`
- :param image_service: `nova.image.service:BaseImageService`
+ :param image_service: `nova.image.glance:GlancemageService`
"""
self._compute_service = compute_service or compute.API()
@@ -206,93 +207,73 @@ class ControllerV11(Controller):
class ImageXMLSerializer(wsgi.XMLDictSerializer):
- xmlns = wsgi.XMLNS_V11
+ NSMAP = {None: xmlutil.XMLNS_V11, 'atom': xmlutil.XMLNS_ATOM}
def __init__(self):
self.metadata_serializer = common.MetadataXMLSerializer()
- def _image_to_xml(self, xml_doc, image):
- image_node = xml_doc.createElement('image')
- image_node.setAttribute('id', str(image['id']))
- image_node.setAttribute('name', image['name'])
- link_nodes = self._create_link_nodes(xml_doc,
- image['links'])
- for link_node in link_nodes:
- image_node.appendChild(link_node)
- return image_node
-
- def _image_to_xml_detailed(self, xml_doc, image):
- image_node = xml_doc.createElement('image')
- self._add_image_attributes(image_node, image)
-
- if 'server' in image:
- server_node = self._create_server_node(xml_doc, image['server'])
- image_node.appendChild(server_node)
-
- metadata = image.get('metadata', {}).items()
- if len(metadata) > 0:
- metadata_node = self._create_metadata_node(xml_doc, metadata)
- image_node.appendChild(metadata_node)
-
- link_nodes = self._create_link_nodes(xml_doc,
- image['links'])
- for link_node in link_nodes:
- image_node.appendChild(link_node)
-
- return image_node
-
- def _add_image_attributes(self, node, image):
- node.setAttribute('id', str(image['id']))
- node.setAttribute('name', image['name'])
- node.setAttribute('created', image['created'])
- node.setAttribute('updated', image['updated'])
- node.setAttribute('status', image['status'])
- if 'progress' in image:
- node.setAttribute('progress', str(image['progress']))
-
- def _create_metadata_node(self, xml_doc, metadata):
- return self.metadata_serializer.meta_list_to_xml(xml_doc, metadata)
-
- def _create_server_node(self, xml_doc, server):
- server_node = xml_doc.createElement('server')
- server_node.setAttribute('id', str(server['id']))
- link_nodes = self._create_link_nodes(xml_doc,
- server['links'])
- for link_node in link_nodes:
- server_node.appendChild(link_node)
- return server_node
-
- def _image_list_to_xml(self, xml_doc, images, detailed):
- container_node = xml_doc.createElement('images')
+ def _create_metadata_node(self, metadata_dict):
+ metadata_elem = etree.Element('metadata', nsmap=self.NSMAP)
+ self.metadata_serializer.populate_metadata(metadata_elem,
+ metadata_dict)
+ return metadata_elem
+
+ def _create_server_node(self, server_dict):
+ server_elem = etree.Element('server', nsmap=self.NSMAP)
+ server_elem.set('id', str(server_dict['id']))
+ for link in server_dict.get('links', []):
+ elem = etree.SubElement(server_elem,
+ '{%s}link' % xmlutil.XMLNS_ATOM)
+ elem.set('rel', link['rel'])
+ elem.set('href', link['href'])
+ return server_elem
+
+ def _populate_image(self, image_elem, image_dict, detailed=False):
+ """Populate an image xml element from a dict."""
+
+ image_elem.set('name', image_dict['name'])
+ image_elem.set('id', str(image_dict['id']))
if detailed:
- image_to_xml = self._image_to_xml_detailed
- else:
- image_to_xml = self._image_to_xml
-
- for image in images:
- item_node = image_to_xml(xml_doc, image)
- container_node.appendChild(item_node)
- return container_node
+ image_elem.set('updated', str(image_dict['updated']))
+ image_elem.set('created', str(image_dict['created']))
+ image_elem.set('status', str(image_dict['status']))
+ if 'progress' in image_dict:
+ image_elem.set('progress', str(image_dict['progress']))
+ if 'server' in image_dict:
+ server_elem = self._create_server_node(image_dict['server'])
+ image_elem.append(server_elem)
+
+ meta_elem = self._create_metadata_node(
+ image_dict.get('metadata', {}))
+ image_elem.append(meta_elem)
+
+ for link in image_dict.get('links', []):
+ elem = etree.SubElement(image_elem,
+ '{%s}link' % xmlutil.XMLNS_ATOM)
+ elem.set('rel', link['rel'])
+ if 'type' in link:
+ elem.set('type', link['type'])
+ elem.set('href', link['href'])
+ return image_elem
def index(self, images_dict):
- xml_doc = minidom.Document()
- node = self._image_list_to_xml(xml_doc,
- images_dict['images'],
- detailed=False)
- return self.to_xml_string(node, True)
+ images = etree.Element('images', nsmap=self.NSMAP)
+ for image_dict in images_dict['images']:
+ image = etree.SubElement(images, 'image')
+ self._populate_image(image, image_dict, False)
+ return self._to_xml(images)
def detail(self, images_dict):
- xml_doc = minidom.Document()
- node = self._image_list_to_xml(xml_doc,
- images_dict['images'],
- detailed=True)
- return self.to_xml_string(node, True)
+ images = etree.Element('images', nsmap=self.NSMAP)
+ for image_dict in images_dict['images']:
+ image = etree.SubElement(images, 'image')
+ self._populate_image(image, image_dict, True)
+ return self._to_xml(images)
def show(self, image_dict):
- xml_doc = minidom.Document()
- node = self._image_to_xml_detailed(xml_doc,
- image_dict['image'])
- return self.to_xml_string(node, True)
+ image = etree.Element('image', nsmap=self.NSMAP)
+ self._populate_image(image, image_dict['image'], True)
+ return self._to_xml(image)
def create_resource(version='1.0'):
diff --git a/nova/api/openstack/ips.py b/nova/api/openstack/ips.py
index a74fae487..7e644ba04 100644
--- a/nova/api/openstack/ips.py
+++ b/nova/api/openstack/ips.py
@@ -15,14 +15,15 @@
# License for the specific language governing permissions and limitations
# under the License.
+from lxml import etree
import time
-from xml.dom import minidom
from webob import exc
import nova
import nova.api.openstack.views.addresses
from nova.api.openstack import wsgi
+from nova.api.openstack import xmlutil
from nova import db
@@ -102,42 +103,36 @@ class ControllerV11(Controller):
class IPXMLSerializer(wsgi.XMLDictSerializer):
+
+ NSMAP = {None: xmlutil.XMLNS_V11}
+
def __init__(self, xmlns=wsgi.XMLNS_V11):
super(IPXMLSerializer, self).__init__(xmlns=xmlns)
- def _ip_to_xml(self, xml_doc, ip_dict):
- ip_node = xml_doc.createElement('ip')
- ip_node.setAttribute('addr', ip_dict['addr'])
- ip_node.setAttribute('version', str(ip_dict['version']))
- return ip_node
-
- def _network_to_xml(self, xml_doc, network_id, ip_dicts):
- network_node = xml_doc.createElement('network')
- network_node.setAttribute('id', network_id)
+ def populate_addresses_node(self, addresses_elem, addresses_dict):
+ for (network_id, ip_dicts) in addresses_dict.items():
+ network_elem = self._create_network_node(network_id, ip_dicts)
+ addresses_elem.append(network_elem)
+ def _create_network_node(self, network_id, ip_dicts):
+ network_elem = etree.Element('network', nsmap=self.NSMAP)
+ network_elem.set('id', str(network_id))
for ip_dict in ip_dicts:
- ip_node = self._ip_to_xml(xml_doc, ip_dict)
- network_node.appendChild(ip_node)
-
- return network_node
-
- def networks_to_xml(self, xml_doc, networks_container):
- addresses_node = xml_doc.createElement('addresses')
- for (network_id, ip_dicts) in networks_container.items():
- network_node = self._network_to_xml(xml_doc, network_id, ip_dicts)
- addresses_node.appendChild(network_node)
- return addresses_node
-
- def show(self, network_container):
- (network_id, ip_dicts) = network_container.items()[0]
- xml_doc = minidom.Document()
- node = self._network_to_xml(xml_doc, network_id, ip_dicts)
- return self.to_xml_string(node, False)
-
- def index(self, addresses_container):
- xml_doc = minidom.Document()
- node = self.networks_to_xml(xml_doc, addresses_container['addresses'])
- return self.to_xml_string(node, False)
+ ip_elem = etree.SubElement(network_elem, 'ip')
+ ip_elem.set('version', str(ip_dict['version']))
+ ip_elem.set('addr', ip_dict['addr'])
+ return network_elem
+
+ def show(self, network_dict):
+ (network_id, ip_dicts) = network_dict.items()[0]
+ network = self._create_network_node(network_id, ip_dicts)
+ return self._to_xml(network)
+
+ def index(self, addresses_dict):
+ addresses = etree.Element('addresses', nsmap=self.NSMAP)
+ self.populate_addresses_node(addresses,
+ addresses_dict.get('addresses', {}))
+ return self._to_xml(addresses)
def create_resource(version):
diff --git a/nova/api/openstack/limits.py b/nova/api/openstack/limits.py
index 86afa3b62..f6df94eea 100644
--- a/nova/api/openstack/limits.py
+++ b/nova/api/openstack/limits.py
@@ -20,12 +20,12 @@ Module dedicated functions/classes dealing with rate limiting requests.
import copy
import httplib
import json
+from lxml import etree
import math
import re
import time
import urllib
import webob.exc
-from xml.dom import minidom
from collections import defaultdict
@@ -38,6 +38,7 @@ from nova.api.openstack import common
from nova.api.openstack import faults
from nova.api.openstack.views import limits as limits_views
from nova.api.openstack import wsgi
+from nova.api.openstack import xmlutil
# Convenience constants for the limits dictionary passed to Limiter().
@@ -81,52 +82,49 @@ class LimitsXMLSerializer(wsgi.XMLDictSerializer):
xmlns = wsgi.XMLNS_V11
+ NSMAP = {None: xmlutil.XMLNS_V11, 'atom': xmlutil.XMLNS_ATOM}
+
def __init__(self):
pass
- def _create_rates_node(self, xml_doc, rates):
- rates_node = xml_doc.createElement('rates')
+ def _create_rates_node(self, rates):
+ rates_elem = etree.Element('rates', nsmap=self.NSMAP)
for rate in rates:
- rate_node = xml_doc.createElement('rate')
- rate_node.setAttribute('uri', rate['uri'])
- rate_node.setAttribute('regex', rate['regex'])
-
+ rate_node = etree.SubElement(rates_elem, 'rate')
+ rate_node.set('uri', rate['uri'])
+ rate_node.set('regex', rate['regex'])
for limit in rate['limit']:
- limit_node = xml_doc.createElement('limit')
- limit_node.setAttribute('value', str(limit['value']))
- limit_node.setAttribute('verb', limit['verb'])
- limit_node.setAttribute('remaining', str(limit['remaining']))
- limit_node.setAttribute('unit', limit['unit'])
- limit_node.setAttribute('next-available',
- str(limit['next-available']))
- rate_node.appendChild(limit_node)
-
- rates_node.appendChild(rate_node)
- return rates_node
-
- def _create_absolute_node(self, xml_doc, absolutes):
- absolute_node = xml_doc.createElement('absolute')
- for key, value in absolutes.iteritems():
- limit_node = xml_doc.createElement('limit')
- limit_node.setAttribute('name', key)
- limit_node.setAttribute('value', str(value))
- absolute_node.appendChild(limit_node)
- return absolute_node
-
- def _limits_to_xml(self, xml_doc, limits):
- limits_node = xml_doc.createElement('limits')
- rates_node = self._create_rates_node(xml_doc, limits['rate'])
- limits_node.appendChild(rates_node)
-
- absolute_node = self._create_absolute_node(xml_doc, limits['absolute'])
- limits_node.appendChild(absolute_node)
-
- return limits_node
+ limit_elem = etree.SubElement(rate_node, 'limit')
+ limit_elem.set('value', str(limit['value']))
+ limit_elem.set('verb', str(limit['verb']))
+ limit_elem.set('remaining', str(limit['remaining']))
+ limit_elem.set('unit', str(limit['unit']))
+ limit_elem.set('next-available', str(limit['next-available']))
+ return rates_elem
+
+ def _create_absolute_node(self, absolute_dict):
+ absolute_elem = etree.Element('absolute', nsmap=self.NSMAP)
+ for key, value in absolute_dict.items():
+ limit_elem = etree.SubElement(absolute_elem, 'limit')
+ limit_elem.set('name', str(key))
+ limit_elem.set('value', str(value))
+ return absolute_elem
+
+ def _populate_limits(self, limits_elem, limits_dict):
+ """Populate a limits xml element from a dict."""
+
+ rates_elem = self._create_rates_node(
+ limits_dict.get('rate', []))
+ limits_elem.append(rates_elem)
+
+ absolutes_elem = self._create_absolute_node(
+ limits_dict.get('absolute', {}))
+ limits_elem.append(absolutes_elem)
def index(self, limits_dict):
- xml_doc = minidom.Document()
- node = self._limits_to_xml(xml_doc, limits_dict['limits'])
- return self.to_xml_string(node, False)
+ limits = etree.Element('limits', nsmap=self.NSMAP)
+ self._populate_limits(limits, limits_dict['limits'])
+ return self._to_xml(limits)
def create_resource(version='1.0'):
diff --git a/nova/api/openstack/schemas/v1.1/addresses.rng b/nova/api/openstack/schemas/v1.1/addresses.rng
new file mode 100644
index 000000000..b498e8a63
--- /dev/null
+++ b/nova/api/openstack/schemas/v1.1/addresses.rng
@@ -0,0 +1,14 @@
+<element name="addresses" ns="http://docs.openstack.org/compute/api/v1.1"
+ xmlns="http://relaxng.org/ns/structure/1.0">
+ <zeroOrMore>
+ <element name="network">
+ <attribute name="id"> <text/> </attribute>
+ <zeroOrMore>
+ <element name="ip">
+ <attribute name="version"> <text/> </attribute>
+ <attribute name="addr"> <text/> </attribute>
+ </element>
+ </zeroOrMore>
+ </element>
+ </zeroOrMore>
+</element>
diff --git a/nova/api/openstack/schemas/v1.1/flavor.rng b/nova/api/openstack/schemas/v1.1/flavor.rng
new file mode 100644
index 000000000..6d3adc8dc
--- /dev/null
+++ b/nova/api/openstack/schemas/v1.1/flavor.rng
@@ -0,0 +1,14 @@
+<element name="flavor" ns="http://docs.openstack.org/compute/api/v1.1"
+ xmlns="http://relaxng.org/ns/structure/1.0">
+ <attribute name="name"> <text/> </attribute>
+ <attribute name="id"> <text/> </attribute>
+ <attribute name="ram"> <text/> </attribute>
+ <attribute name="disk"> <text/> </attribute>
+ <attribute name="rxtx_cap"> <text/> </attribute>
+ <attribute name="rxtx_quota"> <text/> </attribute>
+ <attribute name="swap"> <text/> </attribute>
+ <attribute name="vcpus"> <text/> </attribute>
+ <zeroOrMore>
+ <externalRef href="../atom-link.rng"/>
+ </zeroOrMore>
+</element>
diff --git a/nova/api/openstack/schemas/v1.1/flavors.rng b/nova/api/openstack/schemas/v1.1/flavors.rng
new file mode 100644
index 000000000..b7a3acc01
--- /dev/null
+++ b/nova/api/openstack/schemas/v1.1/flavors.rng
@@ -0,0 +1,6 @@
+<element name="flavors" xmlns="http://relaxng.org/ns/structure/1.0"
+ ns="http://docs.openstack.org/compute/api/v1.1">
+ <zeroOrMore>
+ <externalRef href="flavor.rng"/>
+ </zeroOrMore>
+</element>
diff --git a/nova/api/openstack/schemas/v1.1/flavors_index.rng b/nova/api/openstack/schemas/v1.1/flavors_index.rng
new file mode 100644
index 000000000..d1a4fedb1
--- /dev/null
+++ b/nova/api/openstack/schemas/v1.1/flavors_index.rng
@@ -0,0 +1,12 @@
+<element name="flavors" ns="http://docs.openstack.org/compute/api/v1.1"
+ xmlns="http://relaxng.org/ns/structure/1.0">
+ <zeroOrMore>
+ <element name="flavor">
+ <attribute name="name"> <text/> </attribute>
+ <attribute name="id"> <text/> </attribute>
+ <zeroOrMore>
+ <externalRef href="../atom-link.rng"/>
+ </zeroOrMore>
+ </element>
+ </zeroOrMore>
+</element>
diff --git a/nova/api/openstack/schemas/v1.1/image.rng b/nova/api/openstack/schemas/v1.1/image.rng
new file mode 100644
index 000000000..887f76751
--- /dev/null
+++ b/nova/api/openstack/schemas/v1.1/image.rng
@@ -0,0 +1,30 @@
+<element name="image" ns="http://docs.openstack.org/compute/api/v1.1"
+ xmlns="http://relaxng.org/ns/structure/1.0">
+ <attribute name="name"> <text/> </attribute>
+ <attribute name="id"> <text/> </attribute>
+ <attribute name="updated"> <text/> </attribute>
+ <attribute name="created"> <text/> </attribute>
+ <attribute name="status"> <text/> </attribute>
+ <optional>
+ <attribute name="progress"> <text/> </attribute>
+ </optional>
+ <optional>
+ <element name="server">
+ <attribute name="id"> <text/> </attribute>
+ <zeroOrMore>
+ <externalRef href="../atom-link.rng"/>
+ </zeroOrMore>
+ </element>
+ </optional>
+ <element name="metadata">
+ <zeroOrMore>
+ <element name="meta">
+ <attribute name="key"> <text/> </attribute>
+ <text/>
+ </element>
+ </zeroOrMore>
+ </element>
+ <zeroOrMore>
+ <externalRef href="../atom-link.rng"/>
+ </zeroOrMore>
+</element>
diff --git a/nova/api/openstack/schemas/v1.1/images.rng b/nova/api/openstack/schemas/v1.1/images.rng
new file mode 100644
index 000000000..064d4d9cc
--- /dev/null
+++ b/nova/api/openstack/schemas/v1.1/images.rng
@@ -0,0 +1,6 @@
+<element name="images" xmlns="http://relaxng.org/ns/structure/1.0"
+ ns="http://docs.openstack.org/compute/api/v1.1">
+ <zeroOrMore>
+ <externalRef href="image.rng"/>
+ </zeroOrMore>
+</element>
diff --git a/nova/api/openstack/schemas/v1.1/images_index.rng b/nova/api/openstack/schemas/v1.1/images_index.rng
new file mode 100644
index 000000000..81af19cb5
--- /dev/null
+++ b/nova/api/openstack/schemas/v1.1/images_index.rng
@@ -0,0 +1,12 @@
+<element name="images" ns="http://docs.openstack.org/compute/api/v1.1"
+ xmlns="http://relaxng.org/ns/structure/1.0">
+ <zeroOrMore>
+ <element name="image">
+ <attribute name="name"> <text/> </attribute>
+ <attribute name="id"> <text/> </attribute>
+ <zeroOrMore>
+ <externalRef href="../atom-link.rng"/>
+ </zeroOrMore>
+ </element>
+ </zeroOrMore>
+</element>
diff --git a/nova/api/openstack/schemas/v1.1/limits.rng b/nova/api/openstack/schemas/v1.1/limits.rng
new file mode 100644
index 000000000..1af8108ec
--- /dev/null
+++ b/nova/api/openstack/schemas/v1.1/limits.rng
@@ -0,0 +1,28 @@
+<element name="limits" ns="http://docs.openstack.org/compute/api/v1.1"
+ xmlns="http://relaxng.org/ns/structure/1.0">
+ <element name="rates">
+ <zeroOrMore>
+ <element name="rate">
+ <attribute name="uri"> <text/> </attribute>
+ <attribute name="regex"> <text/> </attribute>
+ <zeroOrMore>
+ <element name="limit">
+ <attribute name="value"> <text/> </attribute>
+ <attribute name="verb"> <text/> </attribute>
+ <attribute name="remaining"> <text/> </attribute>
+ <attribute name="unit"> <text/> </attribute>
+ <attribute name="next-available"> <text/> </attribute>
+ </element>
+ </zeroOrMore>
+ </element>
+ </zeroOrMore>
+ </element>
+ <element name="absolute">
+ <zeroOrMore>
+ <element name="limit">
+ <attribute name="name"> <text/> </attribute>
+ <attribute name="value"> <text/> </attribute>
+ </element>
+ </zeroOrMore>
+ </element>
+</element>
diff --git a/nova/api/openstack/schemas/v1.1/metadata.rng b/nova/api/openstack/schemas/v1.1/metadata.rng
new file mode 100644
index 000000000..b2f5d702a
--- /dev/null
+++ b/nova/api/openstack/schemas/v1.1/metadata.rng
@@ -0,0 +1,9 @@
+ <element name="metadata" ns="http://docs.openstack.org/compute/api/v1.1"
+ xmlns="http://relaxng.org/ns/structure/1.0">
+ <zeroOrMore>
+ <element name="meta">
+ <attribute name="key"> <text/> </attribute>
+ <text/>
+ </element>
+ </zeroOrMore>
+ </element>
diff --git a/nova/api/openstack/schemas/v1.1/server.rng b/nova/api/openstack/schemas/v1.1/server.rng
index ef835e408..4eb1a0b85 100644
--- a/nova/api/openstack/schemas/v1.1/server.rng
+++ b/nova/api/openstack/schemas/v1.1/server.rng
@@ -17,9 +17,6 @@
<optional>
<attribute name="adminPass"> <text/> </attribute>
</optional>
- <zeroOrMore>
- <externalRef href="../atom-link.rng"/>
- </zeroOrMore>
<element name="image">
<attribute name="id"> <text/> </attribute>
<externalRef href="../atom-link.rng"/>
@@ -49,4 +46,7 @@
</element>
</zeroOrMore>
</element>
+ <zeroOrMore>
+ <externalRef href="../atom-link.rng"/>
+ </zeroOrMore>
</element>
diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py
index d084ac360..0e7c37486 100644
--- a/nova/api/openstack/servers.py
+++ b/nova/api/openstack/servers.py
@@ -17,8 +17,8 @@ import base64
import os
import traceback
+from lxml import etree
from webob import exc
-from xml.dom import minidom
import webob
from nova import compute
@@ -38,6 +38,7 @@ 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 xmlutil
LOG = logging.getLogger('nova.api.openstack.servers')
@@ -168,6 +169,12 @@ class Controller(object):
server['server']['adminPass'] = extra_values['password']
return server
+ def _delete(self, context, id):
+ if FLAGS.reclaim_instance_interval:
+ self.compute_api.soft_delete(context, id)
+ else:
+ self.compute_api.delete(context, id)
+
@scheduler_api.redirect_handler
def update(self, req, id, body):
"""Update server then pass on to version-specific controller"""
@@ -334,9 +341,8 @@ class Controller(object):
LOG.exception(msg)
raise exc.HTTPBadRequest(explanation=msg)
try:
- # TODO(gundlach): pass reboot_type, support soft reboot in
- # virt driver
- self.compute_api.reboot(req.environ['nova.context'], id)
+ self.compute_api.reboot(req.environ['nova.context'], id,
+ reboot_type)
except Exception, e:
LOG.exception(_("Error in reboot %s"), e)
raise exc.HTTPUnprocessableEntity()
@@ -477,16 +483,22 @@ class Controller(object):
return webob.Response(status_int=202)
@scheduler_api.redirect_handler
- def rescue(self, req, id):
+ def rescue(self, req, id, body={}):
"""Permit users to rescue the server."""
context = req.environ["nova.context"]
try:
- self.compute_api.rescue(context, id)
+ if 'rescue' in body and body['rescue'] and \
+ 'adminPass' in body['rescue']:
+ password = body['rescue']['adminPass']
+ else:
+ password = utils.generate_password(FLAGS.password_length)
+ self.compute_api.rescue(context, id, rescue_password=password)
except Exception:
readable = traceback.format_exc()
LOG.exception(_("compute.api::rescue %s"), readable)
raise exc.HTTPUnprocessableEntity()
- return webob.Response(status_int=202)
+
+ return {'adminPass': password}
@scheduler_api.redirect_handler
def unrescue(self, req, id):
@@ -566,7 +578,7 @@ class ControllerV10(Controller):
def delete(self, req, id):
""" Destroys a server """
try:
- self.compute_api.delete(req.environ['nova.context'], id)
+ self._delete(req.environ['nova.context'], id)
except exception.NotFound:
raise exc.HTTPNotFound()
return webob.Response(status_int=202)
@@ -618,7 +630,7 @@ class ControllerV10(Controller):
LOG.debug(msg)
raise exc.HTTPBadRequest(explanation=msg)
- password = utils.generate_password(16)
+ password = utils.generate_password(FLAGS.password_length)
try:
self.compute_api.rebuild(context, instance_id, image_id, password)
@@ -644,13 +656,17 @@ class ControllerV11(Controller):
def delete(self, req, id):
""" Destroys a server """
try:
- self.compute_api.delete(req.environ['nova.context'], id)
+ self._delete(req.environ['nova.context'], id)
except exception.NotFound:
raise exc.HTTPNotFound()
def _get_key_name(self, req, body):
if 'server' in body:
- return body['server'].get('key_name')
+ try:
+ return body['server'].get('key_name')
+ except AttributeError:
+ msg = _("Malformed server entity")
+ raise exc.HTTPBadRequest(explanation=msg)
def _image_ref_from_req_data(self, data):
try:
@@ -760,8 +776,10 @@ class ControllerV11(Controller):
self._validate_metadata(metadata)
self._decode_personalities(personalities)
- password = info["rebuild"].get("adminPass",
- utils.generate_password(16))
+ if 'rebuild' in info and 'adminPass' in info['rebuild']:
+ password = info['rebuild']['adminPass']
+ else:
+ password = utils.generate_password(FLAGS.password_length)
try:
self.compute_api.rebuild(context, instance_id, image_href,
@@ -851,130 +869,113 @@ class HeadersSerializer(wsgi.ResponseHeadersSerializer):
class ServerXMLSerializer(wsgi.XMLDictSerializer):
- xmlns = wsgi.XMLNS_V11
+ NSMAP = {None: xmlutil.XMLNS_V11, 'atom': xmlutil.XMLNS_ATOM}
def __init__(self):
self.metadata_serializer = common.MetadataXMLSerializer()
self.addresses_serializer = ips.IPXMLSerializer()
- def _create_basic_entity_node(self, xml_doc, id, links, name):
- basic_node = xml_doc.createElement(name)
- basic_node.setAttribute('id', str(id))
- link_nodes = self._create_link_nodes(xml_doc, links)
- for link_node in link_nodes:
- basic_node.appendChild(link_node)
- return basic_node
-
- def _create_metadata_node(self, xml_doc, metadata):
- return self.metadata_serializer.meta_list_to_xml(xml_doc, metadata)
-
- def _create_addresses_node(self, xml_doc, addresses):
- return self.addresses_serializer.networks_to_xml(xml_doc, addresses)
-
- def _add_server_attributes(self, node, server):
- node.setAttribute('id', str(server['id']))
- node.setAttribute('userId', str(server['user_id']))
- node.setAttribute('tenantId', str(server['tenant_id']))
- node.setAttribute('uuid', str(server['uuid']))
- node.setAttribute('hostId', str(server['hostId']))
- node.setAttribute('name', server['name'])
- node.setAttribute('created', str(server['created']))
- node.setAttribute('updated', str(server['updated']))
- node.setAttribute('status', server['status'])
- if 'accessIPv4' in server:
- node.setAttribute('accessIPv4', str(server['accessIPv4']))
- if 'accessIPv6' in server:
- node.setAttribute('accessIPv6', str(server['accessIPv6']))
- if 'progress' in server:
- node.setAttribute('progress', str(server['progress']))
-
- def _server_to_xml(self, xml_doc, server):
- server_node = xml_doc.createElement('server')
- server_node.setAttribute('id', str(server['id']))
- server_node.setAttribute('name', server['name'])
- link_nodes = self._create_link_nodes(xml_doc,
- server['links'])
- for link_node in link_nodes:
- server_node.appendChild(link_node)
- return server_node
-
- def _server_to_xml_detailed(self, xml_doc, server):
- server_node = xml_doc.createElement('server')
- self._add_server_attributes(server_node, server)
-
- link_nodes = self._create_link_nodes(xml_doc,
- server['links'])
- for link_node in link_nodes:
- server_node.appendChild(link_node)
-
- if 'image' in server:
- image_node = self._create_basic_entity_node(xml_doc,
- server['image']['id'],
- server['image']['links'],
- 'image')
- server_node.appendChild(image_node)
-
- if 'flavor' in server:
- flavor_node = self._create_basic_entity_node(xml_doc,
- server['flavor']['id'],
- server['flavor']['links'],
- 'flavor')
- server_node.appendChild(flavor_node)
-
- metadata = server.get('metadata', {}).items()
- if len(metadata) > 0:
- metadata_node = self._create_metadata_node(xml_doc, metadata)
- server_node.appendChild(metadata_node)
-
- addresses_node = self._create_addresses_node(xml_doc,
- server['addresses'])
- server_node.appendChild(addresses_node)
-
- if 'security_groups' in server:
- security_groups_node = self._create_security_groups_node(xml_doc,
- server['security_groups'])
- server_node.appendChild(security_groups_node)
-
- return server_node
-
- def _server_list_to_xml(self, xml_doc, servers, detailed):
- container_node = xml_doc.createElement('servers')
+ def _create_metadata_node(self, metadata_dict):
+ metadata_elem = etree.Element('metadata', nsmap=self.NSMAP)
+ self.metadata_serializer.populate_metadata(metadata_elem,
+ metadata_dict)
+ return metadata_elem
+
+ def _create_image_node(self, image_dict):
+ image_elem = etree.Element('image', nsmap=self.NSMAP)
+ image_elem.set('id', str(image_dict['id']))
+ for link in image_dict.get('links', []):
+ elem = etree.SubElement(image_elem,
+ '{%s}link' % xmlutil.XMLNS_ATOM)
+ elem.set('rel', link['rel'])
+ elem.set('href', link['href'])
+ return image_elem
+
+ def _create_flavor_node(self, flavor_dict):
+ flavor_elem = etree.Element('flavor', nsmap=self.NSMAP)
+ flavor_elem.set('id', str(flavor_dict['id']))
+ for link in flavor_dict.get('links', []):
+ elem = etree.SubElement(flavor_elem,
+ '{%s}link' % xmlutil.XMLNS_ATOM)
+ elem.set('rel', link['rel'])
+ elem.set('href', link['href'])
+ return flavor_elem
+
+ def _create_addresses_node(self, addresses_dict):
+ addresses_elem = etree.Element('addresses', nsmap=self.NSMAP)
+ self.addresses_serializer.populate_addresses_node(addresses_elem,
+ addresses_dict)
+ return addresses_elem
+
+ def _populate_server(self, server_elem, server_dict, detailed=False):
+ """Populate a server xml element from a dict."""
+
+ server_elem.set('name', server_dict['name'])
+ server_elem.set('id', str(server_dict['id']))
if detailed:
- server_to_xml = self._server_to_xml_detailed
- else:
- server_to_xml = self._server_to_xml
-
- for server in servers:
- item_node = server_to_xml(xml_doc, server)
- container_node.appendChild(item_node)
- return container_node
+ server_elem.set('uuid', str(server_dict['uuid']))
+ server_elem.set('userId', str(server_dict['user_id']))
+ server_elem.set('tenantId', str(server_dict['tenant_id']))
+ server_elem.set('updated', str(server_dict['updated']))
+ server_elem.set('created', str(server_dict['created']))
+ server_elem.set('hostId', str(server_dict['hostId']))
+ server_elem.set('accessIPv4', str(server_dict['accessIPv4']))
+ server_elem.set('accessIPv6', str(server_dict['accessIPv6']))
+ server_elem.set('status', str(server_dict['status']))
+ if 'progress' in server_dict:
+ server_elem.set('progress', str(server_dict['progress']))
+ image_elem = self._create_image_node(server_dict['image'])
+ server_elem.append(image_elem)
+
+ flavor_elem = self._create_flavor_node(server_dict['flavor'])
+ server_elem.append(flavor_elem)
+
+ meta_elem = self._create_metadata_node(
+ server_dict.get('metadata', {}))
+ server_elem.append(meta_elem)
+
+ addresses_elem = self._create_addresses_node(
+ server_dict.get('addresses', {}))
+ server_elem.append(addresses_elem)
+ groups = server_dict.get('security_groups')
+ if groups:
+ groups_elem = etree.SubElement(server_elem, 'security_groups')
+ for group in groups:
+ group_elem = etree.SubElement(groups_elem,
+ 'security_group')
+ group_elem.set('name', group['name'])
+
+ for link in server_dict.get('links', []):
+ elem = etree.SubElement(server_elem,
+ '{%s}link' % xmlutil.XMLNS_ATOM)
+ elem.set('rel', link['rel'])
+ elem.set('href', link['href'])
+ return server_elem
def index(self, servers_dict):
- xml_doc = minidom.Document()
- node = self._server_list_to_xml(xml_doc,
- servers_dict['servers'],
- detailed=False)
- return self.to_xml_string(node, True)
+ servers = etree.Element('servers', nsmap=self.NSMAP)
+ for server_dict in servers_dict['servers']:
+ server = etree.SubElement(servers, 'server')
+ self._populate_server(server, server_dict, False)
+ return self._to_xml(servers)
def detail(self, servers_dict):
- xml_doc = minidom.Document()
- node = self._server_list_to_xml(xml_doc,
- servers_dict['servers'],
- detailed=True)
- return self.to_xml_string(node, True)
+ servers = etree.Element('servers', nsmap=self.NSMAP)
+ for server_dict in servers_dict['servers']:
+ server = etree.SubElement(servers, 'server')
+ self._populate_server(server, server_dict, True)
+ return self._to_xml(servers)
def show(self, server_dict):
- xml_doc = minidom.Document()
- node = self._server_to_xml_detailed(xml_doc,
- server_dict['server'])
- return self.to_xml_string(node, True)
+ server = etree.Element('server', nsmap=self.NSMAP)
+ self._populate_server(server, server_dict['server'], True)
+ return self._to_xml(server)
def create(self, server_dict):
- xml_doc = minidom.Document()
- node = self._server_to_xml_detailed(xml_doc,
- server_dict['server'])
- node.setAttribute('adminPass', server_dict['server']['adminPass'])
- return self.to_xml_string(node, True)
+ server = etree.Element('server', nsmap=self.NSMAP)
+ self._populate_server(server, server_dict['server'], True)
+ server.set('adminPass', server_dict['server']['adminPass'])
+ return self._to_xml(server)
def action(self, server_dict):
#NOTE(bcwaldon): We need a way to serialize actions individually. This
@@ -982,23 +983,9 @@ class ServerXMLSerializer(wsgi.XMLDictSerializer):
return self.create(server_dict)
def update(self, server_dict):
- xml_doc = minidom.Document()
- node = self._server_to_xml_detailed(xml_doc,
- server_dict['server'])
- return self.to_xml_string(node, True)
-
- def _security_group_to_xml(self, doc, security_group):
- node = doc.createElement('security_group')
- node.setAttribute('name', str(security_group.get('name')))
- return node
-
- def _create_security_groups_node(self, xml_doc, security_groups):
- security_groups_node = xml_doc.createElement('security_groups')
- if security_groups:
- for security_group in security_groups:
- node = self._security_group_to_xml(xml_doc, security_group)
- security_groups_node.appendChild(node)
- return security_groups_node
+ server = etree.Element('server', nsmap=self.NSMAP)
+ self._populate_server(server, server_dict['server'], True)
+ return self._to_xml(server)
def create_resource(version='1.0'):
diff --git a/nova/api/openstack/versions.py b/nova/api/openstack/versions.py
index e2f892fb6..75a1d0ba4 100644
--- a/nova/api/openstack/versions.py
+++ b/nova/api/openstack/versions.py
@@ -16,12 +16,13 @@
# under the License.
from datetime import datetime
+from lxml import etree
import webob
import webob.dec
-from xml.dom import minidom
import nova.api.openstack.views.versions
from nova.api.openstack import wsgi
+from nova.api.openstack import xmlutil
VERSIONS = {
@@ -106,7 +107,9 @@ class Versions(wsgi.Resource):
headers_serializer=headers_serializer)
supported_content_types = ('application/json',
+ 'application/vnd.openstack.compute+json',
'application/xml',
+ 'application/vnd.openstack.compute+xml',
'application/atom+xml')
deserializer = VersionsRequestDeserializer(
supported_content_types=supported_content_types)
@@ -159,83 +162,51 @@ class VersionsRequestDeserializer(wsgi.RequestDeserializer):
class VersionsXMLSerializer(wsgi.XMLDictSerializer):
- #TODO(wwolf): this is temporary until we get rid of toprettyxml
- # in the base class (XMLDictSerializer), which I plan to do in
- # another branch
- def to_xml_string(self, node, has_atom=False):
- self._add_xmlns(node, has_atom)
- return node.toxml(encoding='UTF-8')
-
- def _versions_to_xml(self, versions, name="versions", xmlns=None):
- root = self._xml_doc.createElement(name)
- root.setAttribute("xmlns", wsgi.XMLNS_V11)
- root.setAttribute("xmlns:atom", wsgi.XMLNS_ATOM)
- for version in versions:
- root.appendChild(self._create_version_node(version))
-
- return root
-
- def _create_media_types(self, media_types):
- base = self._xml_doc.createElement('media-types')
- for type in media_types:
- node = self._xml_doc.createElement('media-type')
- node.setAttribute('base', type['base'])
- node.setAttribute('type', type['type'])
- base.appendChild(node)
-
- return base
-
- def _create_version_node(self, version, create_ns=False):
- version_node = self._xml_doc.createElement('version')
- if create_ns:
- xmlns = wsgi.XMLNS_V11
- xmlns_atom = wsgi.XMLNS_ATOM
- version_node.setAttribute('xmlns', xmlns)
- version_node.setAttribute('xmlns:atom', xmlns_atom)
-
- version_node.setAttribute('id', version['id'])
- version_node.setAttribute('status', version['status'])
+ def _populate_version(self, version_node, version):
+ version_node.set('id', version['id'])
+ version_node.set('status', version['status'])
if 'updated' in version:
- version_node.setAttribute('updated', version['updated'])
-
+ version_node.set('updated', version['updated'])
if 'media-types' in version:
- media_types = self._create_media_types(version['media-types'])
- version_node.appendChild(media_types)
-
- link_nodes = self._create_link_nodes(self._xml_doc, version['links'])
- for link in link_nodes:
- version_node.appendChild(link)
-
- return version_node
+ media_types = etree.SubElement(version_node, 'media-types')
+ for mtype in version['media-types']:
+ elem = etree.SubElement(media_types, 'media-type')
+ elem.set('base', mtype['base'])
+ elem.set('type', mtype['type'])
+ for link in version.get('links', []):
+ elem = etree.SubElement(version_node,
+ '{%s}link' % xmlutil.XMLNS_ATOM)
+ elem.set('rel', link['rel'])
+ elem.set('href', link['href'])
+ if 'type' in link:
+ elem.set('type', link['type'])
+
+ NSMAP = {None: xmlutil.XMLNS_V11, 'atom': xmlutil.XMLNS_ATOM}
def index(self, data):
- self._xml_doc = minidom.Document()
- node = self._versions_to_xml(data['versions'])
-
- return self.to_xml_string(node)
+ root = etree.Element('versions', nsmap=self.NSMAP)
+ for version in data['versions']:
+ version_elem = etree.SubElement(root, 'version')
+ self._populate_version(version_elem, version)
+ return self._to_xml(root)
def show(self, data):
- self._xml_doc = minidom.Document()
- node = self._create_version_node(data['version'], True)
-
- return self.to_xml_string(node)
+ root = etree.Element('version', nsmap=self.NSMAP)
+ self._populate_version(root, data['version'])
+ return self._to_xml(root)
def multi(self, data):
- self._xml_doc = minidom.Document()
- node = self._versions_to_xml(data['choices'], 'choices',
- xmlns=wsgi.XMLNS_V11)
-
- return self.to_xml_string(node)
+ root = etree.Element('choices', nsmap=self.NSMAP)
+ for version in data['choices']:
+ version_elem = etree.SubElement(root, 'version')
+ self._populate_version(version_elem, version)
+ return self._to_xml(root)
class VersionsAtomSerializer(wsgi.XMLDictSerializer):
- #TODO(wwolf): this is temporary until we get rid of toprettyxml
- # in the base class (XMLDictSerializer), which I plan to do in
- # another branch
- def to_xml_string(self, node, has_atom=False):
- self._add_xmlns(node, has_atom)
- return node.toxml(encoding='UTF-8')
+
+ NSMAP = {None: xmlutil.XMLNS_ATOM}
def __init__(self, metadata=None, xmlns=None):
self.metadata = metadata or {}
@@ -244,14 +215,6 @@ class VersionsAtomSerializer(wsgi.XMLDictSerializer):
else:
self.xmlns = xmlns
- def _create_text_elem(self, name, text, type=None):
- elem = self._xml_doc.createElement(name)
- if type:
- elem.setAttribute('type', type)
- elem_text = self._xml_doc.createTextNode(text)
- elem.appendChild(elem_text)
- return elem
-
def _get_most_recent_update(self, versions):
recent = None
for version in versions:
@@ -269,105 +232,64 @@ class VersionsAtomSerializer(wsgi.XMLDictSerializer):
link_href = link_href.rstrip('/')
return link_href.rsplit('/', 1)[0] + '/'
- def _create_detail_meta(self, root, version):
- title = self._create_text_elem('title', "About This Version",
- type='text')
-
- updated = self._create_text_elem('updated', version['updated'])
-
- uri = version['links'][0]['href']
- id = self._create_text_elem('id', uri)
-
- link = self._xml_doc.createElement('link')
- link.setAttribute('rel', 'self')
- link.setAttribute('href', uri)
+ def _create_feed(self, versions, feed_title, feed_id):
+ feed = etree.Element('feed', nsmap=self.NSMAP)
+ title = etree.SubElement(feed, 'title')
+ title.set('type', 'text')
+ title.text = feed_title
- author = self._xml_doc.createElement('author')
- author_name = self._create_text_elem('name', 'Rackspace')
- author_uri = self._create_text_elem('uri', 'http://www.rackspace.com/')
- author.appendChild(author_name)
- author.appendChild(author_uri)
-
- root.appendChild(title)
- root.appendChild(updated)
- root.appendChild(id)
- root.appendChild(author)
- root.appendChild(link)
-
- def _create_list_meta(self, root, versions):
- title = self._create_text_elem('title', "Available API Versions",
- type='text')
# Set this updated to the most recently updated version
recent = self._get_most_recent_update(versions)
- updated = self._create_text_elem('updated', recent)
-
- base_url = self._get_base_url(versions[0]['links'][0]['href'])
- id = self._create_text_elem('id', base_url)
+ etree.SubElement(feed, 'updated').text = recent
- link = self._xml_doc.createElement('link')
- link.setAttribute('rel', 'self')
- link.setAttribute('href', base_url)
+ etree.SubElement(feed, 'id').text = feed_id
- author = self._xml_doc.createElement('author')
- author_name = self._create_text_elem('name', 'Rackspace')
- author_uri = self._create_text_elem('uri', 'http://www.rackspace.com/')
- author.appendChild(author_name)
- author.appendChild(author_uri)
+ link = etree.SubElement(feed, 'link')
+ link.set('rel', 'self')
+ link.set('href', feed_id)
- root.appendChild(title)
- root.appendChild(updated)
- root.appendChild(id)
- root.appendChild(author)
- root.appendChild(link)
+ author = etree.SubElement(feed, 'author')
+ etree.SubElement(author, 'name').text = 'Rackspace'
+ etree.SubElement(author, 'uri').text = 'http://www.rackspace.com/'
- def _create_version_entries(self, root, versions):
for version in versions:
- entry = self._xml_doc.createElement('entry')
-
- id = self._create_text_elem('id', version['links'][0]['href'])
- title = self._create_text_elem('title',
- 'Version %s' % version['id'],
- type='text')
- updated = self._create_text_elem('updated', version['updated'])
-
- entry.appendChild(id)
- entry.appendChild(title)
- entry.appendChild(updated)
-
- for link in version['links']:
- link_node = self._xml_doc.createElement('link')
- link_node.setAttribute('rel', link['rel'])
- link_node.setAttribute('href', link['href'])
- if 'type' in link:
- link_node.setAttribute('type', link['type'])
-
- entry.appendChild(link_node)
-
- content = self._create_text_elem('content',
- 'Version %s %s (%s)' %
- (version['id'],
- version['status'],
- version['updated']),
- type='text')
-
- entry.appendChild(content)
- root.appendChild(entry)
+ feed.append(self._create_version_entry(version))
+
+ return feed
+
+ def _create_version_entry(self, version):
+ entry = etree.Element('entry')
+ etree.SubElement(entry, 'id').text = version['links'][0]['href']
+ title = etree.SubElement(entry, 'title')
+ title.set('type', 'text')
+ title.text = 'Version %s' % version['id']
+ etree.SubElement(entry, 'updated').text = version['updated']
+
+ for link in version['links']:
+ link_elem = etree.SubElement(entry, 'link')
+ link_elem.set('rel', link['rel'])
+ link_elem.set('href', link['href'])
+ if 'type' in link:
+ link_elem.set('type', link['type'])
+
+ content = etree.SubElement(entry, 'content')
+ content.set('type', 'text')
+ content.text = 'Version %s %s (%s)' % (version['id'],
+ version['status'],
+ version['updated'])
+ return entry
def index(self, data):
- self._xml_doc = minidom.Document()
- node = self._xml_doc.createElementNS(self.xmlns, 'feed')
- self._create_list_meta(node, data['versions'])
- self._create_version_entries(node, data['versions'])
-
- return self.to_xml_string(node)
+ versions = data['versions']
+ feed_id = self._get_base_url(versions[0]['links'][0]['href'])
+ feed = self._create_feed(versions, 'Available API Versions', feed_id)
+ return self._to_xml(feed)
def show(self, data):
- self._xml_doc = minidom.Document()
- node = self._xml_doc.createElementNS(self.xmlns, 'feed')
- self._create_detail_meta(node, data['version'])
- self._create_version_entries(node, [data['version']])
-
- return self.to_xml_string(node)
+ version = data['version']
+ feed_id = version['links'][0]['href']
+ feed = self._create_feed([version], 'About This Version', feed_id)
+ return self._to_xml(feed)
class VersionsHeadersSerializer(wsgi.ResponseHeadersSerializer):
@@ -388,7 +310,9 @@ def create_resource(version='1.0'):
serializer = wsgi.ResponseSerializer(body_serializers)
supported_content_types = ('application/json',
+ 'application/vnd.openstack.compute+json',
'application/xml',
+ 'application/vnd.openstack.compute+xml',
'application/atom+xml')
deserializer = wsgi.RequestDeserializer(
supported_content_types=supported_content_types)
diff --git a/nova/api/openstack/views/flavors.py b/nova/api/openstack/views/flavors.py
index aea34b424..def969a6c 100644
--- a/nova/api/openstack/views/flavors.py
+++ b/nova/api/openstack/views/flavors.py
@@ -50,6 +50,9 @@ class ViewBuilder(object):
"disk": flavor_obj["local_gb"],
}
+ for key in ("vcpus", "swap", "rxtx_quota", "rxtx_cap"):
+ detail[key] = flavor_obj.get(key, "")
+
detail.update(simple)
return detail
diff --git a/nova/api/openstack/views/images.py b/nova/api/openstack/views/images.py
index 21f1b2d3e..659bfd463 100644
--- a/nova/api/openstack/views/images.py
+++ b/nova/api/openstack/views/images.py
@@ -18,6 +18,7 @@
import os.path
from nova.api.openstack import common
+from nova import utils
class ViewBuilder(object):
@@ -37,17 +38,18 @@ class ViewBuilder(object):
def _format_status(self, image):
"""Update the status field to standardize format."""
status_mapping = {
- 'pending': 'QUEUED',
- 'decrypting': 'PREPARING',
- 'untarring': 'SAVING',
- 'available': 'ACTIVE',
- 'killed': 'FAILED',
+ 'active': 'ACTIVE',
+ 'queued': 'SAVING',
+ 'saving': 'SAVING',
+ 'deleted': 'DELETED',
+ 'pending_delete': 'DELETED',
+ 'killed': 'ERROR',
}
try:
- image['status'] = status_mapping[image['status']].upper()
+ image['status'] = status_mapping[image['status']]
except KeyError:
- image['status'] = image['status'].upper()
+ image['status'] = 'UNKNOWN'
def _build_server(self, image, image_obj):
"""Indicates that you must use a ViewBuilder subclass."""
@@ -70,6 +72,7 @@ class ViewBuilder(object):
}
self._build_server(image, image_obj)
+ self._build_image_id(image, image_obj)
if detail:
image.update({
@@ -95,6 +98,12 @@ class ViewBuilderV10(ViewBuilder):
except (KeyError, ValueError):
pass
+ def _build_image_id(self, image, image_obj):
+ try:
+ image['id'] = int(image_obj['id'])
+ except ValueError:
+ pass
+
class ViewBuilderV11(ViewBuilder):
"""OpenStack API v1.1 Image Builder"""
@@ -118,6 +127,9 @@ class ViewBuilderV11(ViewBuilder):
except KeyError:
return
+ def _build_image_id(self, image, image_obj):
+ image['id'] = "%s" % image_obj['id']
+
def generate_href(self, image_id):
"""Return an href string pointing to this object."""
return os.path.join(self.base_url, self.project_id,
@@ -128,6 +140,7 @@ class ViewBuilderV11(ViewBuilder):
image = ViewBuilder.build(self, image_obj, detail)
href = self.generate_href(image_obj["id"])
bookmark = self.generate_bookmark(image_obj["id"])
+ alternate = self.generate_alternate(image_obj["id"])
image["links"] = [
{
@@ -138,6 +151,11 @@ class ViewBuilderV11(ViewBuilder):
"rel": "bookmark",
"href": bookmark,
},
+ {
+ "rel": "alternate",
+ "type": "application/vnd.openstack.image",
+ "href": alternate,
+ },
]
@@ -147,6 +165,13 @@ class ViewBuilderV11(ViewBuilder):
return image
def generate_bookmark(self, image_id):
- """Create an url that refers to a specific flavor id."""
+ """Create a URL that refers to a specific flavor id."""
return os.path.join(common.remove_version_from_href(self.base_url),
self.project_id, "images", str(image_id))
+
+ def generate_alternate(self, image_id):
+ """Create an alternate link for a specific flavor id."""
+ glance_url = utils.generate_glance_url()
+
+ return "%s/%s/images/%s" % (glance_url, self.project_id,
+ str(image_id))
diff --git a/nova/api/openstack/views/servers.py b/nova/api/openstack/views/servers.py
index ac09b5864..473dc9e7e 100644
--- a/nova/api/openstack/views/servers.py
+++ b/nova/api/openstack/views/servers.py
@@ -145,6 +145,8 @@ class ViewBuilderV11(ViewBuilder):
response['server']['accessIPv4'] = inst.get('access_ip_v4') or ""
response['server']['accessIPv6'] = inst.get('access_ip_v6') or ""
+ response['server']['key_name'] = inst.get('key_name', '')
+ response['server']['config_drive'] = inst.get('config_drive')
return response
@@ -185,8 +187,6 @@ class ViewBuilderV11(ViewBuilder):
def _build_extra(self, response, inst):
self._build_links(response, inst)
response['uuid'] = inst['uuid']
- response['key_name'] = inst.get('key_name', '')
- self._build_config_drive(response, inst)
def _build_links(self, response, inst):
href = self.generate_href(inst["id"])
@@ -205,9 +205,6 @@ class ViewBuilderV11(ViewBuilder):
response["links"] = links
- def _build_config_drive(self, response, inst):
- response['config_drive'] = inst.get('config_drive')
-
def generate_href(self, server_id):
"""Create an url that refers to a specific server id."""
return os.path.join(self.base_url, self.project_id,
diff --git a/nova/api/openstack/views/versions.py b/nova/api/openstack/views/versions.py
index 03da80818..1ac398706 100644
--- a/nova/api/openstack/views/versions.py
+++ b/nova/api/openstack/views/versions.py
@@ -52,7 +52,7 @@ class ViewBuilder(object):
def build_versions(self, versions):
version_objs = []
- for version in versions:
+ for version in sorted(versions.keys()):
version = versions[version]
version_objs.append({
"id": version['id'],
diff --git a/nova/api/openstack/wsgi.py b/nova/api/openstack/wsgi.py
index 8641e960a..180f328b9 100644
--- a/nova/api/openstack/wsgi.py
+++ b/nova/api/openstack/wsgi.py
@@ -1,5 +1,22 @@
+# 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.
import json
+from lxml import etree
import webob
from xml.dom import minidom
from xml.parsers import expat
@@ -18,6 +35,21 @@ XMLNS_ATOM = 'http://www.w3.org/2005/Atom'
LOG = logging.getLogger('nova.api.openstack.wsgi')
+# The vendor content types should serialize identically to the non-vendor
+# content types. So to avoid littering the code with both options, we
+# map the vendor to the other when looking up the type
+_CONTENT_TYPE_MAP = {
+ 'application/vnd.openstack.compute+json': 'application/json',
+ 'application/vnd.openstack.compute+xml': 'application/xml',
+}
+
+_SUPPORTED_CONTENT_TYPES = (
+ 'application/json',
+ 'application/vnd.openstack.compute+json',
+ 'application/xml',
+ 'application/vnd.openstack.compute+xml',
+)
+
class Request(webob.Request):
"""Add some Openstack API-specific logic to the base webob.Request."""
@@ -29,7 +61,7 @@ class Request(webob.Request):
"""
supported_content_types = supported_content_types or \
- ('application/json', 'application/xml')
+ _SUPPORTED_CONTENT_TYPES
parts = self.path.rsplit('.', 1)
if len(parts) > 1:
@@ -51,7 +83,7 @@ class Request(webob.Request):
if not "Content-Type" in self.headers:
return None
- allowed_types = ("application/xml", "application/json")
+ allowed_types = _SUPPORTED_CONTENT_TYPES
content_type = self.content_type
if content_type not in allowed_types:
@@ -191,7 +223,7 @@ class RequestDeserializer(object):
supported_content_types=None):
self.supported_content_types = supported_content_types or \
- ('application/json', 'application/xml')
+ _SUPPORTED_CONTENT_TYPES
self.body_deserializers = {
'application/xml': XMLDeserializer(),
@@ -249,7 +281,8 @@ class RequestDeserializer(object):
def get_body_deserializer(self, content_type):
try:
- return self.body_deserializers[content_type]
+ ctype = _CONTENT_TYPE_MAP.get(content_type, content_type)
+ return self.body_deserializers[ctype]
except (KeyError, TypeError):
raise exception.InvalidContentType(content_type=content_type)
@@ -315,7 +348,7 @@ class XMLDictSerializer(DictSerializer):
def to_xml_string(self, node, has_atom=False):
self._add_xmlns(node, has_atom)
- return node.toprettyxml(indent=' ', encoding='UTF-8')
+ return node.toxml('UTF-8')
#NOTE (ameade): the has_atom should be removed after all of the
# xml serializers and view builders have been updated to the current
@@ -392,6 +425,10 @@ class XMLDictSerializer(DictSerializer):
link_nodes.append(link_node)
return link_nodes
+ def _to_xml(self, root):
+ """Convert the xml object to an xml string."""
+ return etree.tostring(root, encoding='UTF-8', xml_declaration=True)
+
class ResponseHeadersSerializer(ActionDispatcher):
"""Default response headers serialization"""
@@ -439,7 +476,8 @@ class ResponseSerializer(object):
def get_body_serializer(self, content_type):
try:
- return self.body_serializers[content_type]
+ ctype = _CONTENT_TYPE_MAP.get(content_type, content_type)
+ return self.body_serializers[ctype]
except (KeyError, TypeError):
raise exception.InvalidContentType(content_type=content_type)