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:
parent
3285709c49
commit
e16036cd2d
@ -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",
|
||||
|
@ -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}"
|
||||
|
@ -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."""
|
||||
|
@ -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
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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'])
|
||||
|
@ -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))
|
||||
|
@ -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,
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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)
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user