sysv-generator-test.py revision e28aa588f04ace17ca94e9e0667819bea265fbd9
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering# systemd-sysv-generator integration test
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering# (C) 2015 Canonical Ltd.
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering# Author: Martin Pitt <martin.pitt@ubuntu.com>
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering# systemd is free software; you can redistribute it and/or modify it
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering# under the terms of the GNU Lesser General Public License as published by
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering# the Free Software Foundation; either version 2.1 of the License, or
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering# (at your option) any later version.
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering# systemd is distributed in the hope that it will be useful, but
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering# WITHOUT ANY WARRANTY; without even the implied warranty of
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering# Lesser General Public License for more details.
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering# You should have received a copy of the GNU Lesser General Public License
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering# along with systemd; If not, see <http://www.gnu.org/licenses/>.
ec2c5e4398f9d65e5dfe61530f2556224733d1e6Lennart Poetteringsysv_generator = os.path.join(os.environ.get('builddir', '.'), 'systemd-sysv-generator')
ec2c5e4398f9d65e5dfe61530f2556224733d1e6Lennart Poetteringclass SysvGeneratorTest(unittest.TestCase):
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering self.workdir = tempfile.mkdtemp(prefix='sysv-gen-test.')
ec2c5e4398f9d65e5dfe61530f2556224733d1e6Lennart Poettering self.init_d_dir = os.path.join(self.workdir, 'init.d')
818f766b12e025683cf4fed12b3da2a025bb0b31Lennart Poettering self.unit_dir = os.path.join(self.workdir, 'systemd')
818f766b12e025683cf4fed12b3da2a025bb0b31Lennart Poettering self.out_dir = os.path.join(self.workdir, 'output')
547973dea7abd6c124ff6c79fe2bbe322a7314aeLennart Poettering # Helper methods
547973dea7abd6c124ff6c79fe2bbe322a7314aeLennart Poettering def run_generator(self, expect_error=False):
b2b796b8ab5565fbe60b544d2579e2bfca31bf6aLennart Poettering '''Run sysv-generator.
b2b796b8ab5565fbe60b544d2579e2bfca31bf6aLennart Poettering Fail if stderr contains any "Fail", unless expect_error is True.
91adc4db33f69606aabd332813a5d7d5751c859fLennart Poettering Return (stderr, filename -> ConfigParser) pair with ouput to stderr and
91adc4db33f69606aabd332813a5d7d5751c859fLennart Poettering parsed generated units.
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering env['SYSTEMD_SYSVINIT_PATH'] = self.init_d_dir
faa133f3aa7a18f26563dc5d6b95898cb315c37aLennart Poettering env['SYSTEMD_SYSVRCND_PATH'] = self.rcnd_dir
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering [sysv_generator, 'ignored', 'ignored', self.out_dir],
ad867662936a4c7ab2c7116d804c272338801231Lennart Poettering stdout=subprocess.PIPE, stderr=subprocess.PIPE,
63c372cb9df3bee01e3bf8cd7f96f336bddda846Lennart Poettering for service in glob(self.out_dir + '/*.service'):
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering cp.optionxform = lambda o: o # don't lower-case option names
ef9fb66c0b292d3543c16bfce99ad677bef0f401Lennart Poettering def add_sysv(self, fname, keys, enable=False, prio=1):
ec2c5e4398f9d65e5dfe61530f2556224733d1e6Lennart Poettering '''Create a SysV init script with the given keys in the LSB header
ad867662936a4c7ab2c7116d804c272338801231Lennart Poettering There are sensible default values for all fields.
ad867662936a4c7ab2c7116d804c272338801231Lennart Poettering If enable is True, links will be created in the rcN.d dirs. In that
ad867662936a4c7ab2c7116d804c272338801231Lennart Poettering case, the priority can be given with "prio" (default to 1).
78c6a153c47f8d597c827bdcaf8c4e42ac87f738Lennart Poettering Return path of generated script.
8ba9fd9cee0eef572f7b3ed7a8c3ed31160e93d3Lennart Poettering name_without_sh = fname.endswith('.sh') and fname[:-3] or fname
8ba9fd9cee0eef572f7b3ed7a8c3ed31160e93d3Lennart Poettering keys.setdefault('Provides', name_without_sh)
8ba9fd9cee0eef572f7b3ed7a8c3ed31160e93d3Lennart Poettering keys.setdefault('Required-Start', '$local_fs')
8ba9fd9cee0eef572f7b3ed7a8c3ed31160e93d3Lennart Poettering keys.setdefault('Required-Stop', keys['Required-Start'])
78c6a153c47f8d597c827bdcaf8c4e42ac87f738Lennart Poettering keys.setdefault('Default-Start', '2 3 4 5')
78c6a153c47f8d597c827bdcaf8c4e42ac87f738Lennart Poettering keys.setdefault('Short-Description', 'test %s service' %
78c6a153c47f8d597c827bdcaf8c4e42ac87f738Lennart Poettering keys.setdefault('Description', 'long description for test %s service' %
8ba9fd9cee0eef572f7b3ed7a8c3ed31160e93d3Lennart Poettering script = os.path.join(self.init_d_dir, fname)
faa133f3aa7a18f26563dc5d6b95898cb315c37aLennart Poettering f.write('#!/bin/init-d-interpreter\n### BEGIN INIT INFO\n')
8ba9fd9cee0eef572f7b3ed7a8c3ed31160e93d3Lennart Poettering if v is not None:
8ba9fd9cee0eef572f7b3ed7a8c3ed31160e93d3Lennart Poettering f.write('### END INIT INFO\ncode --goes here\n')
8ba9fd9cee0eef572f7b3ed7a8c3ed31160e93d3Lennart Poettering d = os.path.join(self.rcnd_dir, 'rc%s.d' % runlevel)
8ba9fd9cee0eef572f7b3ed7a8c3ed31160e93d3Lennart Poettering os.symlink('../init.d/' + fname, os.path.join(d, prefix + fname))
8ba9fd9cee0eef572f7b3ed7a8c3ed31160e93d3Lennart Poettering def assert_enabled(self, unit, runlevels):
8ba9fd9cee0eef572f7b3ed7a8c3ed31160e93d3Lennart Poettering '''assert that a unit is enabled in precisely the given runlevels'''
ad867662936a4c7ab2c7116d804c272338801231Lennart Poettering # should be enabled
4afd3348c7506dd1d36305b7bcb9feb8952b9d6bLennart Poettering link = os.path.join(self.out_dir, 'runlevel%i.target.wants' % runlevel, 'foo.service')
ad867662936a4c7ab2c7116d804c272338801231Lennart Poettering self.assertEqual(os.path.basename(target), 'foo.service')
45ec7efb6c2560c80dfa752bc9d3733749dc52cbLennart Poettering '''no input files'''
7588460aaf6bd33f6c9bd5645916cfd8a862e9c4Tom Gundersen self.assertEqual(os.listdir(self.out_dir), [])
ad867662936a4c7ab2c7116d804c272338801231Lennart Poettering '''simple service without dependencies, disabled'''
51323288fc628a5cac50914df915545d685b793eLennart Poettering # no enablement links or other stuff
8ba9fd9cee0eef572f7b3ed7a8c3ed31160e93d3Lennart Poettering self.assertEqual(os.listdir(self.out_dir), ['foo.service'])
45ec7efb6c2560c80dfa752bc9d3733749dc52cbLennart Poettering self.assertEqual(s.sections(), ['Unit', 'Service'])
3339cb71d44c5198f9546f113674f06dc7b01a6fLennart Poettering self.assertEqual(s.get('Unit', 'Description'), 'LSB: test foo service')
45ec7efb6c2560c80dfa752bc9d3733749dc52cbLennart Poettering # $local_fs does not need translation, don't expect any dependency
23b298bce75a0d1f4f15f34458af9678b4a30c3aLennart Poettering set(['Documentation', 'SourcePath', 'Description']))
23b298bce75a0d1f4f15f34458af9678b4a30c3aLennart Poettering self.assertEqual(s.get('Service', 'Type'), 'forking')
8ba9fd9cee0eef572f7b3ed7a8c3ed31160e93d3Lennart Poettering init_script = os.path.join(self.init_d_dir, 'foo')
2d4c5cbc0ed3ccb09dc086a040088b454c22c644Lennart Poettering self.assertEqual(s.get('Service', 'ExecStart'),
3339cb71d44c5198f9546f113674f06dc7b01a6fLennart Poettering self.assertEqual(s.get('Service', 'ExecStop'),
3339cb71d44c5198f9546f113674f06dc7b01a6fLennart Poettering '''simple service without dependencies, enabled in all runlevels'''
309e9d86f0e7f9c5f0a2a09227bdfdb3174d4436Lennart Poettering self.assertEqual(list(results), ['foo.service'])
3339cb71d44c5198f9546f113674f06dc7b01a6fLennart Poettering self.assert_enabled('foo.service', [2, 3, 4, 5])
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering '''simple service without dependencies, enabled in some runlevels'''
23b298bce75a0d1f4f15f34458af9678b4a30c3aLennart Poettering self.add_sysv('foo', {'Default-Start': '2 4'}, enable=True)
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering self.assertEqual(list(results), ['foo.service'])
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering self.assert_enabled('foo.service', [2, 4])
ad867662936a4c7ab2c7116d804c272338801231Lennart Poettering '''single LSB macro dependency: $network'''
45ec7efb6c2560c80dfa752bc9d3733749dc52cbLennart Poettering self.add_sysv('foo', {'Required-Start': '$network'})
309e9d86f0e7f9c5f0a2a09227bdfdb3174d4436Lennart Poettering s = self.run_generator()[1]['foo.service']
45ec7efb6c2560c80dfa752bc9d3733749dc52cbLennart Poettering set(['Documentation', 'SourcePath', 'Description', 'After', 'Wants']))
45ec7efb6c2560c80dfa752bc9d3733749dc52cbLennart Poettering self.assertEqual(s.get('Unit', 'After'), 'network-online.target')
931851e8e492a4d2715e22dcde50a5e7ccef4b49Lennart Poettering self.assertEqual(s.get('Unit', 'Wants'), 'network-online.target')
309e9d86f0e7f9c5f0a2a09227bdfdb3174d4436Lennart Poettering '''multiple LSB macro dependencies'''
ad867662936a4c7ab2c7116d804c272338801231Lennart Poettering self.add_sysv('foo', {'Required-Start': '$named $portmap'})
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering s = self.run_generator()[1]['foo.service']
da927ba997d68401563b927f92e6e40e021a8e5cMichal Schmidt set(['Documentation', 'SourcePath', 'Description', 'After']))
45ec7efb6c2560c80dfa752bc9d3733749dc52cbLennart Poettering self.assertEqual(s.get('Unit', 'After'), 'nss-lookup.target rpcbind.target')
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering '''LSB header dependencies to other services'''
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering # also give symlink priorities here; they should be ignored
45ec7efb6c2560c80dfa752bc9d3733749dc52cbLennart Poettering self.add_sysv('foo', {'Required-Start': 'must1 must2',
51323288fc628a5cac50914df915545d685b793eLennart Poettering self.add_sysv('must1', {}, enable=True, prio=10)
51323288fc628a5cac50914df915545d685b793eLennart Poettering self.add_sysv('must2', {}, enable=True, prio=15)
51323288fc628a5cac50914df915545d685b793eLennart Poettering self.add_sysv('may1', {}, enable=True, prio=20)
45ec7efb6c2560c80dfa752bc9d3733749dc52cbLennart Poettering # do not create ne_may2
45ec7efb6c2560c80dfa752bc9d3733749dc52cbLennart Poettering ['foo.service', 'may1.service', 'must1.service', 'must2.service'])
51323288fc628a5cac50914df915545d685b793eLennart Poettering # foo should depend on all of them
51323288fc628a5cac50914df915545d685b793eLennart Poettering self.assertEqual(sorted(results['foo.service'].get('Unit', 'After').split()),
51323288fc628a5cac50914df915545d685b793eLennart Poettering ['may1.service', 'must1.service', 'must2.service', 'ne_may2.service'])
190700621f95160d364f8ec1d3e360246c41ce75Lennart Poettering # other services should not depend on each other
23b298bce75a0d1f4f15f34458af9678b4a30c3aLennart Poettering self.assertFalse(results['must1.service'].has_option('Unit', 'After'))
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering self.assertFalse(results['must2.service'].has_option('Unit', 'After'))
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering self.assertFalse(results['may1.service'].has_option('Unit', 'After'))
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering '''script without LSB headers use rcN.d priority'''
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering # create two init.d scripts without LSB header and enable them with
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering # startup priorities
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering for prio, name in [(10, 'provider'), (15, 'consumer')]:
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering with open(os.path.join(self.init_d_dir, name), 'w') as f:
45ec7efb6c2560c80dfa752bc9d3733749dc52cbLennart Poettering f.write('#!/bin/init-d-interpreter\ncode --goes here\n')
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering os.symlink('../init.d/' + name, os.path.join(d, 'S%02i%s' % (prio, name)))
45ec7efb6c2560c80dfa752bc9d3733749dc52cbLennart Poettering self.assertEqual(sorted(results), ['consumer.service', 'provider.service'])
7b9f7afcc04e80b77a2567b0750aa2cd03c1a1cdLennart Poettering self.assertFalse(results['provider.service'].has_option('Unit', 'After'))
45ec7efb6c2560c80dfa752bc9d3733749dc52cbLennart Poettering self.assertEqual(results['consumer.service'].get('Unit', 'After'),
45ec7efb6c2560c80dfa752bc9d3733749dc52cbLennart Poettering 'provider.service')
801ad6a6a9cd8fbd58b9f9c27f20dbb3c87d47ddLennart Poettering '''multiple Provides: names'''
51323288fc628a5cac50914df915545d685b793eLennart Poettering self.add_sysv('foo', {'Provides': 'foo bar baz'})
51323288fc628a5cac50914df915545d685b793eLennart Poettering s = self.run_generator()[1]['foo.service']
23b298bce75a0d1f4f15f34458af9678b4a30c3aLennart Poettering set(['Documentation', 'SourcePath', 'Description']))
23b298bce75a0d1f4f15f34458af9678b4a30c3aLennart Poettering # should create symlinks for the alternative names
23b298bce75a0d1f4f15f34458af9678b4a30c3aLennart Poettering self.assertEqual(os.readlink(os.path.join(self.out_dir, f)),
45ec7efb6c2560c80dfa752bc9d3733749dc52cbLennart Poettering 'foo.service')
23b298bce75a0d1f4f15f34458af9678b4a30c3aLennart Poettering '''ignores non-executable init.d script'''
74b2466e14a1961bf3ac0e8a60cfaceec705bd59Lennart Poettering os.chmod(self.add_sysv('foo', {}), 0o644)