diff --git a/cmd/gnmi-target/target.go b/cmd/gnmi-target/target.go index 4239ea9faaa0bb556e3f122cba9441b0e9b352e5..e72ef7acb77d43cefb5770f79828f8cc6128cac0 100644 --- a/cmd/gnmi-target/target.go +++ b/cmd/gnmi-target/target.go @@ -6,7 +6,6 @@ import ( oc "code.fbi.h-da.de/cocsn/yang-models/generated/arista" "context" "flag" - "fmt" "github.com/google/gnxi/utils/credentials" pb "github.com/openconfig/gnmi/proto/gnmi" "github.com/openconfig/ygot/ygot" @@ -16,7 +15,6 @@ import ( "google.golang.org/grpc/reflection" "google.golang.org/grpc/status" "net" - "os" "reflect" ) @@ -76,19 +74,6 @@ func main() { oc.Unmarshal, oc.ΛEnum) - flag.Usage = func() { - fmt.Fprintf(os.Stderr, "Supported models:\n") - for _, m := range model.SupportedModels() { - fmt.Fprintf(os.Stderr, " %s\n", m) - } - fmt.Fprintf(os.Stderr, "\n") - fmt.Fprintf(os.Stderr, "Usage of %s:\n", os.Args[0]) - flag.PrintDefaults() - } - - flag.Set("logtostderr", "true") - flag.Parse() - g := grpc.NewServer() var configData []byte diff --git a/forks/goarista/openconfig/client/client.go b/forks/goarista/openconfig/client/client.go deleted file mode 100644 index 1d7d1992f375d06b747161dc4570124696202fac..0000000000000000000000000000000000000000 --- a/forks/goarista/openconfig/client/client.go +++ /dev/null @@ -1,132 +0,0 @@ -// Copyright (c) 2016 Arista Networks, Inc. -// Use of this source code is governed by the Apache License 2.0 -// that can be found in the COPYING file. - -// Package ciena provides helper functions for OpenConfig CLI tools. -package client - -import ( - "io" - "strings" - "sync" - - "github.com/golang/glog" - "github.com/golang/protobuf/proto" - "github.com/openconfig/reference/rpc/openconfig" - "golang.org/x/net/context" - "google.golang.org/grpc" - "google.golang.org/grpc/metadata" -) - -const defaultPort = "6030" - -// PublishFunc is the method to publish responses -type PublishFunc func(addr string, message proto.Message) - -// Client is a connected gRPC ciena -type Client struct { - client openconfig.OpenConfigClient - ctx context.Context - device string -} - -// New creates a new gRPC ciena and connects it -func New(username, password, addr string, opts []grpc.DialOption) *Client { - device := addr - if !strings.ContainsRune(addr, ':') { - addr += ":" + defaultPort - } - // Make sure we don't move past the grpc.Dial() call until we actually - // established an HTTP/2 connection successfully. - opts = append(opts, grpc.WithBlock()) - conn, err := grpc.Dial(addr, opts...) - if err != nil { - glog.Fatalf("Failed to dial: %s", err) - } - glog.Infof("Connected to %s", addr) - client := openconfig.NewOpenConfigClient(conn) - - ctx := context.Background() - if username != "" { - ctx = metadata.NewOutgoingContext(ctx, metadata.Pairs( - "username", username, - "password", password)) - } - return &Client{ - client: client, - device: device, - ctx: ctx, - } -} - -// Get sends a get request and returns the responses -func (c *Client) Get(path string) []*openconfig.Notification { - req := &openconfig.GetRequest{ - Path: []*openconfig.Path{ - { - Element: strings.Split(path, "/"), - }, - }, - } - response, err := c.client.Get(c.ctx, req) - if err != nil { - glog.Fatalf("Get failed: %s", err) - } - return response.Notification -} - -// Subscribe sends subscriptions, and consumes responses. -// The given publish function is used to publish SubscribeResponses received -// for the given subscriptions, when connected to the given host, with the -// given user/pass pair, or the ciena-side cert specified in the gRPC opts. -// This function does not normally return so it should probably be run in its -// own goroutine. When this function returns, the given WaitGroup is marked -// as done. -func (c *Client) Subscribe(wg *sync.WaitGroup, subscriptions []string, - publish PublishFunc) { - defer wg.Done() - stream, err := c.client.Subscribe(c.ctx) - if err != nil { - glog.Fatalf("Subscribe failed: %s", err) - } - defer stream.CloseSend() - - for _, path := range subscriptions { - sub := &openconfig.SubscribeRequest{ - Request: &openconfig.SubscribeRequest_Subscribe{ - Subscribe: &openconfig.SubscriptionList{ - Subscription: []*openconfig.Subscription{ - { - Path: &openconfig.Path{Element: strings.Split(path, "/")}, - }, - }, - }, - }, - } - - glog.Infof("Sending subscribe request: %s", sub) - err = stream.Send(sub) - if err != nil { - glog.Fatalf("Failed to subscribe: %s", err) - } - } - - for { - resp, err := stream.Recv() - if err != nil { - if err != io.EOF { - glog.Fatalf("Error received from the server: %s", err) - } - return - } - switch resp := resp.Response.(type) { - case *openconfig.SubscribeResponse_SyncResponse: - if !resp.SyncResponse { - panic("initial sync failed," + - " check that you're using a ciena compatible with the server") - } - } - glog.V(3).Info(resp) - publish(c.device, resp) - } -} diff --git a/forks/goarista/openconfig/client/flags.go b/forks/goarista/openconfig/client/flags.go deleted file mode 100644 index fb91bba3715c286babedb3e58caf221b576d213b..0000000000000000000000000000000000000000 --- a/forks/goarista/openconfig/client/flags.go +++ /dev/null @@ -1,113 +0,0 @@ -// Copyright (c) 2016 Arista Networks, Inc. -// Use of this source code is governed by the Apache License 2.0 -// that can be found in the COPYING file. - -package client - -import ( - "crypto/tls" - "crypto/x509" - "flag" - "io/ioutil" - "os" - "strings" - - "github.com/golang/glog" - "google.golang.org/grpc" - "google.golang.org/grpc/credentials" -) - -const ( - // HostnameArg is the value to be replaced by the actual hostname - HostnameArg = "HOSTNAME" -) - -// ParseHostnames parses a comma-separated list of names and replaces HOSTNAME with the current -// hostname in it -func ParseHostnames(list string) ([]string, error) { - items := strings.Split(list, ",") - hostname, err := os.Hostname() - if err != nil { - return nil, err - } - names := make([]string, len(items)) - for i, name := range items { - if name == HostnameArg { - name = hostname - } - names[i] = name - } - return names, nil -} - -// ParseFlags registers some additional common flags, -// parses the flags, and returns the resulting gRPC options, -// and other settings to connect to the gRPC interface. -func ParseFlags() (username string, password string, subscriptions, addrs []string, - opts []grpc.DialOption) { - - var ( - addrsFlag = flag.String("addrs", "localhost:6030", - "Comma-separated list of addresses of OpenConfig gRPC servers. The address 'HOSTNAME' "+ - "is replaced by the current hostname.") - - caFileFlag = flag.String("cafile", "", - "Path to server TLS certificate file") - - certFileFlag = flag.String("certfile", "", - "Path to ciena TLS certificate file") - - keyFileFlag = flag.String("keyfile", "", - "Path to ciena TLS private key file") - - passwordFlag = flag.String("password", "", - "Password to authenticate with") - - subscribeFlag = flag.String("subscribe", "", - "Comma-separated list of paths to subscribe to upon connecting to the server") - - usernameFlag = flag.String("username", "", - "Username to authenticate with") - - tlsFlag = flag.Bool("tls", false, - "Enable TLS") - ) - - flag.Parse() - if *tlsFlag || *caFileFlag != "" || *certFileFlag != "" { - config := &tls.Config{} - if *caFileFlag != "" { - b, err := ioutil.ReadFile(*caFileFlag) - if err != nil { - glog.Fatal(err) - } - cp := x509.NewCertPool() - if !cp.AppendCertsFromPEM(b) { - glog.Fatalf("credentials: failed to append certificates") - } - config.RootCAs = cp - } else { - config.InsecureSkipVerify = true - } - if *certFileFlag != "" { - if *keyFileFlag == "" { - glog.Fatalf("Please provide both -certfile and -keyfile") - } - cert, err := tls.LoadX509KeyPair(*certFileFlag, *keyFileFlag) - if err != nil { - glog.Fatal(err) - } - config.Certificates = []tls.Certificate{cert} - } - opts = append(opts, grpc.WithTransportCredentials(credentials.NewTLS(config))) - } else { - opts = append(opts, grpc.WithInsecure()) - } - var err error - addrs, err = ParseHostnames(*addrsFlag) - if err != nil { - glog.Fatal(err) - } - subscriptions = strings.Split(*subscribeFlag, ",") - return *usernameFlag, *passwordFlag, subscriptions, addrs, opts -} diff --git a/forks/goarista/openconfig/json.go b/forks/goarista/openconfig/json.go deleted file mode 100644 index 8eba88baded76b426451ef862d332a3c712a1db2..0000000000000000000000000000000000000000 --- a/forks/goarista/openconfig/json.go +++ /dev/null @@ -1,237 +0,0 @@ -// Copyright (c) 2016 Arista Networks, Inc. -// Use of this source code is governed by the Apache License 2.0 -// that can be found in the COPYING file. - -package openconfig - -import ( - "bytes" - "encoding/json" - "fmt" - "strings" - - "github.com/openconfig/reference/rpc/openconfig" -) - -// joinPath builds a string out of an Element -func joinPath(path *openconfig.Path) string { - if path == nil { - return "" - } - return strings.Join(path.Element, "/") -} - -func convertUpdate(update *openconfig.Update) (interface{}, error) { - switch update.Value.Type { - case openconfig.Type_JSON: - var value interface{} - decoder := json.NewDecoder(bytes.NewReader(update.Value.Value)) - decoder.UseNumber() - if err := decoder.Decode(&value); err != nil { - return nil, fmt.Errorf("Malformed JSON update %q in %s", - update.Value.Value, update) - } - return value, nil - case openconfig.Type_BYTES: - return update.Value.Value, nil - default: - return nil, - fmt.Errorf("Unhandled type of value %v in %s", update.Value.Type, update) - } -} - -// NotificationToJSON converts a Notification into a JSON string -func NotificationToJSON(notif *openconfig.Notification) (string, error) { - m := make(map[string]interface{}, 1) - m["timestamp"] = notif.Timestamp - m["path"] = "/" + joinPath(notif.Prefix) - if len(notif.Update) != 0 { - updates := make(map[string]interface{}, len(notif.Update)) - var err error - for _, update := range notif.Update { - updates[joinPath(update.Path)], err = convertUpdate(update) - if err != nil { - return "", err - } - } - m["updates"] = updates - } - if len(notif.Delete) != 0 { - deletes := make([]string, len(notif.Delete)) - for i, del := range notif.Delete { - deletes[i] = joinPath(del) - } - m["deletes"] = deletes - } - m = map[string]interface{}{"notification": m} - js, err := json.MarshalIndent(m, "", " ") - if err != nil { - return "", err - } - return string(js), nil -} - -// SubscribeResponseToJSON converts a SubscribeResponse into a JSON string -func SubscribeResponseToJSON(resp *openconfig.SubscribeResponse) (string, error) { - m := make(map[string]interface{}, 1) - var err error - switch resp := resp.Response.(type) { - case *openconfig.SubscribeResponse_Update: - return NotificationToJSON(resp.Update) - case *openconfig.SubscribeResponse_Heartbeat: - m["heartbeat"] = resp.Heartbeat.Interval - case *openconfig.SubscribeResponse_SyncResponse: - m["syncResponse"] = resp.SyncResponse - default: - return "", fmt.Errorf("Unknown type of response: %T: %s", resp, resp) - } - js, err := json.MarshalIndent(m, "", " ") - if err != nil { - return "", err - } - return string(js), nil -} - -// EscapeFunc is the escaping method for attribute names -type EscapeFunc func(k string) string - -// escapeValue looks for maps in an interface and escapes their keys -func escapeValue(value interface{}, escape EscapeFunc) interface{} { - valueMap, ok := value.(map[string]interface{}) - if !ok { - return value - } - escapedMap := make(map[string]interface{}, len(valueMap)) - for k, v := range valueMap { - escapedKey := escape(k) - escapedMap[escapedKey] = escapeValue(v, escape) - } - return escapedMap -} - -// addPathToMap creates a map[string]interface{} from a path. It returns the node in -// the map corresponding to the last element in the path -func addPathToMap(root map[string]interface{}, path []string, escape EscapeFunc) ( - map[string]interface{}, error) { - parent := root - for _, element := range path { - k := escape(element) - node, found := parent[k] - if !found { - node = map[string]interface{}{} - parent[k] = node - } - var ok bool - parent, ok = node.(map[string]interface{}) - if !ok { - return nil, fmt.Errorf( - "Node %s is of type %T (expected map[string]interface traversing %q)", - element, node, path) - } - } - return parent, nil -} - -// NotificationToMap maps a Notification into a nested map of entities -func NotificationToMap(addr string, notification *openconfig.Notification, - escape EscapeFunc) (map[string]interface{}, error) { - if escape == nil { - escape = func(name string) string { - return name - } - } - prefix := notification.GetPrefix() - - // Convert deletes - var deletes map[string]interface{} - notificationDeletes := notification.GetDelete() - if notificationDeletes != nil { - deletes = make(map[string]interface{}) - node := deletes - if prefix != nil { - var err error - node, err = addPathToMap(node, prefix.Element, escape) - if err != nil { - return nil, err - } - } - for _, delete := range notificationDeletes { - _, err := addPathToMap(node, delete.Element, escape) - if err != nil { - return nil, err - } - } - } - - // Convert updates - var updates map[string]interface{} - notificationUpdates := notification.GetUpdate() - if notificationUpdates != nil { - updates = make(map[string]interface{}) - node := updates - if prefix != nil { - var err error - node, err = addPathToMap(node, prefix.Element, escape) - if err != nil { - return nil, err - } - } - for _, update := range notificationUpdates { - updateNode := node - path := update.GetPath() - elementLen := len(path.Element) - - // Convert all elements before the leaf - if elementLen > 1 { - parentElements := path.Element[:elementLen-1] - var err error - updateNode, err = addPathToMap(updateNode, parentElements, escape) - if err != nil { - return nil, err - } - } - - // Convert the value in the leaf - value := update.GetValue() - var unmarshaledValue interface{} - switch value.Type { - case openconfig.Type_JSON: - if err := json.Unmarshal(value.Value, &unmarshaledValue); err != nil { - return nil, err - } - case openconfig.Type_BYTES: - unmarshaledValue = update.Value.Value - default: - return nil, fmt.Errorf("Unexpected value type %s for path %v", - value.Type, path) - } - updateNode[escape(path.Element[elementLen-1])] = escapeValue( - unmarshaledValue, escape) - } - } - - // Build the complete map to return - root := map[string]interface{}{ - "timestamp": notification.Timestamp, - } - if addr != "" { - root["dataset"] = addr - } - if deletes != nil { - root["delete"] = deletes - } - if updates != nil { - root["update"] = updates - } - return root, nil -} - -// NotificationToJSONDocument maps a Notification into a single JSON document -func NotificationToJSONDocument(addr string, notification *openconfig.Notification, - escape EscapeFunc) ([]byte, error) { - m, err := NotificationToMap(addr, notification, escape) - if err != nil { - return nil, err - } - return json.Marshal(m) -} diff --git a/forks/goarista/openconfig/json_test.go b/forks/goarista/openconfig/json_test.go deleted file mode 100644 index 1dfc41d5789b9c8fcbb1a6a8b30918f77873a88a..0000000000000000000000000000000000000000 --- a/forks/goarista/openconfig/json_test.go +++ /dev/null @@ -1,143 +0,0 @@ -// Copyright (c) 2016 Arista Networks, Inc. -// Use of this source code is governed by the Apache License 2.0 -// that can be found in the COPYING file. - -package openconfig - -import ( - "encoding/json" - "testing" - - "github.com/aristanetworks/goarista/test" - - "github.com/openconfig/reference/rpc/openconfig" -) - -func TestNotificationToMap(t *testing.T) { - value := map[string]interface{}{ - "239.255.255.250_0.0.0.0": map[string]interface{}{ - "creationTime": 4.567969230573434e+06, - }, - } - valueJSON, err := json.Marshal(value) - if err != nil { - t.Fatal(err) - } - tests := []struct { - notification openconfig.Notification - json map[string]interface{} - }{{ - notification: openconfig.Notification{ - Prefix: &openconfig.Path{ - Element: []string{ - "foo", - }, - }, - Update: []*openconfig.Update{ - { - Path: &openconfig.Path{ - Element: []string{ - "route1", - }, - }, - Value: &openconfig.Value{ - Value: valueJSON, - }, - }, { - Path: &openconfig.Path{ - Element: []string{ - "route2", - }, - }, - Value: &openconfig.Value{ - Value: valueJSON, - }, - }}, - }, - json: map[string]interface{}{ - "timestamp": int64(0), - "dataset": "cairo", - "update": map[string]interface{}{ - "foo": map[string]interface{}{ - "route1": map[string]interface{}{ - "239.255.255.250_0.0.0.0": map[string]interface{}{ - "creationTime": 4.567969230573434e+06, - }, - }, - "route2": map[string]interface{}{ - "239.255.255.250_0.0.0.0": map[string]interface{}{ - "creationTime": 4.567969230573434e+06, - }, - }, - }, - }, - }, - }, { - notification: openconfig.Notification{ - Prefix: &openconfig.Path{ - Element: []string{ - "foo", "bar", - }, - }, - Delete: []*openconfig.Path{ - { - Element: []string{ - "route", "237.255.255.250_0.0.0.0", - }}, - { - Element: []string{ - "route", "238.255.255.250_0.0.0.0", - }, - }, - }, - Update: []*openconfig.Update{{ - Path: &openconfig.Path{ - Element: []string{ - "route", - }, - }, - Value: &openconfig.Value{ - Value: valueJSON, - }, - }}, - }, - json: map[string]interface{}{ - "timestamp": int64(0), - "dataset": "cairo", - "delete": map[string]interface{}{ - "foo": map[string]interface{}{ - "bar": map[string]interface{}{ - "route": map[string]interface{}{ - "237.255.255.250_0.0.0.0": map[string]interface{}{}, - "238.255.255.250_0.0.0.0": map[string]interface{}{}, - }, - }, - }, - }, - "update": map[string]interface{}{ - "foo": map[string]interface{}{ - "bar": map[string]interface{}{ - "route": map[string]interface{}{ - "239.255.255.250_0.0.0.0": map[string]interface{}{ - "creationTime": 4.567969230573434e+06, - }, - }, - }, - }, - }, - }, - }} - for _, tcase := range tests { - actual, err := NotificationToMap("cairo", &tcase.notification, nil) - if err != nil { - t.Fatal(err) - } - diff := test.Diff(tcase.json, actual) - if len(diff) > 0 { - expectedJSON, _ := json.Marshal(tcase.json) - actualJSON, _ := json.Marshal(actual) - t.Fatalf("Unexpected diff: %s\nExpected:\n%s\nGot:\n%s\n)", diff, expectedJSON, - actualJSON) - } - } -} diff --git a/forks/google/gnmi/server.go b/forks/google/gnmi/server.go index 3af5d9b50e74aa79c77a87f116920349374fbdc9..0cc56f64d062544d2ab1d5db5b00924e58285c6c 100644 --- a/forks/google/gnmi/server.go +++ b/forks/google/gnmi/server.go @@ -47,7 +47,7 @@ type ConfigCallback func(ygot.ValidatedGoStruct) error var ( pbRootPath = &pb.Path{} - supportedEncodings = []pb.Encoding{pb.Encoding_PROTO} + supportedEncodings = []pb.Encoding{pb.Encoding_PROTO, pb.Encoding_JSON_IETF, pb.Encoding_JSON} ) // Server struct maintains the data structure for device config and implements the interface of gnmi server. diff --git a/go.mod b/go.mod index dd60fd272aab3c56c223acadd69ecda15a8b365d..2ee58ea728a76d130fa59bf80d99c4410a83c6b9 100644 --- a/go.mod +++ b/go.mod @@ -21,6 +21,7 @@ require ( github.com/rivo/tview v0.0.0-20201018122409-d551c850a743 github.com/sirupsen/logrus v1.4.2 github.com/spf13/viper v1.7.1 + github.com/stretchr/testify v1.6.1 github.com/tidwall/gjson v1.6.3 golang.org/x/net v0.0.0-20201216054612-986b41b23924 google.golang.org/grpc v1.34.0 diff --git a/go.sum b/go.sum index 969f8580799a31dd91384a2076fd0099e1f90d4b..a13eb5160fd2c4d42be432c818f1ac93d2137dba 100644 --- a/go.sum +++ b/go.sum @@ -516,6 +516,7 @@ github.com/spf13/viper v1.7.1 h1:pM5oEahlgWv/WnHXpgbKz7iLIxRf65tye2Ci+XFK5sk= github.com/spf13/viper v1.7.1/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48= github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= diff --git a/nucleus/cli-handling_test.go b/nucleus/cli-handling_test.go new file mode 100644 index 0000000000000000000000000000000000000000..5fa897297a3cbdddc8b9156600d469cd7e168cd9 --- /dev/null +++ b/nucleus/cli-handling_test.go @@ -0,0 +1,61 @@ +package nucleus + +import ( + "testing" +) + +func Test_buf_Write(t *testing.T) { + +} + +func Test_getCLIGoing(t *testing.T) { + +} + +func Test_server_AddDevice(t *testing.T) { + +} + +func Test_server_BroadcastLog(t *testing.T) { + +} + +func Test_server_CreateLogStream(t *testing.T) { + +} + +func Test_server_CreatePND(t *testing.T) { + +} + +func Test_server_GetAllPNDs(t *testing.T) { + +} + +func Test_server_GetAllSBINames(t *testing.T) { + +} + +func Test_server_HandleDeviceGetRequest(t *testing.T) { + +} + +func Test_server_SayHello(t *testing.T) { + +} + +func Test_server_Shutdown(t *testing.T) { + +} + +func Test_server_TAPIGetEdge(t *testing.T) { + +} + +func Test_server_TAPIGetEdgeNode(t *testing.T) { + +} + +func Test_server_TAPIGetLink(t *testing.T) { + +} diff --git a/nucleus/gnmi_transport.go b/nucleus/gnmi_transport.go index 5d9dde72e3fc9a4beb7d9bcfdb18d079579aa058..964c22d89ccced928bc7fef7c0e923e4fb255f4a 100644 --- a/nucleus/gnmi_transport.go +++ b/nucleus/gnmi_transport.go @@ -102,7 +102,7 @@ func gnmiFullPath(prefix, path *gpb.Path) (*gpb.Path, error) { // Capabilities calls GNMI capabilities func (g *Gnmi) Capabilities(ctx context.Context) (interface{}, error) { - client, err := gnmi.Dial(ctx.Value("config").(*gnmi.Config)) + client, err := gnmi.Dial(g.config) if err != nil { return nil, err } diff --git a/nucleus/gnmi_transport_test.go b/nucleus/gnmi_transport_test.go index 0fa4ffa903a1f894325aaf5211341e19a2df754a..83471ce9e3c3ea0c4e0f55e68acf98f846cf1aa2 100644 --- a/nucleus/gnmi_transport_test.go +++ b/nucleus/gnmi_transport_test.go @@ -1 +1,535 @@ package nucleus + +import ( + "code.fbi.h-da.de/cocsn/gosdn/forks/goarista/gnmi" + "code.fbi.h-da.de/cocsn/gosdn/test_resources" + "context" + log "github.com/golang/glog" + gpb "github.com/openconfig/gnmi/proto/gnmi" + "github.com/openconfig/gnmi/proto/gnmi_ext" + "github.com/openconfig/goyang/pkg/yang" + "github.com/openconfig/ygot/ytypes" + "os" + "reflect" + "testing" +) + +// TestMain bootstraps all tests. Humongous best +func TestMain(m *testing.M) { + testSetupGnmi() + os.Exit(m.Run()) +} + +// testSetupGnmi bootstraps tests for gnmi transport +func testSetupGnmi() { + // TODO: Set sane defaults + gnmiConfig = &gnmi.Config{ + Username: "test", + Password: "test", + Addr: "localhost:13371", + Encoding: gpb.Encoding_PROTO, + } + + transport = &Gnmi{ + SetNode: nil, + RespChan: make(chan *gpb.SubscribeResponse), + config: gnmiConfig, + } + + startGnmiTarget = make(chan string) + stopGnmiTarget = make(chan bool) + go targetRunner() +} + +func targetRunner() { + for { + addr := <-startGnmiTarget + if err := test_resources.GnmiTarget(stopGnmiTarget, addr); err != nil { + log.Fatal(err) + } + } +} + +var transport *Gnmi +var gnmiConfig *gnmi.Config +var startGnmiTarget chan string +var stopGnmiTarget chan bool + +func TestGnmi_Capabilities(t *testing.T) { + type fields struct { + config *gnmi.Config + } + type args struct { + ctx context.Context + endpoint string + } + tests := []struct { + name string + fields fields + args args + want *gpb.CapabilityResponse + wantErr bool + }{ + { + name: "proto", + fields: fields{config: gnmiConfig}, + args: args{ + ctx: context.Background(), + endpoint: gnmiConfig.Addr, + }, + want: &gpb.CapabilityResponse{}, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + startGnmiTarget <- tt.args.endpoint + g := &Gnmi{ + config: tt.fields.config, + } + got, err := g.Capabilities(tt.args.ctx) + if (err != nil) != tt.wantErr { + t.Errorf("Capabilities() error = %v, wantErr %v", err, tt.wantErr) + return + } + _, ok := got.(*gpb.CapabilityResponse) + if !ok { + t.Errorf("Capabilities() got = %v, want %v", got, tt.want) + } + stopGnmiTarget <- true + }) + } +} + +func TestGnmi_Close(t *testing.T) { + type fields struct { + SetNode func(schema *yang.Entry, root interface{}, path *gpb.Path, val interface{}, opts ...ytypes.SetNodeOpt) error + RespChan chan *gpb.SubscribeResponse + config *gnmi.Config + } + tests := []struct { + name string + fields fields + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + g := &Gnmi{ + SetNode: tt.fields.SetNode, + RespChan: tt.fields.RespChan, + config: tt.fields.config, + } + if err := g.Close(); (err != nil) != tt.wantErr { + t.Errorf("Close() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func TestGnmi_Get(t *testing.T) { + type fields struct { + SetNode func(schema *yang.Entry, root interface{}, path *gpb.Path, val interface{}, opts ...ytypes.SetNodeOpt) error + RespChan chan *gpb.SubscribeResponse + config *gnmi.Config + } + type args struct { + ctx context.Context + params []string + } + tests := []struct { + name string + fields fields + args args + want interface{} + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + g := &Gnmi{ + SetNode: tt.fields.SetNode, + RespChan: tt.fields.RespChan, + config: tt.fields.config, + } + got, err := g.Get(tt.args.ctx, tt.args.params...) + 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) + } + }) + } +} + +func TestGnmi_GetConfig(t *testing.T) { + type fields struct { + SetNode func(schema *yang.Entry, root interface{}, path *gpb.Path, val interface{}, opts ...ytypes.SetNodeOpt) error + RespChan chan *gpb.SubscribeResponse + config *gnmi.Config + } + tests := []struct { + name string + fields fields + want *gnmi.Config + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + g := &Gnmi{ + SetNode: tt.fields.SetNode, + RespChan: tt.fields.RespChan, + config: tt.fields.config, + } + if got := g.GetConfig(); !reflect.DeepEqual(got, tt.want) { + t.Errorf("GetConfig() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestGnmi_ProcessResponse(t *testing.T) { + type fields struct { + SetNode func(schema *yang.Entry, root interface{}, path *gpb.Path, val interface{}, opts ...ytypes.SetNodeOpt) error + RespChan chan *gpb.SubscribeResponse + config *gnmi.Config + } + type args struct { + resp interface{} + root interface{} + s *ytypes.Schema + } + tests := []struct { + name string + fields fields + args args + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + g := &Gnmi{ + SetNode: tt.fields.SetNode, + RespChan: tt.fields.RespChan, + config: tt.fields.config, + } + if err := g.ProcessResponse(tt.args.resp, tt.args.root, tt.args.s); (err != nil) != tt.wantErr { + t.Errorf("ProcessResponse() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func TestGnmi_Set(t *testing.T) { + type fields struct { + SetNode func(schema *yang.Entry, root interface{}, path *gpb.Path, val interface{}, opts ...ytypes.SetNodeOpt) error + RespChan chan *gpb.SubscribeResponse + config *gnmi.Config + } + type args struct { + ctx context.Context + params []string + } + tests := []struct { + name string + fields fields + args args + want interface{} + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + g := &Gnmi{ + SetNode: tt.fields.SetNode, + RespChan: tt.fields.RespChan, + config: tt.fields.config, + } + got, err := g.Set(tt.args.ctx, tt.args.params...) + if (err != nil) != tt.wantErr { + t.Errorf("Set() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("Set() got = %v, want %v", got, tt.want) + } + }) + } +} + +func TestGnmi_SetConfig(t *testing.T) { + type fields struct { + SetNode func(schema *yang.Entry, root interface{}, path *gpb.Path, val interface{}, opts ...ytypes.SetNodeOpt) error + RespChan chan *gpb.SubscribeResponse + config *gnmi.Config + } + type args struct { + config *gnmi.Config + } + tests := []struct { + name string + fields fields + args args + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + + }) + } +} + +func TestGnmi_Subscribe(t *testing.T) { + type fields struct { + SetNode func(schema *yang.Entry, root interface{}, path *gpb.Path, val interface{}, opts ...ytypes.SetNodeOpt) error + RespChan chan *gpb.SubscribeResponse + config *gnmi.Config + } + type args struct { + ctx context.Context + params []string + } + tests := []struct { + name string + fields fields + args args + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + g := &Gnmi{ + SetNode: tt.fields.SetNode, + RespChan: tt.fields.RespChan, + config: tt.fields.config, + } + if err := g.Subscribe(tt.args.ctx, tt.args.params...); (err != nil) != tt.wantErr { + t.Errorf("Subscribe() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func TestGnmi_Type(t *testing.T) { + type fields struct { + SetNode func(schema *yang.Entry, root interface{}, path *gpb.Path, val interface{}, opts ...ytypes.SetNodeOpt) error + RespChan chan *gpb.SubscribeResponse + config *gnmi.Config + } + tests := []struct { + name string + fields fields + want string + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + g := &Gnmi{ + SetNode: tt.fields.SetNode, + RespChan: tt.fields.RespChan, + config: tt.fields.config, + } + if got := g.Type(); got != tt.want { + t.Errorf("Type() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestGnmi_get(t *testing.T) { + type fields struct { + SetNode func(schema *yang.Entry, root interface{}, path *gpb.Path, val interface{}, opts ...ytypes.SetNodeOpt) error + RespChan chan *gpb.SubscribeResponse + config *gnmi.Config + } + type args struct { + ctx context.Context + paths [][]string + origin string + } + tests := []struct { + name string + fields fields + args args + want interface{} + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + g := &Gnmi{ + SetNode: tt.fields.SetNode, + RespChan: tt.fields.RespChan, + config: tt.fields.config, + } + got, err := g.get(tt.args.ctx, tt.args.paths, tt.args.origin) + 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) + } + }) + } +} + +func TestGnmi_getWithRequest(t *testing.T) { + type fields struct { + SetNode func(schema *yang.Entry, root interface{}, path *gpb.Path, val interface{}, opts ...ytypes.SetNodeOpt) error + RespChan chan *gpb.SubscribeResponse + config *gnmi.Config + } + type args struct { + ctx context.Context + req *gpb.GetRequest + } + tests := []struct { + name string + fields fields + args args + want interface{} + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + g := &Gnmi{ + SetNode: tt.fields.SetNode, + RespChan: tt.fields.RespChan, + config: tt.fields.config, + } + got, err := g.getWithRequest(tt.args.ctx, tt.args.req) + if (err != nil) != tt.wantErr { + t.Errorf("getWithRequest() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("getWithRequest() got = %v, want %v", got, tt.want) + } + }) + } +} + +func TestGnmi_set(t *testing.T) { + type fields struct { + SetNode func(schema *yang.Entry, root interface{}, path *gpb.Path, val interface{}, opts ...ytypes.SetNodeOpt) error + RespChan chan *gpb.SubscribeResponse + config *gnmi.Config + } + type args struct { + ctx context.Context + setOps []*gnmi.Operation + exts []*gnmi_ext.Extension + } + tests := []struct { + name string + fields fields + args args + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + g := &Gnmi{ + SetNode: tt.fields.SetNode, + RespChan: tt.fields.RespChan, + config: tt.fields.config, + } + if err := g.set(tt.args.ctx, tt.args.setOps, tt.args.exts...); (err != nil) != tt.wantErr { + t.Errorf("set() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func TestGnmi_subscribe(t *testing.T) { + type fields struct { + SetNode func(schema *yang.Entry, root interface{}, path *gpb.Path, val interface{}, opts ...ytypes.SetNodeOpt) error + RespChan chan *gpb.SubscribeResponse + config *gnmi.Config + } + type args struct { + ctx context.Context + } + tests := []struct { + name string + fields fields + args args + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + g := &Gnmi{ + SetNode: tt.fields.SetNode, + RespChan: tt.fields.RespChan, + config: tt.fields.config, + } + if err := g.subscribe(tt.args.ctx); (err != nil) != tt.wantErr { + t.Errorf("subscribe() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func Test_extractModelKey(t *testing.T) { + type args struct { + path *gpb.Path + } + tests := []struct { + name string + args args + want string + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := extractModelKey(tt.args.path); got != tt.want { + t.Errorf("extractModelKey() = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_gnmiFullPath(t *testing.T) { + type args struct { + prefix *gpb.Path + path *gpb.Path + } + tests := []struct { + name string + args args + want *gpb.Path + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := gnmiFullPath(tt.args.prefix, tt.args.path) + if (err != nil) != tt.wantErr { + t.Errorf("gnmiFullPath() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("gnmiFullPath() got = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/test_resources/targets.go b/test_resources/targets.go new file mode 100644 index 0000000000000000000000000000000000000000..7aa3d84999e972ba0706641e048c40aa0c1f31c9 --- /dev/null +++ b/test_resources/targets.go @@ -0,0 +1,100 @@ +package test_resources + +import ( + "code.fbi.h-da.de/cocsn/gosdn/forks/google/gnmi" + "code.fbi.h-da.de/cocsn/gosdn/forks/google/gnmi/modeldata" + oc "code.fbi.h-da.de/cocsn/yang-models/generated/arista" + pb "github.com/openconfig/gnmi/proto/gnmi" + "github.com/openconfig/ygot/ygot" + log "github.com/sirupsen/logrus" + "google.golang.org/grpc" + "google.golang.org/grpc/reflection" + "net" + "reflect" +) + +type server struct { + *gnmi.Server +} + +func callback(newConfig ygot.ValidatedGoStruct) error { + // Apply the config to your device and return nil if success. return error if fails. + // + // Do something ... + return nil +} + +func newServer(model *gnmi.Model, config []byte) (*server, error) { + s, err := gnmi.NewServer(model, config, callback) + if err != nil { + return nil, err + } + return &server{Server: s}, nil +} + +/* +TODO: Implement multiple server configurations +// Get overrides the Get func of gnmi.Target to provide user auth. +func (s *server) Get(ctx context.Context, req *pb.GetRequest) (*pb.GetResponse, error) { + msg, ok := credentials.AuthorizeUser(ctx) + if !ok { + log.Infof("denied a Get request: %v", msg) + return nil, status.Error(codes.PermissionDenied, msg) + } + log.Infof("allowed a Get request: %v", msg) + return s.Server.Get(ctx, req) +} + +// Set overrides the Set func of gnmi.Target to provide user auth. +func (s *server) Set(ctx context.Context, req *pb.SetRequest) (*pb.SetResponse, error) { + msg, ok := credentials.AuthorizeUser(ctx) + if !ok { + log.Infof("denied a Set request: %v", msg) + return nil, status.Error(codes.PermissionDenied, msg) + } + log.Infof("allowed a Set request: %v", msg) + return s.Server.Set(ctx, req) +} +*/ + +func GnmiTarget(stop chan bool, bindAddr string) error { + if bindAddr == "" { + bindAddr = "localhost:13371" + } + + // Google stuff from here + model := gnmi.NewModel(modeldata.ModelData, + reflect.TypeOf((*oc.Device)(nil)), + oc.SchemaTree["Device"], + oc.Unmarshal, + oc.ΛEnum) + + g := grpc.NewServer() + + var configData []byte + s, err := newServer(model, configData) + if err != nil { + log.Errorf("error in creating gnmi target: %v", err) + return err + } + pb.RegisterGNMIServer(g, s) + reflection.Register(g) + + log.Infof("starting to listen on %s", bindAddr) + listen, err := net.Listen("tcp", bindAddr) + if err != nil { + log.Errorf("failed to listen: %v", err) + return err + } + + log.Info("starting to serve") + go func() { + <-stop + g.GracefulStop() + }() + if err := g.Serve(listen); err != nil { + log.Errorf("failed to serve: %v", err) + return err + } + return nil +}