diff --git a/salt/modules/napalm_mod.py b/salt/modules/napalm_mod.py index ac38cd499bfa..06d308c46309 100644 --- a/salt/modules/napalm_mod.py +++ b/salt/modules/napalm_mod.py @@ -779,6 +779,55 @@ def junos_rpc(cmd=None, dest=None, format=None, **kwargs): return rpc_ret +@proxy_napalm_wrap +def junos_commit(**kwargs): + ''' + .. versionadded:: Fluorine + + Commit the changes loaded in the candidate configuration. + + dev_timeout: ``30`` + The NETCONF RPC timeout (in seconds). + + comment + Provide a comment for the commit. + + confirm + Provide time in minutes for commit confirmation. If this option is + specified, the commit will be rolled back in the specified amount of time + unless the commit is confirmed. + + sync: ``False`` + When ``True``, on dual control plane systems, requests that the candidate + configuration on one control plane be copied to the other control plane, + checked for correct syntax, and committed on both Routing Engines. + + force_sync: ``False`` + When ``True``, on dual control plane systems, force the candidate + configuration on one control plane to be copied to the other control + plane. + + full + When ``True``, requires all the daemons to check and evaluate the new + configuration. + + detail + When ``True``, return commit detail. + + CLI Examples: + + .. code-block:: bash + + salt '*' napalm.junos_commit comment='Commitiing via Salt' detail=True + salt '*' napalm.junos_commit dev_timeout=60 confirm=10 + salt '*' napalm.junos_commit sync=True dev_timeout=90 + ''' + prep = _junos_prep_fun(napalm_device) # pylint: disable=undefined-variable + if not prep['result']: + return prep + return __salt__['junos.commit'](**kwargs) + + @proxy_napalm_wrap def junos_install_os(path=None, **kwargs): ''' diff --git a/salt/modules/napalm_network.py b/salt/modules/napalm_network.py index 1bbe4dac5500..b0f4963daa68 100644 --- a/salt/modules/napalm_network.py +++ b/salt/modules/napalm_network.py @@ -21,6 +21,8 @@ # Import Python libs from __future__ import absolute_import, unicode_literals, print_function + +import time import logging import datetime @@ -34,12 +36,6 @@ # Import 3rd-party libs from salt.ext import six -try: - from salt.utils import fopen - from salt.utils import mkstemp -except ImportError: - from salt.utils.files import fopen - from salt.utils.files import mkstemp # ---------------------------------------------------------------------------------------------------------------------- # module properties @@ -107,6 +103,19 @@ def _filter_dict(input_dict, search_key, search_value): return output_dict +def _safe_commit_config(loaded_result, napalm_device): + _commit = commit(inherit_napalm_device=napalm_device) # calls the function commit, defined below + if not _commit.get('result', False): + # if unable to commit + loaded_result['comment'] += _commit['comment'] if _commit.get('comment') else 'Unable to commit.' + loaded_result['result'] = False + # unable to commit, something went wrong + discarded = _safe_dicard_config(loaded_result, napalm_device) + if not discarded['result']: + return loaded_result + return _commit + + def _safe_dicard_config(loaded_result, napalm_device): ''' ''' @@ -153,6 +162,8 @@ def _config_logic(napalm_device, loaded_config=None, commit_in=None, commit_at=None, + revert_in=None, + revert_at=None, commit_jid=None, **kwargs): ''' @@ -217,6 +228,9 @@ def _config_logic(napalm_device, # if not in testing mode and trying to commit if commit_jid: log.info('Committing the JID: %s', str(commit_jid)) + removed = cancel_commit(commit_jid) + log.debug('Cleaned up the commit from the schedule') + log.debug(removed['comment']) if len(loaded_result.get('diff', '')) > 0: # if not testing mode # and also the user wants to commit (default) @@ -226,10 +240,13 @@ def _config_logic(napalm_device, time_at=commit_in) # schedule job scheduled_job_name = '__napalm_commit_{}'.format(current_jid) + temp_file = salt.utils.files.mkstemp() + with salt.utils.files.fopen(temp_file, 'w') as fp_: + fp_.write(loaded_config) scheduled = __salt__['schedule.add'](scheduled_job_name, function='net.load_config', job_kwargs={ - 'text': loaded_config, + 'filename': temp_file, 'commit_jid': current_jid, 'replace': replace }, @@ -250,18 +267,65 @@ def _config_logic(napalm_device, 'salt {min_id} net.cancel_commit {current_jid}').format(schedule_ts=commit_time, min_id=__opts__['id'], current_jid=current_jid) + loaded_result['commit_id'] = current_jid return loaded_result log.debug('About to commit:') log.debug(loaded_result['diff']) - _commit = commit(inherit_napalm_device=napalm_device) # calls the function commit, defined below - if not _commit.get('result', False): - # if unable to commit - loaded_result['comment'] += _commit['comment'] if _commit.get('comment') else 'Unable to commit.' - loaded_result['result'] = False - # unable to commit, something went wrong - discarded = _safe_dicard_config(loaded_result, napalm_device) - if not discarded['result']: - return loaded_result + if revert_in or revert_at: + revert_time = __utils__['timeutil.get_time_at'](time_in=revert_in, + time_at=revert_at) + if __grains__['os'] == 'junos': + if 'napalm.junos_rpc' not in __salt__: + loaded_result['comment'] = ('This feature requires the library jxmlease to be installed.\n' + 'To install, please execute: ``pip install jxmlease``.') + loaded_result['result'] = False + return loaded_result + timestamp_at = __utils__['timeutil.get_timestamp_at'](time_in=revert_in, + time_at=revert_at) + minutes = int((timestamp_at - time.time())/60) + _comm = __salt__['napalm.junos_commit'](confirm=minutes) + if not _comm['out']: + # If unable to commit confirm, should try to bail out + loaded_result['comment'] = 'Unable to commit confirm: {}'.format(_comm['message']) + loaded_result['result'] = False + # But before exiting, we must gracefully discard the config + discarded = _safe_dicard_config(loaded_result, napalm_device) + if not discarded['result']: + return loaded_result + else: + temp_file = salt.utils.files.mkstemp() + running_config = __salt__['net.config'](source='running')['out']['running'] + with salt.utils.files.fopen(temp_file, 'w') as fp_: + fp_.write(running_config) + committed = _safe_commit_config(loaded_result, napalm_device) + if not committed['result']: + # If unable to commit, dicard the config (which is + # already done by the _safe_commit_config function), and + # return with the command and other details. + return loaded_result + scheduled_job_name = '__napalm_commit_{}'.format(current_jid) + scheduled = __salt__['schedule.add'](scheduled_job_name, + function='net.load_config', + job_kwargs={ + 'filename': temp_file, + 'commit_jid': current_jid, + 'replace': True + }, + once=revert_time) + log.debug('Scheduling commit confirmed') + log.debug(scheduled) + saved = __salt__['schedule.save']() + loaded_result['comment'] = ('The commit ID is: {current_jid}.\n' + 'This commit will be reverted at: {schedule_ts}, unless confirmed.\n' + 'To confirm the commit and avoid reverting, you can execute:\n\n' + 'salt {min_id} net.confirm_commit {current_jid}').format(schedule_ts=revert_time, + min_id=__opts__['id'], + current_jid=current_jid) + loaded_result['commit_id'] = current_jid + return loaded_result + committed = _safe_commit_config(loaded_result, napalm_device) + if not committed['result']: + return loaded_result else: # would like to commit, but there's no change # need to call discard_config() to release the config DB @@ -1232,6 +1296,8 @@ def load_config(filename=None, replace=False, commit_in=None, commit_at=None, + revert_in=None, + revert_at=None, commit_jid=None, inherit_napalm_device=None, saltenv='base', @@ -1333,6 +1399,67 @@ def load_config(filename=None, .. versionadded: Fluorine + revert_in: ``None`` + Commit and revert the changes in a specific number of minutes / hours. + Example of accepted formats: ``5`` (revert in 5 minutes), ``2m`` (revert + in 2 minutes), ``1h`` (revert the changes in 1 hour)`, ``5h30m`` (revert + the changes in 5 hours and 30 minutes). + + .. note:: + To confirm the commit, and prevent reverting the changes, you will + have to execute the + :mod:`net.confirm_commit ` + function, using the commit ID returned by this function. + + .. warning:: + This works on any platform, regardless if they have or don't have + native capabilities to confirming a commit. However, please be + *very* cautious when using this feature: on Junos (as it is the only + NAPALM core platform supporting this natively) it executes a commit + confirmed as you would do from the command line. + All the other platforms don't have this capability natively, + therefore the revert is done via Salt. That means, your device needs + to be reachable at the moment when Salt will attempt to revert your + changes. Be cautious when pushing configuration changes that would + prevent you reach the device. + + Similarly, if an user or a different process apply other + configuration changes in the meanwhile (between the moment you + commit and till the changes are reverted), these changes would be + equally reverted, as Salt cannot be aware of them. + + .. versionadded: Fluorine + + revert_at: ``None`` + Commit and revert the changes at a specific time. Example of accepted + formats: ``1am`` (will commit and revert the changes at the next 1AM), + ``13:20`` (will commit and revert at 13:20), ``1:20am``, etc. + + .. note:: + To confirm the commit, and prevent reverting the changes, you will + have to execute the + :mod:`net.confirm_commit ` + function, using the commit ID returned by this function. + + .. warning:: + This works on any platform, regardless if they have or don't have + native capabilities to confirming a commit. However, please be + *very* cautious when using this feature: on Junos (as it is the only + NAPALM core platform supporting this natively) it executes a commit + confirmed as you would do from the command line. + All the other platforms don't have this capability natively, + therefore the revert is done via Salt. That means, your device needs + to be reachable at the moment when Salt will attempt to revert your + changes. Be cautious when pushing configuration changes that would + prevent you reach the device. + + Similarly, if an user or a different process apply other + configuration changes in the meanwhile (between the moment you + commit and till the changes are reverted), these changes would be + equally reverted, as Salt cannot be aware of them. + + .. versionadded: Fluorine + saltenv: ``base`` Specifies the Salt environment name. @@ -1392,6 +1519,11 @@ def load_config(filename=None, ret['comment'] = 'Unable to read from {}. Please specify a valid file or text.'.format(filename) log.error(ret['comment']) return ret + if commit_jid: + # When the commit_jid argument is passed, it probably is a scheduled + # commit to be executed, and filename is a temporary file which + # can be removed after reading it. + salt.utils.files.safe_rm(filename) _loaded = salt.utils.napalm.call( napalm_device, # pylint: disable=undefined-variable fun, @@ -1408,6 +1540,8 @@ def load_config(filename=None, loaded_config=text, commit_at=commit_at, commit_in=commit_in, + revert_in=revert_in, + revert_at=revert_at, commit_jid=commit_jid, **kwargs) @@ -1431,6 +1565,8 @@ def load_template(template_name, replace=False, commit_in=None, commit_at=None, + revert_in=None, + revert_at=None, inherit_napalm_device=None, # pylint: disable=unused-argument **template_vars): ''' @@ -1605,6 +1741,67 @@ def load_template(template_name, .. versionadded: Fluorine + revert_in: ``None`` + Commit and revert the changes in a specific number of minutes / hours. + Example of accepted formats: ``5`` (revert in 5 minutes), ``2m`` (revert + in 2 minutes), ``1h`` (revert the changes in 1 hour)`, ``5h30m`` (revert + the changes in 5 hours and 30 minutes). + + .. note:: + To confirm the commit, and prevent reverting the changes, you will + have to execute the + :mod:`net.confirm_commit ` + function, using the commit ID returned by this function. + + .. warning:: + This works on any platform, regardless if they have or don't have + native capabilities to confirming a commit. However, please be + *very* cautious when using this feature: on Junos (as it is the only + NAPALM core platform supporting this natively) it executes a commit + confirmed as you would do from the command line. + All the other platforms don't have this capability natively, + therefore the revert is done via Salt. That means, your device needs + to be reachable at the moment when Salt will attempt to revert your + changes. Be cautious when pushing configuration changes that would + prevent you reach the device. + + Similarly, if an user or a different process apply other + configuration changes in the meanwhile (between the moment you + commit and till the changes are reverted), these changes would be + equally reverted, as Salt cannot be aware of them. + + .. versionadded: Fluorine + + revert_at: ``None`` + Commit and revert the changes at a specific time. Example of accepted + formats: ``1am`` (will commit and revert the changes at the next 1AM), + ``13:20`` (will commit and revert at 13:20), ``1:20am``, etc. + + .. note:: + To confirm the commit, and prevent reverting the changes, you will + have to execute the + :mod:`net.confirm_commit ` + function, using the commit ID returned by this function. + + .. warning:: + This works on any platform, regardless if they have or don't have + native capabilities to confirming a commit. However, please be + *very* cautious when using this feature: on Junos (as it is the only + NAPALM core platform supporting this natively) it executes a commit + confirmed as you would do from the command line. + All the other platforms don't have this capability natively, + therefore the revert is done via Salt. That means, your device needs + to be reachable at the moment when Salt will attempt to revert your + changes. Be cautious when pushing configuration changes that would + prevent you reach the device. + + Similarly, if an user or a different process apply other + configuration changes in the meanwhile (between the moment you + commit and till the changes are reverted), these changes would be + equally reverted, as Salt cannot be aware of them. + + .. versionadded: Fluorine + defaults: None Default variables/context passed to the template. @@ -1857,6 +2054,8 @@ def load_template(template_name, loaded_config=loaded_config, commit_at=commit_at, commit_in=commit_in, + revert_in=revert_in, + revert_at=revert_at, **template_vars) @@ -2040,6 +2239,35 @@ def cancel_commit(jid): return removed +def confirm_commit(jid): + ''' + .. versionadded:: Fluorine + + Confirm a commit scheduled to be reverted via the ``revert_in`` and + ``revert_at`` arguments from the + :mod:`net.load_template ` or + :mod:`net.load_config ` + function, using the commit ID returned by this function. + + .. warning:: + This works on any platform, regardless if they have or don't have + native capabilities to confirming a commit. However, please be + *very* cautious when using this feature: on Junos (as it is the only + NAPALM core platform supporting this natively) it executes a commit + confirmed as you would do from the command line. + All the other platforms don't have this capability natively, + therefore the revert is done via Salt. That means, your device needs + to be reachable at the moment when Salt will attempt to revert your + changes. Be cautious when pushing configuration changes that would + prevent you reach the device. + + Similarly, if an user or a different process apply other + configuration changes in the meanwhile (between the moment you + commit and till the changes are reverted), these changes would be + equally reverted, as Salt cannot be aware of them. + + .. versionadded: Fluorine + + revert_at: ``None`` + Commit and revert the changes at a specific time. Example of accepted + formats: ``1am`` (will commit and revert the changes at the next 1AM), + ``13:20`` (will commit and revert at 13:20), ``1:20am``, etc. + + .. note:: + To confirm the commit, and prevent reverting the changes, you will + have to execute the + :mod:`net.confirm_commit ` + function, using the commit ID returned by this function. + + .. warning:: + This works on any platform, regardless if they have or don't have + native capabilities to confirming a commit. However, please be + *very* cautious when using this feature: on Junos (as it is the only + NAPALM core platform supporting this natively) it executes a commit + confirmed as you would do from the command line. + All the other platforms don't have this capability natively, + therefore the revert is done via Salt. That means, your device needs + to be reachable at the moment when Salt will attempt to revert your + changes. Be cautious when pushing configuration changes that would + prevent you reach the device. + + Similarly, if an user or a different process apply other + configuration changes in the meanwhile (between the moment you + commit and till the changes are reverted), these changes would be + equally reverted, as Salt cannot be aware of them. + + .. versionadded: Fluorine + replace: False Load and replace the configuration. Default: ``False`` (will apply load merge). @@ -392,6 +455,8 @@ def managed(name, skip_verify = __salt__['config.merge']('skip_verify', skip_verify) commit_in = __salt__['config.merge']('commit_in', commit_in) commit_at = __salt__['config.merge']('commit_at', commit_at) + revert_in = __salt__['config.merge']('revert_in', revert_in) + revert_at = __salt__['config.merge']('revert_at', revert_at) config_update_ret = _update_config(template_name, template_source=template_source, @@ -409,8 +474,74 @@ def managed(name, commit=commit, commit_in=commit_in, commit_at=commit_at, + revert_in=revert_in, + revert_at=revert_at, debug=debug, replace=replace, **template_vars) return salt.utils.napalm.loaded_ret(ret, config_update_ret, test, debug) + + +def commit_cancelled(name): + ''' + .. versionadded:: Fluorine + + Cancel a commit scheduled to be executed via the ``commit_in`` and + ``commit_at`` arguments from the + :py:func:`net.load_template ` or + :py:func:`net.load_config ` or + :mod:`net.load_config