diff options
25 files changed, 164 insertions, 91 deletions
diff --git a/doc/source/glossary.rst b/doc/source/glossary.rst index d6ec8b1eb..f94735ffd 100644 --- a/doc/source/glossary.rst +++ b/doc/source/glossary.rst @@ -76,10 +76,9 @@ Nova Instance metadata User-provided *key:value* pairs associated with a Compute - Instance. See `Instance specific data (OpenStack Compute Admin - Guide)`_. + Instance. See `Instance specific data (OpenStack Operations Guide)`_. - .. _Instance specific data (OpenStack Compute Admin Guide): http://docs.openstack.org/grizzly/openstack-compute/admin/content/instance-data.html#inserting_metadata + .. _Instance specific data (OpenStack Operations Guide): http://docs.openstack.org/openstack-ops/content/instances.html#instance_specific_data OpenStack Open source software for building private and public clouds. @@ -169,7 +168,7 @@ configure instances at boot time. See also `User data (OpenStack End User Guide)`_. - .. _User data (OpenStack End User Guide): http://docs.openstack.org/user-guide/content/user-data.html#d6e2415 + .. _User data (OpenStack End User Guide): http://docs.openstack.org/user-guide/cli_provide_user_data_to_instances.html .. _cloud-init: https://help.ubuntu.com/community/CloudInit Wait condition diff --git a/heat/engine/resource.py b/heat/engine/resource.py index 0fc547a10..a0ca6449e 100644 --- a/heat/engine/resource.py +++ b/heat/engine/resource.py @@ -942,7 +942,10 @@ class Resource(object): action = self.SUSPEND # Don't try to suspend the resource unless it's in a stable state - if (self.action == self.DELETE or self.status != self.COMPLETE): + # or if the previous suspend failed + if (self.action == self.DELETE or + (self.action != self.SUSPEND and + self.status != self.COMPLETE)): exc = exception.Error(_('State %s invalid for suspend') % six.text_type(self.state)) raise exception.ResourceFailure(exc, self, action) @@ -957,12 +960,15 @@ class Resource(object): ''' action = self.RESUME - # Can't resume a resource unless it's SUSPEND_COMPLETE - if self.state != (self.SUSPEND, self.COMPLETE): + # Allow resume a resource if it's SUSPEND_COMPLETE + # or RESUME_FAILED or RESUME_COMPLETE. Recommend to check + # the real state of physical resource in handle_resume() + if self.state not in ((self.SUSPEND, self.COMPLETE), + (self.RESUME, self.FAILED), + (self.RESUME, self.COMPLETE)): exc = exception.Error(_('State %s invalid for resume') % six.text_type(self.state)) raise exception.ResourceFailure(exc, self, action) - LOG.info(_LI('resuming %s'), six.text_type(self)) return self._do_action(action) diff --git a/heat/engine/resources/aws/ec2/instance.py b/heat/engine/resources/aws/ec2/instance.py index 192e1416a..c0a2719b1 100644 --- a/heat/engine/resources/aws/ec2/instance.py +++ b/heat/engine/resources/aws/ec2/instance.py @@ -795,8 +795,11 @@ class Instance(resource.Resource): else: raise else: - LOG.debug("suspending instance %s" % self.resource_id) - server.suspend() + # if the instance has been suspended successful, + # no need to suspend again + if self.client_plugin().get_status(server) != 'SUSPENDED': + LOG.debug("suspending instance %s" % self.resource_id) + server.suspend() return server.id def check_suspend_complete(self, server_id): @@ -834,8 +837,11 @@ class Instance(resource.Resource): else: raise else: - LOG.debug("resuming instance %s" % self.resource_id) - server.resume() + # if the instance has been resumed successful, + # no need to resume again + if self.client_plugin().get_status(server) != 'ACTIVE': + LOG.debug("resuming instance %s" % self.resource_id) + server.resume() return server.id def check_resume_complete(self, server_id): diff --git a/heat/engine/resources/openstack/nova/server.py b/heat/engine/resources/openstack/nova/server.py index 8f00c7c1a..6b00e0342 100644 --- a/heat/engine/resources/openstack/nova/server.py +++ b/heat/engine/resources/openstack/nova/server.py @@ -1405,8 +1405,11 @@ class Server(stack_user.StackUser): else: raise else: - LOG.debug('suspending server %s' % self.resource_id) - server.suspend() + # if the server has been suspended successful, + # no need to suspend again + if self.client_plugin().get_status(server) != 'SUSPENDED': + LOG.debug('suspending server %s' % self.resource_id) + server.suspend() return server.id def check_suspend_complete(self, server_id): @@ -1444,8 +1447,11 @@ class Server(stack_user.StackUser): else: raise else: - LOG.debug('resuming server %s' % self.resource_id) - server.resume() + # if the server has been resumed successful, + # no need to resume again + if self.client_plugin().get_status(server) != 'ACTIVE': + LOG.debug('resuming server %s' % self.resource_id) + server.resume() return server.id def check_resume_complete(self, server_id): diff --git a/heat/tests/api/__init__.py b/heat/tests/api/__init__.py new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/heat/tests/api/__init__.py diff --git a/heat/tests/api/aws/__init__.py b/heat/tests/api/aws/__init__.py new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/heat/tests/api/aws/__init__.py diff --git a/heat/tests/test_api_aws.py b/heat/tests/api/aws/test_api_aws.py index 98d199f4f..98d199f4f 100644 --- a/heat/tests/test_api_aws.py +++ b/heat/tests/api/aws/test_api_aws.py diff --git a/heat/tests/test_api_ec2token.py b/heat/tests/api/aws/test_api_ec2token.py index 006dc1f92..006dc1f92 100644 --- a/heat/tests/test_api_ec2token.py +++ b/heat/tests/api/aws/test_api_ec2token.py diff --git a/heat/tests/api/cfn/__init__.py b/heat/tests/api/cfn/__init__.py new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/heat/tests/api/cfn/__init__.py diff --git a/heat/tests/test_api_cfn_v1.py b/heat/tests/api/cfn/test_api_cfn_v1.py index d84d27f63..cfb68ccf3 100644 --- a/heat/tests/test_api_cfn_v1.py +++ b/heat/tests/api/cfn/test_api_cfn_v1.py @@ -29,7 +29,7 @@ from heat.rpc import client as rpc_client from heat.tests import common from heat.tests import utils -policy_path = os.path.dirname(os.path.realpath(__file__)) + "/policy/" +policy_path = os.path.dirname(os.path.realpath(__file__)) + "/../../policy/" class CfnStackControllerTest(common.HeatTestCase): @@ -41,12 +41,12 @@ class CfnStackControllerTest(common.HeatTestCase): def setUp(self): super(CfnStackControllerTest, self).setUp() - opts = [ + self.opts = [ cfg.StrOpt('config_dir', default=policy_path), cfg.StrOpt('config_file', default='foo'), cfg.StrOpt('project', default='heat'), ] - cfg.CONF.register_opts(opts) + cfg.CONF.register_opts(self.opts) cfg.CONF.set_default('host', 'host') self.topic = rpc_api.ENGINE_TOPIC self.api_version = '1.0' @@ -62,6 +62,10 @@ class CfnStackControllerTest(common.HeatTestCase): 'deny_stack_user.json') self.addCleanup(self.m.VerifyAll) + def tearDown(self): + super(CfnStackControllerTest, self).tearDown() + cfg.CONF.unregister_opts(self.opts) + def _dummy_GET_request(self, params=None): # Mangle the params dict into a query string params = params or {} diff --git a/heat/tests/api/cloudwatch/__init__.py b/heat/tests/api/cloudwatch/__init__.py new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/heat/tests/api/cloudwatch/__init__.py diff --git a/heat/tests/test_api_cloudwatch.py b/heat/tests/api/cloudwatch/test_api_cloudwatch.py index f7e7988cc..8cc85dab7 100644 --- a/heat/tests/test_api_cloudwatch.py +++ b/heat/tests/api/cloudwatch/test_api_cloudwatch.py @@ -32,6 +32,33 @@ class WatchControllerTest(common.HeatTestCase): the endpoint processing API requests after they are routed ''' + def setUp(self): + super(WatchControllerTest, self).setUp() + self.path = os.path.dirname(os.path.realpath(__file__)) + self.policy_path = self.path + "/../../policy/" + self.opts = [ + cfg.StrOpt('config_dir', default=self.policy_path), + cfg.StrOpt('config_file', default='foo'), + cfg.StrOpt('project', default='heat'), + ] + cfg.CONF.register_opts(self.opts) + cfg.CONF.set_default('host', 'host') + self.topic = rpc_api.ENGINE_TOPIC + self.api_version = '1.0' + + # Create WSGI controller instance + class DummyConfig(object): + bind_port = 8003 + cfgopts = DummyConfig() + self.controller = watches.WatchController(options=cfgopts) + self.controller.policy.enforcer.policy_path = (self.policy_path + + 'deny_stack_user.json') + self.addCleanup(self.m.VerifyAll) + + def tearDown(self): + super(WatchControllerTest, self).tearDown() + cfg.CONF.unregister_opts(self.opts) + def _dummy_GET_request(self, params=None): # Mangle the params dict into a query string params = params or {} @@ -488,26 +515,3 @@ class WatchControllerTest(common.HeatTestCase): # should raise HeatInvalidParameterValueError result = self.controller.set_alarm_state(dummy_req) self.assertIsInstance(result, exception.HeatInvalidParameterValueError) - - def setUp(self): - super(WatchControllerTest, self).setUp() - self.path = os.path.dirname(os.path.realpath(__file__)) - self.policy_path = self.path + "/policy/" - opts = [ - cfg.StrOpt('config_dir', default=self.policy_path), - cfg.StrOpt('config_file', default='foo'), - cfg.StrOpt('project', default='heat'), - ] - cfg.CONF.register_opts(opts) - cfg.CONF.set_default('host', 'host') - self.topic = rpc_api.ENGINE_TOPIC - self.api_version = '1.0' - - # Create WSGI controller instance - class DummyConfig(object): - bind_port = 8003 - cfgopts = DummyConfig() - self.controller = watches.WatchController(options=cfgopts) - self.controller.policy.enforcer.policy_path = (self.policy_path + - 'deny_stack_user.json') - self.addCleanup(self.m.VerifyAll) diff --git a/heat/tests/api/middleware/__init__.py b/heat/tests/api/middleware/__init__.py new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/heat/tests/api/middleware/__init__.py diff --git a/heat/tests/test_ssl_middleware.py b/heat/tests/api/middleware/test_ssl_middleware.py index 5700f683b..5700f683b 100644 --- a/heat/tests/test_ssl_middleware.py +++ b/heat/tests/api/middleware/test_ssl_middleware.py diff --git a/heat/tests/test_version_negotiation_middleware.py b/heat/tests/api/middleware/test_version_negotiation_middleware.py index b720d9601..b720d9601 100644 --- a/heat/tests/test_version_negotiation_middleware.py +++ b/heat/tests/api/middleware/test_version_negotiation_middleware.py diff --git a/heat/tests/api/openstack/__init__.py b/heat/tests/api/openstack/__init__.py new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/heat/tests/api/openstack/__init__.py diff --git a/heat/tests/test_api_openstack_v1.py b/heat/tests/api/openstack/test_api_openstack_v1.py index 9e68b1063..9e68b1063 100644 --- a/heat/tests/test_api_openstack_v1.py +++ b/heat/tests/api/openstack/test_api_openstack_v1.py diff --git a/heat/tests/test_api_openstack_v1_util.py b/heat/tests/api/openstack/test_api_openstack_v1_util.py index 9c832c317..9c832c317 100644 --- a/heat/tests/test_api_openstack_v1_util.py +++ b/heat/tests/api/openstack/test_api_openstack_v1_util.py diff --git a/heat/tests/test_api_openstack_v1_views_stacks_view_builder.py b/heat/tests/api/openstack/test_api_openstack_v1_views_stacks_view_builder.py index cbf904830..cbf904830 100644 --- a/heat/tests/test_api_openstack_v1_views_stacks_view_builder.py +++ b/heat/tests/api/openstack/test_api_openstack_v1_views_stacks_view_builder.py diff --git a/heat/tests/test_api_openstack_v1_views_views_common.py b/heat/tests/api/openstack/test_api_openstack_v1_views_views_common.py index 981dcc542..981dcc542 100644 --- a/heat/tests/test_api_openstack_v1_views_views_common.py +++ b/heat/tests/api/openstack/test_api_openstack_v1_views_views_common.py diff --git a/heat/tests/test_wsgi.py b/heat/tests/api/test_wsgi.py index c7b32fe36..c7b32fe36 100644 --- a/heat/tests/test_wsgi.py +++ b/heat/tests/api/test_wsgi.py diff --git a/heat/tests/aws/test_instance.py b/heat/tests/aws/test_instance.py index 73cd2b421..bdf7a84eb 100644 --- a/heat/tests/aws/test_instance.py +++ b/heat/tests/aws/test_instance.py @@ -1013,16 +1013,14 @@ class InstancesTest(common.HeatTestCase): scheduler.TaskRunner(instance.create)() self.assertEqual((instance.CREATE, instance.COMPLETE), instance.state) - def test_instance_status_suspend(self): + def _test_instance_status_suspend(self, name, + state=('CREATE', 'COMPLETE')): return_server = self.fc.servers.list()[1] - instance = self._create_test_instance(return_server, - 'in_suspend_wait') + instance = self._create_test_instance(return_server, name) instance.resource_id = '1234' - self.m.ReplayAll() + instance.state_set(state[0], state[1]) - # Override the get_servers_1234 handler status to SUSPENDED, but - # return the ACTIVE state first (twice, so we sleep) d1 = {'server': self.fc.client.get_servers_detail()[1]['servers'][0]} d2 = copy.deepcopy(d1) d1['server']['status'] = 'ACTIVE' @@ -1039,16 +1037,28 @@ class InstancesTest(common.HeatTestCase): self.m.VerifyAll() - def test_instance_status_resume(self): + def test_instance_suspend_in_create_complete(self): + self._test_instance_status_suspend( + name='test_suspend_in_create_complete') + + def test_instance_suspend_in_suspend_failed(self): + self._test_instance_status_suspend( + name='test_suspend_in_suspend_failed', + state=('SUSPEND', 'FAILED')) + + def test_server_suspend_in_suspend_complete(self): + self._test_instance_status_suspend( + name='test_suspend_in_suspend_complete', + state=('SUSPEND', 'COMPLETE')) + + def _test_instance_status_resume(self, name, + state=('SUSPEND', 'COMPLETE')): return_server = self.fc.servers.list()[1] - instance = self._create_test_instance(return_server, - 'in_resume_wait') + instance = self._create_test_instance(return_server, name) instance.resource_id = '1234' - self.m.ReplayAll() + instance.state_set(state[0], state[1]) - # Override the get_servers_1234 handler status to ACTIVE, but - # return the SUSPENDED state first (twice, so we sleep) d1 = {'server': self.fc.client.get_servers_detail()[1]['servers'][0]} d2 = copy.deepcopy(d1) d1['server']['status'] = 'SUSPENDED' @@ -1067,6 +1077,20 @@ class InstancesTest(common.HeatTestCase): self.m.VerifyAll() + def test_instance_resume_in_suspend_complete(self): + self._test_instance_status_resume( + name='test_resume_in_suspend_complete') + + def test_instance_resume_in_resume_failed(self): + self._test_instance_status_resume( + name='test_resume_in_resume_failed', + state=('RESUME', 'FAILED')) + + def test_instance_resume_in_resume_complete(self): + self._test_instance_status_resume( + name='test_resume_in_resume_complete', + state=('RESUME', 'COMPLETE')) + def test_server_resume_other_exception(self): return_server = self.fc.servers.list()[1] instance = self._create_test_instance(return_server, diff --git a/heat/tests/manila/test_manila_share.py b/heat/tests/manila/test_manila_share.py index 2a59242eb..ab7d9e264 100644 --- a/heat/tests/manila/test_manila_share.py +++ b/heat/tests/manila/test_manila_share.py @@ -18,6 +18,7 @@ import six from heat.common import exception from heat.common import template_format +from heat.engine import resource from heat.engine.resources.openstack.manila import share as mshare from heat.engine import rsrc_defn from heat.engine import scheduler @@ -113,18 +114,18 @@ class ManilaShareTest(common.HeatTestCase): def test_share_create_fail(self): share = self._init_share("stack_share_create_fail") - share.client().shares.create.return_value = self.fake_share share.client().shares.get.return_value = self.failed_share - exc = self.assertRaises(exception.ResourceFailure, - scheduler.TaskRunner(share.create)) + exc = self.assertRaises(resource.ResourceInError, + share.check_create_complete, + self.failed_share) self.assertIn("Error during creation", six.text_type(exc)) def test_share_create_unknown_status(self): share = self._init_share("stack_share_create_unknown") - share.client().shares.create.return_value = self.fake_share share.client().shares.get.return_value = self.deleting_share - exc = self.assertRaises(exception.ResourceFailure, - scheduler.TaskRunner(share.create)) + exc = self.assertRaises(resource.ResourceUnknownStatus, + share.check_create_complete, + self.deleting_share) self.assertIn("Unknown status", six.text_type(exc)) def test_share_delete(self): diff --git a/heat/tests/nova/test_server.py b/heat/tests/nova/test_server.py index fa6586c9b..85fea958c 100644 --- a/heat/tests/nova/test_server.py +++ b/heat/tests/nova/test_server.py @@ -1905,16 +1905,13 @@ class ServersTest(common.HeatTestCase): self.m.VerifyAll() - def test_server_status_suspend(self): + def _test_server_status_suspend(self, name, state=('CREATE', 'COMPLETE')): return_server = self.fc.servers.list()[1] - server = self._create_test_server(return_server, - 'srv_susp_w') + server = self._create_test_server(return_server, name) server.resource_id = '1234' - self.m.ReplayAll() + server.state_set(state[0], state[1]) - # Override the get_servers_1234 handler status to SUSPENDED, but - # return the ACTIVE state first (twice, so we sleep) d1 = {'server': self.fc.client.get_servers_detail()[1]['servers'][0]} d2 = copy.deepcopy(d1) d1['server']['status'] = 'ACTIVE' @@ -1931,6 +1928,19 @@ class ServersTest(common.HeatTestCase): self.m.VerifyAll() + def test_server_suspend_in_create_complete(self): + self._test_server_status_suspend('test_suspend_in_create_complete') + + def test_server_suspend_in_suspend_failed(self): + self._test_server_status_suspend( + name='test_suspend_in_suspend_failed', + state=('SUSPEND', 'FAILED')) + + def test_server_suspend_in_suspend_complete(self): + self._test_server_status_suspend( + name='test_suspend_in_suspend_complete', + state=('SUSPEND', 'COMPLETE')) + def test_server_status_suspend_unknown_status(self): return_server = self.fc.servers.list()[1] server = self._create_test_server(return_server, @@ -1962,16 +1972,13 @@ class ServersTest(common.HeatTestCase): self.m.VerifyAll() - def test_server_status_resume(self): + def _test_server_status_resume(self, name, state=('SUSPEND', 'COMPLETE')): return_server = self.fc.servers.list()[1] - server = self._create_test_server(return_server, - 'srv_res_w') + server = self._create_test_server(return_server, name) server.resource_id = '1234' - self.m.ReplayAll() + server.state_set(state[0], state[1]) - # Override the get_servers_1234 handler status to ACTIVE, but - # return the SUSPENDED state first (twice, so we sleep) d1 = {'server': self.fc.client.get_servers_detail()[1]['servers'][0]} d2 = copy.deepcopy(d1) d1['server']['status'] = 'SUSPENDED' @@ -1983,13 +1990,25 @@ class ServersTest(common.HeatTestCase): get().AndReturn((200, d2)) self.m.ReplayAll() - server.state_set(server.SUSPEND, server.COMPLETE) - scheduler.TaskRunner(server.resume)() self.assertEqual((server.RESUME, server.COMPLETE), server.state) self.m.VerifyAll() + def test_server_resume_in_suspend_complete(self): + self._test_server_status_resume( + name='test_resume_in_suspend_complete') + + def test_server_resume_in_resume_failed(self): + self._test_server_status_resume( + name='test_resume_in_resume_failed', + state=('RESUME', 'FAILED')) + + def test_server_resume_in_resume_complete(self): + self._test_server_status_resume( + name='test_resume_in_resume_complete', + state=('RESUME', 'COMPLETE')) + def test_server_status_resume_no_resource_id(self): return_server = self.fc.servers.list()[1] server = self._create_test_server(return_server, diff --git a/heat/tests/test_resource.py b/heat/tests/test_resource.py index 10c2acfd5..592da4220 100644 --- a/heat/tests/test_resource.py +++ b/heat/tests/test_resource.py @@ -980,7 +980,7 @@ class ResourceTest(common.HeatTestCase): scheduler.TaskRunner(res.resume)() self.assertEqual((res.RESUME, res.COMPLETE), res.state) - def test_suspend_fail_inprogress(self): + def test_suspend_fail_invalid_states(self): tmpl = rsrc_defn.ResourceDefinition('test_resource', 'GenericResourceType', {'Foo': 'abc'}) @@ -988,19 +988,19 @@ class ResourceTest(common.HeatTestCase): scheduler.TaskRunner(res.create)() self.assertEqual((res.CREATE, res.COMPLETE), res.state) - res.state_set(res.CREATE, res.IN_PROGRESS) - suspend = scheduler.TaskRunner(res.suspend) - self.assertRaises(exception.ResourceFailure, suspend) - - res.state_set(res.UPDATE, res.IN_PROGRESS) - suspend = scheduler.TaskRunner(res.suspend) - self.assertRaises(exception.ResourceFailure, suspend) + invalid_actions = (a for a in res.ACTIONS if a != res.SUSPEND) + invalid_status = (s for s in res.STATUSES if s != res.COMPLETE) + invalid_states = [s for s in + itertools.product(invalid_actions, invalid_status)] - res.state_set(res.DELETE, res.IN_PROGRESS) - suspend = scheduler.TaskRunner(res.suspend) - self.assertRaises(exception.ResourceFailure, suspend) + for state in invalid_states: + res.state_set(*state) + suspend = scheduler.TaskRunner(res.suspend) + expected = 'State %s invalid for suspend' % six.text_type(state) + exc = self.assertRaises(exception.ResourceFailure, suspend) + self.assertIn(expected, six.text_type(exc)) - def test_resume_fail_not_suspend_complete(self): + def test_resume_fail_invalid_states(self): tmpl = rsrc_defn.ResourceDefinition('test_resource', 'GenericResourceType', {'Foo': 'abc'}) @@ -1008,13 +1008,17 @@ class ResourceTest(common.HeatTestCase): scheduler.TaskRunner(res.create)() self.assertEqual((res.CREATE, res.COMPLETE), res.state) - non_suspended_states = [s for s in - itertools.product(res.ACTIONS, res.STATUSES) - if s != (res.SUSPEND, res.COMPLETE)] - for state in non_suspended_states: + invalid_states = [s for s in + itertools.product(res.ACTIONS, res.STATUSES) + if s not in ((res.SUSPEND, res.COMPLETE), + (res.RESUME, res.FAILED), + (res.RESUME, res.COMPLETE))] + for state in invalid_states: res.state_set(*state) resume = scheduler.TaskRunner(res.resume) - self.assertRaises(exception.ResourceFailure, resume) + expected = 'State %s invalid for resume' % six.text_type(state) + exc = self.assertRaises(exception.ResourceFailure, resume) + self.assertIn(expected, six.text_type(exc)) def test_suspend_fail_exception(self): tmpl = rsrc_defn.ResourceDefinition('test_resource', |