Integration test for software-config tools
This test currently exercises the following hooks:
- script
- puppet
- cfn-init
This requires devstack building a custom image. Since gating doesn't
yet have a test image available the test is skipped by default via
config value skip_software_config_tests.
To run this test locally, build your own custom image and set
skip_software_config_tests=false in
heat_integrationtests/heat_integrationtests.conf
Change-Id: I9d27664638de95e52bc954e1fa00299e6711de90
diff --git a/common/config.py b/common/config.py
index ec7df2e..d4d41b0 100644
--- a/common/config.py
+++ b/common/config.py
@@ -85,6 +85,9 @@
cfg.IntOpt('tenant_network_mask_bits',
default=28,
help="The mask bits for tenant ipv4 subnets"),
+ cfg.BoolOpt('skip_software_config_tests',
+ default=True,
+ help="Skip software config deployment tests"),
cfg.IntOpt('volume_size',
default=1,
help='Default size in GB for volumes created by volumes tests'),
diff --git a/common/test.py b/common/test.py
index c8bce8b..02f7f8a 100644
--- a/common/test.py
+++ b/common/test.py
@@ -311,7 +311,8 @@
stack = self.client.stacks.get(name)
stack_identifier = '%s/%s' % (name, stack.id)
- self._wait_for_stack_status(stack_identifier, expected_status)
+ if expected_status:
+ self._wait_for_stack_status(stack_identifier, expected_status)
return stack_identifier
def stack_adopt(self, stack_name=None, files=None,
diff --git a/scenario/templates/test_server_software_config.yaml b/scenario/templates/test_server_software_config.yaml
new file mode 100644
index 0000000..e6ecae4
--- /dev/null
+++ b/scenario/templates/test_server_software_config.yaml
@@ -0,0 +1,174 @@
+heat_template_version: 2014-10-16
+parameters:
+ key_name:
+ type: string
+ flavor:
+ type: string
+ image:
+ type: string
+ network:
+ type: string
+ signal_transport:
+ type: string
+ default: CFN_SIGNAL
+ dep1_foo:
+ default: fooooo
+ type: string
+ dep1_bar:
+ default: baaaaa
+ type: string
+ dep2a_bar:
+ type: string
+ default: barrr
+ dep3_foo:
+ default: fo
+ type: string
+ dep3_bar:
+ default: ba
+ type: string
+
+resources:
+
+ the_sg:
+ type: OS::Neutron::SecurityGroup
+ properties:
+ name: the_sg
+ description: Ping and SSH
+ rules:
+ - protocol: icmp
+ - protocol: tcp
+ port_range_min: 22
+ port_range_max: 22
+
+ cfg1:
+ type: OS::Heat::SoftwareConfig
+ properties:
+ group: script
+ inputs:
+ - name: foo
+ - name: bar
+ outputs:
+ - name: result
+ config: {get_file: cfg1.sh}
+
+ cfg2a:
+ type: OS::Heat::StructuredConfig
+ properties:
+ group: cfn-init
+ inputs:
+ - name: bar
+ config:
+ config:
+ files:
+ /tmp/cfn-init-foo:
+ content:
+ get_input: bar
+ mode: '000644'
+
+ cfg2b:
+ type: OS::Heat::SoftwareConfig
+ properties:
+ group: script
+ outputs:
+ - name: result
+ config: |
+ #!/bin/sh
+ echo -n "The file /tmp/cfn-init-foo contains `cat /tmp/cfn-init-foo` for server $deploy_server_id during $deploy_action" > $heat_outputs_path.result
+
+ cfg3:
+ type: OS::Heat::SoftwareConfig
+ properties:
+ group: puppet
+ inputs:
+ - name: foo
+ - name: bar
+ outputs:
+ - name: result
+ config: {get_file: cfg3.pp}
+
+ dep1:
+ type: OS::Heat::SoftwareDeployment
+ properties:
+ config:
+ get_resource: cfg1
+ server:
+ get_resource: server
+ input_values:
+ foo: {get_param: dep1_foo}
+ bar: {get_param: dep1_bar}
+ signal_transport: {get_param: signal_transport}
+
+ dep2a:
+ type: OS::Heat::StructuredDeployment
+ properties:
+ name: 10_dep2a
+ signal_transport: NO_SIGNAL
+ config:
+ get_resource: cfg2a
+ server:
+ get_resource: server
+ input_values:
+ bar: {get_param: dep2a_bar}
+
+ dep2b:
+ type: OS::Heat::SoftwareDeployment
+ properties:
+ name: 20_dep2b
+ config:
+ get_resource: cfg2b
+ server:
+ get_resource: server
+ signal_transport: {get_param: signal_transport}
+
+ dep3:
+ type: OS::Heat::SoftwareDeployment
+ properties:
+ config:
+ get_resource: cfg3
+ server:
+ get_resource: server
+ input_values:
+ foo: {get_param: dep3_foo}
+ bar: {get_param: dep3_bar}
+ signal_transport: {get_param: signal_transport}
+
+ cfg_user_data:
+ type: OS::Heat::SoftwareConfig
+ properties:
+ config: |
+ #!/bin/sh
+ echo "user data script"
+
+ server:
+ type: OS::Nova::Server
+ properties:
+ image: {get_param: image}
+ flavor: {get_param: flavor}
+ key_name: {get_param: key_name}
+ security_groups:
+ - {get_resource: the_sg}
+ networks:
+ - network: {get_param: network}
+ user_data_format: SOFTWARE_CONFIG
+ software_config_transport: POLL_TEMP_URL
+ user_data: {get_resource: cfg_user_data}
+
+outputs:
+ res1:
+ value:
+ result: {get_attr: [dep1, result]}
+ stdout: {get_attr: [dep1, deploy_stdout]}
+ stderr: {get_attr: [dep1, deploy_stderr]}
+ status_code: {get_attr: [dep1, deploy_status_code]}
+ res2:
+ value:
+ result: {get_attr: [dep2b, result]}
+ stdout: {get_attr: [dep2b, deploy_stdout]}
+ stderr: {get_attr: [dep2b, deploy_stderr]}
+ status_code: {get_attr: [dep2b, deploy_status_code]}
+ res3:
+ value:
+ result: {get_attr: [dep3, result]}
+ stdout: {get_attr: [dep3, deploy_stdout]}
+ stderr: {get_attr: [dep3, deploy_stderr]}
+ status_code: {get_attr: [dep3, deploy_status_code]}
diff --git a/scenario/test_server_software_config.py b/scenario/test_server_software_config.py
new file mode 100644
index 0000000..bd5d18b
--- /dev/null
+++ b/scenario/test_server_software_config.py
@@ -0,0 +1,158 @@
+# 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 six
+
+from heat_integrationtests.common import exceptions
+from heat_integrationtests.common import test
+
+CFG1_SH = '''#!/bin/sh
+echo "Writing to /tmp/$bar"
+echo $foo > /tmp/$bar
+echo -n "The file /tmp/$bar contains `cat /tmp/$bar` for server \
+$deploy_server_id during $deploy_action" > $heat_outputs_path.result
+echo "Written to /tmp/$bar"
+echo "Output to stderr" 1>&2
+'''
+
+CFG3_PP = '''file {'barfile':
+ ensure => file,
+ mode => 0644,
+ path => "/tmp/$::bar",
+ content => "$::foo",
+}
+file {'output_result':
+ ensure => file,
+ path => "$::heat_outputs_path.result",
+ mode => 0644,
+ content => "The file /tmp/$::bar contains $::foo for server \
+$::deploy_server_id during $::deploy_action",
+}'''
+
+
+class SoftwareConfigIntegrationTest(test.HeatIntegrationTest):
+
+ def setUp(self):
+ super(SoftwareConfigIntegrationTest, self).setUp()
+ if self.conf.skip_software_config_tests:
+ self.skipTest('Testing software config disabled in conf, '
+ 'skipping')
+ self.client = self.orchestration_client
+ self.template_name = 'test_server_software_config.yaml'
+ self.sub_dir = 'templates'
+ self.stack_name = self._stack_rand_name()
+ self.maxDiff = None
+
+ def launch_stack(self):
+ net = self._get_default_network()
+ self.parameters = {
+ 'key_name': self.keypair_name,
+ 'flavor': self.conf.instance_type,
+ 'image': self.conf.image_ref,
+ 'network': net['id']
+ }
+
+ # create the stack
+ self.template = self._load_template(__file__, self.template_name,
+ self.sub_dir)
+ self.stack_create(
+ stack_name=self.stack_name,
+ template=self.template,
+ parameters=self.parameters,
+ files={
+ 'cfg1.sh': CFG1_SH,
+ 'cfg3.pp': CFG3_PP
+ },
+ expected_status=None)
+
+ self.stack = self.client.stacks.get(self.stack_name)
+ self.stack_identifier = '%s/%s' % (self.stack_name, self.stack.id)
+
+ def check_stack(self):
+ sid = self.stack_identifier
+ for res in ('cfg2a', 'cfg2b', 'cfg1', 'cfg3', 'server'):
+ self._wait_for_resource_status(
+ sid, res, 'CREATE_COMPLETE')
+
+ server_resource = self.client.resources.get(sid, 'server')
+ server_id = server_resource.physical_resource_id
+ server = self.compute_client.servers.get(server_id)
+
+ try:
+ # wait for each deployment to contribute their
+ # config to resource
+ for res in ('dep2b', 'dep1', 'dep3'):
+ self._wait_for_resource_status(
+ sid, res, 'CREATE_IN_PROGRESS')
+
+ server_metadata = self.client.resources.metadata(sid, 'server')
+ deployments = dict((d['name'], d) for d in
+ server_metadata['deployments'])
+
+ for res in ('dep2a', 'dep2b', 'dep1', 'dep3'):
+ self._wait_for_resource_status(
+ sid, res, 'CREATE_COMPLETE')
+ except (exceptions.StackResourceBuildErrorException,
+ exceptions.TimeoutException) as e:
+ self._log_console_output(servers=[server])
+ raise e
+
+ self._wait_for_stack_status(sid, 'CREATE_COMPLETE')
+
+ complete_server_metadata = self.client.resources.metadata(
+ sid, 'server')
+ # ensure any previously available deployments haven't changed so
+ # config isn't re-triggered
+ complete_deployments = dict((d['name'], d) for d in
+ complete_server_metadata['deployments'])
+ for k, v in six.iteritems(deployments):
+ self.assertEqual(v, complete_deployments[k])
+
+ stack = self.client.stacks.get(sid)
+
+ res1 = self._stack_output(stack, 'res1')
+ self.assertEqual(
+ 'The file %s contains %s for server %s during %s' % (
+ '/tmp/baaaaa', 'fooooo', server_id, 'CREATE'),
+ res1['result'])
+ self.assertEqual(0, res1['status_code'])
+ self.assertEqual('Output to stderr\n', res1['stderr'])
+ self.assertTrue(len(res1['stdout']) > 0)
+
+ res2 = self._stack_output(stack, 'res2')
+ self.assertEqual(
+ 'The file %s contains %s for server %s during %s' % (
+ '/tmp/cfn-init-foo', 'barrr', server_id, 'CREATE'),
+ res2['result'])
+ self.assertEqual(0, res2['status_code'])
+ self.assertEqual('', res2['stderr'])
+ self.assertEqual('', res2['stdout'])
+
+ res3 = self._stack_output(stack, 'res3')
+ self.assertEqual(
+ 'The file %s contains %s for server %s during %s' % (
+ '/tmp/ba', 'fo', server_id, 'CREATE'),
+ res3['result'])
+ self.assertEqual(0, res3['status_code'])
+ self.assertEqual('', res3['stderr'])
+ self.assertTrue(len(res1['stdout']) > 0)
+
+ dep1_resource = self.client.resources.get(sid, 'dep1')
+ dep1_id = dep1_resource.physical_resource_id
+ dep1_dep = self.client.software_deployments.get(dep1_id)
+ self.assertIsNotNone(dep1_dep.updated_time)
+ self.assertNotEqual(dep1_dep.updated_time, dep1_dep.creation_time)
+
+ def test_server_software_config(self):
+ self.assign_keypair()
+ self.launch_stack()
+ self.check_stack()