Merge "Add snapshot restore HTTP API"

This commit is contained in:
Jenkins 2014-11-19 07:32:52 +00:00 committed by Gerrit Code Review
commit bd243e8e64
12 changed files with 86 additions and 9 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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)
self.m.StubOutWithMock(parser.Stack, 'load') if stub:
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)

View File

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

View File

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

View File

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