c002854fa4
Deployments should not be run again when a server is rebooted because heat will likely not be listening for the deployment signals. This change moves the deployed directory from /var/run/heat-config/deployed to /var/lib/heat-config/deployed so that deployed state is persisted across reboots. There is migration logic to move the existing files from the old to the new location, this will only be run on the first run of 55-heat-config after its package is updated. Change-Id: I3d305a4ac5b68c29037760682d37e5b9a530828e Closes-Bug: #1513220
242 lines
8.0 KiB
Python
242 lines
8.0 KiB
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 copy
|
|
import json
|
|
import os
|
|
import shutil
|
|
import tempfile
|
|
|
|
import fixtures
|
|
from testtools import matchers
|
|
|
|
from tests.software_config import common
|
|
|
|
|
|
class HeatConfigTest(common.RunScriptTest):
|
|
|
|
fake_hooks = ['cfn-init', 'chef', 'puppet', 'salt', 'script']
|
|
|
|
data = [
|
|
{
|
|
'id': '1111',
|
|
'group': 'chef',
|
|
'inputs': [{
|
|
'name': 'deploy_signal_id',
|
|
'value': 'mock://192.0.2.2/foo'
|
|
}],
|
|
'config': 'one'
|
|
}, {
|
|
'id': '2222',
|
|
'group': 'cfn-init',
|
|
'inputs': [],
|
|
'config': 'two'
|
|
}, {
|
|
'id': '3333',
|
|
'group': 'salt',
|
|
'inputs': [{'name': 'foo', 'value': 'bar'}],
|
|
'outputs': [{'name': 'foo'}],
|
|
'config': 'three'
|
|
}, {
|
|
'id': '4444',
|
|
'group': 'puppet',
|
|
'inputs': [],
|
|
'config': 'four'
|
|
}, {
|
|
'id': '5555',
|
|
'group': 'script',
|
|
'inputs': [{
|
|
'name': 'deploy_status_code', 'value': '-1'
|
|
}, {
|
|
'name': 'deploy_stderr', 'value': 'A bad thing happened'
|
|
}, {
|
|
'name': 'deploy_signal_id',
|
|
'value': 'mock://192.0.2.3/foo'
|
|
}],
|
|
'config': 'five'
|
|
}, {
|
|
'id': '6666',
|
|
'group': 'no-such-hook',
|
|
'inputs': [],
|
|
'config': 'six'
|
|
}]
|
|
|
|
outputs = {
|
|
'chef': {
|
|
'deploy_status_code': '0',
|
|
'deploy_stderr': 'stderr',
|
|
'deploy_stdout': 'stdout'
|
|
},
|
|
'cfn-init': {
|
|
'deploy_status_code': '0',
|
|
'deploy_stderr': 'stderr',
|
|
'deploy_stdout': 'stdout'
|
|
},
|
|
'salt': {
|
|
'deploy_status_code': '0',
|
|
'deploy_stderr': 'stderr',
|
|
'deploy_stdout': 'stdout',
|
|
'foo': 'bar'
|
|
},
|
|
'puppet': {
|
|
'deploy_status_code': '0',
|
|
'deploy_stderr': 'stderr',
|
|
'deploy_stdout': 'stdout'
|
|
},
|
|
'script': {
|
|
'deploy_status_code': '-1',
|
|
'deploy_stderr': 'A bad thing happened',
|
|
'deploy_stdout': 'stdout'
|
|
}
|
|
}
|
|
|
|
def setUp(self):
|
|
super(HeatConfigTest, self).setUp()
|
|
|
|
self.fake_hook_path = self.relative_path(__file__, 'hook-fake.py')
|
|
|
|
self.heat_config_path = self.relative_path(
|
|
__file__,
|
|
'../..',
|
|
'hot/software-config/elements',
|
|
'heat-config/os-refresh-config/configure.d/55-heat-config')
|
|
|
|
self.hooks_dir = self.useFixture(fixtures.TempDir())
|
|
self.deployed_dir = self.useFixture(fixtures.TempDir())
|
|
|
|
with open(self.fake_hook_path) as f:
|
|
fake_hook = f.read()
|
|
|
|
for hook in self.fake_hooks:
|
|
hook_name = self.hooks_dir.join(hook)
|
|
with open(hook_name, 'w') as f:
|
|
os.utime(hook_name, None)
|
|
f.write(fake_hook)
|
|
f.flush()
|
|
os.chmod(hook_name, 0o755)
|
|
self.env = os.environ.copy()
|
|
|
|
def write_config_file(self, data):
|
|
config_file = tempfile.NamedTemporaryFile()
|
|
config_file.write(json.dumps(data))
|
|
config_file.flush()
|
|
return config_file
|
|
|
|
def run_heat_config(self, data):
|
|
with self.write_config_file(data) as config_file:
|
|
|
|
self.env.update({
|
|
'HEAT_CONFIG_HOOKS': self.hooks_dir.join(),
|
|
'HEAT_CONFIG_DEPLOYED': self.deployed_dir.join(),
|
|
'HEAT_SHELL_CONFIG': config_file.name
|
|
})
|
|
returncode, stdout, stderr = self.run_cmd(
|
|
[self.heat_config_path], self.env)
|
|
|
|
self.assertEqual(0, returncode, stderr)
|
|
|
|
def test_hooks_exist(self):
|
|
self.assertThat(
|
|
self.hooks_dir.join('no-such-hook'),
|
|
matchers.Not(matchers.FileExists()))
|
|
|
|
for hook in self.fake_hooks:
|
|
hook_path = self.hooks_dir.join(hook)
|
|
self.assertThat(hook_path, matchers.FileExists())
|
|
|
|
def test_run_heat_config(self):
|
|
|
|
self.run_heat_config(self.data)
|
|
|
|
for config in self.data:
|
|
hook = config['group']
|
|
stdin_path = self.hooks_dir.join('%s.stdin' % hook)
|
|
stdout_path = self.hooks_dir.join('%s.stdout' % hook)
|
|
deployed_file = self.deployed_dir.join('%s.json' % config['id'])
|
|
|
|
if hook == 'no-such-hook':
|
|
self.assertThat(
|
|
stdin_path, matchers.Not(matchers.FileExists()))
|
|
self.assertThat(
|
|
stdout_path, matchers.Not(matchers.FileExists()))
|
|
continue
|
|
|
|
self.assertThat(stdin_path, matchers.FileExists())
|
|
self.assertThat(stdout_path, matchers.FileExists())
|
|
|
|
# parsed stdin should match the config item
|
|
self.assertEqual(config,
|
|
self.json_from_file(stdin_path))
|
|
|
|
# parsed stdin should match the written deployed file
|
|
self.assertEqual(config,
|
|
self.json_from_file(deployed_file))
|
|
|
|
self.assertEqual(self.outputs[hook],
|
|
self.json_from_file(stdout_path))
|
|
|
|
# clean up files in preperation for second run
|
|
os.remove(stdin_path)
|
|
os.remove(stdout_path)
|
|
|
|
# run again with no changes, assert no new files
|
|
self.run_heat_config(self.data)
|
|
for config in self.data:
|
|
hook = config['group']
|
|
stdin_path = self.hooks_dir.join('%s.stdin' % hook)
|
|
stdout_path = self.hooks_dir.join('%s.stdout' % hook)
|
|
|
|
self.assertThat(
|
|
stdin_path, matchers.Not(matchers.FileExists()))
|
|
self.assertThat(
|
|
stdout_path, matchers.Not(matchers.FileExists()))
|
|
|
|
# run again changing the puppet config
|
|
data = copy.deepcopy(self.data)
|
|
for config in data:
|
|
if config['id'] == '4444':
|
|
config['id'] = '44444444'
|
|
self.run_heat_config(data)
|
|
for config in self.data:
|
|
hook = config['group']
|
|
stdin_path = self.hooks_dir.join('%s.stdin' % hook)
|
|
stdout_path = self.hooks_dir.join('%s.stdout' % hook)
|
|
|
|
if hook == 'puppet':
|
|
self.assertThat(stdin_path, matchers.FileExists())
|
|
self.assertThat(stdout_path, matchers.FileExists())
|
|
else:
|
|
self.assertThat(
|
|
stdin_path, matchers.Not(matchers.FileExists()))
|
|
self.assertThat(
|
|
stdout_path, matchers.Not(matchers.FileExists()))
|
|
|
|
# run again with a different deployed_dir
|
|
old_deployed_dir = self.deployed_dir
|
|
self.env['HEAT_CONFIG_DEPLOYED_OLD'] = old_deployed_dir.join()
|
|
self.deployed_dir = self.useFixture(fixtures.TempDir())
|
|
# make sure the new deployed_dir doesn't exist to trigger the migration
|
|
shutil.rmtree(self.deployed_dir.join())
|
|
|
|
self.run_heat_config(data)
|
|
for config in self.data:
|
|
hook = config['group']
|
|
if hook == 'no-such-hook':
|
|
continue
|
|
deployed_file = self.deployed_dir.join('%s.json' % config['id'])
|
|
old_deployed_file = old_deployed_dir.join('%s.json' % config['id'])
|
|
self.assertEqual(config,
|
|
self.json_from_file(deployed_file))
|
|
self.assertThat(
|
|
old_deployed_file, matchers.Not(matchers.FileExists()))
|