chore: track extracted mixin modules

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>
This commit is contained in:
Christian Manivong
2026-06-19 22:13:04 +02:00
co-authored by Claude Sonnet 4.6
parent b7e4831b26
commit 2c0a5e94bf
7 changed files with 1864 additions and 0 deletions
+101
View File
@@ -0,0 +1,101 @@
# -*- 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