package util import ( "bytes" "fmt" "strings" "time" "unicode" "github.com/sirupsen/logrus" kexec "k8s.io/utils/exec" ) const ( ovsCommandTimeout = 15 ovsVsctlCommand = "ovs-vsctl" ovsOfctlCommand = "ovs-ofctl" ovnNbctlCommand = "ovn-nbctl" ipCommand = "ip" ) // Exec runs various OVN and OVS utilities type execHelper struct { exec kexec.Interface ofctlPath string vsctlPath string nbctlPath string ipPath string } var runner *execHelper // SetExec validates executable paths and saves the given exec interface // to be used for running various OVS and OVN utilites func SetExec(exec kexec.Interface) error { var err error runner = &execHelper{exec: exec} runner.ofctlPath, err = exec.LookPath(ovsOfctlCommand) if err != nil { return err } runner.vsctlPath, err = exec.LookPath(ovsVsctlCommand) if err != nil { return err } runner.nbctlPath, err = exec.LookPath(ovnNbctlCommand) if err != nil { return err } runner.ipPath, err = exec.LookPath(ipCommand) if err != nil { return err } return nil } // Run the ovn-ctl command and retry if "Connection refused" // poll waitng for service to become available func runOVNretry(cmdPath string, args ...string) (*bytes.Buffer, *bytes.Buffer, error) { retriesLeft := 200 for { stdout, stderr, err := run(cmdPath, args...) if err == nil { return stdout, stderr, err } // Connection refused // Master may not be up so keep trying if strings.Contains(stderr.String(), "Connection refused") { if retriesLeft == 0 { return stdout, stderr, err } retriesLeft-- time.Sleep(2 * time.Second) } else { // Some other problem for caller to handle return stdout, stderr, err } } } func run(cmdPath string, args ...string) (*bytes.Buffer, *bytes.Buffer, error) { stdout := &bytes.Buffer{} stderr := &bytes.Buffer{} cmd := runner.exec.Command(cmdPath, args...) cmd.SetStdout(stdout) cmd.SetStderr(stderr) logrus.Debugf("exec: %s %s", cmdPath, strings.Join(args, " ")) err := cmd.Run() if err != nil { logrus.Debugf("exec: %s %s => %v", cmdPath, strings.Join(args, " "), err) } return stdout, stderr, err } // RunOVSOfctl runs a command via ovs-ofctl. func RunOVSOfctl(args ...string) (string, string, error) { stdout, stderr, err := run(runner.ofctlPath, args...) return strings.Trim(stdout.String(), "\" \n"), stderr.String(), err } // RunOVSVsctl runs a command via ovs-vsctl. func RunOVSVsctl(args ...string) (string, string, error) { cmdArgs := []string{fmt.Sprintf("--timeout=%d", ovsCommandTimeout)} cmdArgs = append(cmdArgs, args...) stdout, stderr, err := run(runner.vsctlPath, cmdArgs...) return strings.Trim(strings.TrimSpace(stdout.String()), "\""), stderr.String(), err } // RunOVNNbctlUnix runs command via ovn-nbctl, with ovn-nbctl using the unix // domain sockets to connect to the ovsdb-server backing the OVN NB database. func RunOVNNbctlUnix(args ...string) (string, string, error) { cmdArgs := []string{fmt.Sprintf("--timeout=%d", ovsCommandTimeout)} cmdArgs = append(cmdArgs, args...) stdout, stderr, err := runOVNretry(runner.nbctlPath, cmdArgs...) return strings.Trim(strings.TrimFunc(stdout.String(), unicode.IsSpace), "\""), stderr.String(), err } // RunIP runs a command via the iproute2 "ip" utility func RunIP(args ...string) (string, string, error) { stdout, stderr, err := run(runner.ipPath, args...) return strings.TrimSpace(stdout.String()), stderr.String(), err }