From 50441c89312c0b61eae878e00dd7c12294ec4d75 Mon Sep 17 00:00:00 2001 From: "Gareth J. Greenaway" Date: Mon, 8 Oct 2018 09:57:43 -0700 Subject: [PATCH 1/5] Initial work to be able to disable requisites. --- salt/state.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/salt/state.py b/salt/state.py index aecdd9763f5e..ec92b9f3c9fa 100644 --- a/salt/state.py +++ b/salt/state.py @@ -1521,6 +1521,7 @@ def requisite_in(self, high): req_in_all = req_in.union({'require', 'watch', 'onfail', 'onfail_stop', 'onchanges'}) extend = {} errors = [] + disabled = self.opts.get('disabled_requisites', []) for id_, body in six.iteritems(high): if not isinstance(body, dict): continue @@ -1539,6 +1540,10 @@ def requisite_in(self, high): key = next(iter(arg)) if key not in req_in: continue + log.debug('=== self %s ===', self.opts) + if key in disabled: + log.info('=== %s disabled ===', key) + continue rkey = key.split('_')[0] items = arg[key] if isinstance(items, dict): From bb25c8113c55e1a156e6f88aa243048fffe509df Mon Sep 17 00:00:00 2001 From: "Gareth J. Greenaway" Date: Tue, 9 Oct 2018 13:10:28 -0700 Subject: [PATCH 2/5] Finalizing ability to disable requisites during State runs. Adding to configuration and documentation. --- conf/minion | 9 +++++++++ doc/topics/releases/neon.rst | 3 +++ salt/config/__init__.py | 4 ++++ salt/state.py | 15 +++++++++++---- 4 files changed, 27 insertions(+), 4 deletions(-) diff --git a/conf/minion b/conf/minion index 80bbb45e526d..cc2b603b832f 100644 --- a/conf/minion +++ b/conf/minion @@ -555,6 +555,15 @@ # #state_aggregate: False +# Disable requisites during state runs by specifying a single requisite +# or a list of requisites to disable. +# +# disabled_requisites: require_in +# +# disabled_requisites: +# - require +# - require_in + ##### File Directory Settings ##### ########################################## # The Salt Minion can redirect all file server operations to a local directory, diff --git a/doc/topics/releases/neon.rst b/doc/topics/releases/neon.rst index 1ef96c1e7888..2f41fbcce558 100644 --- a/doc/topics/releases/neon.rst +++ b/doc/topics/releases/neon.rst @@ -177,6 +177,9 @@ State Changes - The ``onchanges`` and ``prereq`` :ref:`requisites ` now behave properly in test mode. +- Adding a new option for the State compiler, ``disabled_requisites`` will allow + requisites to be disabled during State runs. + Module Changes ============== diff --git a/salt/config/__init__.py b/salt/config/__init__.py index 2b9dffc9b894..e502e647a050 100644 --- a/salt/config/__init__.py +++ b/salt/config/__init__.py @@ -1197,6 +1197,9 @@ def _gather_buffer_space(): # Use Adler32 hashing algorithm for server_id (default False until Sodium, "adler32" after) # Possible values are: False, adler32, crc32 'server_id_use_crc': (bool, six.string_types), + + # Disable requisites during State runs + 'disabled_requisites': (six.string_types, list), } # default configurations @@ -1494,6 +1497,7 @@ def _gather_buffer_space(): 'schedule': {}, 'ssh_merge_pillar': True, 'server_id_use_crc': False, + 'disabled_requisites': [], } DEFAULT_MASTER_OPTS = { diff --git a/salt/state.py b/salt/state.py index ec92b9f3c9fa..a2174149fccc 100644 --- a/salt/state.py +++ b/salt/state.py @@ -1521,7 +1521,9 @@ def requisite_in(self, high): req_in_all = req_in.union({'require', 'watch', 'onfail', 'onfail_stop', 'onchanges'}) extend = {} errors = [] - disabled = self.opts.get('disabled_requisites', []) + disabled_reqs = self.opts.get('disabled_requisites', []) + if not isinstance(disabled_reqs, list): + disabled_reqs = [disabled_reqs] for id_, body in six.iteritems(high): if not isinstance(body, dict): continue @@ -1540,9 +1542,8 @@ def requisite_in(self, high): key = next(iter(arg)) if key not in req_in: continue - log.debug('=== self %s ===', self.opts) - if key in disabled: - log.info('=== %s disabled ===', key) + if key in disabled_reqs: + log.warning('The %s requisite has been disabled, Ignoring.', key) continue rkey = key.split('_')[0] items = arg[key] @@ -2259,6 +2260,9 @@ def check_requisite(self, low, running, chunks, pre=False): Look into the running data to check the status of all requisite states ''' + disabled_reqs = self.opts.get('disabled_requisites', []) + if not isinstance(disabled_reqs, list): + disabled_reqs = [disabled_reqs] present = False # If mod_watch is not available make it a require if 'watch' in low: @@ -2309,6 +2313,9 @@ def check_requisite(self, low, running, chunks, pre=False): if pre: reqs['prerequired'] = [] for r_state in reqs: + if r_state in disabled_reqs: + log.warning('The %s requisite has been disabled, Ignoring.', r_state) + continue if r_state in low and low[r_state] is not None: for req in low[r_state]: if isinstance(req, six.string_types): From 8cd3887602c9d61f96385edebd4a0cb692b9b7fe Mon Sep 17 00:00:00 2001 From: "Gareth J. Greenaway" Date: Tue, 9 Oct 2018 16:10:11 -0700 Subject: [PATCH 3/5] Move this down to ensure the req in the low data before we continue --- salt/state.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/salt/state.py b/salt/state.py index a2174149fccc..6b8d266a4d44 100644 --- a/salt/state.py +++ b/salt/state.py @@ -2313,10 +2313,10 @@ def check_requisite(self, low, running, chunks, pre=False): if pre: reqs['prerequired'] = [] for r_state in reqs: - if r_state in disabled_reqs: - log.warning('The %s requisite has been disabled, Ignoring.', r_state) - continue if r_state in low and low[r_state] is not None: + if r_state in disabled_reqs: + log.warning('The %s requisite has been disabled, Ignoring.', r_state) + continue for req in low[r_state]: if isinstance(req, six.string_types): req = {'id': req} From e3e63c9730f433dd1c3cbc7621c64bedcc1dceb2 Mon Sep 17 00:00:00 2001 From: "Gareth J. Greenaway" Date: Wed, 10 Oct 2018 12:53:19 -0700 Subject: [PATCH 4/5] Adding a couple tests for testing disabled_requisites --- tests/unit/test_state.py | 55 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/tests/unit/test_state.py b/tests/unit/test_state.py index d6555a03bb2d..7f78e240dec0 100644 --- a/tests/unit/test_state.py +++ b/tests/unit/test_state.py @@ -73,6 +73,61 @@ def test_render_error_on_invalid_requisite(self): with self.assertRaises(salt.exceptions.SaltRenderError): state_obj.call_high(high_data) + def test_render_requisite_require_disabled(self): + ''' + Test that the state compiler correctly deliver a rendering + exception when a requisite cannot be resolved + ''' + with patch('salt.state.State._gather_pillar') as state_patch: + high_data = { + 'step_one': OrderedDict([ + ('test', [ + OrderedDict([ + ('require', [ + OrderedDict([ + ('test', 'step_two')])])]), + 'succeed_with_changes', {'order': 10000}]), + ('__sls__', 'test.disable_require'), + ('__env__', 'base')]), + 'step_two': {'test': ['succeed_with_changes', + {'order': 10001}], + '__env__': 'base', + '__sls__': 'test.disable_require'}} + + minion_opts = self.get_temp_config('minion') + minion_opts['disabled_requisites'] = ['require'] + state_obj = salt.state.State(minion_opts) + ret = state_obj.call_high(high_data) + run_num = ret['test_|-step_one_|-step_one_|-succeed_with_changes']['__run_num__'] + self.assertEqual(run_num, 0) + + def test_render_requisite_require_in_disabled(self): + ''' + Test that the state compiler correctly deliver a rendering + exception when a requisite cannot be resolved + ''' + with patch('salt.state.State._gather_pillar') as state_patch: + high_data = { + 'step_one': {'test': ['succeed_with_changes', + {'order': 10000}], + '__env__': 'base', + '__sls__': 'test.disable_require_in'}, + 'step_two': OrderedDict([ + ('test', [ + OrderedDict([ + ('require_in', [ + OrderedDict([ + ('test', 'step_one')])])]), + 'succeed_with_changes', {'order': 10001}]), + ('__sls__', 'test.disable_require_in'), + ('__env__', 'base')])} + + minion_opts = self.get_temp_config('minion') + minion_opts['disabled_requisites'] = ['require_in'] + state_obj = salt.state.State(minion_opts) + ret = state_obj.call_high(high_data) + run_num = ret['test_|-step_one_|-step_one_|-succeed_with_changes']['__run_num__'] + self.assertEqual(run_num, 0) class HighStateTestCase(TestCase, AdaptedConfigurationTestCaseMixin): def setUp(self): From 24a80c6df4fc0cd9863afc8814ef3f68caf99181 Mon Sep 17 00:00:00 2001 From: "Gareth J. Greenaway" Date: Sat, 13 Oct 2018 17:38:24 -0700 Subject: [PATCH 5/5] Fixing lint --- tests/unit/test_state.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/unit/test_state.py b/tests/unit/test_state.py index 7f78e240dec0..46479a67b36a 100644 --- a/tests/unit/test_state.py +++ b/tests/unit/test_state.py @@ -129,6 +129,7 @@ def test_render_requisite_require_in_disabled(self): run_num = ret['test_|-step_one_|-step_one_|-succeed_with_changes']['__run_num__'] self.assertEqual(run_num, 0) + class HighStateTestCase(TestCase, AdaptedConfigurationTestCaseMixin): def setUp(self): root_dir = tempfile.mkdtemp(dir=integration.TMP)