Add snapshot restore HTTP API

This adds the external HTTP API and the RPC methods to invoke stack
restore.

blueprint stack-snapshot
Co-Authored-By: ala.rezmerita@cloudwatt.com

Change-Id: Id1ec0c11c9f12c23b3eec12a853db6ee4ff90277
This commit is contained in:
Thomas Herve 2014-04-30 16:10:07 +02:00
parent 3285709c49
commit e16036cd2d
12 changed files with 86 additions and 9 deletions

View File

@ -57,6 +57,7 @@
"stacks:show_snapshot": "rule:deny_stack_user",
"stacks:delete_snapshot": "rule:deny_stack_user",
"stacks:list_snapshots": "rule:deny_stack_user",
"stacks:restore_snapshot": "rule:deny_stack_user",
"software_configs:create": "rule:deny_stack_user",
"software_configs:show": "rule:deny_stack_user",

View File

@ -143,6 +143,12 @@ class API(wsgi.Router):
action="list_snapshots",
conditions={'method': 'GET'})
stack_mapper.connect("stack_snapshot_restore",
"/stacks/{stack_name}/{stack_id}/snapshots/"
"{snapshot_id}/restore",
action="restore_snapshot",
conditions={'method': 'POST'})
# Resources
resources_resource = resources.create_resource(conf)
stack_path = "/{tenant_id}/stacks/{stack_name}/{stack_id}"

View File

@ -464,6 +464,11 @@ class StackController(object):
req.context, identity)
}
@util.identified_stack
def restore_snapshot(self, req, identity, snapshot_id):
self.rpc_client.stack_restore(req.context, identity, snapshot_id)
raise exc.HTTPAccepted()
class StackSerializer(serializers.JSONResponseSerializer):
"""Handles serialization of specific controller method responses."""

View File

@ -20,13 +20,13 @@ from keystoneclient import exceptions as ks_exceptions
from heat.common import exception
from heat.common.i18n import _
from heat.common.i18n import _LI
from heat.engine import clients
from heat.engine.clients import client_plugin
LOG = logging.getLogger(__name__)
class CinderClientPlugin(clients.client_plugin.ClientPlugin):
class CinderClientPlugin(client_plugin.ClientPlugin):
exceptions_module = exceptions

View File

@ -1244,6 +1244,20 @@ class EngineService(service.Service):
self.thread_group_mgr.start_with_lock(cnxt, stack, self.engine_id,
stack.check)
@request_context
def stack_restore(self, cnxt, stack_identity, snapshot_id):
def _stack_restore(stack, snapshot):
LOG.debug("restoring stack %s" % stack.name)
stack.restore(snapshot)
s = self._get_stack(cnxt, stack_identity)
snapshot = db_api.snapshot_get(cnxt, snapshot_id)
stack = parser.Stack.load(cnxt, stack=s)
self.thread_group_mgr.start_with_lock(cnxt, stack, self.engine_id,
_stack_restore, stack, snapshot)
@request_context
def stack_list_snapshots(self, cnxt, stack_identity):
s = self._get_stack(cnxt, stack_identity)

View File

@ -1055,6 +1055,10 @@ class Stack(collections.Mapping):
'''
Restore the given snapshot, invoking handle_restore on all resources.
'''
if snapshot.stack_id != self.id:
self.state_set(self.RESTORE, self.FAILED,
"Can't restore snapshot from other stack")
return
self.updated_time = datetime.utcnow()
tmpl = Template(snapshot.data['template'])

View File

@ -524,3 +524,8 @@ class EngineClient(object):
def stack_list_snapshots(self, cnxt, stack_identity):
return self.call(cnxt, self.make_msg('stack_list_snapshots',
stack_identity=stack_identity))
def stack_restore(self, cnxt, stack_identity, snapshot_id):
return self.call(cnxt, self.make_msg('stack_restore',
stack_identity=stack_identity,
snapshot_id=snapshot_id))

View File

@ -3242,6 +3242,19 @@ class RoutesTest(common.HeatTestCase):
'stack_id': 'bbbb'
})
self.assertRoute(
self.m,
'/aaaa/stacks/teststack/bbbb/snapshots/cccc/restore',
'POST',
'restore_snapshot',
'StackController',
{
'tenant_id': 'aaaa',
'stack_name': 'teststack',
'stack_id': 'bbbb',
'snapshot_id': 'cccc'
})
def test_stack_data_template(self):
self.assertRoute(
self.m,

View File

@ -3558,13 +3558,13 @@ class SnapshotServiceTest(common.HeatTestCase):
utils.setup_dummy_db()
self.addCleanup(self.m.VerifyAll)
def _create_stack(self):
def _create_stack(self, stub=True):
stack = get_wordpress_stack('stack', self.ctx)
sid = stack.store()
s = db_api.stack_get(self.ctx, sid)
if stub:
self.m.StubOutWithMock(parser.Stack, 'load')
parser.Stack.load(self.ctx, stack=s).MultipleTimes().AndReturn(stack)
return stack
@ -3632,3 +3632,28 @@ class SnapshotServiceTest(common.HeatTestCase):
"status_reason": "Stack SNAPSHOT completed successfully",
"data": stack.prepare_abandon()}
self.assertEqual([expected], snapshots)
def test_restore_snapshot(self):
stack = self._create_stack()
self.m.ReplayAll()
snapshot = self.engine.stack_snapshot(
self.ctx, stack.identifier(), 'snap1')
self.engine.thread_group_mgr.groups[stack.id].wait()
snapshot_id = snapshot['id']
self.engine.stack_restore(self.ctx, stack.identifier(), snapshot_id)
self.engine.thread_group_mgr.groups[stack.id].wait()
self.assertEqual((stack.RESTORE, stack.COMPLETE), stack.state)
def test_restore_snapshot_other_stack(self):
stack1 = self._create_stack()
stack2 = self._create_stack(stub=False)
self.m.ReplayAll()
snapshot1 = self.engine.stack_snapshot(
self.ctx, stack1.identifier(), 'snap1')
self.engine.thread_group_mgr.groups[stack1.id].wait()
snapshot_id = snapshot1['id']
self.engine.stack_restore(self.ctx, stack2.identifier(), snapshot_id)
self.engine.thread_group_mgr.groups[stack2.id].wait()
self.assertEqual((stack2.RESTORE, stack2.FAILED), stack2.state)
self.assertEqual("Can't restore snapshot from other stack",
stack2.status_reason)

View File

@ -4335,7 +4335,8 @@ class StackTest(common.HeatTestCase):
self.stack.create()
data = copy.deepcopy(self.stack.prepare_abandon())
fake_snapshot = collections.namedtuple('Snapshot', ('data',))(data)
fake_snapshot = collections.namedtuple(
'Snapshot', ('data', 'stack_id'))(data, self.stack.id)
new_tmpl = {'HeatTemplateFormatVersion': '2012-12-12',
'Resources': {'A': {'Type': 'GenericResourceType'}}}
@ -4374,7 +4375,8 @@ class StackTest(common.HeatTestCase):
data = self.stack.prepare_abandon()
data['resources']['A']['resource_data']['a_string'] = 'foo'
fake_snapshot = collections.namedtuple('Snapshot', ('data',))(data)
fake_snapshot = collections.namedtuple(
'Snapshot', ('data', 'stack_id'))(data, self.stack.id)
self.stack.restore(fake_snapshot)

View File

@ -2753,7 +2753,8 @@ class ServersTest(common.HeatTestCase):
data = stack.prepare_abandon()
resource_data = data['resources']['WebServer']['resource_data']
resource_data['snapshot_image_id'] = 'CentOS 5.2'
fake_snapshot = collections.namedtuple('Snapshot', ('data',))(data)
fake_snapshot = collections.namedtuple(
'Snapshot', ('data', 'stack_id'))(data, stack.id)
stack.restore(fake_snapshot)

View File

@ -1473,7 +1473,8 @@ class CinderVolumeTest(BaseVolumeTest):
self.assertEqual((stack.SNAPSHOT, stack.COMPLETE), stack.state)
data = stack.prepare_abandon()
fake_snapshot = collections.namedtuple('Snapshot', ('data',))(data)
fake_snapshot = collections.namedtuple(
'Snapshot', ('data', 'stack_id'))(data, stack.id)
stack.restore(fake_snapshot)