From a868add749932520bbda095d3ad94538b3047624 Mon Sep 17 00:00:00 2001 From: David Roy Date: Thu, 27 Apr 2023 17:30:21 +0200 Subject: [PATCH] feat(inputs.gnmi): Support Juniper GNMI Extension Header (#13116) --- plugins/inputs/gnmi/README.md | 8 + .../GnmiJuniperTelemetryHeaderExtension.pb.go | 380 ++++++++++++++++++ plugins/inputs/gnmi/gnmi.go | 23 +- plugins/inputs/gnmi/gnmi_test.go | 88 ++++ plugins/inputs/gnmi/handler.go | 69 +++- plugins/inputs/gnmi/sample.conf | 8 + 6 files changed, 565 insertions(+), 11 deletions(-) create mode 100644 plugins/inputs/gnmi/extensions/jnpr_gnmi_extention/GnmiJuniperTelemetryHeaderExtension.pb.go diff --git a/plugins/inputs/gnmi/README.md b/plugins/inputs/gnmi/README.md index 4b3334b29..0a7ea01b8 100644 --- a/plugins/inputs/gnmi/README.md +++ b/plugins/inputs/gnmi/README.md @@ -70,6 +70,14 @@ See the [CONFIGURATION.md][CONFIGURATION.md] for more details. # prefix = "" # target = "" + ## Vendor specific options + ## This defines what vendor specific options to load. + ## * Juniper Header Extension (juniper_header): some sensors are directly managed by + ## Linecard, which adds the Juniper GNMI Header Extension. Enabling this + ## allows the decoding of the Extension header if present. Currently this knob + ## adds component, component_id & sub_component_id as additionnal tags + # vendor_specific = [] + ## Define additional aliases to map encoding paths to measurement names # [inputs.gnmi.aliases] # ifcounters = "openconfig:/interfaces/interface/state/counters" diff --git a/plugins/inputs/gnmi/extensions/jnpr_gnmi_extention/GnmiJuniperTelemetryHeaderExtension.pb.go b/plugins/inputs/gnmi/extensions/jnpr_gnmi_extention/GnmiJuniperTelemetryHeaderExtension.pb.go new file mode 100644 index 000000000..850654c21 --- /dev/null +++ b/plugins/inputs/gnmi/extensions/jnpr_gnmi_extention/GnmiJuniperTelemetryHeaderExtension.pb.go @@ -0,0 +1,380 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.30.0 +// protoc v3.12.4 +// source: GnmiJuniperTelemetryHeaderExtension.proto + +package jnpr_gnmi_extention + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type StreamType int32 + +const ( + StreamType_INITIAL_SYNC StreamType = 0 + StreamType_ONCHANGE StreamType = 1 + StreamType_PERIODIC StreamType = 2 +) + +// Enum value maps for StreamType. +var ( + StreamType_name = map[int32]string{ + 0: "INITIAL_SYNC", + 1: "ONCHANGE", + 2: "PERIODIC", + } + StreamType_value = map[string]int32{ + "INITIAL_SYNC": 0, + "ONCHANGE": 1, + "PERIODIC": 2, + } +) + +func (x StreamType) Enum() *StreamType { + p := new(StreamType) + *p = x + return p +} + +func (x StreamType) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (StreamType) Descriptor() protoreflect.EnumDescriptor { + return file_GnmiJuniperTelemetryHeaderExtension_proto_enumTypes[0].Descriptor() +} + +func (StreamType) Type() protoreflect.EnumType { + return &file_GnmiJuniperTelemetryHeaderExtension_proto_enumTypes[0] +} + +func (x StreamType) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use StreamType.Descriptor instead. +func (StreamType) EnumDescriptor() ([]byte, []int) { + return file_GnmiJuniperTelemetryHeaderExtension_proto_rawDescGZIP(), []int{0} +} + +// Present as first gNMI update in all packets +type GnmiJuniperTelemetryHeaderExtension struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // router name:export IP address + SystemId string `protobuf:"bytes,1,opt,name=system_id,json=systemId,proto3" json:"system_id,omitempty"` + // line card / RE (slot number) + ComponentId uint32 `protobuf:"varint,2,opt,name=component_id,json=componentId,proto3" json:"component_id,omitempty"` + // PFE (if applicable) + SubComponentId uint32 `protobuf:"varint,3,opt,name=sub_component_id,json=subComponentId,proto3" json:"sub_component_id,omitempty"` + // Internal sensor name + SensorName string `protobuf:"bytes,4,opt,name=sensor_name,json=sensorName,proto3" json:"sensor_name,omitempty"` + // Sensor path in the subscribe request + SubscribedPath string `protobuf:"bytes,5,opt,name=subscribed_path,json=subscribedPath,proto3" json:"subscribed_path,omitempty"` + // Internal sensor path in junos + StreamedPath string `protobuf:"bytes,6,opt,name=streamed_path,json=streamedPath,proto3" json:"streamed_path,omitempty"` + Component string `protobuf:"bytes,7,opt,name=component,proto3" json:"component,omitempty"` + // Sequence number, monotonically increasing for each + SequenceNumber uint64 `protobuf:"varint,8,opt,name=sequence_number,json=sequenceNumber,proto3" json:"sequence_number,omitempty"` + // Payload get timestamp in milliseconds + PayloadGetTimestamp int64 `protobuf:"varint,9,opt,name=payload_get_timestamp,json=payloadGetTimestamp,proto3" json:"payload_get_timestamp,omitempty"` + // Stream creation timestamp in milliseconds + StreamCreationTimestamp int64 `protobuf:"varint,10,opt,name=stream_creation_timestamp,json=streamCreationTimestamp,proto3" json:"stream_creation_timestamp,omitempty"` + // [Deprecated] Event timestamp in milliseconds + // + // Deprecated: Marked as deprecated in GnmiJuniperTelemetryHeaderExtension.proto. + EventTimestamp int64 `protobuf:"varint,11,opt,name=event_timestamp,json=eventTimestamp,proto3" json:"event_timestamp,omitempty"` + // Export timestamp in milliseconds + ExportTimestamp int64 `protobuf:"varint,12,opt,name=export_timestamp,json=exportTimestamp,proto3" json:"export_timestamp,omitempty"` + // Subsequence number + SubSequenceNumber uint64 `protobuf:"varint,13,opt,name=sub_sequence_number,json=subSequenceNumber,proto3" json:"sub_sequence_number,omitempty"` + // End of marker + Eom bool `protobuf:"varint,14,opt,name=eom,proto3" json:"eom,omitempty"` + // Event publish timestamp in milliseconds + EventPublishTimestamp int64 `protobuf:"varint,15,opt,name=event_publish_timestamp,json=eventPublishTimestamp,proto3" json:"event_publish_timestamp,omitempty"` + // Stream type of packet + StreamId StreamType `protobuf:"varint,16,opt,name=stream_id,json=streamId,proto3,enum=StreamType" json:"stream_id,omitempty"` +} + +func (x *GnmiJuniperTelemetryHeaderExtension) Reset() { + *x = GnmiJuniperTelemetryHeaderExtension{} + if protoimpl.UnsafeEnabled { + mi := &file_GnmiJuniperTelemetryHeaderExtension_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GnmiJuniperTelemetryHeaderExtension) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GnmiJuniperTelemetryHeaderExtension) ProtoMessage() {} + +func (x *GnmiJuniperTelemetryHeaderExtension) ProtoReflect() protoreflect.Message { + mi := &file_GnmiJuniperTelemetryHeaderExtension_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GnmiJuniperTelemetryHeaderExtension.ProtoReflect.Descriptor instead. +func (*GnmiJuniperTelemetryHeaderExtension) Descriptor() ([]byte, []int) { + return file_GnmiJuniperTelemetryHeaderExtension_proto_rawDescGZIP(), []int{0} +} + +func (x *GnmiJuniperTelemetryHeaderExtension) GetSystemId() string { + if x != nil { + return x.SystemId + } + return "" +} + +func (x *GnmiJuniperTelemetryHeaderExtension) GetComponentId() uint32 { + if x != nil { + return x.ComponentId + } + return 0 +} + +func (x *GnmiJuniperTelemetryHeaderExtension) GetSubComponentId() uint32 { + if x != nil { + return x.SubComponentId + } + return 0 +} + +func (x *GnmiJuniperTelemetryHeaderExtension) GetSensorName() string { + if x != nil { + return x.SensorName + } + return "" +} + +func (x *GnmiJuniperTelemetryHeaderExtension) GetSubscribedPath() string { + if x != nil { + return x.SubscribedPath + } + return "" +} + +func (x *GnmiJuniperTelemetryHeaderExtension) GetStreamedPath() string { + if x != nil { + return x.StreamedPath + } + return "" +} + +func (x *GnmiJuniperTelemetryHeaderExtension) GetComponent() string { + if x != nil { + return x.Component + } + return "" +} + +func (x *GnmiJuniperTelemetryHeaderExtension) GetSequenceNumber() uint64 { + if x != nil { + return x.SequenceNumber + } + return 0 +} + +func (x *GnmiJuniperTelemetryHeaderExtension) GetPayloadGetTimestamp() int64 { + if x != nil { + return x.PayloadGetTimestamp + } + return 0 +} + +func (x *GnmiJuniperTelemetryHeaderExtension) GetStreamCreationTimestamp() int64 { + if x != nil { + return x.StreamCreationTimestamp + } + return 0 +} + +// Deprecated: Marked as deprecated in GnmiJuniperTelemetryHeaderExtension.proto. +func (x *GnmiJuniperTelemetryHeaderExtension) GetEventTimestamp() int64 { + if x != nil { + return x.EventTimestamp + } + return 0 +} + +func (x *GnmiJuniperTelemetryHeaderExtension) GetExportTimestamp() int64 { + if x != nil { + return x.ExportTimestamp + } + return 0 +} + +func (x *GnmiJuniperTelemetryHeaderExtension) GetSubSequenceNumber() uint64 { + if x != nil { + return x.SubSequenceNumber + } + return 0 +} + +func (x *GnmiJuniperTelemetryHeaderExtension) GetEom() bool { + if x != nil { + return x.Eom + } + return false +} + +func (x *GnmiJuniperTelemetryHeaderExtension) GetEventPublishTimestamp() int64 { + if x != nil { + return x.EventPublishTimestamp + } + return 0 +} + +func (x *GnmiJuniperTelemetryHeaderExtension) GetStreamId() StreamType { + if x != nil { + return x.StreamId + } + return StreamType_INITIAL_SYNC +} + +var File_GnmiJuniperTelemetryHeaderExtension_proto protoreflect.FileDescriptor + +var file_GnmiJuniperTelemetryHeaderExtension_proto_rawDesc = []byte{ + 0x0a, 0x29, 0x47, 0x6e, 0x6d, 0x69, 0x4a, 0x75, 0x6e, 0x69, 0x70, 0x65, 0x72, 0x54, 0x65, 0x6c, + 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x45, 0x78, 0x74, 0x65, + 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xb1, 0x05, 0x0a, 0x23, + 0x47, 0x6e, 0x6d, 0x69, 0x4a, 0x75, 0x6e, 0x69, 0x70, 0x65, 0x72, 0x54, 0x65, 0x6c, 0x65, 0x6d, + 0x65, 0x74, 0x72, 0x79, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x45, 0x78, 0x74, 0x65, 0x6e, 0x73, + 0x69, 0x6f, 0x6e, 0x12, 0x1b, 0x0a, 0x09, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x5f, 0x69, 0x64, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x49, 0x64, + 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0b, 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, + 0x74, 0x49, 0x64, 0x12, 0x28, 0x0a, 0x10, 0x73, 0x75, 0x62, 0x5f, 0x63, 0x6f, 0x6d, 0x70, 0x6f, + 0x6e, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0e, 0x73, + 0x75, 0x62, 0x43, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x1f, 0x0a, + 0x0b, 0x73, 0x65, 0x6e, 0x73, 0x6f, 0x72, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x04, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x0a, 0x73, 0x65, 0x6e, 0x73, 0x6f, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x27, + 0x0a, 0x0f, 0x73, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x64, 0x5f, 0x70, 0x61, 0x74, + 0x68, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x73, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, + 0x62, 0x65, 0x64, 0x50, 0x61, 0x74, 0x68, 0x12, 0x23, 0x0a, 0x0d, 0x73, 0x74, 0x72, 0x65, 0x61, + 0x6d, 0x65, 0x64, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, + 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x65, 0x64, 0x50, 0x61, 0x74, 0x68, 0x12, 0x1c, 0x0a, 0x09, + 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x09, 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x12, 0x27, 0x0a, 0x0f, 0x73, 0x65, + 0x71, 0x75, 0x65, 0x6e, 0x63, 0x65, 0x5f, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x18, 0x08, 0x20, + 0x01, 0x28, 0x04, 0x52, 0x0e, 0x73, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x63, 0x65, 0x4e, 0x75, 0x6d, + 0x62, 0x65, 0x72, 0x12, 0x32, 0x0a, 0x15, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x5f, 0x67, + 0x65, 0x74, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x09, 0x20, 0x01, + 0x28, 0x03, 0x52, 0x13, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x47, 0x65, 0x74, 0x54, 0x69, + 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12, 0x3a, 0x0a, 0x19, 0x73, 0x74, 0x72, 0x65, 0x61, + 0x6d, 0x5f, 0x63, 0x72, 0x65, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x73, + 0x74, 0x61, 0x6d, 0x70, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x03, 0x52, 0x17, 0x73, 0x74, 0x72, 0x65, + 0x61, 0x6d, 0x43, 0x72, 0x65, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, + 0x61, 0x6d, 0x70, 0x12, 0x2b, 0x0a, 0x0f, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x5f, 0x74, 0x69, 0x6d, + 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x03, 0x42, 0x02, 0x18, 0x01, + 0x52, 0x0e, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, + 0x12, 0x29, 0x0a, 0x10, 0x65, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x73, + 0x74, 0x61, 0x6d, 0x70, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x65, 0x78, 0x70, 0x6f, + 0x72, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12, 0x2e, 0x0a, 0x13, 0x73, + 0x75, 0x62, 0x5f, 0x73, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x63, 0x65, 0x5f, 0x6e, 0x75, 0x6d, 0x62, + 0x65, 0x72, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x04, 0x52, 0x11, 0x73, 0x75, 0x62, 0x53, 0x65, 0x71, + 0x75, 0x65, 0x6e, 0x63, 0x65, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x12, 0x10, 0x0a, 0x03, 0x65, + 0x6f, 0x6d, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x08, 0x52, 0x03, 0x65, 0x6f, 0x6d, 0x12, 0x36, 0x0a, + 0x17, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x5f, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x5f, 0x74, + 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x03, 0x52, 0x15, + 0x65, 0x76, 0x65, 0x6e, 0x74, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x54, 0x69, 0x6d, 0x65, + 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12, 0x28, 0x0a, 0x09, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x5f, + 0x69, 0x64, 0x18, 0x10, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x0b, 0x2e, 0x53, 0x74, 0x72, 0x65, 0x61, + 0x6d, 0x54, 0x79, 0x70, 0x65, 0x52, 0x08, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x49, 0x64, 0x2a, + 0x3a, 0x0a, 0x0a, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x54, 0x79, 0x70, 0x65, 0x12, 0x10, 0x0a, + 0x0c, 0x49, 0x4e, 0x49, 0x54, 0x49, 0x41, 0x4c, 0x5f, 0x53, 0x59, 0x4e, 0x43, 0x10, 0x00, 0x12, + 0x0c, 0x0a, 0x08, 0x4f, 0x4e, 0x43, 0x48, 0x41, 0x4e, 0x47, 0x45, 0x10, 0x01, 0x12, 0x0c, 0x0a, + 0x08, 0x50, 0x45, 0x52, 0x49, 0x4f, 0x44, 0x49, 0x43, 0x10, 0x02, 0x42, 0x17, 0x5a, 0x15, 0x2e, + 0x3b, 0x6a, 0x6e, 0x70, 0x72, 0x5f, 0x67, 0x6e, 0x6d, 0x69, 0x5f, 0x65, 0x78, 0x74, 0x65, 0x6e, + 0x74, 0x69, 0x6f, 0x6e, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_GnmiJuniperTelemetryHeaderExtension_proto_rawDescOnce sync.Once + file_GnmiJuniperTelemetryHeaderExtension_proto_rawDescData = file_GnmiJuniperTelemetryHeaderExtension_proto_rawDesc +) + +func file_GnmiJuniperTelemetryHeaderExtension_proto_rawDescGZIP() []byte { + file_GnmiJuniperTelemetryHeaderExtension_proto_rawDescOnce.Do(func() { + file_GnmiJuniperTelemetryHeaderExtension_proto_rawDescData = protoimpl.X.CompressGZIP(file_GnmiJuniperTelemetryHeaderExtension_proto_rawDescData) + }) + return file_GnmiJuniperTelemetryHeaderExtension_proto_rawDescData +} + +var file_GnmiJuniperTelemetryHeaderExtension_proto_enumTypes = make([]protoimpl.EnumInfo, 1) +var file_GnmiJuniperTelemetryHeaderExtension_proto_msgTypes = make([]protoimpl.MessageInfo, 1) +var file_GnmiJuniperTelemetryHeaderExtension_proto_goTypes = []interface{}{ + (StreamType)(0), // 0: StreamType + (*GnmiJuniperTelemetryHeaderExtension)(nil), // 1: GnmiJuniperTelemetryHeaderExtension +} +var file_GnmiJuniperTelemetryHeaderExtension_proto_depIdxs = []int32{ + 0, // 0: GnmiJuniperTelemetryHeaderExtension.stream_id:type_name -> StreamType + 1, // [1:1] is the sub-list for method output_type + 1, // [1:1] is the sub-list for method input_type + 1, // [1:1] is the sub-list for extension type_name + 1, // [1:1] is the sub-list for extension extendee + 0, // [0:1] is the sub-list for field type_name +} + +func init() { file_GnmiJuniperTelemetryHeaderExtension_proto_init() } +func file_GnmiJuniperTelemetryHeaderExtension_proto_init() { + if File_GnmiJuniperTelemetryHeaderExtension_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_GnmiJuniperTelemetryHeaderExtension_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GnmiJuniperTelemetryHeaderExtension); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_GnmiJuniperTelemetryHeaderExtension_proto_rawDesc, + NumEnums: 1, + NumMessages: 1, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_GnmiJuniperTelemetryHeaderExtension_proto_goTypes, + DependencyIndexes: file_GnmiJuniperTelemetryHeaderExtension_proto_depIdxs, + EnumInfos: file_GnmiJuniperTelemetryHeaderExtension_proto_enumTypes, + MessageInfos: file_GnmiJuniperTelemetryHeaderExtension_proto_msgTypes, + }.Build() + File_GnmiJuniperTelemetryHeaderExtension_proto = out.File + file_GnmiJuniperTelemetryHeaderExtension_proto_rawDesc = nil + file_GnmiJuniperTelemetryHeaderExtension_proto_goTypes = nil + file_GnmiJuniperTelemetryHeaderExtension_proto_depIdxs = nil +} diff --git a/plugins/inputs/gnmi/gnmi.go b/plugins/inputs/gnmi/gnmi.go index d47a9c8b5..02297913f 100644 --- a/plugins/inputs/gnmi/gnmi.go +++ b/plugins/inputs/gnmi/gnmi.go @@ -18,6 +18,7 @@ import ( "github.com/influxdata/telegraf" "github.com/influxdata/telegraf/config" + "github.com/influxdata/telegraf/internal/choice" internaltls "github.com/influxdata/telegraf/plugins/common/tls" "github.com/influxdata/telegraf/plugins/inputs" ) @@ -36,6 +37,9 @@ device model and the following response data: %+v This message is only printed once.` +// Currently supported GNMI Extensions +var supportedExtensions = []string{"juniper_header"} + // gNMI plugin instance type GNMI struct { Addresses []string `toml:"addresses"` @@ -47,6 +51,7 @@ type GNMI struct { Prefix string `toml:"prefix"` Target string `toml:"target"` UpdatesOnly bool `toml:"updates_only"` + VendorSpecific []string `toml:"vendor_specific"` Username string `toml:"username"` Password string `toml:"password"` Redial config.Duration `toml:"redial"` @@ -187,7 +192,15 @@ func (c *GNMI) Start(acc telegraf.Accumulator) error { for _, addr := range c.Addresses { go func(addr string) { defer c.wg.Done() - h := newHandler(addr, c.internalAliases, c.TagSubscriptions, int(c.MaxMsgSize), c.Log, c.Trace) + confHandler := configHandler{ + aliases: c.internalAliases, + subscriptions: c.TagSubscriptions, + maxSize: int(c.MaxMsgSize), + log: c.Log, + trace: c.Trace, + vendorExt: c.VendorSpecific, + } + h := newHandler(addr, confHandler) for ctx.Err() == nil { if err := h.subscribeGNMI(ctx, acc, tlscfg, request); err != nil && ctx.Err() == nil { acc.AddError(err) @@ -304,6 +317,14 @@ func init() { inputs.Add("cisco_telemetry_gnmi", New) } +func (c *GNMI) Init() error { + // Check vendor_specific options configured by user + if err := choice.CheckSlice(c.VendorSpecific, supportedExtensions); err != nil { + return fmt.Errorf("unsupported vendor_specific option: %w", err) + } + return nil +} + func (s *Subscription) buildFullPath(c *GNMI) error { var err error if s.fullPath, err = xpath.ToGNMIPath(s.Path); err != nil { diff --git a/plugins/inputs/gnmi/gnmi_test.go b/plugins/inputs/gnmi/gnmi_test.go index 38ad6afd6..e976147a9 100644 --- a/plugins/inputs/gnmi/gnmi_test.go +++ b/plugins/inputs/gnmi/gnmi_test.go @@ -13,14 +13,17 @@ import ( "time" gnmiLib "github.com/openconfig/gnmi/proto/gnmi" + gnmiExt "github.com/openconfig/gnmi/proto/gnmi_ext" "github.com/stretchr/testify/require" "google.golang.org/grpc" "google.golang.org/grpc/metadata" "google.golang.org/protobuf/encoding/protojson" + "google.golang.org/protobuf/proto" "github.com/influxdata/telegraf" "github.com/influxdata/telegraf/config" "github.com/influxdata/telegraf/plugins/inputs" + jnprHeader "github.com/influxdata/telegraf/plugins/inputs/gnmi/extensions/jnpr_gnmi_extention" "github.com/influxdata/telegraf/plugins/parsers/influx" "github.com/influxdata/telegraf/testutil" ) @@ -903,6 +906,91 @@ func TestNotification(t *testing.T) { ), }, }, + { + name: "Juniper Extension", + plugin: &GNMI{ + Log: testutil.Logger{}, + Encoding: "proto", + VendorSpecific: []string{"juniper_header"}, + Redial: config.Duration(1 * time.Second), + Subscriptions: []Subscription{ + { + Name: "type", + Origin: "openconfig-platform", + Path: "/components/component[name=CHASSIS0:FPC0]/state", + SubscriptionMode: "sample", + SampleInterval: config.Duration(1 * time.Second), + }, + }, + }, + server: &MockServer{ + SubscribeF: func(server gnmiLib.GNMI_SubscribeServer) error { + if err := server.Send(&gnmiLib.SubscribeResponse{Response: &gnmiLib.SubscribeResponse_SyncResponse{SyncResponse: true}}); err != nil { + return err + } + response := &gnmiLib.SubscribeResponse{ + Response: &gnmiLib.SubscribeResponse_Update{ + Update: &gnmiLib.Notification{ + Timestamp: 1668771585733542546, + Prefix: &gnmiLib.Path{ + Elem: []*gnmiLib.PathElem{ + {Name: "openconfig-platform:components"}, + {Name: "component", Key: map[string]string{"name": "CHASSIS0:FPC0"}}, + {Name: "state"}, + }, + Target: "OC-YANG", + }, + Update: []*gnmiLib.Update{ + { + Path: &gnmiLib.Path{ + Elem: []*gnmiLib.PathElem{ + {Name: "type"}, + }}, + Val: &gnmiLib.TypedValue{ + Value: &gnmiLib.TypedValue_StringVal{StringVal: "LINECARD"}, + }, + }, + }, + }, + }, + Extension: []*gnmiExt.Extension{{ + Ext: &gnmiExt.Extension_RegisteredExt{ + RegisteredExt: &gnmiExt.RegisteredExtension{ + // Juniper Header Extension + //EID_JUNIPER_TELEMETRY_HEADER = 1; + Id: 1, + Msg: func(jnprExt *jnprHeader.GnmiJuniperTelemetryHeaderExtension) []byte { + b, err := proto.Marshal(jnprExt) + if err != nil { + return nil + } + return b + }(&jnprHeader.GnmiJuniperTelemetryHeaderExtension{ComponentId: 15, SubComponentId: 1, Component: "PICD"}), + }, + }, + }}, + } + return server.Send(response) + }, + }, + expected: []telegraf.Metric{ + testutil.MustMetric( + "type", + map[string]string{ + "path": "openconfig-platform:/components/component/state", + "source": "127.0.0.1", + "name": "CHASSIS0:FPC0", + "component_id": "15", + "sub_component_id": "1", + "component": "PICD", + }, + map[string]interface{}{ + "type": "LINECARD", + }, + time.Unix(0, 0), + ), + }, + }, } for _, tt := range tests { diff --git a/plugins/inputs/gnmi/handler.go b/plugins/inputs/gnmi/handler.go index 2cc2ecd11..14840915a 100644 --- a/plugins/inputs/gnmi/handler.go +++ b/plugins/inputs/gnmi/handler.go @@ -12,34 +12,52 @@ import ( "time" "github.com/influxdata/telegraf" + "github.com/influxdata/telegraf/internal/choice" "github.com/influxdata/telegraf/metric" + jnprHeader "github.com/influxdata/telegraf/plugins/inputs/gnmi/extensions/jnpr_gnmi_extention" gnmiLib "github.com/openconfig/gnmi/proto/gnmi" + gnmiExt "github.com/openconfig/gnmi/proto/gnmi_ext" "google.golang.org/grpc" "google.golang.org/grpc/credentials" "google.golang.org/grpc/credentials/insecure" "google.golang.org/protobuf/encoding/protojson" + "google.golang.org/protobuf/proto" ) +const eidJuniperTelemetryHeader = 1 + type handler struct { address string aliases map[string]string tagsubs []TagSubscription maxMsgSize int emptyNameWarnShown bool + vendorExt []string tagStore *tagStore trace bool log telegraf.Logger } -func newHandler(addr string, aliases map[string]string, subs []TagSubscription, maxsize int, l telegraf.Logger, trace bool) *handler { +// Allow to convey additionnal configuration elements +type configHandler struct { + aliases map[string]string + subscriptions []TagSubscription + maxSize int + log telegraf.Logger + trace bool + vendorExt []string +} + +func newHandler(addr string, confHandler configHandler) *handler { return &handler{ address: addr, - aliases: aliases, - tagsubs: subs, - maxMsgSize: maxsize, - tagStore: newTagStore(subs), - trace: trace, - log: l, + aliases: confHandler.aliases, + tagsubs: confHandler.subscriptions, + maxMsgSize: confHandler.maxSize, + vendorExt: confHandler.vendorExt, + tagStore: newTagStore(confHandler.subscriptions), + trace: confHandler.trace, + log: confHandler.log, } } @@ -98,21 +116,52 @@ func (h *handler) subscribeGNMI(ctx context.Context, acc telegraf.Accumulator, t h.log.Debugf("update_%v: %s", t, string(buf)) } } - if response, ok := reply.Response.(*gnmiLib.SubscribeResponse_Update); ok { - h.handleSubscribeResponseUpdate(acc, response) + h.handleSubscribeResponseUpdate(acc, response, reply.GetExtension()) } } return nil } // Handle SubscribeResponse_Update message from gNMI and parse contained telemetry data -func (h *handler) handleSubscribeResponseUpdate(acc telegraf.Accumulator, response *gnmiLib.SubscribeResponse_Update) { +func (h *handler) handleSubscribeResponseUpdate(acc telegraf.Accumulator, response *gnmiLib.SubscribeResponse_Update, extension []*gnmiExt.Extension) { var prefix, prefixAliasPath string grouper := metric.NewSeriesGrouper() timestamp := time.Unix(0, response.Update.Timestamp) prefixTags := make(map[string]string) + // iter on each extension + for _, ext := range extension { + currentExt := ext.GetRegisteredExt().Msg + if currentExt == nil { + break + } + // extension ID + switch ext.GetRegisteredExt().Id { + // Juniper Header extention + //EID_JUNIPER_TELEMETRY_HEADER = 1; + case eidJuniperTelemetryHeader: + // Decode it only if user requested it + if choice.Contains("juniper_header", h.vendorExt) { + juniperHeader := &jnprHeader.GnmiJuniperTelemetryHeaderExtension{} + // unmarshal extention + err := proto.Unmarshal(currentExt, juniperHeader) + if err != nil { + h.log.Errorf("unmarshal gnmi Juniper Header extention failed: %w", err) + break + } + // Add only relevant Tags from the Juniper Header extention. + // These are requiered for aggregation + prefixTags["component_id"] = fmt.Sprint(juniperHeader.GetComponentId()) + prefixTags["component"] = fmt.Sprint(juniperHeader.GetComponent()) + prefixTags["sub_component_id"] = fmt.Sprint(juniperHeader.GetSubComponentId()) + } + + default: + continue + } + } + if response.Update.Prefix != nil { var err error if prefix, prefixAliasPath, err = handlePath(response.Update.Prefix, prefixTags, h.aliases, ""); err != nil { diff --git a/plugins/inputs/gnmi/sample.conf b/plugins/inputs/gnmi/sample.conf index cc8ade872..8e87380b0 100644 --- a/plugins/inputs/gnmi/sample.conf +++ b/plugins/inputs/gnmi/sample.conf @@ -34,6 +34,14 @@ # prefix = "" # target = "" + ## Vendor specific options + ## This defines what vendor specific options to load. + ## * Juniper Header Extension (juniper_header): some sensors are directly managed by + ## Linecard, which adds the Juniper GNMI Header Extension. Enabling this + ## allows the decoding of the Extension header if present. Currently this knob + ## adds component, component_id & sub_component_id as additionnal tags + # vendor_specific = [] + ## Define additional aliases to map encoding paths to measurement names # [inputs.gnmi.aliases] # ifcounters = "openconfig:/interfaces/interface/state/counters"