go - 同时配置网络设备

标签 go concurrency parallel-processing

我正在编写一个 Go 库来表示各种网络设备,例如交换机、路由器、无线 Controller 、接入点等,以便自动配置这些设备。到目前为止,我有一个 Device 结构,它有一个公共(public) Host 字段和各种私有(private)字段,用于处理特定于 SSH 的操作,以及连接到设备的方法,向它发送一组配置命令,并检索命令的输出。截至目前,没有一个方法实现为并发,主要是因为我不知道哪些方法(如果有的话)会从并发中受益。

作为一个整体,我的问题是通过 SSH 配置设备列表,这似乎是使用并发的好案例(而不是盲目地尝试使用并发来“快速”),因为配置单个设备的过程可能很昂贵,但我不确定在我的应用程序中在哪里实现并发以及如何同步所有内容(线程安全?)。对于互斥体、 WaitGroup 、 channel 和协程,像我这样的初学者不知道从哪里开始有点困惑。我希望至少让一个方法同时工作,以便更好地理解 Go 中的(惯用的)并发性。

这是我的 Device 结构及其方法。为了清楚地说明我想要完成的事情以及我对实现细节的想法,它被大量评论。

package device

import (
    "golang.org/x/crypto/ssh"
    "io"
)

// A Device represents a network device, such as a switch, router, controller, etc.
type Device struct {
    Host    string         // Hostname or IP address
    client  *ssh.Client    // the SSH client connection
    session *ssh.Session   // the connection to the remote shell
    stdin   io.WriteCloser // a pipe connected to the remote shell's standard input
    stdout  io.Reader      // a pipe connected to the remote shell's standard output
    stderr  io.Reader      // a pipe connected to the remote shell's standard error
}

// NewDevice constructs a new device with the given hostname or IP address.
func NewDevice(host string) *Device {
    return &Device{Host: host}
}

// Connect starts a client connection to the device, starts a remote
// shell, and creates pipes connected to the remote shell's standard input,
// standard output, and standard error.
func (d *Device) Connect(config *ssh.ClientConfig) error {
    // TODO: connect to client, start session, setup IO
    // Use a goroutine to handle each step? One goroutine for all steps?
    return nil
}

// setupIO connects pipes to the remote shell's standard input, output and error.
func (d *Device) setupIO() error {
    sshIn, err := d.session.StdinPipe()
    if err != nil {
        return err
    }
    d.stdin = sshIn

    sshOut, err := d.session.StdoutPipe()
    if err != nil {
        return err
    }
    d.stdout = sshOut

    sshErr, err := d.session.StderrPipe()
    if err != nil {
        return err
    }
    d.stderr = sshErr

    return nil
}

// SendConfigSet writes a set of configuration commands to the remote shell's
// standard input then waits for the remote commands to exit.
func (d *Device) SendConfigSet(cmds []string) error {
    // TODO: send a set of configuration commands
    // Make concurrent? Commands need to be sent in a specific order.
    //
    // This function will have different setup and cleanup commands
    // that will need to be sent depending on a Device's vendor.
    // For example, a Cisco device and an HPE device have
    // different sets of setup commands needed before sending
    // the `cmds` passed to this function, and have different sets of
    // cleanup commands that must be sent before exiting.
    return nil
}

// sendCmd writes a remote command to the remote shell's standard input
func (d *Device) sendCmd(cmd string) error {
    if _, err := d.stdin.Write([]byte(cmd + "\n")); err != nil {
        return err
    }
    return nil
}

// Output reads the remote shell's standard output line by line into a
// slice of strings.
func (d *Device) Output() ([]string, error) {
    // TODO: read contents of session standard output
    // Concurrently read from stdout and send to channel?
    // If so, use a local channel or add an output channel to `Device`?
    return nil, nil
}

// Output reads the remote shell's standard error line by line into a
// slice of strings.
func (d *Device) Err() ([]string, error) {
    // TODO: read contents of session standard error
    // Concurrently read from stderr and send to channel?
    // If so, use a local channel or add an error channel to `Device`?
    return nil, nil
}

func (d *Device) Close() error {
    if err := d.stdin.Close(); err != nil {
        return err
    }
    if err := d.session.Close(); err != nil {
        return err
    }
    if err := d.client.Close(); err != nil {
        return err
    }
    return nil
}

下面是我的device 包的用法示例:

package main

import (
    "fmt"
    "github.com/mwalto7/concurrency/device"
    "golang.org/x/crypto/ssh"
    "strings"
    "time"
)

func main() {
    var hosts, cmds []string

    config := &ssh.ClientConfig{
        User:            "username",
        Auth:            []ssh.AuthMethod{ssh.Password("password")},
        HostKeyCallback: ssh.InsecureIgnoreHostKey(),
        Timeout:         time.Second * 5,
    }

    outputs := make(chan string)
    for _, host := range hosts {
        go configure(host, cmds, config, outputs)
    }
    for i := 0; i < len(hosts); i++ {
        res := <-outputs
        fmt.Println(res)
    }
}

func configure(host string, cmds []string, config *ssh.ClientConfig, outputs <-chan string) {
    // omitted error handling for brevity
    netDev := device.NewDevice(host)
    defer netDev.Close()
    netDev.Connect(config)
    netDev.SendConfigSet(cmds)
    out, _ := netDev.Output()
    outputs <- strings.Join(out, "\n")
}

我并不是要别人为我编写这段代码。如果您有代码示例,那很好,但我只是想组织实现并发并了解一般的并发。 p>

最佳答案

我编写了一个高度并发的应用程序,同时与多个设备通信。我的设备主要是串行通信(请求、响应、请求等),因此对单个设备使用并发不是一种选择。如果那是您想要的,那么这就是给您的。

设备结构

我假设您的设备无法同时处理多个请求。如果可以,您将需要清楚地表明它们属于哪个请求的答案。如果是后者,请告诉我——我也有处理过这种情况的经验。

关于串行通信的设备最难的部分是请求与答案的同步。这里有一些示例代码,我是如何解决这个问题的:playground, not runnable

免责声明:快速将其复制在一起。希望你能透过困惑看到想法。

上面的示例代码使用单个例程来同步对设备的调用,而每个调用在完成之前等待答案。您可以通过在连接的每次使用周围使用互斥锁来实现相同的目的。同时,我更喜欢使用互斥锁来实现这一点,因为它可以省去 goroutine 的启动和停止——如果操作不当,可能会造成 goroutine 泄漏。

顺便说一句:ConnAdapter 结构是完全并发安全的,可以同时从多个例程中使用。

使用设备结构

从您的代码看来,您启动应用程序,获取主机列表(从某处),获取 cmds 列表(可能从某处每个主机),然后想要在其主机上执行所有命令并等待一切完成了。

如果是这种情况,您在这里可以完美地处理并发。

我要添加的一件事是超时:playground

关于go - 同时配置网络设备,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/49306598/

相关文章:

google-app-engine - 安装appengine sdk后Golang未定义google.JWTAccessTokenSourceFromJSON

r - R 中的异步编程

c++ - 并行编程如何收集slave返回master的值?

go - 传入任何结构作为接口(interface)方法

go - new 和 make 和有什么不一样?

docker - 无法让 google auth 在 docker 中工作以发布到 pubsub

java - 执行器中的意外死锁

node.js - 使用 Socket.io 而不是普通的 ajax 调用会阻止服务器耗尽 TCP 套接字吗?

java - 我可以初始化一个静态成员并跨线程共享吗?

r - 在Mac上的R 3.0.1中,tm_map具有parallel::mclapply错误