summaryrefslogtreecommitdiff
path: root/python/vyos
diff options
context:
space:
mode:
authorJohn Estabrook <jestabro@vyos.io>2024-10-02 12:36:41 -0500
committerJohn Estabrook <jestabro@vyos.io>2024-10-05 00:27:19 -0500
commit4d5f2a58bbf5a366c43871ef27b75d31b3b2a114 (patch)
treecb3047407f14d724b0556df3040007629cee3459 /python/vyos
parentf9c81b121b146fbcf3c458273b2b47e6e571f2e2 (diff)
downloadvyos-1x-4d5f2a58bbf5a366c43871ef27b75d31b3b2a114.tar.gz
vyos-1x-4d5f2a58bbf5a366c43871ef27b75d31b3b2a114.zip
config-mgmt: T5976: add option for commit-confirm to use 'soft' rollback
Commit-confirm will restore a previous configuration if a confirmation is not received in N minutes. Traditionally, this was restored by a reboot into the last configuration on disk; add a configurable option to reload the last completed commit without a reboot. The default setting is to reboot.
Diffstat (limited to 'python/vyos')
-rw-r--r--python/vyos/config_mgmt.py83
-rw-r--r--python/vyos/configsession.py9
2 files changed, 80 insertions, 12 deletions
diff --git a/python/vyos/config_mgmt.py b/python/vyos/config_mgmt.py
index 920a19fec..851ac2134 100644
--- a/python/vyos/config_mgmt.py
+++ b/python/vyos/config_mgmt.py
@@ -33,6 +33,8 @@ from urllib.parse import urlunsplit
from vyos.config import Config
from vyos.configtree import ConfigTree
from vyos.configtree import ConfigTreeError
+from vyos.configsession import ConfigSession
+from vyos.configsession import ConfigSessionError
from vyos.configtree import show_diff
from vyos.load_config import load
from vyos.load_config import LoadConfigError
@@ -139,13 +141,19 @@ class ConfigMgmt:
config = Config()
d = config.get_config_dict(
- ['system', 'config-management'], key_mangling=('-', '_'), get_first_key=True
+ ['system', 'config-management'],
+ key_mangling=('-', '_'),
+ get_first_key=True,
+ with_defaults=True,
)
self.max_revisions = int(d.get('commit_revisions', 0))
self.num_revisions = 0
self.locations = d.get('commit_archive', {}).get('location', [])
self.source_address = d.get('commit_archive', {}).get('source_address', '')
+ self.reboot_unconfirmed = bool(d.get('commit_confirm') == 'reboot')
+ self.config_dict = d
+
if config.exists(['system', 'host-name']):
self.hostname = config.return_value(['system', 'host-name'])
if config.exists(['system', 'domain-name']):
@@ -175,42 +183,63 @@ class ConfigMgmt:
def commit_confirm(
self, minutes: int = DEFAULT_TIME_MINUTES, no_prompt: bool = False
) -> Tuple[str, int]:
- """Commit with reboot to saved config in 'minutes' minutes if
+ """Commit with reload/reboot to saved config in 'minutes' minutes if
'confirm' call is not issued.
"""
if is_systemd_service_active(f'{timer_name}.timer'):
msg = 'Another confirm is pending'
return msg, 1
- if unsaved_commits():
+ if self.reboot_unconfirmed and unsaved_commits():
W = '\nYou should save previous commits before commit-confirm !\n'
else:
W = ''
- prompt_str = f"""
+ if self.reboot_unconfirmed:
+ prompt_str = f"""
commit-confirm will automatically reboot in {minutes} minutes unless changes
-are confirmed.\n
+are confirmed.
+Proceed ?"""
+ else:
+ prompt_str = f"""
+commit-confirm will automatically reload previous config in {minutes} minutes
+unless changes are confirmed.
Proceed ?"""
+
prompt_str = W + prompt_str
if not no_prompt and not ask_yes_no(prompt_str, default=True):
msg = 'commit-confirm canceled'
return msg, 1
- action = 'sg vyattacfg "/usr/bin/config-mgmt revert"'
+ if self.reboot_unconfirmed:
+ action = 'sg vyattacfg "/usr/bin/config-mgmt revert"'
+ else:
+ action = 'sg vyattacfg "/usr/bin/config-mgmt revert_soft"'
+
cmd = f'sudo systemd-run --quiet --on-active={minutes}m --unit={timer_name} {action}'
rc, out = rc_cmd(cmd)
if rc != 0:
raise ConfigMgmtError(out)
# start notify
- cmd = f'sudo -b /usr/libexec/vyos/commit-confirm-notify.py {minutes}'
+ if self.reboot_unconfirmed:
+ cmd = (
+ f'sudo -b /usr/libexec/vyos/commit-confirm-notify.py --reboot {minutes}'
+ )
+ else:
+ cmd = f'sudo -b /usr/libexec/vyos/commit-confirm-notify.py {minutes}'
+
os.system(cmd)
- msg = f'Initialized commit-confirm; {minutes} minutes to confirm before reboot'
+ if self.reboot_unconfirmed:
+ msg = f'Initialized commit-confirm; {minutes} minutes to confirm before reboot'
+ else:
+ msg = f'Initialized commit-confirm; {minutes} minutes to confirm before reload'
+
return msg, 0
def confirm(self) -> Tuple[str, int]:
- """Do not reboot to saved config following 'commit-confirm'.
+ """Do not reboot/reload to saved/completed config following 'commit-confirm'.
Update commit log and archive.
"""
if not is_systemd_service_active(f'{timer_name}.timer'):
@@ -234,7 +263,11 @@ Proceed ?"""
self._add_log_entry(**entry)
self._update_archive()
- msg = 'Reboot timer stopped'
+ if self.reboot_unconfirmed:
+ msg = 'Reboot timer stopped'
+ else:
+ msg = 'Reload timer stopped'
+
return msg, 0
def revert(self) -> Tuple[str, int]:
@@ -248,6 +281,28 @@ Proceed ?"""
return '', 0
+ def revert_soft(self) -> Tuple[str, int]:
+ """Reload last revision, dropping commits from 'commit-confirm'."""
+ _ = self._read_tmp_log_entry()
+
+ # commits under commit-confirm are not added to revision list unless
+ # confirmed, hence a soft revert is to revision 0
+ revert_ct = self._get_config_tree_revision(0)
+
+ mask = os.umask(0o002)
+ session = ConfigSession(os.getpid(), app='config-mgmt')
+
+ try:
+ session.load_explicit(revert_ct)
+ session.commit()
+ except ConfigSessionError as e:
+ raise ConfigMgmtError(e) from e
+ finally:
+ os.umask(mask)
+ del session
+
+ return '', 0
+
def rollback(self, rev: int, no_prompt: bool = False) -> Tuple[str, int]:
"""Reboot to config revision 'rev'."""
msg = ''
@@ -684,7 +739,10 @@ Proceed ?"""
entry = f.read()
os.unlink(tmp_log_entry)
except OSError as e:
- logger.critical(f'error on file {tmp_log_entry}: {e}')
+ logger.info(f'error on file {tmp_log_entry}: {e}')
+ # fail gracefully in corner case:
+ # delete commit-revisions; commit-confirm
+ return {}
return self._get_log_entry(entry)
@@ -752,7 +810,8 @@ def run():
)
subparsers.add_parser('confirm', help='Confirm commit')
- subparsers.add_parser('revert', help='Revert commit-confirm')
+ subparsers.add_parser('revert', help='Revert commit-confirm with reboot')
+ subparsers.add_parser('revert_soft', help='Revert commit-confirm with reload')
rollback = subparsers.add_parser('rollback', help='Rollback to earlier config')
rollback.add_argument('--rev', type=int, help='Revision number for rollback')
diff --git a/python/vyos/configsession.py b/python/vyos/configsession.py
index c0d3c7ecb..9c56d246a 100644
--- a/python/vyos/configsession.py
+++ b/python/vyos/configsession.py
@@ -268,6 +268,15 @@ class ConfigSession(object):
out = self.__run_command(LOAD_CONFIG + [file_path])
return out
+ def load_explicit(self, file_path):
+ from vyos.load_config import load
+ from vyos.load_config import LoadConfigError
+
+ try:
+ load(file_path, switch='explicit')
+ except LoadConfigError as e:
+ raise ConfigSessionError(e) from e
+
def migrate_and_load_config(self, file_path):
out = self.__run_command(MIGRATE_LOAD_CONFIG + [file_path])
return out