operation.go 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312
  1. // Copyright (c) 2017 Arista Networks, Inc.
  2. // Use of this source code is governed by the Apache License 2.0
  3. // that can be found in the COPYING file.
  4. package gnmi
  5. import (
  6. "bytes"
  7. "context"
  8. "encoding/base64"
  9. "encoding/json"
  10. "errors"
  11. "fmt"
  12. "io"
  13. "io/ioutil"
  14. "path"
  15. "strconv"
  16. "strings"
  17. "time"
  18. pb "github.com/openconfig/gnmi/proto/gnmi"
  19. "google.golang.org/grpc/codes"
  20. )
  21. // Get sents a GetRequest to the given client.
  22. func Get(ctx context.Context, client pb.GNMIClient, paths [][]string) error {
  23. req, err := NewGetRequest(paths)
  24. if err != nil {
  25. return err
  26. }
  27. resp, err := client.Get(ctx, req)
  28. if err != nil {
  29. return err
  30. }
  31. for _, notif := range resp.Notification {
  32. prefix := StrPath(notif.Prefix)
  33. for _, update := range notif.Update {
  34. fmt.Printf("%s:\n", path.Join(prefix, StrPath(update.Path)))
  35. fmt.Println(StrUpdateVal(update))
  36. }
  37. }
  38. return nil
  39. }
  40. // Capabilities retuns the capabilities of the client.
  41. func Capabilities(ctx context.Context, client pb.GNMIClient) error {
  42. resp, err := client.Capabilities(ctx, &pb.CapabilityRequest{})
  43. if err != nil {
  44. return err
  45. }
  46. fmt.Printf("Version: %s\n", resp.GNMIVersion)
  47. for _, mod := range resp.SupportedModels {
  48. fmt.Printf("SupportedModel: %s\n", mod)
  49. }
  50. for _, enc := range resp.SupportedEncodings {
  51. fmt.Printf("SupportedEncoding: %s\n", enc)
  52. }
  53. return nil
  54. }
  55. // val may be a path to a file or it may be json. First see if it is a
  56. // file, if so return its contents, otherwise return val
  57. func extractJSON(val string) []byte {
  58. if jsonBytes, err := ioutil.ReadFile(val); err == nil {
  59. return jsonBytes
  60. }
  61. // Best effort check if the value might a string literal, in which
  62. // case wrap it in quotes. This is to allow a user to do:
  63. // gnmi update ../hostname host1234
  64. // gnmi update ../description 'This is a description'
  65. // instead of forcing them to quote the string:
  66. // gnmi update ../hostname '"host1234"'
  67. // gnmi update ../description '"This is a description"'
  68. maybeUnquotedStringLiteral := func(s string) bool {
  69. if s == "true" || s == "false" || s == "null" || // JSON reserved words
  70. strings.ContainsAny(s, `"'{}[]`) { // Already quoted or is a JSON object or array
  71. return false
  72. } else if _, err := strconv.ParseInt(s, 0, 32); err == nil {
  73. // Integer. Using byte size of 32 because larger integer
  74. // types are supposed to be sent as strings in JSON.
  75. return false
  76. } else if _, err := strconv.ParseFloat(s, 64); err == nil {
  77. // Float
  78. return false
  79. }
  80. return true
  81. }
  82. if maybeUnquotedStringLiteral(val) {
  83. out := make([]byte, len(val)+2)
  84. out[0] = '"'
  85. copy(out[1:], val)
  86. out[len(out)-1] = '"'
  87. return out
  88. }
  89. return []byte(val)
  90. }
  91. // StrUpdateVal will return a string representing the value within the supplied update
  92. func StrUpdateVal(u *pb.Update) string {
  93. if u.Value != nil {
  94. // Backwards compatibility with pre-v0.4 gnmi
  95. switch u.Value.Type {
  96. case pb.Encoding_JSON, pb.Encoding_JSON_IETF:
  97. return strJSON(u.Value.Value)
  98. case pb.Encoding_BYTES, pb.Encoding_PROTO:
  99. return base64.StdEncoding.EncodeToString(u.Value.Value)
  100. case pb.Encoding_ASCII:
  101. return string(u.Value.Value)
  102. default:
  103. return string(u.Value.Value)
  104. }
  105. }
  106. return StrVal(u.Val)
  107. }
  108. // StrVal will return a string representing the supplied value
  109. func StrVal(val *pb.TypedValue) string {
  110. switch v := val.GetValue().(type) {
  111. case *pb.TypedValue_StringVal:
  112. return v.StringVal
  113. case *pb.TypedValue_JsonIetfVal:
  114. return strJSON(v.JsonIetfVal)
  115. case *pb.TypedValue_JsonVal:
  116. return strJSON(v.JsonVal)
  117. case *pb.TypedValue_IntVal:
  118. return strconv.FormatInt(v.IntVal, 10)
  119. case *pb.TypedValue_UintVal:
  120. return strconv.FormatUint(v.UintVal, 10)
  121. case *pb.TypedValue_BoolVal:
  122. return strconv.FormatBool(v.BoolVal)
  123. case *pb.TypedValue_BytesVal:
  124. return base64.StdEncoding.EncodeToString(v.BytesVal)
  125. case *pb.TypedValue_DecimalVal:
  126. return strDecimal64(v.DecimalVal)
  127. case *pb.TypedValue_FloatVal:
  128. return strconv.FormatFloat(float64(v.FloatVal), 'g', -1, 32)
  129. case *pb.TypedValue_LeaflistVal:
  130. return strLeaflist(v.LeaflistVal)
  131. case *pb.TypedValue_AsciiVal:
  132. return v.AsciiVal
  133. case *pb.TypedValue_AnyVal:
  134. return v.AnyVal.String()
  135. default:
  136. panic(v)
  137. }
  138. }
  139. func strJSON(inJSON []byte) string {
  140. var out bytes.Buffer
  141. err := json.Indent(&out, inJSON, "", " ")
  142. if err != nil {
  143. return fmt.Sprintf("(error unmarshalling json: %s)\n", err) + string(inJSON)
  144. }
  145. return out.String()
  146. }
  147. func strDecimal64(d *pb.Decimal64) string {
  148. var i, frac int64
  149. if d.Precision > 0 {
  150. div := int64(10)
  151. it := d.Precision - 1
  152. for it > 0 {
  153. div *= 10
  154. it--
  155. }
  156. i = d.Digits / div
  157. frac = d.Digits % div
  158. } else {
  159. i = d.Digits
  160. }
  161. if frac < 0 {
  162. frac = -frac
  163. }
  164. return fmt.Sprintf("%d.%d", i, frac)
  165. }
  166. // strLeafList builds a human-readable form of a leaf-list. e.g. [1, 2, 3] or [a, b, c]
  167. func strLeaflist(v *pb.ScalarArray) string {
  168. var buf bytes.Buffer
  169. buf.WriteByte('[')
  170. for i, elm := range v.Element {
  171. buf.WriteString(StrVal(elm))
  172. if i < len(v.Element)-1 {
  173. buf.WriteString(", ")
  174. }
  175. }
  176. buf.WriteByte(']')
  177. return buf.String()
  178. }
  179. func update(p *pb.Path, val string) *pb.Update {
  180. var v *pb.TypedValue
  181. switch p.Origin {
  182. case "":
  183. v = &pb.TypedValue{
  184. Value: &pb.TypedValue_JsonIetfVal{JsonIetfVal: extractJSON(val)}}
  185. case "cli":
  186. v = &pb.TypedValue{
  187. Value: &pb.TypedValue_AsciiVal{AsciiVal: val}}
  188. default:
  189. panic(fmt.Errorf("unexpected origin: %q", p.Origin))
  190. }
  191. return &pb.Update{Path: p, Val: v}
  192. }
  193. // Operation describes an gNMI operation.
  194. type Operation struct {
  195. Type string
  196. Path []string
  197. Val string
  198. }
  199. func newSetRequest(setOps []*Operation) (*pb.SetRequest, error) {
  200. req := &pb.SetRequest{}
  201. for _, op := range setOps {
  202. p, err := ParseGNMIElements(op.Path)
  203. if err != nil {
  204. return nil, err
  205. }
  206. switch op.Type {
  207. case "delete":
  208. req.Delete = append(req.Delete, p)
  209. case "update":
  210. req.Update = append(req.Update, update(p, op.Val))
  211. case "replace":
  212. req.Replace = append(req.Replace, update(p, op.Val))
  213. }
  214. }
  215. return req, nil
  216. }
  217. // Set sends a SetRequest to the given client.
  218. func Set(ctx context.Context, client pb.GNMIClient, setOps []*Operation) error {
  219. req, err := newSetRequest(setOps)
  220. if err != nil {
  221. return err
  222. }
  223. resp, err := client.Set(ctx, req)
  224. if err != nil {
  225. return err
  226. }
  227. if resp.Message != nil && codes.Code(resp.Message.Code) != codes.OK {
  228. return errors.New(resp.Message.Message)
  229. }
  230. // TODO: Iterate over SetResponse.Response for more detailed error message?
  231. return nil
  232. }
  233. // Subscribe sends a SubscribeRequest to the given client.
  234. func Subscribe(ctx context.Context, client pb.GNMIClient, paths [][]string,
  235. respChan chan<- *pb.SubscribeResponse, errChan chan<- error) {
  236. ctx, cancel := context.WithCancel(ctx)
  237. defer cancel()
  238. stream, err := client.Subscribe(ctx)
  239. if err != nil {
  240. errChan <- err
  241. return
  242. }
  243. req, err := NewSubscribeRequest(paths)
  244. if err != nil {
  245. errChan <- err
  246. return
  247. }
  248. if err := stream.Send(req); err != nil {
  249. errChan <- err
  250. return
  251. }
  252. for {
  253. resp, err := stream.Recv()
  254. if err != nil {
  255. if err == io.EOF {
  256. return
  257. }
  258. errChan <- err
  259. return
  260. }
  261. respChan <- resp
  262. }
  263. }
  264. // LogSubscribeResponse logs update responses to stderr.
  265. func LogSubscribeResponse(response *pb.SubscribeResponse) error {
  266. switch resp := response.Response.(type) {
  267. case *pb.SubscribeResponse_Error:
  268. return errors.New(resp.Error.Message)
  269. case *pb.SubscribeResponse_SyncResponse:
  270. if !resp.SyncResponse {
  271. return errors.New("initial sync failed")
  272. }
  273. case *pb.SubscribeResponse_Update:
  274. t := time.Unix(0, resp.Update.Timestamp).UTC()
  275. prefix := StrPath(resp.Update.Prefix)
  276. for _, update := range resp.Update.Update {
  277. fmt.Printf("[%s] %s = %s\n", t.Format(time.RFC3339Nano),
  278. path.Join(prefix, StrPath(update.Path)),
  279. StrUpdateVal(update))
  280. }
  281. for _, del := range resp.Update.Delete {
  282. fmt.Printf("[%s] Deleted %s\n", t.Format(time.RFC3339Nano),
  283. path.Join(prefix, StrPath(del)))
  284. }
  285. }
  286. return nil
  287. }