Add parallelism

This commit is contained in:
Pim van Pelt
2025-07-07 01:08:42 +02:00
parent c6775736ac
commit 53c7bca43e
3 changed files with 87 additions and 17 deletions

View File

@ -142,6 +142,7 @@ types:
- **`--password`**: SSH password - **`--password`**: SSH password
- **`--key-file`**: SSH private key file path - **`--key-file`**: SSH private key file path
- **`--port`**: SSH port (default: `22`) - **`--port`**: SSH port (default: `22`)
- **`--parallel`**: Maximum number of devices to process in parallel (default: `10`)
### Examples ### Examples
@ -160,6 +161,9 @@ ipng-router-backup --yaml config.yaml --output-dir /backup/network
# With password authentication # With password authentication
ipng-router-backup --yaml config.yaml --password mypassword ipng-router-backup --yaml config.yaml --password mypassword
# Process more devices in parallel
ipng-router-backup --yaml config.yaml --parallel 20
``` ```
## SSH Authentication ## SSH Authentication

View File

@ -35,6 +35,9 @@ SSH port number (default: 22)
.BR --host " \fIHOSTNAME\fR" .BR --host " \fIHOSTNAME\fR"
Specific host(s) or glob patterns to process (can be repeated, processes all if not specified) Specific host(s) or glob patterns to process (can be repeated, processes all if not specified)
.TP .TP
.BR --parallel " \fINUMBER\fR"
Maximum number of devices to process in parallel (default: 10)
.TP
.BR --help .BR --help
Show help message Show help message
.SH CONFIGURATION .SH CONFIGURATION
@ -98,6 +101,11 @@ Process hosts matching patterns:
.EX .EX
ipng-router-backup --yaml config.yaml --host "asw*" --host "*switch*" ipng-router-backup --yaml config.yaml --host "asw*" --host "*switch*"
.EE .EE
.TP
Process devices in parallel:
.EX
ipng-router-backup --yaml config.yaml --parallel 20
.EE
.SH FILES .SH FILES
.TP .TP
.I /etc/ipng-router-backup/config.yaml.example .I /etc/ipng-router-backup/config.yaml.example

View File

@ -7,6 +7,7 @@ import (
"log" "log"
"os" "os"
"path/filepath" "path/filepath"
"sync"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
@ -21,6 +22,29 @@ const Version = "1.2.4"
// SSH helper functions are now in ssh.go // SSH helper functions are now in ssh.go
// processDevice handles backup processing for a single device
func processDevice(hostname string, deviceConfig Device, commands []string, excludePatterns []string, password, keyFile string, port int, outputDir string) bool {
// Create backup instance
backup := NewRouterBackup(hostname, deviceConfig.Address, deviceConfig.User, password, keyFile, port)
// Connect and backup
if err := backup.Connect(); err != nil {
fmt.Printf("%s: Failed to connect: %v\n", hostname, err)
return false
}
err := backup.BackupCommands(commands, excludePatterns, outputDir)
backup.Disconnect()
if err != nil {
fmt.Printf("%s: Backup failed: %v\n", hostname, err)
return false
} else {
fmt.Printf("%s: Backup completed\n", hostname)
return true
}
}
func main() { func main() {
var yamlFiles []string var yamlFiles []string
var password string var password string
@ -28,6 +52,7 @@ func main() {
var port int var port int
var outputDir string var outputDir string
var hostFilter []string var hostFilter []string
var parallel int
var rootCmd = &cobra.Command{ var rootCmd = &cobra.Command{
Use: "ipng-router-backup", Use: "ipng-router-backup",
@ -93,12 +118,41 @@ func main() {
} }
} }
successCount := 0
totalCount := len(devicesToProcess) totalCount := len(devicesToProcess)
for hostname, deviceConfig := range devicesToProcess { // Create channels for work distribution and result collection
fmt.Printf("\n%s: Processing device (type: %s)\n", hostname, deviceConfig.Type) type DeviceWork struct {
hostname string
deviceConfig Device
commands []string
excludePatterns []string
}
type DeviceResult struct {
hostname string
success bool
}
workChan := make(chan DeviceWork, totalCount)
resultChan := make(chan DeviceResult, totalCount)
// Start worker pool
var wg sync.WaitGroup
for i := 0; i < parallel; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for work := range workChan {
fmt.Printf("%s: Processing device (type: %s)\n", work.hostname, work.deviceConfig.Type)
success := processDevice(work.hostname, work.deviceConfig, work.commands, work.excludePatterns, password, keyFile, port, outputDir)
resultChan <- DeviceResult{hostname: work.hostname, success: success}
}
}()
}
// Queue all work
for hostname, deviceConfig := range devicesToProcess {
user := deviceConfig.User user := deviceConfig.User
commands := deviceConfig.Commands commands := deviceConfig.Commands
deviceType := deviceConfig.Type deviceType := deviceConfig.Type
@ -122,27 +176,30 @@ func main() {
continue continue
} }
// Create backup instance workChan <- DeviceWork{
backup := NewRouterBackup(hostname, deviceConfig.Address, user, password, keyFile, port) hostname: hostname,
deviceConfig: deviceConfig,
// Connect and backup commands: commands,
if err := backup.Connect(); err != nil { excludePatterns: excludePatterns,
fmt.Printf("%s: Failed to connect: %v\n", hostname, err)
continue
} }
}
close(workChan)
err = backup.BackupCommands(commands, excludePatterns, outputDir) // Wait for all workers to finish
backup.Disconnect() go func() {
wg.Wait()
close(resultChan)
}()
if err != nil { // Collect results
fmt.Printf("%s: Backup failed: %v\n", hostname, err) successCount := 0
} else { for result := range resultChan {
fmt.Printf("%s: Backup completed\n", hostname) if result.success {
successCount++ successCount++
} }
} }
fmt.Printf("\nOverall summary: %d/%d devices processed successfully\n", successCount, totalCount) fmt.Printf("Overall summary: %d/%d devices processed successfully\n", successCount, totalCount)
// Set exit code based on results // Set exit code based on results
if successCount == 0 { if successCount == 0 {
@ -160,6 +217,7 @@ func main() {
rootCmd.Flags().IntVar(&port, "port", 22, "SSH port") rootCmd.Flags().IntVar(&port, "port", 22, "SSH port")
rootCmd.Flags().StringVar(&outputDir, "output-dir", "/tmp", "Output directory for command output files") rootCmd.Flags().StringVar(&outputDir, "output-dir", "/tmp", "Output directory for command output files")
rootCmd.Flags().StringSliceVar(&hostFilter, "host", []string{}, "Specific host(s) to process (can be repeated, processes all if not specified)") rootCmd.Flags().StringSliceVar(&hostFilter, "host", []string{}, "Specific host(s) to process (can be repeated, processes all if not specified)")
rootCmd.Flags().IntVar(&parallel, "parallel", 10, "Maximum number of devices to process in parallel")
rootCmd.MarkFlagRequired("yaml") rootCmd.MarkFlagRequired("yaml")