見出し画像

TWSNMP FC/FKのgNMI対応開発7日目:gNMIツールのPath指定で悩んでいます

浦和レッズ 3連敗!
もやもやしますが、開発に集中して切り替えします。
今朝は、助手の猫さんが4時半にベッドからはみ出した私の足の裏をペロッと舐めて起こしてくれました。おかげで一気に目が覚めました。

昨日作ったgNMIツールのGet機能ですが、とんでもなく間違っていました。最初にテストしたPathは、/system/nameだったので、なんとなくうまく言っているように見えたのですが、Pathに
/interface/statistics
を指定すると、

のようなことになりました。LANポートの情報全部が1つのJSONに入っていて、それだけをテーブルに表示している状態でした。
gNMIcのAPIの使い方を誤解していたようです。取得したJSONデータから自分で情報を取り出す必要がありました。その処理がすっぽり抜けていたのでした。
JSONを解析して情報を取り出す処理を作ることにしました。作ってみると、JSONで配列になっているデータの位置を特定するPathの指定が、さっぱりわかりません。同じPath(情報の名前)で複数あるデータの位置を指定する方法です。"XPATH 配列"で検索して /interfces[0]のような指定でできるのかと試しましたがだめでした。
昨日は諦めてモヤモヤしながら寝ました。今朝、頭がスッキリしたので、いろいろ探して

の資料を見つけました。
どうやら

/interfaces/interface[name=Ethernet/1/2/3]/state

のようにKeyを指定して特定するようです。[name=Ethernet/1/2/3]の部分です。
前に作ったテストプログラムを改造して

package main

import (
	"context"
	"encoding/json"
	"fmt"
	"log"
	"reflect"
	"strings"

	"github.com/openconfig/gnmic/pkg/api"
)

type PathValueEnt struct {
	Path  string
	Value string
}

func main() {
	// create a target
	tg, err := api.NewTarget(
		api.Name("srl1"),
		api.Address("192.168.1.50:57400"),
		api.Username("admin"),
		api.Password("NokiaSrl1!"),
		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("/interface"),
		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))
	for _, not := range getResp.GetNotification() {
		for _, u := range not.GetUpdate() {
			pa := []string{}
			for _, p := range u.Path.Elem {
				pa = append(pa, p.GetName())
			}
			j := u.Val.GetJsonIetfVal()
			var d interface{}
			if err := json.Unmarshal(j, &d); err != nil {
				log.Fatalln(err)
			}
			path := ""
			if len(pa) > 0 {
				path = "/" + strings.Join(pa, "/")
			}
			for i, r := range getPathValue(d, path, false) {
				log.Printf("%d %s = %s", i, r.Path, r.Value)
			}
		}
	}
}

func getPathValue(d interface{}, path string, inArray bool) []PathValueEnt {
	r := []PathValueEnt{}
	switch v := d.(type) {
	case string:
		r = append(r, PathValueEnt{
			Path:  path,
			Value: v,
		})
		return r
	case float64:
		r = append(r, PathValueEnt{
			Path:  path,
			Value: fmt.Sprintf("%v", v),
		})
	case bool:
		r = append(r, PathValueEnt{
			Path:  path,
			Value: fmt.Sprintf("%v", v),
		})
	case map[string]interface{}:
		n := ""
		if in, ok := v["name"]; ok {
			if sn, ok := in.(string); ok {
				n = sn
			}
		}
		for k, vv := range v {
			if inArray && n != "" {
				r = append(r, getPathValue(vv, fmt.Sprintf("%s[name=%s]/%s", path, n, k), false)...)
			} else {
				r = append(r, getPathValue(vv, path+"/"+k, false)...)
			}
		}
	case []interface{}:
		for _, vv := range v {
			r = append(r, getPathValue(vv, path, true)...)
		}
	default:
		log.Fatalf("%s=%+v type=%v", path, v, reflect.TypeOf(d))
	}
	return r
}

のように再帰的に取得してPathに[name=xxx]を含めるようにしてみました。かなりうまくいくようです。
実行すると

2024/10/06 07:06:51 1535 /srl_nokia-interfaces:interface[name=mgmt0]/admin-state = enable
2024/10/06 07:06:51 1536 /srl_nokia-interfaces:interface[name=mgmt0]/mtu = 1514
2024/10/06 07:06:51 1537 /srl_nokia-interfaces:interface[name=mgmt0]/ifindex = 1.07795251e+09
2024/10/06 07:06:51 1538 /srl_nokia-interfaces:interface[name=mgmt0]/last-change = 2024-09-25T01:40:55.293Z

のような感じです。配列のキーがnameの場合はOKですが、それ以外には対応していません。indexで位置を指定するもの、キーがないものもありました。取得した時にエラーになる場合は、エラーメッセージでキーの指定の間違いがわかるよにしておきました。

このテストプログラムをTWSNMP FCに反映させて、

のように取得できるようになりました。
ほとんどの場合、Pathの列の値をコピペすれば、値を取得できるはずです。

なんとなく画期的な方法じゃないかと思っています。
ポーリングの時は、JQで取得する方法がよいのではと思っています。

明日に続く

いいなと思ったら応援しよう!

twsnmp
開発のための諸経費(機材、Appleの開発者、サーバー運用)に利用します。 ソフトウェアのマニュアルをnoteの記事で提供しています。 サポートによりnoteの運営にも貢献できるのでよろしくお願います。