diff --git a/.gitignore b/.gitignore index f6d6b861ea9bea47efbaec7442ef2f133e6225e4..78c6e10062c15b5eb23543e4c43b585851060baa 100644 --- a/.gitignore +++ b/.gitignore @@ -14,3 +14,4 @@ documentation/design/*.pdf .idea/workspace.xml restconf/bin/bin test/.terraform.local/ +configs/gosdn.toml \ No newline at end of file diff --git a/cli/cli_test.go b/cli/cli_test.go deleted file mode 100644 index 65bf6e6497dd747d4cbd951143cf4c56e2b46ea4..0000000000000000000000000000000000000000 --- a/cli/cli_test.go +++ /dev/null @@ -1,259 +0,0 @@ -package cli - -import ( - "code.fbi.h-da.de/cocsn/gosdn/forks/google/gnmi" - "context" - gpb "github.com/openconfig/gnmi/proto/gnmi" - "github.com/openconfig/ygot/ygot" - "reflect" - "testing" -) - -func TestCapabilities(t *testing.T) { - type args struct { - a string - u string - p string - } - tests := []struct { - name string - args args - wantErr bool - }{ - // TODO: Add test cases. - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if err := Capabilities(tt.args.a, tt.args.u, tt.args.p); (err != nil) != tt.wantErr { - t.Errorf("Capabilities() error = %v, wantErr %v", err, tt.wantErr) - } - }) - } -} - -func TestGet(t *testing.T) { - type args struct { - a string - u string - p string - args []string - } - tests := []struct { - name string - args args - wantErr bool - }{ - // TODO: Add test cases. - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if err := Get(tt.args.a, tt.args.u, tt.args.p, tt.args.args...); (err != nil) != tt.wantErr { - t.Errorf("Get() error = %v, wantErr %v", err, tt.wantErr) - } - }) - } -} - -func TestHttpGet(t *testing.T) { - type args struct { - apiEndpoint string - f string - args []string - } - tests := []struct { - name string - args args - wantErr bool - }{ - // TODO: Add test cases. - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if err := HTTPGet(tt.args.apiEndpoint, tt.args.f, tt.args.args...); (err != nil) != tt.wantErr { - t.Errorf("HTTPGet() error = %v, wantErr %v", err, tt.wantErr) - } - }) - } -} - -func TestLeafPaths(t *testing.T) { - tests := []struct { - name string - wantErr bool - }{ - // TODO: Add test cases. - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if err := LeafPaths(); (err != nil) != tt.wantErr { - t.Errorf("LeafPaths() error = %v, wantErr %v", err, tt.wantErr) - } - }) - } -} - -func TestPathTraversal(t *testing.T) { - tests := []struct { - name string - wantErr bool - }{ - // TODO: Add test cases. - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if err := PathTraversal(); (err != nil) != tt.wantErr { - t.Errorf("PathTraversal() error = %v, wantErr %v", err, tt.wantErr) - } - }) - } -} - -func TestSet(t *testing.T) { - type args struct { - a string - u string - p string - typ string - args []string - } - tests := []struct { - name string - args args - wantErr bool - }{ - // TODO: Add test cases. - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if err := Set(tt.args.a, tt.args.u, tt.args.p, tt.args.typ, tt.args.args...); (err != nil) != tt.wantErr { - t.Errorf("Set() error = %v, wantErr %v", err, tt.wantErr) - } - }) - } -} - -func TestSubscribe(t *testing.T) { - type args struct { - a string - u string - p string - sample int64 - heartbeat int64 - args []string - } - tests := []struct { - name string - args args - wantErr bool - }{ - // TODO: Add test cases. - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if err := Subscribe(tt.args.a, tt.args.u, tt.args.p, tt.args.sample, tt.args.heartbeat, tt.args.args...); (err != nil) != tt.wantErr { - t.Errorf("Subscribe() error = %v, wantErr %v", err, tt.wantErr) - } - }) - } -} - -func TestTarget(t *testing.T) { - type args struct { - bindAddr string - } - tests := []struct { - name string - args args - wantErr bool - }{ - // TODO: Add test cases. - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if err := Target(tt.args.bindAddr); (err != nil) != tt.wantErr { - t.Errorf("Target() error = %v, wantErr %v", err, tt.wantErr) - } - }) - } -} - -func Test_callback(t *testing.T) { - type args struct { - newConfig ygot.ValidatedGoStruct - } - tests := []struct { - name string - args args - wantErr bool - }{ - // TODO: Add test cases. - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if err := callback(tt.args.newConfig); (err != nil) != tt.wantErr { - t.Errorf("callback() error = %v, wantErr %v", err, tt.wantErr) - } - }) - } -} - -func Test_newServer(t *testing.T) { - type args struct { - model *gnmi.Model - config []byte - } - tests := []struct { - name string - args args - want *server - wantErr bool - }{ - // TODO: Add test cases. - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got, err := newServer(tt.args.model, tt.args.config) - if (err != nil) != tt.wantErr { - t.Errorf("newServer() error = %v, wantErr %v", err, tt.wantErr) - return - } - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("newServer() got = %v, want %v", got, tt.want) - } - }) - } -} - -func Test_server_Get(t *testing.T) { - type fields struct { - Server *gnmi.Server - } - type args struct { - ctx context.Context - req *gpb.GetRequest - } - tests := []struct { - name string - fields fields - args args - want *gpb.GetResponse - wantErr bool - }{ - // TODO: Add test cases. - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - s := &server{ - Server: tt.fields.Server, - } - got, err := s.Get(tt.args.ctx, tt.args.req) - if (err != nil) != tt.wantErr { - t.Errorf("Get() error = %v, wantErr %v", err, tt.wantErr) - return - } - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("Get() got = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/cli/get.go b/cli/get.go index 36dec3a4b75b2c86c225ee7beb71634148c2da39..f2b2e6e80aff4e1e8a53b2d61e2d339524df0bdd 100644 --- a/cli/get.go +++ b/cli/get.go @@ -9,7 +9,7 @@ import ( ) // Get sends a gNMI Get request to the specified target and prints the response to stdout -func Get(a, u, p string, args ...string) error { +func Get(a, u, p string, args ...string) (*gpb.GetResponse,error) { sbi := &nucleus.OpenConfig{} opts := &nucleus.GnmiTransportOptions{ Config: gnmi.Config{ @@ -22,12 +22,16 @@ func Get(a, u, p string, args ...string) error { } t, err := nucleus.NewGnmiTransport(opts) if err != nil { - return err + return nil,err } resp, err := t.Get(context.Background(), args...) if err != nil { - return err + return nil, err } - log.Info(resp) - return nil + log.Debug(resp) + r, ok := resp.(*gpb.GetResponse) + if !ok { + return nil, &nucleus.ErrInvalidTypeAssertion{} + } + return r, nil } diff --git a/cli/http.go b/cli/http.go index ba3b2f6765f1a5a5f2adc5137533e2cb95a420eb..91c4bb65c29971cc49a837d0a1bb30abd8690d01 100644 --- a/cli/http.go +++ b/cli/http.go @@ -1,6 +1,7 @@ package cli import ( + "errors" "fmt" log "github.com/sirupsen/logrus" "github.com/spf13/viper" @@ -34,12 +35,15 @@ func HTTPGet(apiEndpoint, f string, args ...string) error { if err != nil { return err } - if f == "init" { + switch f { + case "init": pnd := string(bytes[:36]) sbi := string(bytes[36:]) viper.Set("CLI_PND", pnd) viper.Set("CLI_SBI", sbi) return viper.WriteConfig() + default: + fmt.Println(string(bytes)) } fmt.Println(string(bytes)) case http.StatusCreated: @@ -48,11 +52,14 @@ func HTTPGet(apiEndpoint, f string, args ...string) error { if err != nil { return err } + uuid := string(bytes[19:55]) + viper.Set("LAST_DEVICE_UUID", uuid) fmt.Println(string(bytes)) default: log.WithFields(log.Fields{ "status code": resp.StatusCode, }).Error("operation unsuccessful") + return errors.New(resp.Status) } return nil } diff --git a/cli/integration_test.go b/cli/integration_test.go new file mode 100644 index 0000000000000000000000000000000000000000..3e1a36808bd34c5a337a313a38cb0e2b4798cb70 --- /dev/null +++ b/cli/integration_test.go @@ -0,0 +1,240 @@ +package cli + +import ( + "os" + "testing" +) + +const unreachable = "203.0.113.10:6030" + +var address = "141.100.70.171:6030" +var apiEndpoint = "http://141.100.70.171:8080" +var username = "admin" +var password = "arista" +var defaultPath = []string{"/system/config/hostname"} + +func testSetupIntegration() { + a := os.Getenv("GOSDN_TEST_ENDPOINT") + if a != "" { + address = a + } + api := os.Getenv("GOSDN_TEST_API_ENDPOINT") + if api != "" { + apiEndpoint = api + } +} + +func TestMain(m *testing.M) { + testSetupIntegration() + os.Exit(m.Run()) +} + +func TestCapabilities(t *testing.T) { + if testing.Short() { + t.Skip("skipping integration test") + } + type args struct { + a string + u string + p string + } + tests := []struct { + name string + args args + wantErr bool + }{ + { + name: "default", + args: args{ + a: address, + u: username, + p: password, + }, + wantErr: false, + }, + { + name: "destination unreachable", + args: args{ + a: unreachable, + u: username, + p: password, + }, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := Capabilities(tt.args.a, tt.args.u, tt.args.p); (err != nil) != tt.wantErr { + t.Errorf("Capabilities() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func TestGet(t *testing.T) { + if testing.Short() { + t.Skip("skipping integration test") + } + type args struct { + a string + u string + p string + args []string + } + tests := []struct { + name string + args args + wantErr bool + }{ + { + name: "default", + args: args{ + a: address, + u: username, + p: password, + args: defaultPath, + }, + wantErr: false, + }, + { + name: "destination unreachable", + args: args{ + a: unreachable, + u: username, + p: password, + args: defaultPath, + }, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if _,err := Get(tt.args.a, tt.args.u, tt.args.p, tt.args.args...); (err != nil) != tt.wantErr { + t.Errorf("Get() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func TestHttpGet(t *testing.T) { + if testing.Short() { + t.Skip("skipping integration test") + } + type args struct { + apiEndpoint string + f string + args []string + } + tests := []struct { + name string + args args + wantErr bool + }{ + { + name: "default", + args: args{ + apiEndpoint: apiEndpoint, + f: "init", + args: nil, + }, + wantErr: false, + }, + { + name: "destination unreachable", + args: args{ + apiEndpoint: "http://" + unreachable, + f: "init", + args: nil, + }, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := HttpGet(tt.args.apiEndpoint, tt.args.f, tt.args.args...); (err != nil) != tt.wantErr { + t.Errorf("HttpGet() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func TestPathTraversal(t *testing.T) { + if testing.Short() { + t.Skip("skipping integration test") + } + tests := []struct { + name string + wantErr bool + }{ + { + name: "default", + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := PathTraversal(); (err != nil) != tt.wantErr { + t.Errorf("PathTraversal() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func TestSet(t *testing.T) { + if testing.Short() { + t.Skip("skipping integration test") + } + type args struct { + a string + u string + p string + typ string + args []string + } + tests := []struct { + name string + args args + wantErr bool + }{ + { + name: "default", + args: args{ + a: address, + u: username, + p: password, + typ: "update", + args: []string{"/system/config/hostname", "ceos3000"}, + }, + wantErr: false, + }, + { + name: "destination unreachable", + args: args{ + a: unreachable, + u: username, + p: password, + typ: "update", + args: []string{"/system/config/hostname", "ceos3000"}, + }, + wantErr: true, + }, + { + name: "invalid path", + args: args{ + a: address, + u: username, + p: password, + typ: "update", + args: []string{"invalid/path", "ceos3000"}, + }, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := Set(tt.args.a, tt.args.u, tt.args.p, tt.args.typ, tt.args.args...); (err != nil) != tt.wantErr { + t.Errorf("Set() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} diff --git a/cli/ygot.go b/cli/ygot.go deleted file mode 100644 index 51b12852e6274d94279972793333ea3be3131657..0000000000000000000000000000000000000000 --- a/cli/ygot.go +++ /dev/null @@ -1,17 +0,0 @@ -package cli - -import ( - "github.com/openconfig/ygot/util" - log "github.com/sirupsen/logrus" -) - -func LeafPaths() error { - for _, v := range testSchema.SchemaTree { - entry, err := util.FindLeafRefSchema(v, "/interface/") - if err != nil { - log.Error(err) - } - log.Info(entry) - } - return nil -} diff --git a/cmd/get.go b/cmd/get.go index 055d6c6f9ee9fb4a94262bc43317336a26a9e6ec..46a709e6d1d87e7497942ab3a902ccdac029c1bb 100644 --- a/cmd/get.go +++ b/cmd/get.go @@ -42,7 +42,8 @@ var getCmd = &cobra.Command{ Short: "get request", Long: `Sends a gNMI Get request to the specified target and prints the response to stdout`, RunE: func(cmd *cobra.Command, args []string) error { - return cli.Get(address, username, password, args...) + _,err := cli.Get(address, username, password, args...) + return err }, } diff --git a/cmd/integration_test.go b/cmd/integration_test.go new file mode 100644 index 0000000000000000000000000000000000000000..3eda98ed9d98a5d558d4084e32a02bb2426d9198 --- /dev/null +++ b/cmd/integration_test.go @@ -0,0 +1,131 @@ +package cmd + +import ( + "code.fbi.h-da.de/cocsn/gosdn/cli" + guuid "github.com/google/uuid" + "github.com/spf13/viper" + "os" + "testing" +) + +var testAddress = "141.100.70.171:6030" +var testApiEndpoint = "http://141.100.70.171:8080" +var testUsername = "admin" +var testPassword = "arista" + +func testSetupIntegration() { + a := os.Getenv("GOSDN_TEST_ENDPOINT") + if a != "" { + testAddress = a + } + api := os.Getenv("GOSDN_TEST_API_ENDPOINT") + if api != "" { + testApiEndpoint = api + } + u := os.Getenv("GOSDN_TEST_USER") + if u != "" { + testUsername = u + } + p := os.Getenv("GOSDN_TEST_PASSWORD") + if p != "" { + testPassword = p + } +} + +func TestMain(m *testing.M) { + testSetupIntegration() + os.Exit(m.Run()) +} + +func TestCliIntegration(t *testing.T) { + if testing.Short() { + t.Skip("skipping integration test") + } + tests := []struct { + name string + wantErr bool + }{ + { + name: "default", + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + defer viper.Reset() + if err := cli.HttpGet(testApiEndpoint, "init"); (err != nil) != tt.wantErr { + switch err.(type) { + case viper.ConfigFileNotFoundError: + default: + t.Errorf("gosdn cli init error = %v, wantErr %v", err, tt.wantErr) + return + } + } + cliPnd = viper.GetString("CLI_PND") + cliSbi = viper.GetString("CLI_SBI") + + if err := cli.HttpGet( + testApiEndpoint, + "addDevice", + "address="+testAddress, + "password="+testPassword, + "username="+testUsername, + "sbi="+cliSbi, + "pnd="+cliPnd, + ); (err != nil) != tt.wantErr { + t.Errorf("gosdn cli add-device error = %v, wantErr %v", err, tt.wantErr) + return + } + did := viper.GetString("LAST_DEVICE_UUID") + + if err := cli.HttpGet( + testApiEndpoint, + "request", + "uuid="+did, + "sbi="+cliSbi, + "pnd="+cliPnd, + "path=/system/config/hostname", + ); (err != nil) != tt.wantErr { + t.Errorf("gosdn cli request error = %v, wantErr %v", err, tt.wantErr) + return + } + + if err := cli.HttpGet( + testApiEndpoint, + "getDevice", + "address="+testAddress, + "uuid="+did, + "sbi="+cliSbi, + "pnd="+cliPnd, + ); (err != nil) != tt.wantErr { + t.Errorf("gosdn cli get-device error = %v, wantErr %v", err, tt.wantErr) + return + } + + hostname := guuid.New().String() + if err := cli.HttpGet( + testApiEndpoint, + "set", + "address="+testAddress, + "uuid="+did, + "sbi="+cliSbi, + "pnd="+cliPnd, + "path=/system/config/hostname", + "value="+hostname, + ); (err != nil) != tt.wantErr { + t.Errorf("gosdn cli set error = %v, wantErr %v", err, tt.wantErr) + return + } + + resp, err := cli.Get(testAddress, testUsername, testPassword, "/system/config/hostname") + if (err != nil) != tt.wantErr { + t.Errorf("cli.Get() error = %v, wantErr %v", err, tt.wantErr) + return + } + got := resp.Notification[0].Update[0].Val.GetStringVal() + if got != hostname { + t.Errorf("integration test failed = got: %v, want: %v", got, hostname) + } + }) + } +} diff --git a/nucleus/controller.go b/nucleus/controller.go index ae666ee9dfb07e40671869b0f0c8b45fd339df7d..f9e2b6dd968e54172a8a1db92fe5850f107dfa5d 100644 --- a/nucleus/controller.go +++ b/nucleus/controller.go @@ -5,6 +5,9 @@ import ( "context" "github.com/google/uuid" log "github.com/sirupsen/logrus" + "net/http" + "os" + "os/signal" "time" ) @@ -13,19 +16,28 @@ type Core struct { // deprecated database database.Database - pndc pndStore - sbic sbiStore + pndc pndStore + sbic sbiStore + httpServer *http.Server + stopChan chan os.Signal } var c *Core -//Initialize does start-up housekeeping like reading controller config files -func initialize() error { +func init() { c = &Core{ - database: database.Database{}, - pndc: pndStore{}, - sbic: sbiStore{}, + database: database.Database{}, + pndc: pndStore{}, + sbic: sbiStore{}, + stopChan: make(chan os.Signal, 1), } + + // Setting up signal capturing + signal.Notify(c.stopChan, os.Interrupt) +} + +// initialize does start-up housekeeping like reading controller config files +func initialize() error { c.sbic = sbiStore{ store{}, } @@ -84,10 +96,17 @@ func Run(ctx context.Context) error { log.WithFields(log.Fields{}).Info("initialisation finished") for { select { + case <-c.stopChan: + return shutdown() case <-ctx.Done(): - return nil + return shutdown() case <-time.Tick(time.Minute): log.Debug("up and running") } } } + +func shutdown()error{ + log.Info("shutting down controller") + return stopHttpServer() +} \ No newline at end of file diff --git a/nucleus/controller_test.go b/nucleus/controller_test.go index 0fa4ffa903a1f894325aaf5211341e19a2df754a..8b59316492b90f54093c586d75eb800320dc68ef 100644 --- a/nucleus/controller_test.go +++ b/nucleus/controller_test.go @@ -1 +1,52 @@ package nucleus + +import ( + "context" + "net/http" + "testing" + "time" +) + +func TestRun(t *testing.T) { + type args struct { + request string + } + tests := []struct { + name string + args args + want interface{} + wantErr bool + }{ + { + name: "liveliness indicator", + args: args{request: apiEndpoint + "/livez"}, + want: &http.Response{StatusCode: http.StatusOK}, + wantErr: false, + }, + { + name: "readyness indicator", + args: args{request: apiEndpoint + "/readyz"}, + want: &http.Response{StatusCode: http.StatusOK}, + wantErr: false, + }, + { + name: "init", + args: args{request: apiEndpoint + "/api?q=init"}, + want: &http.Response{StatusCode: http.StatusOK}, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + go func() { + if err := Run(ctx); (err != nil) != tt.wantErr { + t.Errorf("Run() error = %v, wantErr %v", err, tt.wantErr) + } + }() + time.Sleep(time.Second) + cancel() + time.Sleep(time.Second) + }) + } +} diff --git a/nucleus/errors_test.go b/nucleus/errors_test.go deleted file mode 100644 index 09d172db926b14465fb4e16f641017a4f6d430ae..0000000000000000000000000000000000000000 --- a/nucleus/errors_test.go +++ /dev/null @@ -1,196 +0,0 @@ -package nucleus - -import "testing" - -func TestErrAlreadyExists_Error(t *testing.T) { - type fields struct { - item interface{} - } - tests := []struct { - name string - fields fields - want string - }{ - // TODO: Add test cases. - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - e := &ErrAlreadyExists{ - item: tt.fields.item, - } - if got := e.Error(); got != tt.want { - t.Errorf("Error() = %v, want %v", got, tt.want) - } - }) - } -} - -func TestErrInvalidParameters_Error(t *testing.T) { - type fields struct { - f interface{} - r interface{} - } - tests := []struct { - name string - fields fields - want string - }{ - // TODO: Add test cases. - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - e := ErrInvalidParameters{ - f: tt.fields.f, - r: tt.fields.r, - } - if got := e.Error(); got != tt.want { - t.Errorf("Error() = %v, want %v", got, tt.want) - } - }) - } -} - -func TestErrInvalidTransportOptions_Error(t *testing.T) { - type fields struct { - t interface{} - } - tests := []struct { - name string - fields fields - want string - }{ - // TODO: Add test cases. - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - e := ErrInvalidTransportOptions{ - t: tt.fields.t, - } - if got := e.Error(); got != tt.want { - t.Errorf("Error() = %v, want %v", got, tt.want) - } - }) - } -} - -func TestErrInvalidTypeAssertion_Error(t *testing.T) { - type fields struct { - v interface{} - t interface{} - } - tests := []struct { - name string - fields fields - want string - }{ - // TODO: Add test cases. - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - e := ErrInvalidTypeAssertion{ - v: tt.fields.v, - t: tt.fields.t, - } - if got := e.Error(); got != tt.want { - t.Errorf("Error() = %v, want %v", got, tt.want) - } - }) - } -} - -func TestErrNilClient_Error(t *testing.T) { - tests := []struct { - name string - want string - }{ - // TODO: Add test cases. - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - e := &ErrNilClient{} - if got := e.Error(); got != tt.want { - t.Errorf("Error() = %v, want %v", got, tt.want) - } - }) - } -} - -func TestErrNil_Error(t *testing.T) { - tests := []struct { - name string - want string - }{ - // TODO: Add test cases. - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - e := &ErrNil{} - if got := e.Error(); got != tt.want { - t.Errorf("Error() = %v, want %v", got, tt.want) - } - }) - } -} - -func TestErrNotFound_Error(t *testing.T) { - type fields struct { - id interface{} - } - tests := []struct { - name string - fields fields - want string - }{ - // TODO: Add test cases. - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - e := &ErrNotFound{ - id: tt.fields.id, - } - if got := e.Error(); got != tt.want { - t.Errorf("Error() = %v, want %v", got, tt.want) - } - }) - } -} - -func TestErrNotYetImplemented_Error(t *testing.T) { - tests := []struct { - name string - want string - }{ - // TODO: Add test cases. - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - e := ErrNotYetImplemented{} - if got := e.Error(); got != tt.want { - t.Errorf("Error() = %v, want %v", got, tt.want) - } - }) - } -} - -func TestErrUnsupportedPath_Error(t *testing.T) { - type fields struct { - p interface{} - } - tests := []struct { - name string - fields fields - want string - }{ - // TODO: Add test cases. - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - e := ErrUnsupportedPath{ - p: tt.fields.p, - } - if got := e.Error(); got != tt.want { - t.Errorf("Error() = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/nucleus/gnmi_transport_test.go b/nucleus/gnmi_transport_test.go index 9bc9a4de923b8d29fa0ca9ba5481f9b8e3ff51ca..d77cbe0caef4de1daf8f373b0bb821724ba3a0e3 100644 --- a/nucleus/gnmi_transport_test.go +++ b/nucleus/gnmi_transport_test.go @@ -47,6 +47,7 @@ func TestMain(m *testing.M) { testSetupPnd() testSetupStore() testSetupSbi() + testSetupHttp() testSetupIntegration() os.Exit(m.Run()) } diff --git a/nucleus/http.go b/nucleus/http.go index 7463c4d78135b8aed2114e4a8d1cdf88d79fa619..57b94ade9e5bafc855c7dc285408565079df5b6f 100644 --- a/nucleus/http.go +++ b/nucleus/http.go @@ -2,24 +2,40 @@ package nucleus import ( "code.fbi.h-da.de/cocsn/gosdn/forks/goarista/gnmi" + "context" "fmt" "github.com/google/uuid" gpb "github.com/openconfig/gnmi/proto/gnmi" log "github.com/sirupsen/logrus" "net/http" "net/url" + "time" ) -const basePath = "/api" +func stopHttpServer() error { + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + log.Info("shutting down http server") + return c.httpServer.Shutdown(ctx) +} -// deprecated -func httpAPI() (err error) { - http.HandleFunc(basePath, httpHandler) +func registerHttpHandler(){ + defer func() { + if r := recover(); r != nil { + fmt.Println("Recovered in f", r) + } + }() + http.HandleFunc("/api", httpHandler) http.HandleFunc("/livez", healthCheck) - http.HandleFunc("/readyz", healthCheck) + http.HandleFunc("/readyz", readynessCheck) +} +// deprecated +func httpAPI() (err error) { + registerHttpHandler() + c.httpServer = &http.Server{Addr: ":8080"} go func() { - err = http.ListenAndServe(":8080", nil) + err = c.httpServer.ListenAndServe() if err != nil { return } @@ -31,6 +47,11 @@ func healthCheck(writer http.ResponseWriter, request *http.Request) { writer.WriteHeader(http.StatusOK) } +func readynessCheck(writer http.ResponseWriter, request *http.Request) { + writer.WriteHeader(http.StatusOK) +} + +// nolint func httpHandler(writer http.ResponseWriter, request *http.Request) { log.WithFields(log.Fields{ "request": request, @@ -39,6 +60,7 @@ func httpHandler(writer http.ResponseWriter, request *http.Request) { query, err := url.ParseQuery(request.URL.RawQuery) if err != nil { log.Error(err) + writer.WriteHeader(http.StatusBadRequest) return } @@ -63,9 +85,19 @@ func httpHandler(writer http.ResponseWriter, request *http.Request) { pnd, err = c.pndc.get(pid) if err != nil { log.Error(err) + writer.WriteHeader(http.StatusInternalServerError) + return } sbic := pnd.GetSBIs() sbi, err = sbic.(*sbiStore).get(sid) + if err != nil { + log.WithFields(log.Fields{ + "requested uuid": sid, + "available uuids": sbic.(*sbiStore).UUIDs(), + }).Error(err) + writer.WriteHeader(http.StatusInternalServerError) + return + } } switch query.Get("q") { @@ -83,7 +115,7 @@ func httpHandler(writer http.ResponseWriter, request *http.Request) { }) err = pnd.AddDevice(d) if err != nil { - writer.WriteHeader(http.StatusBadRequest) + writer.WriteHeader(http.StatusInternalServerError) log.Error(err) return } diff --git a/nucleus/http_test.go b/nucleus/http_test.go index b4210a6a84ba092b1680eeb2667b609b24bf4748..4658b16423a1b5261989b8508b98b38a7b842aa9 100644 --- a/nucleus/http_test.go +++ b/nucleus/http_test.go @@ -1,25 +1,179 @@ package nucleus import ( - "context" + "code.fbi.h-da.de/cocsn/gosdn/mocks" + "errors" + "github.com/google/uuid" + log "github.com/sirupsen/logrus" + "github.com/stretchr/testify/mock" + "net/http" "testing" ) -func Test_httpApi(t *testing.T) { - type args struct { - ctx context.Context +const apiEndpoint = "http://localhost:8080" + +var sbi SouthboundInterface +var pnd PrincipalNetworkDomain +var args string +var argsNotFound string +var d Device + +func testSetupHttp() { + testSetupPnd() + sbi = &OpenConfig{id: defaultSbiID} + sbi.Schema() + var err error + pnd, err = NewPND("test", "test pnd", defaultPndID, sbi) + if err != nil { + log.Fatal(err) + } + d = mockDevice() + tr := d.Transport.(*mocks.Transport) + mockError := errors.New("mock error") + tr.On("Get", mockContext, "/system/config/hostname").Return(mock.Anything, nil) + tr.On("Get", mockContext, "error").Return(mock.Anything, mockError) + tr.On("Set", mockContext, mock.Anything).Return(mock.Anything, nil) + tr.On("ProcessResponse", mock.Anything, mock.Anything, mock.Anything).Return(nil) + if err := pnd.AddDevice(&d); err != nil { + log.Fatal(err) + } + args = "&uuid=" + mdid.String() + "&pnd=" + defaultPndID.String() + "&sbi=" + defaultSbiID.String() + argsNotFound = "&uuid=" + uuid.New().String() + "&pnd=" + defaultPndID.String() + "&sbi=" + defaultSbiID.String() + + c = &Core{ + pndc: pndStore{store{}}, + sbic: sbiStore{store{}}, + } + if err := c.sbic.add(sbi); err != nil { + log.Fatal(err) + } + if err := c.pndc.add(pnd); err != nil { + log.Fatal(err) } +} + +func Test_httpApi(t *testing.T) { tests := []struct { name string - args args + request string + want *http.Response wantErr bool }{ - // TODO: Add test cases. + { + name: "liveliness indicator", + request: apiEndpoint + "/livez", + want: &http.Response{StatusCode: http.StatusOK}, + wantErr: false, + }, + { + name: "readyness indicator", + request: apiEndpoint + "/readyz", + want: &http.Response{StatusCode: http.StatusOK}, + wantErr: false, + }, + { + name: "init", + request: apiEndpoint + "/api?q=init", + want: &http.Response{StatusCode: http.StatusOK}, + wantErr: false, + }, + { + name: "get-ids", + request: apiEndpoint + "/api?q=getIDs", + want: &http.Response{StatusCode: http.StatusOK}, + wantErr: false, + }, + { + name: "add-device", + request: apiEndpoint + "/api?q=addDevice" + args, + want: &http.Response{StatusCode: http.StatusCreated}, + wantErr: false, + }, + { + name: "request", + request: apiEndpoint + "/api?q=request" + args + "&path=/system/config/hostname", + want: &http.Response{StatusCode: http.StatusOK}, + wantErr: false, + }, + { + name: "request not found", + request: apiEndpoint + "/api?q=request" + argsNotFound + "&path=/system/config/hostname", + want: &http.Response{StatusCode: http.StatusNotFound}, + wantErr: false, + }, + { + name: "request internal server error", + request: apiEndpoint + "/api?q=request" + args + "&path=error", + want: &http.Response{StatusCode: http.StatusInternalServerError}, + wantErr: false, + }, + { + name: "request-all", + request: apiEndpoint + "/api?q=requestAll" + args + "&path=/system/config/hostname", + want: &http.Response{StatusCode: http.StatusOK}, + wantErr: false, + }, + { + name: "request-all internal server error", + request: apiEndpoint + "/api?q=requestAll" + args + "&path=error", + want: &http.Response{StatusCode: http.StatusInternalServerError}, + wantErr: false, + }, + + { + name: "get-device", + request: apiEndpoint + "/api?q=getDevice" + args, + want: &http.Response{StatusCode: http.StatusOK}, + wantErr: false, + }, + { + name: "get-device not found", + request: apiEndpoint + "/api?q=getDevice" + argsNotFound, + want: &http.Response{StatusCode: http.StatusNotFound}, + wantErr: false, + }, + { + name: "set", + request: apiEndpoint + "/api?q=set" + args + "&path=/system/config/hostname&value=ceos3000", + want: &http.Response{StatusCode: http.StatusOK}, + wantErr: false, + }, + { + name: "internal server errror: wrong pnd", + request: apiEndpoint + "/api?pnd=" + uuid.New().String(), + want: &http.Response{StatusCode: http.StatusInternalServerError}, + wantErr: false, + }, + { + name: "internal server errror: wrong sbi", + request: apiEndpoint + "/api?sbi=" + uuid.New().String(), + want: &http.Response{StatusCode: http.StatusInternalServerError}, + wantErr: false, + }, + } + if err := httpAPI(); err != nil { + t.Errorf("httpApi() error = %v", err) + return } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if err := httpAPI(); (err != nil) != tt.wantErr { - t.Errorf("httpAPI() error = %v, wantErr %v", err, tt.wantErr) + got, err := http.Get(tt.request) + if (err != nil) != tt.wantErr { + t.Errorf("httpApi() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got.StatusCode != tt.want.StatusCode { + t.Errorf("httpApi() got: %v, want %v", got.StatusCode, tt.want.StatusCode) + } + if tt.name == "add-device" { + for k := range pnd.(*pndImplementation).devices.store{ + if k != mdid { + if err := pnd.RemoveDevice(k); err != nil { + t.Error(err) + return + } + } + } } }) } diff --git a/test/terraform/containers.tf b/test/terraform/containers.tf index a6e381a9169eef45337aaf0b843bc1c89ddebef0..f9144a450c0691daef380b918ccbbb90f311a0c2 100644 --- a/test/terraform/containers.tf +++ b/test/terraform/containers.tf @@ -5,13 +5,22 @@ resource "docker_container" "gosdn" { image = docker_image.gosdn.name restart = "always" + networks_advanced { + name = "bridge" + } + ports { internal = 55055 external = 5555 } + ports { + internal = 8080 + external = 8080 + } + env = [ - "GOSDN_DEBUG=", + "GOSDN_LOG=debug", ] }