summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--heat/api/openstack/v1/views/stacks_view.py1
-rw-r--r--heat/engine/api.py1
-rw-r--r--heat/engine/clients/os/nova.py7
-rw-r--r--heat/engine/hot/functions.py7
-rw-r--r--heat/engine/resources/template_resource.py30
-rw-r--r--heat/rpc/api.py4
-rw-r--r--heat/tests/test_engine_api_utils.py1
-rw-r--r--heat/tests/test_hot.py44
-rw-r--r--heat/tests/test_nova_client.py9
-rw-r--r--heat/tests/test_provider_template.py34
-rw-r--r--heat/tests/test_server.py25
-rw-r--r--requirements.txt4
12 files changed, 152 insertions, 15 deletions
diff --git a/heat/api/openstack/v1/views/stacks_view.py b/heat/api/openstack/v1/views/stacks_view.py
index 16fc5a9f1..4cc7fbfdf 100644
--- a/heat/api/openstack/v1/views/stacks_view.py
+++ b/heat/api/openstack/v1/views/stacks_view.py
@@ -30,6 +30,7 @@ basic_keys = (
engine_api.STACK_UPDATED_TIME,
engine_api.STACK_OWNER,
engine_api.STACK_PARENT,
+ engine_api.STACK_USER_PROJECT_ID,
)
diff --git a/heat/engine/api.py b/heat/engine/api.py
index 1ad332acc..7e6364980 100644
--- a/heat/engine/api.py
+++ b/heat/engine/api.py
@@ -102,6 +102,7 @@ def format_stack(stack, preview=False):
api.STACK_TIMEOUT: stack.timeout_mins,
api.STACK_OWNER: stack.username,
api.STACK_PARENT: stack.owner_id,
+ api.STACK_USER_PROJECT_ID: stack.stack_user_project_id,
}
if not preview:
diff --git a/heat/engine/clients/os/nova.py b/heat/engine/clients/os/nova.py
index c851ea486..564f1c0a0 100644
--- a/heat/engine/clients/os/nova.py
+++ b/heat/engine/clients/os/nova.py
@@ -11,6 +11,7 @@
# License for the specific language governing permissions and limitations
# under the License.
+import collections
import email
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
@@ -307,7 +308,7 @@ echo -e '%s\tALL=(ALL)\tNOPASSWD: ALL' >> /etc/sudoers
else:
# Some clouds append extra (STATUS) strings to the status
short_server_status = server.status.split('(')[0]
- if short_server_status == "DELETED":
+ if short_server_status in ("DELETED", "SOFT_DELETED"):
break
if short_server_status == "ERROR":
fault = getattr(server, 'fault', {})
@@ -369,6 +370,10 @@ echo -e '%s\tALL=(ALL)\tNOPASSWD: ALL' >> /etc/sudoers
Serialize non-string metadata values before sending them to
Nova.
"""
+ if not isinstance(metadata, collections.Mapping):
+ raise exception.StackValidationFailed(message=_(
+ "nova server metadata needs to be a Map."))
+
return dict((key, (value if isinstance(value,
six.string_types)
else json.dumps(value))
diff --git a/heat/engine/hot/functions.py b/heat/engine/hot/functions.py
index 599f7b3d2..a318df3f1 100644
--- a/heat/engine/hot/functions.py
+++ b/heat/engine/hot/functions.py
@@ -199,13 +199,18 @@ class GetFile(function.Function):
key.
"""
+ def __init__(self, stack, fn_name, args):
+ super(GetFile, self).__init__(stack, fn_name, args)
+
+ self.files = self.stack.t.files
+
def result(self):
args = function.resolve(self.args)
if not (isinstance(args, basestring)):
raise TypeError(_('Argument to "%s" must be a string') %
self.fn_name)
- f = self.stack.t.files.get(args)
+ f = self.files.get(args)
if f is None:
fmt_data = {'fn_name': self.fn_name,
'file_key': args}
diff --git a/heat/engine/resources/template_resource.py b/heat/engine/resources/template_resource.py
index 2543a17fc..44649d3c9 100644
--- a/heat/engine/resources/template_resource.py
+++ b/heat/engine/resources/template_resource.py
@@ -56,8 +56,19 @@ class TemplateResource(stack_resource.StackResource):
self.stack = stack
self.validation_exception = None
- tri = stack.env.get_resource_info(
- json_snippet['Type'],
+ self._get_resource_info(json_snippet)
+
+ self.properties_schema = {}
+ self.attributes_schema = {}
+
+ # run Resource.__init__() so we can call self.nested()
+ super(TemplateResource, self).__init__(name, json_snippet, stack)
+ if self.validation_exception is None:
+ self._generate_schema(self.t)
+
+ def _get_resource_info(self, rsrc_defn):
+ tri = self.stack.env.get_resource_info(
+ rsrc_defn['Type'],
registry_type=environment.TemplateResourceInfo)
if tri is None:
self.validation_exception = ValueError(_(
@@ -71,13 +82,6 @@ class TemplateResource(stack_resource.StackResource):
else:
self.allowed_schemes = ('http', 'https', 'file')
- # run Resource.__init__() so we can call self.nested()
- self.properties_schema = {}
- self.attributes_schema = {}
- super(TemplateResource, self).__init__(name, json_snippet, stack)
- if self.validation_exception is None:
- self._generate_schema(self.t)
-
def _generate_schema(self, definition):
self._parsed_nested = None
try:
@@ -255,7 +259,15 @@ class TemplateResource(stack_resource.StackResource):
return self.create_with_template(self.child_template(),
self.child_params())
+ def metadata_update(self, new_metadata=None):
+ '''
+ Refresh the metadata if new_metadata is None
+ '''
+ if new_metadata is None:
+ self.metadata_set(self.t.metadata())
+
def handle_update(self, json_snippet, tmpl_diff, prop_diff):
+ self._get_resource_info(json_snippet)
self._generate_schema(json_snippet)
return self.update_with_template(self.child_template(),
self.child_params())
diff --git a/heat/rpc/api.py b/heat/rpc/api.py
index 8157f0042..6167b8d41 100644
--- a/heat/rpc/api.py
+++ b/heat/rpc/api.py
@@ -32,7 +32,7 @@ STACK_KEYS = (
STACK_PARAMETERS, STACK_OUTPUTS, STACK_ACTION,
STACK_STATUS, STACK_STATUS_DATA, STACK_CAPABILITIES,
STACK_DISABLE_ROLLBACK, STACK_TIMEOUT, STACK_OWNER,
- STACK_PARENT
+ STACK_PARENT, STACK_USER_PROJECT_ID
) = (
'stack_name', 'stack_identity',
'creation_time', 'updated_time', 'deletion_time',
@@ -41,7 +41,7 @@ STACK_KEYS = (
'parameters', 'outputs', 'stack_action',
'stack_status', 'stack_status_reason', 'capabilities',
'disable_rollback', 'timeout_mins', 'stack_owner',
- 'parent'
+ 'parent', 'stack_user_project_id'
)
STACK_OUTPUT_KEYS = (
diff --git a/heat/tests/test_engine_api_utils.py b/heat/tests/test_engine_api_utils.py
index 0f69ccfc6..9468406b9 100644
--- a/heat/tests/test_engine_api_utils.py
+++ b/heat/tests/test_engine_api_utils.py
@@ -238,6 +238,7 @@ class FormatTest(HeatTestCase):
'stack_owner': 'test_username',
'stack_status': 'IN_PROGRESS',
'stack_status_reason': '',
+ 'stack_user_project_id': None,
'template_description': 'No description',
'timeout_mins': None,
'parameters': {
diff --git a/heat/tests/test_hot.py b/heat/tests/test_hot.py
index c122a000f..1d236417c 100644
--- a/heat/tests/test_hot.py
+++ b/heat/tests/test_hot.py
@@ -865,6 +865,50 @@ class StackTest(test_parser.StackTest):
self.assertEqual('xyz', self.stack['AResource'].properties['Foo'])
self.m.VerifyAll()
+ def test_update_modify_files_ok_replace(self):
+ tmpl = {
+ 'heat_template_version': '2013-05-23',
+ 'parameters': {},
+ 'resources': {
+ 'AResource': {
+ 'type': 'ResourceWithPropsType',
+ 'properties': {'Foo': {'get_file': 'foo'}}
+ }
+ }
+ }
+
+ self.m.StubOutWithMock(generic_rsrc.ResourceWithProps,
+ 'update_template_diff')
+
+ self.stack = parser.Stack(self.ctx, 'update_test_stack',
+ template.Template(tmpl,
+ files={'foo': 'abc'}))
+ self.stack.store()
+ self.stack.create()
+ self.assertEqual((parser.Stack.CREATE, parser.Stack.COMPLETE),
+ self.stack.state)
+
+ updated_stack = parser.Stack(self.ctx, 'updated_stack',
+ template.Template(tmpl,
+ files={'foo': 'xyz'}))
+
+ def check_props(*args):
+ self.assertEqual('abc', self.stack['AResource'].properties['Foo'])
+
+ generic_rsrc.ResourceWithProps.update_template_diff(
+ {'Type': 'ResourceWithPropsType',
+ 'Properties': {'Foo': 'xyz'}},
+ {'Type': 'ResourceWithPropsType',
+ 'Properties': {'Foo': 'abc'}}
+ ).WithSideEffects(check_props).AndRaise(resource.UpdateReplace)
+ self.m.ReplayAll()
+
+ self.stack.update(updated_stack)
+ self.assertEqual((parser.Stack.UPDATE, parser.Stack.COMPLETE),
+ self.stack.state)
+ self.assertEqual('xyz', self.stack['AResource'].properties['Foo'])
+ self.m.VerifyAll()
+
class StackAttributesTest(HeatTestCase):
"""
diff --git a/heat/tests/test_nova_client.py b/heat/tests/test_nova_client.py
index dc55437c6..55fcc1843 100644
--- a/heat/tests/test_nova_client.py
+++ b/heat/tests/test_nova_client.py
@@ -15,6 +15,7 @@
import mock
from novaclient import exceptions as nova_exceptions
from oslo.config import cfg
+import six
import uuid
from heat.common import exception
@@ -232,6 +233,14 @@ class NovaUtilsMetadataTests(NovaClientPluginTestCase):
expected = {'test_key': 'null'}
self.assertEqual(expected, self.nova_plugin.meta_serialize(original))
+ def test_serialize_no_value(self):
+ """This test is to prove that the user can only pass in a dict to nova
+ metadata.
+ """
+ excp = self.assertRaises(exception.StackValidationFailed,
+ self.nova_plugin.meta_serialize, "foo")
+ self.assertIn('metadata needs to be a Map', six.text_type(excp))
+
def test_serialize_combined(self):
original = {
'test_key_1': 123,
diff --git a/heat/tests/test_provider_template.py b/heat/tests/test_provider_template.py
index 44e578e3a..21a990a29 100644
--- a/heat/tests/test_provider_template.py
+++ b/heat/tests/test_provider_template.py
@@ -443,6 +443,40 @@ class ProviderTemplateTest(HeatTestCase):
cls = env.get_class('OS::ResourceType', 'fred')
self.assertEqual(template_resource.TemplateResource, cls)
+ def test_metadata_update_called(self):
+ provider = {
+ 'HeatTemplateFormatVersion': '2012-12-12',
+ 'Parameters': {
+ 'Foo': {'Type': 'Boolean'},
+ },
+ }
+ files = {'test_resource.template': json.dumps(provider)}
+
+ class DummyResource(object):
+ support_status = support.SupportStatus()
+ properties_schema = {"Foo":
+ properties.Schema(properties.Schema.BOOLEAN)}
+ attributes_schema = {}
+
+ env = environment.Environment()
+ resource._register_class('DummyResource', DummyResource)
+ env.load({'resource_registry':
+ {'DummyResource': 'test_resource.template'}})
+ stack = parser.Stack(utils.dummy_context(), 'test_stack',
+ parser.Template(
+ {'HeatTemplateFormatVersion': '2012-12-12'},
+ files=files), env=env,
+ stack_id=str(uuid.uuid4()))
+
+ definition = rsrc_defn.ResourceDefinition('test_t_res',
+ "DummyResource",
+ {"Foo": "False"})
+ temp_res = template_resource.TemplateResource('test_t_res',
+ definition, stack)
+ temp_res.metadata_set = mock.Mock()
+ temp_res.metadata_update()
+ temp_res.metadata_set.assert_called_once_with({})
+
def test_get_template_resource_class(self):
test_templ_name = 'file:///etc/heatr/frodo.yaml'
minimal_temp = json.dumps({'HeatTemplateFormatVersion': '2012-12-12',
diff --git a/heat/tests/test_server.py b/heat/tests/test_server.py
index 47de116ed..49ea2f4f9 100644
--- a/heat/tests/test_server.py
+++ b/heat/tests/test_server.py
@@ -1054,6 +1054,31 @@ class ServersTest(HeatTestCase):
self.m.VerifyAll()
+ def test_server_soft_delete(self):
+ return_server = self.fc.servers.list()[1]
+ server = self._create_test_server(return_server,
+ 'create_delete')
+ server.resource_id = '1234'
+
+ # this makes sure the auto increment worked on server creation
+ self.assertTrue(server.id > 0)
+
+ server_get = self.fc.client.get_servers_1234()
+ self.m.StubOutWithMock(self.fc.client, 'get_servers_1234')
+
+ def make_soft_delete():
+ server_get[1]["server"]['status'] = "SOFT_DELETED"
+
+ get = self.fc.client.get_servers_1234
+ get().AndReturn(server_get)
+ get().AndReturn(server_get)
+ get().WithSideEffects(make_soft_delete).AndReturn(server_get)
+ mox.Replay(get)
+
+ scheduler.TaskRunner(server.delete)()
+ self.assertEqual((server.DELETE, server.COMPLETE), server.state)
+ self.m.VerifyAll()
+
def test_server_update_metadata(self):
return_server = self.fc.servers.list()[1]
server = self._create_test_server(return_server,
diff --git a/requirements.txt b/requirements.txt
index d93379bb9..8ceac4557 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -15,7 +15,7 @@ lxml<=3.3.3,>=2.3
netaddr<=0.7.13,>=0.7.12
oslo.config<=1.6.0,>=1.4.0 # Apache-2.0
oslo.db<1.1,>=1.0.0 # Apache-2.0
-oslo.i18n<=1.3.1,>=1.0.0 # Apache-2.0
+oslo.i18n<=1.3.1,>=1.3.0 # Apache-2.0
oslo.messaging<1.5.0,>=1.4.0
oslo.serialization<=1.2.0,>=1.0.0 # Apache-2.0
oslo.utils<1.5.0,>=1.4.0 # Apache-2.0
@@ -24,7 +24,7 @@ posix-ipc<=0.9.9
pycrypto<=2.6.1,>=2.6
python-ceilometerclient!=1.0.12,!=1.0.13,!=1.0.14,<1.1.0,>=1.0.6
python-cinderclient<=1.1.1,>=1.1.0
-python-glanceclient<=0.15.0,>=0.14.0
+python-glanceclient<0.15.0,>=0.14.0
python-heatclient<0.3.0,>=0.2.9
python-keystoneclient<1.2.0,>=0.10.0
python-neutronclient<2.4.0,>=2.3.6