summaryrefslogtreecommitdiffstats
path: root/nova/api
diff options
context:
space:
mode:
authorIsaku Yamahata <yamahata@valinux.co.jp>2011-08-02 11:36:58 +0900
committerIsaku Yamahata <yamahata@valinux.co.jp>2011-08-02 11:36:58 +0900
commit0ffc3b80490c24ee116f5be070249dcbbed9357b (patch)
tree74b5a542119899a9b483288d2fa6a354751d4090 /nova/api
parenta52b643b18e1bac18b642ecfd781809eb5612763 (diff)
parentbdcfaa5b927a096f507fb0f7e2d81989173957f8 (diff)
downloadnova-0ffc3b80490c24ee116f5be070249dcbbed9357b.tar.gz
nova-0ffc3b80490c24ee116f5be070249dcbbed9357b.tar.xz
nova-0ffc3b80490c24ee116f5be070249dcbbed9357b.zip
merged with nova trunk
Diffstat (limited to 'nova/api')
-rw-r--r--nova/api/direct.py3
-rw-r--r--nova/api/ec2/__init__.py32
-rw-r--r--nova/api/ec2/cloud.py142
-rw-r--r--nova/api/openstack/__init__.py10
-rw-r--r--nova/api/openstack/auth.py28
-rw-r--r--nova/api/openstack/common.py107
-rw-r--r--nova/api/openstack/contrib/volumes.py53
-rw-r--r--nova/api/openstack/create_instance_helper.py103
-rw-r--r--nova/api/openstack/extensions.py51
-rw-r--r--nova/api/openstack/faults.py17
-rw-r--r--nova/api/openstack/image_metadata.py71
-rw-r--r--nova/api/openstack/images.py2
-rw-r--r--nova/api/openstack/ips.py56
-rw-r--r--nova/api/openstack/servers.py203
-rw-r--r--nova/api/openstack/views/servers.py46
-rw-r--r--nova/api/openstack/wsgi.py42
16 files changed, 724 insertions, 242 deletions
diff --git a/nova/api/direct.py b/nova/api/direct.py
index ec79151b1..993815fc7 100644
--- a/nova/api/direct.py
+++ b/nova/api/direct.py
@@ -107,7 +107,8 @@ class DelegatedAuthMiddleware(wsgi.Middleware):
def process_request(self, request):
os_user = request.headers['X-OpenStack-User']
os_project = request.headers['X-OpenStack-Project']
- context_ref = context.RequestContext(user=os_user, project=os_project)
+ context_ref = context.RequestContext(user_id=os_user,
+ project_id=os_project)
request.environ['openstack.context'] = context_ref
diff --git a/nova/api/ec2/__init__.py b/nova/api/ec2/__init__.py
index cf1734281..af232edda 100644
--- a/nova/api/ec2/__init__.py
+++ b/nova/api/ec2/__init__.py
@@ -66,7 +66,7 @@ class RequestLogging(wsgi.Middleware):
else:
controller = None
action = None
- ctxt = request.environ.get('ec2.context', None)
+ ctxt = request.environ.get('nova.context', None)
delta = utils.utcnow() - start
seconds = delta.seconds
microseconds = delta.microseconds
@@ -139,8 +139,7 @@ class Lockout(wsgi.Middleware):
class Authenticate(wsgi.Middleware):
-
- """Authenticate an EC2 request and add 'ec2.context' to WSGI environ."""
+ """Authenticate an EC2 request and add 'nova.context' to WSGI environ."""
@webob.dec.wsgify(RequestClass=wsgi.Request)
def __call__(self, req):
@@ -157,8 +156,9 @@ class Authenticate(wsgi.Middleware):
auth_params.pop('Signature')
# Authenticate the request.
+ authman = manager.AuthManager()
try:
- (user, project) = manager.AuthManager().authenticate(
+ (user, project) = authman.authenticate(
access,
signature,
auth_params,
@@ -174,14 +174,17 @@ class Authenticate(wsgi.Middleware):
remote_address = req.remote_addr
if FLAGS.use_forwarded_for:
remote_address = req.headers.get('X-Forwarded-For', remote_address)
- ctxt = context.RequestContext(user=user,
- project=project,
+ roles = authman.get_active_roles(user, project)
+ ctxt = context.RequestContext(user_id=user.id,
+ project_id=project.id,
+ is_admin=user.is_admin(),
+ roles=roles,
remote_address=remote_address)
- req.environ['ec2.context'] = ctxt
+ req.environ['nova.context'] = ctxt
uname = user.name
pname = project.name
msg = _('Authenticated Request For %(uname)s:%(pname)s)') % locals()
- LOG.audit(msg, context=req.environ['ec2.context'])
+ LOG.audit(msg, context=req.environ['nova.context'])
return self.application
@@ -228,7 +231,7 @@ class Authorizer(wsgi.Middleware):
"""Authorize an EC2 API request.
Return a 401 if ec2.controller and ec2.action in WSGI environ may not be
- executed in ec2.context.
+ executed in nova.context.
"""
def __init__(self, application):
@@ -282,7 +285,7 @@ class Authorizer(wsgi.Middleware):
@webob.dec.wsgify(RequestClass=wsgi.Request)
def __call__(self, req):
- context = req.environ['ec2.context']
+ context = req.environ['nova.context']
controller = req.environ['ec2.request'].controller.__class__.__name__
action = req.environ['ec2.request'].action
allowed_roles = self.action_roles[controller].get(action, ['none'])
@@ -295,28 +298,27 @@ class Authorizer(wsgi.Middleware):
def _matches_any_role(self, context, roles):
"""Return True if any role in roles is allowed in context."""
- if context.user.is_superuser():
+ if context.is_admin:
return True
if 'all' in roles:
return True
if 'none' in roles:
return False
- return any(context.project.has_role(context.user_id, role)
- for role in roles)
+ return any(role in context.roles for role in roles)
class Executor(wsgi.Application):
"""Execute an EC2 API request.
- Executes 'ec2.action' upon 'ec2.controller', passing 'ec2.context' and
+ Executes 'ec2.action' upon 'ec2.controller', passing 'nova.context' and
'ec2.action_args' (all variables in WSGI environ.) Returns an XML
response, or a 400 upon failure.
"""
@webob.dec.wsgify(RequestClass=wsgi.Request)
def __call__(self, req):
- context = req.environ['ec2.context']
+ context = req.environ['nova.context']
api_request = req.environ['ec2.request']
result = None
try:
diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py
index 9b0ec2fde..c4651f374 100644
--- a/nova/api/ec2/cloud.py
+++ b/nova/api/ec2/cloud.py
@@ -554,7 +554,55 @@ class CloudController(object):
g['ipPermissions'] += [r]
return g
- def _revoke_rule_args_to_dict(self, context, to_port=None, from_port=None,
+ def _rule_args_to_dict(self, context, kwargs):
+ rules = []
+ if not 'groups' in kwargs and not 'ip_ranges' in kwargs:
+ rule = self._rule_dict_last_step(context, **kwargs)
+ if rule:
+ rules.append(rule)
+ return rules
+ if 'ip_ranges' in kwargs:
+ rules = self._cidr_args_split(kwargs)
+ else:
+ rules = [kwargs]
+ finalset = []
+ for rule in rules:
+ if 'groups' in rule:
+ groups_values = self._groups_args_split(rule)
+ for groups_value in groups_values:
+ final = self._rule_dict_last_step(context, **groups_value)
+ finalset.append(final)
+ else:
+ final = self._rule_dict_last_step(context, **rule)
+ finalset.append(final)
+ return finalset
+
+ def _cidr_args_split(self, kwargs):
+ cidr_args_split = []
+ cidrs = kwargs['ip_ranges']
+ for key, cidr in cidrs.iteritems():
+ mykwargs = kwargs.copy()
+ del mykwargs['ip_ranges']
+ mykwargs['cidr_ip'] = cidr['cidr_ip']
+ cidr_args_split.append(mykwargs)
+ return cidr_args_split
+
+ def _groups_args_split(self, kwargs):
+ groups_args_split = []
+ groups = kwargs['groups']
+ for key, group in groups.iteritems():
+ mykwargs = kwargs.copy()
+ del mykwargs['groups']
+ if 'group_name' in group:
+ mykwargs['source_security_group_name'] = group['group_name']
+ if 'user_id' in group:
+ mykwargs['source_security_group_owner_id'] = group['user_id']
+ if 'group_id' in group:
+ mykwargs['source_security_group_id'] = group['group_id']
+ groups_args_split.append(mykwargs)
+ return groups_args_split
+
+ def _rule_dict_last_step(self, context, to_port=None, from_port=None,
ip_protocol=None, cidr_ip=None, user_id=None,
source_security_group_name=None,
source_security_group_owner_id=None):
@@ -569,6 +617,9 @@ class CloudController(object):
db.security_group_get_by_name(context.elevated(),
source_project_id,
source_security_group_name)
+ notfound = exception.SecurityGroupNotFound
+ if not source_security_group:
+ raise notfound(security_group_id=source_security_group_name)
values['group_id'] = source_security_group['id']
elif cidr_ip:
# If this fails, it throws an exception. This is what we want.
@@ -607,7 +658,7 @@ class CloudController(object):
for rule in security_group.rules:
if 'group_id' in values:
if rule['group_id'] == values['group_id']:
- return True
+ return rule['id']
else:
is_duplicate = True
for key in ('cidr', 'from_port', 'to_port', 'protocol'):
@@ -615,7 +666,7 @@ class CloudController(object):
is_duplicate = False
break
if is_duplicate:
- return True
+ return rule['id']
return False
def revoke_security_group_ingress(self, context, group_name=None,
@@ -638,22 +689,30 @@ class CloudController(object):
msg = "Revoke security group ingress %s"
LOG.audit(_(msg), security_group['name'], context=context)
-
- criteria = self._revoke_rule_args_to_dict(context, **kwargs)
- if criteria is None:
- raise exception.ApiError(_("Not enough parameters to build a "
- "valid rule."))
-
- for rule in security_group.rules:
- match = True
- for (k, v) in criteria.iteritems():
- if getattr(rule, k, False) != v:
- match = False
- if match:
- db.security_group_rule_destroy(context, rule['id'])
- self.compute_api.trigger_security_group_rules_refresh(context,
- security_group_id=security_group['id'])
- return True
+ prevalues = []
+ try:
+ prevalues = kwargs['ip_permissions']
+ except KeyError:
+ prevalues.append(kwargs)
+ rule_id = None
+ for values in prevalues:
+ rulesvalues = self._rule_args_to_dict(context, values)
+ if not rulesvalues:
+ err = "%s Not enough parameters to build a valid rule"
+ raise exception.ApiError(_(err % rulesvalues))
+
+ for values_for_rule in rulesvalues:
+ values_for_rule['parent_group_id'] = security_group.id
+ rule_id = self._security_group_rule_exists(security_group,
+ values_for_rule)
+ if rule_id:
+ db.security_group_rule_destroy(context, rule_id)
+ if rule_id:
+ # NOTE(vish): we removed a rule, so refresh
+ self.compute_api.trigger_security_group_rules_refresh(
+ context,
+ security_group_id=security_group['id'])
+ return True
raise exception.ApiError(_("No rule for the specified parameters."))
# TODO(soren): This has only been tested with Boto as the client.
@@ -680,22 +739,37 @@ class CloudController(object):
msg = "Authorize security group ingress %s"
LOG.audit(_(msg), security_group['name'], context=context)
- values = self._revoke_rule_args_to_dict(context, **kwargs)
- if values is None:
- raise exception.ApiError(_("Not enough parameters to build a "
- "valid rule."))
- values['parent_group_id'] = security_group.id
-
- if self._security_group_rule_exists(security_group, values):
- raise exception.ApiError(_('This rule already exists in group %s')
- % group_name)
-
- security_group_rule = db.security_group_rule_create(context, values)
+ prevalues = []
+ try:
+ prevalues = kwargs['ip_permissions']
+ except KeyError:
+ prevalues.append(kwargs)
+ postvalues = []
+ for values in prevalues:
+ rulesvalues = self._rule_args_to_dict(context, values)
+ if not rulesvalues:
+ err = "%s Not enough parameters to build a valid rule"
+ raise exception.ApiError(_(err % rulesvalues))
+ for values_for_rule in rulesvalues:
+ values_for_rule['parent_group_id'] = security_group.id
+ if self._security_group_rule_exists(security_group,
+ values_for_rule):
+ err = '%s - This rule already exists in group'
+ raise exception.ApiError(_(err) % values_for_rule)
+ postvalues.append(values_for_rule)
+
+ for values_for_rule in postvalues:
+ security_group_rule = db.security_group_rule_create(
+ context,
+ values_for_rule)
- self.compute_api.trigger_security_group_rules_refresh(context,
- security_group_id=security_group['id'])
+ if postvalues:
+ self.compute_api.trigger_security_group_rules_refresh(
+ context,
+ security_group_id=security_group['id'])
+ return True
- return True
+ raise exception.ApiError(_("No rule for the specified parameters."))
def _get_source_project_id(self, context, source_security_group_owner_id):
if source_security_group_owner_id:
@@ -1274,7 +1348,7 @@ class CloudController(object):
def rescue_instance(self, context, instance_id, **kwargs):
"""This is an extension to the normal ec2_api"""
- self._do_instance(self.compute_api.rescue, contect, instnace_id)
+ self._do_instance(self.compute_api.rescue, context, instance_id)
return True
def unrescue_instance(self, context, instance_id, **kwargs):
diff --git a/nova/api/openstack/__init__.py b/nova/api/openstack/__init__.py
index e87d7c754..868b98a31 100644
--- a/nova/api/openstack/__init__.py
+++ b/nova/api/openstack/__init__.py
@@ -164,11 +164,17 @@ class APIRouterV11(APIRouter):
def _setup_routes(self, mapper):
super(APIRouterV11, self)._setup_routes(mapper, '1.1')
- mapper.resource("image_meta", "meta",
- controller=image_metadata.create_resource(),
+ image_metadata_controller = image_metadata.create_resource()
+ mapper.resource("image_meta", "metadata",
+ controller=image_metadata_controller,
parent_resource=dict(member_name='image',
collection_name='images'))
+ mapper.connect("metadata", "/images/{image_id}/metadata",
+ controller=image_metadata_controller,
+ action='update_all',
+ conditions={"method": ['PUT']})
+
mapper.resource("server_meta", "meta",
controller=server_metadata.create_resource(),
parent_resource=dict(member_name='server',
diff --git a/nova/api/openstack/auth.py b/nova/api/openstack/auth.py
index 7c3e683d6..d42abe1f8 100644
--- a/nova/api/openstack/auth.py
+++ b/nova/api/openstack/auth.py
@@ -48,31 +48,35 @@ class AuthMiddleware(wsgi.Middleware):
def __call__(self, req):
if not self.has_authentication(req):
return self.authenticate(req)
- user = self.get_user_by_authentication(req)
- if not user:
+ user_id = self.get_user_by_authentication(req)
+ if not user_id:
token = req.headers["X-Auth-Token"]
- msg = _("%(user)s could not be found with token '%(token)s'")
+ msg = _("%(user_id)s could not be found with token '%(token)s'")
LOG.warn(msg % locals())
return faults.Fault(webob.exc.HTTPUnauthorized())
try:
- account = req.headers["X-Auth-Project-Id"]
+ project_id = req.headers["X-Auth-Project-Id"]
except KeyError:
# FIXME(usrleon): It needed only for compatibility
# while osapi clients don't use this header
- accounts = self.auth.get_projects(user=user)
- if accounts:
- account = accounts[0]
+ projects = self.auth.get_projects(user_id)
+ if projects:
+ project_id = projects[0].id
else:
return faults.Fault(webob.exc.HTTPUnauthorized())
- if not self.auth.is_admin(user) and \
- not self.auth.is_project_member(user, account):
- msg = _("%(user)s must be an admin or a member of %(account)s")
+ is_admin = self.auth.is_admin(user_id)
+ req.environ['nova.context'] = context.RequestContext(user_id,
+ project_id,
+ is_admin)
+ if not is_admin and not self.auth.is_project_member(user_id,
+ project_id):
+ msg = _("%(user_id)s must be an admin or a "
+ "member of %(project_id)s")
LOG.warn(msg % locals())
return faults.Fault(webob.exc.HTTPUnauthorized())
- req.environ['nova.context'] = context.RequestContext(user, account)
return self.application
def has_authentication(self, req):
@@ -133,7 +137,7 @@ class AuthMiddleware(wsgi.Middleware):
if delta.days >= 2:
self.db.auth_token_destroy(ctxt, token['token_hash'])
else:
- return self.auth.get_user(token['user_id'])
+ return token['user_id']
return None
def _authorize_user(self, username, key, req):
diff --git a/nova/api/openstack/common.py b/nova/api/openstack/common.py
index 57031ebf1..efa4ab385 100644
--- a/nova/api/openstack/common.py
+++ b/nova/api/openstack/common.py
@@ -17,12 +17,14 @@
import re
from urlparse import urlparse
+from xml.dom import minidom
import webob
from nova import exception
from nova import flags
from nova import log as logging
+from nova.api.openstack import wsgi
LOG = logging.getLogger('nova.api.openstack.common')
@@ -167,3 +169,108 @@ def remove_version_from_href(href):
msg = _('href does not contain version')
raise ValueError(msg)
return new_href
+
+
+def get_version_from_href(href):
+ """Returns the api version in the href.
+
+ Returns the api version in the href.
+ If no version is found, 1.0 is returned
+
+ Given: 'http://www.nova.com/123'
+ Returns: '1.0'
+
+ Given: 'http://www.nova.com/v1.1'
+ Returns: '1.1'
+
+ """
+ try:
+ #finds the first instance that matches /v#.#/
+ version = re.findall(r'[/][v][0-9]+\.[0-9]+[/]', href)
+ #if no version was found, try finding /v#.# at the end of the string
+ if not version:
+ version = re.findall(r'[/][v][0-9]+\.[0-9]+$', href)
+ version = re.findall(r'[0-9]+\.[0-9]', version[0])[0]
+ except IndexError:
+ version = '1.0'
+ return version
+
+
+class MetadataXMLDeserializer(wsgi.MetadataXMLDeserializer):
+
+ def _extract_metadata_container(self, datastring):
+ dom = minidom.parseString(datastring)
+ metadata_node = self.find_first_child_named(dom, "metadata")
+ metadata = self.extract_metadata(metadata_node)
+ return {'body': {'metadata': metadata}}
+
+ def create(self, datastring):
+ return self._extract_metadata_container(datastring)
+
+ def update_all(self, datastring):
+ return self._extract_metadata_container(datastring)
+
+ def update(self, datastring):
+ dom = minidom.parseString(datastring)
+ metadata_item = self.extract_metadata(dom)
+ return {'body': {'meta': metadata_item}}
+
+
+class MetadataHeadersSerializer(wsgi.ResponseHeadersSerializer):
+
+ def delete(self, response, data):
+ response.status_int = 204
+
+
+class MetadataXMLSerializer(wsgi.XMLDictSerializer):
+ 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.toprettyxml(indent=' ', encoding='UTF-8')
+
+ def index(self, metadata_dict):
+ return self._meta_list_to_xml_string(metadata_dict)
+
+ def create(self, metadata_dict):
+ return self._meta_list_to_xml_string(metadata_dict)
+
+ def 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.toprettyxml(indent=' ', encoding='UTF-8')
+
+ def show(self, meta_item_dict):
+ return self._meta_item_to_xml_string(meta_item_dict['meta'])
+
+ def update(self, meta_item_dict):
+ return self._meta_item_to_xml_string(meta_item_dict['meta'])
+
+ def default(self, *args, **kwargs):
+ return ''
diff --git a/nova/api/openstack/contrib/volumes.py b/nova/api/openstack/contrib/volumes.py
index 827e36097..867fe301e 100644
--- a/nova/api/openstack/contrib/volumes.py
+++ b/nova/api/openstack/contrib/volumes.py
@@ -22,10 +22,12 @@ from nova import compute
from nova import exception
from nova import flags
from nova import log as logging
+from nova import quota
from nova import volume
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.volumes")
@@ -297,6 +299,53 @@ class VolumeAttachmentController(object):
return {'volumeAttachments': res}
+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]
+
+ builder = self._get_view_builder(req)
+ server = builder.build(inst, is_detail=True)
+ server['server']['adminPass'] = extra_values['password']
+ return server
+
+
class Volumes(extensions.ExtensionDescriptor):
def get_name(self):
return "Volumes"
@@ -330,4 +379,8 @@ class Volumes(extensions.ExtensionDescriptor):
collection_name='servers'))
resources.append(res)
+ res = extensions.ResourceExtension('os-volumes_boot',
+ BootFromVolumeController())
+ resources.append(res)
+
return resources
diff --git a/nova/api/openstack/create_instance_helper.py b/nova/api/openstack/create_instance_helper.py
index 7249f1261..9199c193d 100644
--- a/nova/api/openstack/create_instance_helper.py
+++ b/nova/api/openstack/create_instance_helper.py
@@ -20,6 +20,7 @@ import webob
from webob import exc
from xml.dom import minidom
+from nova import db
from nova import exception
from nova import flags
from nova import log as logging
@@ -29,7 +30,6 @@ from nova import utils
from nova.compute import instance_types
from nova.api.openstack import wsgi
-from nova.auth import manager as auth_manager
LOG = logging.getLogger('nova.api.openstack.create_instance_helper')
@@ -71,13 +71,19 @@ class CreateInstanceHelper(object):
if not body:
raise exc.HTTPUnprocessableEntity()
- context = req.environ['nova.context']
+ if not 'server' in body:
+ raise exc.HTTPUnprocessableEntity()
- password = self.controller._get_server_admin_password(body['server'])
+ server_dict = body['server']
+ context = req.environ['nova.context']
+ password = self.controller._get_server_admin_password(server_dict)
key_name = None
key_data = None
- key_pairs = auth_manager.AuthManager.get_key_pairs(context)
+ # TODO(vish): Key pair access should move into a common library
+ # instead of being accessed directly from the db.
+ key_pairs = db.key_pair_get_all_by_user(context.elevated(),
+ context.user_id)
if key_pairs:
key_pair = key_pairs[0]
key_name = key_pair['name']
@@ -95,7 +101,7 @@ class CreateInstanceHelper(object):
locals())
raise exc.HTTPBadRequest(explanation=msg)
- personality = body['server'].get('personality')
+ personality = server_dict.get('personality')
injected_files = []
if personality:
@@ -107,18 +113,18 @@ class CreateInstanceHelper(object):
msg = _("Invalid flavorRef provided.")
raise exc.HTTPBadRequest(explanation=msg)
- if not 'name' in body['server']:
+ if not 'name' in server_dict:
msg = _("Server name is not defined")
raise exc.HTTPBadRequest(explanation=msg)
- zone_blob = body['server'].get('blob')
- name = body['server']['name']
+ zone_blob = server_dict.get('blob')
+ name = server_dict['name']
self._validate_server_name(name)
name = name.strip()
- reservation_id = body['server'].get('reservation_id')
- min_count = body['server'].get('min_count')
- max_count = body['server'].get('max_count')
+ 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'.
@@ -145,7 +151,7 @@ class CreateInstanceHelper(object):
display_description=name,
key_name=key_name,
key_data=key_data,
- metadata=body['server'].get('metadata', {}),
+ metadata=server_dict.get('metadata', {}),
injected_files=injected_files,
admin_password=password,
zone_blob=zone_blob,
@@ -185,7 +191,7 @@ class CreateInstanceHelper(object):
Overrides normal behavior in the case of xml content
"""
if request.content_type == "application/xml":
- deserializer = ServerCreateRequestXMLDeserializer()
+ deserializer = ServerXMLDeserializer()
return deserializer.deserialize(request.body)
else:
return self._deserialize(request.body, request.get_content_type())
@@ -282,7 +288,7 @@ class CreateInstanceHelper(object):
return password
-class ServerXMLDeserializer(wsgi.XMLDeserializer):
+class ServerXMLDeserializer(wsgi.MetadataXMLDeserializer):
"""
Deserializer to handle xml-formatted server create requests.
@@ -299,61 +305,30 @@ class ServerXMLDeserializer(wsgi.XMLDeserializer):
def _extract_server(self, node):
"""Marshal the server attribute of a parsed request"""
server = {}
- server_node = self._find_first_child_named(node, 'server')
- for attr in ["name", "imageId", "flavorId", "imageRef", "flavorRef"]:
+ server_node = self.find_first_child_named(node, 'server')
+
+ attributes = ["name", "imageId", "flavorId", "imageRef",
+ "flavorRef", "adminPass"]
+ for attr in attributes:
if server_node.getAttribute(attr):
server[attr] = server_node.getAttribute(attr)
- metadata = self._extract_metadata(server_node)
- if metadata is not None:
- server["metadata"] = metadata
- personality = self._extract_personality(server_node)
- if personality is not None:
- server["personality"] = personality
- return server
- def _extract_metadata(self, server_node):
- """Marshal the metadata attribute of a parsed request"""
- metadata_node = self._find_first_child_named(server_node, "metadata")
- if metadata_node is None:
- return None
- metadata = {}
- for meta_node in self._find_children_named(metadata_node, "meta"):
- key = meta_node.getAttribute("key")
- metadata[key] = self._extract_text(meta_node)
- return metadata
+ metadata_node = self.find_first_child_named(server_node, "metadata")
+ server["metadata"] = self.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"""
- personality_node = \
- self._find_first_child_named(server_node, "personality")
- if personality_node is None:
- return None
+ node = self.find_first_child_named(server_node, "personality")
personality = []
- for file_node in self._find_children_named(personality_node, "file"):
- item = {}
- if file_node.hasAttribute("path"):
- item["path"] = file_node.getAttribute("path")
- item["contents"] = self._extract_text(file_node)
- personality.append(item)
+ 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
-
- def _find_first_child_named(self, parent, name):
- """Search a nodes children for the first child with a given name"""
- for node in parent.childNodes:
- if node.nodeName == name:
- return node
- return None
-
- def _find_children_named(self, parent, name):
- """Return all of a nodes children who have the given name"""
- for node in parent.childNodes:
- if node.nodeName == name:
- yield node
-
- def _extract_text(self, node):
- """Get the text field contained by the given node"""
- if len(node.childNodes) == 1:
- child = node.childNodes[0]
- if child.nodeType == child.TEXT_NODE:
- return child.nodeValue
- return ""
diff --git a/nova/api/openstack/extensions.py b/nova/api/openstack/extensions.py
index da06ecd15..cc889703e 100644
--- a/nova/api/openstack/extensions.py
+++ b/nova/api/openstack/extensions.py
@@ -23,6 +23,7 @@ import sys
import routes
import webob.dec
import webob.exc
+from xml.etree import ElementTree
from nova import exception
from nova import flags
@@ -194,7 +195,7 @@ class ExtensionsResource(wsgi.Resource):
def show(self, req, id):
# NOTE(dprince): the extensions alias is used as the 'id' for show
ext = self.extension_manager.extensions[id]
- return self._translate(ext)
+ return dict(extension=self._translate(ext))
def delete(self, req, id):
raise faults.Fault(webob.exc.HTTPNotFound())
@@ -258,15 +259,18 @@ class ExtensionMiddleware(base_wsgi.Middleware):
mapper = routes.Mapper()
+ serializer = wsgi.ResponseSerializer(
+ {'application/xml': ExtensionsXMLSerializer()})
# extended resources
for resource in ext_mgr.get_resources():
LOG.debug(_('Extended resource: %s'),
resource.collection)
mapper.resource(resource.collection, resource.collection,
- controller=wsgi.Resource(resource.controller),
- collection=resource.collection_actions,
- member=resource.member_actions,
- parent_resource=resource.parent)
+ controller=wsgi.Resource(
+ resource.controller, serializer=serializer),
+ collection=resource.collection_actions,
+ member=resource.member_actions,
+ parent_resource=resource.parent)
# extended actions
action_resources = self._action_ext_resources(application, ext_mgr,
@@ -462,3 +466,40 @@ class ResourceExtension(object):
self.parent = parent
self.collection_actions = collection_actions
self.member_actions = member_actions
+
+
+class ExtensionsXMLSerializer(wsgi.XMLDictSerializer):
+
+ def show(self, ext_dict):
+ ext = self._create_ext_elem(ext_dict['extension'])
+ return self._to_xml(ext)
+
+ def index(self, exts_dict):
+ exts = ElementTree.Element('extensions')
+ for ext_dict in exts_dict['extensions']:
+ exts.append(self._create_ext_elem(ext_dict))
+ return self._to_xml(exts)
+
+ def _create_ext_elem(self, ext_dict):
+ """Create an extension xml element from a dict."""
+ ext_elem = ElementTree.Element('extension')
+ ext_elem.set('name', ext_dict['name'])
+ ext_elem.set('namespace', ext_dict['namespace'])
+ ext_elem.set('alias', ext_dict['alias'])
+ ext_elem.set('updated', ext_dict['updated'])
+ desc = ElementTree.Element('description')
+ desc.text = ext_dict['description']
+ ext_elem.append(desc)
+ for link in ext_dict.get('links', []):
+ elem = ElementTree.Element('atom:link')
+ elem.set('rel', link['rel'])
+ elem.set('href', link['href'])
+ elem.set('type', link['type'])
+ ext_elem.append(elem)
+ return ext_elem
+
+ def _to_xml(self, root):
+ """Convert the xml tree object to an xml string."""
+ root.set('xmlns', wsgi.XMLNS_V11)
+ root.set('xmlns:atom', wsgi.XMLNS_ATOM)
+ return ElementTree.tostring(root, encoding='UTF-8')
diff --git a/nova/api/openstack/faults.py b/nova/api/openstack/faults.py
index 24cde69e4..1ab45d4f1 100644
--- a/nova/api/openstack/faults.py
+++ b/nova/api/openstack/faults.py
@@ -19,6 +19,7 @@
import webob.dec
import webob.exc
+from nova.api.openstack import common
from nova.api.openstack import wsgi
@@ -61,9 +62,13 @@ class Fault(webob.exc.HTTPException):
content_type = req.best_match_content_type()
+ xml_serializer = {
+ '1.0': wsgi.XMLDictSerializer(metadata, wsgi.XMLNS_V10),
+ '1.1': wsgi.XMLDictSerializer(metadata, wsgi.XMLNS_V11),
+ }[common.get_version_from_href(req.url)]
+
serializer = {
- 'application/xml': wsgi.XMLDictSerializer(metadata=metadata,
- xmlns=wsgi.XMLNS_V10),
+ 'application/xml': xml_serializer,
'application/json': wsgi.JSONDictSerializer(),
}[content_type]
@@ -100,9 +105,13 @@ class OverLimitFault(webob.exc.HTTPException):
content_type = request.best_match_content_type()
metadata = {"attributes": {"overLimitFault": "code"}}
+ xml_serializer = {
+ '1.0': wsgi.XMLDictSerializer(metadata, wsgi.XMLNS_V10),
+ '1.1': wsgi.XMLDictSerializer(metadata, wsgi.XMLNS_V11),
+ }[common.get_version_from_href(request.url)]
+
serializer = {
- 'application/xml': wsgi.XMLDictSerializer(metadata=metadata,
- xmlns=wsgi.XMLNS_V10),
+ 'application/xml': xml_serializer,
'application/json': wsgi.JSONDictSerializer(),
}[content_type]
diff --git a/nova/api/openstack/image_metadata.py b/nova/api/openstack/image_metadata.py
index c0fc8c09b..aaf64a123 100644
--- a/nova/api/openstack/image_metadata.py
+++ b/nova/api/openstack/image_metadata.py
@@ -16,12 +16,12 @@
# under the License.
from webob import exc
-from xml.dom import minidom
from nova import flags
from nova import image
from nova import quota
from nova import utils
+from nova.api.openstack import common
from nova.api.openstack import wsgi
@@ -96,8 +96,16 @@ class Controller(object):
self._check_quota_limit(context, metadata)
img['properties'] = metadata
self.image_service.update(context, image_id, img, None)
+ return dict(meta=meta)
- return req.body
+ def update_all(self, req, image_id, body):
+ context = req.environ['nova.context']
+ img = self.image_service.show(context, image_id)
+ metadata = body.get('metadata', {})
+ self._check_quota_limit(context, metadata)
+ img['properties'] = metadata
+ self.image_service.update(context, image_id, img, None)
+ return dict(metadata=metadata)
def delete(self, req, image_id, id):
context = req.environ['nova.context']
@@ -110,58 +118,17 @@ class Controller(object):
self.image_service.update(context, image_id, img, None)
-class ImageMetadataXMLSerializer(wsgi.XMLDictSerializer):
- def __init__(self, xmlns=wsgi.XMLNS_V11):
- super(ImageMetadataXMLSerializer, self).__init__(xmlns=xmlns)
-
- def _meta_item_to_xml(self, doc, key, value):
- node = doc.createElement('meta')
- 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.toprettyxml(indent=' ', encoding='UTF-8')
-
- def index(self, metadata_dict):
- return self._meta_list_to_xml_string(metadata_dict)
-
- def create(self, metadata_dict):
- return self._meta_list_to_xml_string(metadata_dict)
-
- def _meta_item_to_xml_string(self, meta_item_dict):
- xml_doc = minidom.Document()
- item_key, item_value = meta_item_dict.items()[0]
- item_node = self._meta_item_to_xml(xml_doc, item_key, item_value)
- xml_doc.appendChild(item_node)
- self._add_xmlns(item_node)
- return xml_doc.toprettyxml(indent=' ', encoding='UTF-8')
-
- def show(self, meta_item_dict):
- return self._meta_item_to_xml_string(meta_item_dict['meta'])
-
- def update(self, meta_item_dict):
- return self._meta_item_to_xml_string(meta_item_dict['meta'])
+def create_resource():
+ headers_serializer = common.MetadataHeadersSerializer()
+ body_deserializers = {
+ 'application/xml': common.MetadataXMLDeserializer(),
+ }
-def create_resource():
body_serializers = {
- 'application/xml': ImageMetadataXMLSerializer(),
+ 'application/xml': common.MetadataXMLSerializer(),
}
- serializer = wsgi.ResponseSerializer(body_serializers)
+ serializer = wsgi.ResponseSerializer(body_serializers, headers_serializer)
+ deserializer = wsgi.RequestDeserializer(body_deserializers)
- return wsgi.Resource(Controller(), serializer=serializer)
+ return wsgi.Resource(Controller(), deserializer, serializer)
diff --git a/nova/api/openstack/images.py b/nova/api/openstack/images.py
index 30e4fd389..9ba8b639e 100644
--- a/nova/api/openstack/images.py
+++ b/nova/api/openstack/images.py
@@ -284,7 +284,7 @@ class ImageXMLSerializer(wsgi.XMLDictSerializer):
xmlns = wsgi.XMLNS_V11
def __init__(self):
- self.metadata_serializer = image_metadata.ImageMetadataXMLSerializer()
+ self.metadata_serializer = common.MetadataXMLSerializer()
def _image_to_xml(self, xml_doc, image):
image_node = xml_doc.createElement('image')
diff --git a/nova/api/openstack/ips.py b/nova/api/openstack/ips.py
index 2996b032d..a74fae487 100644
--- a/nova/api/openstack/ips.py
+++ b/nova/api/openstack/ips.py
@@ -16,6 +16,7 @@
# under the License.
import time
+from xml.dom import minidom
from webob import exc
@@ -100,17 +101,51 @@ class ControllerV11(Controller):
return nova.api.openstack.views.addresses.ViewBuilderV11()
+class IPXMLSerializer(wsgi.XMLDictSerializer):
+ 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)
+
+ 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)
+
+
def create_resource(version):
controller = {
'1.0': ControllerV10,
'1.1': ControllerV11,
}[version]()
- xmlns = {
- '1.0': wsgi.XMLNS_V10,
- '1.1': wsgi.XMLNS_V11,
- }[version]
-
metadata = {
'list_collections': {
'public': {'item_name': 'ip', 'item_key': 'addr'},
@@ -118,10 +153,11 @@ def create_resource(version):
},
}
- body_serializers = {
- 'application/xml': wsgi.XMLDictSerializer(metadata=metadata,
- xmlns=xmlns),
- }
- serializer = wsgi.ResponseSerializer(body_serializers)
+ xml_serializer = {
+ '1.0': wsgi.XMLDictSerializer(metadata=metadata, xmlns=wsgi.XMLNS_V11),
+ '1.1': IPXMLSerializer(),
+ }[version]
+
+ serializer = wsgi.ResponseSerializer({'application/xml': xml_serializer})
return wsgi.Resource(controller, serializer=serializer)
diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py
index 7bef1d9b2..30169d450 100644
--- a/nova/api/openstack/servers.py
+++ b/nova/api/openstack/servers.py
@@ -17,23 +17,25 @@ import base64
import traceback
from webob import exc
+from xml.dom import minidom
import webob
from nova import compute
-from nova import db
from nova import exception
from nova import flags
from nova import log as logging
from nova import utils
from nova.api.openstack import common
from nova.api.openstack import create_instance_helper as helper
+from nova.api.openstack import 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
import nova.api.openstack.views.flavors
import nova.api.openstack.views.images
import nova.api.openstack.views.servers
-from nova.api.openstack import wsgi
-import nova.api.openstack
-from nova.scheduler import api as scheduler_api
LOG = logging.getLogger('nova.api.openstack.servers')
@@ -436,18 +438,25 @@ class ControllerV10(Controller):
def _action_resize(self, input_dict, req, id):
""" Resizes a given instance to the flavor size requested """
- if 'resize' in input_dict and 'flavorId' in input_dict['resize']:
- flavor_id = input_dict['resize']['flavorId']
- self.compute_api.resize(req.environ['nova.context'], id,
- flavor_id)
- else:
- LOG.exception(_("Missing 'flavorId' argument for resize"))
- raise exc.HTTPUnprocessableEntity()
+ try:
+ flavor_id = input_dict["resize"]["flavorId"]
+ except (KeyError, TypeError):
+ msg = _("Resize requests require 'flavorId' attribute.")
+ raise exc.HTTPBadRequest(explanation=msg)
+
+ try:
+ i_type = instance_types.get_instance_type_by_flavor_id(flavor_id)
+ except exception.FlavorNotFound:
+ msg = _("Unable to locate requested flavor.")
+ raise exc.HTTPBadRequest(explanation=msg)
+
+ context = req.environ["nova.context"]
+ self.compute_api.resize(context, id, i_type["id"])
+
return webob.Response(status_int=202)
def _action_rebuild(self, info, request, instance_id):
context = request.environ['nova.context']
- instance_id = int(instance_id)
try:
image_id = info["rebuild"]["imageId"]
@@ -459,7 +468,7 @@ class ControllerV10(Controller):
try:
self.compute_api.rebuild(context, instance_id, image_id)
except exception.BuildInProgress:
- msg = _("Instance %d is currently being rebuilt.") % instance_id
+ msg = _("Instance %s is currently being rebuilt.") % instance_id
LOG.debug(msg)
raise exc.HTTPConflict(explanation=msg)
@@ -481,11 +490,20 @@ class ControllerV11(Controller):
raise exc.HTTPNotFound()
def _image_ref_from_req_data(self, data):
- return data['server']['imageRef']
+ try:
+ return data['server']['imageRef']
+ except (TypeError, KeyError):
+ msg = _("Missing imageRef attribute")
+ raise exc.HTTPBadRequest(explanation=msg)
def _flavor_id_from_req_data(self, data):
- href = data['server']['flavorRef']
- return common.get_id_from_href(href)
+ try:
+ flavor_ref = data['server']['flavorRef']
+ except (TypeError, KeyError):
+ msg = _("Missing flavorRef attribute")
+ raise exc.HTTPBadRequest(explanation=msg)
+
+ return common.get_id_from_href(flavor_ref)
def _build_view(self, req, instance, is_detail=False):
base_url = req.application_url
@@ -545,22 +563,24 @@ class ControllerV11(Controller):
def _action_resize(self, input_dict, req, id):
""" Resizes a given instance to the flavor size requested """
try:
- if 'resize' in input_dict and 'flavorRef' in input_dict['resize']:
- flavor_ref = input_dict['resize']['flavorRef']
- flavor_id = common.get_id_from_href(flavor_ref)
- self.compute_api.resize(req.environ['nova.context'], id,
- flavor_id)
- else:
- LOG.exception(_("Missing 'flavorRef' argument for resize"))
- raise exc.HTTPUnprocessableEntity()
- except Exception, e:
- LOG.exception(_("Error in resize %s"), e)
- raise exc.HTTPBadRequest()
+ flavor_ref = input_dict["resize"]["flavorRef"]
+ except (KeyError, TypeError):
+ msg = _("Resize requests require 'flavorRef' attribute.")
+ raise exc.HTTPBadRequest(explanation=msg)
+
+ try:
+ i_type = instance_types.get_instance_type_by_flavor_id(flavor_ref)
+ except exception.FlavorNotFound:
+ msg = _("Unable to locate requested flavor.")
+ raise exc.HTTPBadRequest(explanation=msg)
+
+ context = req.environ["nova.context"]
+ self.compute_api.resize(context, id, i_type["id"])
+
return webob.Response(status_int=202)
def _action_rebuild(self, info, request, instance_id):
context = request.environ['nova.context']
- instance_id = int(instance_id)
try:
image_href = info["rebuild"]["imageRef"]
@@ -581,7 +601,7 @@ class ControllerV11(Controller):
self.compute_api.rebuild(context, instance_id, image_href, name,
metadata, personalities)
except exception.BuildInProgress:
- msg = _("Instance %d is currently being rebuilt.") % instance_id
+ msg = _("Instance %s is currently being rebuilt.") % instance_id
LOG.debug(msg)
raise exc.HTTPConflict(explanation=msg)
@@ -601,6 +621,123 @@ class HeadersSerializer(wsgi.ResponseHeadersSerializer):
response.status_int = 204
+class ServerXMLSerializer(wsgi.XMLDictSerializer):
+
+ xmlns = wsgi.XMLNS_V11
+
+ 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('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 '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)
+
+ return server_node
+
+ def _server_list_to_xml(self, xml_doc, servers, detailed):
+ container_node = xml_doc.createElement('servers')
+ 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
+
+ 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)
+
+ 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)
+
+ 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)
+
+ 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)
+
+
def create_resource(version='1.0'):
controller = {
'1.0': ControllerV10,
@@ -630,9 +767,13 @@ def create_resource(version='1.0'):
headers_serializer = HeadersSerializer()
+ xml_serializer = {
+ '1.0': wsgi.XMLDictSerializer(metadata, wsgi.XMLNS_V10),
+ '1.1': ServerXMLSerializer(),
+ }[version]
+
body_serializers = {
- 'application/xml': wsgi.XMLDictSerializer(metadata=metadata,
- xmlns=xmlns),
+ 'application/xml': xml_serializer,
}
body_deserializers = {
diff --git a/nova/api/openstack/views/servers.py b/nova/api/openstack/views/servers.py
index 7131db088..2873a8e0f 100644
--- a/nova/api/openstack/views/servers.py
+++ b/nova/api/openstack/views/servers.py
@@ -15,6 +15,7 @@
# License for the specific language governing permissions and limitations
# under the License.
+import datetime
import hashlib
import os
@@ -50,7 +51,7 @@ class ViewBuilder(object):
else:
server = self._build_simple(inst)
- self._build_extra(server, inst)
+ self._build_extra(server['server'], inst)
return server
@@ -99,7 +100,6 @@ class ViewBuilder(object):
self._build_flavor(inst_dict, inst)
self._build_addresses(inst_dict, inst)
- inst_dict['uuid'] = inst['uuid']
return dict(server=inst_dict)
def _build_addresses(self, response, inst):
@@ -121,6 +121,9 @@ class ViewBuilder(object):
class ViewBuilderV10(ViewBuilder):
"""Model an Openstack API V1.0 server response."""
+ def _build_extra(self, response, inst):
+ response['uuid'] = inst['uuid']
+
def _build_image(self, response, inst):
if 'image_ref' in dict(inst):
image_ref = inst['image_ref']
@@ -145,18 +148,46 @@ class ViewBuilderV11(ViewBuilder):
self.image_builder = image_builder
self.base_url = base_url
+ def _build_detail(self, inst):
+ response = super(ViewBuilderV11, self)._build_detail(inst)
+ response['server']['created'] = utils.isotime(inst['created_at'])
+ response['server']['updated'] = utils.isotime(inst['updated_at'])
+ if 'status' in response['server']:
+ if response['server']['status'] == "ACTIVE":
+ response['server']['progress'] = 100
+ elif response['server']['status'] == "BUILD":
+ response['server']['progress'] = 0
+ return response
+
def _build_image(self, response, inst):
if 'image_ref' in dict(inst):
image_href = inst['image_ref']
- if str(image_href).isdigit():
- image_href = int(image_href)
- response['imageRef'] = image_href
+ image_id = str(common.get_id_from_href(image_href))
+ _bookmark = self.image_builder.generate_bookmark(image_id)
+ response['image'] = {
+ "id": image_id,
+ "links": [
+ {
+ "rel": "bookmark",
+ "href": _bookmark,
+ },
+ ]
+ }
def _build_flavor(self, response, inst):
if "instance_type" in dict(inst):
flavor_id = inst["instance_type"]['flavorid']
flavor_ref = self.flavor_builder.generate_href(flavor_id)
- response["flavorRef"] = flavor_ref
+ flavor_bookmark = self.flavor_builder.generate_bookmark(flavor_id)
+ response["flavor"] = {
+ "id": str(common.get_id_from_href(flavor_ref)),
+ "links": [
+ {
+ "rel": "bookmark",
+ "href": flavor_bookmark,
+ },
+ ]
+ }
def _build_addresses(self, response, inst):
interfaces = inst.get('virtual_interfaces', [])
@@ -164,6 +195,7 @@ class ViewBuilderV11(ViewBuilder):
def _build_extra(self, response, inst):
self._build_links(response, inst)
+ response['uuid'] = inst['uuid']
def _build_links(self, response, inst):
href = self.generate_href(inst["id"])
@@ -180,7 +212,7 @@ class ViewBuilderV11(ViewBuilder):
},
]
- response["server"]["links"] = links
+ response["links"] = links
def generate_href(self, server_id):
"""Create an url that refers to a specific server id."""
diff --git a/nova/api/openstack/wsgi.py b/nova/api/openstack/wsgi.py
index 1e5a5143d..53dab22e8 100644
--- a/nova/api/openstack/wsgi.py
+++ b/nova/api/openstack/wsgi.py
@@ -13,6 +13,7 @@ from nova import wsgi
XMLNS_V10 = 'http://docs.rackspacecloud.com/servers/api/v1.0'
XMLNS_V11 = 'http://docs.openstack.org/compute/api/v1.1'
+XMLNS_ATOM = 'http://www.w3.org/2005/Atom'
LOG = logging.getLogger('nova.api.openstack.wsgi')
@@ -135,10 +136,43 @@ class XMLDeserializer(TextDeserializer):
listnames)
return result
+ def find_first_child_named(self, parent, name):
+ """Search a nodes children for the first child with a given name"""
+ for node in parent.childNodes:
+ if node.nodeName == name:
+ return node
+ return None
+
+ def find_children_named(self, parent, name):
+ """Return all of a nodes children who have the given name"""
+ for node in parent.childNodes:
+ if node.nodeName == name:
+ yield node
+
+ def extract_text(self, node):
+ """Get the text field contained by the given node"""
+ if len(node.childNodes) == 1:
+ child = node.childNodes[0]
+ if child.nodeType == child.TEXT_NODE:
+ return child.nodeValue
+ return ""
+
def default(self, datastring):
return {'body': self._from_xml(datastring)}
+class MetadataXMLDeserializer(XMLDeserializer):
+
+ def extract_metadata(self, metadata_node):
+ """Marshal the metadata attribute of a parsed request"""
+ metadata = {}
+ if metadata_node is not None:
+ for meta_node in self.find_children_named(metadata_node, "meta"):
+ key = meta_node.getAttribute("key")
+ metadata[key] = self.extract_text(meta_node)
+ return metadata
+
+
class RequestHeadersDeserializer(ActionDispatcher):
"""Default request headers deserializer"""
@@ -396,8 +430,9 @@ class ResponseSerializer(object):
def serialize_body(self, response, data, content_type, action):
response.headers['Content-Type'] = content_type
- serializer = self.get_body_serializer(content_type)
- response.body = serializer.serialize(data, action)
+ if data is not None:
+ serializer = self.get_body_serializer(content_type)
+ response.body = serializer.serialize(data, action)
def get_body_serializer(self, content_type):
try:
@@ -443,7 +478,7 @@ class Resource(wsgi.Application):
action, args, accept = self.deserializer.deserialize(request)
except exception.InvalidContentType:
msg = _("Unsupported Content-Type")
- return webob.exc.HTTPBadRequest(explanation=msg)
+ return faults.Fault(webob.exc.HTTPBadRequest(explanation=msg))
except exception.MalformedRequestBody:
msg = _("Malformed request body")
return faults.Fault(webob.exc.HTTPBadRequest(explanation=msg))
@@ -454,7 +489,6 @@ class Resource(wsgi.Application):
LOG.info(_("HTTP exception thrown: %s"), unicode(ex))
action_result = faults.Fault(ex)
- #TODO(bcwaldon): find a more elegant way to pass through non-dict types
if type(action_result) is dict or action_result is None:
response = self.serializer.serialize(action_result,
accept,