github.com/aristanetworks/goarista@v0.0.0-20240514173732-cca2755bbd44/gnmireverse/server/server.go (about) 1 // Copyright (c) 2020 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 5 package server 6 7 import ( 8 "context" 9 "crypto/tls" 10 "crypto/x509" 11 "flag" 12 "fmt" 13 "io/ioutil" 14 "log" 15 "math" 16 "net" 17 "os" 18 "path" 19 "strings" 20 "time" 21 22 "github.com/aristanetworks/glog" 23 gnmilib "github.com/aristanetworks/goarista/gnmi" 24 "github.com/aristanetworks/goarista/gnmireverse" 25 "github.com/openconfig/gnmi/proto/gnmi" 26 "google.golang.org/grpc" 27 "google.golang.org/grpc/credentials" 28 _ "google.golang.org/grpc/encoding/gzip" // Enable gzip encoding for the server. 29 "google.golang.org/grpc/peer" 30 "google.golang.org/protobuf/proto" 31 ) 32 33 // The debug flag is composed of these flag bits. 34 // These flag bits are bitwise OR'ed to select what debug information is printed. 35 const ( 36 debugSilent = 1 << iota // Do not print anything. 37 debugGetResponseSummary // Print GetResponse summary. 38 debugResponseProto // Print response protobuf text format. 39 debugUpdates // Print update receive and notification times. 40 debugUpdatesTiming // Print debugUpdates and update timing calculations. 41 debugUpdatesPaths // Print debugUpdates and update paths. 42 debugUpdatesValues // Print debugUpdates and update values. 43 debugUpdatesAll = debugUpdates | debugUpdatesTiming | debugUpdatesPaths | 44 debugUpdatesValues // Print all update information. 45 ) 46 47 // Use log.Logger for thread-safe printing. 48 var logger = log.New(os.Stdout, "", 0) 49 50 func newTLSConfig(clientCertAuth bool, certFile, keyFile, clientCAFile string) (*tls.Config, 51 error) { 52 tlsConfig := tls.Config{} 53 if clientCertAuth { 54 tlsConfig.ClientAuth = tls.RequireAndVerifyClientCert 55 if clientCAFile == "" { 56 return nil, fmt.Errorf("client_cert_auth enable, client_cafile must also be set") 57 } 58 b, err := ioutil.ReadFile(clientCAFile) 59 if err != nil { 60 return nil, err 61 } 62 cp := x509.NewCertPool() 63 if !cp.AppendCertsFromPEM(b) { 64 return nil, fmt.Errorf("credentials: failed to append certificates") 65 } 66 tlsConfig.ClientCAs = cp 67 } 68 if certFile != "" { 69 if keyFile == "" { 70 return nil, fmt.Errorf("please provide both -certfile and -keyfile") 71 } 72 cert, err := tls.LoadX509KeyPair(certFile, keyFile) 73 if err != nil { 74 return nil, err 75 } 76 tlsConfig.Certificates = []tls.Certificate{cert} 77 } 78 return &tlsConfig, nil 79 } 80 81 // Main initializes the gNMIReverse server. 82 func Main() { 83 addr := flag.String("addr", "127.0.0.1:6035", "address to listen on") 84 useTLS := flag.Bool("tls", true, "set to false to disable TLS in collector") 85 clientCertAuth := flag.Bool("client_cert_auth", false, 86 "require and verify a client certificate. -client_cafile must also be set.") 87 certFile := flag.String("certfile", "", "path to TLS certificate file") 88 keyFile := flag.String("keyfile", "", "path to TLS key file") 89 clientCAFile := flag.String("client_cafile", "", 90 "path to TLS CA file to verify client certificate") 91 92 debugFlagUsage := `Debug flags. Use bitwise OR to select what to print. 93 -1 Enable all debug flags. 94 1 Silent mode: do not print anything. Any other enabled flag overrides silent mode. 95 2 Print GetResponse summary: receive time, last receive time, response size and 96 number of notifications. 97 4 Print GetResponse/SubscribeResponse protobuf text format. 98 8 Print update receive time and notification time. 99 16 Print update receive time, notification time and update timing calculations: difference 100 between the last receive time of the same update path, difference between the last 101 notification time of the same update path and latency (receive time - notification time). 102 32 Print update receive time, notification time and update path. 103 64 Print update receive time, notification time and update value. 104 Example: Use 50 (= 2 | 16 | 32) to print the GetResponse summary and the receive time, 105 notification time, timing calculations and path of updates.` 106 debugFlag := flag.Int("debug", 0, debugFlagUsage) 107 108 flag.Parse() 109 110 var config *tls.Config 111 if *useTLS { 112 var err error 113 config, err = newTLSConfig(*clientCertAuth, *certFile, *keyFile, *clientCAFile) 114 if err != nil { 115 glog.Fatal(err) 116 } 117 } 118 119 var serverOptions []grpc.ServerOption 120 if config != nil { 121 serverOptions = append(serverOptions, grpc.Creds(credentials.NewTLS(config))) 122 } 123 124 serverOptions = append(serverOptions, 125 grpc.MaxRecvMsgSize(math.MaxInt32), 126 ) 127 128 grpcServer := grpc.NewServer(serverOptions...) 129 s := &server{ 130 debugFlag: *debugFlag, 131 } 132 gnmireverse.RegisterGNMIReverseServer(grpcServer, s) 133 134 listener, err := net.Listen("tcp", *addr) 135 if err != nil { 136 glog.Fatal(err) 137 } 138 if err := grpcServer.Serve(listener); err != nil { 139 glog.Fatal(err) 140 } 141 } 142 143 type server struct { 144 debugFlag int 145 gnmireverse.UnimplementedGNMIReverseServer 146 } 147 148 func (s *server) Publish(stream gnmireverse.GNMIReverse_PublishServer) error { 149 debugger := newDebugger(stream.Context(), "subscribe", s.debugFlag) 150 for { 151 resp, err := stream.Recv() 152 if err != nil { 153 return err 154 } 155 if s.debugFlag != 0 { 156 debugger.logSubscribeResponse(resp) 157 continue 158 } 159 160 if err := gnmilib.LogSubscribeResponse(resp); err != nil { 161 glog.Error(err) 162 } 163 } 164 } 165 166 func (s *server) PublishGet(stream gnmireverse.GNMIReverse_PublishGetServer) error { 167 debugger := newDebugger(stream.Context(), "get", s.debugFlag) 168 for { 169 resp, err := stream.Recv() 170 if err != nil { 171 return err 172 } 173 if s.debugFlag != 0 { 174 debugger.logGetResponse(resp) 175 continue 176 } 177 178 for _, notif := range resp.GetNotification() { 179 notifTime := time.Unix(0, notif.GetTimestamp()).UTC().Format(time.RFC3339Nano) 180 var notifTarget string 181 if target := notif.GetPrefix().GetTarget(); target != "" { 182 notifTarget = " (" + target + ")" 183 } 184 prefix := gnmilib.StrPath(notif.GetPrefix()) 185 for _, update := range notif.GetUpdate() { 186 pathValSeperator := " = " 187 // If the value is a subtree, print the subtree on a new line. 188 if _, ok := update.GetVal().GetValue().(*gnmi.TypedValue_JsonIetfVal); ok { 189 pathValSeperator = "\n" 190 } 191 pth := path.Join(prefix, gnmilib.StrPath(update.GetPath())) 192 val := gnmilib.StrUpdateVal(update) 193 logger.Printf("[%s]%s %s%s%s", 194 notifTime, 195 notifTarget, 196 pth, 197 pathValSeperator, 198 val, 199 ) 200 } 201 } 202 } 203 } 204 205 // debugger stores debug information related to responses received from a client. 206 type debugger struct { 207 clientAddr string // Address of the client sending the response. 208 responseName string // Name of the response received. 209 responsesCounter uint64 // Updates of the same response have the same counter. 210 lastResponseTime time.Time // Time of the last response received. 211 lastUpdateTimes map[string]time.Time // Map of update paths to the last notification time. 212 logBuffer strings.Builder // Resultant log text to be printed. 213 debugFlag int // Debug flag to control what to print. 214 } 215 216 func newDebugger(ctx context.Context, responseName string, debugFlag int) *debugger { 217 var clientAddr string 218 if pr, ok := peer.FromContext(ctx); ok && pr.Addr != nil { 219 clientAddr = pr.Addr.String() 220 } 221 return &debugger{ 222 clientAddr: clientAddr, 223 responseName: responseName, 224 lastUpdateTimes: make(map[string]time.Time), 225 debugFlag: debugFlag, 226 } 227 } 228 229 // logGetResponse prints GetResponse debug information based on 230 // the bits of the debug flag. 231 func (d *debugger) logGetResponse(res *gnmi.GetResponse) { 232 if d.debugFlag^debugSilent == 0 { // Any other enabled flag overrides silence. 233 return 234 } 235 receiveTime := time.Now().UTC() 236 if d.debugFlag&debugGetResponseSummary != 0 { 237 d.logGetResponseSummary(res, receiveTime) 238 } 239 if d.debugFlag&debugResponseProto != 0 { 240 d.logResponseProto(res) 241 } 242 if d.debugFlag&debugUpdatesAll != 0 { 243 d.logGetResponseNotifs(res, receiveTime) 244 } 245 logger.Printf(d.logBuffer.String()) 246 d.logBuffer.Reset() 247 d.lastResponseTime = receiveTime 248 d.responsesCounter++ 249 } 250 251 func (d *debugger) logGetResponseSummary(res *gnmi.GetResponse, receiveTime time.Time) { 252 debugFields := append( 253 d.logPrefixFields(receiveTime), 254 d.logLastReceiveAgoField(receiveTime), 255 fmt.Sprintf("size_bytes=%d", proto.Size(res)), 256 fmt.Sprintf("num_notifs=%d", len(res.GetNotification())), 257 ) 258 d.logBuffer.WriteString(strings.Join(debugFields, " ")) 259 d.logBuffer.WriteByte('\n') 260 } 261 262 func (d *debugger) logResponseProto(res fmt.Stringer) { 263 d.logBuffer.WriteString(res.String()) 264 d.logBuffer.WriteByte('\n') 265 } 266 267 func (d *debugger) logGetResponseNotifs(res *gnmi.GetResponse, receiveTime time.Time) { 268 for _, notif := range res.GetNotification() { 269 d.logNotif(notif, receiveTime) 270 } 271 } 272 273 func (d *debugger) logNotif(notif *gnmi.Notification, receiveTime time.Time) { 274 notifTime := time.Unix(0, notif.GetTimestamp()).UTC() 275 prefix := gnmilib.StrPath(notif.GetPrefix()) 276 for _, update := range notif.GetUpdate() { 277 pth := path.Join(prefix, gnmilib.StrPath(update.GetPath())) 278 val := gnmilib.StrValCompactJSON(update.GetVal()) 279 d.logUpdate(receiveTime, notifTime, pth, val) 280 } 281 } 282 283 func (d *debugger) logUpdate(receiveTime time.Time, notifTime time.Time, path, val string) { 284 debugFields := append( 285 d.logPrefixFields(receiveTime), 286 fmt.Sprintf("notif_time=%s", notifTime.Format(time.RFC3339Nano)), 287 ) 288 if d.debugFlag&debugUpdatesTiming != 0 { 289 // Difference between the notification time and last notification time. 290 if _, ok := d.lastUpdateTimes[path]; !ok { 291 d.lastUpdateTimes[path] = time.Time{} 292 } 293 lastUpdateTime := d.lastUpdateTimes[path] 294 var lastNotifAgo time.Duration 295 if !lastUpdateTime.IsZero() { 296 lastNotifAgo = notifTime.Sub(lastUpdateTime) 297 } 298 d.lastUpdateTimes[path] = notifTime 299 300 // Difference between the response receive time and notification time. 301 var latency time.Duration 302 if !lastUpdateTime.IsZero() { 303 latency = receiveTime.Sub(lastUpdateTime) 304 } 305 306 debugFields = append(debugFields, 307 d.logLastReceiveAgoField(receiveTime), 308 fmt.Sprintf("last_notif_ago=%s", lastNotifAgo), 309 fmt.Sprintf("latency=%s", latency), 310 ) 311 } 312 if d.debugFlag&debugUpdatesPaths != 0 { 313 debugFields = append(debugFields, fmt.Sprintf("path=%s", path)) 314 } 315 if d.debugFlag&debugUpdatesValues != 0 { 316 debugFields = append(debugFields, fmt.Sprintf("val=%s", val)) 317 } 318 d.logBuffer.WriteString(strings.Join(debugFields, " ")) 319 d.logBuffer.WriteByte('\n') 320 } 321 322 // logSubscribeResponse prints SubscribeResponse debug information based on 323 // the bits of the debug flag. 324 func (d *debugger) logSubscribeResponse(res *gnmi.SubscribeResponse) { 325 if d.debugFlag^debugSilent == 0 { // Any other enabled flag overrides silence. 326 return 327 } 328 receiveTime := time.Now().UTC() 329 if d.debugFlag&debugResponseProto != 0 { 330 d.logResponseProto(res) 331 } 332 if d.debugFlag&debugUpdatesAll != 0 { 333 d.logSubscribeResponseNotif(res, receiveTime) 334 } 335 logger.Printf(d.logBuffer.String()) 336 d.logBuffer.Reset() 337 d.lastResponseTime = receiveTime 338 d.responsesCounter++ 339 } 340 341 func (d *debugger) logSubscribeResponseNotif(res *gnmi.SubscribeResponse, receiveTime time.Time) { 342 if res.GetSyncResponse() { 343 debugFields := d.logPrefixFields(receiveTime) 344 if d.debugFlag&debugUpdatesTiming != 0 { 345 debugFields = append(debugFields, d.logLastReceiveAgoField(receiveTime)) 346 } 347 debugFields = append(debugFields, "sync_response=true") 348 d.logBuffer.WriteString(strings.Join(debugFields, " ")) 349 d.logBuffer.WriteByte('\n') 350 return 351 } 352 d.logNotif(res.GetUpdate(), receiveTime) 353 } 354 355 // logPrefixFields returns the base debug prefix fields. 356 func (d *debugger) logPrefixFields(receiveTime time.Time) []string { 357 return []string{ 358 fmt.Sprintf("client=%s", d.clientAddr), 359 fmt.Sprintf("res=%s", d.responseName), 360 fmt.Sprintf("n=%d", d.responsesCounter), 361 fmt.Sprintf("rx_time=%s", receiveTime.Format(time.RFC3339Nano)), 362 } 363 } 364 365 // logLastReceiveAgoField returns the debug field of difference between the 366 // the response receive time and last response receive time. 367 func (d *debugger) logLastReceiveAgoField(receiveTime time.Time) string { 368 var lastReceiveAgo time.Duration 369 if !d.lastResponseTime.IsZero() { 370 lastReceiveAgo = receiveTime.Sub(d.lastResponseTime) 371 } 372 return fmt.Sprintf("last_rx_ago=%s", lastReceiveAgo) 373 }