diff --git a/doc/v3/api_samples/os-extended-volumes/server-post-req.json b/doc/v3/api_samples/os-extended-volumes/server-post-req.json
new file mode 100644
index 000000000000..30851df41a56
--- /dev/null
+++ b/doc/v3/api_samples/os-extended-volumes/server-post-req.json
@@ -0,0 +1,16 @@
+{
+ "server" : {
+ "name" : "new-server-test",
+ "image_ref" : "http://glance.openstack.example.com/images/70a599e0-31e7-49b7-b260-868f441e862b",
+ "flavor_ref" : "http://openstack.example.com/flavors/1",
+ "metadata" : {
+ "My Server Name" : "Apache1"
+ },
+ "personality" : [
+ {
+ "path" : "/etc/banner.txt",
+ "contents" : "ICAgICAgDQoiQSBjbG91ZCBkb2VzIG5vdCBrbm93IHdoeSBpdCBtb3ZlcyBpbiBqdXN0IHN1Y2ggYSBkaXJlY3Rpb24gYW5kIGF0IHN1Y2ggYSBzcGVlZC4uLkl0IGZlZWxzIGFuIGltcHVsc2lvbi4uLnRoaXMgaXMgdGhlIHBsYWNlIHRvIGdvIG5vdy4gQnV0IHRoZSBza3kga25vd3MgdGhlIHJlYXNvbnMgYW5kIHRoZSBwYXR0ZXJucyBiZWhpbmQgYWxsIGNsb3VkcywgYW5kIHlvdSB3aWxsIGtub3csIHRvbywgd2hlbiB5b3UgbGlmdCB5b3Vyc2VsZiBoaWdoIGVub3VnaCB0byBzZWUgYmV5b25kIGhvcml6b25zLiINCg0KLVJpY2hhcmQgQmFjaA=="
+ }
+ ]
+ }
+}
\ No newline at end of file
diff --git a/doc/v3/api_samples/os-extended-volumes/server-post-req.xml b/doc/v3/api_samples/os-extended-volumes/server-post-req.xml
new file mode 100644
index 000000000000..24eabe8533ba
--- /dev/null
+++ b/doc/v3/api_samples/os-extended-volumes/server-post-req.xml
@@ -0,0 +1,19 @@
+
+
+
+ Apache1
+
+
+
+ ICAgICAgDQoiQSBjbG91ZCBkb2VzIG5vdCBrbm93IHdoeSBp
+ dCBtb3ZlcyBpbiBqdXN0IHN1Y2ggYSBkaXJlY3Rpb24gYW5k
+ IGF0IHN1Y2ggYSBzcGVlZC4uLkl0IGZlZWxzIGFuIGltcHVs
+ c2lvbi4uLnRoaXMgaXMgdGhlIHBsYWNlIHRvIGdvIG5vdy4g
+ QnV0IHRoZSBza3kga25vd3MgdGhlIHJlYXNvbnMgYW5kIHRo
+ ZSBwYXR0ZXJucyBiZWhpbmQgYWxsIGNsb3VkcywgYW5kIHlv
+ dSB3aWxsIGtub3csIHRvbywgd2hlbiB5b3UgbGlmdCB5b3Vy
+ c2VsZiBoaWdoIGVub3VnaCB0byBzZWUgYmV5b25kIGhvcml6
+ b25zLiINCg0KLVJpY2hhcmQgQmFjaA==
+
+
+
\ No newline at end of file
diff --git a/doc/v3/api_samples/os-extended-volumes/server-post-resp.json b/doc/v3/api_samples/os-extended-volumes/server-post-resp.json
new file mode 100644
index 000000000000..1e8d03dadacc
--- /dev/null
+++ b/doc/v3/api_samples/os-extended-volumes/server-post-resp.json
@@ -0,0 +1,16 @@
+{
+ "server": {
+ "admin_pass": "wCGJKvs9RApD",
+ "id": "c43b584c-86ac-438c-aa9d-70aac14c2b27",
+ "links": [
+ {
+ "href": "http://openstack.example.com/v3/servers/c43b584c-86ac-438c-aa9d-70aac14c2b27",
+ "rel": "self"
+ },
+ {
+ "href": "http://openstack.example.com/servers/c43b584c-86ac-438c-aa9d-70aac14c2b27",
+ "rel": "bookmark"
+ }
+ ]
+ }
+}
\ No newline at end of file
diff --git a/doc/v3/api_samples/os-extended-volumes/server-post-resp.xml b/doc/v3/api_samples/os-extended-volumes/server-post-resp.xml
new file mode 100644
index 000000000000..14559418555f
--- /dev/null
+++ b/doc/v3/api_samples/os-extended-volumes/server-post-resp.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/doc/v3/api_samples/os-extended-volumes/swap-volume-req.json b/doc/v3/api_samples/os-extended-volumes/swap-volume-req.json
new file mode 100644
index 000000000000..32aaf11dfdd2
--- /dev/null
+++ b/doc/v3/api_samples/os-extended-volumes/swap-volume-req.json
@@ -0,0 +1,6 @@
+{
+ "swap": {
+ "old_volume_id": "a26887c6-c47b-4654-abb5-dfadf7d3f803",
+ "new_volume_id": "a26887c6-c47b-4654-abb5-dfadf7d3f804"
+ }
+}
\ No newline at end of file
diff --git a/doc/v3/api_samples/os-extended-volumes/swap-volume-req.xml b/doc/v3/api_samples/os-extended-volumes/swap-volume-req.xml
new file mode 100644
index 000000000000..32bc36436f75
--- /dev/null
+++ b/doc/v3/api_samples/os-extended-volumes/swap-volume-req.xml
@@ -0,0 +1,2 @@
+
+
\ No newline at end of file
diff --git a/nova/api/openstack/compute/plugins/v3/extended_volumes.py b/nova/api/openstack/compute/plugins/v3/extended_volumes.py
index 7c2aac7514ad..62ec84eb5702 100644
--- a/nova/api/openstack/compute/plugins/v3/extended_volumes.py
+++ b/nova/api/openstack/compute/plugins/v3/extended_volumes.py
@@ -54,7 +54,7 @@ class ExtendedVolumesController(wsgi.Controller):
@extensions.expected_errors((400, 404, 409))
@wsgi.action('swap_volume_attachment')
- def swap(self, req, server_id, body):
+ def swap(self, req, id, body):
context = req.environ['nova.context']
authorize_swap(context)
@@ -72,7 +72,7 @@ class ExtendedVolumesController(wsgi.Controller):
raise exc.HTTPBadRequest("The request body is invalid")
try:
- instance = self.compute_api.get(context, server_id,
+ instance = self.compute_api.get(context, id,
want_objects=True)
except exception.InstanceNotFound as e:
raise exc.HTTPNotFound(explanation=e.format_message())
diff --git a/nova/tests/integrated/v3/api_samples/os-extended-volumes/server-post-req.json.tpl b/nova/tests/integrated/v3/api_samples/os-extended-volumes/server-post-req.json.tpl
new file mode 100644
index 000000000000..e6c046ceb4e9
--- /dev/null
+++ b/nova/tests/integrated/v3/api_samples/os-extended-volumes/server-post-req.json.tpl
@@ -0,0 +1,16 @@
+{
+ "server" : {
+ "name" : "new-server-test",
+ "image_ref" : "%(glance_host)s/images/%(image_id)s",
+ "flavor_ref" : "%(host)s/flavors/1",
+ "metadata" : {
+ "My Server Name" : "Apache1"
+ },
+ "personality" : [
+ {
+ "path" : "/etc/banner.txt",
+ "contents" : "ICAgICAgDQoiQSBjbG91ZCBkb2VzIG5vdCBrbm93IHdoeSBpdCBtb3ZlcyBpbiBqdXN0IHN1Y2ggYSBkaXJlY3Rpb24gYW5kIGF0IHN1Y2ggYSBzcGVlZC4uLkl0IGZlZWxzIGFuIGltcHVsc2lvbi4uLnRoaXMgaXMgdGhlIHBsYWNlIHRvIGdvIG5vdy4gQnV0IHRoZSBza3kga25vd3MgdGhlIHJlYXNvbnMgYW5kIHRoZSBwYXR0ZXJucyBiZWhpbmQgYWxsIGNsb3VkcywgYW5kIHlvdSB3aWxsIGtub3csIHRvbywgd2hlbiB5b3UgbGlmdCB5b3Vyc2VsZiBoaWdoIGVub3VnaCB0byBzZWUgYmV5b25kIGhvcml6b25zLiINCg0KLVJpY2hhcmQgQmFjaA=="
+ }
+ ]
+ }
+}
diff --git a/nova/tests/integrated/v3/api_samples/os-extended-volumes/server-post-req.xml.tpl b/nova/tests/integrated/v3/api_samples/os-extended-volumes/server-post-req.xml.tpl
new file mode 100644
index 000000000000..31892a3c1f4d
--- /dev/null
+++ b/nova/tests/integrated/v3/api_samples/os-extended-volumes/server-post-req.xml.tpl
@@ -0,0 +1,19 @@
+
+
+
+ Apache1
+
+
+
+ ICAgICAgDQoiQSBjbG91ZCBkb2VzIG5vdCBrbm93IHdoeSBp
+ dCBtb3ZlcyBpbiBqdXN0IHN1Y2ggYSBkaXJlY3Rpb24gYW5k
+ IGF0IHN1Y2ggYSBzcGVlZC4uLkl0IGZlZWxzIGFuIGltcHVs
+ c2lvbi4uLnRoaXMgaXMgdGhlIHBsYWNlIHRvIGdvIG5vdy4g
+ QnV0IHRoZSBza3kga25vd3MgdGhlIHJlYXNvbnMgYW5kIHRo
+ ZSBwYXR0ZXJucyBiZWhpbmQgYWxsIGNsb3VkcywgYW5kIHlv
+ dSB3aWxsIGtub3csIHRvbywgd2hlbiB5b3UgbGlmdCB5b3Vy
+ c2VsZiBoaWdoIGVub3VnaCB0byBzZWUgYmV5b25kIGhvcml6
+ b25zLiINCg0KLVJpY2hhcmQgQmFjaA==
+
+
+
diff --git a/nova/tests/integrated/v3/api_samples/os-extended-volumes/server-post-resp.json.tpl b/nova/tests/integrated/v3/api_samples/os-extended-volumes/server-post-resp.json.tpl
new file mode 100644
index 000000000000..d061e9cb2e80
--- /dev/null
+++ b/nova/tests/integrated/v3/api_samples/os-extended-volumes/server-post-resp.json.tpl
@@ -0,0 +1,16 @@
+{
+ "server": {
+ "admin_pass": "%(password)s",
+ "id": "%(id)s",
+ "links": [
+ {
+ "href": "%(host)s/v3/servers/%(uuid)s",
+ "rel": "self"
+ },
+ {
+ "href": "%(host)s/servers/%(uuid)s",
+ "rel": "bookmark"
+ }
+ ]
+ }
+}
diff --git a/nova/tests/integrated/v3/api_samples/os-extended-volumes/server-post-resp.xml.tpl b/nova/tests/integrated/v3/api_samples/os-extended-volumes/server-post-resp.xml.tpl
new file mode 100644
index 000000000000..3470373e171f
--- /dev/null
+++ b/nova/tests/integrated/v3/api_samples/os-extended-volumes/server-post-resp.xml.tpl
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/nova/tests/integrated/v3/api_samples/os-extended-volumes/swap-volume-req.json.tpl b/nova/tests/integrated/v3/api_samples/os-extended-volumes/swap-volume-req.json.tpl
new file mode 100644
index 000000000000..07a32684213b
--- /dev/null
+++ b/nova/tests/integrated/v3/api_samples/os-extended-volumes/swap-volume-req.json.tpl
@@ -0,0 +1,6 @@
+{
+ "swap_volume_attachment": {
+ "old_volume_id": "%(old_volume_id)s",
+ "new_volume_id": "%(new_volume_id)s"
+ }
+}
diff --git a/nova/tests/integrated/v3/api_samples/os-extended-volumes/swap-volume-req.xml.tpl b/nova/tests/integrated/v3/api_samples/os-extended-volumes/swap-volume-req.xml.tpl
new file mode 100644
index 000000000000..1d2df75f3e98
--- /dev/null
+++ b/nova/tests/integrated/v3/api_samples/os-extended-volumes/swap-volume-req.xml.tpl
@@ -0,0 +1,2 @@
+
+
diff --git a/nova/tests/integrated/v3/test_extended_volumes.py b/nova/tests/integrated/v3/test_extended_volumes.py
new file mode 100644
index 000000000000..1163c5a5a1c9
--- /dev/null
+++ b/nova/tests/integrated/v3/test_extended_volumes.py
@@ -0,0 +1,74 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+# Copyright 2012 Nebula, Inc.
+# Copyright 2013 IBM Corp.
+#
+# 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 nova.compute import api as compute_api
+from nova.compute import manager as compute_manager
+from nova.tests.api.openstack import fakes
+from nova.tests.integrated.v3 import test_servers
+from nova.volume import cinder
+
+
+class ExtendedVolumesSampleJsonTests(test_servers.ServersSampleBase):
+ extension_name = "os-extended-volumes"
+
+ def _stub_compute_api_get_instance_bdms(self, server_id):
+
+ def fake_compute_api_get_instance_bdms(self, context, instance):
+ bdms = [
+ {'volume_id': 'a26887c6-c47b-4654-abb5-dfadf7d3f803',
+ 'instance_uuid': server_id,
+ 'device_name': '/dev/sdd'},
+ {'volume_id': 'a26887c6-c47b-4654-abb5-dfadf7d3f804',
+ 'instance_uuid': server_id,
+ 'device_name': '/dev/sdc'}
+ ]
+ return bdms
+
+ self.stubs.Set(compute_api.API, "get_instance_bdms",
+ fake_compute_api_get_instance_bdms)
+
+ def test_swap_volume(self):
+ server_id = self._post_server()
+ old_volume_id = "a26887c6-c47b-4654-abb5-dfadf7d3f803"
+ old_new_volume = 'a26887c6-c47b-4654-abb5-dfadf7d3f805'
+ self._stub_compute_api_get_instance_bdms(server_id)
+
+ def stub_volume_get(self, context, volume_id):
+ if volume_id == old_volume_id:
+ return fakes.stub_volume(volume_id, instance_uuid=server_id)
+ else:
+ return fakes.stub_volume(volume_id, instance_uuid=None,
+ attach_status='detached')
+
+ self.stubs.Set(cinder.API, 'get', stub_volume_get)
+ self.stubs.Set(cinder.API, 'begin_detaching', lambda *a, **k: None)
+ self.stubs.Set(cinder.API, 'check_attach', lambda *a, **k: None)
+ self.stubs.Set(cinder.API, 'check_detach', lambda *a, **k: None)
+ self.stubs.Set(cinder.API, 'reserve_volume', lambda *a, **k: None)
+ self.stubs.Set(compute_manager.ComputeManager, 'swap_volume',
+ lambda *a, **k: None)
+ subs = {
+ 'old_volume_id': old_volume_id,
+ 'new_volume_id': old_new_volume
+ }
+ response = self._do_post('servers/%s/action' % server_id,
+ 'swap-volume-req', subs)
+ self.assertEqual(response.status, 202)
+ self.assertEqual(response.read(), '')
+
+
+class ExtendedVolumesSampleXmlTests(ExtendedVolumesSampleJsonTests):
+ ctype = 'xml'
diff --git a/nova/tests/integrated/v3/test_servers.py b/nova/tests/integrated/v3/test_servers.py
index fd6bcc8afe20..84e7ab3c5fa9 100644
--- a/nova/tests/integrated/v3/test_servers.py
+++ b/nova/tests/integrated/v3/test_servers.py
@@ -23,6 +23,7 @@ class ServersSampleBase(api_sample_base.ApiSampleTestBaseV3):
subs = {
'image_id': fake.get_valid_image_id(),
'host': self._get_host(),
+ 'glance_host': self._get_glance_host()
}
response = self._do_post('servers', 'server-post-req', subs)
subs = self._get_regexes()