Merge "Use oslo.utils.reflection to extract class name"
diff --git a/common/clients.py b/common/clients.py
index 9718d03..cff3bf2 100644
--- a/common/clients.py
+++ b/common/clients.py
@@ -12,14 +12,49 @@
 
 import os
 
-import ceilometerclient.client
-import cinderclient.client
-import heatclient.client
-import keystoneclient.exceptions
-import keystoneclient.v2_0.client
-import neutronclient.v2_0.client
-import novaclient.client
-import swiftclient
+from ceilometerclient import client as ceilometer_client
+from cinderclient import client as cinder_client
+from heatclient import client as heat_client
+from keystoneclient.auth.identity.generic import password
+from keystoneclient import exceptions as kc_exceptions
+from keystoneclient import session
+from neutronclient.v2_0 import client as neutron_client
+from novaclient import client as nova_client
+from swiftclient import client as swift_client
+
+
+class KeystoneWrapperClient(object):
+    """Wrapper object for keystone client
+
+    This wraps keystone client, so we can encpasulate certain
+    added properties like auth_token, project_id etc.
+    """
+    def __init__(self, auth_plugin, verify=True):
+        self.auth_plugin = auth_plugin
+        self.session = session.Session(
+            auth=auth_plugin,
+            verify=verify)
+
+    @property
+    def auth_token(self):
+        return self.auth_plugin.get_token(self.session)
+
+    @property
+    def auth_ref(self):
+        return self.auth_plugin.get_access(self.session)
+
+    @property
+    def project_id(self):
+        return self.auth_plugin.get_project_id(self.session)
+
+    def get_endpoint_url(self, service_type, region=None):
+        kwargs = {
+            'service_type': service_type,
+            'endpoint_type': 'publicURL'}
+        if region:
+            kwargs.update({'attr': 'region',
+                           'filter_value': region})
+        return self.auth_ref.service_catalog.url_for(**kwargs)
 
 
 class ClientManager(object):
@@ -36,6 +71,8 @@
 
     def __init__(self, conf):
         self.conf = conf
+        self.v2_auth_url = self.conf.auth_url.replace('/v3', '/v2.0')
+        self.auth_version = self.conf.auth_url.split('/v')[1]
         self.identity_client = self._get_identity_client()
         self.orchestration_client = self._get_orchestration_client()
         self.compute_client = self._get_compute_client()
@@ -45,24 +82,19 @@
         self.metering_client = self._get_metering_client()
 
     def _get_orchestration_client(self):
-        region = self.conf.region
         endpoint = os.environ.get('HEAT_URL')
         if os.environ.get('OS_NO_CLIENT_AUTH') == 'True':
             token = None
         else:
-            keystone = self._get_identity_client()
-            token = keystone.auth_token
+            token = self.identity_client.auth_token
         try:
             if endpoint is None:
-                endpoint = keystone.service_catalog.url_for(
-                    attr='region',
-                    filter_value=region,
-                    service_type='orchestration',
-                    endpoint_type='publicURL')
-        except keystoneclient.exceptions.EndpointNotFound:
+                endpoint = self.identity_client.get_endpoint_url(
+                    'orchestration', self.conf.region)
+        except kc_exceptions.EndpointNotFound:
             return None
         else:
-            return heatclient.client.Client(
+            return heat_client.Client(
                 self.HEATCLIENT_VERSION,
                 endpoint,
                 token=token,
@@ -70,12 +102,22 @@
                 password=self.conf.password)
 
     def _get_identity_client(self):
-        return keystoneclient.v2_0.client.Client(
-            username=self.conf.username,
-            password=self.conf.password,
-            tenant_name=self.conf.tenant_name,
-            auth_url=self.conf.auth_url,
-            insecure=self.conf.disable_ssl_certificate_validation)
+        domain = self.conf.domain_name
+        kwargs = {
+            'username': self.conf.username,
+            'password': self.conf.password,
+            'tenant_name': self.conf.tenant_name,
+            'auth_url': self.conf.auth_url
+        }
+        # keystone v2 can't ignore domain details
+        if self.auth_version == '3':
+            kwargs.update({
+                'project_domain_name': domain,
+                'user_domain_name': domain})
+        auth = password.Password(**kwargs)
+        return KeystoneWrapperClient(
+            auth,
+            not self.conf.disable_ssl_certificate_validation)
 
     def _get_compute_client(self):
 
@@ -86,11 +128,12 @@
             self.conf.username,
             self.conf.password,
             self.conf.tenant_name,
-            self.conf.auth_url
+            # novaclient can not use v3 url
+            self.v2_auth_url
         )
 
         # Create our default Nova client to use in testing
-        return novaclient.client.Client(
+        return nova_client.Client(
             self.NOVACLIENT_VERSION,
             *client_args,
             service_type='compute',
@@ -101,28 +144,28 @@
             http_log_debug=True)
 
     def _get_network_client(self):
-        auth_url = self.conf.auth_url
         dscv = self.conf.disable_ssl_certificate_validation
 
-        return neutronclient.v2_0.client.Client(
+        return neutron_client.Client(
             username=self.conf.username,
             password=self.conf.password,
             tenant_name=self.conf.tenant_name,
             endpoint_type='publicURL',
-            auth_url=auth_url,
+            # neutronclient can not use v3 url
+            auth_url=self.v2_auth_url,
             insecure=dscv)
 
     def _get_volume_client(self):
-        auth_url = self.conf.auth_url
         region = self.conf.region
         endpoint_type = 'publicURL'
         dscv = self.conf.disable_ssl_certificate_validation
-        return cinderclient.client.Client(
+        return cinder_client.Client(
             self.CINDERCLIENT_VERSION,
             self.conf.username,
             self.conf.password,
             self.conf.tenant_name,
-            auth_url,
+            # cinderclient can not use v3 url
+            self.v2_auth_url,
             region_name=region,
             endpoint_type=endpoint_type,
             insecure=dscv,
@@ -131,7 +174,7 @@
     def _get_object_client(self):
         dscv = self.conf.disable_ssl_certificate_validation
         args = {
-            'auth_version': '2.0',
+            'auth_version': self.auth_version,
             'tenant_name': self.conf.tenant_name,
             'user': self.conf.username,
             'key': self.conf.password,
@@ -139,20 +182,15 @@
             'os_options': {'endpoint_type': 'publicURL'},
             'insecure': dscv,
         }
-        return swiftclient.client.Connection(**args)
+        return swift_client.Connection(**args)
 
     def _get_metering_client(self):
         dscv = self.conf.disable_ssl_certificate_validation
-
-        keystone = self._get_identity_client()
+        domain = self.conf.domain_name
         try:
-            endpoint = keystone.service_catalog.url_for(
-                attr='region',
-                filter_value=self.conf.region,
-                service_type='metering',
-                endpoint_type='publicURL')
-
-        except keystoneclient.exceptions.EndpointNotFound:
+            endpoint = self.identity_client.get_endpoint_url('metering',
+                                                             self.conf.region)
+        except kc_exceptions.EndpointNotFound:
             return None
         else:
             args = {
@@ -165,6 +203,12 @@
                 'endpoint_type': 'publicURL',
                 'service_type': 'metering',
             }
+            # ceilometerclient can't ignore domain details for
+            # v2 auth_url
+            if self.auth_version == '3':
+                args.update(
+                    {'user_domain_name': domain,
+                     'project_domain_name': domain})
 
-            return ceilometerclient.client.Client(self.CEILOMETER_VERSION,
-                                                  endpoint, **args)
+            return ceilometer_client.Client(self.CEILOMETER_VERSION,
+                                            endpoint, **args)
diff --git a/common/config.py b/common/config.py
index 9ad6337..6d35600 100644
--- a/common/config.py
+++ b/common/config.py
@@ -21,10 +21,15 @@
 
     cfg.StrOpt('username',
                default=os.environ.get('OS_USERNAME'),
-               help="Username to use for API requests."),
+               help="Username to use for non admin API requests."),
     cfg.StrOpt('password',
                default=os.environ.get('OS_PASSWORD'),
-               help="API key to use when authenticating.",
+               help="Non admin API key to use when authenticating.",
+               secret=True),
+    cfg.StrOpt('admin_username',
+               help="Username to use for admin API requests."),
+    cfg.StrOpt('admin_password',
+               help="Admin API key to use when authentication.",
                secret=True),
     cfg.StrOpt('tenant_name',
                default=(os.environ.get('OS_PROJECT_NAME') or
@@ -32,10 +37,14 @@
                help="Tenant name to use for API requests."),
     cfg.StrOpt('auth_url',
                default=os.environ.get('OS_AUTH_URL'),
-               help="Full URI of the OpenStack Identity API (Keystone), v2"),
+               help="Full URI of the OpenStack Identity API (Keystone)"),
+    cfg.StrOpt('domain_name',
+               default='default',
+               help="User/project domain name, if keystone v3 auth_url"
+                    "is used"),
     cfg.StrOpt('region',
                default=os.environ.get('OS_REGION_NAME'),
-               help="The region name to us"),
+               help="The region name to use"),
     cfg.StrOpt('instance_type',
                help="Instance type for tests. Needs to be big enough for a "
                     "full OS plus the test workload"),
@@ -48,10 +57,6 @@
     cfg.StrOpt('minimal_image_ref',
                help="Name of minimal (e.g cirros) image to use when "
                     "launching test instances."),
-    cfg.StrOpt('auth_version',
-               default='v2',
-               help="Identity API version to be used for authentication "
-                    "for API tests."),
     cfg.BoolOpt('disable_ssl_certificate_validation',
                 default=False,
                 help="Set to True if using self-signed SSL certificates."),
@@ -124,6 +129,7 @@
     cfg.StrOpt('heat-config-notify-script',
                default=('heat-config-notify'),
                help="Path to the script heat-config-notify"),
+
 ]
 
 
diff --git a/common/test.py b/common/test.py
index 1ffe222..4eaa2b7 100644
--- a/common/test.py
+++ b/common/test.py
@@ -257,6 +257,14 @@
                    (resource_name, status, build_timeout))
         raise exceptions.TimeoutException(message)
 
+    def verify_resource_status(self, stack_identifier, resource_name,
+                               status='CREATE_COMPLETE'):
+        try:
+            res = self.client.resources.get(stack_identifier, resource_name)
+        except heat_exceptions.HTTPNotFound:
+            return False
+        return res.resource_status == status
+
     def _verify_status(self, stack, stack_identifier, status, fail_regexp):
         if stack.stack_status == status:
             # Handle UPDATE_COMPLETE/FAILED case: Make sure we don't
@@ -310,7 +318,8 @@
         while timeutils.delta_seconds(start,
                                       timeutils.utcnow()) < build_timeout:
             try:
-                stack = self.client.stacks.get(stack_identifier)
+                stack = self.client.stacks.get(stack_identifier,
+                                               resolve_outputs=False)
             except heat_exceptions.HTTPNotFound:
                 if success_on_not_found:
                     return
@@ -367,7 +376,7 @@
         stack_name = stack_identifier.split('/')[0]
 
         self.updated_time[stack_identifier] = self.client.stacks.get(
-            stack_identifier).updated_time
+            stack_identifier, resolve_outputs=False).updated_time
 
         self._handle_in_progress(
             self.client.stacks.update,
@@ -392,7 +401,8 @@
 
     def preview_update_stack(self, stack_identifier, template,
                              environment=None, files=None, parameters=None,
-                             tags=None, disable_rollback=True):
+                             tags=None, disable_rollback=True,
+                             show_nested=False):
         env = environment or {}
         env_files = files or {}
         parameters = parameters or {}
@@ -406,7 +416,8 @@
             disable_rollback=disable_rollback,
             parameters=parameters,
             environment=env,
-            tags=tags
+            tags=tags,
+            show_nested=show_nested
         )
 
     def assert_resource_is_a_stack(self, stack_identifier, res_name,
@@ -437,7 +448,7 @@
         nested_identifier = '/'.join(nested_href.split('/')[-2:])
         self.assertEqual(rsrc.physical_resource_id, nested_id)
 
-        nested_stack = self.client.stacks.get(nested_id)
+        nested_stack = self.client.stacks.get(nested_id, resolve_outputs=False)
         nested_identifier2 = '%s/%s' % (nested_stack.stack_name,
                                         nested_stack.id)
         self.assertEqual(nested_identifier, nested_identifier2)
@@ -451,7 +462,8 @@
         rsrc = self.client.resources.get(stack_identifier, group_name)
         physical_resource_id = rsrc.physical_resource_id
 
-        nested_stack = self.client.stacks.get(physical_resource_id)
+        nested_stack = self.client.stacks.get(physical_resource_id,
+                                              resolve_outputs=False)
         nested_identifier = '%s/%s' % (nested_stack.stack_name,
                                        nested_stack.id)
         parent_id = stack_identifier.split("/")[-1]
@@ -473,7 +485,8 @@
     def stack_create(self, stack_name=None, template=None, files=None,
                      parameters=None, environment=None, tags=None,
                      expected_status='CREATE_COMPLETE',
-                     disable_rollback=True, enable_cleanup=True):
+                     disable_rollback=True, enable_cleanup=True,
+                     environment_files=None):
         name = stack_name or self._stack_rand_name()
         templ = template or self.template
         templ_files = files or {}
@@ -486,12 +499,13 @@
             disable_rollback=disable_rollback,
             parameters=params,
             environment=env,
-            tags=tags
+            tags=tags,
+            environment_files=environment_files
         )
         if expected_status not in ['ROLLBACK_COMPLETE'] and enable_cleanup:
             self.addCleanup(self._stack_delete, name)
 
-        stack = self.client.stacks.get(name)
+        stack = self.client.stacks.get(name, resolve_outputs=False)
         stack_identifier = '%s/%s' % (name, stack.id)
         kwargs = {'stack_identifier': stack_identifier,
                   'status': expected_status}
@@ -522,8 +536,7 @@
             adopt_stack_data=adopt_data,
         )
         self.addCleanup(self._stack_delete, name)
-
-        stack = self.client.stacks.get(name)
+        stack = self.client.stacks.get(name, resolve_outputs=False)
         stack_identifier = '%s/%s' % (name, stack.id)
         self._wait_for_stack_status(stack_identifier, wait_for_status)
         return stack_identifier
diff --git a/functional/test_aws_stack.py b/functional/test_aws_stack.py
index 678ce7e..13c4278 100644
--- a/functional/test_aws_stack.py
+++ b/functional/test_aws_stack.py
@@ -77,7 +77,7 @@
     def setUp(self):
         super(AwsStackTest, self).setUp()
         self.object_container_name = test.rand_name()
-        self.project_id = self.identity_client.auth_ref.project_id
+        self.project_id = self.identity_client.project_id
         self.swift_key = hashlib.sha224(
             str(random.getrandbits(256))).hexdigest()[:32]
         key_header = 'x-container-meta-temp-url-key'
@@ -115,7 +115,7 @@
     def test_nested_stack_create_with_timeout(self):
         url = self.publish_template(self.nested_template)
         self.template = self.test_template.replace('the.yaml', url)
-        timeout_template = yaml.load(self.template)
+        timeout_template = yaml.safe_load(self.template)
         props = timeout_template['Resources']['the_nested']['Properties']
         props['TimeoutInMinutes'] = '50'
 
@@ -142,7 +142,7 @@
                 }
             },
             "environment": {"parameters": {}},
-            "template": yaml.load(self.template)
+            "template": yaml.safe_load(self.template)
         }
 
         stack_identifier = self.stack_adopt(adopt_data=json.dumps(adopt_data))
@@ -163,7 +163,7 @@
                 }
             },
             "environment": {"parameters": {}},
-            "template": yaml.load(self.template)
+            "template": yaml.safe_load(self.template)
         }
 
         stack_identifier = self.stack_adopt(adopt_data=json.dumps(adopt_data),
@@ -180,7 +180,7 @@
         stack = self.client.stacks.get(stack_identifier)
         self.assertEqual('bar', self._stack_output(stack, 'output_foo'))
 
-        new_template = yaml.load(self.template)
+        new_template = yaml.safe_load(self.template)
         props = new_template['Resources']['the_nested']['Properties']
         props['TemplateURL'] = self.publish_template(self.update_template,
                                                      cleanup=False)
diff --git a/functional/test_conditional_exposure.py b/functional/test_conditional_exposure.py
index 90f7d7a..c1175f1 100644
--- a/functional/test_conditional_exposure.py
+++ b/functional/test_conditional_exposure.py
@@ -42,13 +42,9 @@
                           "Sahara resources availability.")
 
     def _is_sahara_deployed(self):
-        keystone = self.identity_client
         try:
-            keystone.service_catalog.url_for(
-                attr='region',
-                filter_value=self.conf.region,
-                service_type='data-processing',
-                endpoint_type='publicURL')
+            self.identity_client.get_endpoint_url('data-processing',
+                                                  self.conf.region)
         except keystoneclient.exceptions.EndpointNotFound:
             return False
         return True
diff --git a/functional/test_default_parameters.py b/functional/test_default_parameters.py
index 3e00c35..7201969 100644
--- a/functional/test_default_parameters.py
+++ b/functional/test_default_parameters.py
@@ -75,9 +75,9 @@
 
         if not self.temp_def:
             # remove the default from the parameter in the nested template.
-            ntempl = yaml.load(self.nested_template)
+            ntempl = yaml.safe_load(self.nested_template)
             del ntempl['parameters']['length']['default']
-            nested_template = yaml.dump(ntempl)
+            nested_template = yaml.safe_dump(ntempl)
         else:
             nested_template = self.nested_template
 
diff --git a/functional/test_encryption_vol_type.py b/functional/test_encryption_vol_type.py
index e1a3e76..2679990 100644
--- a/functional/test_encryption_vol_type.py
+++ b/functional/test_encryption_vol_type.py
@@ -42,11 +42,14 @@
 class EncryptionVolTypeTest(functional_base.FunctionalTestsBase):
     def setUp(self):
         super(EncryptionVolTypeTest, self).setUp()
+        if not self.conf.admin_username or not self.conf.admin_password:
+            self.skipTest('No admin creds found, skipping')
         self.conf = config.init_conf()
         # cinder security policy usage of volume type is limited
         # to being used by administrators only.
-        # Temporarily set username as admin for this test case.
-        self.conf.username = 'admin'
+        # Temporarily switch to admin
+        self.conf.username = self.conf.admin_username
+        self.conf.password = self.conf.admin_password
         self.manager = clients.ClientManager(self.conf)
         self.client = self.manager.orchestration_client
         self.volume_client = self.manager.volume_client
diff --git a/functional/test_env_merge.py b/functional/test_env_merge.py
new file mode 100644
index 0000000..5e222b8
--- /dev/null
+++ b/functional/test_env_merge.py
@@ -0,0 +1,95 @@
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
+from heat_integrationtests.functional import functional_base
+
+
+TEMPLATE = '''
+    heat_template_version: 2015-04-30
+    parameters:
+      p0:
+        type: string
+        default: CORRECT
+      p1:
+        type: string
+        default: INCORRECT
+      p2:
+        type: string
+        default: INCORRECT
+    resources:
+      r1:
+        type: test::R1
+      r2:
+        type: test::R2
+      r3a:
+        type: test::R3
+      r3b:
+        type: test::R3
+'''
+
+ENV_1 = '''
+    parameters:
+      p1: CORRECT
+      p2: INCORRECT-E1
+    resource_registry:
+      test::R1: OS::Heat::RandomString
+      test::R2: BROKEN
+      test::R3: OS::Heat::None
+'''
+
+ENV_2 = '''
+    parameters:
+      p2: CORRECT
+    resource_registry:
+      test::R2: OS::Heat::RandomString
+      resources:
+        r3b:
+          test::R3: OS::Heat::RandomString
+'''
+
+
+class EnvironmentMergingTests(functional_base.FunctionalTestsBase):
+
+    def test_server_environment_merging(self):
+
+        # Setup
+        files = {'env1.yaml': ENV_1, 'env2.yaml': ENV_2}
+        environment_files = ['env1.yaml', 'env2.yaml']
+
+        # Test
+        stack_id = self.stack_create(stack_name='env_merge',
+                                     template=TEMPLATE,
+                                     files=files,
+                                     environment_files=environment_files)
+
+        # Verify
+
+        # Since there is no environment show, the registry overriding
+        # is partially verified by there being no error. If it wasn't
+        # working, test::R2 would remain mapped to BROKEN in env1.
+
+        # Sanity check
+        resources = self.list_resources(stack_id)
+        self.assertEqual(4, len(resources))
+
+        # Verify the parameters are correctly set
+        stack = self.client.stacks.get(stack_id)
+        self.assertEqual('CORRECT', stack.parameters['p0'])
+        self.assertEqual('CORRECT', stack.parameters['p1'])
+        self.assertEqual('CORRECT', stack.parameters['p2'])
+
+        # Verify that r3b has been overridden into a RandomString
+        # by checking to see that it has a value
+        r3b = self.client.resources.get(stack_id, 'r3b')
+        r3b_attrs = r3b.attributes
+        self.assertTrue('value' in r3b_attrs)
diff --git a/functional/test_event_sinks.py b/functional/test_event_sinks.py
new file mode 100644
index 0000000..e4a23ff
--- /dev/null
+++ b/functional/test_event_sinks.py
@@ -0,0 +1,66 @@
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
+import uuid
+
+from zaqarclient.queues.v1 import client as zaqarclient
+
+from heat_integrationtests.functional import functional_base
+
+
+class ZaqarEventSinkTest(functional_base.FunctionalTestsBase):
+    template = '''
+heat_template_version: "2013-05-23"
+resources:
+  test_resource:
+    type: OS::Heat::TestResource
+    properties:
+      value: ok
+'''
+
+    def test_events(self):
+        queue_id = str(uuid.uuid4())
+        environment = {'event_sinks': [{'type': 'zaqar-queue',
+                                        'target': queue_id,
+                                        'ttl': 120}]}
+        stack_identifier = self.stack_create(
+            template=self.template,
+            environment=environment)
+        stack_name, stack_id = stack_identifier.split('/')
+        conf = {
+            'auth_opts': {
+                'backend': 'keystone',
+                'options': {
+                    'os_username': self.conf.username,
+                    'os_password': self.conf.password,
+                    'os_project_name': self.conf.tenant_name,
+                    'os_auth_url': self.conf.auth_url
+                }
+            }
+        }
+
+        zaqar = zaqarclient.Client(conf=conf, version=1.1)
+        queue = zaqar.queue(queue_id)
+        messages = list(queue.messages())
+        self.assertEqual(4, len(messages))
+        types = [m.body['type'] for m in messages]
+        self.assertEqual(['os.heat.event'] * 4, types)
+        resources = set([m.body['payload']['resource_name'] for m in messages])
+        self.assertEqual(set([stack_name, 'test_resource']), resources)
+        stack_ids = [m.body['payload']['stack_id'] for m in messages]
+        self.assertEqual([stack_id] * 4, stack_ids)
+        statuses = [m.body['payload']['resource_status'] for m in messages]
+        statuses.sort()
+        self.assertEqual(
+            ['COMPLETE', 'COMPLETE', 'IN_PROGRESS', 'IN_PROGRESS'], statuses)
+        actions = [m.body['payload']['resource_action'] for m in messages]
+        self.assertEqual(['CREATE'] * 4, actions)
diff --git a/functional/test_hooks.py b/functional/test_hooks.py
index d9ebd44..bafb0ef 100644
--- a/functional/test_hooks.py
+++ b/functional/test_hooks.py
@@ -191,7 +191,7 @@
                          res_after.physical_resource_id)
 
     def test_hook_pre_create_nested(self):
-        files = {'nested.yaml': yaml.dump(self.template)}
+        files = {'nested.yaml': yaml.safe_dump(self.template)}
         env = {'resource_registry':
                {'resources':
                 {'nested':
diff --git a/functional/test_immutable_parameters.py b/functional/test_immutable_parameters.py
new file mode 100644
index 0000000..d223b14
--- /dev/null
+++ b/functional/test_immutable_parameters.py
@@ -0,0 +1,141 @@
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
+from heat_integrationtests.functional import functional_base
+from heatclient import exc as heat_exceptions
+
+
+class ImmutableParametersTest(functional_base.FunctionalTestsBase):
+
+    template_param_has_no_immutable_field = '''
+heat_template_version: 2014-10-16
+parameters:
+  param1:
+    type: string
+    default: default_value
+outputs:
+  param1_output:
+    description: 'parameter 1 details'
+    value: { get_param: param1 }
+'''
+
+    template_param_has_immutable_field = '''
+heat_template_version: 2014-10-16
+parameters:
+  param1:
+    type: string
+    default: default_value
+    immutable: false
+outputs:
+  param1_output:
+    description: 'parameter 1 details'
+    value: { get_param: param1 }
+'''
+
+    def test_no_immutable_param_field(self):
+        param1_create_value = 'value1'
+        create_parameters = {"param1": param1_create_value}
+
+        stack_identifier = self.stack_create(
+            template=self.template_param_has_no_immutable_field,
+            parameters=create_parameters
+        )
+        stack = self.client.stacks.get(stack_identifier)
+
+        # Verify the value of the parameter
+        self.assertEqual(param1_create_value,
+                         self._stack_output(stack, 'param1_output'))
+
+        param1_update_value = 'value2'
+        update_parameters = {"param1": param1_update_value}
+
+        self.update_stack(
+            stack_identifier,
+            template=self.template_param_has_no_immutable_field,
+            parameters=update_parameters)
+
+        stack = self.client.stacks.get(stack_identifier)
+
+        # Verify the value of the updated parameter
+        self.assertEqual(param1_update_value,
+                         self._stack_output(stack, 'param1_output'))
+
+    def test_immutable_param_field_allowed(self):
+        param1_create_value = 'value1'
+        create_parameters = {"param1": param1_create_value}
+
+        stack_identifier = self.stack_create(
+            template=self.template_param_has_immutable_field,
+            parameters=create_parameters
+        )
+        stack = self.client.stacks.get(stack_identifier)
+
+        # Verify the value of the parameter
+        self.assertEqual(param1_create_value,
+                         self._stack_output(stack, 'param1_output'))
+
+        param1_update_value = 'value2'
+        update_parameters = {"param1": param1_update_value}
+
+        self.update_stack(
+            stack_identifier,
+            template=self.template_param_has_immutable_field,
+            parameters=update_parameters)
+        stack = self.client.stacks.get(stack_identifier)
+
+        # Verify the value of the updated parameter
+        self.assertEqual(param1_update_value,
+                         self._stack_output(stack, 'param1_output'))
+
+        # Ensure stack is not in a failed state
+        self.assertEqual('UPDATE_COMPLETE', stack.stack_status)
+
+    def test_immutable_param_field_error(self):
+        param1_create_value = 'value1'
+        create_parameters = {"param1": param1_create_value}
+
+        # Toggle the immutable field to preclude updating
+        immutable_true = self.template_param_has_immutable_field.replace(
+            'immutable: false', 'immutable: true')
+
+        stack_identifier = self.stack_create(
+            template=immutable_true,
+            parameters=create_parameters
+        )
+        stack = self.client.stacks.get(stack_identifier)
+
+        param1_update_value = 'value2'
+        update_parameters = {"param1": param1_update_value}
+
+        # Verify the value of the parameter
+        self.assertEqual(param1_create_value,
+                         self._stack_output(stack, 'param1_output'))
+
+        # Attempt to update the stack with a new parameter value
+        try:
+            self.update_stack(
+                stack_identifier,
+                template=immutable_true,
+                parameters=update_parameters)
+        except heat_exceptions.HTTPBadRequest as exc:
+            exp = ('The following parameters are immutable and may not be '
+                   'updated: param1')
+            self.assertIn(exp, str(exc))
+
+        stack = self.client.stacks.get(stack_identifier)
+
+        # Ensure stack is not in a failed state
+        self.assertEqual('CREATE_COMPLETE', stack.stack_status)
+
+        # Ensure immutable parameter has not changed
+        self.assertEqual(param1_create_value,
+                         self._stack_output(stack, 'param1_output'))
diff --git a/functional/test_nova_server_networks.py b/functional/test_nova_server_networks.py
index 99311d0..ae550b2 100644
--- a/functional/test_nova_server_networks.py
+++ b/functional/test_nova_server_networks.py
@@ -64,3 +64,25 @@
             parameters=parms)
         networks = self.get_outputs(stack_identifier, 'networks')
         self.assertEqual(['11.11.11.11'], networks['my_net'])
+
+    def test_create_update_server_with_subnet(self):
+        parms = {'flavor': self.conf.minimal_instance_type,
+                 'image': self.conf.minimal_image_ref}
+        template = server_with_sub_fixed_ip_template.replace(
+            'fixed_ip: 11.11.11.11', 'fixed_ip: 11.11.11.22')
+        stack_identifier = self.stack_create(
+            template=template,
+            stack_name='create_server_with_sub_ip',
+            parameters=parms)
+        networks = self.get_outputs(stack_identifier, 'networks')
+        self.assertEqual(['11.11.11.22'], networks['my_net'])
+
+        # update the server only with subnet, we won't pass
+        # both port_id and net_id to attach interface, then update success
+        template_only_subnet = template.replace(
+            'fixed_ip: 11.11.11.22', '')
+        self.update_stack(stack_identifier,
+                          template_only_subnet,
+                          parameters=parms)
+        new_networks = self.get_outputs(stack_identifier, 'networks')
+        self.assertNotEqual(['11.11.11.22'], new_networks['my_net'])
diff --git a/functional/test_preview.py b/functional/test_preview.py
index b927a25..5846523 100644
--- a/functional/test_preview.py
+++ b/functional/test_preview.py
@@ -43,7 +43,7 @@
     def setUp(self):
         super(StackPreviewTest, self).setUp()
         self.client = self.orchestration_client
-        self.project_id = self.identity_client.auth_ref.project_id
+        self.project_id = self.identity_client.project_id
 
     def _assert_resource(self, res, stack_name):
         self.assertEqual(stack_name, res['stack_name'])
diff --git a/functional/test_preview_update.py b/functional/test_preview_update.py
index 0e39bc9..971e9c5 100644
--- a/functional/test_preview_update.py
+++ b/functional/test_preview_update.py
@@ -54,7 +54,14 @@
 }
 
 
-class UpdatePreviewStackTest(functional_base.FunctionalTestsBase):
+class UpdatePreviewBase(functional_base.FunctionalTestsBase):
+
+    def assert_empty_sections(self, changes, empty_sections):
+        for section in empty_sections:
+            self.assertEqual([], changes[section])
+
+
+class UpdatePreviewStackTest(UpdatePreviewBase):
 
     def test_add_resource(self):
         self.stack_identifier = self.stack_create(
@@ -69,9 +76,7 @@
         added = changes['added'][0]['resource_name']
         self.assertEqual('test2', added)
 
-        empty_sections = ('updated', 'replaced', 'deleted')
-        for section in empty_sections:
-            self.assertEqual([], changes[section])
+        self.assert_empty_sections(changes, ['updated', 'replaced', 'deleted'])
 
     def test_no_change(self):
         self.stack_identifier = self.stack_create(
@@ -83,9 +88,8 @@
         unchanged = changes['unchanged'][0]['resource_name']
         self.assertEqual('test1', unchanged)
 
-        empty_sections = ('updated', 'replaced', 'deleted', 'added')
-        for section in empty_sections:
-            self.assertEqual([], changes[section])
+        self.assert_empty_sections(
+            changes, ['updated', 'replaced', 'deleted', 'added'])
 
     def test_update_resource(self):
         self.stack_identifier = self.stack_create(
@@ -113,9 +117,8 @@
         updated = changes['updated'][0]['resource_name']
         self.assertEqual('test1', updated)
 
-        empty_sections = ('added', 'unchanged', 'replaced', 'deleted')
-        for section in empty_sections:
-            self.assertEqual([], changes[section])
+        self.assert_empty_sections(
+            changes, ['added', 'unchanged', 'replaced', 'deleted'])
 
     def test_replaced_resource(self):
         self.stack_identifier = self.stack_create(
@@ -139,9 +142,8 @@
         replaced = changes['replaced'][0]['resource_name']
         self.assertEqual('test1', replaced)
 
-        empty_sections = ('added', 'unchanged', 'updated', 'deleted')
-        for section in empty_sections:
-            self.assertEqual([], changes[section])
+        self.assert_empty_sections(
+            changes, ['added', 'unchanged', 'updated', 'deleted'])
 
     def test_delete_resource(self):
         self.stack_identifier = self.stack_create(
@@ -156,6 +158,141 @@
         deleted = changes['deleted'][0]['resource_name']
         self.assertEqual('test2', deleted)
 
-        empty_sections = ('updated', 'replaced', 'added')
-        for section in empty_sections:
-            self.assertEqual([], changes[section])
+        self.assert_empty_sections(changes, ['updated', 'replaced', 'added'])
+
+
+class UpdatePreviewStackTestNested(UpdatePreviewBase):
+    template_nested_parent = '''
+heat_template_version: 2016-04-08
+resources:
+  nested1:
+    type: nested1.yaml
+'''
+
+    template_nested1 = '''
+heat_template_version: 2016-04-08
+resources:
+  nested2:
+    type: nested2.yaml
+'''
+
+    template_nested2 = '''
+heat_template_version: 2016-04-08
+resources:
+  random:
+    type: OS::Heat::RandomString
+'''
+
+    template_nested2_2 = '''
+heat_template_version: 2016-04-08
+resources:
+  random:
+    type: OS::Heat::RandomString
+  random2:
+    type: OS::Heat::RandomString
+'''
+
+    def _get_by_resource_name(self, changes, name, action):
+        filtered_l = [x for x in changes[action]
+                      if x['resource_name'] == name]
+        self.assertEqual(1, len(filtered_l))
+        return filtered_l[0]
+
+    def test_nested_resources_nochange(self):
+        files = {'nested1.yaml': self.template_nested1,
+                 'nested2.yaml': self.template_nested2}
+        self.stack_identifier = self.stack_create(
+            template=self.template_nested_parent, files=files)
+        result = self.preview_update_stack(
+            self.stack_identifier,
+            template=self.template_nested_parent,
+            files=files, show_nested=True)
+        changes = result['resource_changes']
+
+        # The nested random resource should be unchanged, but we always
+        # update nested stacks even when there are no changes
+        self.assertEqual(1, len(changes['unchanged']))
+        self.assertEqual('random', changes['unchanged'][0]['resource_name'])
+        self.assertEqual('nested2', changes['unchanged'][0]['parent_resource'])
+
+        self.assertEqual(2, len(changes['updated']))
+        u_nested1 = self._get_by_resource_name(changes, 'nested1', 'updated')
+        self.assertNotIn('parent_resource', u_nested1)
+        u_nested2 = self._get_by_resource_name(changes, 'nested2', 'updated')
+        self.assertEqual('nested1', u_nested2['parent_resource'])
+
+        self.assert_empty_sections(changes, ['replaced', 'deleted', 'added'])
+
+    def test_nested_resources_add(self):
+        files = {'nested1.yaml': self.template_nested1,
+                 'nested2.yaml': self.template_nested2}
+        self.stack_identifier = self.stack_create(
+            template=self.template_nested_parent, files=files)
+        files['nested2.yaml'] = self.template_nested2_2
+        result = self.preview_update_stack(
+            self.stack_identifier,
+            template=self.template_nested_parent,
+            files=files, show_nested=True)
+        changes = result['resource_changes']
+
+        # The nested random resource should be unchanged, but we always
+        # update nested stacks even when there are no changes
+        self.assertEqual(1, len(changes['unchanged']))
+        self.assertEqual('random', changes['unchanged'][0]['resource_name'])
+        self.assertEqual('nested2', changes['unchanged'][0]['parent_resource'])
+
+        self.assertEqual(1, len(changes['added']))
+        self.assertEqual('random2', changes['added'][0]['resource_name'])
+        self.assertEqual('nested2', changes['added'][0]['parent_resource'])
+
+        self.assert_empty_sections(changes, ['replaced', 'deleted'])
+
+    def test_nested_resources_delete(self):
+        files = {'nested1.yaml': self.template_nested1,
+                 'nested2.yaml': self.template_nested2_2}
+        self.stack_identifier = self.stack_create(
+            template=self.template_nested_parent, files=files)
+        files['nested2.yaml'] = self.template_nested2
+        result = self.preview_update_stack(
+            self.stack_identifier,
+            template=self.template_nested_parent,
+            files=files, show_nested=True)
+        changes = result['resource_changes']
+
+        # The nested random resource should be unchanged, but we always
+        # update nested stacks even when there are no changes
+        self.assertEqual(1, len(changes['unchanged']))
+        self.assertEqual('random', changes['unchanged'][0]['resource_name'])
+        self.assertEqual('nested2', changes['unchanged'][0]['parent_resource'])
+
+        self.assertEqual(1, len(changes['deleted']))
+        self.assertEqual('random2', changes['deleted'][0]['resource_name'])
+        self.assertEqual('nested2', changes['deleted'][0]['parent_resource'])
+
+        self.assert_empty_sections(changes, ['replaced', 'added'])
+
+    def test_nested_resources_replace(self):
+        files = {'nested1.yaml': self.template_nested1,
+                 'nested2.yaml': self.template_nested2}
+        self.stack_identifier = self.stack_create(
+            template=self.template_nested_parent, files=files)
+        parent_none = self.template_nested_parent.replace(
+            'nested1.yaml', 'OS::Heat::None')
+        result = self.preview_update_stack(
+            self.stack_identifier,
+            template=parent_none,
+            show_nested=True)
+        changes = result['resource_changes']
+
+        # The nested random resource should be unchanged, but we always
+        # update nested stacks even when there are no changes
+        self.assertEqual(1, len(changes['replaced']))
+        self.assertEqual('nested1', changes['replaced'][0]['resource_name'])
+
+        self.assertEqual(2, len(changes['deleted']))
+        d_random = self._get_by_resource_name(changes, 'random', 'deleted')
+        self.assertEqual('nested2', d_random['parent_resource'])
+        d_nested2 = self._get_by_resource_name(changes, 'nested2', 'deleted')
+        self.assertEqual('nested1', d_nested2['parent_resource'])
+
+        self.assert_empty_sections(changes, ['updated', 'unchanged', 'added'])
diff --git a/functional/test_resource_group.py b/functional/test_resource_group.py
index e40376c..1e9edd5 100644
--- a/functional/test_resource_group.py
+++ b/functional/test_resource_group.py
@@ -419,7 +419,7 @@
         super(ResourceGroupAdoptTest, self).setUp()
 
     def _yaml_to_json(self, yaml_templ):
-        return yaml.load(yaml_templ)
+        return yaml.safe_load(yaml_templ)
 
     def test_adopt(self):
         data = {
@@ -455,7 +455,7 @@
                 }
             },
             "environment": {"parameters": {}},
-            "template": yaml.load(self.main_template)
+            "template": yaml.safe_load(self.main_template)
         }
         stack_identifier = self.stack_adopt(
             adopt_data=json.dumps(data))
@@ -556,7 +556,7 @@
         Simple rolling update with no conflict in batch size
         and minimum instances in service.
         """
-        updt_template = yaml.load(copy.deepcopy(self.template))
+        updt_template = yaml.safe_load(copy.deepcopy(self.template))
         grp = updt_template['resources']['random_group']
         policy = grp['update_policy']['rolling_update']
         policy['min_in_service'] = '1'
@@ -575,7 +575,7 @@
         Simple rolling update replace with no conflict in batch size
         and minimum instances in service.
         """
-        updt_template = yaml.load(copy.deepcopy(self.template))
+        updt_template = yaml.safe_load(copy.deepcopy(self.template))
         grp = updt_template['resources']['random_group']
         policy = grp['update_policy']['rolling_update']
         policy['min_in_service'] = '1'
@@ -594,7 +594,7 @@
 
         Simple rolling update with reduced size.
         """
-        updt_template = yaml.load(copy.deepcopy(self.template))
+        updt_template = yaml.safe_load(copy.deepcopy(self.template))
         grp = updt_template['resources']['random_group']
         policy = grp['update_policy']['rolling_update']
         policy['min_in_service'] = '1'
@@ -613,7 +613,7 @@
 
         Simple rolling update with increased size.
         """
-        updt_template = yaml.load(copy.deepcopy(self.template))
+        updt_template = yaml.safe_load(copy.deepcopy(self.template))
         grp = updt_template['resources']['random_group']
         policy = grp['update_policy']['rolling_update']
         policy['min_in_service'] = '1'
@@ -632,7 +632,7 @@
 
         Update  with capacity adjustment with enough resources.
         """
-        updt_template = yaml.load(copy.deepcopy(self.template))
+        updt_template = yaml.safe_load(copy.deepcopy(self.template))
         grp = updt_template['resources']['random_group']
         policy = grp['update_policy']['rolling_update']
         policy['min_in_service'] = '8'
@@ -652,7 +652,7 @@
         Rolling update with capacity adjustment due to conflict in
         batch size and minimum instances in service.
         """
-        updt_template = yaml.load(copy.deepcopy(self.template))
+        updt_template = yaml.safe_load(copy.deepcopy(self.template))
         grp = updt_template['resources']['random_group']
         policy = grp['update_policy']['rolling_update']
         policy['min_in_service'] = '8'
@@ -671,7 +671,7 @@
         Rolling Update with a huge batch size(more than
         current size).
         """
-        updt_template = yaml.load(copy.deepcopy(self.template))
+        updt_template = yaml.safe_load(copy.deepcopy(self.template))
         grp = updt_template['resources']['random_group']
         policy = grp['update_policy']['rolling_update']
         policy['min_in_service'] = '0'
@@ -689,7 +689,7 @@
         Rolling Update with a huge number of minimum instances
         in service.
         """
-        updt_template = yaml.load(copy.deepcopy(self.template))
+        updt_template = yaml.safe_load(copy.deepcopy(self.template))
         grp = updt_template['resources']['random_group']
         policy = grp['update_policy']['rolling_update']
         policy['min_in_service'] = '20'
diff --git a/functional/test_template_resource.py b/functional/test_template_resource.py
index 9249a6e..9a3e833 100644
--- a/functional/test_template_resource.py
+++ b/functional/test_template_resource.py
@@ -602,7 +602,7 @@
         super(TemplateResourceAdoptTest, self).setUp()
 
     def _yaml_to_json(self, yaml_templ):
-        return yaml.load(yaml_templ)
+        return yaml.safe_load(yaml_templ)
 
     def test_abandon(self):
         stack_identifier = self.stack_create(
@@ -635,7 +635,7 @@
                 }
             },
             "environment": {"parameters": {}},
-            "template": yaml.load(self.main_template)
+            "template": yaml.safe_load(self.main_template)
         }
 
         stack_identifier = self.stack_adopt(
@@ -848,3 +848,90 @@
             exp = ('ERROR: Attribute here-it-is for facade '
                    'OS::Thingy missing in provider')
             self.assertEqual(exp, six.text_type(exc))
+
+
+class TemplateResourceNewParamTest(functional_base.FunctionalTestsBase):
+
+    main_template = '''
+heat_template_version: 2013-05-23
+resources:
+  my_resource:
+    type: resource.yaml
+    properties:
+      value1: foo
+'''
+    nested_templ = '''
+heat_template_version: 2013-05-23
+parameters:
+  value1:
+    type: string
+resources:
+  test:
+    type: OS::Heat::TestResource
+    properties:
+      value: {get_param: value1}
+'''
+    main_template_update = '''
+heat_template_version: 2013-05-23
+resources:
+  my_resource:
+    type: resource.yaml
+    properties:
+      value1: foo
+      value2: foo
+'''
+    nested_templ_update_fail = '''
+heat_template_version: 2013-05-23
+parameters:
+  value1:
+    type: string
+  value2:
+    type: string
+resources:
+  test:
+    type: OS::Heat::TestResource
+    properties:
+      fail: True
+      value:
+        str_replace:
+          template: VAL1-VAL2
+          params:
+            VAL1: {get_param: value1}
+            VAL2: {get_param: value2}
+'''
+    nested_templ_update = '''
+heat_template_version: 2013-05-23
+parameters:
+  value1:
+    type: string
+  value2:
+    type: string
+resources:
+  test:
+    type: OS::Heat::TestResource
+    properties:
+      value:
+        str_replace:
+          template: VAL1-VAL2
+          params:
+            VAL1: {get_param: value1}
+            VAL2: {get_param: value2}
+'''
+
+    def test_update(self):
+        stack_identifier = self.stack_create(
+            template=self.main_template,
+            files={'resource.yaml': self.nested_templ})
+
+        # Make the update fails with the new parameter inserted.
+        self.update_stack(
+            stack_identifier,
+            self.main_template_update,
+            files={'resource.yaml': self.nested_templ_update_fail},
+            expected_status='UPDATE_FAILED')
+
+        # Fix the update, it should succeed now.
+        self.update_stack(
+            stack_identifier,
+            self.main_template_update,
+            files={'resource.yaml': self.nested_templ_update})
diff --git a/functional/test_update_restricted.py b/functional/test_update_restricted.py
new file mode 100644
index 0000000..ae1907b
--- /dev/null
+++ b/functional/test_update_restricted.py
@@ -0,0 +1,155 @@
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
+from heat_integrationtests.functional import functional_base
+
+test_template = {
+    'heat_template_version': '2013-05-23',
+    'description': 'Test template to create one instance.',
+    'resources': {
+        'bar': {
+            'type': 'OS::Heat::TestResource',
+            'properties': {
+                'value': '1234',
+                'update_replace': False,
+            }
+        }
+    }
+}
+
+env_both_restrict = {u'resource_registry': {
+    u'resources': {
+        'bar': {'restricted_actions': ['update', 'replace']}
+    }
+}
+}
+
+env_replace_restrict = {u'resource_registry': {
+    u'resources': {
+        '*ar': {'restricted_actions': 'replace'}
+    }
+}
+}
+
+reason_update_restrict = 'update is restricted for resource.'
+reason_replace_restrict = 'replace is restricted for resource.'
+
+
+class UpdateRestrictedStackTest(functional_base.FunctionalTestsBase):
+
+    def _check_for_restriction_reason(self, events,
+                                      reason, num_expected=1):
+        matched = [e for e in events
+                   if e.resource_status_reason == reason]
+        return len(matched) == num_expected
+
+    def test_update(self):
+        stack_identifier = self.stack_create(template=test_template)
+
+        update_template = test_template.copy()
+        props = update_template['resources']['bar']['properties']
+        props['value'] = '4567'
+
+        # check update fails - with 'both' restricted
+        self.update_stack(stack_identifier, update_template,
+                          env_both_restrict,
+                          expected_status='UPDATE_FAILED')
+
+        self.assertTrue(self.verify_resource_status(stack_identifier, 'bar',
+                                                    'CREATE_COMPLETE'))
+        resource_events = self.client.events.list(stack_identifier, 'bar')
+        self.assertTrue(
+            self._check_for_restriction_reason(resource_events,
+                                               reason_update_restrict))
+
+        # check update succeeds - with only 'replace' restricted
+        self.update_stack(stack_identifier, update_template,
+                          env_replace_restrict,
+                          expected_status='UPDATE_COMPLETE')
+
+        self.assertTrue(self.verify_resource_status(stack_identifier, 'bar',
+                                                    'UPDATE_COMPLETE'))
+        resource_events = self.client.events.list(stack_identifier, 'bar')
+        self.assertFalse(
+            self._check_for_restriction_reason(resource_events,
+                                               reason_update_restrict, 2))
+        self.assertTrue(
+            self._check_for_restriction_reason(resource_events,
+                                               reason_replace_restrict, 0))
+
+    def test_replace(self):
+        stack_identifier = self.stack_create(template=test_template)
+
+        update_template = test_template.copy()
+        props = update_template['resources']['bar']['properties']
+        props['update_replace'] = True
+
+        # check replace fails - with 'both' restricted
+        self.update_stack(stack_identifier, update_template,
+                          env_replace_restrict,
+                          expected_status='UPDATE_FAILED')
+
+        self.assertTrue(self.verify_resource_status(stack_identifier, 'bar',
+                                                    'CREATE_COMPLETE'))
+        resource_events = self.client.events.list(stack_identifier, 'bar')
+        self.assertTrue(
+            self._check_for_restriction_reason(resource_events,
+                                               reason_replace_restrict))
+
+        # check replace fails - with only 'replace' restricted
+        self.update_stack(stack_identifier, update_template,
+                          env_replace_restrict,
+                          expected_status='UPDATE_FAILED')
+
+        self.assertTrue(self.verify_resource_status(stack_identifier, 'bar',
+                                                    'CREATE_COMPLETE'))
+        resource_events = self.client.events.list(stack_identifier, 'bar')
+        self.assertTrue(
+            self._check_for_restriction_reason(resource_events,
+                                               reason_replace_restrict, 2))
+        self.assertTrue(
+            self._check_for_restriction_reason(resource_events,
+                                               reason_update_restrict, 0))
+
+    def test_update_type_changed(self):
+        stack_identifier = self.stack_create(template=test_template)
+
+        update_template = test_template.copy()
+        rsrc = update_template['resources']['bar']
+        rsrc['type'] = 'OS::Heat::None'
+
+        # check replace fails - with 'both' restricted
+        self.update_stack(stack_identifier, update_template,
+                          env_both_restrict,
+                          expected_status='UPDATE_FAILED')
+
+        self.assertTrue(self.verify_resource_status(stack_identifier, 'bar',
+                                                    'CREATE_COMPLETE'))
+        resource_events = self.client.events.list(stack_identifier, 'bar')
+        self.assertTrue(
+            self._check_for_restriction_reason(resource_events,
+                                               reason_replace_restrict))
+
+        # check replace fails - with only 'replace' restricted
+        self.update_stack(stack_identifier, update_template,
+                          env_replace_restrict,
+                          expected_status='UPDATE_FAILED')
+
+        self.assertTrue(self.verify_resource_status(stack_identifier, 'bar',
+                                                    'CREATE_COMPLETE'))
+        resource_events = self.client.events.list(stack_identifier, 'bar')
+        self.assertTrue(
+            self._check_for_restriction_reason(resource_events,
+                                               reason_replace_restrict, 2))
+        self.assertTrue(
+            self._check_for_restriction_reason(resource_events,
+                                               reason_update_restrict, 0))
diff --git a/functional/test_waitcondition.py b/functional/test_waitcondition.py
new file mode 100644
index 0000000..fd5399f
--- /dev/null
+++ b/functional/test_waitcondition.py
@@ -0,0 +1,72 @@
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
+import json
+
+from keystoneclient.v3 import client as keystoneclient
+from zaqarclient.queues.v1 import client as zaqarclient
+
+from heat_integrationtests.functional import functional_base
+
+
+class ZaqarWaitConditionTest(functional_base.FunctionalTestsBase):
+    template = '''
+heat_template_version: "2013-05-23"
+
+resources:
+  wait_condition:
+    type: OS::Heat::WaitCondition
+    properties:
+      handle: {get_resource: wait_handle}
+      timeout: 120
+  wait_handle:
+    type: OS::Heat::WaitConditionHandle
+    properties:
+      signal_transport: ZAQAR_SIGNAL
+
+outputs:
+  wait_data:
+   value: {'Fn::Select': ['data_id', {get_attr: [wait_condition, data]}]}
+'''
+
+    def test_signal_queues(self):
+        stack_identifier = self.stack_create(
+            template=self.template,
+            expected_status=None)
+        self._wait_for_resource_status(stack_identifier, 'wait_handle',
+                                       'CREATE_COMPLETE')
+        resource = self.client.resources.get(stack_identifier, 'wait_handle')
+        signal = json.loads(resource.attributes['signal'])
+        ks = keystoneclient.Client(
+            auth_url=signal['auth_url'],
+            user_id=signal['user_id'],
+            password=signal['password'],
+            project_id=signal['project_id'])
+        endpoint = ks.service_catalog.url_for(
+            service_type='messaging', endpoint_type='publicURL')
+        conf = {
+            'auth_opts': {
+                'backend': 'keystone',
+                'options': {
+                    'os_auth_token': ks.auth_token,
+                    'os_project_id': signal['project_id']
+                }
+            }
+        }
+
+        zaqar = zaqarclient.Client(endpoint, conf=conf, version=1.1)
+
+        queue = zaqar.queue(signal['queue_id'])
+        queue.post({'body': {'data': 'here!', 'id': 'data_id'}, 'ttl': 600})
+        self._wait_for_stack_status(stack_identifier, 'CREATE_COMPLETE')
+        stack = self.client.stacks.get(stack_identifier)
+        self.assertEqual('here!', stack.outputs[0]['output_value'])
diff --git a/scenario/test_autoscaling_lb.py b/scenario/test_autoscaling_lb.py
index bba55e1..e3de091 100644
--- a/scenario/test_autoscaling_lb.py
+++ b/scenario/test_autoscaling_lb.py
@@ -38,8 +38,12 @@
         resp = set()
         for count in range(retries):
             time.sleep(1)
-            r = requests.get(url)
-            # skip unsuccessfull requests
+            try:
+                r = requests.get(url)
+            except requests.exceptions.ConnectionError:
+                # The LB may not be up yet, let's retry
+                continue
+            # skip unsuccessful requests
             if r.status_code == 200:
                 resp.add(r.text)
         self.assertEqual(expected_num, len(resp))