openconfig / gnmi

gRPC Network Management Interface
Apache License 2.0
467 stars 197 forks source link

Cannot send requests to Cisco IOS XR device #149

Closed JonasKs closed 1 year ago

JonasKs commented 1 year ago

Hi 😊

I'm unable to perform GET request through gnmi, even though it works with gNMIc.

We're sending this request with gNMIc:

gnmic -a 10.242.0.33:4126 --username x --password x --insecure get --path "Cisco-IOS-XR-um-vrf-cfg:vrfs/vrf" -e json_ietf

Attempting the same with gnmi(and ygot), generating the path with this:

func NewGetRequest(path string) (*gnmi.GetRequest, error) {
    structuredPath, err := ygot.StringToStructuredPath("vrfs/vrf")
    if err != nil {
        return nil, fmt.Errorf("failed to create path: %w", err)
    }
    structuredPath.Origin = "Cisco-IOS-XR-um-vrf-cfg"

    gnmiRequest := &gnmi.GetRequest{
        Path:     []*gnmi.Path{structuredPath},
        Encoding: gnmi.Encoding_JSON_IETF,
    }
    fmt.Println(prototext.Format(gnmiRequest))
    return gnmiRequest, nil
}

and then calling the Cisco IOS XR device:

Target.Client.Get(c.ctx, req)

we keep getting the error Error: failed to fetch from target: rpc error: code = Unauthenticated desc = gNMI: get: metadata credentials not present.


The target is created like this:

target, err := api.NewTarget(
        api.Name("Router"),
        api.Address("x:x"),
        api.Username("x"),
        api.Password("x"),
        api.Insecure(true),
        api.SkipVerify(true),
    )

and the request body is formatted like this, when printed with fmt.Println(prototext.Format(gnmiRequest)) looks correct:

path: {
  origin: "Cisco-IOS-XR-um-vrf-cfg"
  elem: {
    name: "vrfs"
  }
  elem: {
    name: "vrf"
  }
}
encoding: JSON_IETF

We're not sure what happens. Printing Target.ConnState() also says READY, so it seems like it is connected, but the GET request fails for some reason. Anyone experienced this?

hellt commented 1 year ago

Hi, I wonder, have you tried creating the request with gnmic API, since you're using gnmic API to create the target.

https://gnmic.openconfig.net/user_guide/golang_package/intro/#get-request https://gnmic.openconfig.net/user_guide/golang_package/examples/get/

JonasKs commented 1 year ago

This works:

package main

import (
    "context"
    "fmt"
    "log"

    "github.com/openconfig/gnmic/api"
    "google.golang.org/protobuf/encoding/prototext"
)

func main() {
    // create a target
    tg, err := api.NewTarget(
        api.Name("Router"),
        api.Address("10.242.0.33:4126"),
        api.Username("x"),
        api.Password("x"),
        api.Insecure(true),
        api.SkipVerify(true),
    )
    if err != nil {
        log.Fatal(err)
    }

    ctx, cancel := context.WithCancel(context.Background())
    defer cancel()

    // create a gNMI client
    err = tg.CreateGNMIClient(ctx)
    if err != nil {
        log.Fatal(err)
    }
    defer tg.Close()

    // create a GetRequest
    getReq, err := api.NewGetRequest(
        api.Path("Cisco-IOS-XR-um-vrf-cfg:vrfs/vrf"),
        api.Encoding("json_ietf"))
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println(prototext.Format(getReq))

    // send the created gNMI GetRequest to the created target
    getResp, err := tg.Get(ctx, getReq)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println(prototext.Format(getResp))
}

Result:

image

This also work:

package main

import (
    "context"
    "fmt"
    "log"

    "github.com/openconfig/gnmi/proto/gnmi"
    "github.com/openconfig/gnmic/api"
    "google.golang.org/protobuf/encoding/prototext"
)

func main() {
    // create a target
    tg, err := api.NewTarget(
        api.Name("Router"),
        api.Address("10.242.0.33:4126"),
        api.Username("x"),
        api.Password("x"),
        api.Insecure(true),
        api.SkipVerify(true),
    )
    if err != nil {
        log.Fatal(err)
    }

    ctx, cancel := context.WithCancel(context.Background())
    defer cancel()

    // create a gNMI client
    err = tg.CreateGNMIClient(ctx)
    if err != nil {
        log.Fatal(err)
    }
    defer tg.Close()

    // create a GetRequest
    getReq := &gnmi.GetRequest{
        Path: []*gnmi.Path{
            {
                Origin: "Cisco-IOS-XR-um-vrf-cfg",
                Elem: []*gnmi.PathElem{
                    {Name: "vrfs"},
                    {Name: "vrf"},
                },
            },
        },
        Type:     gnmi.GetRequest_STATE,
        Encoding: gnmi.Encoding_JSON_IETF,
    }
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println(prototext.Format(getReq))

    // send the created gNMI GetRequest to the created target
    getResp, err := tg.Get(ctx, getReq)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println(prototext.Format(getResp))
}

Result:

image

So I should use this instead? πŸ€”

Thanks for the quick reply!

karimra commented 1 year ago

You should use target.Get(ctx, req) not target.Client.Get(ctx, req). The former adds the credentials to the metadata (ctx), the latter does not.

You should also create the gNMI client (target.CreateGNMIClient()) before calling the RPC functions.

PS: you don't need both api.Skipverify(true) and api.Insecure(true)

JonasKs commented 1 year ago

You were right, that was the issue! Thank you so much. 😊

Also thank you for your talk on gNMIc, it is what made me look further than Python and NETCONF and eventually landed on using go and all these awesome libraries for this project. Stumbled upon it when looking for Python gNMI resources on youtube. πŸ™

You should also create the gNMI client (target.CreateGNMIClient()) before calling the RPC functions.

Yes, we do this, sorry I forgot to attach that.

PS: you don't need both api.Skipverify(true) and api.Insecure(true)

Thanks! Initially only had api.Skipverify(true), but tried everything when troubleshooting 😁