Split from monolithic openwrt.py into separate mixin files for interfaces, VLANs, packages, routing, wireless, LLDP, and config. Already in use by the main driver; just missing from git tracking. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
102 lines
4.1 KiB
Python
102 lines
4.1 KiB
Python
# -*- coding: utf-8 -*-
|
||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||
# you may not use this file except in compliance with the License.
|
||
# You may obtain a copy of the License at
|
||
#
|
||
# http://www.apache.org/licenses/LICENSE-2.0
|
||
#
|
||
# Unless required by applicable law or agreed to in writing, software
|
||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||
# See the License for the specific language governing permissions and
|
||
# limitations under the License.
|
||
|
||
from __future__ import annotations
|
||
|
||
from napalm.base.exceptions import (
|
||
CommandErrorException,
|
||
MergeConfigException,
|
||
ReplaceConfigException,
|
||
)
|
||
from napalm_device_types import ConfigLifecycleMixin
|
||
|
||
|
||
class OpenWrtConfigMixin(ConfigLifecycleMixin):
|
||
"""Mixin providing configuration-management NAPALM methods for OpenWrt.
|
||
|
||
Reuses the standard lifecycle (load/compare/discard) from
|
||
:class:`ConfigLifecycleMixin` and adds OpenWrt-specific
|
||
``commit_config`` and ``rollback``.
|
||
"""
|
||
|
||
_comment_chars = ("#",)
|
||
|
||
def _get_running_config(self) -> str:
|
||
return self._send_command("uci export")
|
||
|
||
def commit_config(self, message: str = "", revert_in: int | None = None) -> None:
|
||
"""Apply the staged candidate configuration and commit it.
|
||
|
||
**Merge mode**: each UCI command line is sent to the device shell, then
|
||
``uci commit`` is called to persist the changes.
|
||
|
||
**Replace mode**: the candidate is piped through ``uci import`` and
|
||
then ``uci commit`` is called for every affected package.
|
||
|
||
:raises MergeConfigException: if no candidate is staged or if
|
||
commands are rejected.
|
||
:raises ReplaceConfigException: same, for replace candidates.
|
||
"""
|
||
if self._candidate_config is None:
|
||
raise MergeConfigException("No candidate configuration is staged.")
|
||
|
||
ex_cls = ReplaceConfigException if self._candidate_mode == "replace" else MergeConfigException
|
||
|
||
self._backup_config = self._send_command("uci export")
|
||
|
||
errors: list[str] = []
|
||
try:
|
||
if self._candidate_mode == "merge":
|
||
for line in self._candidate_config.splitlines():
|
||
stripped = line.strip()
|
||
if not stripped or stripped.startswith("#"):
|
||
continue
|
||
out = self._send_command(stripped)
|
||
if out and ("uci: " in out.lower() or "error" in out.lower()):
|
||
errors.append(f" {stripped!r}: {out}")
|
||
self._send_command("uci commit")
|
||
else:
|
||
escaped = self._candidate_config.replace("'", "'\\''")
|
||
self._send_command(f"printf '%s' '{escaped}' > /tmp/napalm_candidate.uci")
|
||
out = self._send_command("uci import < /tmp/napalm_candidate.uci && uci commit")
|
||
self._send_command("rm -f /tmp/napalm_candidate.uci")
|
||
if out and "error" in out.lower():
|
||
errors.append(out)
|
||
except Exception as exc:
|
||
raise ex_cls(str(exc)) from exc
|
||
|
||
if errors:
|
||
raise ex_cls("The following commands were rejected:\n" + "\n".join(errors))
|
||
|
||
self._candidate_config = None
|
||
self._candidate_mode = None
|
||
|
||
def rollback(self) -> None:
|
||
"""Restore the UCI configuration to the state before the last :meth:`commit_config`.
|
||
|
||
Pipes the saved backup through ``uci import`` and then commits.
|
||
|
||
:raises CommandErrorException: if no backup is available.
|
||
"""
|
||
if self._backup_config is None:
|
||
raise CommandErrorException(
|
||
"No backup configuration available – commit_config has not been called in this session."
|
||
)
|
||
|
||
escaped = self._backup_config.replace("'", "'\\''")
|
||
self._send_command(f"printf '%s' '{escaped}' > /tmp/napalm_rollback.uci")
|
||
self._send_command("uci import < /tmp/napalm_rollback.uci && uci commit")
|
||
self._send_command("rm -f /tmp/napalm_rollback.uci")
|
||
|
||
self._backup_config = None
|