TP45 Schicht B2: VLAN30-Reservierungen ins Oktett-Schema
36 DHCP-Reservierungen wandern aus dem Pool-Fenster (.5, .8, .202-.244) in die Schema-Baender .11-.14 / .151-.176 / .177-.196. Dazu die CCU-Leiche .232 (status=waiting, last-seen=never; die CCU lebt in VLAN20) entfernt und SONOFF-NSPanelPro von einer dynamischen Lease auf .249 zu einer statischen Reservierung auf .12 gepinnt. Neuer Job `tp45-v30-leases` in gc-configplan.py. Er laedt MOVES/REMOVE/ADDS per importlib aus tp45-leases.py, statt sie zu kopieren -- die Tabelle dort ist die SSoT des Umzugs, eine zweite Kopie liefe still auseinander. Warum der Job zwei Selektor-Formen traegt: die erste Fassung schrieb ueberall `[/ip dhcp-server lease [find where ...]]`. In `[:len ...]` ist das Syntaxfehler (`expected command name (line 1 column 55)`, live gemessen) -- Golden Config meldete trotzdem Job SUCCESS und Plan Completed, das Geraet blieb unveraendert. Aufgefallen ist es an der Zahl der Log-Zeilen: von 38 `:if`-Statements hat kein einziger Zweig geloggt. Richtig ist `[/ip dhcp-server lease find where ...]` ohne Klammerpaar um `find`; die Argument-Form `set [find where ...]` bleibt. Abnahme am Geraet, nicht am Plan-Status: 39 Leases im Soll-Schema, .232 und .249 geraeumt, NSPanelPro dynamic=false; Compliance 145/145, keine Drifts. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -52,8 +52,11 @@ BENUTZUNG
|
|||||||
python3 nautobot/gc-configplan.py --job drift-cleanup --apply --no-deploy
|
python3 nautobot/gc-configplan.py --job drift-cleanup --apply --no-deploy
|
||||||
"""
|
"""
|
||||||
import argparse
|
import argparse
|
||||||
|
import importlib.util
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
|
import pathlib
|
||||||
|
import re
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
import urllib.error
|
import urllib.error
|
||||||
@@ -596,6 +599,130 @@ def job_tp45_pool_v20():
|
|||||||
return [(["crs326-core"], _pool_set(20, "10.40.20.200-10.40.20.249"), "TP45-pool-v20")]
|
return [(["crs326-core"], _pool_set(20, "10.40.20.200-10.40.20.249"), "TP45-pool-v20")]
|
||||||
|
|
||||||
|
|
||||||
|
def _tp45_leases_module():
|
||||||
|
"""MOVES/REMOVE/ADDS aus `tp45-leases.py` laden.
|
||||||
|
|
||||||
|
🔴 **NICHT kopieren.** Diese Tabellen sind die SSoT des Umzugs. Eine zweite
|
||||||
|
Kopie hier liefe beim ersten Edit auseinander — und zwar still, weil beide
|
||||||
|
Seiten fuer sich plausibel aussehen; am Geraet faellt nur auf, dass eine
|
||||||
|
Adresse fehlt.
|
||||||
|
|
||||||
|
Der Dateiname traegt einen Bindestrich (Repo-Konvention fuer Skripte), ist
|
||||||
|
also nicht regulaer importierbar -> importlib ueber den Pfad. `__name__` ist
|
||||||
|
dann nicht `__main__`, das `main()`-Guard der Datei greift also nicht.
|
||||||
|
"""
|
||||||
|
spec = importlib.util.spec_from_file_location(
|
||||||
|
"tp45_leases", pathlib.Path(__file__).with_name("tp45-leases.py"))
|
||||||
|
mod = importlib.util.module_from_spec(spec)
|
||||||
|
spec.loader.exec_module(mod)
|
||||||
|
return mod
|
||||||
|
|
||||||
|
|
||||||
|
def _lease_move(mac, soll):
|
||||||
|
"""Eine Umzugs-Zeile — EINZEILIG und klammer-vollstaendig (E1015-Lehre, s. _pool_set).
|
||||||
|
|
||||||
|
Der Selektor wird GEZAEHLT: ein `find where`, das 0 trifft, sieht wie
|
||||||
|
„nicht vorhanden" aus und nicht wie „kaputt", und ein blankes `set [find …]`
|
||||||
|
liefe ins Leere, waehrend der Job Erfolg meldet.
|
||||||
|
|
||||||
|
Absichtlich OHNE `address=<ist>` im Selektor: ein zweiter Lauf muss ein
|
||||||
|
No-Op sein, kein Fehler. Die Ist-Adresse wird stattdessen VOR dem Deploy
|
||||||
|
geprueft — `tp45-leases.py move` verlangt, dass das Soll genau `ist` traegt
|
||||||
|
und bricht sonst ab.
|
||||||
|
|
||||||
|
🔴🔴 **ZWEI VERSCHIEDENE Selektor-Formen, und das ist keine Kosmetik.**
|
||||||
|
Die erste Fassung schrieb ueberall die Argument-Form `[… [find where …]]`.
|
||||||
|
Die ist **Syntaxfehler**, sobald sie in `[:len …]` steht:
|
||||||
|
`expected command name (line 1 column 55)` (live gemessen 2026-09-21)
|
||||||
|
Der Deploy lief trotzdem als `Completed` durch, das Geraet blieb
|
||||||
|
**unveraendert**, und im Log stand nur die Schlusszeile — von den 38
|
||||||
|
`:if`-Statements hat **kein einziger** Zweig geloggt, also ist auch kein
|
||||||
|
`set` gelaufen.
|
||||||
|
|
||||||
|
* in `[:len …]` und direkt vor `find`: **`/ip dhcp-server lease find where …`**
|
||||||
|
(kein Klammerpaar um `find`) — Muster aus `_pool_set`/`job_tp45_agh_dhcp`
|
||||||
|
* als Argument einer Vollpfad-Kommandozeile: `set [find where …]`
|
||||||
|
— diese Form ist in `_pool_set` bewiesen und bleibt.
|
||||||
|
|
||||||
|
⚠️ **Merkregel:** `Completed` am Plan und `SUCCESS` am Job beweisen hier
|
||||||
|
nichts. Der Beweis ist die **Zahl der Log-Zeilen** (jeder Zweig loggt) oder
|
||||||
|
ein Geraete-Read. Beides gehoert zur Abnahme — genau wie beim
|
||||||
|
`pool-v20`-Deploy, der ohne Gegenprobe als gelungen gegolten haette.
|
||||||
|
"""
|
||||||
|
find = f'find where mac-address={mac}'
|
||||||
|
arg = f'[find where mac-address={mac}]'
|
||||||
|
return (f':if ([:len [/ip dhcp-server lease {find}]] = 1) do={{ '
|
||||||
|
f'/ip dhcp-server lease set {arg} address={soll}; '
|
||||||
|
f':log info ("tp45-v30: {mac} -> {soll}") }} '
|
||||||
|
f'else={{ :log info ("tp45-v30: {mac} NICHT EINDEUTIG -- nicht angefasst") }}')
|
||||||
|
|
||||||
|
|
||||||
|
def job_tp45_v30_leases():
|
||||||
|
"""TP45 Schicht B2 (2026-09-21): die VLAN30-Reservierungen ins Oktett-Schema.
|
||||||
|
|
||||||
|
36 Reservierungen wandern aus dem DHCP-Pool-Fenster (.202-.244, .5, .8) in die
|
||||||
|
Schema-Baender: `.11-.14` fest verbaute Infrastruktur, `.151-.176` Shelly-Block,
|
||||||
|
`.177-.196` App-Services. Dazu zwei Sonderfaelle:
|
||||||
|
|
||||||
|
* `pve-homematic-raspi-ccu` (MAC BC:24:11:8E:0A:57) wird ENTFERNT — eine
|
||||||
|
Leiche aus dem Core-Cutover (`status=waiting`, `last-seen=never`). Die CCU
|
||||||
|
ist live in VLAN20 (.20.232, ARP + HTTP 200 belegt, Caddys `ccu.bf` proxyt
|
||||||
|
dorthin); die VLAN30-Reservierung ist ein Zombie.
|
||||||
|
* `SONOFF-NSPanelPro` hat GAR KEINE Reservierung, nur eine dynamische Lease
|
||||||
|
auf `.249`. Wird per `make-static` gepinnt und auf `.12` gezogen.
|
||||||
|
|
||||||
|
⚠️ **Reihenfolge:** dieser Job gehoert VOR `tp45-pool-v30`. Die Verengung auf
|
||||||
|
`.200-.249` setzt voraus, dass keine Reservierung mehr im Schema-Band liegt —
|
||||||
|
sonst zieht der Pool Adressen, die schon vergeben sind.
|
||||||
|
|
||||||
|
⚠️ **Kein Ausfall, kein Lockout:** ueber DHCP-Reservierungen umzuziehen heisst,
|
||||||
|
dass die Geraete beim naechsten Lease-Renew selbst nachziehen (lease-time 1d).
|
||||||
|
Nur zwei der 36 sind im Moment ueberhaupt `bound` (SwitchBot, DTUBI); alle
|
||||||
|
anderen stehen auf `last-seen=never`. Nichts wird hart umgeschaltet.
|
||||||
|
|
||||||
|
⚠️ **`.150` (Ender3V3KE) und `.180` (espressif/Airfryer) bleiben, wo sie sind** —
|
||||||
|
sie liegen bereits im App-Band und werden bewusst nicht bewegt.
|
||||||
|
|
||||||
|
Rueckweg: `MOVES` traegt Ist UND Soll je MAC, das inverse `set` ist also
|
||||||
|
mechanisch; zusaetzlich liegt vor dem Deploy ein binaeres Backup auf dem Core.
|
||||||
|
"""
|
||||||
|
mod = _tp45_leases_module()
|
||||||
|
lines = [_lease_move(mac, soll) for mac, (_ist, soll) in sorted(mod.MOVES.items())]
|
||||||
|
|
||||||
|
for mac in sorted(mod.REMOVE):
|
||||||
|
find = f'find where mac-address={mac}'
|
||||||
|
arg = f'[find where mac-address={mac}]'
|
||||||
|
lines.append(f':if ([:len [/ip dhcp-server lease {find}]] = 1) do={{ '
|
||||||
|
f'/ip dhcp-server lease remove {arg}; '
|
||||||
|
f':log info ("tp45-v30: CCU-Leiche {mac} entfernt") }} '
|
||||||
|
f'else={{ :log info ("tp45-v30: CCU-Leiche schon weg") }}')
|
||||||
|
|
||||||
|
for add in mod.ADDS:
|
||||||
|
mac = re.search(r"mac-address=(\S+)", add).group(1)
|
||||||
|
ip = re.search(r"address=(\S+)", add).group(1)
|
||||||
|
cmt = re.search(r"comment=(\S+)", add).group(1)
|
||||||
|
find = f'find where mac-address={mac}'
|
||||||
|
arg = f'[find where mac-address={mac}]'
|
||||||
|
# make-static VOR set: auf einer dynamischen Lease ist `set address=` wirkungslos
|
||||||
|
# bzw. wird beim naechsten Renew ohnehin verworfen -- erst statisch, dann pinnen.
|
||||||
|
lines.append(f':if ([:len [/ip dhcp-server lease {find}]] = 1) do={{ '
|
||||||
|
f'/ip dhcp-server lease make-static {arg}; '
|
||||||
|
f'/ip dhcp-server lease set {arg} address={ip} comment="{cmt}"; '
|
||||||
|
f':log info ("tp45-v30: NSPanelPro statisch auf {ip}") }} '
|
||||||
|
f'else={{ :log info ("tp45-v30: NSPanelPro hat keine Lease -- spaeter nachziehen") }}')
|
||||||
|
|
||||||
|
# Abnahme-Zeile. `address=` ist ein echter Eigenschaftsvergleich (kein Regex) --
|
||||||
|
# bewusst NICHT `~"10.40.30.11"`, das traefe auch .110-.119.
|
||||||
|
lines.append(
|
||||||
|
':log info ("tp45-v30: .11=" . [:tostr [:len [/ip dhcp-server lease '
|
||||||
|
'find where address=10.40.30.11]]] . " .12=" . [:tostr [:len [/ip dhcp-server '
|
||||||
|
'lease find where address=10.40.30.12]]] . " .151=" . [:tostr [:len '
|
||||||
|
'[/ip dhcp-server lease find where address=10.40.30.151]]] . " .196=" . '
|
||||||
|
'[:tostr [:len [/ip dhcp-server lease find where address=10.40.30.196]]])')
|
||||||
|
|
||||||
|
return [(["crs326-core"], "\n".join(lines), "TP45-v30-leases")]
|
||||||
|
|
||||||
|
|
||||||
JOBS = {
|
JOBS = {
|
||||||
"drift-cleanup": job_drift_cleanup,
|
"drift-cleanup": job_drift_cleanup,
|
||||||
"mgmt-acl": job_mgmt_acl,
|
"mgmt-acl": job_mgmt_acl,
|
||||||
@@ -604,6 +731,7 @@ JOBS = {
|
|||||||
"tp45-agh-fw": job_tp45_agh_fw,
|
"tp45-agh-fw": job_tp45_agh_fw,
|
||||||
"tp45-agh-dhcp": job_tp45_agh_dhcp,
|
"tp45-agh-dhcp": job_tp45_agh_dhcp,
|
||||||
"tp45-pool-v20": job_tp45_pool_v20,
|
"tp45-pool-v20": job_tp45_pool_v20,
|
||||||
|
"tp45-v30-leases": job_tp45_v30_leases,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user