diff --git a/doc/v3/api_samples/os-admin-actions/admin-actions-backup-server.json b/doc/v3/api_samples/os-create-backup/create-backup-req.json similarity index 100% rename from doc/v3/api_samples/os-admin-actions/admin-actions-backup-server.json rename to doc/v3/api_samples/os-create-backup/create-backup-req.json diff --git a/doc/v3/api_samples/os-create-backup/server-post-req.json b/doc/v3/api_samples/os-create-backup/server-post-req.json new file mode 100644 index 000000000000..30851df41a56 --- /dev/null +++ b/doc/v3/api_samples/os-create-backup/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-create-backup/server-post-resp.json b/doc/v3/api_samples/os-create-backup/server-post-resp.json new file mode 100644 index 000000000000..270cb8463484 --- /dev/null +++ b/doc/v3/api_samples/os-create-backup/server-post-resp.json @@ -0,0 +1,16 @@ +{ + "server": { + "admin_password": "DM3QzjhGTzLB", + "id": "bebeec79-497e-4711-a311-d0d2e3dfc73b", + "links": [ + { + "href": "http://openstack.example.com/v3/servers/bebeec79-497e-4711-a311-d0d2e3dfc73b", + "rel": "self" + }, + { + "href": "http://openstack.example.com/servers/bebeec79-497e-4711-a311-d0d2e3dfc73b", + "rel": "bookmark" + } + ] + } +} diff --git a/etc/nova/policy.json b/etc/nova/policy.json index 75e5719c38cc..83f18607d060 100644 --- a/etc/nova/policy.json +++ b/etc/nova/policy.json @@ -45,7 +45,6 @@ "compute_extension:v3:os-admin-actions:discoverable": "", "compute_extension:v3:os-admin-actions:reset_network": "rule:admin_api", "compute_extension:v3:os-admin-actions:inject_network_info": "rule:admin_api", - "compute_extension:v3:os-admin-actions:create_backup": "rule:admin_or_owner", "compute_extension:v3:os-admin-actions:reset_state": "rule:admin_api", "compute_extension:v3:os-admin-password": "", "compute_extension:v3:os-admin-password:discoverable": "", @@ -83,6 +82,8 @@ "compute_extension:v3:os-remote-consoles": "", "compute_extension:v3:os-remote-consoles:discoverable": "", "compute_extension:createserverext": "", + "compute_extension:v3:os-create-backup:discoverable": "", + "compute_extension:v3:os-create-backup": "rule:admin_or_owner", "compute_extension:deferred_delete": "", "compute_extension:v3:os-deferred-delete": "", "compute_extension:v3:os-deferred-delete:discoverable": "", diff --git a/nova/api/openstack/compute/plugins/v3/admin_actions.py b/nova/api/openstack/compute/plugins/v3/admin_actions.py index 1dc51f20c9a6..636e420db043 100644 --- a/nova/api/openstack/compute/plugins/v3/admin_actions.py +++ b/nova/api/openstack/compute/plugins/v3/admin_actions.py @@ -12,8 +12,6 @@ # License for the specific language governing permissions and limitations # under the License. -import os.path - import webob from webob import exc @@ -71,74 +69,6 @@ class AdminActionsController(wsgi.Controller): raise exc.HTTPConflict(explanation=e.format_message()) return webob.Response(status_int=202) - @extensions.expected_errors((400, 404, 409, 413)) - @wsgi.action('create_backup') - def _create_backup(self, req, id, body): - """Backup a server instance. - - Images now have an `image_type` associated with them, which can be - 'snapshot' or the backup type, like 'daily' or 'weekly'. - - If the image_type is backup-like, then the rotation factor can be - included and that will cause the oldest backups that exceed the - rotation factor to be deleted. - - """ - context = req.environ["nova.context"] - authorize(context, 'create_backup') - entity = body["create_backup"] - - try: - image_name = entity["name"] - backup_type = entity["backup_type"] - rotation = entity["rotation"] - - except KeyError as missing_key: - msg = _("create_backup entity requires %s attribute") % missing_key - raise exc.HTTPBadRequest(explanation=msg) - - except TypeError: - msg = _("Malformed create_backup entity") - raise exc.HTTPBadRequest(explanation=msg) - - try: - rotation = int(rotation) - except ValueError: - msg = _("create_backup attribute 'rotation' must be an integer") - raise exc.HTTPBadRequest(explanation=msg) - if rotation < 0: - msg = _("create_backup attribute 'rotation' must be greater " - "than or equal to zero") - raise exc.HTTPBadRequest(explanation=msg) - - props = {} - metadata = entity.get('metadata', {}) - common.check_img_metadata_properties_quota(context, metadata) - try: - props.update(metadata) - except ValueError: - msg = _("Invalid metadata") - raise exc.HTTPBadRequest(explanation=msg) - - instance = common.get_instance(self.compute_api, context, id, - want_objects=True) - try: - image = self.compute_api.backup(context, instance, image_name, - backup_type, rotation, extra_properties=props) - except exception.InstanceInvalidState as state_error: - common.raise_http_conflict_for_instance_invalid_state(state_error, - 'create_backup') - - resp = webob.Response(status_int=202) - - # build location of newly-created image entity if rotation is not zero - if rotation > 0: - image_id = str(image['id']) - image_ref = os.path.join(req.application_url, 'images', image_id) - resp.headers['Location'] = image_ref - - return resp - @extensions.expected_errors((400, 404)) @wsgi.action('reset_state') def _reset_state(self, req, id, body): diff --git a/nova/api/openstack/compute/plugins/v3/create_backup.py b/nova/api/openstack/compute/plugins/v3/create_backup.py new file mode 100644 index 000000000000..87a3f0d2536a --- /dev/null +++ b/nova/api/openstack/compute/plugins/v3/create_backup.py @@ -0,0 +1,123 @@ +# Copyright 2011 OpenStack Foundation +# 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. + +import os.path + +import webob +from webob import exc + +from nova.api.openstack import common +from nova.api.openstack import extensions +from nova.api.openstack import wsgi +from nova import compute +from nova import exception +from nova.openstack.common.gettextutils import _ +from nova.openstack.common import log as logging + +LOG = logging.getLogger(__name__) +ALIAS = "os-create-backup" +authorize = extensions.extension_authorizer('compute', "v3:" + ALIAS) + + +class CreateBackupController(wsgi.Controller): + def __init__(self, *args, **kwargs): + super(CreateBackupController, self).__init__(*args, **kwargs) + self.compute_api = compute.API() + + @extensions.expected_errors((400, 404, 409, 413)) + @wsgi.action('create_backup') + def _create_backup(self, req, id, body): + """Backup a server instance. + + Images now have an `image_type` associated with them, which can be + 'snapshot' or the backup type, like 'daily' or 'weekly'. + + If the image_type is backup-like, then the rotation factor can be + included and that will cause the oldest backups that exceed the + rotation factor to be deleted. + + """ + context = req.environ["nova.context"] + authorize(context) + entity = body["create_backup"] + + try: + image_name = entity["name"] + backup_type = entity["backup_type"] + rotation = entity["rotation"] + + except KeyError as missing_key: + msg = _("create_backup entity requires %s attribute") % missing_key + raise exc.HTTPBadRequest(explanation=msg) + + except TypeError: + msg = _("Malformed create_backup entity") + raise exc.HTTPBadRequest(explanation=msg) + + try: + rotation = int(rotation) + except ValueError: + msg = _("create_backup attribute 'rotation' must be an integer") + raise exc.HTTPBadRequest(explanation=msg) + if rotation < 0: + msg = _("create_backup attribute 'rotation' must be greater " + "than or equal to zero") + raise exc.HTTPBadRequest(explanation=msg) + + props = {} + metadata = entity.get('metadata', {}) + common.check_img_metadata_properties_quota(context, metadata) + try: + props.update(metadata) + except ValueError: + msg = _("Invalid metadata") + raise exc.HTTPBadRequest(explanation=msg) + + instance = common.get_instance(self.compute_api, context, id, + want_objects=True) + + try: + image = self.compute_api.backup(context, instance, image_name, + backup_type, rotation, extra_properties=props) + except exception.InstanceInvalidState as state_error: + common.raise_http_conflict_for_instance_invalid_state(state_error, + 'create_backup') + + resp = webob.Response(status_int=202) + + # build location of newly-created image entity if rotation is not zero + if rotation > 0: + image_id = str(image['id']) + image_ref = os.path.join(req.application_url, 'images', image_id) + resp.headers['Location'] = image_ref + + return resp + + +class CreateBackup(extensions.V3APIExtensionBase): + """Create a backup of a server.""" + + name = "CreateBackup" + alias = ALIAS + namespace = "http://docs.openstack.org/compute/ext/%s/api/v3" % ALIAS + version = 1 + + def get_controller_extensions(self): + controller = CreateBackupController() + extension = extensions.ControllerExtension(self, 'servers', controller) + return [extension] + + def get_resources(self): + return [] diff --git a/nova/tests/api/openstack/compute/plugins/v3/test_admin_actions.py b/nova/tests/api/openstack/compute/plugins/v3/test_admin_actions.py index 246b7acc9269..efa457d7512d 100644 --- a/nova/tests/api/openstack/compute/plugins/v3/test_admin_actions.py +++ b/nova/tests/api/openstack/compute/plugins/v3/test_admin_actions.py @@ -14,7 +14,6 @@ import webob -from nova.api.openstack import common from nova.api.openstack.compute.plugins.v3 import admin_actions from nova.compute import vm_states import nova.context @@ -183,185 +182,6 @@ class AdminActionsTest(CommonMixin, test.NoDBTestCase): self.mox.StubOutWithMock(self.compute_api, 'get') -class CreateBackupTests(CommonMixin, test.NoDBTestCase): - def setUp(self): - super(CreateBackupTests, self).setUp() - self.mox.StubOutWithMock(common, - 'check_img_metadata_properties_quota') - self.mox.StubOutWithMock(self.compute_api, - 'backup') - - def _make_url(self, uuid): - return '/servers/%s/action' % uuid - - def test_create_backup_with_metadata(self): - metadata = {'123': 'asdf'} - body = { - 'create_backup': { - 'name': 'Backup 1', - 'backup_type': 'daily', - 'rotation': 1, - 'metadata': metadata, - }, - } - - image = dict(id='fake-image-id', status='ACTIVE', name='Backup 1', - properties=metadata) - - common.check_img_metadata_properties_quota(self.context, metadata) - instance = self._stub_instance_get() - self.compute_api.backup(self.context, instance, 'Backup 1', - 'daily', 1, - extra_properties=metadata).AndReturn(image) - - self.mox.ReplayAll() - - res = self._make_request(self._make_url(instance['uuid']), body) - self.assertEqual(202, res.status_int) - self.assertIn('fake-image-id', res.headers['Location']) - - def test_create_backup_no_name(self): - # Name is required for backups. - body = { - 'create_backup': { - 'backup_type': 'daily', - 'rotation': 1, - }, - } - res = self._make_request(self._make_url('fake'), body) - self.assertEqual(400, res.status_int) - - def test_create_backup_no_rotation(self): - # Rotation is required for backup requests. - body = { - 'create_backup': { - 'name': 'Backup 1', - 'backup_type': 'daily', - }, - } - res = self._make_request(self._make_url('fake'), body) - self.assertEqual(400, res.status_int) - - def test_create_backup_negative_rotation(self): - """Rotation must be greater than or equal to zero - for backup requests - """ - body = { - 'create_backup': { - 'name': 'Backup 1', - 'backup_type': 'daily', - 'rotation': -1, - }, - } - res = self._make_request(self._make_url('fake'), body) - self.assertEqual(400, res.status_int) - - def test_create_backup_no_backup_type(self): - # Backup Type (daily or weekly) is required for backup requests. - body = { - 'create_backup': { - 'name': 'Backup 1', - 'rotation': 1, - }, - } - res = self._make_request(self._make_url('fake'), body) - self.assertEqual(400, res.status_int) - - def test_create_backup_bad_entity(self): - body = {'create_backup': 'go'} - res = self._make_request(self._make_url('fake'), body) - self.assertEqual(400, res.status_int) - - def test_create_backup_rotation_is_zero(self): - # The happy path for creating backups if rotation is zero. - body = { - 'create_backup': { - 'name': 'Backup 1', - 'backup_type': 'daily', - 'rotation': 0, - }, - } - - image = dict(id='fake-image-id', status='ACTIVE', name='Backup 1', - properties={}) - common.check_img_metadata_properties_quota(self.context, {}) - instance = self._stub_instance_get() - self.compute_api.backup(self.context, instance, 'Backup 1', - 'daily', 0, - extra_properties={}).AndReturn(image) - - self.mox.ReplayAll() - - res = self._make_request(self._make_url(instance['uuid']), body) - self.assertEqual(202, res.status_int) - self.assertNotIn('Location', res.headers) - - def test_create_backup_rotation_is_positive(self): - # The happy path for creating backups if rotation is positive. - body = { - 'create_backup': { - 'name': 'Backup 1', - 'backup_type': 'daily', - 'rotation': 1, - }, - } - - image = dict(id='fake-image-id', status='ACTIVE', name='Backup 1', - properties={}) - common.check_img_metadata_properties_quota(self.context, {}) - instance = self._stub_instance_get() - self.compute_api.backup(self.context, instance, 'Backup 1', - 'daily', 1, - extra_properties={}).AndReturn(image) - - self.mox.ReplayAll() - - res = self._make_request(self._make_url(instance['uuid']), body) - self.assertEqual(202, res.status_int) - self.assertIn('fake-image-id', res.headers['Location']) - - def test_create_backup_raises_conflict_on_invalid_state(self): - body_map = { - 'create_backup': { - 'name': 'Backup 1', - 'backup_type': 'daily', - 'rotation': 1, - }, - } - args_map = { - 'create_backup': ( - ('Backup 1', 'daily', 1), {'extra_properties': {}} - ), - } - common.check_img_metadata_properties_quota(self.context, {}) - self._test_invalid_state('create_backup', method='backup', - body_map=body_map, - compute_api_args_map=args_map) - - def test_create_backup_with_non_existed_instance(self): - body_map = { - 'create_backup': { - 'name': 'Backup 1', - 'backup_type': 'daily', - 'rotation': 1, - }, - } - common.check_img_metadata_properties_quota(self.context, {}) - self._test_non_existing_instance('create_backup', - body_map=body_map) - - def test_create_backup_with_invalid_create_backup(self): - body = { - 'create_backupup': { - 'name': 'Backup 1', - 'backup_type': 'daily', - 'rotation': 1, - }, - } - res = self._make_request(self._make_url('fake'), body) - self.assertEqual(400, res.status_int) - - class ResetStateTests(test.NoDBTestCase): def setUp(self): super(ResetStateTests, self).setUp() diff --git a/nova/tests/api/openstack/compute/plugins/v3/test_create_backup.py b/nova/tests/api/openstack/compute/plugins/v3/test_create_backup.py new file mode 100644 index 000000000000..ac66be7156dd --- /dev/null +++ b/nova/tests/api/openstack/compute/plugins/v3/test_create_backup.py @@ -0,0 +1,215 @@ +# Copyright 2011 OpenStack Foundation +# 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.api.openstack import common +from nova.api.openstack.compute.plugins.v3 import create_backup +from nova.openstack.common import uuidutils +from nova import test +from nova.tests.api.openstack.compute.plugins.v3 import \ + admin_only_action_common +from nova.tests.api.openstack import fakes + + +class CreateBackupTests(admin_only_action_common.CommonMixin, + test.NoDBTestCase): + def setUp(self): + super(CreateBackupTests, self).setUp() + self.controller = create_backup.CreateBackupController() + self.compute_api = self.controller.compute_api + + def _fake_controller(*args, **kwargs): + return self.controller + + self.stubs.Set(create_backup, 'CreateBackupController', + _fake_controller) + self.app = fakes.wsgi_app_v3(init_only=('servers', + 'os-create-backup'), + fake_auth_context=self.context) + self.mox.StubOutWithMock(self.compute_api, 'get') + self.mox.StubOutWithMock(common, + 'check_img_metadata_properties_quota') + self.mox.StubOutWithMock(self.compute_api, 'backup') + + def _make_url(self, uuid=None): + if uuid is None: + uuid = uuidutils.generate_uuid() + return '/servers/%s/action' % uuid + + def test_create_backup_with_metadata(self): + metadata = {'123': 'asdf'} + body = { + 'create_backup': { + 'name': 'Backup 1', + 'backup_type': 'daily', + 'rotation': 1, + 'metadata': metadata, + }, + } + + image = dict(id='fake-image-id', status='ACTIVE', name='Backup 1', + properties=metadata) + + common.check_img_metadata_properties_quota(self.context, metadata) + instance = self._stub_instance_get() + self.compute_api.backup(self.context, instance, 'Backup 1', + 'daily', 1, + extra_properties=metadata).AndReturn(image) + + self.mox.ReplayAll() + + res = self._make_request(self._make_url(instance.uuid), body) + self.assertEqual(202, res.status_int) + self.assertIn('fake-image-id', res.headers['Location']) + + def test_create_backup_no_name(self): + # Name is required for backups. + body = { + 'create_backup': { + 'backup_type': 'daily', + 'rotation': 1, + }, + } + res = self._make_request(self._make_url(), body) + self.assertEqual(400, res.status_int) + + def test_create_backup_no_rotation(self): + # Rotation is required for backup requests. + body = { + 'create_backup': { + 'name': 'Backup 1', + 'backup_type': 'daily', + }, + } + res = self._make_request(self._make_url(), body) + self.assertEqual(400, res.status_int) + + def test_create_backup_negative_rotation(self): + """Rotation must be greater than or equal to zero + for backup requests + """ + body = { + 'create_backup': { + 'name': 'Backup 1', + 'backup_type': 'daily', + 'rotation': -1, + }, + } + res = self._make_request(self._make_url(), body) + self.assertEqual(400, res.status_int) + + def test_create_backup_no_backup_type(self): + # Backup Type (daily or weekly) is required for backup requests. + body = { + 'create_backup': { + 'name': 'Backup 1', + 'rotation': 1, + }, + } + res = self._make_request(self._make_url(), body) + self.assertEqual(400, res.status_int) + + def test_create_backup_bad_entity(self): + body = {'create_backup': 'go'} + res = self._make_request(self._make_url(), body) + self.assertEqual(400, res.status_int) + + def test_create_backup_rotation_is_zero(self): + # The happy path for creating backups if rotation is zero. + body = { + 'create_backup': { + 'name': 'Backup 1', + 'backup_type': 'daily', + 'rotation': 0, + }, + } + + image = dict(id='fake-image-id', status='ACTIVE', name='Backup 1', + properties={}) + common.check_img_metadata_properties_quota(self.context, {}) + instance = self._stub_instance_get() + self.compute_api.backup(self.context, instance, 'Backup 1', + 'daily', 0, + extra_properties={}).AndReturn(image) + + self.mox.ReplayAll() + + res = self._make_request(self._make_url(instance.uuid), body) + self.assertEqual(202, res.status_int) + self.assertNotIn('Location', res.headers) + + def test_create_backup_rotation_is_positive(self): + # The happy path for creating backups if rotation is positive. + body = { + 'create_backup': { + 'name': 'Backup 1', + 'backup_type': 'daily', + 'rotation': 1, + }, + } + + image = dict(id='fake-image-id', status='ACTIVE', name='Backup 1', + properties={}) + common.check_img_metadata_properties_quota(self.context, {}) + instance = self._stub_instance_get() + self.compute_api.backup(self.context, instance, 'Backup 1', + 'daily', 1, + extra_properties={}).AndReturn(image) + + self.mox.ReplayAll() + + res = self._make_request(self._make_url(instance.uuid), body) + self.assertEqual(202, res.status_int) + self.assertIn('fake-image-id', res.headers['Location']) + + def test_create_backup_raises_conflict_on_invalid_state(self): + body_map = { + 'create_backup': { + 'name': 'Backup 1', + 'backup_type': 'daily', + 'rotation': 1, + }, + } + args_map = { + 'create_backup': ( + ('Backup 1', 'daily', 1), {'extra_properties': {}} + ), + } + common.check_img_metadata_properties_quota(self.context, {}) + self._test_invalid_state('create_backup', method='backup', + body_map=body_map, + compute_api_args_map=args_map) + + def test_create_backup_with_non_existed_instance(self): + body_map = { + 'create_backup': { + 'name': 'Backup 1', + 'backup_type': 'daily', + 'rotation': 1, + }, + } + common.check_img_metadata_properties_quota(self.context, {}) + self._test_non_existing_instance('create_backup', + body_map=body_map) + + def test_create_backup_with_invalid_create_backup(self): + body = { + 'create_backupup': { + 'name': 'Backup 1', + 'backup_type': 'daily', + 'rotation': 1, + }, + } + res = self._make_request(self._make_url(), body) + self.assertEqual(400, res.status_int) diff --git a/nova/tests/fake_policy.py b/nova/tests/fake_policy.py index a868c3626389..e6e8389af845 100644 --- a/nova/tests/fake_policy.py +++ b/nova/tests/fake_policy.py @@ -119,7 +119,6 @@ policy_data = """ "compute_extension:admin_actions:migrate": "", "compute_extension:v3:os-admin-actions:reset_network": "", "compute_extension:v3:os-admin-actions:inject_network_info": "", - "compute_extension:v3:os-admin-actions:create_backup": "", "compute_extension:v3:os-admin-actions:reset_state": "", "compute_extension:v3:os-admin-password": "", "compute_extension:aggregates": "rule:admin_api", @@ -150,6 +149,7 @@ policy_data = """ "compute_extension:consoles": "", "compute_extension:v3:os-remote-consoles": "", "compute_extension:createserverext": "", + "compute_extension:v3:os-create-backup": "", "compute_extension:deferred_delete": "", "compute_extension:v3:os-deferred-delete": "", "compute_extension:disk_config": "", diff --git a/nova/tests/integrated/v3/api_samples/os-admin-actions/admin-actions-backup-server.json.tpl b/nova/tests/integrated/v3/api_samples/os-create-backup/create-backup-req.json.tpl similarity index 100% rename from nova/tests/integrated/v3/api_samples/os-admin-actions/admin-actions-backup-server.json.tpl rename to nova/tests/integrated/v3/api_samples/os-create-backup/create-backup-req.json.tpl diff --git a/nova/tests/integrated/v3/api_samples/os-create-backup/server-post-req.json.tpl b/nova/tests/integrated/v3/api_samples/os-create-backup/server-post-req.json.tpl new file mode 100644 index 000000000000..e6c046ceb4e9 --- /dev/null +++ b/nova/tests/integrated/v3/api_samples/os-create-backup/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-create-backup/server-post-resp.json.tpl b/nova/tests/integrated/v3/api_samples/os-create-backup/server-post-resp.json.tpl new file mode 100644 index 000000000000..eb3f76ebe6d3 --- /dev/null +++ b/nova/tests/integrated/v3/api_samples/os-create-backup/server-post-resp.json.tpl @@ -0,0 +1,16 @@ +{ + "server": { + "admin_password": "%(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/test_admin_actions.py b/nova/tests/integrated/v3/test_admin_actions.py index 4c30c8a7af26..a00a9a2ba084 100644 --- a/nova/tests/integrated/v3/test_admin_actions.py +++ b/nova/tests/integrated/v3/test_admin_actions.py @@ -13,7 +13,6 @@ # License for the specific language governing permissions and limitations # under the License. -from nova.tests.image import fake from nova.tests.integrated.v3 import test_servers @@ -40,21 +39,6 @@ class AdminActionsSamplesJsonTest(test_servers.ServersSampleBase): 'admin-actions-inject-network-info', {}) self.assertEqual(response.status, 202) - def test_post_backup_server(self): - # Get api samples to backup server request. - def image_details(self, context, **kwargs): - """This stub is specifically used on the backup action.""" - # NOTE(maurosr): I've added this simple stub cause backup action - # was trapped in infinite loop during fetch image phase since the - # fake Image Service always returns the same set of images - return [] - - self.stubs.Set(fake._FakeImageService, 'detail', image_details) - - response = self._do_post('servers/%s/action' % self.uuid, - 'admin-actions-backup-server', {}) - self.assertEqual(response.status, 202) - def test_post_reset_state(self): # get api samples to server reset state request. response = self._do_post('servers/%s/action' % self.uuid, diff --git a/nova/tests/integrated/v3/test_create_backup.py b/nova/tests/integrated/v3/test_create_backup.py new file mode 100644 index 000000000000..0cfde247d7ae --- /dev/null +++ b/nova/tests/integrated/v3/test_create_backup.py @@ -0,0 +1,38 @@ +# 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. + +import mock + +from nova.tests.image import fake +from nova.tests.integrated.v3 import test_servers + + +class CreateBackupSamplesJsonTest(test_servers.ServersSampleBase): + extension_name = "os-create-backup" + + def setUp(self): + """setUp Method for PauseServer api samples extension + + This method creates the server that will be used in each tests + """ + super(CreateBackupSamplesJsonTest, self).setUp() + self.uuid = self._post_server() + + @mock.patch.object(fake._FakeImageService, 'detail', return_value=[]) + def test_post_backup_server(self, mock_method): + # Get api samples to backup server request. + response = self._do_post('servers/%s/action' % self.uuid, + 'create-backup-req', {}) + self.assertEqual(202, response.status) diff --git a/setup.cfg b/setup.cfg index ebcc2441f7a7..b61b38042700 100644 --- a/setup.cfg +++ b/setup.cfg @@ -69,6 +69,7 @@ nova.api.v3.extensions = config_drive = nova.api.openstack.compute.plugins.v3.config_drive:ConfigDrive console_output = nova.api.openstack.compute.plugins.v3.console_output:ConsoleOutput consoles = nova.api.openstack.compute.plugins.v3.consoles:Consoles + create_backup = nova.api.openstack.compute.plugins.v3.create_backup:CreateBackup deferred_delete = nova.api.openstack.compute.plugins.v3.deferred_delete:DeferredDelete evacuate = nova.api.openstack.compute.plugins.v3.evacuate:Evacuate extended_availability_zone = nova.api.openstack.compute.plugins.v3.extended_availability_zone:ExtendedAvailabilityZone