Adds create backup server extension for the V3 API

Moves create_backup functionality out of admin_actions
into its own extension. This is part of blueprint v3-admin-actions-split
which allows more selective enablement of features contained
in the admin actions extension.

Note that XML api samples are no longer generated because bp
remove-v3-xml-api has been approved.

Partially implements bp v3-api-admin-actions-split
DocImpact: Adds os-create-backup extension and moves the
create_backup functionality out of os-admin-actions into
this new extension.

Change-Id: I4858a06df20c552bd55ff2841adbcfc761304f42
This commit is contained in:
Chris Yeoh
2013-12-16 12:29:57 +10:30
parent e0f0f14fb6
commit d94203beb4
15 changed files with 444 additions and 268 deletions

View File

@@ -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=="
}
]
}
}

View File

@@ -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"
}
]
}
}

View File

@@ -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": "",

View File

@@ -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):

View File

@@ -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 []

View File

@@ -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()

View File

@@ -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)

View File

@@ -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": "",

View File

@@ -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=="
}
]
}
}

View File

@@ -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"
}
]
}
}

View File

@@ -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,

View File

@@ -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)

View File

@@ -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