// parseValue returns the integer or floating point value of the given update, // or nil if it's not a numerical update. func parseValue(update *openconfig.Update) (value interface{}) { decoder := json.NewDecoder(bytes.NewReader(update.Value.Value)) decoder.UseNumber() err := decoder.Decode(&value) if err != nil { glog.Fatalf("Malformed JSON update %q in %s", update.Value.Value, update) } num, ok := value.(json.Number) if !ok { return nil } // Convert our json.Number to either an int64, uint64, or float64. if value, err = num.Int64(); err != nil { // num is either a large unsigned integer or a floating point. if strings.Contains(err.Error(), "value out of range") { // Sigh. value, err = strconv.ParseUint(num.String(), 10, 64) } else { value, err = num.Float64() if err != nil { glog.Fatalf("Malformed JSON number %q in %s", num, update) } } } return }
// Subscribe sends subscriptions, and consumes responses. // The given publish function is used to publish SubscribeResponses received // for the given subscriptions, when connected to the given host, with the // given user/pass pair, or the client-side cert specified in the gRPC opts. // This function does not normally return so it should probably be run in its // own goroutine. When this function returns, the given WaitGroup is marked // as done. func (c *Client) Subscribe(wg *sync.WaitGroup, subscriptions []string, publish PublishFunc) { defer wg.Done() stream, err := c.client.Subscribe(c.ctx) if err != nil { glog.Fatalf("Subscribe failed: %s", err) } defer stream.CloseSend() for _, path := range subscriptions { sub := &openconfig.SubscribeRequest{ Request: &openconfig.SubscribeRequest_Subscribe{ Subscribe: &openconfig.SubscriptionList{ Subscription: []*openconfig.Subscription{ &openconfig.Subscription{ Path: &openconfig.Path{Element: strings.Split(path, "/")}, }, }, }, }, } glog.Infof("Sending subscribe request: %s", sub) err = stream.Send(sub) if err != nil { glog.Fatalf("Failed to subscribe: %s", err) } } for { resp, err := stream.Recv() if err != nil { if err != io.EOF { glog.Fatalf("Error received from the server: %s", err) } return } switch resp := resp.Response.(type) { case *openconfig.SubscribeResponse_SyncResponse: if !resp.SyncResponse { panic("initial sync failed," + " check that you're using a client compatible with the server") } } glog.V(3).Info(resp) publish(c.device, resp) } }
func convertUpdate(update *openconfig.Update) interface{} { switch update.Value.Type { case openconfig.Type_JSON: var value interface{} err := json.Unmarshal(update.Value.Value, &value) if err != nil { glog.Fatalf("Malformed JSON update %q in %s", update.Value.Value, update) } return value case openconfig.Type_BYTES: return update.Value.Value default: glog.Fatalf("Unhandled type of value %v in %s", update.Value.Type, update) return nil } }
func bufferToRedis(addr string, notif *openconfig.Notification) { path := addr + "/" + joinPath(notif.Prefix) data := &redisData{key: path} if len(notif.Update) != 0 { hmset := make(map[string]string, len(notif.Update)) // Updates to publish on the pub/sub. pub := make(map[string]interface{}, len(notif.Update)) for _, update := range notif.Update { key := joinPath(update.Path) value := convertUpdate(update) pub[key] = value marshaledValue, err := json.Marshal(value) if err != nil { glog.Fatalf("Failed to JSON marshal update %#v", update) } hmset[key] = string(marshaledValue) } data.hmset = hmset data.pub = pub } if len(notif.Delete) != 0 { hdel := make([]string, len(notif.Delete)) for i, del := range notif.Delete { hdel[i] = joinPath(del) } data.hdel = hdel } pushToRedis(data) }
func redisPublish(path, kind string, payload interface{}) { js, err := json.Marshal(map[string]interface{}{ "kind": kind, "payload": payload, }) if err != nil { glog.Fatalf("JSON error: %s", err) } if reply := client.Publish(path, string(js)); reply.Err() != nil { glog.Fatal("Redis PUBLISH error: ", reply.Err()) } }
// Get sends a get request and returns the responses func (c *Client) Get(path string) []*openconfig.Notification { req := &openconfig.GetRequest{ Path: []*openconfig.Path{ { Element: strings.Split(path, "/"), }, }, } response, err := c.client.Get(c.ctx, req) if err != nil { glog.Fatalf("Get failed: %s", err) } return response.Notification }
func pbsToStream(pbs []*pb.LanzRecord) []byte { var r []byte for _, p := range pbs { b, err := proto.Marshal(p) if err != nil { glog.Fatalf("Can't marshal pb: %v", err) } bLen := uint64(len(b)) s := make([]byte, binary.MaxVarintLen64) sLen := binary.PutUvarint(s, bLen) r = append(r, s[:sLen]...) r = append(r, b...) } return r }
// Run sets up the HTTP server and any handlers func (s *server) Run() { http.HandleFunc("/debug", debugHandler) http.HandleFunc("/debug/latency", latencyHandler) var listener net.Listener var listenErr error err := netns.Do(s.vrfName, func() { listener, listenErr = net.Listen("tcp", s.serverName) }) if err != nil { glog.Fatalf("Failed to go to network namespace for vrf %s: %s", s.vrfName, err) } if listenErr != nil { glog.Fatal("Could not start monitor server:", listenErr) } err = http.Serve(listener, nil) if err != nil { glog.Fatal("http serve returned with error:", err) } }
func pushToOpenTSDB(addr string, conn OpenTSDBConn, config *Config, notif *openconfig.Notification) { if notif.Timestamp <= 0 { glog.Fatalf("Invalid timestamp %d in %s", notif.Timestamp, notif) } host := addr[:strings.IndexRune(addr, ':')] prefix := "/" + strings.Join(notif.Prefix.Element, "/") for _, update := range notif.Update { if update.Value == nil || update.Value.Type != openconfig.Type_JSON { glog.V(9).Infof("Ignoring incompatible update value in %s", update) continue } value := parseValue(update) if value == nil { glog.V(9).Infof("Ignoring non-numeric value in %s", update) continue } path := prefix + "/" + strings.Join(update.Path.Element, "/") metricName, tags := config.Match(path) if metricName == "" { glog.V(8).Infof("Ignoring unmatched update at %s: %+v", path, update.Value) continue } tags["host"] = host conn.Put(&DataPoint{ Metric: metricName, Timestamp: uint64(notif.Timestamp), Value: value, Tags: tags, }) } }
// New creates a new gRPC client and connects it func New(username, password, addr string, opts []grpc.DialOption) *Client { device := addr if !strings.ContainsRune(addr, ':') { addr += ":" + defaultPort } conn, err := grpc.Dial(addr, opts...) if err != nil { glog.Fatalf("Failed to dial: %s", err) } glog.Infof("Connected to %s", addr) client := openconfig.NewOpenConfigClient(conn) ctx := context.Background() if username != "" { ctx = metadata.NewContext(ctx, metadata.Pairs( "username", username, "password", password)) } return &Client{ client: client, device: device, ctx: ctx, } }
// ParseFlags registers some additional common flags, // parses the flags, and returns the resulting gRPC options, // and other settings to connect to the gRPC interface. func ParseFlags() (username string, password string, subscriptions, addrs []string, opts []grpc.DialOption) { var ( addrsFlag = flag.String("addrs", "localhost:6042", "Comma-separated list of addresses of OpenConfig gRPC servers") caFileFlag = flag.String("cafile", "", "Path to server TLS certificate file") certFileFlag = flag.String("certfile", "", "Path to client TLS certificate file") keyFileFlag = flag.String("keyfile", "", "Path to client TLS private key file") passwordFlag = flag.String("password", "", "Password to authenticate with") subscribeFlag = flag.String("subscribe", "", "Comma-separated list of paths to subscribe to upon connecting to the server") usernameFlag = flag.String("username", "", "Username to authenticate with") tlsFlag = flag.Bool("tls", false, "Enable TLS") ) flag.Parse() if *tlsFlag || *caFileFlag != "" || *certFileFlag != "" { config := &tls.Config{} if *caFileFlag != "" { b, err := ioutil.ReadFile(*caFileFlag) if err != nil { glog.Fatal(err) } cp := x509.NewCertPool() if !cp.AppendCertsFromPEM(b) { glog.Fatalf("credentials: failed to append certificates") } config.RootCAs = cp } else { config.InsecureSkipVerify = true } if *certFileFlag != "" { if *keyFileFlag == "" { glog.Fatalf("Please provide both -certfile and -keyfile") } cert, err := tls.LoadX509KeyPair(*certFileFlag, *keyFileFlag) if err != nil { glog.Fatal(err) } config.Certificates = []tls.Certificate{cert} } opts = append(opts, grpc.WithTransportCredentials(credentials.NewTLS(config))) } else { opts = append(opts, grpc.WithInsecure()) } addrs = strings.Split(*addrsFlag, ",") subscriptions = strings.Split(*subscribeFlag, ",") return *usernameFlag, *passwordFlag, subscriptions, addrs, opts }