summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorEd Leafe <ed@leafe.com>2011-08-04 19:36:29 +0000
committerEd Leafe <ed@leafe.com>2011-08-04 19:36:29 +0000
commit416686617944dba7fe9b77de294eee9e7e92a1cd (patch)
treea90054955cc2a201577a60ec9947eab3e9638b73
parent7b69ef4fe1e4aabcf44789455b96492b168ad6f5 (diff)
parentb97140981a30571750ca135330fe0efaaa5f50cf (diff)
Merged trunk
-rw-r--r--HACKING119
-rw-r--r--nova/api/openstack/__init__.py13
-rw-r--r--nova/api/openstack/common.py16
-rw-r--r--nova/api/openstack/create_instance_helper.py11
-rw-r--r--nova/api/openstack/server_metadata.py113
-rw-r--r--nova/api/openstack/servers.py12
-rw-r--r--nova/api/openstack/versions.py16
-rw-r--r--nova/api/openstack/views/versions.py6
-rw-r--r--nova/compute/api.py6
-rw-r--r--nova/compute/manager.py47
-rw-r--r--nova/db/sqlalchemy/migrate_repo/versions/036_change_flavor_id_in_migrations.py72
-rw-r--r--nova/db/sqlalchemy/models.py6
-rw-r--r--nova/flags.py5
-rw-r--r--nova/tests/api/openstack/test_common.py60
-rw-r--r--nova/tests/api/openstack/test_server_actions.py773
-rw-r--r--nova/tests/api/openstack/test_server_metadata.py264
-rw-r--r--nova/tests/api/openstack/test_servers.py755
-rw-r--r--nova/tests/api/openstack/test_versions.py78
-rw-r--r--nova/tests/test_compute.py4
-rw-r--r--nova/tests/test_xenapi.py12
-rw-r--r--nova/virt/libvirt/connection.py30
-rw-r--r--nova/virt/xenapi/vm_utils.py2
-rw-r--r--nova/virt/xenapi_conn.py7
23 files changed, 1412 insertions, 1015 deletions
diff --git a/HACKING b/HACKING
index 2f364c894..232a353fd 100644
--- a/HACKING
+++ b/HACKING
@@ -5,12 +5,23 @@ Step 1: Read http://www.python.org/dev/peps/pep-0008/
Step 2: Read http://www.python.org/dev/peps/pep-0008/ again
Step 3: Read on
+
+General
+-------
+- Put two newlines between top-level code (funcs, classes, etc)
+- Put one newline between methods in classes and anywhere else
+- Do not write "except:", use "except Exception:" at the very least
+- Include your name with TODOs as in "#TODO(termie)"
+- Do not name anything the same name as a built-in or reserved word
+
+
Imports
-------
-- thou shalt not import objects, only modules
-- thou shalt not import more than one module per line
-- thou shalt not make relative imports
-- thou shalt organize your imports according to the following template
+- Do not import objects, only modules
+- Do not import more than one module per line
+- Do not make relative imports
+- Order your imports by the full module path
+- Organize your imports according to the following template
::
# vim: tabstop=4 shiftwidth=4 softtabstop=4
@@ -22,16 +33,6 @@ Imports
{{begin your code}}
-General
--------
-- thou shalt put two newlines twixt toplevel code (funcs, classes, etc)
-- thou shalt put one newline twixt methods in classes and anywhere else
-- thou shalt not write "except:", use "except Exception:" at the very least
-- thou shalt include your name with TODOs as in "TODO(termie)"
-- thou shalt not name anything the same name as a builtin or reserved word
-- thou shalt not violate causality in our time cone, or else
-
-
Human Alphabetical Order Examples
---------------------------------
::
@@ -42,11 +43,13 @@ Human Alphabetical Order Examples
import time
import unittest
- from nova import flags
- from nova import test
+ import nova.api.ec2
+ from nova.api import openstack
from nova.auth import users
- from nova.endpoint import api
+ import nova.flags
from nova.endpoint import cloud
+ from nova import test
+
Docstrings
----------
@@ -70,6 +73,88 @@ Docstrings
:param foo: the foo parameter
:param bar: the bar parameter
+ :returns: return_type -- description of the return value
:returns: description of the return value
+ :raises: AttributeError, KeyError
"""
+
+
+Dictionaries/Lists
+------------------
+ If a dictionary (dict) or list object is longer than 80 characters, its
+ items should be split with newlines. Embedded iterables should have their
+ items indented. Additionally, the last item in the dictionary should have
+ a trailing comma. This increases readability and simplifies future diffs.
+
+ Example:
+
+ my_dictionary = {
+ "image": {
+ "name": "Just a Snapshot",
+ "size": 2749573,
+ "properties": {
+ "user_id": 12,
+ "arch": "x86_64",
+ },
+ "things": [
+ "thing_one",
+ "thing_two",
+ ],
+ "status": "ACTIVE",
+ },
+ }
+
+
+Calling Methods
+---------------
+ Calls to methods 80 characters or longer should format each argument with
+ newlines. This is not a requirement, but a guideline.
+
+ unnecessarily_long_function_name('string one',
+ 'string two',
+ kwarg1=constants.ACTIVE,
+ kwarg2=['a', 'b', 'c'])
+
+
+ Rather than constructing parameters inline, it is better to break things up:
+
+ list_of_strings = [
+ 'what_a_long_string',
+ 'not as long',
+ ]
+
+ dict_of_numbers = {
+ 'one': 1,
+ 'two': 2,
+ 'twenty four': 24,
+ }
+
+ object_one.call_a_method('string three',
+ 'string four',
+ kwarg1=list_of_strings,
+ kwarg2=dict_of_numbers)
+
+
+Internationalization (i18n) Strings
+-----------------------------------
+ In order to support multiple languages, we have a mechanism to support
+ automatic translations of exception and log strings.
+
+ Example:
+ msg = _("An error occurred")
+ raise HTTPBadRequest(explanation=msg)
+
+ If you have a variable to place within the string, first internationalize
+ the template string then do the replacement.
+
+ Example:
+ msg = _("Missing parameter: %s") % ("flavor",)
+ LOG.error(msg)
+
+ If you have multiple variables to place in the string, use keyword
+ parameters. This helps our translators reorder parameters when needed.
+
+ Example:
+ msg = _("The server with id %(s_id)s has no key %(m_key)s")
+ LOG.error(msg % {"s_id": "1234", "m_key": "imageId"})
diff --git a/nova/api/openstack/__init__.py b/nova/api/openstack/__init__.py
index 9ab8aeb58..d6a98c2cd 100644
--- a/nova/api/openstack/__init__.py
+++ b/nova/api/openstack/__init__.py
@@ -170,7 +170,9 @@ class APIRouterV11(APIRouter):
def _setup_routes(self, mapper):
super(APIRouterV11, self)._setup_routes(mapper, '1.1')
+
image_metadata_controller = image_metadata.create_resource()
+
mapper.resource("image_meta", "metadata",
controller=image_metadata_controller,
parent_resource=dict(member_name='image',
@@ -181,7 +183,14 @@ class APIRouterV11(APIRouter):
action='update_all',
conditions={"method": ['PUT']})
- mapper.resource("server_meta", "meta",
- controller=server_metadata.create_resource(),
+ server_metadata_controller = server_metadata.create_resource()
+
+ mapper.resource("server_meta", "metadata",
+ controller=server_metadata_controller,
parent_resource=dict(member_name='server',
collection_name='servers'))
+
+ mapper.connect("metadata", "/servers/{server_id}/metadata",
+ controller=server_metadata_controller,
+ action='update_all',
+ conditions={"method": ['PUT']})
diff --git a/nova/api/openstack/common.py b/nova/api/openstack/common.py
index efa4ab385..829028169 100644
--- a/nova/api/openstack/common.py
+++ b/nova/api/openstack/common.py
@@ -196,7 +196,17 @@ def get_version_from_href(href):
return version
-class MetadataXMLDeserializer(wsgi.MetadataXMLDeserializer):
+class MetadataXMLDeserializer(wsgi.XMLDeserializer):
+
+ def extract_metadata(self, metadata_node):
+ """Marshal the metadata attribute of a parsed request"""
+ if metadata_node is None:
+ return {}
+ 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_metadata_container(self, datastring):
dom = minidom.parseString(datastring)
@@ -247,7 +257,7 @@ class MetadataXMLSerializer(wsgi.XMLDictSerializer):
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')
+ return xml_doc.toxml('UTF-8')
def index(self, metadata_dict):
return self._meta_list_to_xml_string(metadata_dict)
@@ -264,7 +274,7 @@ class MetadataXMLSerializer(wsgi.XMLDictSerializer):
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')
+ return xml_doc.toxml('UTF-8')
def show(self, meta_item_dict):
return self._meta_item_to_xml_string(meta_item_dict['meta'])
diff --git a/nova/api/openstack/create_instance_helper.py b/nova/api/openstack/create_instance_helper.py
index 333994fcc..2a8e7fd7e 100644
--- a/nova/api/openstack/create_instance_helper.py
+++ b/nova/api/openstack/create_instance_helper.py
@@ -29,6 +29,7 @@ from nova import quota
from nova import utils
from nova.compute import instance_types
+from nova.api.openstack import common
from nova.api.openstack import wsgi
@@ -293,7 +294,7 @@ class CreateInstanceHelper(object):
return password
-class ServerXMLDeserializer(wsgi.MetadataXMLDeserializer):
+class ServerXMLDeserializer(wsgi.XMLDeserializer):
"""
Deserializer to handle xml-formatted server create requests.
@@ -301,6 +302,8 @@ class ServerXMLDeserializer(wsgi.MetadataXMLDeserializer):
and personality attributes
"""
+ metadata_deserializer = common.MetadataXMLDeserializer()
+
def action(self, string):
dom = minidom.parseString(string)
action_node = dom.childNodes[0]
@@ -329,7 +332,8 @@ class ServerXMLDeserializer(wsgi.MetadataXMLDeserializer):
if value:
data[attribute] = value
metadata_node = self.find_first_child_named(node, 'metadata')
- data['metadata'] = self.extract_metadata(metadata_node)
+ metadata = self.metadata_deserializer.extract_metadata(metadata_node)
+ data['metadata'] = metadata
return data
def create(self, string):
@@ -350,7 +354,8 @@ class ServerXMLDeserializer(wsgi.MetadataXMLDeserializer):
server[attr] = server_node.getAttribute(attr)
metadata_node = self.find_first_child_named(server_node, "metadata")
- server["metadata"] = self.extract_metadata(metadata_node)
+ server["metadata"] = self.metadata_deserializer.extract_metadata(
+ metadata_node)
server["personality"] = self._extract_personality(server_node)
diff --git a/nova/api/openstack/server_metadata.py b/nova/api/openstack/server_metadata.py
index d4f42bbf5..b0b014f86 100644
--- a/nova/api/openstack/server_metadata.py
+++ b/nova/api/openstack/server_metadata.py
@@ -18,6 +18,7 @@
from webob import exc
from nova import compute
+from nova.api.openstack import common
from nova.api.openstack import wsgi
from nova import exception
from nova import quota
@@ -31,36 +32,37 @@ class Controller(object):
super(Controller, self).__init__()
def _get_metadata(self, context, server_id):
- metadata = self.compute_api.get_instance_metadata(context, server_id)
+ try:
+ meta = self.compute_api.get_instance_metadata(context, server_id)
+ except exception.InstanceNotFound:
+ msg = _('Server does not exist')
+ raise exc.HTTPNotFound(explanation=msg)
+
meta_dict = {}
- for key, value in metadata.iteritems():
+ for key, value in meta.iteritems():
meta_dict[key] = value
- return dict(metadata=meta_dict)
-
- def _check_body(self, body):
- if body == None or body == "":
- expl = _('No Request Body')
- raise exc.HTTPBadRequest(explanation=expl)
+ return meta_dict
def index(self, req, server_id):
""" Returns the list of metadata for a given instance """
context = req.environ['nova.context']
- try:
- return self._get_metadata(context, server_id)
- except exception.InstanceNotFound:
- msg = _('Server %(server_id)s does not exist') % locals()
- raise exc.HTTPNotFound(explanation=msg)
+ return {'metadata': self._get_metadata(context, server_id)}
def create(self, req, server_id, body):
- self._check_body(body)
+ try:
+ metadata = body['metadata']
+ except (KeyError, TypeError):
+ msg = _("Malformed request body")
+ raise exc.HTTPBadRequest(explanation=msg)
+
context = req.environ['nova.context']
- metadata = body.get('metadata')
+
try:
self.compute_api.update_or_create_instance_metadata(context,
server_id,
metadata)
except exception.InstanceNotFound:
- msg = _('Server %(server_id)s does not exist') % locals()
+ msg = _('Server does not exist')
raise exc.HTTPNotFound(explanation=msg)
except quota.QuotaError as error:
@@ -69,51 +71,80 @@ class Controller(object):
return body
def update(self, req, server_id, id, body):
- self._check_body(body)
- context = req.environ['nova.context']
- if not id in body:
+ try:
+ meta_item = body['meta']
+ except (TypeError, KeyError):
+ expl = _('Malformed request body')
+ raise exc.HTTPBadRequest(explanation=expl)
+
+ try:
+ meta_value = meta_item.pop(id)
+ except (AttributeError, KeyError):
expl = _('Request body and URI mismatch')
raise exc.HTTPBadRequest(explanation=expl)
- if len(body) > 1:
+
+ if len(meta_item) > 0:
expl = _('Request body contains too many items')
raise exc.HTTPBadRequest(explanation=expl)
+
+ context = req.environ['nova.context']
+ self._set_instance_metadata(context, server_id, meta_item)
+
+ return {'meta': {id: meta_value}}
+
+ def update_all(self, req, server_id, body):
+ try:
+ metadata = body['metadata']
+ except (TypeError, KeyError):
+ expl = _('Malformed request body')
+ raise exc.HTTPBadRequest(explanation=expl)
+
+ context = req.environ['nova.context']
+ self._set_instance_metadata(context, server_id, metadata)
+
+ return {'metadata': metadata}
+
+ def _set_instance_metadata(self, context, server_id, metadata):
try:
self.compute_api.update_or_create_instance_metadata(context,
server_id,
- body)
+ metadata)
except exception.InstanceNotFound:
- msg = _('Server %(server_id)s does not exist') % locals()
+ msg = _('Server does not exist')
raise exc.HTTPNotFound(explanation=msg)
+ except ValueError:
+ msg = _("Malformed request body")
+ raise exc.HTTPBadRequest(explanation=msg)
+
except quota.QuotaError as error:
self._handle_quota_error(error)
- return body
-
def show(self, req, server_id, id):
""" Return a single metadata item """
context = req.environ['nova.context']
- try:
- data = self._get_metadata(context, server_id)
- except exception.InstanceNotFound:
- msg = _('Server %(server_id)s does not exist') % locals()
- raise exc.HTTPNotFound(explanation=msg)
+ data = self._get_metadata(context, server_id)
try:
- return {id: data['metadata'][id]}
+ return {'meta': {id: data[id]}}
except KeyError:
- msg = _("metadata item %s was not found" % (id))
+ msg = _("Metadata item was not found")
raise exc.HTTPNotFound(explanation=msg)
def delete(self, req, server_id, id):
""" Deletes an existing metadata """
context = req.environ['nova.context']
+
+ metadata = self._get_metadata(context, server_id)
+
try:
- self.compute_api.delete_instance_metadata(context, server_id, id)
- except exception.InstanceNotFound:
- msg = _('Server %(server_id)s does not exist') % locals()
+ meta_key = metadata[id]
+ except KeyError:
+ msg = _("Metadata item was not found")
raise exc.HTTPNotFound(explanation=msg)
+ self.compute_api.delete_instance_metadata(context, server_id, meta_key)
+
def _handle_quota_error(self, error):
"""Reraise quota errors as api-specific http exceptions."""
if error.code == "MetadataLimitExceeded":
@@ -122,10 +153,16 @@ class Controller(object):
def create_resource():
- body_serializers = {
- 'application/xml': wsgi.XMLDictSerializer(xmlns=wsgi.XMLNS_V11),
+ headers_serializer = common.MetadataHeadersSerializer()
+
+ body_deserializers = {
+ 'application/xml': common.MetadataXMLDeserializer(),
}
- serializer = wsgi.ResponseSerializer(body_serializers)
+ body_serializers = {
+ 'application/xml': common.MetadataXMLSerializer(),
+ }
+ 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/servers.py b/nova/api/openstack/servers.py
index 002b47edb..d17714371 100644
--- a/nova/api/openstack/servers.py
+++ b/nova/api/openstack/servers.py
@@ -328,7 +328,7 @@ class Controller(object):
return webob.Response(status_int=202)
@scheduler_api.redirect_handler
- def reset_network(self, req, id, body):
+ def reset_network(self, req, id):
"""
Reset networking on an instance (admin only).
@@ -343,7 +343,7 @@ class Controller(object):
return webob.Response(status_int=202)
@scheduler_api.redirect_handler
- def inject_network_info(self, req, id, body):
+ def inject_network_info(self, req, id):
"""
Inject network info for an instance (admin only).
@@ -358,7 +358,7 @@ class Controller(object):
return webob.Response(status_int=202)
@scheduler_api.redirect_handler
- def pause(self, req, id, body):
+ def pause(self, req, id):
""" Permit Admins to Pause the server. """
ctxt = req.environ['nova.context']
try:
@@ -370,7 +370,7 @@ class Controller(object):
return webob.Response(status_int=202)
@scheduler_api.redirect_handler
- def unpause(self, req, id, body):
+ def unpause(self, req, id):
""" Permit Admins to Unpause the server. """
ctxt = req.environ['nova.context']
try:
@@ -382,7 +382,7 @@ class Controller(object):
return webob.Response(status_int=202)
@scheduler_api.redirect_handler
- def suspend(self, req, id, body):
+ def suspend(self, req, id):
"""permit admins to suspend the server"""
context = req.environ['nova.context']
try:
@@ -394,7 +394,7 @@ class Controller(object):
return webob.Response(status_int=202)
@scheduler_api.redirect_handler
- def resume(self, req, id, body):
+ def resume(self, req, id):
"""permit admins to resume the server from suspend"""
context = req.environ['nova.context']
try:
diff --git a/nova/api/openstack/versions.py b/nova/api/openstack/versions.py
index 3ef72b7f6..e2f892fb6 100644
--- a/nova/api/openstack/versions.py
+++ b/nova/api/openstack/versions.py
@@ -34,23 +34,23 @@ VERSIONS = {
"rel": "describedby",
"type": "application/pdf",
"href": "http://docs.rackspacecloud.com/"
- "servers/api/v1.0/cs-devguide-20110125.pdf"
+ "servers/api/v1.0/cs-devguide-20110125.pdf",
},
{
"rel": "describedby",
"type": "application/vnd.sun.wadl+xml",
"href": "http://docs.rackspacecloud.com/"
- "servers/api/v1.0/application.wadl"
+ "servers/api/v1.0/application.wadl",
},
],
"media-types": [
{
"base": "application/xml",
- "type": "application/vnd.openstack.compute-v1.0+xml"
+ "type": "application/vnd.openstack.compute-v1.0+xml",
},
{
"base": "application/json",
- "type": "application/vnd.openstack.compute-v1.0+json"
+ "type": "application/vnd.openstack.compute-v1.0+json",
}
],
},
@@ -63,23 +63,23 @@ VERSIONS = {
"rel": "describedby",
"type": "application/pdf",
"href": "http://docs.rackspacecloud.com/"
- "servers/api/v1.1/cs-devguide-20110125.pdf"
+ "servers/api/v1.1/cs-devguide-20110125.pdf",
},
{
"rel": "describedby",
"type": "application/vnd.sun.wadl+xml",
"href": "http://docs.rackspacecloud.com/"
- "servers/api/v1.1/application.wadl"
+ "servers/api/v1.1/application.wadl",
},
],
"media-types": [
{
"base": "application/xml",
- "type": "application/vnd.openstack.compute-v1.1+xml"
+ "type": "application/vnd.openstack.compute-v1.1+xml",
},
{
"base": "application/json",
- "type": "application/vnd.openstack.compute-v1.1+json"
+ "type": "application/vnd.openstack.compute-v1.1+json",
}
],
},
diff --git a/nova/api/openstack/views/versions.py b/nova/api/openstack/views/versions.py
index 547289034..03da80818 100644
--- a/nova/api/openstack/views/versions.py
+++ b/nova/api/openstack/views/versions.py
@@ -42,10 +42,10 @@ class ViewBuilder(object):
"links": [
{
"rel": "self",
- "href": self.generate_href(version['id'], req.path)
- }
+ "href": self.generate_href(version['id'], req.path),
+ },
],
- "media-types": version['media-types']
+ "media-types": version['media-types'],
})
return dict(choices=version_objs)
diff --git a/nova/compute/api.py b/nova/compute/api.py
index 5ea7a1c01..3e0cd7cfa 100644
--- a/nova/compute/api.py
+++ b/nova/compute/api.py
@@ -888,7 +888,7 @@ class API(base.Base):
params = {'migration_id': migration_ref['id']}
self._cast_compute_message('revert_resize', context,
instance_ref['uuid'],
- migration_ref['source_compute'],
+ migration_ref['dest_compute'],
params=params)
self.db.migration_update(context, migration_ref['id'],
@@ -908,7 +908,7 @@ class API(base.Base):
params = {'migration_id': migration_ref['id']}
self._cast_compute_message('confirm_resize', context,
instance_ref['uuid'],
- migration_ref['dest_compute'],
+ migration_ref['source_compute'],
params=params)
self.db.migration_update(context, migration_ref['id'],
@@ -955,7 +955,7 @@ class API(base.Base):
{"method": "prep_resize",
"args": {"topic": FLAGS.compute_topic,
"instance_id": instance_ref['uuid'],
- "flavor_id": new_instance_type['id']}})
+ "instance_type_id": new_instance_type['id']}})
@scheduler_api.reroute_compute("add_fixed_ip")
def add_fixed_ip(self, context, instance_id, network_id):
diff --git a/nova/compute/manager.py b/nova/compute/manager.py
index ebe301404..220b09554 100644
--- a/nova/compute/manager.py
+++ b/nova/compute/manager.py
@@ -44,6 +44,7 @@ import functools
from eventlet import greenthread
+import nova.context
from nova import exception
from nova import flags
import nova.image
@@ -147,6 +148,29 @@ class ComputeManager(manager.SchedulerDependentManager):
def init_host(self):
"""Initialization for a standalone compute service."""
self.driver.init_host(host=self.host)
+ context = nova.context.get_admin_context()
+ instances = self.db.instance_get_all_by_host(context, self.host)
+ for instance in instances:
+ inst_name = instance['name']
+ db_state = instance['state']
+ drv_state = self._update_state(context, instance['id'])
+
+ expect_running = db_state == power_state.RUNNING \
+ and drv_state != db_state
+
+ LOG.debug(_('Current state of %(inst_name)s is %(drv_state)s, '
+ 'state in DB is %(db_state)s.'), locals())
+
+ if (expect_running and FLAGS.resume_guests_state_on_host_boot)\
+ or FLAGS.start_guests_on_host_boot:
+ LOG.info(_('Rebooting instance %(inst_name)s after '
+ 'nova-compute restart.'), locals())
+ self.reboot_instance(context, instance['id'])
+ elif drv_state == power_state.RUNNING:
+ try: # Hyper-V and VMWareAPI drivers will raise and exception
+ self.driver.ensure_filtering_rules_for_instance(instance)
+ except NotImplementedError:
+ LOG.warning(_('Hypervisor driver does not support firewall rules'))
def _update_state(self, context, instance_id, state=None):
"""Update the state of an instance from the driver info."""
@@ -154,6 +178,7 @@ class ComputeManager(manager.SchedulerDependentManager):
if state is None:
try:
+ LOG.debug(_('Checking state of %s'), instance_ref['name'])
info = self.driver.get_info(instance_ref['name'])
except exception.NotFound:
info = None
@@ -164,6 +189,7 @@ class ComputeManager(manager.SchedulerDependentManager):
state = power_state.FAILED
self.db.instance_set_state(context, instance_id, state)
+ return state
def _update_launched_at(self, context, instance_id, launched_at=None):
"""Update the launched_at parameter of the given instance."""
@@ -736,8 +762,8 @@ class ComputeManager(manager.SchedulerDependentManager):
instance_ref = self.db.instance_get_by_uuid(context,
migration_ref.instance_uuid)
- instance_type = self.db.instance_type_get_by_flavor_id(context,
- migration_ref['old_flavor_id'])
+ instance_type = self.db.instance_type_get(context,
+ migration_ref['old_instance_type_id'])
# Just roll back the record. There's no need to resize down since
# the 'old' VM already has the preferred attributes
@@ -758,7 +784,7 @@ class ComputeManager(manager.SchedulerDependentManager):
@exception.wrap_exception(notifier=notifier, publisher_id=publisher_id())
@checks_instance_lock
- def prep_resize(self, context, instance_id, flavor_id):
+ def prep_resize(self, context, instance_id, instance_type_id):
"""Initiates the process of moving a running instance to another host.
Possibly changes the RAM and disk size in the process.
@@ -777,16 +803,16 @@ class ComputeManager(manager.SchedulerDependentManager):
old_instance_type = self.db.instance_type_get(context,
instance_ref['instance_type_id'])
- new_instance_type = self.db.instance_type_get_by_flavor_id(context,
- flavor_id)
+ new_instance_type = self.db.instance_type_get(context,
+ instance_type_id)
migration_ref = self.db.migration_create(context,
{'instance_uuid': instance_ref['uuid'],
'source_compute': instance_ref['host'],
'dest_compute': FLAGS.host,
'dest_host': self.driver.get_host_ip_addr(),
- 'old_flavor_id': old_instance_type['flavorid'],
- 'new_flavor_id': flavor_id,
+ 'old_instance_type_id': old_instance_type['id'],
+ 'new_instance_type_id': instance_type_id,
'status': 'pre-migrating'})
LOG.audit(_('instance %s: migrating'), instance_ref['uuid'],
@@ -849,9 +875,10 @@ class ComputeManager(manager.SchedulerDependentManager):
resize_instance = False
instance_ref = self.db.instance_get_by_uuid(context,
migration_ref.instance_uuid)
- if migration_ref['old_flavor_id'] != migration_ref['new_flavor_id']:
- instance_type = self.db.instance_type_get_by_flavor_id(context,
- migration_ref['new_flavor_id'])
+ if migration_ref['old_instance_type_id'] != \
+ migration_ref['new_instance_type_id']:
+ instance_type = self.db.instance_type_get(context,
+ migration_ref['new_instance_type_id'])
self.db.instance_update(context, instance_ref.uuid,
dict(instance_type_id=instance_type['id'],
memory_mb=instance_type['memory_mb'],
diff --git a/nova/db/sqlalchemy/migrate_repo/versions/036_change_flavor_id_in_migrations.py b/nova/db/sqlalchemy/migrate_repo/versions/036_change_flavor_id_in_migrations.py
new file mode 100644
index 000000000..f3244033b
--- /dev/null
+++ b/nova/db/sqlalchemy/migrate_repo/versions/036_change_flavor_id_in_migrations.py
@@ -0,0 +1,72 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2011 OpenStack LLC.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.from sqlalchemy import *
+
+from sqlalchemy import Column, Integer, MetaData, Table
+
+
+meta = MetaData()
+
+
+#
+# Tables to alter
+#
+#
+
+old_flavor_id = Column('old_flavor_id', Integer())
+new_flavor_id = Column('new_flavor_id', Integer())
+old_instance_type_id = Column('old_instance_type_id', Integer())
+new_instance_type_id = Column('new_instance_type_id', Integer())
+
+
+def upgrade(migrate_engine):
+ meta.bind = migrate_engine
+ instance_types = Table('instance_types', meta, autoload=True)
+ migrations = Table('migrations', meta, autoload=True)
+ migrations.create_column(old_instance_type_id)
+ migrations.create_column(new_instance_type_id)
+
+ # Convert flavor_id to instance_type_id
+ for instance_type in migrate_engine.execute(instance_types.select()):
+ migrate_engine.execute(migrations.update()\
+ .where(migrations.c.old_flavor_id == instance_type.flavorid)\
+ .values(old_instance_type_id=instance_type.id))
+ migrate_engine.execute(migrations.update()\
+ .where(migrations.c.new_flavor_id == instance_type.flavorid)\
+ .values(new_instance_type_id=instance_type.id))
+
+ migrations.c.old_flavor_id.drop()
+ migrations.c.new_flavor_id.drop()
+
+
+def downgrade(migrate_engine):
+ meta.bind = migrate_engine
+ instance_types = Table('instance_types', meta, autoload=True)
+ migrations = Table('migrations', meta, autoload=True)
+ migrations.create_column(old_flavor_id)
+ migrations.create_column(new_flavor_id)
+
+ # Convert instance_type_id to flavor_id
+ for instance_type in migrate_engine.execute(instance_types.select()):
+ migrate_engine.execute(migrations.update()\
+ .where(migrations.c.old_instance_type_id == instance_type.id)\
+ .values(old_flavor_id=instance_type.flavorid))
+ migrate_engine.execute(migrations.update()\
+ .where(migrations.c.new_instance_type_id == instance_type.id)\
+ .values(new_flavor_id=instance_type.flavorid))
+
+ migrations.c.old_instance_type_id.drop()
+ migrations.c.new_instance_type_id.drop()
diff --git a/nova/db/sqlalchemy/models.py b/nova/db/sqlalchemy/models.py
index 056259539..9f4c7a0aa 100644
--- a/nova/db/sqlalchemy/models.py
+++ b/nova/db/sqlalchemy/models.py
@@ -202,7 +202,7 @@ class Instance(BASE, NovaBase):
hostname = Column(String(255))
host = Column(String(255)) # , ForeignKey('hosts.id'))
- # aka flavor_id
+ # *not* flavor_id
instance_type_id = Column(Integer)
user_data = Column(Text)
@@ -511,8 +511,8 @@ class Migration(BASE, NovaBase):
source_compute = Column(String(255))
dest_compute = Column(String(255))
dest_host = Column(String(255))
- old_flavor_id = Column(Integer())
- new_flavor_id = Column(Integer())
+ old_instance_type_id = Column(Integer())
+ new_instance_type_id = Column(Integer())
instance_uuid = Column(String(255), ForeignKey('instances.uuid'),
nullable=True)
#TODO(_cerberus_): enum
diff --git a/nova/flags.py b/nova/flags.py
index fa6d8860a..12c6d1356 100644
--- a/nova/flags.py
+++ b/nova/flags.py
@@ -387,3 +387,8 @@ DEFINE_list('zone_capabilities',
'Key/Multi-value list representng capabilities of this zone')
DEFINE_string('build_plan_encryption_key', None,
'128bit (hex) encryption key for scheduler build plans.')
+
+DEFINE_bool('start_guests_on_host_boot', False,
+ 'Whether to restart guests when the host reboots')
+DEFINE_bool('resume_guests_state_on_host_boot', False,
+ 'Whether to start guests, that was running before the host reboot')
diff --git a/nova/tests/api/openstack/test_common.py b/nova/tests/api/openstack/test_common.py
index 0b76841f0..5a6e43579 100644
--- a/nova/tests/api/openstack/test_common.py
+++ b/nova/tests/api/openstack/test_common.py
@@ -323,14 +323,10 @@ class MetadataXMLSerializationTest(test.TestCase):
expected = minidom.parseString("""
<metadata xmlns="http://docs.openstack.org/compute/api/v1.1">
- <meta key="three">
- four
- </meta>
- <meta key="one">
- two
- </meta>
+ <meta key="three">four</meta>
+ <meta key="one">two</meta>
</metadata>
- """.replace(" ", ""))
+ """.replace(" ", "").replace("\n", ""))
self.assertEqual(expected.toxml(), actual.toxml())
@@ -346,11 +342,9 @@ class MetadataXMLSerializationTest(test.TestCase):
expected = minidom.parseString("""
<metadata xmlns="http://docs.openstack.org/compute/api/v1.1">
- <meta key="None">
- None
- </meta>
+ <meta key="None">None</meta>
</metadata>
- """.replace(" ", ""))
+ """.replace(" ", "").replace("\n", ""))
self.assertEqual(expected.toxml(), actual.toxml())
@@ -366,11 +360,9 @@ class MetadataXMLSerializationTest(test.TestCase):
expected = minidom.parseString(u"""
<metadata xmlns="http://docs.openstack.org/compute/api/v1.1">
- <meta key="three">
- Jos\xe9
- </meta>
+ <meta key="three">Jos\xe9</meta>
</metadata>
- """.encode("UTF-8").replace(" ", ""))
+ """.encode("UTF-8").replace(" ", "").replace("\n", ""))
self.assertEqual(expected.toxml(), actual.toxml())
@@ -385,10 +377,9 @@ class MetadataXMLSerializationTest(test.TestCase):
actual = minidom.parseString(output.replace(" ", ""))
expected = minidom.parseString("""
- <meta xmlns="http://docs.openstack.org/compute/api/v1.1" key="one">
- two
- </meta>
- """.replace(" ", ""))
+ <meta xmlns="http://docs.openstack.org/compute/api/v1.1"
+ key="one">two</meta>
+ """.replace(" ", "").replace("\n", ""))
self.assertEqual(expected.toxml(), actual.toxml())
@@ -405,14 +396,10 @@ class MetadataXMLSerializationTest(test.TestCase):
expected = minidom.parseString("""
<metadata xmlns="http://docs.openstack.org/compute/api/v1.1">
- <meta key="key6">
- value6
- </meta>
- <meta key="key4">
- value4
- </meta>
+ <meta key="key6">value6</meta>
+ <meta key="key4">value4</meta>
</metadata>
- """.replace(" ", ""))
+ """.replace(" ", "").replace("\n", ""))
self.assertEqual(expected.toxml(), actual.toxml())
@@ -427,10 +414,9 @@ class MetadataXMLSerializationTest(test.TestCase):
actual = minidom.parseString(output.replace(" ", ""))
expected = minidom.parseString("""
- <meta xmlns="http://docs.openstack.org/compute/api/v1.1" key="one">
- two
- </meta>
- """.replace(" ", ""))
+ <meta xmlns="http://docs.openstack.org/compute/api/v1.1"
+ key="one">two</meta>
+ """.replace(" ", "").replace("\n", ""))
self.assertEqual(expected.toxml(), actual.toxml())
@@ -448,17 +434,11 @@ class MetadataXMLSerializationTest(test.TestCase):
expected = minidom.parseString("""
<metadata xmlns="http://docs.openstack.org/compute/api/v1.1">
- <meta key="key2">
- value2
- </meta>
- <meta key="key9">
- value9
- </meta>
- <meta key="key1">
- value1
- </meta>
+ <meta key="key2">value2</meta>
+ <meta key="key9">value9</meta>
+ <meta key="key1">value1</meta>
</metadata>
- """.replace(" ", ""))
+ """.replace(" ", "").replace("\n", ""))
self.assertEqual(expected.toxml(), actual.toxml())
diff --git a/nova/tests/api/openstack/test_server_actions.py b/nova/tests/api/openstack/test_server_actions.py
new file mode 100644
index 000000000..fc08a2d53
--- /dev/null
+++ b/nova/tests/api/openstack/test_server_actions.py
@@ -0,0 +1,773 @@
+import base64
+import json
+import unittest
+from xml.dom import minidom
+
+import stubout
+import webob
+
+from nova import context
+from nova import db
+from nova import flags
+from nova import utils
+from nova.api.openstack import create_instance_helper
+from nova.compute import instance_types
+from nova.compute import power_state
+import nova.db.api
+from nova import test
+from nova.tests.api.openstack import common
+from nova.tests.api.openstack import fakes
+
+
+FLAGS = flags.FLAGS
+FLAGS.verbose = True
+
+
+def return_server_by_id(context, id):
+ return _get_instance()
+
+
+def instance_update(context, instance_id, kwargs):
+ return _get_instance()
+
+
+def return_server_with_power_state(power_state):
+ def _return_server(context, id):
+ instance = _get_instance()
+ instance['state'] = power_state
+ return instance
+ return _return_server
+
+
+def return_server_with_uuid_and_power_state(power_state):
+ def _return_server(context, id):
+ return return_server_with_power_state(power_state)
+ return _return_server
+
+
+class MockSetAdminPassword(object):
+ def __init__(self):
+ self.instance_id = None
+ self.password = None
+
+ def __call__(self, context, instance_id, password):
+ self.instance_id = instance_id
+ self.password = password
+
+
+def _get_instance():
+ instance = {
+ "id": 1,
+ "created_at": "2010-10-10 12:00:00",
+ "updated_at": "2010-11-11 11:00:00",
+ "admin_pass": "",
+ "user_id": "",
+ "project_id": "",
+ "image_ref": "5",
+ "kernel_id": "",
+ "ramdisk_id": "",
+ "launch_index": 0,
+ "key_name": "",
+ "key_data": "",
+ "state": 0,
+ "state_description": "",
+ "memory_mb": 0,
+ "vcpus": 0,
+ "local_gb": 0,
+ "hostname": "",
+ "host": "",
+ "instance_type": {
+ "flavorid": 1,
+ },
+ "user_data": "",
+ "reservation_id": "",
+ "mac_address": "",
+ "scheduled_at": utils.utcnow(),
+ "launched_at": utils.utcnow(),
+ "terminated_at": utils.utcnow(),
+ "availability_zone": "",
+ "display_name": "test_server",
+ "display_description": "",
+ "locked": False,
+ "metadata": [],
+ #"address": ,
+ #"floating_ips": [{"address":ip} for ip in public_addresses]}
+ "uuid": "deadbeef-feed-edee-beef-d0ea7beefedd"}
+
+ return instance
+
+
+class ServerActionsTest(test.TestCase):
+
+ def setUp(self):
+ self.maxDiff = None
+ super(ServerActionsTest, self).setUp()
+ self.stubs = stubout.StubOutForTesting()
+ fakes.FakeAuthManager.reset_fake_data()
+ fakes.FakeAuthDatabase.data = {}
+ fakes.stub_out_auth(self.stubs)
+ self.stubs.Set(nova.db.api, 'instance_get', return_server_by_id)
+ self.stubs.Set(nova.db.api, 'instance_update', instance_update)
+
+ self.webreq = common.webob_factory('/v1.0/servers')
+
+ def tearDown(self):
+ self.stubs.UnsetAll()
+
+ def test_server_change_password(self):
+ body = {'changePassword': {'adminPass': '1234pass'}}
+ req = webob.Request.blank('/v1.0/servers/1/action')
+ req.method = 'POST'
+ req.content_type = 'application/json'
+ req.body = json.dumps(body)
+ res = req.get_response(fakes.wsgi_app())
+ self.assertEqual(res.status_int, 501)
+
+ def test_server_change_password_xml(self):
+ req = webob.Request.blank('/v1.0/servers/1/action')
+ req.method = 'POST'
+ req.content_type = 'application/xml'
+ req.body = '<changePassword adminPass="1234pass">'
+# res = req.get_response(fakes.wsgi_app())
+# self.assertEqual(res.status_int, 501)
+
+ def test_server_reboot(self):
+ body = dict(server=dict(
+ name='server_test', imageId=2, flavorId=2, metadata={},
+ personality={}))
+ req = webob.Request.blank('/v1.0/servers/1/action')
+ req.method = 'POST'
+ req.content_type = 'application/json'
+ req.body = json.dumps(body)
+ res = req.get_response(fakes.wsgi_app())
+
+ def test_server_rebuild_accepted(self):
+ body = {
+ "rebuild": {
+ "imageId": 2,
+ },
+ }
+
+ req = webob.Request.blank('/v1.0/servers/1/action')
+ req.method = 'POST'
+ req.content_type = 'application/json'
+ req.body = json.dumps(body)
+
+ res = req.get_response(fakes.wsgi_app())
+ self.assertEqual(res.status_int, 202)
+ self.assertEqual(res.body, "")
+
+ def test_server_rebuild_rejected_when_building(self):
+ body = {
+ "rebuild": {
+ "imageId": 2,
+ },
+ }
+
+ state = power_state.BUILDING
+ new_return_server = return_server_with_power_state(state)
+ self.stubs.Set(nova.db.api, 'instance_get', new_return_server)
+ self.stubs.Set(nova.db, 'instance_get_by_uuid',
+ return_server_with_uuid_and_power_state(state))
+
+ req = webob.Request.blank('/v1.0/servers/1/action')
+ req.method = 'POST'
+ req.content_type = 'application/json'
+ req.body = json.dumps(body)
+
+ res = req.get_response(fakes.wsgi_app())
+ self.assertEqual(res.status_int, 409)
+
+ def test_server_rebuild_bad_entity(self):
+ body = {
+ "rebuild": {
+ },
+ }
+
+ req = webob.Request.blank('/v1.0/servers/1/action')
+ req.method = 'POST'
+ req.content_type = 'application/json'
+ req.body = json.dumps(body)
+
+ res = req.get_response(fakes.wsgi_app())
+ self.assertEqual(res.status_int, 400)
+
+ def test_resize_server(self):
+ req = self.webreq('/1/action', 'POST', dict(resize=dict(flavorId=3)))
+
+ self.resize_called = False
+
+ def resize_mock(*args):
+ self.resize_called = True
+
+ self.stubs.Set(nova.compute.api.API, 'resize', resize_mock)
+
+ res = req.get_response(fakes.wsgi_app())
+ self.assertEqual(res.status_int, 202)
+ self.assertEqual(self.resize_called, True)
+
+ def test_resize_bad_flavor_fails(self):
+ req = self.webreq('/1/action', 'POST', dict(resize=dict(derp=3)))
+
+ self.resize_called = False
+
+ def resize_mock(*args):
+ self.resize_called = True
+
+ 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(self.resize_called, False)
+
+ def test_resize_raises_fails(self):
+ req = self.webreq('/1/action', 'POST', dict(resize=dict(flavorId=3)))
+
+ def resize_mock(*args):
+ raise Exception('hurr durr')
+
+ self.stubs.Set(nova.compute.api.API, 'resize', resize_mock)
+
+ res = req.get_response(fakes.wsgi_app())
+ self.assertEqual(res.status_int, 500)
+
+ def test_resized_server_has_correct_status(self):
+ req = self.webreq('/1', 'GET')
+
+ def fake_migration_get(*args):
+ return {}
+
+ self.stubs.Set(nova.db, 'migration_get_by_instance_and_status',
+ fake_migration_get)
+ res = req.get_response(fakes.wsgi_app())
+ self.assertEqual(res.status_int, 200)
+ body = json.loads(res.body)
+ self.assertEqual(body['server']['status'], 'RESIZE-CONFIRM')
+
+ def test_confirm_resize_server(self):
+ req = self.webreq('/1/action', 'POST', dict(confirmResize=None))
+
+ self.resize_called = False
+
+ def confirm_resize_mock(*args):
+ self.resize_called = True
+
+ self.stubs.Set(nova.compute.api.API, 'confirm_resize',
+ confirm_resize_mock)
+
+ res = req.get_response(fakes.wsgi_app())
+ self.assertEqual(res.status_int, 204)
+ self.assertEqual(self.resize_called, True)
+
+ def test_confirm_resize_server_fails(self):
+ req = self.webreq('/1/action', 'POST', dict(confirmResize=None))
+
+ def confirm_resize_mock(*args):
+ raise Exception('hurr durr')
+
+ self.stubs.Set(nova.compute.api.API, 'confirm_resize',
+ confirm_resize_mock)
+
+ res = req.get_response(fakes.wsgi_app())
+ self.assertEqual(res.status_int, 400)
+
+ def test_revert_resize_server(self):
+ req = self.webreq('/1/action', 'POST', dict(revertResize=None))
+
+ self.resize_called = False
+
+ def revert_resize_mock(*args):
+ self.resize_called = True
+
+ self.stubs.Set(nova.compute.api.API, 'revert_resize',
+ revert_resize_mock)
+
+ res = req.get_response(fakes.wsgi_app())
+ self.assertEqual(res.status_int, 202)
+ self.assertEqual(self.resize_called, True)
+
+ def test_revert_resize_server_fails(self):
+ req = self.webreq('/1/action', 'POST', dict(revertResize=None))
+
+ def revert_resize_mock(*args):
+ raise Exception('hurr durr')
+
+ self.stubs.Set(nova.compute.api.API, 'revert_resize',
+ revert_resize_mock)
+
+ res = req.get_response(fakes.wsgi_app())
+ self.assertEqual(res.status_int, 400)
+
+ def test_migrate_server(self):
+ """This is basically the same as resize, only we provide the `migrate`
+ attribute in the body's dict.
+ """
+ req = self.webreq('/1/migrate', 'POST')
+
+ self.resize_called = False
+
+ def resize_mock(*args):
+ self.resize_called = True
+
+ self.stubs.Set(nova.compute.api.API, 'resize', resize_mock)
+
+ res = req.get_response(fakes.wsgi_app())
+ self.assertEqual(res.status_int, 202)
+ self.assertEqual(self.resize_called, True)
+
+ def test_create_backup(self):
+ """The happy path for creating backups"""
+ self.flags(allow_admin_api=True)
+
+ body = {
+ 'createBackup': {
+ 'name': 'Backup 1',
+ 'backup_type': 'daily',
+ 'rotation': 1,
+ },
+ }
+
+ req = webob.Request.blank('/v1.0/servers/1/action')
+ req.method = 'POST'
+ req.body = json.dumps(body)
+ req.headers["content-type"] = "application/json"
+ response = req.get_response(fakes.wsgi_app())
+ self.assertEqual(202, response.status_int)
+ self.assertTrue(response.headers['Location'])
+
+ def test_create_backup_admin_api_off(self):
+ """The happy path for creating backups"""
+ self.flags(allow_admin_api=False)
+
+ body = {
+ 'createBackup': {
+ 'name': 'Backup 1',
+ 'backup_type': 'daily',
+ 'rotation': 1,
+ },
+ }
+
+ req = webob.Request.blank('/v1.0/servers/1/action')
+ req.method = 'POST'
+ req.body = json.dumps(body)
+ req.headers["content-type"] = "application/json"
+ response = req.get_response(fakes.wsgi_app())
+ self.assertEqual(501, response.status_int)
+
+ def test_create_backup_with_metadata(self):
+ self.flags(allow_admin_api=True)
+
+ body = {
+ 'createBackup': {
+ 'name': 'Backup 1',
+ 'backup_type': 'daily',
+ 'rotation': 1,
+ 'metadata': {'123': 'asdf'},
+ },
+ }
+
+ req = webob.Request.blank('/v1.0/servers/1/action')
+ req.method = 'POST'
+ req.body = json.dumps(body)
+ req.headers["content-type"] = "application/json"
+ response = req.get_response(fakes.wsgi_app())
+ self.assertEqual(202, response.status_int)
+ self.assertTrue(response.headers['Location'])
+
+ def test_create_backup_no_name(self):
+ """Name is required for backups"""
+ self.flags(allow_admin_api=True)
+
+ body = {
+ 'createBackup': {
+ 'backup_type': 'daily',
+ 'rotation': 1,
+ },
+ }
+
+ req = webob.Request.blank('/v1.0/images')
+ req.method = 'POST'
+ req.body = json.dumps(body)
+ req.headers["content-type"] = "application/json"
+ response = req.get_response(fakes.wsgi_app())
+ self.assertEqual(400, response.status_int)
+
+ def test_create_backup_no_rotation(self):
+ """Rotation is required for backup requests"""
+ self.flags(allow_admin_api=True)
+
+ body = {
+ 'createBackup': {
+ 'name': 'Backup 1',
+ 'backup_type': 'daily',
+ },
+ }
+
+ req = webob.Request.blank('/v1.0/images')
+ req.method = 'POST'
+ req.body = json.dumps(body)
+ req.headers["content-type"] = "application/json"
+
+ response = req.get_response(fakes.wsgi_app())
+ self.assertEqual(400, response.status_int)
+
+ def test_create_backup_no_backup_type(self):
+ """Backup Type (daily or weekly) is required for backup requests"""
+ self.flags(allow_admin_api=True)
+
+ body = {
+ 'createBackup': {
+ 'name': 'Backup 1',
+ 'rotation': 1,
+ },
+ }
+ req = webob.Request.blank('/v1.0/images')
+ req.method = 'POST'
+ req.body = json.dumps(body)
+ req.headers["content-type"] = "application/json"
+
+ response = req.get_response(fakes.wsgi_app())
+ self.assertEqual(400, response.status_int)
+
+ def test_create_backup_bad_entity(self):
+ self.flags(allow_admin_api=True)
+
+ body = {'createBackup': 'go'}
+ req = webob.Request.blank('/v1.0/images')
+ req.method = 'POST'
+ req.body = json.dumps(body)
+ req.headers["content-type"] = "application/json"
+
+ response = req.get_response(fakes.wsgi_app())
+ self.assertEqual(400, response.status_int)
+
+
+class ServerActionsTestV11(test.TestCase):
+
+ def setUp(self):
+ self.maxDiff = None
+ super(ServerActionsTestV11, self).setUp()
+ self.stubs = stubout.StubOutForTesting()
+ fakes.FakeAuthManager.reset_fake_data()
+ fakes.FakeAuthDatabase.data = {}
+ fakes.stub_out_auth(self.stubs)
+ self.stubs.Set(nova.db.api, 'instance_get', return_server_by_id)
+ self.stubs.Set(nova.db.api, 'instance_update', instance_update)
+
+ fakes.stub_out_glance(self.stubs)
+ fakes.stub_out_compute_api_snapshot(self.stubs)
+ service_class = 'nova.image.glance.GlanceImageService'
+ self.service = utils.import_object(service_class)
+ self.context = context.RequestContext(1, None)
+ self.service.delete_all()
+ self.sent_to_glance = {}
+ fakes.stub_out_glance_add_image(self.stubs, self.sent_to_glance)
+
+ def tearDown(self):
+ self.stubs.UnsetAll()
+
+ def test_server_change_password(self):
+ mock_method = MockSetAdminPassword()
+ self.stubs.Set(nova.compute.api.API, 'set_admin_password', mock_method)
+ body = {'changePassword': {'adminPass': '1234pass'}}
+ req = webob.Request.blank('/v1.1/servers/1/action')
+ req.method = 'POST'
+ req.content_type = 'application/json'
+ req.body = json.dumps(body)
+ res = req.get_response(fakes.wsgi_app())
+ self.assertEqual(res.status_int, 202)
+ self.assertEqual(mock_method.instance_id, '1')
+ self.assertEqual(mock_method.password, '1234pass')
+
+ def test_server_change_password_not_a_string(self):
+ body = {'changePassword': {'adminPass': 1234}}
+ req = webob.Request.blank('/v1.1/servers/1/action')
+ req.method = 'POST'
+ req.content_type = 'application/json'
+ req.body = json.dumps(body)
+ res = req.get_response(fakes.wsgi_app())
+ self.assertEqual(res.status_int, 400)
+
+ def test_server_change_password_bad_request(self):
+ body = {'changePassword': {'pass': '12345'}}
+ req = webob.Request.blank('/v1.1/servers/1/action')
+ req.method = 'POST'
+ req.content_type = 'application/json'
+ req.body = json.dumps(body)
+ res = req.get_response(fakes.wsgi_app())
+ self.assertEqual(res.status_int, 400)
+
+ def test_server_change_password_empty_string(self):
+ body = {'changePassword': {'adminPass': ''}}
+ req = webob.Request.blank('/v1.1/servers/1/action')
+ req.method = 'POST'
+ req.content_type = 'application/json'
+ req.body = json.dumps(body)
+ res = req.get_response(fakes.wsgi_app())
+ self.assertEqual(res.status_int, 400)
+
+ def test_server_change_password_none(self):
+ body = {'changePassword': {'adminPass': None}}
+ req = webob.Request.blank('/v1.1/servers/1/action')
+ req.method = 'POST'
+ req.content_type = 'application/json'
+ req.body = json.dumps(body)
+ res = req.get_response(fakes.wsgi_app())
+ self.assertEqual(res.status_int, 400)
+
+ def test_server_rebuild_accepted_minimum(self):
+ body = {
+ "rebuild": {
+ "imageRef": "http://localhost/images/2",
+ },
+ }
+
+ req = webob.Request.blank('/v1.1/servers/1/action')
+ req.method = 'POST'
+ req.content_type = 'application/json'
+ req.body = json.dumps(body)
+
+ res = req.get_response(fakes.wsgi_app())
+ self.assertEqual(res.status_int, 202)
+
+ def test_server_rebuild_rejected_when_building(self):
+ body = {
+ "rebuild": {
+ "imageRef": "http://localhost/images/2",
+ },
+ }
+
+ state = power_state.BUILDING
+ new_return_server = return_server_with_power_state(state)
+ self.stubs.Set(nova.db.api, 'instance_get', new_return_server)
+ self.stubs.Set(nova.db, 'instance_get_by_uuid',
+ return_server_with_uuid_and_power_state(state))
+
+ req = webob.Request.blank('/v1.1/servers/1/action')
+ req.method = 'POST'
+ req.content_type = 'application/json'
+ req.body = json.dumps(body)
+
+ res = req.get_response(fakes.wsgi_app())
+ self.assertEqual(res.status_int, 409)
+
+ def test_server_rebuild_accepted_with_metadata(self):
+ body = {
+ "rebuild": {
+ "imageRef": "http://localhost/images/2",
+ "metadata": {
+ "new": "metadata",
+ },
+ },
+ }
+
+ req = webob.Request.blank('/v1.1/servers/1/action')
+ req.method = 'POST'
+ req.content_type = 'application/json'
+ req.body = json.dumps(body)
+
+ res = req.get_response(fakes.wsgi_app())
+ self.assertEqual(res.status_int, 202)
+
+ def test_server_rebuild_accepted_with_bad_metadata(self):
+ body = {
+ "rebuild": {
+ "imageRef": "http://localhost/images/2",
+ "metadata": "stack",
+ },
+ }
+
+ req = webob.Request.blank('/v1.1/servers/1/action')
+ req.method = 'POST'
+ req.content_type = 'application/json'
+ req.body = json.dumps(body)
+
+ res = req.get_response(fakes.wsgi_app())
+ self.assertEqual(res.status_int, 400)
+
+ def test_server_rebuild_bad_entity(self):
+ body = {
+ "rebuild": {
+ "imageId": 2,
+ },
+ }
+
+ req = webob.Request.blank('/v1.1/servers/1/action')
+ req.method = 'POST'
+ req.content_type = 'application/json'
+ req.body = json.dumps(body)
+
+ res = req.get_response(fakes.wsgi_app())
+ self.assertEqual(res.status_int, 400)
+
+ def test_server_rebuild_bad_personality(self):
+ body = {
+ "rebuild": {
+ "imageRef": "http://localhost/images/2",
+ "personality": [{
+ "path": "/path/to/file",
+ "contents": "INVALID b64",
+ }]
+ },
+ }
+
+ req = webob.Request.blank('/v1.1/servers/1/action')
+ req.method = 'POST'
+ req.content_type = 'application/json'
+ req.body = json.dumps(body)
+
+ res = req.get_response(fakes.wsgi_app())
+ self.assertEqual(res.status_int, 400)
+
+ def test_server_rebuild_personality(self):
+ body = {
+ "rebuild": {
+ "imageRef": "http://localhost/images/2",
+ "personality": [{
+ "path": "/path/to/file",
+ "contents": base64.b64encode("Test String"),
+ }]
+ },
+ }
+
+ req = webob.Request.blank('/v1.1/servers/1/action')
+ req.method = 'POST'
+ req.content_type = 'application/json'
+ req.body = json.dumps(body)
+
+ res = req.get_response(fakes.wsgi_app())
+ self.assertEqual(res.status_int, 202)
+
+ def test_resize_server(self):
+
+ req = webob.Request.blank('/v1.1/servers/1/action')
+ req.content_type = 'application/json'
+ req.method = 'POST'
+ body_dict = dict(resize=dict(flavorRef="http://localhost/3"))
+ req.body = json.dumps(body_dict)
+
+ self.resize_called = False
+
+ def resize_mock(*args):
+ self.resize_called = True
+
+ self.stubs.Set(nova.compute.api.API, 'resize', resize_mock)
+
+ res = req.get_response(fakes.wsgi_app())
+ self.assertEqual(res.status_int, 202)
+ self.assertEqual(self.resize_called, True)
+
+ def test_create_image(self):
+ body = {
+ 'createImage': {
+ 'name': 'Snapshot 1',
+ },
+ }
+ req = webob.Request.blank('/v1.1/servers/1/action')
+ req.method = 'POST'
+ req.body = json.dumps(body)
+ req.headers["content-type"] = "application/json"
+ response = req.get_response(fakes.wsgi_app())
+ self.assertEqual(202, response.status_int)
+ location = response.headers['Location']
+ self.assertEqual('http://localhost/v1.1/images/123', location)
+
+ def test_create_image_with_metadata(self):
+ body = {
+ 'createImage': {
+ 'name': 'Snapshot 1',
+ 'metadata': {'key': 'asdf'},
+ },
+ }
+ req = webob.Request.blank('/v1.1/servers/1/action')
+ req.method = 'POST'
+ req.body = json.dumps(body)
+ req.headers["content-type"] = "application/json"
+ response = req.get_response(fakes.wsgi_app())
+ self.assertEqual(202, response.status_int)
+ location = response.headers['Location']
+ self.assertEqual('http://localhost/v1.1/images/123', location)
+
+ def test_create_image_no_name(self):
+ body = {
+ 'createImage': {},
+ }
+ req = webob.Request.blank('/v1.1/servers/1/action')
+ req.method = 'POST'
+ req.body = json.dumps(body)
+ req.headers["content-type"] = "application/json"
+ response = req.get_response(fakes.wsgi_app())
+ self.assertEqual(400, response.status_int)
+
+ def test_create_image_bad_metadata(self):
+ body = {
+ 'createImage': {
+ 'name': 'geoff',
+ 'metadata': 'henry',
+ },
+ }
+ req = webob.Request.blank('/v1.1/servers/1/action')
+ req.method = 'POST'
+ req.body = json.dumps(body)
+ req.headers["content-type"] = "application/json"
+ response = req.get_response(fakes.wsgi_app())
+ self.assertEqual(400, response.status_int)
+
+ def test_create_backup(self):
+ """The happy path for creating backups"""
+ self.flags(allow_admin_api=True)
+
+ body = {
+ 'createBackup': {
+ 'name': 'Backup 1',
+ 'backup_type': 'daily',
+ 'rotation': 1,
+ },
+ }
+
+ req = webob.Request.blank('/v1.1/servers/1/action')
+ req.method = 'POST'
+ req.body = json.dumps(body)
+ req.headers["content-type"] = "application/json"
+ response = req.get_response(fakes.wsgi_app())
+ self.assertEqual(202, response.status_int)
+ self.assertTrue(response.headers['Location'])
+
+
+class TestServerActionXMLDeserializer(test.TestCase):
+
+ def setUp(self):
+ self.deserializer = create_instance_helper.ServerXMLDeserializer()
+
+ def tearDown(self):
+ pass
+
+ def test_create_image(self):
+ serial_request = """
+<createImage xmlns="http://docs.openstack.org/compute/api/v1.1"
+ name="new-server-test"/>"""
+ request = self.deserializer.deserialize(serial_request, 'action')
+ expected = {
+ "createImage": {
+ "name": "new-server-test",
+ "metadata": {},
+ },
+ }
+ self.assertEquals(request['body'], expected)
+
+ def test_create_image_with_metadata(self):
+ serial_request = """
+<createImage xmlns="http://docs.openstack.org/compute/api/v1.1"
+ name="new-server-test">
+ <metadata>
+ <meta key="key1">value1</meta>
+ </metadata>
+</createImage>"""
+ request = self.deserializer.deserialize(serial_request, 'action')
+ expected = {
+ "createImage": {
+ "name": "new-server-test",
+ "metadata": {"key1": "value1"},
+ },
+ }
+ self.assertEquals(request['body'], expected)
diff --git a/nova/tests/api/openstack/test_server_metadata.py b/nova/tests/api/openstack/test_server_metadata.py
index f90485067..08a6a062a 100644
--- a/nova/tests/api/openstack/test_server_metadata.py
+++ b/nova/tests/api/openstack/test_server_metadata.py
@@ -17,7 +17,7 @@
import json
import webob
-
+from xml.dom import minidom
from nova import exception
from nova import flags
@@ -51,11 +51,10 @@ def delete_server_metadata(context, server_id, key):
def stub_server_metadata():
metadata = {
- "key1": "value1",
- "key2": "value2",
- "key3": "value3",
- "key4": "value4",
- "key5": "value5"}
+ "key1": "value1",
+ "key2": "value2",
+ "key3": "value3",
+ }
return metadata
@@ -84,71 +83,120 @@ class ServerMetaDataTest(test.TestCase):
def test_index(self):
self.stubs.Set(nova.db.api, 'instance_metadata_get',
return_server_metadata)
- req = webob.Request.blank('/v1.1/servers/1/meta')
- req.environ['api.version'] = '1.1'
+ req = webob.Request.blank('/v1.1/servers/1/metadata')
res = req.get_response(fakes.wsgi_app())
- res_dict = json.loads(res.body)
self.assertEqual(200, res.status_int)
+ res_dict = json.loads(res.body)
self.assertEqual('application/json', res.headers['Content-Type'])
- self.assertEqual('value1', res_dict['metadata']['key1'])
+ expected = {
+ 'metadata': {
+ 'key1': 'value1',
+ 'key2': 'value2',
+ 'key3': 'value3',
+ },
+ }
+ self.assertEqual(expected, res_dict)
+
+ def test_index_xml(self):
+ self.stubs.Set(nova.db.api, 'instance_metadata_get',
+ return_server_metadata)
+ request = webob.Request.blank("/v1.1/servers/1/metadata")
+ request.accept = "application/xml"
+ response = request.get_response(fakes.wsgi_app())
+ self.assertEqual(200, response.status_int)
+ self.assertEqual("application/xml", response.content_type)
+
+ actual_metadata = minidom.parseString(response.body.replace(" ", ""))
+
+ expected_metadata = minidom.parseString("""
+ <metadata xmlns="http://docs.openstack.org/compute/api/v1.1">
+ <meta key="key3">value3</meta>
+ <meta key="key2">value2</meta>
+ <meta key="key1">value1</meta>
+ </metadata>
+ """.replace(" ", "").replace("\n", ""))
+
+ self.assertEqual(expected_metadata.toxml(), actual_metadata.toxml())
def test_index_nonexistant_server(self):
self.stubs.Set(nova.db.api, 'instance_get', return_server_nonexistant)
- req = webob.Request.blank('/v1.1/servers/1/meta')
- req.environ['api.version'] = '1.1'
+ req = webob.Request.blank('/v1.1/servers/1/metadata')
res = req.get_response(fakes.wsgi_app())
self.assertEqual(404, res.status_int)
def test_index_no_data(self):
self.stubs.Set(nova.db.api, 'instance_metadata_get',
return_empty_server_metadata)
- req = webob.Request.blank('/v1.1/servers/1/meta')
- req.environ['api.version'] = '1.1'
+ req = webob.Request.blank('/v1.1/servers/1/metadata')
res = req.get_response(fakes.wsgi_app())
- res_dict = json.loads(res.body)
self.assertEqual(200, res.status_int)
- self.assertEqual('application/json', res.headers['Content-Type'])
- self.assertEqual(0, len(res_dict['metadata']))
+ res_dict = json.loads(res.body)
+ expected = {'metadata': {}}
+ self.assertEqual(expected, res_dict)
def test_show(self):
self.stubs.Set(nova.db.api, 'instance_metadata_get',
return_server_metadata)
- req = webob.Request.blank('/v1.1/servers/1/meta/key5')
- req.environ['api.version'] = '1.1'
+ req = webob.Request.blank('/v1.1/servers/1/metadata/key2')
res = req.get_response(fakes.wsgi_app())
res_dict = json.loads(res.body)
self.assertEqual(200, res.status_int)
- self.assertEqual('application/json', res.headers['Content-Type'])
- self.assertEqual('value5', res_dict['key5'])
+ expected = {'meta': {'key2': 'value2'}}
+ self.assertEqual(expected, res_dict)
+
+ def test_show_xml(self):
+ self.stubs.Set(nova.db.api, 'instance_metadata_get',
+ return_server_metadata)
+ request = webob.Request.blank("/v1.1/servers/1/metadata/key2")
+ request.accept = "application/xml"
+ response = request.get_response(fakes.wsgi_app())
+ self.assertEqual(200, response.status_int)
+ self.assertEqual("application/xml", response.content_type)
+
+ actual_metadata = minidom.parseString(response.body.replace(" ", ""))
+
+ expected_metadata = minidom.parseString("""
+ <meta xmlns="http://docs.openstack.org/compute/api/v1.1"
+ key="key2">value2</meta>
+ """.replace(" ", "").replace("\n", ""))
+
+ self.assertEqual(expected_metadata.toxml(), actual_metadata.toxml())
def test_show_nonexistant_server(self):
self.stubs.Set(nova.db.api, 'instance_get', return_server_nonexistant)
- req = webob.Request.blank('/v1.1/servers/1/meta/key5')
- req.environ['api.version'] = '1.1'
+ req = webob.Request.blank('/v1.1/servers/1/metadata/key2')
res = req.get_response(fakes.wsgi_app())
self.assertEqual(404, res.status_int)
def test_show_meta_not_found(self):
self.stubs.Set(nova.db.api, 'instance_metadata_get',
return_empty_server_metadata)
- req = webob.Request.blank('/v1.1/servers/1/meta/key6')
- req.environ['api.version'] = '1.1'
+ req = webob.Request.blank('/v1.1/servers/1/metadata/key6')
res = req.get_response(fakes.wsgi_app())
self.assertEqual(404, res.status_int)
def test_delete(self):
+ self.stubs.Set(nova.db.api, 'instance_metadata_get',
+ return_server_metadata)
self.stubs.Set(nova.db.api, 'instance_metadata_delete',
delete_server_metadata)
- req = webob.Request.blank('/v1.1/servers/1/meta/key5')
- req.environ['api.version'] = '1.1'
+ req = webob.Request.blank('/v1.1/servers/1/metadata/key2')
req.method = 'DELETE'
res = req.get_response(fakes.wsgi_app())
- self.assertEqual(200, res.status_int)
+ self.assertEqual(204, res.status_int)
+ self.assertEqual('', res.body)
def test_delete_nonexistant_server(self):
self.stubs.Set(nova.db.api, 'instance_get', return_server_nonexistant)
- req = webob.Request.blank('/v1.1/servers/1/meta/key5')
- req.environ['api.version'] = '1.1'
+ req = webob.Request.blank('/v1.1/servers/1/metadata/key1')
+ req.method = 'DELETE'
+ res = req.get_response(fakes.wsgi_app())
+ self.assertEqual(404, res.status_int)
+
+ def test_delete_meta_not_found(self):
+ self.stubs.Set(nova.db.api, 'instance_metadata_get',
+ return_empty_server_metadata)
+ req = webob.Request.blank('/v1.1/servers/1/metadata/key6')
req.method = 'DELETE'
res = req.get_response(fakes.wsgi_app())
self.assertEqual(404, res.status_int)
@@ -156,22 +204,45 @@ class ServerMetaDataTest(test.TestCase):
def test_create(self):
self.stubs.Set(nova.db.api, 'instance_metadata_update_or_create',
return_create_instance_metadata)
- req = webob.Request.blank('/v1.1/servers/1/meta')
- req.environ['api.version'] = '1.1'
+ req = webob.Request.blank('/v1.1/servers/1/metadata')
req.method = 'POST'
- req.body = '{"metadata": {"key1": "value1"}}'
- req.headers["content-type"] = "application/json"
+ req.content_type = "application/json"
+ expected = {"metadata": {"key1": "value1"}}
+ req.body = json.dumps(expected)
res = req.get_response(fakes.wsgi_app())
+
self.assertEqual(200, res.status_int)
res_dict = json.loads(res.body)
- self.assertEqual('application/json', res.headers['Content-Type'])
- self.assertEqual('value1', res_dict['metadata']['key1'])
+ self.assertEqual(expected, res_dict)
+
+ def test_create_xml(self):
+ self.stubs.Set(nova.db.api, "instance_metadata_update_or_create",
+ return_create_instance_metadata)
+ req = webob.Request.blank("/v1.1/servers/1/metadata")
+ req.method = "POST"
+ req.content_type = "application/xml"
+ req.accept = "application/xml"
+
+ request_metadata = minidom.parseString("""
+ <metadata xmlns="http://docs.openstack.org/compute/api/v1.1">
+ <meta key="key3">value3</meta>
+ <meta key="key2">value2</meta>
+ <meta key="key1">value1</meta>
+ </metadata>
+ """.replace(" ", "").replace("\n", ""))
+
+ req.body = str(request_metadata.toxml())
+ response = req.get_response(fakes.wsgi_app())
+
+ self.assertEqual(200, response.status_int)
+ actual_metadata = minidom.parseString(response.body)
+
+ self.assertEqual(request_metadata.toxml(), actual_metadata.toxml())
def test_create_empty_body(self):
self.stubs.Set(nova.db.api, 'instance_metadata_update_or_create',
return_create_instance_metadata)
- req = webob.Request.blank('/v1.1/servers/1/meta')
- req.environ['api.version'] = '1.1'
+ req = webob.Request.blank('/v1.1/servers/1/metadata')
req.method = 'POST'
req.headers["content-type"] = "application/json"
res = req.get_response(fakes.wsgi_app())
@@ -179,34 +250,112 @@ class ServerMetaDataTest(test.TestCase):
def test_create_nonexistant_server(self):
self.stubs.Set(nova.db.api, 'instance_get', return_server_nonexistant)
- req = webob.Request.blank('/v1.1/servers/100/meta')
- req.environ['api.version'] = '1.1'
+ req = webob.Request.blank('/v1.1/servers/100/metadata')
req.method = 'POST'
req.body = '{"metadata": {"key1": "value1"}}'
req.headers["content-type"] = "application/json"
res = req.get_response(fakes.wsgi_app())
self.assertEqual(404, res.status_int)
+ def test_update_all(self):
+ self.stubs.Set(nova.db.api, 'instance_metadata_update_or_create',
+ return_create_instance_metadata)
+ req = webob.Request.blank('/v1.1/servers/1/metadata')
+ req.method = 'PUT'
+ req.content_type = "application/json"
+ expected = {
+ 'metadata': {
+ 'key10': 'value10',
+ 'key99': 'value99',
+ },
+ }
+ req.body = json.dumps(expected)
+ res = req.get_response(fakes.wsgi_app())
+ self.assertEqual(200, res.status_int)
+ res_dict = json.loads(res.body)
+ self.assertEqual(expected, res_dict)
+
+ def test_update_all_empty_container(self):
+ self.stubs.Set(nova.db.api, 'instance_metadata_update_or_create',
+ return_create_instance_metadata)
+ req = webob.Request.blank('/v1.1/servers/1/metadata')
+ req.method = 'PUT'
+ req.content_type = "application/json"
+ expected = {'metadata': {}}
+ req.body = json.dumps(expected)
+ res = req.get_response(fakes.wsgi_app())
+ self.assertEqual(200, res.status_int)
+ res_dict = json.loads(res.body)
+ self.assertEqual(expected, res_dict)
+
+ def test_update_all_malformed_container(self):
+ self.stubs.Set(nova.db.api, 'instance_metadata_update_or_create',
+ return_create_instance_metadata)
+ req = webob.Request.blank('/v1.1/servers/1/metadata')
+ req.method = 'PUT'
+ req.content_type = "application/json"
+ expected = {'meta': {}}
+ req.body = json.dumps(expected)
+ res = req.get_response(fakes.wsgi_app())
+ self.assertEqual(400, res.status_int)
+
+ def test_update_all_malformed_data(self):
+ self.stubs.Set(nova.db.api, 'instance_metadata_update_or_create',
+ return_create_instance_metadata)
+ req = webob.Request.blank('/v1.1/servers/1/metadata')
+ req.method = 'PUT'
+ req.content_type = "application/json"
+ expected = {'metadata': ['asdf']}
+ req.body = json.dumps(expected)
+ res = req.get_response(fakes.wsgi_app())
+ self.assertEqual(400, res.status_int)
+
+ def test_update_all_nonexistant_server(self):
+ self.stubs.Set(nova.db.api, 'instance_get', return_server_nonexistant)
+ req = webob.Request.blank('/v1.1/servers/100/metadata')
+ req.method = 'PUT'
+ req.content_type = "application/json"
+ req.body = json.dumps({'metadata': {'key10': 'value10'}})
+ res = req.get_response(fakes.wsgi_app())
+ self.assertEqual(404, res.status_int)
+
def test_update_item(self):
self.stubs.Set(nova.db.api, 'instance_metadata_update_or_create',
return_create_instance_metadata)
- req = webob.Request.blank('/v1.1/servers/1/meta/key1')
- req.environ['api.version'] = '1.1'
+ req = webob.Request.blank('/v1.1/servers/1/metadata/key1')
req.method = 'PUT'
- req.body = '{"key1": "value1"}'
+ req.body = '{"meta": {"key1": "value1"}}'
req.headers["content-type"] = "application/json"
res = req.get_response(fakes.wsgi_app())
self.assertEqual(200, res.status_int)
self.assertEqual('application/json', res.headers['Content-Type'])
res_dict = json.loads(res.body)
- self.assertEqual('value1', res_dict['key1'])
+ expected = {'meta': {'key1': 'value1'}}
+ self.assertEqual(expected, res_dict)
+
+ def test_update_item_xml(self):
+ self.stubs.Set(nova.db.api, 'instance_metadata_update_or_create',
+ return_create_instance_metadata)
+ req = webob.Request.blank('/v1.1/servers/1/metadata/key9')
+ req.method = 'PUT'
+ req.accept = "application/json"
+ req.content_type = "application/xml"
+ req.body = """
+ <meta xmlns="http://docs.openstack.org/compute/api/v1.1"
+ key="key9">value9</meta>
+ """
+ res = req.get_response(fakes.wsgi_app())
+ self.assertEqual(200, res.status_int)
+ self.assertEqual('application/json', res.headers['Content-Type'])
+ res_dict = json.loads(res.body)
+ expected = {'meta': {'key9': 'value9'}}
+ self.assertEqual(expected, res_dict)
def test_update_item_nonexistant_server(self):
self.stubs.Set(nova.db.api, 'instance_get', return_server_nonexistant)
- req = webob.Request.blank('/v1.1/servers/asdf/100/key1')
- req.environ['api.version'] = '1.1'
+ req = webob.Request.blank('/v1.1/servers/asdf/metadata/key1')
req.method = 'PUT'
- req.body = '{"key1": "value1"}'
+ req.body = '{"meta":{"key1": "value1"}}'
req.headers["content-type"] = "application/json"
res = req.get_response(fakes.wsgi_app())
self.assertEqual(404, res.status_int)
@@ -214,8 +363,7 @@ class ServerMetaDataTest(test.TestCase):
def test_update_item_empty_body(self):
self.stubs.Set(nova.db.api, 'instance_metadata_update_or_create',
return_create_instance_metadata)
- req = webob.Request.blank('/v1.1/servers/1/meta/key1')
- req.environ['api.version'] = '1.1'
+ req = webob.Request.blank('/v1.1/servers/1/metadata/key1')
req.method = 'PUT'
req.headers["content-type"] = "application/json"
res = req.get_response(fakes.wsgi_app())
@@ -224,10 +372,9 @@ class ServerMetaDataTest(test.TestCase):
def test_update_item_too_many_keys(self):
self.stubs.Set(nova.db.api, 'instance_metadata_update_or_create',
return_create_instance_metadata)
- req = webob.Request.blank('/v1.1/servers/1/meta/key1')
- req.environ['api.version'] = '1.1'
+ req = webob.Request.blank('/v1.1/servers/1/metadata/key1')
req.method = 'PUT'
- req.body = '{"key1": "value1", "key2": "value2"}'
+ req.body = '{"meta": {"key1": "value1", "key2": "value2"}}'
req.headers["content-type"] = "application/json"
res = req.get_response(fakes.wsgi_app())
self.assertEqual(400, res.status_int)
@@ -235,10 +382,9 @@ class ServerMetaDataTest(test.TestCase):
def test_update_item_body_uri_mismatch(self):
self.stubs.Set(nova.db.api, 'instance_metadata_update_or_create',
return_create_instance_metadata)
- req = webob.Request.blank('/v1.1/servers/1/meta/bad')
- req.environ['api.version'] = '1.1'
+ req = webob.Request.blank('/v1.1/servers/1/metadata/bad')
req.method = 'PUT'
- req.body = '{"key1": "value1"}'
+ req.body = '{"meta": {"key1": "value1"}}'
req.headers["content-type"] = "application/json"
res = req.get_response(fakes.wsgi_app())
self.assertEqual(400, res.status_int)
@@ -250,8 +396,7 @@ class ServerMetaDataTest(test.TestCase):
for num in range(FLAGS.quota_metadata_items + 1):
data['metadata']['key%i' % num] = "blah"
json_string = str(data).replace("\'", "\"")
- req = webob.Request.blank('/v1.1/servers/1/meta')
- req.environ['api.version'] = '1.1'
+ req = webob.Request.blank('/v1.1/servers/1/metadata')
req.method = 'POST'
req.body = json_string
req.headers["content-type"] = "application/json"
@@ -261,10 +406,9 @@ class ServerMetaDataTest(test.TestCase):
def test_to_many_metadata_items_on_update_item(self):
self.stubs.Set(nova.db.api, 'instance_metadata_update_or_create',
return_create_instance_metadata_max)
- req = webob.Request.blank('/v1.1/servers/1/meta/key1')
- req.environ['api.version'] = '1.1'
+ req = webob.Request.blank('/v1.1/servers/1/metadata/key1')
req.method = 'PUT'
- req.body = '{"a new key": "a new value"}'
+ req.body = '{"meta": {"a new key": "a new value"}}'
req.headers["content-type"] = "application/json"
res = req.get_response(fakes.wsgi_app())
self.assertEqual(400, res.status_int)
diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py
index 4d42972c1..1349b289d 100644
--- a/nova/tests/api/openstack/test_servers.py
+++ b/nova/tests/api/openstack/test_servers.py
@@ -260,17 +260,6 @@ class ServersTest(test.TestCase):
self.stubs.Set(nova.compute.API, "get_diagnostics", fake_compute_api)
self.stubs.Set(nova.compute.API, "get_actions", fake_compute_api)
- fakes.stub_out_glance(self.stubs)
- fakes.stub_out_compute_api_snapshot(self.stubs)
- service_class = 'nova.image.glance.GlanceImageService'
- self.service = utils.import_object(service_class)
- self.context = context.RequestContext(1, None)
- self.service.delete_all()
- self.sent_to_glance = {}
- fakes.stub_out_glance_add_image(self.stubs, self.sent_to_glance)
-
- self.allow_admin = FLAGS.allow_admin_api
-
self.webreq = common.webob_factory('/v1.0/servers')
def test_get_server_by_id(self):
@@ -767,7 +756,7 @@ class ServersTest(test.TestCase):
self.assertEquals(ip.getAttribute('addr'), private)
def test_get_server_by_id_with_addresses_v1_1(self):
- FLAGS.use_ipv6 = True
+ self.flags(use_ipv6=True)
interfaces = [
{
'network': {'label': 'network_1'},
@@ -811,7 +800,7 @@ class ServersTest(test.TestCase):
self.assertEqual(addresses, expected)
def test_get_server_by_id_with_addresses_v1_1_ipv6_disabled(self):
- FLAGS.use_ipv6 = False
+ self.flags(use_ipv6=False)
interfaces = [
{
'network': {'label': 'network_1'},
@@ -854,7 +843,7 @@ class ServersTest(test.TestCase):
self.assertEqual(addresses, expected)
def test_get_server_addresses_v1_1(self):
- FLAGS.use_ipv6 = True
+ self.flags(use_ipv6=True)
interfaces = [
{
'network': {'label': 'network_1'},
@@ -905,7 +894,7 @@ class ServersTest(test.TestCase):
self.assertEqual(res_dict, expected)
def test_get_server_addresses_single_network_v1_1(self):
- FLAGS.use_ipv6 = True
+ self.flags(use_ipv6=True)
interfaces = [
{
'network': {'label': 'network_1'},
@@ -1756,339 +1745,67 @@ class ServersTest(test.TestCase):
def test_server_pause(self):
self.flags(allow_admin_api=True)
- body = dict(server=dict(
- name='server_test', imageId=2, flavorId=2, metadata={},
- personality={}))
req = webob.Request.blank('/v1.0/servers/1/pause')
req.method = 'POST'
req.content_type = 'application/json'
- req.body = json.dumps(body)
res = req.get_response(fakes.wsgi_app())
self.assertEqual(res.status_int, 202)
def test_server_unpause(self):
self.flags(allow_admin_api=True)
- body = dict(server=dict(
- name='server_test', imageId=2, flavorId=2, metadata={},
- personality={}))
req = webob.Request.blank('/v1.0/servers/1/unpause')
req.method = 'POST'
req.content_type = 'application/json'
- req.body = json.dumps(body)
res = req.get_response(fakes.wsgi_app())
self.assertEqual(res.status_int, 202)
def test_server_suspend(self):
self.flags(allow_admin_api=True)
- body = dict(server=dict(
- name='server_test', imageId=2, flavorId=2, metadata={},
- personality={}))
req = webob.Request.blank('/v1.0/servers/1/suspend')
req.method = 'POST'
req.content_type = 'application/json'
- req.body = json.dumps(body)
res = req.get_response(fakes.wsgi_app())
self.assertEqual(res.status_int, 202)
def test_server_resume(self):
self.flags(allow_admin_api=True)
- body = dict(server=dict(
- name='server_test', imageId=2, flavorId=2, metadata={},
- personality={}))
req = webob.Request.blank('/v1.0/servers/1/resume')
req.method = 'POST'
req.content_type = 'application/json'
- req.body = json.dumps(body)
res = req.get_response(fakes.wsgi_app())
self.assertEqual(res.status_int, 202)
def test_server_reset_network(self):
self.flags(allow_admin_api=True)
- body = dict(server=dict(
- name='server_test', imageId=2, flavorId=2, metadata={},
- personality={}))
req = webob.Request.blank('/v1.0/servers/1/reset_network')
req.method = 'POST'
req.content_type = 'application/json'
- req.body = json.dumps(body)
res = req.get_response(fakes.wsgi_app())
self.assertEqual(res.status_int, 202)
def test_server_inject_network_info(self):
self.flags(allow_admin_api=True)
- body = dict(server=dict(
- name='server_test', imageId=2, flavorId=2, metadata={},
- personality={}))
req = webob.Request.blank(
'/v1.0/servers/1/inject_network_info')
req.method = 'POST'
req.content_type = 'application/json'
- req.body = json.dumps(body)
res = req.get_response(fakes.wsgi_app())
self.assertEqual(res.status_int, 202)
def test_server_diagnostics(self):
+ self.flags(allow_admin_api=False)
req = webob.Request.blank("/v1.0/servers/1/diagnostics")
req.method = "GET"
res = req.get_response(fakes.wsgi_app())
self.assertEqual(res.status_int, 404)
def test_server_actions(self):
+ self.flags(allow_admin_api=False)
req = webob.Request.blank("/v1.0/servers/1/actions")
req.method = "GET"
res = req.get_response(fakes.wsgi_app())
self.assertEqual(res.status_int, 404)
- def test_server_change_password(self):
- body = {'changePassword': {'adminPass': '1234pass'}}
- req = webob.Request.blank('/v1.0/servers/1/action')
- req.method = 'POST'
- req.content_type = 'application/json'
- req.body = json.dumps(body)
- res = req.get_response(fakes.wsgi_app())
- self.assertEqual(res.status_int, 501)
-
- def test_server_change_password_xml(self):
- req = webob.Request.blank('/v1.0/servers/1/action')
- req.method = 'POST'
- req.content_type = 'application/xml'
- req.body = '<changePassword adminPass="1234pass">'
-# res = req.get_response(fakes.wsgi_app())
-# self.assertEqual(res.status_int, 501)
-
- def test_server_change_password_v1_1(self):
- mock_method = MockSetAdminPassword()
- self.stubs.Set(nova.compute.api.API, 'set_admin_password', mock_method)
- body = {'changePassword': {'adminPass': '1234pass'}}
- req = webob.Request.blank('/v1.1/servers/1/action')
- req.method = 'POST'
- req.content_type = 'application/json'
- req.body = json.dumps(body)
- res = req.get_response(fakes.wsgi_app())
- self.assertEqual(res.status_int, 202)
- self.assertEqual(mock_method.instance_id, '1')
- self.assertEqual(mock_method.password, '1234pass')
-
- def test_server_change_password_bad_request_v1_1(self):
- body = {'changePassword': {'pass': '12345'}}
- req = webob.Request.blank('/v1.1/servers/1/action')
- req.method = 'POST'
- req.content_type = 'application/json'
- req.body = json.dumps(body)
- res = req.get_response(fakes.wsgi_app())
- self.assertEqual(res.status_int, 400)
-
- def test_server_change_password_empty_string_v1_1(self):
- body = {'changePassword': {'adminPass': ''}}
- req = webob.Request.blank('/v1.1/servers/1/action')
- req.method = 'POST'
- req.content_type = 'application/json'
- req.body = json.dumps(body)
- res = req.get_response(fakes.wsgi_app())
- self.assertEqual(res.status_int, 400)
-
- def test_server_change_password_none_v1_1(self):
- body = {'changePassword': {'adminPass': None}}
- req = webob.Request.blank('/v1.1/servers/1/action')
- req.method = 'POST'
- req.content_type = 'application/json'
- req.body = json.dumps(body)
- res = req.get_response(fakes.wsgi_app())
- self.assertEqual(res.status_int, 400)
-
- def test_server_change_password_not_a_string_v1_1(self):
- body = {'changePassword': {'adminPass': 1234}}
- req = webob.Request.blank('/v1.1/servers/1/action')
- req.method = 'POST'
- req.content_type = 'application/json'
- req.body = json.dumps(body)
- res = req.get_response(fakes.wsgi_app())
- self.assertEqual(res.status_int, 400)
-
- def test_server_reboot(self):
- body = dict(server=dict(
- name='server_test', imageId=2, flavorId=2, metadata={},
- personality={}))
- req = webob.Request.blank('/v1.0/servers/1/action')
- req.method = 'POST'
- req.content_type = 'application/json'
- req.body = json.dumps(body)
- res = req.get_response(fakes.wsgi_app())
-
- def test_server_rebuild_accepted(self):
- body = {
- "rebuild": {
- "imageId": 2,
- },
- }
-
- req = webob.Request.blank('/v1.0/servers/1/action')
- req.method = 'POST'
- req.content_type = 'application/json'
- req.body = json.dumps(body)
-
- res = req.get_response(fakes.wsgi_app())
- self.assertEqual(res.status_int, 202)
- self.assertEqual(res.body, "")
-
- def test_server_rebuild_rejected_when_building(self):
- body = {
- "rebuild": {
- "imageId": 2,
- },
- }
-
- state = power_state.BUILDING
- new_return_server = return_server_with_power_state(state)
- self.stubs.Set(nova.db.api, 'instance_get', new_return_server)
- self.stubs.Set(nova.db, 'instance_get_by_uuid',
- return_server_with_uuid_and_power_state(state))
-
- req = webob.Request.blank('/v1.0/servers/1/action')
- req.method = 'POST'
- req.content_type = 'application/json'
- req.body = json.dumps(body)
-
- res = req.get_response(fakes.wsgi_app())
- self.assertEqual(res.status_int, 409)
-
- def test_server_rebuild_bad_entity(self):
- body = {
- "rebuild": {
- },
- }
-
- req = webob.Request.blank('/v1.0/servers/1/action')
- req.method = 'POST'
- req.content_type = 'application/json'
- req.body = json.dumps(body)
-
- res = req.get_response(fakes.wsgi_app())
- self.assertEqual(res.status_int, 400)
-
- def test_server_rebuild_accepted_minimum_v1_1(self):
- body = {
- "rebuild": {
- "imageRef": "http://localhost/images/2",
- },
- }
-
- req = webob.Request.blank('/v1.1/servers/1/action')
- req.method = 'POST'
- req.content_type = 'application/json'
- req.body = json.dumps(body)
-
- res = req.get_response(fakes.wsgi_app())
- self.assertEqual(res.status_int, 202)
-
- def test_server_rebuild_rejected_when_building_v1_1(self):
- body = {
- "rebuild": {
- "imageRef": "http://localhost/images/2",
- },
- }
-
- state = power_state.BUILDING
- new_return_server = return_server_with_power_state(state)
- self.stubs.Set(nova.db.api, 'instance_get', new_return_server)
- self.stubs.Set(nova.db, 'instance_get_by_uuid',
- return_server_with_uuid_and_power_state(state))
-
- req = webob.Request.blank('/v1.1/servers/1/action')
- req.method = 'POST'
- req.content_type = 'application/json'
- req.body = json.dumps(body)
-
- res = req.get_response(fakes.wsgi_app())
- self.assertEqual(res.status_int, 409)
-
- def test_server_rebuild_accepted_with_metadata_v1_1(self):
- body = {
- "rebuild": {
- "imageRef": "http://localhost/images/2",
- "metadata": {
- "new": "metadata",
- },
- },
- }
-
- req = webob.Request.blank('/v1.1/servers/1/action')
- req.method = 'POST'
- req.content_type = 'application/json'
- req.body = json.dumps(body)
-
- res = req.get_response(fakes.wsgi_app())
- self.assertEqual(res.status_int, 202)
-
- def test_server_rebuild_accepted_with_bad_metadata_v1_1(self):
- body = {
- "rebuild": {
- "imageRef": "http://localhost/images/2",
- "metadata": "stack",
- },
- }
-
- req = webob.Request.blank('/v1.1/servers/1/action')
- req.method = 'POST'
- req.content_type = 'application/json'
- req.body = json.dumps(body)
-
- res = req.get_response(fakes.wsgi_app())
- self.assertEqual(res.status_int, 400)
-
- def test_server_rebuild_bad_entity_v1_1(self):
- body = {
- "rebuild": {
- "imageId": 2,
- },
- }
-
- req = webob.Request.blank('/v1.1/servers/1/action')
- req.method = 'POST'
- req.content_type = 'application/json'
- req.body = json.dumps(body)
-
- res = req.get_response(fakes.wsgi_app())
- self.assertEqual(res.status_int, 400)
-
- def test_server_rebuild_bad_personality_v1_1(self):
- body = {
- "rebuild": {
- "imageRef": "http://localhost/images/2",
- "personality": [{
- "path": "/path/to/file",
- "contents": "INVALID b64",
- }]
- },
- }
-
- req = webob.Request.blank('/v1.1/servers/1/action')
- req.method = 'POST'
- req.content_type = 'application/json'
- req.body = json.dumps(body)
-
- res = req.get_response(fakes.wsgi_app())
- self.assertEqual(res.status_int, 400)
-
- def test_server_rebuild_personality_v1_1(self):
- body = {
- "rebuild": {
- "imageRef": "http://localhost/images/2",
- "personality": [{
- "path": "/path/to/file",
- "contents": base64.b64encode("Test String"),
- }]
- },
- }
-
- req = webob.Request.blank('/v1.1/servers/1/action')
- req.method = 'POST'
- req.content_type = 'application/json'
- req.body = json.dumps(body)
-
- res = req.get_response(fakes.wsgi_app())
- self.assertEqual(res.status_int, 202)
-
def test_delete_server_instance(self):
req = webob.Request.blank('/v1.0/servers/1')
req.method = 'DELETE'
@@ -2156,199 +1873,6 @@ class ServersTest(test.TestCase):
self.assertEqual(res.status_int, 204)
self.assertEqual(self.server_delete_called, True)
- def test_resize_server(self):
- req = self.webreq('/1/action', 'POST', dict(resize=dict(flavorId=3)))
-
- self.resize_called = False
-
- def resize_mock(*args):
- self.resize_called = True
-
- self.stubs.Set(nova.compute.api.API, 'resize', resize_mock)
-
- res = req.get_response(fakes.wsgi_app())
- self.assertEqual(res.status_int, 202)
- self.assertEqual(self.resize_called, True)
-
- def test_resize_server_v11(self):
- req = webob.Request.blank('/v1.1/servers/1/action')
- req.content_type = 'application/json'
- req.method = 'POST'
- body_dict = {
- "resize": {
- "flavorRef": 3,
- },
- }
- req.body = json.dumps(body_dict)
-
- self.resize_called = False
-
- def resize_mock(*args):
- self.resize_called = True
-
- self.stubs.Set(nova.compute.api.API, 'resize', resize_mock)
-
- res = req.get_response(fakes.wsgi_app())
- self.assertEqual(res.status_int, 202)
- self.assertEqual(self.resize_called, True)
-
- def test_resize_bad_flavor_data(self):
- req = self.webreq('/1/action', 'POST', {"resize": "bad_data"})
-
- self.resize_called = False
-
- def resize_mock(*args):
- self.resize_called = True
-
- 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(self.resize_called, False)
-
- def test_resize_invalid_flavorid(self):
- req = self.webreq('/1/action', 'POST', {"resize": {"flavorId": 300}})
- res = req.get_response(fakes.wsgi_app())
- self.assertEqual(res.status_int, 400)
-
- def test_resize_nonint_flavorid(self):
- req = self.webreq('/1/action', 'POST', {"resize": {"flavorId": "a"}})
- res = req.get_response(fakes.wsgi_app())
- self.assertEqual(res.status_int, 400)
-
- def test_resize_invalid_flavorid_v1_1(self):
- req = webob.Request.blank('/v1.1/servers/1/action')
- req.content_type = 'application/json'
- req.method = 'POST'
- resize_body = {
- "resize": {
- "image": {
- "id": 300,
- },
- },
- }
- req.body = json.dumps(resize_body)
- res = req.get_response(fakes.wsgi_app())
- self.assertEqual(res.status_int, 400)
-
- def test_resize_nonint_flavorid_v1_1(self):
- req = webob.Request.blank('/v1.1/servers/1/action')
- req.content_type = 'application/json'
- req.method = 'POST'
- resize_body = {
- "resize": {
- "image": {
- "id": "a",
- },
- },
- }
- req.body = json.dumps(resize_body)
- res = req.get_response(fakes.wsgi_app())
- self.assertEqual(res.status_int, 400)
-
- def test_resize_raises_fails(self):
- req = self.webreq('/1/action', 'POST', dict(resize=dict(flavorId=3)))
-
- def resize_mock(*args):
- raise Exception("An error occurred.")
-
- self.stubs.Set(nova.compute.api.API, 'resize', resize_mock)
-
- res = req.get_response(fakes.wsgi_app())
- self.assertEqual(res.status_int, 500)
-
- def test_resized_server_has_correct_status(self):
- req = self.webreq('/1', 'GET')
-
- def fake_migration_get(*args):
- return {}
-
- self.stubs.Set(nova.db, 'migration_get_by_instance_and_status',
- fake_migration_get)
- res = req.get_response(fakes.wsgi_app())
- body = json.loads(res.body)
- self.assertEqual(body['server']['status'], 'RESIZE-CONFIRM')
-
- def test_confirm_resize_server(self):
- req = self.webreq('/1/action', 'POST', dict(confirmResize=None))
-
- self.resize_called = False
-
- def confirm_resize_mock(*args):
- self.resize_called = True
-
- self.stubs.Set(nova.compute.api.API, 'confirm_resize',
- confirm_resize_mock)
-
- res = req.get_response(fakes.wsgi_app())
- self.assertEqual(res.status_int, 204)
- self.assertEqual(self.resize_called, True)
-
- def test_confirm_resize_server_fails(self):
- req = self.webreq('/1/action', 'POST', dict(confirmResize=None))
-
- def confirm_resize_mock(*args):
- raise Exception("An error occurred.")
-
- self.stubs.Set(nova.compute.api.API, 'confirm_resize',
- confirm_resize_mock)
-
- res = req.get_response(fakes.wsgi_app())
- self.assertEqual(res.status_int, 400)
-
- def test_revert_resize_server(self):
- req = self.webreq('/1/action', 'POST', dict(revertResize=None))
-
- self.resize_called = False
-
- def revert_resize_mock(*args):
- self.resize_called = True
-
- self.stubs.Set(nova.compute.api.API, 'revert_resize',
- revert_resize_mock)
-
- res = req.get_response(fakes.wsgi_app())
- self.assertEqual(res.status_int, 202)
- self.assertEqual(self.resize_called, True)
-
- def test_revert_resize_server_fails(self):
- req = self.webreq('/1/action', 'POST', dict(revertResize=None))
-
- def revert_resize_mock(*args):
- raise Exception("An error occurred.")
-
- self.stubs.Set(nova.compute.api.API, 'revert_resize',
- revert_resize_mock)
-
- res = req.get_response(fakes.wsgi_app())
- self.assertEqual(res.status_int, 400)
-
- def test_migrate_server(self):
- """This is basically the same as resize, only we provide the `migrate`
- attribute in the body's dict.
- """
- req = self.webreq('/1/migrate', 'POST')
-
- FLAGS.allow_admin_api = True
- self.resize_called = False
-
- def resize_mock(*args):
- self.resize_called = True
-
- self.stubs.Set(nova.compute.api.API, 'resize', resize_mock)
-
- res = req.get_response(fakes.wsgi_app())
- self.assertEqual(res.status_int, 202)
- self.assertEqual(self.resize_called, True)
-
- def test_migrate_server_no_admin_api_fails(self):
- req = self.webreq('/1/migrate', 'POST')
-
- FLAGS.allow_admin_api = False
-
- res = req.get_response(fakes.wsgi_app())
- self.assertEqual(res.status_int, 404)
-
def test_shutdown_status(self):
new_server = return_server_with_power_state(power_state.SHUTDOWN)
self.stubs.Set(nova.db.api, 'instance_get', new_server)
@@ -2367,268 +1891,6 @@ class ServersTest(test.TestCase):
res_dict = json.loads(res.body)
self.assertEqual(res_dict['server']['status'], 'SHUTOFF')
- def test_create_image_v1_1(self):
- body = {
- 'createImage': {
- 'name': 'Snapshot 1',
- },
- }
- req = webob.Request.blank('/v1.1/servers/1/action')
- req.method = 'POST'
- req.body = json.dumps(body)
- req.headers["content-type"] = "application/json"
- response = req.get_response(fakes.wsgi_app())
- self.assertEqual(202, response.status_int)
- location = response.headers['Location']
- self.assertEqual('http://localhost/v1.1/images/123', location)
-
- def test_create_image_v1_1_with_metadata(self):
- body = {
- 'createImage': {
- 'name': 'Snapshot 1',
- 'metadata': {'key': 'asdf'},
- },
- }
- req = webob.Request.blank('/v1.1/servers/1/action')
- req.method = 'POST'
- req.body = json.dumps(body)
- req.headers["content-type"] = "application/json"
- response = req.get_response(fakes.wsgi_app())
- self.assertEqual(202, response.status_int)
- location = response.headers['Location']
- self.assertEqual('http://localhost/v1.1/images/123', location)
-
- def test_create_image_v1_1_no_name(self):
- body = {
- 'createImage': {},
- }
- req = webob.Request.blank('/v1.1/servers/1/action')
- req.method = 'POST'
- req.body = json.dumps(body)
- req.headers["content-type"] = "application/json"
- response = req.get_response(fakes.wsgi_app())
- self.assertEqual(400, response.status_int)
-
- def test_create_image_v1_1_bad_metadata(self):
- body = {
- 'createImage': {
- 'name': 'geoff',
- 'metadata': 'henry',
- },
- }
- req = webob.Request.blank('/v1.1/servers/1/action')
- req.method = 'POST'
- req.body = json.dumps(body)
- req.headers["content-type"] = "application/json"
- response = req.get_response(fakes.wsgi_app())
- self.assertEqual(400, response.status_int)
-
- def test_create_backup(self):
- """The happy path for creating backups"""
- FLAGS.allow_admin_api = True
-
- body = {
- 'createBackup': {
- 'name': 'Backup 1',
- 'backup_type': 'daily',
- 'rotation': 1,
- },
- }
-
- req = webob.Request.blank('/v1.0/servers/1/action')
- req.method = 'POST'
- req.body = json.dumps(body)
- req.headers["content-type"] = "application/json"
- response = req.get_response(fakes.wsgi_app())
- self.assertEqual(202, response.status_int)
- self.assertTrue(response.headers['Location'])
-
- def test_create_backup_v1_1(self):
- """The happy path for creating backups through v1.1 api"""
- FLAGS.allow_admin_api = True
-
- body = {
- 'createBackup': {
- 'name': 'Backup 1',
- 'backup_type': 'daily',
- 'rotation': 1,
- },
- }
-
- req = webob.Request.blank('/v1.1/servers/1/action')
- req.method = 'POST'
- req.body = json.dumps(body)
- req.headers["content-type"] = "application/json"
- response = req.get_response(fakes.wsgi_app())
- self.assertEqual(202, response.status_int)
- self.assertTrue(response.headers['Location'])
-
- def test_create_backup_admin_api_off(self):
- """The happy path for creating backups"""
- FLAGS.allow_admin_api = False
-
- body = {
- 'createBackup': {
- 'name': 'Backup 1',
- 'backup_type': 'daily',
- 'rotation': 1,
- },
- }
-
- req = webob.Request.blank('/v1.0/servers/1/action')
- req.method = 'POST'
- req.body = json.dumps(body)
- req.headers["content-type"] = "application/json"
- response = req.get_response(fakes.wsgi_app())
- self.assertEqual(501, response.status_int)
-
- def test_create_backup_with_metadata(self):
- FLAGS.allow_admin_api = True
-
- body = {
- 'createBackup': {
- 'name': 'Backup 1',
- 'backup_type': 'daily',
- 'rotation': 1,
- 'metadata': {'123': 'asdf'},
- },
- }
-
- req = webob.Request.blank('/v1.0/servers/1/action')
- req.method = 'POST'
- req.body = json.dumps(body)
- req.headers["content-type"] = "application/json"
- response = req.get_response(fakes.wsgi_app())
- self.assertEqual(202, response.status_int)
- self.assertTrue(response.headers['Location'])
-
- def test_create_backup_no_name(self):
- """Name is required for backups"""
- FLAGS.allow_admin_api = True
-
- body = {
- 'createBackup': {
- 'backup_type': 'daily',
- 'rotation': 1,
- },
- }
-
- req = webob.Request.blank('/v1.0/images')
- req.method = 'POST'
- req.body = json.dumps(body)
- req.headers["content-type"] = "application/json"
- response = req.get_response(fakes.wsgi_app())
- self.assertEqual(400, response.status_int)
-
- def test_create_backup_no_rotation(self):
- """Rotation is required for backup requests"""
- FLAGS.allow_admin_api = True
-
- body = {
- 'createBackup': {
- 'name': 'Backup 1',
- 'backup_type': 'daily',
- },
- }
-
- req = webob.Request.blank('/v1.0/images')
- req.method = 'POST'
- req.body = json.dumps(body)
- req.headers["content-type"] = "application/json"
-
- response = req.get_response(fakes.wsgi_app())
- self.assertEqual(400, response.status_int)
-
- def test_create_backup_no_backup_type(self):
- """Backup Type (daily or weekly) is required for backup requests"""
- FLAGS.allow_admin_api = True
-
- body = {
- 'createBackup': {
- 'name': 'Backup 1',
- 'rotation': 1,
- },
- }
- req = webob.Request.blank('/v1.0/images')
- req.method = 'POST'
- req.body = json.dumps(body)
- req.headers["content-type"] = "application/json"
-
- response = req.get_response(fakes.wsgi_app())
- self.assertEqual(400, response.status_int)
-
- def test_create_backup_bad_entity(self):
- FLAGS.allow_admin_api = True
-
- body = {'createBackup': 'go'}
- req = webob.Request.blank('/v1.0/images')
- req.method = 'POST'
- req.body = json.dumps(body)
- req.headers["content-type"] = "application/json"
-
- response = req.get_response(fakes.wsgi_app())
- self.assertEqual(400, response.status_int)
-
-
-class TestServerActionXMLDeserializer(test.TestCase):
-
- def setUp(self):
- self.deserializer = create_instance_helper.ServerXMLDeserializer()
-
- def tearDown(self):
- pass
-
- def test_create_image(self):
- serial_request = """
-<createImage xmlns="http://docs.openstack.org/compute/api/v1.1"
- name="new-server-test"/>"""
- request = self.deserializer.deserialize(serial_request, 'action')
- expected = {
- "createImage": {
- "name": "new-server-test",
- "metadata": {},
- },
- }
- self.assertEquals(request['body'], expected)
-
- def test_create_image_with_metadata(self):
- serial_request = """
-<createImage xmlns="http://docs.openstack.org/compute/api/v1.1"
- name="new-server-test">
- <metadata>
- <meta key="key1">value1</meta>
- </metadata>
-</createImage>"""
- request = self.deserializer.deserialize(serial_request, 'action')
- expected = {
- "createImage": {
- "name": "new-server-test",
- "metadata": {"key1": "value1"},
- },
- }
- self.assertEquals(request['body'], expected)
-
- def test_create_backup_with_metadata(self):
- serial_request = """
-<createBackup xmlns="http://docs.openstack.org/compute/api/v1.1"
- name="new-server-test"
- rotation="12"
- backup_type="daily">
- <metadata>
- <meta key="key1">value1</meta>
- </metadata>
-</createBackup>"""
- request = self.deserializer.deserialize(serial_request, 'action')
- expected = {
- "createBackup": {
- "name": "new-server-test",
- "rotation": "12",
- "backup_type": "daily",
- "metadata": {"key1": "value1"},
- },
- }
- self.assertEquals(request['body'], expected)
-
class TestServerCreateRequestXMLDeserializerV10(unittest.TestCase):
@@ -2915,9 +2177,10 @@ b25zLiINCg0KLVJpY2hhcmQgQmFjaA==""",
self.assertEqual(request['body'], expected)
-class TestServerCreateRequestXMLDeserializerV11(unittest.TestCase):
+class TestServerCreateRequestXMLDeserializerV11(test.TestCase):
def setUp(self):
+ super(TestServerCreateRequestXMLDeserializerV11, self).setUp()
self.deserializer = create_instance_helper.ServerXMLDeserializer()
def test_minimal_request(self):
@@ -3062,7 +2325,7 @@ class TestServerCreateRequestXMLDeserializerV11(unittest.TestCase):
],
},
}
- self.assertEquals(request['body'], expected)
+ self.assertDictMatch(request['body'], expected)
def test_spec_request(self):
image_bookmark_link = "http://servers.api.openstack.org/1234/" + \
diff --git a/nova/tests/api/openstack/test_versions.py b/nova/tests/api/openstack/test_versions.py
index e68455778..1269f13c9 100644
--- a/nova/tests/api/openstack/test_versions.py
+++ b/nova/tests/api/openstack/test_versions.py
@@ -38,24 +38,24 @@ VERSIONS = {
"rel": "describedby",
"type": "application/pdf",
"href": "http://docs.rackspacecloud.com/"
- "servers/api/v1.0/cs-devguide-20110125.pdf"
+ "servers/api/v1.0/cs-devguide-20110125.pdf",
},
{
"rel": "describedby",
"type": "application/vnd.sun.wadl+xml",
"href": "http://docs.rackspacecloud.com/"
- "servers/api/v1.0/application.wadl"
+ "servers/api/v1.0/application.wadl",
},
],
"media-types": [
{
"base": "application/xml",
- "type": "application/vnd.openstack.compute-v1.0+xml"
+ "type": "application/vnd.openstack.compute-v1.0+xml",
},
{
"base": "application/json",
- "type": "application/vnd.openstack.compute-v1.0+json"
- }
+ "type": "application/vnd.openstack.compute-v1.0+json",
+ },
],
},
"v1.1": {
@@ -67,24 +67,24 @@ VERSIONS = {
"rel": "describedby",
"type": "application/pdf",
"href": "http://docs.rackspacecloud.com/"
- "servers/api/v1.1/cs-devguide-20110125.pdf"
+ "servers/api/v1.1/cs-devguide-20110125.pdf",
},
{
"rel": "describedby",
"type": "application/vnd.sun.wadl+xml",
"href": "http://docs.rackspacecloud.com/"
- "servers/api/v1.1/application.wadl"
+ "servers/api/v1.1/application.wadl",
},
],
"media-types": [
{
"base": "application/xml",
- "type": "application/vnd.openstack.compute-v1.1+xml"
+ "type": "application/vnd.openstack.compute-v1.1+xml",
},
{
"base": "application/json",
- "type": "application/vnd.openstack.compute-v1.1+json"
- }
+ "type": "application/vnd.openstack.compute-v1.1+json",
+ },
],
},
}
@@ -150,34 +150,34 @@ class VersionsTest(test.TestCase):
"links": [
{
"rel": "self",
- "href": "http://localhost/v1.0/"
+ "href": "http://localhost/v1.0/",
},
{
"rel": "describedby",
"type": "application/pdf",
"href": "http://docs.rackspacecloud.com/"
- "servers/api/v1.0/cs-devguide-20110125.pdf"
+ "servers/api/v1.0/cs-devguide-20110125.pdf",
},
{
"rel": "describedby",
"type": "application/vnd.sun.wadl+xml",
"href": "http://docs.rackspacecloud.com/"
- "servers/api/v1.0/application.wadl"
- }
+ "servers/api/v1.0/application.wadl",
+ },
],
"media-types": [
{
"base": "application/xml",
"type": "application/"
- "vnd.openstack.compute-v1.0+xml"
+ "vnd.openstack.compute-v1.0+xml",
},
{
"base": "application/json",
"type": "application/"
- "vnd.openstack.compute-v1.0+json"
- }
- ]
- }
+ "vnd.openstack.compute-v1.0+json",
+ },
+ ],
+ },
}
self.assertEqual(expected, version)
@@ -196,34 +196,34 @@ class VersionsTest(test.TestCase):
"links": [
{
"rel": "self",
- "href": "http://localhost/v1.1/"
+ "href": "http://localhost/v1.1/",
},
{
"rel": "describedby",
"type": "application/pdf",
"href": "http://docs.rackspacecloud.com/"
- "servers/api/v1.1/cs-devguide-20110125.pdf"
+ "servers/api/v1.1/cs-devguide-20110125.pdf",
},
{
"rel": "describedby",
"type": "application/vnd.sun.wadl+xml",
"href": "http://docs.rackspacecloud.com/"
- "servers/api/v1.1/application.wadl"
- }
+ "servers/api/v1.1/application.wadl",
+ },
],
"media-types": [
{
"base": "application/xml",
"type": "application/"
- "vnd.openstack.compute-v1.1+xml"
+ "vnd.openstack.compute-v1.1+xml",
},
{
"base": "application/json",
"type": "application/"
- "vnd.openstack.compute-v1.1+json"
- }
- ]
- }
+ "vnd.openstack.compute-v1.1+json",
+ },
+ ],
+ },
}
self.assertEqual(expected, version)
@@ -739,30 +739,30 @@ class VersionsSerializerTests(test.TestCase):
"links": [
{
"rel": "self",
- "href": "http://localhost/v1.0/"
+ "href": "http://localhost/v1.0/",
},
{
"rel": "describedby",
"type": "application/pdf",
"href": "http://docs.rackspacecloud.com/"
- "servers/api/v1.0/cs-devguide-20110125.pdf"
+ "servers/api/v1.0/cs-devguide-20110125.pdf",
},
{
"rel": "describedby",
"type": "application/vnd.sun.wadl+xml",
"href": "http://docs.rackspacecloud.com/"
- "servers/api/v1.0/application.wadl"
+ "servers/api/v1.0/application.wadl",
},
],
"media-types": [
{
"base": "application/xml",
- "type": "application/vnd.openstack.compute-v1.0+xml"
+ "type": "application/vnd.openstack.compute-v1.0+xml",
},
{
"base": "application/json",
- "type": "application/vnd.openstack.compute-v1.0+json"
- }
+ "type": "application/vnd.openstack.compute-v1.0+json",
+ },
],
},
}
@@ -874,29 +874,29 @@ class VersionsSerializerTests(test.TestCase):
"links": [
{
"rel": "self",
- "href": "http://localhost/v1.1/"
+ "href": "http://localhost/v1.1/",
},
{
"rel": "describedby",
"type": "application/pdf",
"href": "http://docs.rackspacecloud.com/"
- "servers/api/v1.1/cs-devguide-20110125.pdf"
+ "servers/api/v1.1/cs-devguide-20110125.pdf",
},
{
"rel": "describedby",
"type": "application/vnd.sun.wadl+xml",
"href": "http://docs.rackspacecloud.com/"
- "servers/api/v1.1/application.wadl"
+ "servers/api/v1.1/application.wadl",
},
],
"media-types": [
{
"base": "application/xml",
- "type": "application/vnd.openstack.compute-v1.1+xml"
+ "type": "application/vnd.openstack.compute-v1.1+xml",
},
{
"base": "application/json",
- "type": "application/vnd.openstack.compute-v1.1+json"
+ "type": "application/vnd.openstack.compute-v1.1+json",
}
],
},
diff --git a/nova/tests/test_compute.py b/nova/tests/test_compute.py
index 879e4b9cb..bbf9ddcc6 100644
--- a/nova/tests/test_compute.py
+++ b/nova/tests/test_compute.py
@@ -535,7 +535,9 @@ class ComputeTestCase(test.TestCase):
db.instance_update(self.context, instance_id, {'host': 'foo'})
- self.compute.prep_resize(context, inst_ref['uuid'], 3)
+ new_instance_type_ref = db.instance_type_get_by_flavor_id(context, 3)
+ self.compute.prep_resize(context, inst_ref['uuid'],
+ new_instance_type_ref['id'])
migration_ref = db.migration_get_by_instance_and_status(context,
inst_ref['uuid'], 'pre-migrating')
diff --git a/nova/tests/test_xenapi.py b/nova/tests/test_xenapi.py
index a795b3c74..5f3aec700 100644
--- a/nova/tests/test_xenapi.py
+++ b/nova/tests/test_xenapi.py
@@ -397,7 +397,7 @@ class XenAPIVMTestCase(test.TestCase):
instance_type_id="3", os_type="linux",
architecture="x86-64", instance_id=1,
check_injection=False,
- create_record=True):
+ create_record=True, empty_dns=False):
stubs.stubout_loopingcall_start(self.stubs)
if create_record:
values = {'id': instance_id,
@@ -426,12 +426,22 @@ class XenAPIVMTestCase(test.TestCase):
'label': 'fake',
'mac': 'DE:AD:BE:EF:00:00',
'rxtx_cap': 3})]
+ if empty_dns:
+ network_info[0][1]['dns'] = []
+
self.conn.spawn(self.context, instance, network_info)
self.create_vm_record(self.conn, os_type, instance_id)
self.check_vm_record(self.conn, check_injection)
self.assertTrue(instance.os_type)
self.assertTrue(instance.architecture)
+ def test_spawn_empty_dns(self):
+ """"Test spawning with an empty dns list"""
+ self._test_spawn(glance_stubs.FakeGlance.IMAGE_VHD, None, None,
+ os_type="linux", architecture="x86-64",
+ empty_dns=True)
+ self.check_vm_params_for_linux()
+
def test_spawn_not_enough_memory(self):
self.assertRaises(Exception,
self._test_spawn,
diff --git a/nova/virt/libvirt/connection.py b/nova/virt/libvirt/connection.py
index c226df247..f0b2014ac 100644
--- a/nova/virt/libvirt/connection.py
+++ b/nova/virt/libvirt/connection.py
@@ -121,8 +121,6 @@ flags.DEFINE_integer('live_migration_bandwidth', 0,
'Define live migration behavior')
flags.DEFINE_string('qemu_img', 'qemu-img',
'binary to use for qemu-img commands')
-flags.DEFINE_bool('start_guests_on_host_boot', False,
- 'Whether to restart guests when the host reboots')
flags.DEFINE_string('libvirt_vif_type', 'bridge',
'Type of VIF to create.')
flags.DEFINE_string('libvirt_vif_driver',
@@ -173,27 +171,8 @@ class LibvirtConnection(driver.ComputeDriver):
self.vif_driver = utils.import_object(FLAGS.libvirt_vif_driver)
def init_host(self, host):
- # Adopt existing VM's running here
- ctxt = nova_context.get_admin_context()
- for instance in db.instance_get_all_by_host(ctxt, host):
- try:
- LOG.debug(_('Checking state of %s'), instance['name'])
- state = self.get_info(instance['name'])['state']
- except exception.NotFound:
- state = power_state.SHUTOFF
-
- LOG.debug(_('Current state of %(name)s was %(state)s.'),
- {'name': instance['name'], 'state': state})
- db.instance_set_state(ctxt, instance['id'], state)
-
- # NOTE(justinsb): We no longer delete SHUTOFF instances,
- # the user may want to power them back on
-
- if state != power_state.RUNNING:
- continue
- self.firewall_driver.setup_basic_filtering(instance)
- self.firewall_driver.prepare_instance_filter(instance)
- self.firewall_driver.apply_instance_filter(instance)
+ # NOTE(nsokolov): moved instance restarting to ComputeManager
+ pass
def _get_connection(self):
if not self._wrapped_conn or not self._test_connection():
@@ -604,11 +583,6 @@ class LibvirtConnection(driver.ComputeDriver):
LOG.debug(_("instance %s: is running"), instance['name'])
self.firewall_driver.apply_instance_filter(instance)
- if FLAGS.start_guests_on_host_boot:
- LOG.debug(_("instance %s: setting autostart ON") %
- instance['name'])
- domain.setAutostart(1)
-
def _wait_for_boot():
"""Called at an interval until the VM is running."""
instance_name = instance['name']
diff --git a/nova/virt/xenapi/vm_utils.py b/nova/virt/xenapi/vm_utils.py
index 63bc191cf..6d2340ccd 100644
--- a/nova/virt/xenapi/vm_utils.py
+++ b/nova/virt/xenapi/vm_utils.py
@@ -1095,6 +1095,8 @@ def _prepare_injectables(inst, networks_info):
ip_v6 = info['ip6s'][0]
if len(info['dns']) > 0:
dns = info['dns'][0]
+ else:
+ dns = ''
interface_info = {'name': 'eth%d' % ifc_num,
'address': ip_v4 and ip_v4['ip'] or '',
'netmask': ip_v4 and ip_v4['netmask'] or '',
diff --git a/nova/virt/xenapi_conn.py b/nova/virt/xenapi_conn.py
index 0cddb062e..fa5b78a2a 100644
--- a/nova/virt/xenapi_conn.py
+++ b/nova/virt/xenapi_conn.py
@@ -398,11 +398,10 @@ class XenAPISession(object):
try:
name = self._session.xenapi.task.get_name_label(task)
status = self._session.xenapi.task.get_status(task)
+ # Ensure action is never > 255
+ action = dict(action=name[:255], error=None)
if id:
- action = dict(
- instance_id=int(id),
- action=name[0:255], # Ensure action is never > 255
- error=None)
+ action["instance_id"] = int(id)
if status == "pending":
return
elif status == "success":