From 7c5aae183aabb3129fc90c60ccdaf5c1f17dd6ba Mon Sep 17 00:00:00 2001 From: Dan Prince Date: Wed, 3 Aug 2016 08:34:25 -0400 Subject: A hook which invokes os-apply-config. The intent is for this element (hook script) to be used in place of the one in tripleo-image-elements which relies on an external signal handling shell script at the end of the os-refresh-config run (99-refresh-completed). This version will run os-apply-config and return a signal immediately. Because it uses the heat-hook mechanisms it also supports a broader set of signal handling capabilities... which 99-refresh-completed doesn't fully support. Change-Id: Ic9402ff93cbc840bec1debcd8881de563d03cbf0 --- .../elements/heat-config-apply-config/README.rst | 14 ++++ .../elements/heat-config-apply-config/element-deps | 1 + .../install.d/50-heat-config-apply-config | 6 ++ .../install.d/hook-apply-config.py | 57 +++++++++++++++ test-requirements.txt | 1 + tests/software_config/test_heat_config.py | 15 +++- tests/software_config/test_hook_apply_config.py | 81 ++++++++++++++++++++++ 7 files changed, 173 insertions(+), 2 deletions(-) create mode 100644 hot/software-config/elements/heat-config-apply-config/README.rst create mode 100644 hot/software-config/elements/heat-config-apply-config/element-deps create mode 100755 hot/software-config/elements/heat-config-apply-config/install.d/50-heat-config-apply-config create mode 100755 hot/software-config/elements/heat-config-apply-config/install.d/hook-apply-config.py create mode 100644 tests/software_config/test_hook_apply_config.py diff --git a/hot/software-config/elements/heat-config-apply-config/README.rst b/hot/software-config/elements/heat-config-apply-config/README.rst new file mode 100644 index 0000000..444d36a --- /dev/null +++ b/hot/software-config/elements/heat-config-apply-config/README.rst @@ -0,0 +1,14 @@ +A hook which invokes os-apply-config. + +The intent is for this element (hook script) to be used in place of the one in +tripleo-image-elements which relies on an external signal handling +shell script at the end of the os-refresh-config run (99-refresh-completed). +This version will run os-apply-config and return a signal immediately. Because +it uses the heat-hook mechanisms it also supports a broader set of signal +handling capabilities... which 99-refresh-completed doesn't fully support. + +It is worth noting that this hook runs os-apply-config against all the +accumulated metadata, not just data supplied to an individual hook. + +To use this hook set group: to 'apply-config' instead of 'os-apply-config' +in your Heat software configuration resources. diff --git a/hot/software-config/elements/heat-config-apply-config/element-deps b/hot/software-config/elements/heat-config-apply-config/element-deps new file mode 100644 index 0000000..31d7aa5 --- /dev/null +++ b/hot/software-config/elements/heat-config-apply-config/element-deps @@ -0,0 +1 @@ +heat-config diff --git a/hot/software-config/elements/heat-config-apply-config/install.d/50-heat-config-apply-config b/hot/software-config/elements/heat-config-apply-config/install.d/50-heat-config-apply-config new file mode 100755 index 0000000..53c3878 --- /dev/null +++ b/hot/software-config/elements/heat-config-apply-config/install.d/50-heat-config-apply-config @@ -0,0 +1,6 @@ +#!/bin/bash +set -x + +SCRIPTDIR=$(dirname $0) + +install -D -g root -o root -m 0755 ${SCRIPTDIR}/hook-apply-config.py /var/lib/heat-config/hooks/apply-config diff --git a/hot/software-config/elements/heat-config-apply-config/install.d/hook-apply-config.py b/hot/software-config/elements/heat-config-apply-config/install.d/hook-apply-config.py new file mode 100755 index 0000000..df29cb2 --- /dev/null +++ b/hot/software-config/elements/heat-config-apply-config/install.d/hook-apply-config.py @@ -0,0 +1,57 @@ +#!/usr/bin/env python +# +# 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 +import logging +import os +import subprocess +import sys + +APPLY_CONFIG_CMD = os.environ.get('HEAT_APPLY_CONFIG_CMD', 'os-apply-config') + + +def main(argv=sys.argv): + log = logging.getLogger('heat-config') + handler = logging.StreamHandler(sys.stderr) + handler.setFormatter( + logging.Formatter( + '[%(asctime)s] (%(name)s) [%(levelname)s] %(message)s')) + log.addHandler(handler) + log.setLevel('DEBUG') + + env = os.environ.copy() + + log.debug('Running %s' % APPLY_CONFIG_CMD) + subproc = subprocess.Popen([APPLY_CONFIG_CMD], stdout=subprocess.PIPE, + stderr=subprocess.PIPE, env=env) + stdout, stderr = subproc.communicate() + + log.info(stdout) + log.debug(stderr) + + if subproc.returncode: + log.error("Error running apply-config: [%s]\n" % subproc.returncode) + else: + log.info('Completed apply-config.') + + response = { + 'deploy_stdout': stdout, + 'deploy_stderr': stderr, + 'deploy_status_code': subproc.returncode, + } + + json.dump(response, sys.stdout) + +if __name__ == '__main__': + sys.exit(main(sys.argv)) diff --git a/test-requirements.txt b/test-requirements.txt index 4ef137f..402c212 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -12,6 +12,7 @@ testrepository>=0.0.18 testscenarios>=0.4 testtools>=0.9.34 yamllint>=1.2.0 +os-apply-config python-heatclient>=1.2.0 python-keystoneclient>=0.10.0 diff --git a/tests/software_config/test_heat_config.py b/tests/software_config/test_heat_config.py index df23bda..2ed37b8 100644 --- a/tests/software_config/test_heat_config.py +++ b/tests/software_config/test_heat_config.py @@ -25,7 +25,8 @@ from tests.software_config import common class HeatConfigTest(common.RunScriptTest): - fake_hooks = ['cfn-init', 'chef', 'puppet', 'salt', 'script'] + fake_hooks = ['cfn-init', 'chef', 'puppet', 'salt', 'script', + 'apply-config'] data = [ { @@ -66,9 +67,14 @@ class HeatConfigTest(common.RunScriptTest): 'config': 'five' }, { 'id': '6666', + 'group': 'apply-config', + 'inputs': [{'name': 'foo', 'value': 'bar'}], + 'config': 'six' + }, { + 'id': '9999', 'group': 'no-such-hook', 'inputs': [], - 'config': 'six' + 'config': 'nine' }] outputs = { @@ -97,6 +103,11 @@ class HeatConfigTest(common.RunScriptTest): 'deploy_status_code': '-1', 'deploy_stderr': 'A bad thing happened', 'deploy_stdout': 'stdout' + }, + 'apply-config': { + 'deploy_status_code': '0', + 'deploy_stderr': 'stderr', + 'deploy_stdout': 'stdout' } } diff --git a/tests/software_config/test_hook_apply_config.py b/tests/software_config/test_hook_apply_config.py new file mode 100644 index 0000000..5aa27d7 --- /dev/null +++ b/tests/software_config/test_hook_apply_config.py @@ -0,0 +1,81 @@ +# +# 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 fixtures +import json +import logging +import os +import tempfile +import yaml + +from tests.software_config import common + +log = logging.getLogger('test_hook_apply_config') + + +class HookApplyConfigTest(common.RunScriptTest): + + data = { + 'id': 'test_apply_config', + 'name': 'fake_resource_name', + 'group': 'apply-config', + 'config': {'foo': 'bar'} + } + + def setUp(self): + super(HookApplyConfigTest, self).setUp() + self.hook_path = self.relative_path( + __file__, + '../..', + 'hot/software-config/elements', + 'heat-config-apply-config/install.d/hook-apply-config.py') + + self.metadata_dir = self.useFixture(fixtures.TempDir()) + self.templates_dir = self.useFixture(fixtures.TempDir()) + tmp_dir = tempfile.NamedTemporaryFile(mode='w', delete=False).name + os.unlink(tmp_dir) + self.tmp_file = os.path.basename(tmp_dir) + self.out_dir = self.templates_dir.join('tmp') + + self.metadata = self.metadata_dir.join(self.tmp_file) + + self.env = os.environ.copy() + self.env.update({ + 'OS_CONFIG_FILES': self.metadata, + 'OS_CONFIG_APPLIER_TEMPLATES': self.templates_dir.join(), + }) + + # our fake metadata file + with open(self.metadata, "w+") as md: + md.write(json.dumps({'foo': 'bar'})) + + # This is our fake template root we use to verify os-apply-config + # works as expected + os.mkdir(self.out_dir) + with open(os.path.join(self.out_dir, self.tmp_file), "w+") as template: + template.write("foo={{foo}}") + + def test_hook(self): + + returncode, stdout, stderr = self.run_cmd( + [self.hook_path], self.env, json.dumps(self.data)) + + self.assertEqual(0, returncode, stderr) + ret = yaml.safe_load(stdout) + self.assertIsNotNone(ret['deploy_stderr']) + self.assertEqual('', ret['deploy_stdout']) + self.assertEqual(0, ret['deploy_status_code']) + f = os.path.join('/tmp', self.tmp_file) + with open(f) as out_file: + self.assertEqual('foo=bar', out_file.read()) + os.unlink(f) -- cgit v1.2.1