Merge "Always commit after deployment db operations"
diff --git a/common/config.py b/common/config.py
index 0de6480..5a33590 100644
--- a/common/config.py
+++ b/common/config.py
@@ -27,7 +27,8 @@
                help="API key to use when authenticating.",
                secret=True),
     cfg.StrOpt('tenant_name',
-               default=os.environ.get('OS_TENANT_NAME'),
+               default=(os.environ.get('OS_PROJECT_NAME') or
+                        os.environ.get('OS_TENANT_NAME')),
                help="Tenant name to use for API requests."),
     cfg.StrOpt('auth_url',
                default=os.environ.get('OS_AUTH_URL'),
@@ -105,6 +106,9 @@
     cfg.BoolOpt('skip_stack_abandon_tests',
                 default=False,
                 help="Skip Stack Abandon Integration tests"),
+    cfg.BoolOpt('skip_notification_tests',
+                default=False,
+                help="Skip Notification Integration tests"),
     cfg.IntOpt('connectivity_timeout',
                default=120,
                help="Timeout in seconds to wait for connectivity to "
diff --git a/common/test.py b/common/test.py
index acfe1ad..584af6d 100644
--- a/common/test.py
+++ b/common/test.py
@@ -175,10 +175,18 @@
                 return net
 
     @staticmethod
-    def _stack_output(stack, output_key):
+    def _stack_output(stack, output_key, validate_errors=True):
         """Return a stack output value for a given key."""
-        return next((o['output_value'] for o in stack.outputs
-                    if o['output_key'] == output_key), None)
+        value = None
+        for o in stack.outputs:
+            if validate_errors and 'output_error' in o:
+                # scan for errors in the stack output.
+                raise ValueError(
+                    'Unexpected output errors in %s : %s' % (
+                        output_key, o['output_error']))
+            if o['output_key'] == output_key:
+                value = o['output_value']
+        return value
 
     def _ping_ip_address(self, ip_address, should_succeed=True):
         cmd = ['ping', '-c1', '-w1', ip_address]
diff --git a/functional/test_encrypted_parameter.py b/functional/test_encrypted_parameter.py
new file mode 100644
index 0000000..c862f17
--- /dev/null
+++ b/functional/test_encrypted_parameter.py
@@ -0,0 +1,48 @@
+#    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.common import test
+
+
+class EncryptedParametersTest(test.HeatIntegrationTest):
+
+    template = '''
+heat_template_version: 2013-05-23
+parameters:
+  foo:
+    type: string
+    description: Parameter with encryption turned on
+    hidden: true
+    default: secret
+outputs:
+  encrypted_foo_param:
+    description: ''
+    value: {get_param: foo}
+'''
+
+    def setUp(self):
+        super(EncryptedParametersTest, self).setUp()
+        self.client = self.orchestration_client
+
+    def test_db_encryption(self):
+        # Create a stack with a non-default value for 'foo' to be encrypted
+        foo_param = 'my_encrypted_foo'
+        stack_identifier = self.stack_create(
+            template=self.template,
+            parameters={'foo': foo_param}
+        )
+        stack = self.client.stacks.get(stack_identifier)
+
+        # Verify the output value for 'foo' parameter
+        for out in stack.outputs:
+            if out['output_key'] == 'encrypted_foo_param':
+                self.assertEqual(foo_param, out['output_value'])
diff --git a/functional/test_notifications.py b/functional/test_notifications.py
index a4c419c..c729a67 100644
--- a/functional/test_notifications.py
+++ b/functional/test_notifications.py
@@ -124,6 +124,8 @@
 
     def setUp(self):
         super(NotificationTest, self).setUp()
+        if self.conf.skip_notification_tests:
+            self.skipTest('Testing Notifications disabled in conf, skipping')
 
         self.client = self.orchestration_client
         self.exchange = kombu.Exchange('heat', 'topic', durable=False)
diff --git a/functional/test_resource_group.py b/functional/test_resource_group.py
index a41f841..8bc9950 100644
--- a/functional/test_resource_group.py
+++ b/functional/test_resource_group.py
@@ -244,14 +244,14 @@
 
         env = {'resource_registry':
                {'My::RandomString': 'OS::Heat::RandomString'}}
-        template_one = self.template.replace("count: 0", "count: 1")
+        template_one = self.template.replace("count: 0", "count: 2")
         stack_identifier = self.stack_create(template=template_one,
                                              environment=env)
         self.assertEqual({u'random_group': u'OS::Heat::ResourceGroup'},
                          self.list_resources(stack_identifier))
 
         initial_nested_ident = self._group_nested_identifier(stack_identifier)
-        self.assertEqual({'0': 'My::RandomString'},
+        self.assertEqual({'0': 'My::RandomString', '1': 'My::RandomString'},
                          self.list_resources(initial_nested_ident))
         # get the output
         stack0 = self.client.stacks.get(stack_identifier)
@@ -301,7 +301,7 @@
         env = {'resource_registry':
                {'My::RandomString': 'my_random.yaml'}}
 
-        template_one = self.template.replace("count: 0", "count: 1")
+        template_one = self.template.replace("count: 0", "count: 2")
         stack_identifier = self.stack_create(template=template_one,
                                              environment=env,
                                              files=files1)
@@ -309,7 +309,7 @@
                          self.list_resources(stack_identifier))
 
         initial_nested_ident = self._group_nested_identifier(stack_identifier)
-        self.assertEqual({'0': 'My::RandomString'},
+        self.assertEqual({'0': 'My::RandomString', '1': 'My::RandomString'},
                          self.list_resources(initial_nested_ident))
         # get the output
         stack0 = self.client.stacks.get(stack_identifier)
@@ -472,3 +472,43 @@
         stack = self.client.stacks.get(stack_identifier)
         self.assertEqual('goopie', self._stack_output(stack, 'test0'))
         self.assertEqual('different', self._stack_output(stack, 'test1'))
+
+
+class ResourceGroupErrorResourceTest(test.HeatIntegrationTest):
+    template = '''
+heat_template_version: "2013-05-23"
+resources:
+  group1:
+    type: OS::Heat::ResourceGroup
+    properties:
+      count: 2
+      resource_def:
+        type: fail.yaml
+'''
+    nested_templ = '''
+heat_template_version: "2013-05-23"
+resources:
+  oops:
+    type: OS::Heat::TestResource
+    properties:
+      fail: true
+      wait_secs: 2
+'''
+
+    def setUp(self):
+        super(ResourceGroupErrorResourceTest, self).setUp()
+        self.client = self.orchestration_client
+
+    def test_fail(self):
+        stack_identifier = self.stack_create(
+            template=self.template,
+            files={'fail.yaml': self.nested_templ},
+            expected_status='CREATE_FAILED',
+            enable_cleanup=False)
+        stack = self.client.stacks.get(stack_identifier)
+
+        self.assertEqual('CREATE_FAILED', stack.stack_status)
+        self.client.stacks.delete(stack_identifier)
+        self._wait_for_stack_status(
+            stack_identifier, 'DELETE_COMPLETE',
+            success_on_not_found=True)
diff --git a/functional/test_swiftsignal_update.py b/functional/test_swiftsignal_update.py
new file mode 100644
index 0000000..0f1c43c
--- /dev/null
+++ b/functional/test_swiftsignal_update.py
@@ -0,0 +1,48 @@
+#    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.common import test
+
+test_template = '''
+heat_template_version: 2014-10-16
+
+resources:
+  signal_handle:
+    type: "OS::Heat::SwiftSignalHandle"
+
+outputs:
+  signal_curl:
+    value: { get_attr: ['signal_handle', 'curl_cli'] }
+    description: Swift signal cURL
+
+  signal_url:
+    value: { get_attr: ['signal_handle', 'endpoint'] }
+    description: Swift signal URL
+'''
+
+
+class SwiftSignalHandleUpdateTest(test.HeatIntegrationTest):
+
+    def setUp(self):
+        super(SwiftSignalHandleUpdateTest, self).setUp()
+        self.client = self.orchestration_client
+
+    def test_stack_update_same_template_replace_no_url(self):
+        stack_identifier = self.stack_create(template=test_template)
+        stack = self.client.stacks.get(stack_identifier)
+        orig_url = self._stack_output(stack, 'signal_url')
+        orig_curl = self._stack_output(stack, 'signal_curl')
+        self.update_stack(stack_identifier, test_template)
+        stack = self.client.stacks.get(stack_identifier)
+        self.assertEqual(orig_url, self._stack_output(stack, 'signal_url'))
+        self.assertEqual(orig_curl, self._stack_output(stack, 'signal_curl'))
diff --git a/functional/test_template_resource.py b/functional/test_template_resource.py
index 0d5734b..00e08b0 100644
--- a/functional/test_template_resource.py
+++ b/functional/test_template_resource.py
@@ -180,6 +180,9 @@
 resources:
   secret1:
     type: OS::Heat::RandomString
+outputs:
+  nested_str:
+    value: {get_attr: [secret1, value]}
 '''
         stack_identifier = self.stack_create(
             template=self.main_templ,
diff --git a/scenario/templates/test_server_cfn_init.yaml b/scenario/templates/test_server_cfn_init.yaml
index ffb8e9b..9f94717 100644
--- a/scenario/templates/test_server_cfn_init.yaml
+++ b/scenario/templates/test_server_cfn_init.yaml
@@ -28,10 +28,16 @@
     Properties:
       UserName: {Ref: CfnUser}
 
-  IPAddress:
+  ElasticIp:
     Type: AWS::EC2::EIP
     Properties:
-      InstanceId: {Ref: SmokeServer}
+      Domain: vpc
+
+  SmokeServerElasticIp:
+    Type: AWS::EC2::EIPAssociation
+    Properties:
+       EIP: {Ref: ElasticIp}
+       InstanceId: {Ref: SmokeServer}
 
   SmokeServer:
     Type: AWS::EC2::Instance
@@ -81,7 +87,11 @@
     Description: Contents of /tmp/smoke-status on SmokeServer
     Value:
       Fn::GetAtt: [WaitCondition, Data]
-  SmokeServerIp:
-    Description: IP address of server
+  ElasticIp_Id:
+    Description: Elastic ip allocation id
     Value:
-      Ref: IPAddress
+      Fn::GetAtt: [ElasticIp, AllocationId]
+  SmokeServerElasticIp:
+    Description: Elastic ip address of server
+    Value:
+      Ref: ElasticIp
diff --git a/scenario/test_neutron_autoscaling.py b/scenario/test_neutron_autoscaling.py
index e7aae19..c81e22d 100644
--- a/scenario/test_neutron_autoscaling.py
+++ b/scenario/test_neutron_autoscaling.py
@@ -20,6 +20,7 @@
 
     def setUp(self):
         super(NeutronAutoscalingTest, self).setUp()
+        raise self.skipException("Skipping until bug #1479869 is fixed.")
         if not self.conf.fixed_subnet_name:
             raise self.skipException("No sub-network configured to test")
         self.template_name = 'test_neutron_autoscaling.yaml'
diff --git a/scenario/test_neutron_loadbalancer.py b/scenario/test_neutron_loadbalancer.py
index d8e0197..a890257 100644
--- a/scenario/test_neutron_loadbalancer.py
+++ b/scenario/test_neutron_loadbalancer.py
@@ -25,6 +25,7 @@
 
     def setUp(self):
         super(NeutronLoadBalancerTest, self).setUp()
+        raise self.skipException("Skipping until bug #1479869 is fixed.")
         self.public_net = self._get_network(self.conf.floating_network_name)
         self.template_name = 'test_neutron_loadbalancer.yaml'
 
diff --git a/scenario/test_server_cfn_init.py b/scenario/test_server_cfn_init.py
index b5b1e67..197e0c0 100644
--- a/scenario/test_server_cfn_init.py
+++ b/scenario/test_server_cfn_init.py
@@ -23,11 +23,12 @@
 
     def setUp(self):
         super(CfnInitIntegrationTest, self).setUp()
+        raise self.skipException("Skipping until bug #1479869 is fixed.")
 
     def check_stack(self, sid):
         # Check status of all resources
         for res in ('WaitHandle', 'SmokeSecurityGroup', 'SmokeKeys',
-                    'CfnUser', 'SmokeServer', 'IPAddress'):
+                    'CfnUser', 'SmokeServer', 'SmokeServerElasticIp'):
             self._wait_for_resource_status(
                 sid, res, 'CREATE_COMPLETE')
 
@@ -59,7 +60,22 @@
             self._stack_output(stack, 'WaitConditionStatus'))
         self.assertEqual('smoke test complete', wait_status['smoke_status'])
 
-        server_ip = self._stack_output(stack, 'SmokeServerIp')
+        # Check EIP attributes.
+        server_floatingip_id = self._stack_output(stack,
+                                                  'ElasticIp_Id')
+        self.assertIsNotNone(server_floatingip_id)
+
+        # Fetch EIP details.
+        net_show = self.network_client.show_floatingip(
+            floatingip=server_floatingip_id)
+        floating_ip = net_show['floatingip']['floating_ip_address']
+        port_id = net_show['floatingip']['port_id']
+
+        # Ensure that EIP was assigned to server.
+        port_show = self.network_client.show_port(port=port_id)
+        self.assertEqual(server.id, port_show['port']['device_id'])
+        server_ip = self._stack_output(stack, 'SmokeServerElasticIp')
+        self.assertEqual(server_ip, floating_ip)
 
         # Check that created server is reachable
         if not self._ping_ip_address(server_ip):