summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--nova/api/openstack/accounts.py6
-rw-r--r--nova/api/openstack/backup_schedules.py9
-rw-r--r--nova/api/openstack/common.py19
-rw-r--r--nova/api/openstack/consoles.py7
-rw-r--r--nova/api/openstack/create_instance_helper.py17
-rw-r--r--nova/api/openstack/image_metadata.py5
-rw-r--r--nova/api/openstack/images.py3
-rw-r--r--nova/api/openstack/ips.py11
-rw-r--r--nova/api/openstack/limits.py61
-rw-r--r--nova/api/openstack/server_metadata.py1
-rw-r--r--nova/api/openstack/servers.py92
-rw-r--r--nova/api/openstack/shared_ip_groups.py13
-rw-r--r--nova/api/openstack/users.py3
-rw-r--r--nova/api/openstack/views/limits.py6
-rw-r--r--nova/api/openstack/views/servers.py2
-rw-r--r--nova/api/openstack/wsgi.py6
-rw-r--r--nova/api/openstack/zones.py8
-rw-r--r--nova/compute/api.py4
-rw-r--r--nova/db/api.py6
-rw-r--r--nova/image/glance.py8
-rw-r--r--nova/image/s3.py2
-rw-r--r--nova/tests/api/openstack/test_limits.py218
-rw-r--r--nova/tests/api/openstack/test_servers.py34
-rw-r--r--smoketests/test_sysadmin.py33
24 files changed, 442 insertions, 132 deletions
diff --git a/nova/api/openstack/accounts.py b/nova/api/openstack/accounts.py
index e3201b14f..a13a758ab 100644
--- a/nova/api/openstack/accounts.py
+++ b/nova/api/openstack/accounts.py
@@ -47,10 +47,10 @@ class Controller(object):
raise exception.AdminRequired()
def index(self, req):
- raise faults.Fault(webob.exc.HTTPNotImplemented())
+ raise webob.exc.HTTPNotImplemented()
def detail(self, req):
- raise faults.Fault(webob.exc.HTTPNotImplemented())
+ raise webob.exc.HTTPNotImplemented()
def show(self, req, id):
"""Return data about the given account id"""
@@ -65,7 +65,7 @@ class Controller(object):
def create(self, req, body):
"""We use update with create-or-update semantics
because the id comes from an external source"""
- raise faults.Fault(webob.exc.HTTPNotImplemented())
+ raise webob.exc.HTTPNotImplemented()
def update(self, req, id, body):
"""This is really create or update."""
diff --git a/nova/api/openstack/backup_schedules.py b/nova/api/openstack/backup_schedules.py
index 3e95aedf3..7ff0d999e 100644
--- a/nova/api/openstack/backup_schedules.py
+++ b/nova/api/openstack/backup_schedules.py
@@ -19,7 +19,6 @@ import time
from webob import exc
-from nova.api.openstack import faults
from nova.api.openstack import wsgi
@@ -36,20 +35,20 @@ class Controller(object):
def index(self, req, server_id, **kwargs):
""" Returns the list of backup schedules for a given instance """
- return faults.Fault(exc.HTTPNotImplemented())
+ raise exc.HTTPNotImplemented()
def show(self, req, server_id, id, **kwargs):
""" Returns a single backup schedule for a given instance """
- return faults.Fault(exc.HTTPNotImplemented())
+ raise exc.HTTPNotImplemented()
def create(self, req, server_id, **kwargs):
""" No actual update method required, since the existing API allows
both create and update through a POST """
- return faults.Fault(exc.HTTPNotImplemented())
+ raise exc.HTTPNotImplemented()
def delete(self, req, server_id, id, **kwargs):
""" Deletes an existing backup schedule """
- return faults.Fault(exc.HTTPNotImplemented())
+ raise exc.HTTPNotImplemented()
def create_resource():
diff --git a/nova/api/openstack/common.py b/nova/api/openstack/common.py
index 8e12ce0c0..57031ebf1 100644
--- a/nova/api/openstack/common.py
+++ b/nova/api/openstack/common.py
@@ -53,10 +53,10 @@ def get_pagination_params(request):
params[param] = int(request.GET[param])
except ValueError:
msg = _('%s param must be an integer') % param
- raise webob.exc.HTTPBadRequest(msg)
+ raise webob.exc.HTTPBadRequest(explanation=msg)
if params[param] < 0:
msg = _('%s param must be positive') % param
- raise webob.exc.HTTPBadRequest(msg)
+ raise webob.exc.HTTPBadRequest(explanation=msg)
return params
@@ -77,18 +77,22 @@ def limited(items, request, max_limit=FLAGS.osapi_max_limit):
try:
offset = int(request.GET.get('offset', 0))
except ValueError:
- raise webob.exc.HTTPBadRequest(_('offset param must be an integer'))
+ msg = _('offset param must be an integer')
+ raise webob.exc.HTTPBadRequest(explanation=msg)
try:
limit = int(request.GET.get('limit', max_limit))
except ValueError:
- raise webob.exc.HTTPBadRequest(_('limit param must be an integer'))
+ msg = _('limit param must be an integer')
+ raise webob.exc.HTTPBadRequest(explanation=msg)
if limit < 0:
- raise webob.exc.HTTPBadRequest(_('limit param must be positive'))
+ msg = _('limit param must be positive')
+ raise webob.exc.HTTPBadRequest(explanation=msg)
if offset < 0:
- raise webob.exc.HTTPBadRequest(_('offset param must be positive'))
+ msg = _('offset param must be positive')
+ raise webob.exc.HTTPBadRequest(explanation=msg)
limit = min(max_limit, limit or max_limit)
range_end = offset + limit
@@ -111,7 +115,8 @@ def limited_by_marker(items, request, max_limit=FLAGS.osapi_max_limit):
start_index = i + 1
break
if start_index < 0:
- raise webob.exc.HTTPBadRequest(_('marker [%s] not found' % marker))
+ msg = _('marker [%s] not found') % marker
+ raise webob.exc.HTTPBadRequest(explanation=msg)
range_end = start_index + limit
return items[start_index:range_end]
diff --git a/nova/api/openstack/consoles.py b/nova/api/openstack/consoles.py
index 9c7b37f0d..d2655acfa 100644
--- a/nova/api/openstack/consoles.py
+++ b/nova/api/openstack/consoles.py
@@ -20,7 +20,6 @@ import webob
from nova import console
from nova import exception
-from nova.api.openstack import faults
from nova.api.openstack import wsgi
@@ -72,12 +71,12 @@ class Controller(object):
int(server_id),
int(id))
except exception.NotFound:
- return faults.Fault(exc.HTTPNotFound())
+ raise exc.HTTPNotFound()
return _translate_detail_keys(console)
def update(self, req, server_id, id):
"""You can't update a console"""
- raise faults.Fault(exc.HTTPNotImplemented())
+ raise exc.HTTPNotImplemented()
def delete(self, req, server_id, id):
"""Deletes a console"""
@@ -86,7 +85,7 @@ class Controller(object):
int(server_id),
int(id))
except exception.NotFound:
- return faults.Fault(exc.HTTPNotFound())
+ raise exc.HTTPNotFound()
return webob.Response(status_int=202)
diff --git a/nova/api/openstack/create_instance_helper.py b/nova/api/openstack/create_instance_helper.py
index 2654e3c40..7249f1261 100644
--- a/nova/api/openstack/create_instance_helper.py
+++ b/nova/api/openstack/create_instance_helper.py
@@ -28,7 +28,6 @@ from nova import quota
from nova import utils
from nova.compute import instance_types
-from nova.api.openstack import faults
from nova.api.openstack import wsgi
from nova.auth import manager as auth_manager
@@ -70,7 +69,7 @@ class CreateInstanceHelper(object):
return type from this method is left to the caller.
"""
if not body:
- raise faults.Fault(exc.HTTPUnprocessableEntity())
+ raise exc.HTTPUnprocessableEntity()
context = req.environ['nova.context']
@@ -94,7 +93,7 @@ class CreateInstanceHelper(object):
except Exception, e:
msg = _("Cannot find requested image %(image_href)s: %(e)s" %
locals())
- raise faults.Fault(exc.HTTPBadRequest(explanation=msg))
+ raise exc.HTTPBadRequest(explanation=msg)
personality = body['server'].get('personality')
@@ -102,7 +101,11 @@ class CreateInstanceHelper(object):
if personality:
injected_files = self._get_injected_files(personality)
- flavor_id = self.controller._flavor_id_from_req_data(body)
+ try:
+ flavor_id = self.controller._flavor_id_from_req_data(body)
+ except ValueError as error:
+ msg = _("Invalid flavorRef provided.")
+ raise exc.HTTPBadRequest(explanation=msg)
if not 'name' in body['server']:
msg = _("Server name is not defined")
@@ -153,8 +156,10 @@ class CreateInstanceHelper(object):
self._handle_quota_error(error)
except exception.ImageNotFound as error:
msg = _("Can not find requested image")
- raise faults.Fault(exc.HTTPBadRequest(explanation=msg))
-
+ raise exc.HTTPBadRequest(explanation=msg)
+ except exception.FlavorNotFound as error:
+ msg = _("Invalid flavorRef provided.")
+ raise exc.HTTPBadRequest(explanation=msg)
# Let the caller deal with unhandled exceptions.
def _handle_quota_error(self, error):
diff --git a/nova/api/openstack/image_metadata.py b/nova/api/openstack/image_metadata.py
index 4f33844fa..c0fc8c09b 100644
--- a/nova/api/openstack/image_metadata.py
+++ b/nova/api/openstack/image_metadata.py
@@ -22,7 +22,6 @@ from nova import flags
from nova import image
from nova import quota
from nova import utils
-from nova.api.openstack import faults
from nova.api.openstack import wsgi
@@ -62,7 +61,7 @@ class Controller(object):
if id in metadata:
return {'meta': {id: metadata[id]}}
else:
- return faults.Fault(exc.HTTPNotFound())
+ raise exc.HTTPNotFound()
def create(self, req, image_id, body):
context = req.environ['nova.context']
@@ -105,7 +104,7 @@ class Controller(object):
img = self.image_service.show(context, image_id)
metadata = self._get_metadata(context, image_id)
if not id in metadata:
- return faults.Fault(exc.HTTPNotFound())
+ raise exc.HTTPNotFound()
metadata.pop(id)
img['properties'] = metadata
self.image_service.update(context, image_id, img, None)
diff --git a/nova/api/openstack/images.py b/nova/api/openstack/images.py
index f82b98a38..30e4fd389 100644
--- a/nova/api/openstack/images.py
+++ b/nova/api/openstack/images.py
@@ -25,7 +25,6 @@ from nova import flags
import nova.image
from nova import log
from nova.api.openstack import common
-from nova.api.openstack import faults
from nova.api.openstack import image_metadata
from nova.api.openstack import servers
from nova.api.openstack.views import images as images_view
@@ -85,7 +84,7 @@ class Controller(object):
image = self._image_service.show(context, id)
except (exception.NotFound, exception.InvalidImageRef):
explanation = _("Image not found.")
- raise faults.Fault(webob.exc.HTTPNotFound(explanation=explanation))
+ raise webob.exc.HTTPNotFound(explanation=explanation)
return dict(image=self.get_builder(req).build(image, detail=True))
diff --git a/nova/api/openstack/ips.py b/nova/api/openstack/ips.py
index 1ebfdb831..2996b032d 100644
--- a/nova/api/openstack/ips.py
+++ b/nova/api/openstack/ips.py
@@ -20,7 +20,6 @@ import time
from webob import exc
import nova
-from nova.api.openstack import faults
import nova.api.openstack.views.addresses
from nova.api.openstack import wsgi
from nova import db
@@ -37,14 +36,14 @@ class Controller(object):
instance = self.compute_api.get(
req.environ['nova.context'], server_id)
except nova.exception.NotFound:
- return faults.Fault(exc.HTTPNotFound())
+ raise exc.HTTPNotFound()
return instance
def create(self, req, server_id, body):
- return faults.Fault(exc.HTTPNotImplemented())
+ raise exc.HTTPNotImplemented()
def delete(self, req, server_id, id):
- return faults.Fault(exc.HTTPNotImplemented())
+ raise exc.HTTPNotImplemented()
class ControllerV10(Controller):
@@ -63,7 +62,7 @@ class ControllerV10(Controller):
view = builder.build_public_parts(instance)
else:
msg = _("Only private and public networks available")
- return faults.Fault(exc.HTTPNotFound(explanation=msg))
+ raise exc.HTTPNotFound(explanation=msg)
return {id: view}
@@ -86,7 +85,7 @@ class ControllerV11(Controller):
if network is None:
msg = _("Instance is not a member of specified network")
- return faults.Fault(exc.HTTPNotFound(explanation=msg))
+ raise exc.HTTPNotFound(explanation=msg)
return network
diff --git a/nova/api/openstack/limits.py b/nova/api/openstack/limits.py
index bc76547d8..86afa3b62 100644
--- a/nova/api/openstack/limits.py
+++ b/nova/api/openstack/limits.py
@@ -25,6 +25,7 @@ import re
import time
import urllib
import webob.exc
+from xml.dom import minidom
from collections import defaultdict
@@ -76,6 +77,58 @@ class LimitsControllerV11(LimitsController):
return limits_views.ViewBuilderV11()
+class LimitsXMLSerializer(wsgi.XMLDictSerializer):
+
+ xmlns = wsgi.XMLNS_V11
+
+ def __init__(self):
+ pass
+
+ def _create_rates_node(self, xml_doc, rates):
+ rates_node = xml_doc.createElement('rates')
+ for rate in rates:
+ rate_node = xml_doc.createElement('rate')
+ rate_node.setAttribute('uri', rate['uri'])
+ rate_node.setAttribute('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
+
+ 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)
+
+
def create_resource(version='1.0'):
controller = {
'1.0': LimitsControllerV10,
@@ -97,9 +150,13 @@ def create_resource(version='1.0'):
},
}
+ xml_serializer = {
+ '1.0': wsgi.XMLDictSerializer(xmlns=xmlns, metadata=metadata),
+ '1.1': LimitsXMLSerializer(),
+ }[version]
+
body_serializers = {
- 'application/xml': wsgi.XMLDictSerializer(xmlns=xmlns,
- metadata=metadata),
+ 'application/xml': xml_serializer,
}
serializer = wsgi.ResponseSerializer(body_serializers)
diff --git a/nova/api/openstack/server_metadata.py b/nova/api/openstack/server_metadata.py
index 3b9169f81..d4f42bbf5 100644
--- a/nova/api/openstack/server_metadata.py
+++ b/nova/api/openstack/server_metadata.py
@@ -18,7 +18,6 @@
from webob import exc
from nova import compute
-from nova.api.openstack import faults
from nova.api.openstack import wsgi
from nova import exception
from nova import quota
diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py
index 3b3d0685d..7bef1d9b2 100644
--- a/nova/api/openstack/servers.py
+++ b/nova/api/openstack/servers.py
@@ -27,7 +27,6 @@ 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 faults
import nova.api.openstack.views.addresses
import nova.api.openstack.views.flavors
import nova.api.openstack.views.images
@@ -102,17 +101,14 @@ class Controller(object):
req.environ['nova.context'], id)
return self._build_view(req, instance, is_detail=True)
except exception.NotFound:
- return faults.Fault(exc.HTTPNotFound())
+ raise exc.HTTPNotFound()
def create(self, req, body):
""" Creates a new server for a given user """
extra_values = None
result = None
- try:
- extra_values, instances = self.helper.create_instance(
- req, body, self.compute_api.create)
- except faults.Fault, f:
- return f
+ extra_values, instances = self.helper.create_instance(
+ req, body, self.compute_api.create)
# 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
@@ -132,7 +128,7 @@ class Controller(object):
raise exc.HTTPUnprocessableEntity()
if not body:
- return faults.Fault(exc.HTTPUnprocessableEntity())
+ raise exc.HTTPUnprocessableEntity()
ctxt = req.environ['nova.context']
update_dict = {}
@@ -147,7 +143,7 @@ class Controller(object):
try:
self.compute_api.update(ctxt, id, **update_dict)
except exception.NotFound:
- return faults.Fault(exc.HTTPNotFound())
+ raise exc.HTTPNotFound()
return exc.HTTPNoContent()
@@ -171,7 +167,7 @@ class Controller(object):
for key in actions.keys():
if key in body:
return actions[key](body, req, id)
- return faults.Fault(exc.HTTPNotImplemented())
+ raise exc.HTTPNotImplemented()
def _action_change_password(self, input_dict, req, id):
return exc.HTTPNotImplemented()
@@ -181,7 +177,7 @@ class Controller(object):
self.compute_api.confirm_resize(req.environ['nova.context'], id)
except Exception, e:
LOG.exception(_("Error in confirm-resize %s"), e)
- return faults.Fault(exc.HTTPBadRequest())
+ raise exc.HTTPBadRequest()
return exc.HTTPNoContent()
def _action_revert_resize(self, input_dict, req, id):
@@ -189,7 +185,7 @@ class Controller(object):
self.compute_api.revert_resize(req.environ['nova.context'], id)
except Exception, e:
LOG.exception(_("Error in revert-resize %s"), e)
- return faults.Fault(exc.HTTPBadRequest())
+ raise exc.HTTPBadRequest()
return webob.Response(status_int=202)
def _action_resize(self, input_dict, req, id):
@@ -200,14 +196,14 @@ class Controller(object):
reboot_type = input_dict['reboot']['type']
else:
LOG.exception(_("Missing argument 'type' for reboot"))
- return faults.Fault(exc.HTTPUnprocessableEntity())
+ raise exc.HTTPUnprocessableEntity()
try:
# TODO(gundlach): pass reboot_type, support soft reboot in
# virt driver
self.compute_api.reboot(req.environ['nova.context'], id)
except Exception, e:
LOG.exception(_("Error in reboot %s"), e)
- return faults.Fault(exc.HTTPUnprocessableEntity())
+ raise exc.HTTPUnprocessableEntity()
return webob.Response(status_int=202)
def _action_migrate(self, input_dict, req, id):
@@ -215,7 +211,7 @@ class Controller(object):
self.compute_api.resize(req.environ['nova.context'], id)
except Exception, e:
LOG.exception(_("Error in migrate %s"), e)
- return faults.Fault(exc.HTTPBadRequest())
+ raise exc.HTTPBadRequest()
return webob.Response(status_int=202)
@scheduler_api.redirect_handler
@@ -231,7 +227,7 @@ class Controller(object):
except:
readable = traceback.format_exc()
LOG.exception(_("Compute.api::lock %s"), readable)
- return faults.Fault(exc.HTTPUnprocessableEntity())
+ raise exc.HTTPUnprocessableEntity()
return webob.Response(status_int=202)
@scheduler_api.redirect_handler
@@ -247,7 +243,7 @@ class Controller(object):
except:
readable = traceback.format_exc()
LOG.exception(_("Compute.api::unlock %s"), readable)
- return faults.Fault(exc.HTTPUnprocessableEntity())
+ raise exc.HTTPUnprocessableEntity()
return webob.Response(status_int=202)
@scheduler_api.redirect_handler
@@ -262,7 +258,7 @@ class Controller(object):
except:
readable = traceback.format_exc()
LOG.exception(_("Compute.api::get_lock %s"), readable)
- return faults.Fault(exc.HTTPUnprocessableEntity())
+ raise exc.HTTPUnprocessableEntity()
return webob.Response(status_int=202)
@scheduler_api.redirect_handler
@@ -277,7 +273,7 @@ class Controller(object):
except:
readable = traceback.format_exc()
LOG.exception(_("Compute.api::reset_network %s"), readable)
- return faults.Fault(exc.HTTPUnprocessableEntity())
+ raise exc.HTTPUnprocessableEntity()
return webob.Response(status_int=202)
@scheduler_api.redirect_handler
@@ -292,7 +288,7 @@ class Controller(object):
except:
readable = traceback.format_exc()
LOG.exception(_("Compute.api::inject_network_info %s"), readable)
- return faults.Fault(exc.HTTPUnprocessableEntity())
+ raise exc.HTTPUnprocessableEntity()
return webob.Response(status_int=202)
@scheduler_api.redirect_handler
@@ -304,7 +300,7 @@ class Controller(object):
except:
readable = traceback.format_exc()
LOG.exception(_("Compute.api::pause %s"), readable)
- return faults.Fault(exc.HTTPUnprocessableEntity())
+ raise exc.HTTPUnprocessableEntity()
return webob.Response(status_int=202)
@scheduler_api.redirect_handler
@@ -316,7 +312,7 @@ class Controller(object):
except:
readable = traceback.format_exc()
LOG.exception(_("Compute.api::unpause %s"), readable)
- return faults.Fault(exc.HTTPUnprocessableEntity())
+ raise exc.HTTPUnprocessableEntity()
return webob.Response(status_int=202)
@scheduler_api.redirect_handler
@@ -328,7 +324,7 @@ class Controller(object):
except:
readable = traceback.format_exc()
LOG.exception(_("compute.api::suspend %s"), readable)
- return faults.Fault(exc.HTTPUnprocessableEntity())
+ raise exc.HTTPUnprocessableEntity()
return webob.Response(status_int=202)
@scheduler_api.redirect_handler
@@ -340,7 +336,7 @@ class Controller(object):
except:
readable = traceback.format_exc()
LOG.exception(_("compute.api::resume %s"), readable)
- return faults.Fault(exc.HTTPUnprocessableEntity())
+ raise exc.HTTPUnprocessableEntity()
return webob.Response(status_int=202)
@scheduler_api.redirect_handler
@@ -352,7 +348,7 @@ class Controller(object):
except:
readable = traceback.format_exc()
LOG.exception(_("compute.api::rescue %s"), readable)
- return faults.Fault(exc.HTTPUnprocessableEntity())
+ raise exc.HTTPUnprocessableEntity()
return webob.Response(status_int=202)
@scheduler_api.redirect_handler
@@ -364,7 +360,7 @@ class Controller(object):
except:
readable = traceback.format_exc()
LOG.exception(_("compute.api::unrescue %s"), readable)
- return faults.Fault(exc.HTTPUnprocessableEntity())
+ raise exc.HTTPUnprocessableEntity()
return webob.Response(status_int=202)
@scheduler_api.redirect_handler
@@ -374,7 +370,7 @@ class Controller(object):
self.compute_api.get_ajax_console(req.environ['nova.context'],
int(id))
except exception.NotFound:
- return faults.Fault(exc.HTTPNotFound())
+ raise exc.HTTPNotFound()
return webob.Response(status_int=202)
@scheduler_api.redirect_handler
@@ -384,7 +380,7 @@ class Controller(object):
self.compute_api.get_vnc_console(req.environ['nova.context'],
int(id))
except exception.NotFound:
- return faults.Fault(exc.HTTPNotFound())
+ raise exc.HTTPNotFound()
return webob.Response(status_int=202)
@scheduler_api.redirect_handler
@@ -416,7 +412,7 @@ class ControllerV10(Controller):
try:
self.compute_api.delete(req.environ['nova.context'], id)
except exception.NotFound:
- return faults.Fault(exc.HTTPNotFound())
+ raise exc.HTTPNotFound()
return webob.Response(status_int=202)
def _image_ref_from_req_data(self, data):
@@ -440,17 +436,13 @@ class ControllerV10(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 '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"))
- return faults.Fault(exc.HTTPUnprocessableEntity())
- except Exception, e:
- LOG.exception(_("Error in resize %s"), e)
- return faults.Fault(exc.HTTPBadRequest())
+ 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()
return webob.Response(status_int=202)
def _action_rebuild(self, info, request, instance_id):
@@ -462,14 +454,14 @@ class ControllerV10(Controller):
except (KeyError, TypeError):
msg = _("Could not parse imageId from request.")
LOG.debug(msg)
- return faults.Fault(exc.HTTPBadRequest(explanation=msg))
+ raise exc.HTTPBadRequest(explanation=msg)
try:
self.compute_api.rebuild(context, instance_id, image_id)
except exception.BuildInProgress:
msg = _("Instance %d is currently being rebuilt.") % instance_id
LOG.debug(msg)
- return faults.Fault(exc.HTTPConflict(explanation=msg))
+ raise exc.HTTPConflict(explanation=msg)
return webob.Response(status_int=202)
@@ -486,7 +478,7 @@ class ControllerV11(Controller):
try:
self.compute_api.delete(req.environ['nova.context'], id)
except exception.NotFound:
- return faults.Fault(exc.HTTPNotFound())
+ raise exc.HTTPNotFound()
def _image_ref_from_req_data(self, data):
return data['server']['imageRef']
@@ -530,7 +522,7 @@ class ControllerV11(Controller):
except AttributeError as ex:
msg = _("Unable to parse metadata key/value pairs.")
LOG.debug(msg)
- raise faults.Fault(exc.HTTPBadRequest(explanation=msg))
+ raise exc.HTTPBadRequest(explanation=msg)
def _decode_personalities(self, personalities):
"""Decode the Base64-encoded personalities."""
@@ -541,14 +533,14 @@ class ControllerV11(Controller):
except (KeyError, TypeError):
msg = _("Unable to parse personality path/contents.")
LOG.info(msg)
- raise faults.Fault(exc.HTTPBadRequest(explanation=msg))
+ raise exc.HTTPBadRequest(explanation=msg)
try:
personality["contents"] = base64.b64decode(contents)
except TypeError:
msg = _("Personality content could not be Base64 decoded.")
LOG.info(msg)
- raise faults.Fault(exc.HTTPBadRequest(explanation=msg))
+ raise exc.HTTPBadRequest(explanation=msg)
def _action_resize(self, input_dict, req, id):
""" Resizes a given instance to the flavor size requested """
@@ -560,10 +552,10 @@ class ControllerV11(Controller):
flavor_id)
else:
LOG.exception(_("Missing 'flavorRef' argument for resize"))
- return faults.Fault(exc.HTTPUnprocessableEntity())
+ raise exc.HTTPUnprocessableEntity()
except Exception, e:
LOG.exception(_("Error in resize %s"), e)
- return faults.Fault(exc.HTTPBadRequest())
+ raise exc.HTTPBadRequest()
return webob.Response(status_int=202)
def _action_rebuild(self, info, request, instance_id):
@@ -575,7 +567,7 @@ class ControllerV11(Controller):
except (KeyError, TypeError):
msg = _("Could not parse imageRef from request.")
LOG.debug(msg)
- return faults.Fault(exc.HTTPBadRequest(explanation=msg))
+ raise exc.HTTPBadRequest(explanation=msg)
personalities = info["rebuild"].get("personality", [])
metadata = info["rebuild"].get("metadata")
@@ -591,7 +583,7 @@ class ControllerV11(Controller):
except exception.BuildInProgress:
msg = _("Instance %d is currently being rebuilt.") % instance_id
LOG.debug(msg)
- return faults.Fault(exc.HTTPConflict(explanation=msg))
+ raise exc.HTTPConflict(explanation=msg)
return webob.Response(status_int=202)
diff --git a/nova/api/openstack/shared_ip_groups.py b/nova/api/openstack/shared_ip_groups.py
index cf2ddbabb..54d0a8334 100644
--- a/nova/api/openstack/shared_ip_groups.py
+++ b/nova/api/openstack/shared_ip_groups.py
@@ -17,7 +17,6 @@
from webob import exc
-from nova.api.openstack import faults
from nova.api.openstack import wsgi
@@ -26,27 +25,27 @@ class Controller(object):
def index(self, req, **kwargs):
""" Returns a list of Shared IP Groups for the user """
- raise faults.Fault(exc.HTTPNotImplemented())
+ raise exc.HTTPNotImplemented()
def show(self, req, id, **kwargs):
""" Shows in-depth information on a specific Shared IP Group """
- raise faults.Fault(exc.HTTPNotImplemented())
+ raise exc.HTTPNotImplemented()
def update(self, req, id, **kwargs):
""" You can't update a Shared IP Group """
- raise faults.Fault(exc.HTTPNotImplemented())
+ raise exc.HTTPNotImplemented()
def delete(self, req, id, **kwargs):
""" Deletes a Shared IP Group """
- raise faults.Fault(exc.HTTPNotImplemented())
+ raise exc.HTTPNotImplemented()
def detail(self, req, **kwargs):
""" Returns a complete list of Shared IP Groups """
- raise faults.Fault(exc.HTTPNotImplemented())
+ raise exc.HTTPNotImplemented()
def create(self, req, **kwargs):
""" Creates a new Shared IP group """
- raise faults.Fault(exc.HTTPNotImplemented())
+ raise exc.HTTPNotImplemented()
def create_resource():
diff --git a/nova/api/openstack/users.py b/nova/api/openstack/users.py
index 6ae1eaf2a..8dd72d559 100644
--- a/nova/api/openstack/users.py
+++ b/nova/api/openstack/users.py
@@ -19,7 +19,6 @@ from nova import exception
from nova import flags
from nova import log as logging
from nova.api.openstack import common
-from nova.api.openstack import faults
from nova.api.openstack import wsgi
from nova.auth import manager
@@ -69,7 +68,7 @@ class Controller(object):
user = None
if user is None:
- raise faults.Fault(exc.HTTPNotFound())
+ raise exc.HTTPNotFound()
return dict(user=_translate_keys(user))
diff --git a/nova/api/openstack/views/limits.py b/nova/api/openstack/views/limits.py
index 934b4921a..f603d7cb4 100644
--- a/nova/api/openstack/views/limits.py
+++ b/nova/api/openstack/views/limits.py
@@ -15,9 +15,11 @@
# License for the specific language governing permissions and limitations
# under the License.
+import datetime
import time
from nova.api.openstack import common
+from nova import utils
class ViewBuilder(object):
@@ -113,10 +115,12 @@ class ViewBuilderV11(ViewBuilder):
return limits
def _build_rate_limit(self, rate_limit):
+ next_avail = \
+ datetime.datetime.utcfromtimestamp(rate_limit["resetTime"])
return {
"verb": rate_limit["verb"],
"value": rate_limit["value"],
"remaining": int(rate_limit["remaining"]),
"unit": rate_limit["unit"],
- "next-available": rate_limit["resetTime"],
+ "next-available": utils.isotime(at=next_avail),
}
diff --git a/nova/api/openstack/views/servers.py b/nova/api/openstack/views/servers.py
index ab7e8da61..7131db088 100644
--- a/nova/api/openstack/views/servers.py
+++ b/nova/api/openstack/views/servers.py
@@ -82,7 +82,7 @@ class ViewBuilder(object):
ctxt = nova.context.get_admin_context()
compute_api = nova.compute.API()
- if compute_api.has_finished_migration(ctxt, inst['id']):
+ if compute_api.has_finished_migration(ctxt, inst['uuid']):
inst_dict['status'] = 'RESIZE-CONFIRM'
# Return the metadata as a dictionary
diff --git a/nova/api/openstack/wsgi.py b/nova/api/openstack/wsgi.py
index 9df6fd058..1e5a5143d 100644
--- a/nova/api/openstack/wsgi.py
+++ b/nova/api/openstack/wsgi.py
@@ -448,7 +448,11 @@ class Resource(wsgi.Application):
msg = _("Malformed request body")
return faults.Fault(webob.exc.HTTPBadRequest(explanation=msg))
- action_result = self.dispatch(request, action, args)
+ try:
+ action_result = self.dispatch(request, action, args)
+ except webob.exc.HTTPException as ex:
+ 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:
diff --git a/nova/api/openstack/zones.py b/nova/api/openstack/zones.py
index 2e02ec380..f7fd87bcd 100644
--- a/nova/api/openstack/zones.py
+++ b/nova/api/openstack/zones.py
@@ -27,7 +27,6 @@ from nova.scheduler import api
from nova.api.openstack import create_instance_helper as helper
from nova.api.openstack import common
-from nova.api.openstack import faults
from nova.api.openstack import wsgi
@@ -127,11 +126,8 @@ class Controller(object):
Returns a reservation ID (a UUID).
"""
result = None
- try:
- extra_values, result = self.helper.create_instance(req, body,
- self.compute_api.create_all_at_once)
- except faults.Fault, f:
- return f
+ extra_values, result = self.helper.create_instance(req, body,
+ self.compute_api.create_all_at_once)
reservation_id = result
return {'reservation_id': reservation_id}
diff --git a/nova/compute/api.py b/nova/compute/api.py
index 9a1ce7452..d1c3fd6fd 100644
--- a/nova/compute/api.py
+++ b/nova/compute/api.py
@@ -467,10 +467,10 @@ class API(base.Base):
return [dict(x.iteritems()) for x in instances]
- def has_finished_migration(self, context, instance_id):
+ def has_finished_migration(self, context, instance_uuid):
"""Returns true if an instance has a finished migration."""
try:
- db.migration_get_by_instance_and_status(context, instance_id,
+ db.migration_get_by_instance_and_status(context, instance_uuid,
'finished')
return True
except exception.NotFound:
diff --git a/nova/db/api.py b/nova/db/api.py
index d69732920..47308bdba 100644
--- a/nova/db/api.py
+++ b/nova/db/api.py
@@ -314,9 +314,9 @@ def migration_get(context, migration_id):
return IMPL.migration_get(context, migration_id)
-def migration_get_by_instance_and_status(context, instance_id, status):
- """Finds a migration by the instance id its migrating."""
- return IMPL.migration_get_by_instance_and_status(context, instance_id,
+def migration_get_by_instance_and_status(context, instance_uuid, status):
+ """Finds a migration by the instance uuid its migrating."""
+ return IMPL.migration_get_by_instance_and_status(context, instance_uuid,
status)
diff --git a/nova/image/glance.py b/nova/image/glance.py
index 55d948a32..5c2dc957b 100644
--- a/nova/image/glance.py
+++ b/nova/image/glance.py
@@ -89,6 +89,10 @@ class GlanceImageService(service.BaseImageService):
# `get_images` here because we need `is_public` and `properties`
# included so we can filter by user
filtered = []
+ filters = filters or {}
+ if 'is_public' not in filters:
+ # NOTE(vish): don't filter out private images
+ filters['is_public'] = 'none'
image_metas = self.client.get_images_detailed(filters=filters,
marker=marker,
limit=limit)
@@ -101,6 +105,10 @@ class GlanceImageService(service.BaseImageService):
def detail(self, context, filters=None, marker=None, limit=None):
"""Calls out to Glance for a list of detailed image information."""
filtered = []
+ filters = filters or {}
+ if 'is_public' not in filters:
+ # NOTE(vish): don't filter out private images
+ filters['is_public'] = 'none'
image_metas = self.client.get_images_detailed(filters=filters,
marker=marker,
limit=limit)
diff --git a/nova/image/s3.py b/nova/image/s3.py
index dd5c957a5..8685c96fd 100644
--- a/nova/image/s3.py
+++ b/nova/image/s3.py
@@ -168,7 +168,7 @@ class S3ImageService(service.BaseImageService):
metadata.update({'disk_format': image_format,
'container_format': image_format,
'status': 'queued',
- 'is_public': True,
+ 'is_public': False,
'properties': properties})
metadata['properties']['image_state'] = 'pending'
image = self.service.create(context, metadata)
diff --git a/nova/tests/api/openstack/test_limits.py b/nova/tests/api/openstack/test_limits.py
index 76363450d..8a3fe681a 100644
--- a/nova/tests/api/openstack/test_limits.py
+++ b/nova/tests/api/openstack/test_limits.py
@@ -24,11 +24,12 @@ import stubout
import time
import unittest
import webob
-
-from xml.dom.minidom import parseString
+from xml.dom import minidom
import nova.context
from nova.api.openstack import limits
+from nova.api.openstack import views
+from nova import test
TEST_LIMITS = [
@@ -166,7 +167,7 @@ class LimitsControllerV10Test(BaseLimitTestSuite):
request = self._get_index_request("application/xml")
response = request.get_response(self.controller)
- expected = parseString("""
+ expected = minidom.parseString("""
<limits
xmlns="http://docs.rackspacecloud.com/servers/api/v1.0">
<rate/>
@@ -174,7 +175,7 @@ class LimitsControllerV10Test(BaseLimitTestSuite):
</limits>
""".replace(" ", ""))
- body = parseString(response.body.replace(" ", ""))
+ body = minidom.parseString(response.body.replace(" ", ""))
self.assertEqual(expected.toxml(), body.toxml())
@@ -184,7 +185,7 @@ class LimitsControllerV10Test(BaseLimitTestSuite):
request = self._populate_limits(request)
response = request.get_response(self.controller)
- expected = parseString("""
+ expected = minidom.parseString("""
<limits
xmlns="http://docs.rackspacecloud.com/servers/api/v1.0">
<rate>
@@ -196,7 +197,7 @@ class LimitsControllerV10Test(BaseLimitTestSuite):
<absolute/>
</limits>
""".replace(" ", ""))
- body = parseString(response.body.replace(" ", ""))
+ body = minidom.parseString(response.body.replace(" ", ""))
self.assertEqual(expected.toxml(), body.toxml())
@@ -210,6 +211,7 @@ class LimitsControllerV11Test(BaseLimitTestSuite):
"""Run before each test."""
BaseLimitTestSuite.setUp(self)
self.controller = limits.create_resource('1.1')
+ self.maxDiff = None
def _get_index_request(self, accept_header="application/json"):
"""Helper to set routing arguments."""
@@ -266,14 +268,14 @@ class LimitsControllerV11Test(BaseLimitTestSuite):
"limit": [
{
"verb": "GET",
- "next-available": 0,
+ "next-available": "1970-01-01T00:00:00Z",
"unit": "MINUTE",
"value": 10,
"remaining": 10,
},
{
"verb": "POST",
- "next-available": 0,
+ "next-available": "1970-01-01T00:00:00Z",
"unit": "HOUR",
"value": 5,
"remaining": 5,
@@ -286,7 +288,7 @@ class LimitsControllerV11Test(BaseLimitTestSuite):
"limit": [
{
"verb": "GET",
- "next-available": 0,
+ "next-available": "1970-01-01T00:00:00Z",
"unit": "MINUTE",
"value": 5,
"remaining": 5,
@@ -328,7 +330,7 @@ class LimitsControllerV11Test(BaseLimitTestSuite):
"limit": [
{
"verb": "GET",
- "next-available": 0,
+ "next-available": "1970-01-01T00:00:00Z",
"unit": "MINUTE",
"value": 10,
"remaining": 10,
@@ -341,7 +343,7 @@ class LimitsControllerV11Test(BaseLimitTestSuite):
"limit": [
{
"verb": "GET",
- "next-available": 0,
+ "next-available": "1970-01-01T00:00:00Z",
"unit": "MINUTE",
"value": 10,
"remaining": 10,
@@ -458,7 +460,7 @@ class LimitMiddlewareTest(BaseLimitTestSuite):
response = request.get_response(self.app)
self.assertEqual(response.status_int, 403)
- root = parseString(response.body).childNodes[0]
+ root = minidom.parseString(response.body).childNodes[0]
expected = "Only 1 GET request(s) can be made to * every minute."
details = root.getElementsByTagName("details")
@@ -904,3 +906,195 @@ class WsgiLimiterProxyTest(BaseLimitTestSuite):
"made to /delayed every minute.")
self.assertEqual((delay, error), expected)
+
+
+class LimitsViewBuilderV11Test(test.TestCase):
+
+ def setUp(self):
+ self.view_builder = views.limits.ViewBuilderV11()
+ self.rate_limits = [
+ {
+ "URI": "*",
+ "regex": ".*",
+ "value": 10,
+ "verb": "POST",
+ "remaining": 2,
+ "unit": "MINUTE",
+ "resetTime": 1311272226
+ },
+ {
+ "URI": "*/servers",
+ "regex": "^/servers",
+ "value": 50,
+ "verb": "POST",
+ "remaining": 10,
+ "unit": "DAY",
+ "resetTime": 1311272226
+ },
+ ]
+ self.absolute_limits = {
+ "metadata_items": 1,
+ "injected_files": 5,
+ "injected_file_content_bytes": 5,
+ }
+
+ def tearDown(self):
+ pass
+
+ def test_build_limits(self):
+ expected_limits = {
+ "limits": {
+ "rate": [
+ {
+ "uri": "*",
+ "regex": ".*",
+ "limit": [
+ {
+ "value": 10,
+ "verb": "POST",
+ "remaining": 2,
+ "unit": "MINUTE",
+ "next-available": "2011-07-21T18:17:06Z"
+ },
+ ]
+ },
+ {
+ "uri": "*/servers",
+ "regex": "^/servers",
+ "limit": [
+ {
+ "value": 50,
+ "verb": "POST",
+ "remaining": 10,
+ "unit": "DAY",
+ "next-available": "2011-07-21T18:17:06Z"
+ },
+ ]
+ },
+ ],
+ "absolute": {
+ "maxServerMeta": 1,
+ "maxImageMeta": 1,
+ "maxPersonality": 5,
+ "maxPersonalitySize": 5
+ }
+ }
+ }
+
+ output = self.view_builder.build(self.rate_limits,
+ self.absolute_limits)
+ self.assertDictMatch(output, expected_limits)
+
+ def test_build_limits_empty_limits(self):
+ expected_limits = {
+ "limits": {
+ "rate": [],
+ "absolute": {}
+ }
+ }
+
+ abs_limits = {}
+ rate_limits = []
+ output = self.view_builder.build(rate_limits, abs_limits)
+ self.assertDictMatch(output, expected_limits)
+
+
+class LimitsXMLSerializationTest(test.TestCase):
+
+ def setUp(self):
+ self.maxDiff = None
+
+ def tearDown(self):
+ pass
+
+ def test_index(self):
+ serializer = limits.LimitsXMLSerializer()
+
+ fixture = {
+ "limits": {
+ "rate": [
+ {
+ "uri": "*",
+ "regex": ".*",
+ "limit": [
+ {
+ "value": 10,
+ "verb": "POST",
+ "remaining": 2,
+ "unit": "MINUTE",
+ "next-available": "2011-12-15T22:42:45Z"
+ },
+ ]
+ },
+ {
+ "uri": "*/servers",
+ "regex": "^/servers",
+ "limit": [
+ {
+ "value": 50,
+ "verb": "POST",
+ "remaining": 10,
+ "unit": "DAY",
+ "next-available": "2011-12-15T22:42:45Z"
+ },
+ ]
+ },
+ ],
+ "absolute": {
+ "maxServerMeta": 1,
+ "maxImageMeta": 1,
+ "maxPersonality": 5,
+ "maxPersonalitySize": 10240
+ }
+ }
+ }
+
+ output = serializer.serialize(fixture, 'index')
+ actual = minidom.parseString(output.replace(" ", ""))
+
+ expected = minidom.parseString("""
+ <limits xmlns="http://docs.openstack.org/compute/api/v1.1">
+ <rates>
+ <rate uri="*" regex=".*">
+ <limit value="10" verb="POST" remaining="2"
+ unit="MINUTE"
+ next-available="2011-12-15T22:42:45Z"/>
+ </rate>
+ <rate uri="*/servers" regex="^/servers">
+ <limit value="50" verb="POST" remaining="10"
+ unit="DAY"
+ next-available="2011-12-15T22:42:45Z"/>
+ </rate>
+ </rates>
+ <absolute>
+ <limit name="maxServerMeta" value="1"/>
+ <limit name="maxPersonality" value="5"/>
+ <limit name="maxImageMeta" value="1"/>
+ <limit name="maxPersonalitySize" value="10240"/>
+ </absolute>
+ </limits>
+ """.replace(" ", ""))
+
+ self.assertEqual(expected.toxml(), actual.toxml())
+
+ def test_index_no_limits(self):
+ serializer = limits.LimitsXMLSerializer()
+
+ fixture = {
+ "limits": {
+ "rate": [],
+ "absolute": {}
+ }
+ }
+
+ output = serializer.serialize(fixture, 'index')
+ actual = minidom.parseString(output.replace(" ", ""))
+
+ expected = minidom.parseString("""
+ <limits xmlns="http://docs.openstack.org/compute/api/v1.1">
+ <rates />
+ <absolute />
+ </limits>
+ """.replace(" ", ""))
+
+ self.assertEqual(expected.toxml(), actual.toxml())
diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py
index 91025fcb9..f05310325 100644
--- a/nova/tests/api/openstack/test_servers.py
+++ b/nova/tests/api/openstack/test_servers.py
@@ -988,6 +988,38 @@ class ServersTest(test.TestCase):
self.assertEqual(flavor_ref, server['flavorRef'])
self.assertEqual(image_href, server['imageRef'])
+ def test_create_instance_v1_1_invalid_flavor_href(self):
+ self._setup_for_create_instance()
+
+ image_href = 'http://localhost/v1.1/images/2'
+ flavor_ref = 'http://localhost/v1.1/flavors/asdf'
+ body = dict(server=dict(
+ name='server_test', imageRef=image_href, flavorRef=flavor_ref,
+ metadata={'hello': 'world', 'open': 'stack'},
+ personality={}))
+ req = webob.Request.blank('/v1.1/servers')
+ req.method = 'POST'
+ req.body = json.dumps(body)
+ req.headers["content-type"] = "application/json"
+ res = req.get_response(fakes.wsgi_app())
+ self.assertEqual(res.status_int, 400)
+
+ def test_create_instance_v1_1_bad_flavor_href(self):
+ self._setup_for_create_instance()
+
+ image_href = 'http://localhost/v1.1/images/2'
+ flavor_ref = 'http://localhost/v1.1/flavors/17'
+ body = dict(server=dict(
+ name='server_test', imageRef=image_href, flavorRef=flavor_ref,
+ metadata={'hello': 'world', 'open': 'stack'},
+ personality={}))
+ req = webob.Request.blank('/v1.1/servers')
+ req.method = 'POST'
+ req.body = json.dumps(body)
+ req.headers["content-type"] = "application/json"
+ res = req.get_response(fakes.wsgi_app())
+ self.assertEqual(res.status_int, 400)
+
def test_create_instance_v1_1_bad_href(self):
self._setup_for_create_instance()
@@ -1749,7 +1781,7 @@ class ServersTest(test.TestCase):
self.stubs.Set(nova.compute.api.API, 'resize', resize_mock)
res = req.get_response(fakes.wsgi_app())
- self.assertEqual(res.status_int, 400)
+ self.assertEqual(res.status_int, 500)
def test_resized_server_has_correct_status(self):
req = self.webreq('/1', 'GET')
diff --git a/smoketests/test_sysadmin.py b/smoketests/test_sysadmin.py
index 268d9865b..454f6f1d5 100644
--- a/smoketests/test_sysadmin.py
+++ b/smoketests/test_sysadmin.py
@@ -103,27 +103,48 @@ class ImageTests(base.UserSmokeTestCase):
'launchPermission')
self.assert_(attrs.name, 'launch_permission')
- def test_009_can_modify_image_launch_permission(self):
+ def test_009_can_add_image_launch_permission(self):
+ image = self.conn.get_image(self.data['image_id'])
+ self.assertEqual(image.id, self.data['image_id'])
+ self.assertEqual(image.is_public, False)
self.conn.modify_image_attribute(image_id=self.data['image_id'],
operation='add',
attribute='launchPermission',
groups='all')
image = self.conn.get_image(self.data['image_id'])
self.assertEqual(image.id, self.data['image_id'])
+ self.assertEqual(image.is_public, True)
def test_010_can_see_launch_permission(self):
attrs = self.conn.get_image_attribute(self.data['image_id'],
'launchPermission')
- self.assert_(attrs.name, 'launch_permission')
- self.assert_(attrs.attrs['groups'][0], 'all')
+ self.assertEqual(attrs.name, 'launch_permission')
+ self.assertEqual(attrs.attrs['groups'][0], 'all')
+
+ def test_011_can_remove_image_launch_permission(self):
+ image = self.conn.get_image(self.data['image_id'])
+ self.assertEqual(image.id, self.data['image_id'])
+ self.assertEqual(image.is_public, True)
+ self.conn.modify_image_attribute(image_id=self.data['image_id'],
+ operation='remove',
+ attribute='launchPermission',
+ groups='all')
+ image = self.conn.get_image(self.data['image_id'])
+ self.assertEqual(image.id, self.data['image_id'])
+ self.assertEqual(image.is_public, False)
+
+ def test_012_private_image_shows_in_list(self):
+ images = self.conn.get_all_images()
+ image_ids = [image.id for image in images]
+ self.assertTrue(self.data['image_id'] in image_ids)
- def test_011_user_can_deregister_kernel(self):
+ def test_013_user_can_deregister_kernel(self):
self.assertTrue(self.conn.deregister_image(self.data['kernel_id']))
- def test_012_can_deregister_image(self):
+ def test_014_can_deregister_image(self):
self.assertTrue(self.conn.deregister_image(self.data['image_id']))
- def test_013_can_delete_bundle(self):
+ def test_015_can_delete_bundle(self):
self.assertTrue(self.delete_bundle_bucket(TEST_BUCKET))