Merge ~chad.smith/cloud-init:config-modules-allow-distros-all into cloud-init:master

Proposed by Chad Smith
Status: Merged
Merged at revision: f761f2b5f58c8cf13cfee63619f32046216cf66a
Proposed branch: ~chad.smith/cloud-init:config-modules-allow-distros-all
Merge into: cloud-init:master
Diff against target: 256 lines (+131/-34)
4 files modified
cloudinit/config/cc_runcmd.py (+2/-1)
cloudinit/distros/__init__.py (+4/-0)
cloudinit/stages.py (+20/-13)
tests/unittests/test_runs/test_simple_run.py (+105/-20)
Reviewer Review Type Date Requested Status
Ryan Harper Approve
Server Team CI bot continuous-integration Approve
Review via email: mp+330384@code.launchpad.net

Commit message

cloud-config modules: honor distros definitions in each module

Modules can optionally define a list of supported distros on which they can run by declaring a distros attribute in the cc_*py module. This branch fixes handling of cloudinit.stages.Modules.run_section. The behavior of run_section is now the following:
 - always run a module if the module doesn't declare a distros attribute
 - always run a module if the module declares distros = [ALL_DISTROS]
 - skip a module if the distribution on which we run isn't in module.distros
 - force a run of a skipped module if unverified_modules configuration contains the module name

LP: #1715738
LP: #1715690

Description of the change

cloud-config modules: honor distros definitions in each module

Modules can optionally define a list of supported distros on which they can run by declaring a distros attribute in the cc_*py module. This branch fixes handling of cloudinit.stages.Modules.run_section. The behavior of run_section is now the following:
 - always run a module if the module doesn't declare a distros attribute
 - always run a module if the module declares distros = [CONFIG_MODULE_SUPPORT_ALL_DISTROS]
 - skip a module if the distribution on which we run isn't in module.distros
 - force a run of a skipped module if unverified_modules configuration contains the module name

LP: #1715738
LP: #1715690

To post a comment you must log in.
Revision history for this message
Server Team CI bot (server-team-bot) wrote :

PASSED: Continuous integration, rev:547c29e3b8a05d769046bd4575984bfb9d972434
https://jenkins.ubuntu.com/server/job/cloud-init-ci/271/
Executed test runs:
    SUCCESS: Checkout
    SUCCESS: Unit & Style Tests
    SUCCESS: Ubuntu LTS: Build
    SUCCESS: Ubuntu LTS: Integration
    SUCCESS: MAAS Compatability Testing
    IN_PROGRESS: Declarative: Post Actions

Click here to trigger a rebuild:
https://jenkins.ubuntu.com/server/job/cloud-init-ci/271/rebuild

review: Approve (continuous-integration)
95560e3... by Chad Smith

drop patchOS patchUtils from test_none_ds_forces_run_via_unverified_modules as it is done in setUp

Revision history for this message
Server Team CI bot (server-team-bot) wrote :

PASSED: Continuous integration, rev:f847f5b869e5eed51bc0502ab98d8fec7cd322b2
https://jenkins.ubuntu.com/server/job/cloud-init-ci/272/
Executed test runs:
    SUCCESS: Checkout
    SUCCESS: Unit & Style Tests
    SUCCESS: Ubuntu LTS: Build
    SUCCESS: Ubuntu LTS: Integration
    SUCCESS: MAAS Compatability Testing
    IN_PROGRESS: Declarative: Post Actions

Click here to trigger a rebuild:
https://jenkins.ubuntu.com/server/job/cloud-init-ci/272/rebuild

review: Approve (continuous-integration)
Revision history for this message
Ryan Harper (raharper) wrote :

Thanks for tackling this.

I do worry that we're changing existing behavior here: The skipped and forced were mostly advertising; cloud-init didn't not run modules just because they didn't declare their distro support.

If we're going to change that we should be careful about how we introduce the change.

Also, I do think we'd like a subcommand:

cloud-init devel list-modules [--mode=[config|final]] [--distro=[all|distro1|...]]

Maybe we could do something cleaner?

all_modules = find_all_modules
allowed_modules = [mod for mod in all_modules if d_name in mod.distros or mod.distros == 'all']
skipped = set(all_modules).difference(set(allowed_modules))
forced = cfg.get('unverified_modules', [])

Revision history for this message
Scott Moser (smoser) wrote :

I'm generally ok with this.
it does sure seem like we can clean up 'run_section'.

but this isnt making anything any worse.

Revision history for this message
Ryan Harper (raharper) wrote :

I'm ok with the changes, though I would much prefer not using a string that's 11 times longer than 'all';

if we can shorten that sanely, I'm +1

930181c... by Chad Smith

address raharper's review comments: -CONFIG_MODULE_SUPPORT_ALL_DISTROS -> ALL_DISTROS, drop empty conditional check worked_distros

Revision history for this message
Chad Smith (chad.smith) :
1d890d0... by Chad Smith

condense multi-line params in unit test method calls

Revision history for this message
Chad Smith (chad.smith) :
Revision history for this message
Server Team CI bot (server-team-bot) wrote :

FAILED: Continuous integration, rev:1d890d0d6cc4cdb8488e8a6ada068492e9e5eccb
https://jenkins.ubuntu.com/server/job/cloud-init-ci/292/
Executed test runs:
    SUCCESS: Checkout
    FAILED: Unit & Style Tests

Click here to trigger a rebuild:
https://jenkins.ubuntu.com/server/job/cloud-init-ci/292/rebuild

review: Needs Fixing (continuous-integration)
9a6a0ca... by Chad Smith

need to check that the module actually defines distros before deciding to skip

Revision history for this message
Chad Smith (chad.smith) :
Revision history for this message
Server Team CI bot (server-team-bot) wrote :

PASSED: Continuous integration, rev:9a6a0ca79c108e432a3e2c2e009ff5464642563a
https://jenkins.ubuntu.com/server/job/cloud-init-ci/293/
Executed test runs:
    SUCCESS: Checkout
    SUCCESS: Unit & Style Tests
    SUCCESS: Ubuntu LTS: Build
    SUCCESS: Ubuntu LTS: Integration
    SUCCESS: MAAS Compatability Testing
    IN_PROGRESS: Declarative: Post Actions

Click here to trigger a rebuild:
https://jenkins.ubuntu.com/server/job/cloud-init-ci/293/rebuild

review: Approve (continuous-integration)
Revision history for this message
Ryan Harper (raharper) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/cloudinit/config/cc_runcmd.py b/cloudinit/config/cc_runcmd.py
2index 7f99569..449872f 100644
3--- a/cloudinit/config/cc_runcmd.py
4+++ b/cloudinit/config/cc_runcmd.py
5@@ -10,6 +10,7 @@
6
7 from cloudinit.config.schema import (
8 get_schema_doc, validate_cloudconfig_schema)
9+from cloudinit.distros import ALL_DISTROS
10 from cloudinit.settings import PER_INSTANCE
11 from cloudinit import util
12
13@@ -23,7 +24,7 @@ from textwrap import dedent
14 # configuration options before actually attempting to deploy with said
15 # configuration.
16
17-distros = ['all']
18+distros = [ALL_DISTROS]
19
20 schema = {
21 'id': 'cc_runcmd',
22diff --git a/cloudinit/distros/__init__.py b/cloudinit/distros/__init__.py
23index b714b9a..d5becd1 100755
24--- a/cloudinit/distros/__init__.py
25+++ b/cloudinit/distros/__init__.py
26@@ -30,6 +30,10 @@ from cloudinit import util
27 from cloudinit.distros.parsers import hosts
28
29
30+# Used when a cloud-config module can be run on all cloud-init distibutions.
31+# The value 'all' is surfaced in module documentation for distro support.
32+ALL_DISTROS = 'all'
33+
34 OSFAMILIES = {
35 'debian': ['debian', 'ubuntu'],
36 'redhat': ['centos', 'fedora', 'rhel'],
37diff --git a/cloudinit/stages.py b/cloudinit/stages.py
38index a1c4a51..d045268 100644
39--- a/cloudinit/stages.py
40+++ b/cloudinit/stages.py
41@@ -821,28 +821,35 @@ class Modules(object):
42 skipped = []
43 forced = []
44 overridden = self.cfg.get('unverified_modules', [])
45+ active_mods = []
46+ all_distros = set([distros.ALL_DISTROS])
47 for (mod, name, _freq, _args) in mostly_mods:
48- worked_distros = set(mod.distros)
49+ worked_distros = set(mod.distros) # Minimally [] per fixup_modules
50 worked_distros.update(
51 distros.Distro.expand_osfamily(mod.osfamilies))
52
53- # module does not declare 'distros' or lists this distro
54- if not worked_distros or d_name in worked_distros:
55- continue
56-
57- if name in overridden:
58- forced.append(name)
59- else:
60- skipped.append(name)
61+ # Skip only when the following conditions are all met:
62+ # - distros are defined in the module != ALL_DISTROS
63+ # - the current d_name isn't in distros
64+ # - and the module is unverified and not in the unverified_modules
65+ # override list
66+ if worked_distros and worked_distros != all_distros:
67+ if d_name not in worked_distros:
68+ if name not in overridden:
69+ skipped.append(name)
70+ continue
71+ forced.append(name)
72+ active_mods.append([mod, name, _freq, _args])
73
74 if skipped:
75- LOG.info("Skipping modules %s because they are not verified "
76+ LOG.info("Skipping modules '%s' because they are not verified "
77 "on distro '%s'. To run anyway, add them to "
78- "'unverified_modules' in config.", skipped, d_name)
79+ "'unverified_modules' in config.",
80+ ','.join(skipped), d_name)
81 if forced:
82- LOG.info("running unverified_modules: %s", forced)
83+ LOG.info("running unverified_modules: '%s'", ', '.join(forced))
84
85- return self._run_modules(mostly_mods)
86+ return self._run_modules(active_mods)
87
88
89 def read_runtime_config():
90diff --git a/tests/unittests/test_runs/test_simple_run.py b/tests/unittests/test_runs/test_simple_run.py
91index 5cf666f..b8fb479 100644
92--- a/tests/unittests/test_runs/test_simple_run.py
93+++ b/tests/unittests/test_runs/test_simple_run.py
94@@ -1,8 +1,6 @@
95 # This file is part of cloud-init. See LICENSE file for license information.
96
97 import os
98-import shutil
99-import tempfile
100
101 from cloudinit.tests import helpers
102
103@@ -12,16 +10,19 @@ from cloudinit import util
104
105
106 class TestSimpleRun(helpers.FilesystemMockingTestCase):
107- def _patchIn(self, root):
108- self.patchOS(root)
109- self.patchUtils(root)
110-
111- def test_none_ds(self):
112- new_root = tempfile.mkdtemp()
113- self.addCleanup(shutil.rmtree, new_root)
114- self.replicateTestRoot('simple_ubuntu', new_root)
115- cfg = {
116+
117+ with_logs = True
118+
119+ def setUp(self):
120+ super(TestSimpleRun, self).setUp()
121+ self.new_root = self.tmp_dir()
122+ self.replicateTestRoot('simple_ubuntu', self.new_root)
123+
124+ # Seed cloud.cfg file for our tests
125+ self.cfg = {
126 'datasource_list': ['None'],
127+ 'runcmd': ['ls /etc'], # test ALL_DISTROS
128+ 'spacewalk': {}, # test non-ubuntu distros module definition
129 'write_files': [
130 {
131 'path': '/etc/blah.ini',
132@@ -29,14 +30,17 @@ class TestSimpleRun(helpers.FilesystemMockingTestCase):
133 'permissions': 0o755,
134 },
135 ],
136- 'cloud_init_modules': ['write-files'],
137+ 'cloud_init_modules': ['write-files', 'spacewalk', 'runcmd'],
138 }
139- cloud_cfg = util.yaml_dumps(cfg)
140- util.ensure_dir(os.path.join(new_root, 'etc', 'cloud'))
141- util.write_file(os.path.join(new_root, 'etc',
142+ cloud_cfg = util.yaml_dumps(self.cfg)
143+ util.ensure_dir(os.path.join(self.new_root, 'etc', 'cloud'))
144+ util.write_file(os.path.join(self.new_root, 'etc',
145 'cloud', 'cloud.cfg'), cloud_cfg)
146- self._patchIn(new_root)
147+ self.patchOS(self.new_root)
148+ self.patchUtils(self.new_root)
149
150+ def test_none_ds_populates_var_lib_cloud(self):
151+ """Init and run_section default behavior creates appropriate dirs."""
152 # Now start verifying whats created
153 initer = stages.Init()
154 initer.read_cfg()
155@@ -51,10 +55,16 @@ class TestSimpleRun(helpers.FilesystemMockingTestCase):
156 initer.update()
157 self.assertTrue(os.path.islink("var/lib/cloud/instance"))
158
159- initer.cloudify().run('consume_data',
160- initer.consume_data,
161- args=[PER_INSTANCE],
162- freq=PER_INSTANCE)
163+ def test_none_ds_runs_modules_which_do_not_define_distros(self):
164+ """Any modules which do not define a distros attribute are run."""
165+ initer = stages.Init()
166+ initer.read_cfg()
167+ initer.initialize()
168+ initer.fetch()
169+ initer.instancify()
170+ initer.update()
171+ initer.cloudify().run('consume_data', initer.consume_data,
172+ args=[PER_INSTANCE], freq=PER_INSTANCE)
173
174 mods = stages.Modules(initer)
175 (which_ran, failures) = mods.run_section('cloud_init_modules')
176@@ -63,5 +73,80 @@ class TestSimpleRun(helpers.FilesystemMockingTestCase):
177 self.assertIn('write-files', which_ran)
178 contents = util.load_file('/etc/blah.ini')
179 self.assertEqual(contents, 'blah')
180+ self.assertNotIn(
181+ "Skipping modules ['write-files'] because they are not verified on"
182+ " distro 'ubuntu'",
183+ self.logs.getvalue())
184+
185+ def test_none_ds_skips_modules_which_define_unmatched_distros(self):
186+ """Skip modules which define distros which don't match the current."""
187+ initer = stages.Init()
188+ initer.read_cfg()
189+ initer.initialize()
190+ initer.fetch()
191+ initer.instancify()
192+ initer.update()
193+ initer.cloudify().run('consume_data', initer.consume_data,
194+ args=[PER_INSTANCE], freq=PER_INSTANCE)
195+
196+ mods = stages.Modules(initer)
197+ (which_ran, failures) = mods.run_section('cloud_init_modules')
198+ self.assertTrue(len(failures) == 0)
199+ self.assertIn(
200+ "Skipping modules 'spacewalk' because they are not verified on"
201+ " distro 'ubuntu'",
202+ self.logs.getvalue())
203+ self.assertNotIn('spacewalk', which_ran)
204+
205+ def test_none_ds_runs_modules_which_distros_all(self):
206+ """Skip modules which define distros attribute as supporting 'all'.
207+
208+ This is done in the module with the declaration:
209+ distros = [ALL_DISTROS]. runcmd is an example.
210+ """
211+ initer = stages.Init()
212+ initer.read_cfg()
213+ initer.initialize()
214+ initer.fetch()
215+ initer.instancify()
216+ initer.update()
217+ initer.cloudify().run('consume_data', initer.consume_data,
218+ args=[PER_INSTANCE], freq=PER_INSTANCE)
219+
220+ mods = stages.Modules(initer)
221+ (which_ran, failures) = mods.run_section('cloud_init_modules')
222+ self.assertTrue(len(failures) == 0)
223+ self.assertIn('runcmd', which_ran)
224+ self.assertNotIn(
225+ "Skipping modules 'runcmd' because they are not verified on"
226+ " distro 'ubuntu'",
227+ self.logs.getvalue())
228+
229+ def test_none_ds_forces_run_via_unverified_modules(self):
230+ """run_section forced skipped modules by using unverified_modules."""
231+
232+ # re-write cloud.cfg with unverified_modules override
233+ self.cfg['unverified_modules'] = ['spacewalk'] # Would have skipped
234+ cloud_cfg = util.yaml_dumps(self.cfg)
235+ util.ensure_dir(os.path.join(self.new_root, 'etc', 'cloud'))
236+ util.write_file(os.path.join(self.new_root, 'etc',
237+ 'cloud', 'cloud.cfg'), cloud_cfg)
238+
239+ initer = stages.Init()
240+ initer.read_cfg()
241+ initer.initialize()
242+ initer.fetch()
243+ initer.instancify()
244+ initer.update()
245+ initer.cloudify().run('consume_data', initer.consume_data,
246+ args=[PER_INSTANCE], freq=PER_INSTANCE)
247+
248+ mods = stages.Modules(initer)
249+ (which_ran, failures) = mods.run_section('cloud_init_modules')
250+ self.assertTrue(len(failures) == 0)
251+ self.assertIn('spacewalk', which_ran)
252+ self.assertIn(
253+ "running unverified_modules: 'spacewalk'",
254+ self.logs.getvalue())
255
256 # vi: ts=4 expandtab

Subscribers

People subscribed via source and target branches