Merge "Ignore errors on old properties during update"
diff --git a/common/test.py b/common/test.py
index 8fc5c60..f6889ab 100644
--- a/common/test.py
+++ b/common/test.py
@@ -310,7 +310,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 +368,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,
@@ -439,7 +440,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)
@@ -453,7 +454,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]
@@ -475,7 +477,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 {}
@@ -488,12 +491,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}
@@ -524,8 +528,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 9649a02..13c4278 100644
--- a/functional/test_aws_stack.py
+++ b/functional/test_aws_stack.py
@@ -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_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_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_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_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_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 95930a4..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(
diff --git a/scenario/test_autoscaling_lb.py b/scenario/test_autoscaling_lb.py
index 080f17f..e3de091 100644
--- a/scenario/test_autoscaling_lb.py
+++ b/scenario/test_autoscaling_lb.py
@@ -38,7 +38,11 @@
         resp = set()
         for count in range(retries):
             time.sleep(1)
-            r = requests.get(url)
+            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)