misc: add test framework for host stack

Type: feature

Signed-off-by: Filip Tehlar <ftehlar@cisco.com>
Change-Id: I5a64a2c095cae3a4d5f8fdc73e624b010339ec8e
diff --git a/extras/hs-test/main.go b/extras/hs-test/main.go
new file mode 100755
index 0000000..1014917
--- /dev/null
+++ b/extras/hs-test/main.go
@@ -0,0 +1,155 @@
+package main
+
+import (
+	"context"
+	"encoding/json"
+	"fmt"
+	"os"
+	"os/exec"
+	"os/signal"
+
+	"git.fd.io/govpp.git/api"
+)
+
+type CfgTable map[string]func([]string) *ActionResult
+
+var cfgTable CfgTable
+
+type ConfFn func(context.Context, api.Connection) error
+
+func newVppContext() (context.Context, context.CancelFunc) {
+	ctx, cancel := signal.NotifyContext(
+		context.Background(),
+		os.Interrupt,
+	)
+	return ctx, cancel
+}
+
+func Vppcli(runDir, command string) (string, error) {
+	cmd := exec.Command("vppctl", "-s", fmt.Sprintf("%s/var/run/vpp/cli.sock", runDir), command)
+	o, err := cmd.CombinedOutput()
+	if err != nil {
+		fmt.Printf("failed to execute command: '%v'.\n", err)
+	}
+	fmt.Printf("Command output %s", string(o))
+	return string(o), err
+}
+
+func exitOnErrCh(ctx context.Context, cancel context.CancelFunc, errCh <-chan error) {
+	// If we already have an error, log it and exit
+	select {
+	case err := <-errCh:
+		fmt.Printf("%v", err)
+	default:
+	}
+	go func(ctx context.Context, errCh <-chan error) {
+		<-errCh
+		cancel()
+	}(ctx, errCh)
+}
+
+func writeSyncFile(res *ActionResult) error {
+	syncFile := "/tmp/sync/rc"
+
+	var jsonRes JsonResult
+
+	jsonRes.ErrOutput = res.ErrOutput
+	jsonRes.StdOutput = res.StdOutput
+	if res.Err != nil {
+		jsonRes.Code = 1
+		jsonRes.Desc = fmt.Sprintf("%s :%v", res.Desc, res.Err)
+	} else {
+		jsonRes.Code = 0
+	}
+
+	str, err := json.Marshal(jsonRes)
+	if err != nil {
+		return fmt.Errorf("error marshaling json result data! %v", err)
+	}
+
+	_, err = os.Open(syncFile)
+	if err != nil {
+		// expecting the file does not exist
+		f, e := os.Create(syncFile)
+		if e != nil {
+			return fmt.Errorf("failed to open sync file")
+		}
+		defer f.Close()
+		f.Write([]byte(str))
+	} else {
+		return fmt.Errorf("sync file exists, delete the file frst")
+	}
+	return nil
+}
+
+func NewActionResult(err error, opts ...ActionResultOptionFn) *ActionResult {
+	res := &ActionResult{
+		Err: err,
+	}
+	for _, o := range opts {
+		o(res)
+	}
+	return res
+}
+
+type ActionResultOptionFn func(res *ActionResult)
+
+func ActionResultWithDesc(s string) ActionResultOptionFn {
+	return func(res *ActionResult) {
+		res.Desc = s
+	}
+}
+
+func ActionResultWithStderr(s string) ActionResultOptionFn {
+	return func(res *ActionResult) {
+		res.ErrOutput = s
+	}
+}
+
+func ActionResultWithStdout(s string) ActionResultOptionFn {
+	return func(res *ActionResult) {
+		res.ErrOutput = s
+	}
+}
+
+func OkResult() *ActionResult {
+	return NewActionResult(nil)
+}
+
+func reg(key string, fn func([]string) *ActionResult) {
+	cfgTable[key] = fn
+}
+
+func processArgs() *ActionResult {
+	fn := cfgTable[os.Args[1]]
+	if fn == nil {
+		return NewActionResult(fmt.Errorf("internal: no config found for %s", os.Args[1]))
+	}
+	return fn(os.Args)
+}
+
+func main() {
+	if len(os.Args) == 0 {
+		fmt.Println("args required")
+		return
+	}
+
+	if os.Args[1] == "rm" {
+		topology, err := LoadTopology(TopologyDir, os.Args[2])
+		if err != nil {
+			fmt.Printf("falied to load topologies: %v\n", err)
+			os.Exit(1)
+		}
+		topology.Unconfigure()
+		os.Exit(0)
+	}
+
+	RegisterActions()
+
+	var err error
+	res := processArgs()
+	err = writeSyncFile(res)
+	if err != nil {
+		fmt.Printf("failed to write to sync file: %v\n", err)
+	}
+}