summaryrefslogtreecommitdiffstats
path: root/nova/api
diff options
context:
space:
mode:
authorTrey Morris <trey.morris@rackspace.com>2011-03-17 15:57:04 -0500
committerTrey Morris <trey.morris@rackspace.com>2011-03-17 15:57:04 -0500
commit400ca259f49a741cf2cefd86afcf2494ee0bd446 (patch)
tree881a11a7ba5a306f0d728c085a8021cb7e527bd5 /nova/api
parent038d99d9fa4354bd617adfa332d69a87a9f7918e (diff)
parent88ae79505a84736ebdf57ba67c60ff16de5c9e87 (diff)
merged trunk, merged qos, slight refactor regarding merges
Diffstat (limited to 'nova/api')
-rw-r--r--nova/api/ec2/cloud.py13
-rw-r--r--nova/api/openstack/auth.py6
-rw-r--r--nova/api/openstack/flavors.py3
-rw-r--r--nova/api/openstack/servers.py176
4 files changed, 175 insertions, 23 deletions
diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py
index cadda97db..e257e44e7 100644
--- a/nova/api/ec2/cloud.py
+++ b/nova/api/ec2/cloud.py
@@ -147,8 +147,6 @@ class CloudController(object):
instance_ref['id'])
ec2_id = ec2utils.id_to_ec2_id(instance_ref['id'])
image_ec2_id = self._image_ec2_id(instance_ref['image_id'], 'machine')
- k_ec2_id = self._image_ec2_id(instance_ref['kernel_id'], 'kernel')
- r_ec2_id = self._image_ec2_id(instance_ref['ramdisk_id'], 'ramdisk')
data = {
'user-data': base64.b64decode(instance_ref['user_data']),
'meta-data': {
@@ -167,8 +165,6 @@ class CloudController(object):
'instance-type': instance_ref['instance_type'],
'local-hostname': hostname,
'local-ipv4': address,
- 'kernel-id': k_ec2_id,
- 'ramdisk-id': r_ec2_id,
'placement': {'availability-zone': availability_zone},
'public-hostname': hostname,
'public-ipv4': floating_ip or '',
@@ -176,6 +172,13 @@ class CloudController(object):
'reservation-id': instance_ref['reservation_id'],
'security-groups': '',
'mpi': mpi}}
+
+ for image_type in ['kernel', 'ramdisk']:
+ if '%s_id' % image_type in instance_ref:
+ ec2_id = self._image_ec2_id(instance_ref['%s_id' % image_type],
+ image_type)
+ data['meta-data']['%s-id' % image_type] = ec2_id
+
if False: # TODO(vish): store ancestor ids
data['ancestor-ami-ids'] = []
if False: # TODO(vish): store product codes
@@ -956,7 +959,7 @@ class CloudController(object):
raise exception.NotFound(_('Image %s not found') % image_id)
internal_id = image['id']
del(image['id'])
- raise Exception(image)
+
image['properties']['is_public'] = (operation_type == 'add')
return self.image_service.update(context, internal_id, image)
diff --git a/nova/api/openstack/auth.py b/nova/api/openstack/auth.py
index 4c6b58eff..f3a9bdeca 100644
--- a/nova/api/openstack/auth.py
+++ b/nova/api/openstack/auth.py
@@ -135,7 +135,11 @@ class AuthMiddleware(wsgi.Middleware):
req - wsgi.Request object
"""
ctxt = context.get_admin_context()
- user = self.auth.get_user_from_access_key(key)
+
+ try:
+ user = self.auth.get_user_from_access_key(key)
+ except exception.NotFound:
+ user = None
if user and user.name == username:
token_hash = hashlib.sha1('%s%s%f' % (username, key,
diff --git a/nova/api/openstack/flavors.py b/nova/api/openstack/flavors.py
index f3d040ba3..1c440b3a9 100644
--- a/nova/api/openstack/flavors.py
+++ b/nova/api/openstack/flavors.py
@@ -36,7 +36,7 @@ class Controller(wsgi.Controller):
def index(self, req):
"""Return all flavors in brief."""
- return dict(flavors=[dict(id=flavor['id'], name=flavor['name'])
+ return dict(flavors=[dict(id=flavor['flavorid'], name=flavor['name'])
for flavor in self.detail(req)['flavors']])
def detail(self, req):
@@ -48,6 +48,7 @@ class Controller(wsgi.Controller):
"""Return data about the given flavor id."""
ctxt = req.environ['nova.context']
values = db.instance_type_get_by_flavor_id(ctxt, id)
+ values['id'] = values['flavorid']
return dict(flavor=values)
raise faults.Fault(exc.HTTPNotFound())
diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py
index dc28a0782..2f26fa873 100644
--- a/nova/api/openstack/servers.py
+++ b/nova/api/openstack/servers.py
@@ -13,9 +13,11 @@
# License for the specific language governing permissions and limitations
# under the License.
+import base64
import hashlib
import json
import traceback
+from xml.dom import minidom
from webob import exc
@@ -30,6 +32,7 @@ from nova.api.openstack import faults
from nova.auth import manager as auth_manager
from nova.compute import instance_types
from nova.compute import power_state
+from nova.quota import QuotaError
import nova.api.openstack
@@ -141,15 +144,19 @@ class Controller(wsgi.Controller):
def create(self, req):
""" Creates a new server for a given user """
- env = self._deserialize(req.body, req.get_content_type())
+ env = self._deserialize_create(req)
if not env:
return faults.Fault(exc.HTTPUnprocessableEntity())
context = req.environ['nova.context']
+
+ key_name = None
+ key_data = None
key_pairs = auth_manager.AuthManager.get_key_pairs(context)
- if not key_pairs:
- raise exception.NotFound(_("No keypairs defined"))
- key_pair = key_pairs[0]
+ if key_pairs:
+ key_pair = key_pairs[0]
+ key_name = key_pair['name']
+ key_data = key_pair['public_key']
image_id = common.get_image_id_from_image_hash(self._image_service,
context, env['server']['imageId'])
@@ -166,18 +173,24 @@ class Controller(wsgi.Controller):
for k, v in env['server']['metadata'].items():
metadata.append({'key': k, 'value': v})
- instances = self.compute_api.create(
- context,
- instance_types.get_by_flavor_id(env['server']['flavorId']),
- image_id,
- kernel_id=kernel_id,
- ramdisk_id=ramdisk_id,
- display_name=env['server']['name'],
- display_description=env['server']['name'],
- key_name=key_pair['name'],
- key_data=key_pair['public_key'],
- metadata=metadata,
- onset_files=env.get('onset_files', []))
+ personality = env['server'].get('personality', [])
+ injected_files = self._get_injected_files(personality)
+
+ try:
+ instances = self.compute_api.create(
+ context,
+ instance_types.get_by_flavor_id(env['server']['flavorId']),
+ image_id,
+ kernel_id=kernel_id,
+ ramdisk_id=ramdisk_id,
+ display_name=env['server']['name'],
+ display_description=env['server']['name'],
+ key_name=key_name,
+ key_data=key_data,
+ metadata=metadata,
+ injected_files=injected_files)
+ except QuotaError as error:
+ self._handle_quota_errors(error)
server = _translate_keys(instances[0])
password = "%s%s" % (server['server']['name'][:4],
@@ -187,6 +200,61 @@ class Controller(wsgi.Controller):
password)
return server
+ def _deserialize_create(self, request):
+ """
+ Deserialize a create request
+
+ Overrides normal behavior in the case of xml content
+ """
+ if request.content_type == "application/xml":
+ deserializer = ServerCreateRequestXMLDeserializer()
+ return deserializer.deserialize(request.body)
+ else:
+ return self._deserialize(request.body, request.get_content_type())
+
+ def _get_injected_files(self, personality):
+ """
+ Create a list of injected files from the personality attribute
+
+ At this time, injected_files must be formatted as a list of
+ (file_path, file_content) pairs for compatibility with the
+ underlying compute service.
+ """
+ injected_files = []
+ for item in personality:
+ try:
+ path = item['path']
+ contents = item['contents']
+ except KeyError as key:
+ expl = _('Bad personality format: missing %s') % key
+ raise exc.HTTPBadRequest(explanation=expl)
+ except TypeError:
+ expl = _('Bad personality format')
+ raise exc.HTTPBadRequest(explanation=expl)
+ try:
+ contents = base64.b64decode(contents)
+ except TypeError:
+ expl = _('Personality content for %s cannot be decoded') % path
+ raise exc.HTTPBadRequest(explanation=expl)
+ injected_files.append((path, contents))
+ return injected_files
+
+ def _handle_quota_errors(self, error):
+ """
+ Reraise quota errors as api-specific http exceptions
+ """
+ if error.code == "OnsetFileLimitExceeded":
+ expl = _("Personality file limit exceeded")
+ raise exc.HTTPBadRequest(explanation=expl)
+ if error.code == "OnsetFilePathLimitExceeded":
+ expl = _("Personality file path too long")
+ raise exc.HTTPBadRequest(explanation=expl)
+ if error.code == "OnsetFileContentLimitExceeded":
+ expl = _("Personality file content too long")
+ raise exc.HTTPBadRequest(explanation=expl)
+ # if the original error is okay, just reraise it
+ raise error
+
def update(self, req, id):
""" Updates the server name or password """
if len(req.body) == 0:
@@ -476,3 +544,79 @@ class Controller(wsgi.Controller):
_("Ramdisk not found for image %(image_id)s") % locals())
return kernel_id, ramdisk_id
+
+
+class ServerCreateRequestXMLDeserializer(object):
+ """
+ Deserializer to handle xml-formatted server create requests.
+
+ Handles standard server attributes as well as optional metadata
+ and personality attributes
+ """
+
+ def deserialize(self, string):
+ """Deserialize an xml-formatted server create request"""
+ dom = minidom.parseString(string)
+ server = self._extract_server(dom)
+ return {'server': server}
+
+ def _extract_server(self, node):
+ """Marshal the server attribute of a parsed request"""
+ server = {}
+ server_node = self._find_first_child_named(node, 'server')
+ for attr in ["name", "imageId", "flavorId"]:
+ server[attr] = server_node.getAttribute(attr)
+ metadata = self._extract_metadata(server_node)
+ if metadata is not None:
+ server["metadata"] = metadata
+ personality = self._extract_personality(server_node)
+ if personality is not None:
+ server["personality"] = personality
+ return server
+
+ def _extract_metadata(self, server_node):
+ """Marshal the metadata attribute of a parsed request"""
+ metadata_node = self._find_first_child_named(server_node, "metadata")
+ if metadata_node is None:
+ return None
+ metadata = {}
+ for meta_node in self._find_children_named(metadata_node, "meta"):
+ key = meta_node.getAttribute("key")
+ metadata[key] = self._extract_text(meta_node)
+ return metadata
+
+ def _extract_personality(self, server_node):
+ """Marshal the personality attribute of a parsed request"""
+ personality_node = \
+ self._find_first_child_named(server_node, "personality")
+ if personality_node is None:
+ return None
+ personality = []
+ for file_node in self._find_children_named(personality_node, "file"):
+ item = {}
+ if file_node.hasAttribute("path"):
+ item["path"] = file_node.getAttribute("path")
+ item["contents"] = self._extract_text(file_node)
+ personality.append(item)
+ return personality
+
+ def _find_first_child_named(self, parent, name):
+ """Search a nodes children for the first child with a given name"""
+ for node in parent.childNodes:
+ if node.nodeName == name:
+ return node
+ return None
+
+ def _find_children_named(self, parent, name):
+ """Return all of a nodes children who have the given name"""
+ for node in parent.childNodes:
+ if node.nodeName == name:
+ yield node
+
+ def _extract_text(self, node):
+ """Get the text field contained by the given node"""
+ if len(node.childNodes) == 1:
+ child = node.childNodes[0]
+ if child.nodeType == child.TEXT_NODE:
+ return child.nodeValue
+ return ""