dpauxqt1281 2018-03-15 18:16
浏览 52
已采纳

并发配置网络设备

I am writing a Go library to represent various networking devices, such as a switch, router, wireless controller, access point, etc, in order to automate configuring these devices. So far, I have a single Device struct that has a public Host field and various private fields for handling SSH-specific actions, as well as methods for connecting to a device, sending a set of configuration commands to it, and retrieving the output of the commands. As of now, none of the methods are implemented to be concurrent, mainly because I don't know which methods (if any) would benefit from concurrency.

My problem as a whole, configuring a list of devices over SSH, seems like a good case for using concurrency (not blindly trying to use concurrency to "go fast"), since the process for configuring a single device can be expensive, but I am unsure of where to implement the concurrency in my application and how to synchronize everything (thread safety?). With mutexes, waitgroups, channels, and goroutines, it is a bit confusing for a beginner like me to know where to start. I'd like to at least get a single method working concurrently just to get a better understanding of (idiomatic) concurrency in Go.

Here is my Device struct and its methods. It is heavily commented for clarity on what I want to accomplish and ideas I have for implementation details.

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 + "
")); 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
}

Here is an example usage of my device package:

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, "
")
}

I am not asking for someone to write this code for me. If you have a code example, great, but I am simply trying to organize implementing concurrency and learn about concurrency in general.

  • 写回答

2条回答 默认 最新

  • doudu2591 2018-03-17 10:24
    关注

    I have written a highly concurrent application talking to several devices at the same time. My devices talk mostly serial (request, response, request, etc.) so using concurrency to a single device was not an option. If that is what you want then this is for you.

    Device struct

    I assume your devices cannot handle multiple requests at the same time. If it can you will need answers that clearly indicate which request they belong to. If latter is the case let me know -- I have experience with this case, too.

    The hardest part about devices talking serial is the synchronisation of the request with the answer. Here some sample code how I have solved this: playground, not runnable

    Disclaimer: quickly copied this together. Hope you can see the idea through the mess.

    The sample code above uses a single routine to synchronise calls to the device while every call waits for the answer before being done. You can achieve the same with using a mutex around every usage of the connection. Meanwhile I prefer using mutexes to achieve this as it saves me the starting and stopping of the goroutine -- which can create goroutine leaks if not done properly.

    BTW: the ConnAdapter struct is fully concurrency safe and can be used from multiple routines at the same time.

    Using the Device struct

    From your code it looks like you start the application, get a list of hosts (from somewhere), get a list of cmds (maybe per host from somewhere), then want to execute all commands on its host and wait until everything is done.

    If that is the case you are handling concurrency perfectly fine here.

    One thing I would add is a timeout: playground

    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论
查看更多回答(1条)

报告相同问题?

悬赏问题

  • ¥15 鼠标右键,撤销删除 复制 移动,要怎样删除
  • ¥15 使用MATLAB进行余弦相似度计算加速
  • ¥15 服务器安装php5.6版本
  • ¥15 我想用51单片机和数码管做一个从0开始的计数表 我写了一串代码 但是放到单片机里面数码管只闪烁一下然后熄灭
  • ¥20 系统工程中,状态空间模型中状态方程的应用。请猛男来完整讲一下下面所有问题
  • ¥15 我想在WPF的Model Code中获取ViewModel Code中的一个参数
  • ¥15 arcgis处理土地利用道路 建筑 林地分类
  • ¥20 使用visual studio 工具用C++语音,调用openslsx库读取excel文件的sheet问题
  • ¥100 寻会做云闪付tn转h5支付链接的技术
  • ¥15 DockerSwarm跨节点无法访问问题