Merge "Add snapshot restore HTTP API"
This commit is contained in:
commit
bd243e8e64
@ -57,6 +57,7 @@
|
|||||||
"stacks:show_snapshot": "rule:deny_stack_user",
|
"stacks:show_snapshot": "rule:deny_stack_user",
|
||||||
"stacks:delete_snapshot": "rule:deny_stack_user",
|
"stacks:delete_snapshot": "rule:deny_stack_user",
|
||||||
"stacks:list_snapshots": "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:create": "rule:deny_stack_user",
|
||||||
"software_configs:show": "rule:deny_stack_user",
|
"software_configs:show": "rule:deny_stack_user",
|
||||||
|
@ -143,6 +143,12 @@ class API(wsgi.Router):
|
|||||||
action="list_snapshots",
|
action="list_snapshots",
|
||||||
conditions={'method': 'GET'})
|
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
|
||||||
resources_resource = resources.create_resource(conf)
|
resources_resource = resources.create_resource(conf)
|
||||||
stack_path = "/{tenant_id}/stacks/{stack_name}/{stack_id}"
|
stack_path = "/{tenant_id}/stacks/{stack_name}/{stack_id}"
|
||||||
|
@ -464,6 +464,11 @@ class StackController(object):
|
|||||||
req.context, identity)
|
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):
|
class StackSerializer(serializers.JSONResponseSerializer):
|
||||||
"""Handles serialization of specific controller method responses."""
|
"""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 import exception
|
||||||
from heat.common.i18n import _
|
from heat.common.i18n import _
|
||||||
from heat.common.i18n import _LI
|
from heat.common.i18n import _LI
|
||||||
from heat.engine import clients
|
from heat.engine.clients import client_plugin
|
||||||
|
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class CinderClientPlugin(clients.client_plugin.ClientPlugin):
|
class CinderClientPlugin(client_plugin.ClientPlugin):
|
||||||
|
|
||||||
exceptions_module = exceptions
|
exceptions_module = exceptions
|
||||||
|
|
||||||
|
@ -1245,6 +1245,20 @@ class EngineService(service.Service):
|
|||||||
self.thread_group_mgr.start_with_lock(cnxt, stack, self.engine_id,
|
self.thread_group_mgr.start_with_lock(cnxt, stack, self.engine_id,
|
||||||
stack.check)
|
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
|
@request_context
|
||||||
def stack_list_snapshots(self, cnxt, stack_identity):
|
def stack_list_snapshots(self, cnxt, stack_identity):
|
||||||
s = self._get_stack(cnxt, stack_identity)
|
s = self._get_stack(cnxt, stack_identity)
|
||||||
|
@ -1061,6 +1061,10 @@ class Stack(collections.Mapping):
|
|||||||
'''
|
'''
|
||||||
Restore the given snapshot, invoking handle_restore on all resources.
|
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()
|
self.updated_time = datetime.utcnow()
|
||||||
|
|
||||||
tmpl = Template(snapshot.data['template'])
|
tmpl = Template(snapshot.data['template'])
|
||||||
|
@ -526,3 +526,8 @@ class EngineClient(object):
|
|||||||
def stack_list_snapshots(self, cnxt, stack_identity):
|
def stack_list_snapshots(self, cnxt, stack_identity):
|
||||||
return self.call(cnxt, self.make_msg('stack_list_snapshots',
|
return self.call(cnxt, self.make_msg('stack_list_snapshots',
|
||||||
stack_identity=stack_identity))
|
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))
|
||||||
|
@ -3250,6 +3250,19 @@ class RoutesTest(common.HeatTestCase):
|
|||||||
'stack_id': 'bbbb'
|
'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):
|
def test_stack_data_template(self):
|
||||||
self.assertRoute(
|
self.assertRoute(
|
||||||
self.m,
|
self.m,
|
||||||
|
@ -3562,13 +3562,13 @@ class SnapshotServiceTest(common.HeatTestCase):
|
|||||||
utils.setup_dummy_db()
|
utils.setup_dummy_db()
|
||||||
self.addCleanup(self.m.VerifyAll)
|
self.addCleanup(self.m.VerifyAll)
|
||||||
|
|
||||||
def _create_stack(self):
|
def _create_stack(self, stub=True):
|
||||||
stack = get_wordpress_stack('stack', self.ctx)
|
stack = get_wordpress_stack('stack', self.ctx)
|
||||||
sid = stack.store()
|
sid = stack.store()
|
||||||
|
|
||||||
s = db_api.stack_get(self.ctx, sid)
|
s = db_api.stack_get(self.ctx, sid)
|
||||||
|
if stub:
|
||||||
self.m.StubOutWithMock(parser.Stack, 'load')
|
self.m.StubOutWithMock(parser.Stack, 'load')
|
||||||
|
|
||||||
parser.Stack.load(self.ctx, stack=s).MultipleTimes().AndReturn(stack)
|
parser.Stack.load(self.ctx, stack=s).MultipleTimes().AndReturn(stack)
|
||||||
return stack
|
return stack
|
||||||
|
|
||||||
@ -3636,3 +3636,28 @@ class SnapshotServiceTest(common.HeatTestCase):
|
|||||||
"status_reason": "Stack SNAPSHOT completed successfully",
|
"status_reason": "Stack SNAPSHOT completed successfully",
|
||||||
"data": stack.prepare_abandon()}
|
"data": stack.prepare_abandon()}
|
||||||
self.assertEqual([expected], snapshots)
|
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()
|
self.stack.create()
|
||||||
|
|
||||||
data = copy.deepcopy(self.stack.prepare_abandon())
|
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',
|
new_tmpl = {'HeatTemplateFormatVersion': '2012-12-12',
|
||||||
'Resources': {'A': {'Type': 'GenericResourceType'}}}
|
'Resources': {'A': {'Type': 'GenericResourceType'}}}
|
||||||
@ -4374,7 +4375,8 @@ class StackTest(common.HeatTestCase):
|
|||||||
|
|
||||||
data = self.stack.prepare_abandon()
|
data = self.stack.prepare_abandon()
|
||||||
data['resources']['A']['resource_data']['a_string'] = 'foo'
|
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)
|
self.stack.restore(fake_snapshot)
|
||||||
|
|
||||||
|
@ -2750,7 +2750,8 @@ class ServersTest(common.HeatTestCase):
|
|||||||
data = stack.prepare_abandon()
|
data = stack.prepare_abandon()
|
||||||
resource_data = data['resources']['WebServer']['resource_data']
|
resource_data = data['resources']['WebServer']['resource_data']
|
||||||
resource_data['snapshot_image_id'] = 'CentOS 5.2'
|
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)
|
stack.restore(fake_snapshot)
|
||||||
|
|
||||||
|
@ -1473,7 +1473,8 @@ class CinderVolumeTest(BaseVolumeTest):
|
|||||||
self.assertEqual((stack.SNAPSHOT, stack.COMPLETE), stack.state)
|
self.assertEqual((stack.SNAPSHOT, stack.COMPLETE), stack.state)
|
||||||
|
|
||||||
data = stack.prepare_abandon()
|
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)
|
stack.restore(fake_snapshot)
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user