EVE-NG实验快照脚本

作者:waMoYu 发布时间: 2026-01-07 阅读量:6 评论数:0

嗯,同样豆包写的

用于实现EVE-NG中单个、多个节点或整个LAB(LAB内所有快照)的内置快照。

本质是qemu-img snapshot -c命令实现,并且是基于tmp中qcow2做的,所以擦除镜像后快照也没了

#!/bin/bash

# 颜色定义
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
WHITE='\033[1;37m'
NC='\033[0m'

# 默认参数
USER_ID="0"
LAB_UUID=""
BASE_LAB_PATH="/opt/unetlab/tmp"

# 日志函数
log_info() {
    echo -e "${WHITE}[$(date '+%Y-%m-%d %H:%M')] [信息] $1${NC}"
}

log_warn() {
    echo -e "${YELLOW}[$(date '+%Y-%m-%d %H:%M')] [警告] $1${NC}"
}

log_error() {
    echo -e "${RED}[$(date '+%Y-%m-%d %H:%M')] [错误] $1${NC}"
}

log_success() {
    echo -e "${GREEN}[$(date '+%Y-%m-%d %H:%M')] [成功] $1${NC}"
}

# 显示用法
show_usage() {
    echo "用法: $0 -i 用户ID -u 实验UUID"
    echo "示例: $0 -i 0 -u 19bf7a61-dfa1-4800-99d3-fc10da453fe4"
    echo "参数说明:"
    echo "  -i 用户ID (admin默认是0)"
    echo "  -u 实验UUID"
    exit 1
}

# 解析命令行参数
parse_arguments() {
    while getopts "i:u:h" opt; do
        case $opt in
            i)
                USER_ID="$OPTARG"
                ;;
            u)
                LAB_UUID="$OPTARG"
                ;;
            h)
                show_usage
                ;;
            \?)
                log_error "无效参数: -$OPTARG"
                show_usage
                ;;
        esac
    done
    
    # 验证必需参数
    if [ -z "$USER_ID" ] || [ -z "$LAB_UUID" ]; then
        log_error "缺少必需参数"
        show_usage
    fi
    
    # 更新基础路径
    BASE_LAB_PATH="/opt/unetlab/tmp/$USER_ID"
}

# 检查路径是否存在
check_path_exists() {
    if [ ! -d "$1" ]; then
        return 1
    fi
    return 0
}

# 验证UUID是否存在
validate_uuid() {
    local uuid="$1"
    local lab_path="${BASE_LAB_PATH}/${uuid}"
    
    if ! check_path_exists "$lab_path"; then
        log_error "实验UUID不存在: $uuid"
        return 1
    fi
    
    return 0
}

# 查找实验室UNL文件
find_lab_unl_file() {
    local target_uuid="$1"
    local labs_path="/opt/unetlab/labs"
    local found_file=""
    
    # 使用while循环处理find输出,处理特殊字符
    while IFS= read -r -d '' unl_file; do
        # 检查文件是否可读
        if [ ! -r "$unl_file" ]; then
            continue
        fi
        
        # 使用临时变量存储结果
        local lab_line
        lab_line=$(grep -m1 '<lab' "$unl_file" 2>/dev/null)
        
        if [ -n "$lab_line" ]; then
            local lab_uuid
            # 使用awk提取UUID
            lab_uuid=$(echo "$lab_line" | awk -F'id="' '{print $2}' | awk -F'"' '{print $1}')
            
            if [ "$lab_uuid" = "$target_uuid" ]; then
                found_file="$unl_file"
                break
            fi
        fi
    done < <(find "$labs_path" -name "*.unl" -type f -print0 2>/dev/null)
    
    if [ -n "$found_file" ]; then
        echo "$found_file"
        return 0
    else
        return 1
    fi
}

# 获取实验室名称
get_lab_name() {
    local unl_file="$1"
    if [ -r "$unl_file" ]; then
        grep -m1 '<lab' "$unl_file" 2>/dev/null | sed -n 's/.*name="\([^"]*\).*/\1/p'
    fi
}

# 获取节点信息
get_node_info() {
    local unl_file="$1"
    if [ -r "$unl_file" ]; then
        grep '<node' "$unl_file" 2>/dev/null | awk -F' ' '
        {
            id = name = type = uuid = ""
            for(i=1; i<=NF; i++) {
                if($i ~ /^id=/) {gsub(/^id="|"$/, "", $i); id = $i}
                if($i ~ /^name=/) {gsub(/^name="|"$/, "", $i); name = $i}
                if($i ~ /^type=/) {gsub(/^type="|"$/, "", $i); type = $i}
                if($i ~ /^uuid=/) {gsub(/^uuid="|"$/, "", $i); uuid = $i}
            }
            if(id != "" && name != "") {
                print id "|" name "|" type "|" uuid
            }
        }'
    fi
}

# 检查节点是否运行中
is_node_running() {
    local node_uuid="$1"
    if [ -n "$node_uuid" ]; then
        if ps aux | grep -v grep | grep -q "$node_uuid"; then
            return 0  # 运行中
        fi
    fi
    return 1  # 未运行
}

# 检查节点是否可以执行快照操作
check_snapshot_allowed() {
    local uuid="$1"
    local node_id="$2"
    local operation="$3"  # create, restore, delete
    
    # 获取节点UUID
    local unl_file=$(find_lab_unl_file "$uuid")
    if [ -n "$unl_file" ]; then
        local node_info=$(get_node_info "$unl_file")
        local node_line=$(echo "$node_info" | grep "^$node_id|")
        if [ -n "$node_line" ]; then
            local node_uuid=$(echo "$node_line" | cut -d'|' -f4)
            
            if is_node_running "$node_uuid"; then
                log_error "节点 $node_id 正在运行中,无法执行快照${operation}操作!请先关闭节点。"
                return 1
            fi
        fi
    fi
    
    return 0
}

# 显示实验室和节点信息
show_lab_node_info() {
    local uuid="$1"
    
    log_info "正在获取实验室信息..."
    
    # 查找UNL文件
    local unl_file=$(find_lab_unl_file "$uuid")
    if [ -z "$unl_file" ]; then
        log_warn "未找到实验室UNL文件,仅显示基础节点信息"
        # 显示实验目录下的节点ID
        local lab_path="${BASE_LAB_PATH}/${uuid}"
        if [ -d "$lab_path" ]; then
            echo "基础节点信息(通过扫描实验目录):"
            echo "======================================"
            local node_dirs=$(find "$lab_path" -maxdepth 1 -type d -name "[0-9]*" | sort -V)
            if [ -n "$node_dirs" ]; then
                echo "节点ID:"
                for node_dir in $node_dirs; do
                    local node_id=$(basename "$node_dir")
                    echo "  $node_id"
                done
            else
                echo "未找到任何节点"
            fi
            echo "======================================"
        else
            log_error "实验目录不存在: $lab_path"
        fi
        return 1
    fi
    
    local lab_name=$(get_lab_name "$unl_file")
    log_info "实验室名称: $lab_name"
    log_info "实验室UUID: $uuid"
    echo "======================================"
    
    # 获取节点信息
    local node_info=$(get_node_info "$unl_file")
    if [ -n "$node_info" ]; then
        echo "节点列表:"
        echo "--------------------------------------"
        printf "%-8s %-20s %-12s %-10s\n" "节点ID" "节点名称" "节点类型" "状态"
        echo "--------------------------------------"
        
        while IFS='|' read -r node_id node_name node_type node_uuid; do
            local status="${GREEN}关机${NC}"
            if is_node_running "$node_uuid"; then
                status="${RED}运行中${NC}"
            fi
            printf "%-8s %-20s %-12s %b\n" "$node_id" "$node_name" "$node_type" "$status"
        done <<< "$node_info"
    else
        log_warn "未在UNL文件中找到节点信息"
    fi
    
    echo "======================================"
}

# 验证节点ID是否存在
validate_node_id() {
    local uuid="$1"
    local node_id="$2"
    local node_path="${BASE_LAB_PATH}/${uuid}/${node_id}"
    
    if ! check_path_exists "$node_path"; then
        log_error "节点ID不存在: $node_id"
        return 1
    fi
    
    # 检查节点目录中是否有qcow2文件
    local qcow2_files=$(find "$node_path" -maxdepth 1 -name "*.qcow2" 2>/dev/null)
    if [ -z "$qcow2_files" ]; then
        log_error "节点 $node_id 中没有找到qcow2文件"
        return 1
    fi
    
    return 0
}

# 检查bin文件并警告
check_bin_files() {
    local uuid="$1"
    local node_id="$2"
    local node_path="${BASE_LAB_PATH}/${uuid}/${node_id}"
    
    local bin_files=$(find "$node_path" -maxdepth 1 -name "*.bin" 2>/dev/null)
    if [ -n "$bin_files" ]; then
        log_warn "节点 $node_id 中发现bin文件,已忽略"
        return 1
    fi
}

# 获取节点中的所有qcow2文件
get_qcow2_files() {
    local uuid="$1"
    local node_id="$2"
    local node_path="${BASE_LAB_PATH}/${uuid}/${node_id}"
    
    find "$node_path" -maxdepth 1 -name "*.qcow2" 2>/dev/null
}

# 显示快照信息
show_snapshot_info() {
    local uuid="$1"
    local node_id="$2"
    local node_path="${BASE_LAB_PATH}/${uuid}/${node_id}"
    local qcow2_files=$(find "$node_path" -maxdepth 1 -name "*.qcow2" 2>/dev/null)
    
    if [ -n "$qcow2_files" ]; then
        echo "=== 节点 $node_id 快照信息 ==="
        for qcow2_file in $qcow2_files; do
            echo "文件: $(basename "$qcow2_file")"
            qemu-img snapshot -l "$qcow2_file"
            echo
        done
    fi
}

# 节点快照 - 创建
node_snapshot_create() {
    local uuid="$LAB_UUID"
    
    # 显示实验室和节点信息
    show_lab_node_info "$uuid"
    
    log_info "[节点快照-节点快照LOG] -> 请输入节点ID(多个则以,间隔)"
    read -p "节点ID:" node_ids_input
    
    # 处理节点ID输入,支持逗号分隔
    IFS=',' read -ra node_ids <<< "$node_ids_input"
    
    local valid_nodes=()
    for node_id in "${node_ids[@]}"; do
        node_id=$(echo "$node_id" | tr -d ' ') # 去除空格
        
        if validate_node_id "$uuid" "$node_id"; then
            # 检查节点是否运行中
            if ! check_snapshot_allowed "$uuid" "$node_id" "创建"; then
                continue
            fi
            check_bin_files "$uuid" "$node_id"
            valid_nodes+=("$node_id")
        fi
    done
    
    if [ ${#valid_nodes[@]} -eq 0 ]; then
        log_error "没有有效的节点ID"
        return 1
    fi
    
    log_info "[节点快照-节点快照LOG] -> 请输入快照名称"
    read -p "快照名称:" snapshot_name
    
    if [ -z "$snapshot_name" ]; then
        log_error "快照名称不能为空"
        return 1
    fi
    
    local success_count=0
    local results=()
    
    for node_id in "${valid_nodes[@]}"; do
        local qcow2_files=$(get_qcow2_files "$uuid" "$node_id")
        
        for qcow2_file in $qcow2_files; do
            log_info "为节点 $node_id 创建快照: $qcow2_file"
            
            # 创建快照
            if qemu-img snapshot -c "$snapshot_name" "$qcow2_file" >/dev/null 2>&1; then
                # 验证快照是否创建成功
                if qemu-img snapshot -l "$qcow2_file" | grep -q "$snapshot_name"; then
                    ((success_count++))
                    results+=("节点ID $node_id 快照 ---> $snapshot_name")
                    log_success "节点 $node_id 快照创建成功"
                else
                    log_error "节点 $node_id 快照创建验证失败"
                fi
            else
                log_error "节点 $node_id 快照创建失败"
            fi
        done
    done
    
    if [ $success_count -gt 0 ]; then
        log_success "快照创建成功"
        for result in "${results[@]}"; do
            echo -e "${GREEN}$result${NC}"
        done
    else
        log_error "没有成功创建任何快照"
    fi
}

# 节点快照 - 查看信息
node_snapshot_list() {
    local uuid="$LAB_UUID"
    
    # 显示实验室和节点信息
    show_lab_node_info "$uuid"
    
    log_info "[节点快照-快照信息查看LOG] -> 请输入节点ID(多个则以,间隔)"
    read -p "节点ID:" node_ids_input
    
    IFS=',' read -ra node_ids <<< "$node_ids_input"
    
    local valid_nodes=()
    for node_id in "${node_ids[@]}"; do
        node_id=$(echo "$node_id" | tr -d ' ')
        
        if validate_node_id "$uuid" "$node_id"; then
            valid_nodes+=("$node_id")
        fi
    done
    
    if [ ${#valid_nodes[@]} -eq 0 ]; then
        log_error "没有有效的节点ID"
        return 1
    fi
    
    for node_id in "${valid_nodes[@]}"; do
        show_snapshot_info "$uuid" "$node_id"
    done
}

# 节点快照 - 恢复
node_snapshot_restore() {
    local uuid="$LAB_UUID"
    
    # 显示实验室和节点信息
    show_lab_node_info "$uuid"
    
    # 显示所有节点的快照信息
    local lab_path="${BASE_LAB_PATH}/${uuid}"
    local node_dirs=$(find "$lab_path" -maxdepth 1 -type d -name "[0-9]*" 2>/dev/null | sort -V)
    
    if [ -z "$node_dirs" ]; then
        log_error "实验中没有找到任何节点"
        return 1
    fi
    
    echo "=== 实验所有节点快照信息 ==="
    for node_dir in $node_dirs; do
        local node_id=$(basename "$node_dir")
        show_snapshot_info "$uuid" "$node_id"
    done
    
    log_info "[节点快照-恢复快照LOG] -> 请输入节点ID(多个则以,间隔)"
    read -p "节点ID:" node_ids_input
    
    IFS=',' read -ra node_ids <<< "$node_ids_input"
    
    local valid_nodes=()
    for node_id in "${node_ids[@]}"; do
        node_id=$(echo "$node_id" | tr -d ' ')
        
        if validate_node_id "$uuid" "$node_id"; then
            # 检查节点是否运行中
            if ! check_snapshot_allowed "$uuid" "$node_id" "恢复"; then
                continue
            fi
            valid_nodes+=("$node_id")
        fi
    done
    
    if [ ${#valid_nodes[@]} -eq 0 ]; then
        log_error "没有有效的节点ID"
        return 1
    fi
    
    log_info "[节点快照-恢复快照LOG] -> 请输入快照名称"
    read -p "快照名称:" snapshot_name
    
    if [ -z "$snapshot_name" ]; then
        log_error "快照名称不能为空"
        return 1
    fi
    
    local success_count=0
    local results=()
    
    for node_id in "${valid_nodes[@]}"; do
        local qcow2_files=$(get_qcow2_files "$uuid" "$node_id")
        
        for qcow2_file in $qcow2_files; do
            log_info "恢复节点 $node_id 快照: $qcow2_file"
            
            # 检查快照是否存在
            if qemu-img snapshot -l "$qcow2_file" | grep -q "$snapshot_name"; then
                # 恢复快照
                if qemu-img snapshot -a "$snapshot_name" "$qcow2_file" >/dev/null 2>&1; then
                    ((success_count++))
                    results+=("节点ID $node_id 恢复 ---> $snapshot_name")
                    log_success "节点 $node_id 快照恢复成功"
                else
                    log_error "节点 $node_id 快照恢复失败"
                fi
            else
                log_error "节点 $node_id 中未找到快照: $snapshot_name"
            fi
        done
    done
    
    if [ $success_count -gt 0 ]; then
        log_success "快照恢复成功"
        for result in "${results[@]}"; do
            echo -e "${GREEN}$result${NC}"
        done
    else
        log_error "没有成功恢复任何快照"
    fi
}

# 节点快照 - 删除
node_snapshot_delete() {
    local uuid="$LAB_UUID"
    
    # 显示实验室和节点信息
    show_lab_node_info "$uuid"
    
    # 显示所有节点的快照信息
    local lab_path="${BASE_LAB_PATH}/${uuid}"
    local node_dirs=$(find "$lab_path" -maxdepth 1 -type d -name "[0-9]*" 2>/dev/null | sort -V)
    
    if [ -z "$node_dirs" ]; then
        log_error "实验中没有找到任何节点"
        return 1
    fi
    
    echo "=== 实验中所有节点快照信息 ==="
    for node_dir in $node_dirs; do
        local node_id=$(basename "$node_dir")
        show_snapshot_info "$uuid" "$node_id"
    done
    
    log_info "[节点快照-删除快照LOG] -> 请输入节点ID(多个则以,间隔)"
    read -p "节点ID:" node_ids_input
    
    IFS=',' read -ra node_ids <<< "$node_ids_input"
    
    local valid_nodes=()
    for node_id in "${node_ids[@]}"; do
        node_id=$(echo "$node_id" | tr -d ' ')
        
        if validate_node_id "$uuid" "$node_id"; then
            # 检查节点是否运行中
            if ! check_snapshot_allowed "$uuid" "$node_id" "删除"; then
                continue
            fi
            valid_nodes+=("$node_id")
        fi
    done
    
    if [ ${#valid_nodes[@]} -eq 0 ]; then
        log_error "没有有效的节点ID"
        return 1
    fi
    
    log_info "[节点快照-删除快照LOG] -> 请输入要删除的快照名称"
    read -p "快照名称:" snapshot_name
    
    if [ -z "$snapshot_name" ]; then
        log_error "快照名称不能为空"
        return 1
    fi
    
    local success_count=0
    local results=()
    
    for node_id in "${valid_nodes[@]}"; do
        local qcow2_files=$(get_qcow2_files "$uuid" "$node_id")
        
        for qcow2_file in $qcow2_files; do
            log_info "删除节点 $node_id 快照: $qcow2_file"
            
            # 检查快照是否存在
            if qemu-img snapshot -l "$qcow2_file" | grep -q "$snapshot_name"; then
                # 删除快照
                if qemu-img snapshot -d "$snapshot_name" "$qcow2_file" >/dev/null 2>&1; then
                    ((success_count++))
                    results+=("节点ID $node_id 删除 ---> $snapshot_name")
                    log_success "节点 $node_id 快照删除成功"
                else
                    log_error "节点 $node_id 快照删除失败"
                fi
            else
                log_error "节点 $node_id 中未找到快照: $snapshot_name"
            fi
        done
    done
    
    if [ $success_count -gt 0 ]; then
        log_success "快照删除成功"
        for result in "${results[@]}"; do
            echo -e "${GREEN}$result${NC}"
        done
    else
        log_error "没有成功删除任何快照"
    fi
}

# 实验快照 - 创建
lab_snapshot_create() {
    local uuid="$LAB_UUID"
    
    # 显示实验室和节点信息
    show_lab_node_info "$uuid"
    
    log_info "[实验快照-实验快照LOG] -> 请输入快照名称"
    read -p "快照名称:" snapshot_name
    
    if [ -z "$snapshot_name" ]; then
        log_error "快照名称不能为空"
        return 1
    fi
    
    local lab_path="${BASE_LAB_PATH}/${uuid}"
    local success_count=0
    local skipped_count=0
    local results=()
    local skipped_nodes=()
    
    # 获取实验中的所有节点目录
    local node_dirs=$(find "$lab_path" -maxdepth 1 -type d -name "[0-9]*" 2>/dev/null | sort -V)
    
    if [ -z "$node_dirs" ]; then
        log_error "实验中没有找到任何节点"
        return 1
    fi
    
    # 获取节点运行状态信息
    local unl_file=$(find_lab_unl_file "$uuid")
    local node_info=""
    if [ -n "$unl_file" ]; then
        node_info=$(get_node_info "$unl_file")
    fi
    
    for node_dir in $node_dirs; do
        local node_id=$(basename "$node_dir")
        
        # 检查节点是否运行中
        local node_running=0
        if [ -n "$node_info" ]; then
            local node_line=$(echo "$node_info" | grep "^$node_id|")
            if [ -n "$node_line" ]; then
                local node_uuid=$(echo "$node_line" | cut -d'|' -f4)
                if is_node_running "$node_uuid"; then
                    node_running=1
                fi
            fi
        fi
        
        if [ $node_running -eq 1 ]; then
            log_warn "节点 $node_id 正在运行中,已跳过"
            skipped_nodes+=("$node_id")
            ((skipped_count++))
            continue
        fi
        
        # 直接检查节点路径和qcow2文件
        local node_path="${BASE_LAB_PATH}/${uuid}/${node_id}"
        local qcow2_files=$(find "$node_path" -maxdepth 1 -name "*.qcow2" 2>/dev/null)
        
        if [ -n "$qcow2_files" ]; then
            check_bin_files "$uuid" "$node_id"
            
            for qcow2_file in $qcow2_files; do
                log_info "为节点 $node_id 创建快照: $qcow2_file"
                
                if qemu-img snapshot -c "$snapshot_name" "$qcow2_file" >/dev/null 2>&1; then
                    if qemu-img snapshot -l "$qcow2_file" | grep -q "$snapshot_name"; then
                        ((success_count++))
                        results+=("节点ID $node_id 快照 ---> $snapshot_name")
                        log_success "节点 $node_id 快照创建成功"
                    else
                        log_error "节点 $node_id 快照创建验证失败"
                    fi
                else
                    log_error "节点 $node_id 快照创建失败"
                fi
            done
        else
            log_warn "节点 $node_id 中没有找到qcow2文件,已跳过"
        fi
    done
    
    # 显示操作结果摘要
    echo "=== 操作结果摘要 ==="
    if [ ${#skipped_nodes[@]} -gt 0 ]; then
        log_warn "跳过的运行中节点ID: ${skipped_nodes[*]}"
    fi
    
    if [ $success_count -gt 0 ]; then
        log_success "实验快照创建成功"
        for result in "${results[@]}"; do
            echo -e "${GREEN}$result${NC}"
        done
    else
        if [ $skipped_count -eq ${#node_dirs[@]} ]; then
            log_error "所有节点都在运行中,没有执行任何快照操作"
        else
            log_error "没有成功创建任何快照"
        fi
    fi
}

# 实验快照 - 查看信息
lab_snapshot_list() {
    local uuid="$LAB_UUID"
    
    # 显示实验室和节点信息
    show_lab_node_info "$uuid"
    
    local lab_path="${BASE_LAB_PATH}/${uuid}"
    local node_dirs=$(find "$lab_path" -maxdepth 1 -type d -name "[0-9]*" 2>/dev/null | sort -V)
    
    if [ -z "$node_dirs" ]; then
        log_error "实验中没有找到任何节点"
        return 1
    fi
    
    for node_dir in $node_dirs; do
        local node_id=$(basename "$node_dir")
        show_snapshot_info "$uuid" "$node_id"
    done
}

# 实验快照 - 恢复
lab_snapshot_restore() {
    local uuid="$LAB_UUID"
    
    # 显示实验室和节点信息
    show_lab_node_info "$uuid"
    
    # 显示所有节点的快照信息
    local lab_path="${BASE_LAB_PATH}/${uuid}"
    local node_dirs=$(find "$lab_path" -maxdepth 1 -type d -name "[0-9]*" 2>/dev/null | sort -V)
    
    if [ -z "$node_dirs" ]; then
        log_error "实验中没有找到任何节点"
        return 1
    fi
    
    echo "=== 实验所有节点快照信息 ==="
    for node_dir in $node_dirs; do
        local node_id=$(basename "$node_dir")
        show_snapshot_info "$uuid" "$node_id"
    done
    
    log_info "[实验快照-恢复快照LOG] -> 请输入快照名称"
    read -p "快照名称:" snapshot_name
    
    if [ -z "$snapshot_name" ]; then
        log_error "快照名称不能为空"
        return 1
    fi
    
    local success_count=0
    local skipped_count=0
    local results=()
    local skipped_nodes=()
    
    # 获取节点运行状态信息
    local unl_file=$(find_lab_unl_file "$uuid")
    local node_info=""
    if [ -n "$unl_file" ]; then
        node_info=$(get_node_info "$unl_file")
    fi
    
    for node_dir in $node_dirs; do
        local node_id=$(basename "$node_dir")
        
        # 检查节点是否运行中
        local node_running=0
        if [ -n "$node_info" ]; then
            local node_line=$(echo "$node_info" | grep "^$node_id|")
            if [ -n "$node_line" ]; then
                local node_uuid=$(echo "$node_line" | cut -d'|' -f4)
                if is_node_running "$node_uuid"; then
                    node_running=1
                fi
            fi
        fi
        
        if [ $node_running -eq 1 ]; then
            log_warn "节点 $node_id 正在运行中,已跳过"
            skipped_nodes+=("$node_id")
            ((skipped_count++))
            continue
        fi
        
        local node_path="${BASE_LAB_PATH}/${uuid}/${node_id}"
        local qcow2_files=$(find "$node_path" -maxdepth 1 -name "*.qcow2" 2>/dev/null)
        
        if [ -n "$qcow2_files" ]; then
            for qcow2_file in $qcow2_files; do
                log_info "恢复节点 $node_id 快照: $qcow2_file"
                
                if qemu-img snapshot -l "$qcow2_file" | grep -q "$snapshot_name"; then
                    if qemu-img snapshot -a "$snapshot_name" "$qcow2_file" >/dev/null 2>&1; then
                        ((success_count++))
                        results+=("节点ID $node_id 恢复 ---> $snapshot_name")
                        log_success "节点 $node_id 快照恢复成功"
                    else
                        log_error "节点 $node_id 快照恢复失败"
                    fi
                else
                    log_error "节点 $node_id 中未找到快照: $snapshot_name"
                fi
            done
        fi
    done
    
    # 显示操作结果摘要
    echo "=== 操作结果摘要 ==="
    if [ ${#skipped_nodes[@]} -gt 0 ]; then
        log_warn "跳过的运行中节点ID: ${skipped_nodes[*]}"
    fi
    
    if [ $success_count -gt 0 ]; then
        log_success "实验快照恢复成功"
        for result in "${results[@]}"; do
            echo -e "${GREEN}$result${NC}"
        done
    else
        if [ $skipped_count -eq ${#node_dirs[@]} ]; then
            log_error "所有节点都在运行中,没有执行任何快照恢复操作"
        else
            log_error "没有成功恢复任何快照"
        fi
    fi
}

# 实验快照 - 删除
lab_snapshot_delete() {
    local uuid="$LAB_UUID"
    
    # 显示实验室和节点信息
    show_lab_node_info "$uuid"
    
    # 显示所有节点的快照信息
    local lab_path="${BASE_LAB_PATH}/${uuid}"
    local node_dirs=$(find "$lab_path" -maxdepth 1 -type d -name "[0-9]*" 2>/dev/null | sort -V)
    
    if [ -z "$node_dirs" ]; then
        log_error "实验中没有找到任何节点"
        return 1
    fi
    
    echo "=== 实验所有节点快照信息 ==="
    for node_dir in $node_dirs; do
        local node_id=$(basename "$node_dir")
        show_snapshot_info "$uuid" "$node_id"
    done
    
    log_info "[实验快照-删除快照LOG] -> 请输入要删除的快照名称"
    read -p "快照名称:" snapshot_name
    
    if [ -z "$snapshot_name" ]; then
        log_error "快照名称不能为空"
        return 1
    fi
    
    local success_count=0
    local skipped_count=0
    local results=()
    local skipped_nodes=()
    
    # 获取节点运行状态信息
    local unl_file=$(find_lab_unl_file "$uuid")
    local node_info=""
    if [ -n "$unl_file" ]; then
        node_info=$(get_node_info "$unl_file")
    fi
    
    for node_dir in $node_dirs; do
        local node_id=$(basename "$node_dir")
        
        # 检查节点是否运行中
        local node_running=0
        if [ -n "$node_info" ]; then
            local node_line=$(echo "$node_info" | grep "^$node_id|")
            if [ -n "$node_line" ]; then
                local node_uuid=$(echo "$node_line" | cut -d'|' -f4)
                if is_node_running "$node_uuid"; then
                    node_running=1
                fi
            fi
        fi
        
        if [ $node_running -eq 1 ]; then
            log_warn "节点 $node_id 正在运行中,已跳过"
            skipped_nodes+=("$node_id")
            ((skipped_count++))
            continue
        fi
        
        local node_path="${BASE_LAB_PATH}/${uuid}/${node_id}"
        local qcow2_files=$(find "$node_path" -maxdepth 1 -name "*.qcow2" 2>/dev/null)
        
        if [ -n "$qcow2_files" ]; then
            for qcow2_file in $qcow2_files; do
                log_info "删除节点 $node_id 快照: $qcow2_file"
                
                # 检查快照是否存在
                if qemu-img snapshot -l "$qcow2_file" | grep -q "$snapshot_name"; then
                    # 删除快照
                    if qemu-img snapshot -d "$snapshot_name" "$qcow2_file" >/dev/null 2>&1; then
                        ((success_count++))
                        results+=("节点ID $node_id 删除 ---> $snapshot_name")
                        log_success "节点 $node_id 快照删除成功"
                    else
                        log_error "节点 $node_id 快照删除失败"
                    fi
                else
                    log_error "节点 $node_id 中未找到快照: $snapshot_name"
                fi
            done
        fi
    done
    
    # 显示操作结果摘要
    echo "=== 操作结果摘要 ==="
    if [ ${#skipped_nodes[@]} -gt 0 ]; then
        log_warn "跳过的运行中节点ID: ${skipped_nodes[*]}"
    fi
    
    if [ $success_count -gt 0 ]; then
        log_success "实验快照删除成功"
        for result in "${results[@]}"; do
            echo -e "${GREEN}$result${NC}"
        done
    else
        if [ $skipped_count -eq ${#node_dirs[@]} ]; then
            log_error "所有节点都在运行中,没有执行任何快照删除操作"
        else
            log_error "没有成功删除任何快照"
        fi
    fi
}

# 节点快照菜单
node_snapshot_menu() {
    while true; do
        echo "========== 节点快照 =========="
        echo "[1] 节点快照"
        echo "[2] 快照信息查看"
        echo "[3] 恢复快照"
        echo "[4] 删除快照"
        echo "[0] 返回主菜单"
        read -p "选择 [1-4]:" node_choice
        
        case $node_choice in
            1)
                echo "节点快照......"
                node_snapshot_create
                ;;
            2)
                echo "快照信息查看......"
                node_snapshot_list
                ;;
            3)
                echo "恢复快照......"
                node_snapshot_restore
                ;;
            4)
                echo "删除快照......"
                node_snapshot_delete
                ;;
            0)
                break
                ;;
            *)
                log_error "无效选择"
                ;;
        esac
        
        echo
        read -p "按回车键继续..."
        echo
    done
}

# 实验快照菜单
lab_snapshot_menu() {
    while true; do
        echo "========== 实验快照 =========="
        echo "[1] 实验快照"
        echo "[2] 快照信息查看"
        echo "[3] 恢复快照"
        echo "[4] 删除快照"
        echo "[0] 返回主菜单"
        read -p "选择 [1-4]:" lab_choice
        
        case $lab_choice in
            1)
                echo "实验快照......"
                lab_snapshot_create
                ;;
            2)
                echo "快照信息查看......"
                lab_snapshot_list
                ;;
            3)
                echo "恢复快照......"
                lab_snapshot_restore
                ;;
            4)
                echo "删除快照......"
                lab_snapshot_delete
                ;;
            0)
                break
                ;;
            *)
                log_error "无效选择"
                ;;
        esac
        
        echo
        read -p "按回车键继续..."
        echo
    done
}

# 主菜单
main_menu() {
    while true; do
        echo "========== EVE-NG 快照工具 =========="
        echo -e "${RED}警告!请在节点关机下执行,否则可能出问题!!!${NC}"
		echo -e "${RED}警告!擦除节点会清空快照!!!${NC}"
        echo "https://www.emulatedlab.com/forum.php"
		echo "v1.0.0"
        echo "==================================="
        echo "用户ID: $USER_ID"
        echo "实验UUID: $LAB_UUID"
        echo "==================================="
        echo "[1] 节点快照"
        echo "[2] 实验快照"
        echo "[0] 退出"
        read -p "选择 [1-2]:" main_choice
        
        case $main_choice in
            1)
                node_snapshot_menu
                ;;
            2)
                lab_snapshot_menu
                ;;
            0)
                log_info "退出脚本"
                echo
                echo
                exit 0
                ;;
            *)
                log_error "无效选择"
                ;;
        esac
    done
}

# 入口
if [ $# -eq 0 ]; then
    # 如果没有参数,显示提示
    show_usage
else
    # 解析参数
    parse_arguments "$@"
    
    # 检测UUID是否存在
    if ! validate_uuid "$LAB_UUID"; then
        exit 1
    fi
    
    # 进入主菜单
    main_menu
fi

评论