最近优化的,增加了日志功能

import pandas as pd

from paramiko import SSHClient, AutoAddPolicy

from datetime import date

import os

import logging

import ipaddress

from time import sleep

from concurrent.futures import ThreadPoolExecutor

try:

    from ncclient import manager

except ImportError:

    logging.error("缺少'ncclient'模块,请使用 `pip install ncclient` 进行安装。")

    raise

# 配置日志

logging.basicConfig(

    level=logging.INFO,

    format="%(asctime)s - %(levelname)s - %(message)s",

    handlers=[logging.FileHandler("automation.log"), logging.StreamHandler()]

)

# 配置文件路径

EXCEL_FILE = r"devices.xlsx"  # Windows 路径需要加 r

BACKUP_DIR = os.path.join(".", "backups", str(date.today()))

if not os.path.exists(BACKUP_DIR):

    os.makedirs(BACKUP_DIR)

YANG_XML_TEMPLATE = """

     

       

         

           

              {{LogName}}

             

                {{LogServer}}

                43

             

           

         

       

     

"""

class NetworkDevice:

    def __init__(self, ip, username, password):

        self.server = ip

        self.username = username

        self.password = password

        self.client = self._connect_ssh()

    def _connect_ssh(self):

        """建立SSH连接,带重试机制"""

        attempts = 0

        while attempts < 3:

            try:

                client = SSHClient()

                client.set_missing_host_key_policy(AutoAddPolicy)

                client.connect(self.server, username=self.username, password=self.password)

                return client

            except Exception as e:

                attempts += 1

                logging.warning(f"SSH连接失败,正在重试 {attempts}/3 次: {e}")

                sleep(5)

        raise ConnectionError(f"无法连接到设备 {self.server},请检查网络或凭据。")

    def execute_commands(self, commands):

        """通过SSH执行命令"""

        with self.client.invoke_shell() as shell:

            for cmd in commands:

                logging.info(f"执行命令: {cmd.strip()}")

                shell.send(cmd + "\n")

                sleep(2)

                output = shell.recv(99999).decode()

                logging.info(output)

    def save_config(self, filename):

        """保存设备配置"""

        cmd = f"save force {filename}.zip\n"

        self.execute_commands([cmd])

    def download_config(self, remote_file, local_file):

        """通过SFTP下载设备配置"""

        try:

            sftp = self.client.open_sftp()

            sftp.get(remote_file, local_file)

            logging.info(f"配置已下载到: {local_file}")

        except Exception as e:

            logging.error(f"配置下载失败: {e}")

    def close(self):

        self.client.close()

def configure_netconf(xml, device_ip, username, password):

    """通过Netconf配置设备"""

    try:

        with manager.connect(

            host=device_ip,

            username=username,

            password=password,

            hostkey_verify=False,

            device_params={"name": "huawei"}

        ) as m:

            response = m.edit_config(target="running", config=xml)

            logging.info(f"Netconf配置结果: {response}")

    except Exception as e:

        logging.error(f"Netconf配置失败: {e}")

def validate_device_info(device):

    """验证设备信息格式"""

    try:

        # 检查IP地址格式

        ipaddress.ip_address(device["IP"])

    except ValueError:

        raise ValueError(f"无效的IP地址: {device['IP']}")

    # 检查端口号是否为纯数字(如果有端口字段)

    if "Port" in device and not str(device["Port"]).isdigit():

        raise ValueError(f"无效的端口号: {device['Port']}")

def process_device(device):

    """处理单个设备:备份、巡检、配置Netconf"""

    try:

        validate_device_info(device)

        device_instance = NetworkDevice(device["IP"], device["SSHUser"], device["SSHPassword"])

        # 初次备份

        today = str(date.today())

        backup_filename = f"{today}_{device['DeviceName']}"

        device_instance.save_config(backup_filename)

        device_instance.download_config(f"{backup_filename}.zip", os.path.join(BACKUP_DIR, f"{backup_filename}.bak"))

        # 配置Netconf日志服务器

        xml_config = YANG_XML_TEMPLATE.replace("{{LogName}}", device["LogName"]).replace("{{LogServer}}", device["LogServer"])

        configure_netconf(xml_config, device["IP"], device["NetConfUser"], device["NetConfPassword"])

    except Exception as e:

        logging.error(f"设备 {device['DeviceName']} 处理失败: {e}")

    finally:

        if 'device_instance' in locals():

            device_instance.close()

def main():

    """主流程:从Excel读取设备信息并并发处理"""

    try:

        # 从Excel加载设备信息,忽略列名中的空格

        devices = pd.read_excel(EXCEL_FILE).rename(columns=lambda x: x.strip()).to_dict(orient="records")

        with ThreadPoolExecutor(max_workers=10) as executor:

            futures = [executor.submit(process_device, device) for device in devices]

            for future in futures:

                try:

                    future.result()

                except Exception as e:

                    logging.error(f"设备处理失败: {e}")

    except Exception as e:

        logging.error(f"运行失败: {e}")

if __name__ == "__main__":

    main()

你可能感兴趣的:(python)