Implements set_hostname on ProxmoxSystemMixin: 1. Writes /etc/hostname (short name, base64-safe transfer) 2. Replaces old hostname in /etc/hosts via Python regex + base64 3. Updates /etc/mailname if present 4. Updates Postfix myhostname via postconf -e if installed 5. Applies hostname immediately at runtime via hostname(1) 6. Regenerates Proxmox node TLS certificate via pvecm updatecerts -f (falls back to pvenode cert create → pveproxy restart) Accepts bare hostname or FQDN. A reboot is required for the Proxmox node name to update in the web UI / cluster — the driver logs this. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
108 lines
4.2 KiB
Python
108 lines
4.2 KiB
Python
# Copyright 2025 The NetOrk Authors
|
|
#
|
|
# 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.
|
|
|
|
"""LLDP-related NAPALM getters for Proxmox VE nodes."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import re
|
|
from typing import Any
|
|
|
|
|
|
_JsonDict = dict[str, Any]
|
|
|
|
|
|
class ProxmoxLLDPMixin:
|
|
"""Mixin providing LLDP NAPALM methods."""
|
|
|
|
def get_lldp_neighbors(self) -> dict[str, list[_JsonDict]]:
|
|
"""Return LLDP neighbours (requires lldpd on the Proxmox node)."""
|
|
result: dict[str, list[_JsonDict]] = {}
|
|
raw = self._exec_ssh_command(
|
|
"lldpcli show neighbors summary 2>/dev/null || true"
|
|
)
|
|
current_iface = ""
|
|
for line in raw.splitlines():
|
|
m_iface = re.match(r"^\s*Interface:\s+(\S+?),?\s", line)
|
|
if m_iface:
|
|
current_iface = m_iface.group(1)
|
|
result.setdefault(current_iface, [])
|
|
continue
|
|
m_sys = re.match(r"^\s*SysName:\s+(.+)", line)
|
|
m_port = re.match(r"^\s*PortID:\s+\S+\s+(.+)", line)
|
|
if m_sys and current_iface:
|
|
hostname = m_sys.group(1).strip()
|
|
if result[current_iface]:
|
|
result[current_iface][-1]["hostname"] = hostname
|
|
else:
|
|
result[current_iface].append({"hostname": hostname, "port": ""})
|
|
if m_port and current_iface and result[current_iface]:
|
|
result[current_iface][-1]["port"] = m_port.group(1).strip()
|
|
return result
|
|
|
|
def get_lldp_neighbors_detail(self, interface: str = "") -> dict[str, list[_JsonDict]]:
|
|
"""Return detailed LLDP neighbour information."""
|
|
result: dict[str, list[_JsonDict]] = {}
|
|
raw = self._exec_ssh_command(
|
|
"lldpcli show neighbors details 2>/dev/null || true"
|
|
)
|
|
current_iface = ""
|
|
current_entry: _JsonDict = {}
|
|
|
|
def _flush():
|
|
if current_iface and current_entry:
|
|
result.setdefault(current_iface, []).append(current_entry.copy())
|
|
|
|
for line in raw.splitlines():
|
|
m_iface = re.match(r"^\s*Interface:\s+(\S+?),?\s", line)
|
|
if m_iface:
|
|
_flush()
|
|
current_iface = m_iface.group(1)
|
|
if interface and current_iface != interface:
|
|
current_iface = ""
|
|
current_entry = {
|
|
"parent_interface": "",
|
|
"remote_chassis_id": "",
|
|
"remote_system_name": "",
|
|
"remote_port": "",
|
|
"remote_port_description": "",
|
|
"remote_system_description": "",
|
|
"remote_system_capab": [],
|
|
"remote_system_enable_capab": [],
|
|
}
|
|
continue
|
|
if not current_iface:
|
|
continue
|
|
for key, pattern in (
|
|
("remote_chassis_id", r"ChassisID:\s+\S+\s+(.+)"),
|
|
("remote_system_name", r"SysName:\s+(.+)"),
|
|
("remote_port", r"PortID:\s+\S+\s+(.+)"),
|
|
("remote_port_description", r"PortDescr:\s+(.+)"),
|
|
("remote_system_description", r"SysDescr:\s+(.+)"),
|
|
):
|
|
m = re.match(rf"^\s*{pattern}", line)
|
|
if m:
|
|
current_entry[key] = m.group(1).strip()
|
|
|
|
m_cap = re.match(r"^\s*Capability:\s+(\S+),\s+(\w+)", line)
|
|
if m_cap:
|
|
cap = m_cap.group(1).lower()
|
|
enabled = m_cap.group(2).lower() == "on"
|
|
current_entry["remote_system_capab"].append(cap)
|
|
if enabled:
|
|
current_entry["remote_system_enable_capab"].append(cap)
|
|
|
|
_flush()
|
|
return result
|