gNOI Intro

This blog will serve as a purpose for gNOI which is the gRPC interface that allows for operations. Other than Rob Shakir’s post on general Openconfig public projects I have not seen much on gNOI in the public and its given purpose. We also like gnmic have a binary dedicated to gNOI called gnoic.

So what is gNOI? Why should you care to use it or where would it help you within your current operations, organization etc.

gnoioverall

gNOI as a whole is a called a operations interface. So it allows for multiple operations(Think about daily operations you would connect to a switch and execute) over a gRPC interface that runs on network switches. So if you have the following issues that I can think of on the top of my head which some I will provide some demo examples for.

  • Can this switch ping a destination from a sourced interface?
  • Can this switch reach or traceroute properly what is its path?
  • Can I upgrade this device using the same method across all my network devices?
  • Can I retrieve a file on my device or add an extension to my device?

These are general issues in which on multivendor platforms gNOIC can solve. Up until previously to gNOI all of these had their own ways of connecting to the device over either ssh or vendor api’s and issuing some sort of CLI commands or bash prompt engineering. I think the biggest issue here for most is multi vendor upgrades in which we will provide an example later on in this post.

gNOI has well defined protobufs where a end user can write code to connect to the switch and call specific methods within the protobuf that accomplish all of these bullet points across multivendor.

Some of the github examples will be leveraged here For the prometheus exporter we will use this repo

System services example

On the previous bullet points the first two. Ping and Traceroute seem to be things in which should be very obvious starters for most. Lets take a look at the system service.

Taking a look at ping for example.

service System {
  // Ping executes the ping command on the target and streams back
  // the results.  Some targets may not stream any results until all
  // results are in.  The stream should provide single ping packet responses
  // and must provide summary statistics.
  rpc Ping(PingRequest) returns (stream PingResponse) {}

This is the System service which runs on the device itself. So when the service is called with the Ping method and PingRequest is passed in it will return PingResponse. Lets take a look at both because they are generally the easiest.

message PingRequest {
  string destination = 1;   // Destination address to ping. required.
  string source = 2;        // Source address to ping from.
  int32 count = 3;          // Number of packets.
  int64 interval = 4;       // Nanoseconds between requests.
  int64 wait = 5;           // Nanoseconds to wait for a response.
  int32 size = 6;           // Size of request packet. (excluding ICMP header)
  bool do_not_fragment = 7; // Set the do not fragment bit. (IPv4 destinations)
  bool do_not_resolve = 8;  // Do not try resolve the address returned.
  types.L3Protocol l3protocol = 9; // Layer3 protocol requested for the ping.
  string network_instance = 10; // Network instance to ping the destination in
}
message PingResponse {
  string source = 1;        // Source of received bytes.
  int64 time = 2;

  int32 sent = 3;           // Total packets sent.
  int32 received = 4;       // Total packets received.
  int64 min_time = 5;       // Minimum round trip time in nanoseconds.
  int64 avg_time = 6;       // Average round trip time in nanoseconds.
  int64 max_time = 7;       // Maximum round trip time in nanoseconds.
  int64 std_dev = 8;        // Standard deviation in round trip time.

  int32 bytes = 11;         // Bytes received.
  int32 sequence = 12;      // Sequence number of received packet.
  int32 ttl = 13;           // Remaining time to live value.
}

Ping go code

Taking a look at some of the code within a repo I put together some time ago we will programmatically through the gNOI interface and system service tell the device to ping a certain destination. Sounds pretty trivial and it is but I need to demo the overall power here of gNOI.

Looking at what is relevent within the code I will provide detail explanations.

Sys := system.NewSystemClient(conn)
		// pass in context blank information with the timeout.
		ctx, cancel := context.WithTimeout(context.Background(), time.Second)
		// cancel when the function is over.
		defer cancel()
		// Since Metadata needs a map to pass into the header of gRPC request create a map for it.
		metamap := make(map[string]string)
		// Set the username and password
		metamap["username"] = *username
		metamap["password"] = *password
		// Set the metadata needed in the metadata package
		md := metadata.New(metamap)
        ctx = metadata.NewOutgoingContext(ctx, md)

We initialize Sys as the type systemclient the system package is the actual gnoi system package. We then have to add a map[string]string that is username and password and add it to the metadata of grpc for username/password and for every outgoing grpc method it will attach this metadata where the switch will use it for AAA purposes.

response, err := Sys.Ping(ctx, &system.PingRequest{Destination: *destination})
		if err != nil {
			log.Fatalf("Error trying to ping: %v", err)
			fmt.Println("Could not get a receive message")
		}

		PingResponse, _ := response.Recv()
		fmt.Println(PingResponse)
		fmt.Println(PingResponse.Source, PingResponse.Time, PingResponse.Bytes, PingResponse.Ttl, PingResponse.AvgTime, PingResponse.MaxTime, PingResponse.MaxTime)
	}

Here we ping destinations. The Sys.Ping command is what executes the ping it takes in the context and PingRequest which is what we looked at previous in the proto.

PingResponse and I skipped over the error there with the _ will then stream the response back to the client. We can simply print out some of the values within the ping response.

Lets get to the pinging!

go run main.go -username admin -password admin -target 172.20.20.2:6030 -destination 8.8.8.8

Output

source:"8.8.8.8" time:8940000 bytes:64 sequence:1 ttl:59 8.8.8.8 8940000 64 59 0 0 0

I truncated it a bit but all of these pings are sources from the device which is pretty interesting. You can also source where the pings come from.

Using gNOIC to upgrade a device.

So using the gNOIC binary very similar to the gnmic binary you can call these services and methods without doing it in code in the previous examples. Say if we wanted to do a code upgrade we can do this through gnoi which is multi vendor and send the file through the grpc service and then later upgrade. This is all possible within gnoi.

gnoic -a 172.20.20.2:6030 --insecure  --gzip -u admin -p admin \
   os install \
   --version 4.29.1F \
   --pkg EOS.swi

This will then give you the output the file is sending and when it is complete.

INFO[0000] starting install RPC
INFO[0000] target "172.20.20.2:6030": starting Install stream
INFO[0003] target "172.20.20.2:6030": TransferProgress bytes_received:5242880
INFO[0003] target "172.20.20.2:6030": TransferProgress bytes_received:10485760
...
INFO[0411] target "172.20.20.2:6030": TransferProgress bytes_received:1030750208
INFO[0413] target "172.20.20.2:6030": sending TransferEnd
INFO[0413] target "172.20.20.2:6030": TransferProgress bytes_received:1035993088
INFO[0413] target "172.20.20.2:6030": TransferContent done...

To activate that version we then need to call the OS activation method.

gnoic -a 172.20.20.2:6030 --insecure  --gzip -u admin -p admin \
   os activate \
   --version 4.29.1F \
   --no-reboot

Output

INFO[0034] target "172.20.20.2:6030" activate response "activate_ok:{}"

This is super useful for most. If your device supports all of these services you can then upgrade through the same method.

Ping Prometheus exporter.

I wrote a small prometheus exporter for a given use case. The idea was to be able to ping a given destination and graph that it is working from the switch. gNOI was a pretty much perfect match for this sort of ideaology. The exporter can be found here.

Clone the repo.

git clone https://github.com/arista-netdevops-community/gnoi-prometheus-exporter

Edit the yaml file to your liking.

destinations:
 - name: Google
   destination: 8.8.8.8
 - name: Cloudflaire
   destination: 1.1.1.1

I put a few binaries there for different archs. I am going to use the linux one which is a x86 amd based arch.

➜  gnoi-prometheus-exporter git:(master) ls -l bin
total 42296
-rwxrwxr-x 1 burnyd burnyd 13340222 Feb 25  2022 gnoi-prometheus-exporter-386
-rwxrwxr-x 1 burnyd burnyd 15115562 Feb 25  2022 gnoi-prometheus-exporter-linux
-rwxrwxr-x 1 burnyd burnyd 14849680 Feb 25  2022 gnoi-prometheus-exporter-mac

Running the binary.

bin git:(master) ./gnoi-prometheus-exporter-linux -username admin -password admin -target 172.20.20.2:6030 -config ../config.yaml

Output

Google 	 source:"8.8.8.8"  time:7530000  bytes:64  sequence:1  ttl:59
Cloudflaire 	 source:"1.1.1.1"  time:20000  bytes:64  sequence:1  ttl:64

We can then check the exporter on the local system.

3