feat(inputs.gnmi): Add yang-model decoding of JSON IETF payloads (#15201)
This commit is contained in:
parent
12ab6dfb33
commit
4aa288b25f
|
|
@ -291,6 +291,7 @@ following works:
|
|||
- github.com/olivere/elastic [MIT License](https://github.com/olivere/elastic/blob/release-branch.v7/LICENSE)
|
||||
- github.com/open-telemetry/opentelemetry-collector-contrib/pkg/pdatautil [Apache License 2.0](https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/main/LICENSE)
|
||||
- github.com/openconfig/gnmi [Apache License 2.0](https://github.com/openconfig/gnmi/blob/master/LICENSE)
|
||||
- github.com/openconfig/goyang [Apache License 2.0](https://github.com/openconfig/goyang/blob/master/LICENSE)
|
||||
- github.com/opencontainers/go-digest [Apache License 2.0](https://github.com/opencontainers/go-digest/blob/master/LICENSE)
|
||||
- github.com/opencontainers/image-spec [Apache License 2.0](https://github.com/opencontainers/image-spec/blob/master/LICENSE)
|
||||
- github.com/opensearch-project/opensearch-go [Apache License 2.0](https://github.com/opensearch-project/opensearch-go/blob/main/LICENSE.txt)
|
||||
|
|
|
|||
1
go.mod
1
go.mod
|
|
@ -148,6 +148,7 @@ require (
|
|||
github.com/nwaples/tacplus v0.0.3
|
||||
github.com/olivere/elastic v6.2.37+incompatible
|
||||
github.com/openconfig/gnmi v0.10.0
|
||||
github.com/openconfig/goyang v1.0.0
|
||||
github.com/opensearch-project/opensearch-go/v2 v2.3.0
|
||||
github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b
|
||||
github.com/openzipkin-contrib/zipkin-go-opentracing v0.5.0
|
||||
|
|
|
|||
1
go.sum
1
go.sum
|
|
@ -1945,6 +1945,7 @@ github.com/openconfig/gnmi v0.10.0 h1:kQEZ/9ek3Vp2Y5IVuV2L/ba8/77TgjdXg505QXvYmg
|
|||
github.com/openconfig/gnmi v0.10.0/go.mod h1:Y9os75GmSkhHw2wX8sMsxfI7qRGAEcDh8NTa5a8vj6E=
|
||||
github.com/openconfig/goyang v0.0.0-20200115183954-d0a48929f0ea/go.mod h1:dhXaV0JgHJzdrHi2l+w0fZrwArtXL7jEFoiqLEdmkvU=
|
||||
github.com/openconfig/goyang v0.2.2/go.mod h1:vX61x01Q46AzbZUzG617vWqh/cB+aisc+RrNkXRd3W8=
|
||||
github.com/openconfig/goyang v1.0.0 h1:nYaFu7BOAk/eQn4CgAUjgYPfp3J6CdXrBryp32E5CjI=
|
||||
github.com/openconfig/goyang v1.0.0/go.mod h1:vX61x01Q46AzbZUzG617vWqh/cB+aisc+RrNkXRd3W8=
|
||||
github.com/openconfig/gribi v0.1.1-0.20210423184541-ce37eb4ba92f/go.mod h1:OoH46A2kV42cIXGyviYmAlGmn6cHjGduyC2+I9d/iVs=
|
||||
github.com/openconfig/grpctunnel v0.0.0-20210610163803-fde4a9dc048d/go.mod h1:x9tAZ4EwqCQ0jI8D6S8Yhw9Z0ee7/BxWQX0k0Uib5Q8=
|
||||
|
|
|
|||
|
|
@ -0,0 +1,263 @@
|
|||
package yangmodel
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
|
||||
"github.com/openconfig/goyang/pkg/yang"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrInsufficientData = errors.New("insufficient data")
|
||||
ErrNotFound = errors.New("no such node")
|
||||
)
|
||||
|
||||
type Decoder struct {
|
||||
modules map[string]*yang.Module
|
||||
rootNodes map[string][]yang.Node
|
||||
}
|
||||
|
||||
func NewDecoder(paths ...string) (*Decoder, error) {
|
||||
modules := yang.NewModules()
|
||||
modules.ParseOptions.IgnoreSubmoduleCircularDependencies = true
|
||||
|
||||
var moduleFiles []string
|
||||
modulePaths := paths
|
||||
unresolved := paths
|
||||
for {
|
||||
var newlyfound []string
|
||||
for _, path := range unresolved {
|
||||
entries, err := os.ReadDir(path)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("reading directory %q failed: %w", path, err)
|
||||
}
|
||||
for _, entry := range entries {
|
||||
info, err := entry.Info()
|
||||
if err != nil {
|
||||
fmt.Printf("Couldn't get info for %q: %v", entry.Name(), err)
|
||||
continue
|
||||
}
|
||||
|
||||
if info.Mode()&os.ModeSymlink != 0 {
|
||||
target, err := filepath.EvalSymlinks(entry.Name())
|
||||
if err != nil {
|
||||
fmt.Printf("Couldn't evaluate symbolic links for %q: %v", entry.Name(), err)
|
||||
continue
|
||||
}
|
||||
info, err = os.Lstat(target)
|
||||
if err != nil {
|
||||
fmt.Printf("Couldn't stat target %v: %v", target, err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
newPath := filepath.Join(path, info.Name())
|
||||
if info.IsDir() {
|
||||
newlyfound = append(newlyfound, newPath)
|
||||
continue
|
||||
}
|
||||
if info.Mode().IsRegular() && filepath.Ext(info.Name()) == ".yang" {
|
||||
moduleFiles = append(moduleFiles, info.Name())
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(newlyfound) == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
modulePaths = append(modulePaths, newlyfound...)
|
||||
unresolved = newlyfound
|
||||
}
|
||||
|
||||
// Add the module paths
|
||||
modules.AddPath(modulePaths...)
|
||||
for _, fn := range moduleFiles {
|
||||
if err := modules.Read(fn); err != nil {
|
||||
fmt.Printf("reading file %q failed: %v\n", fn, err)
|
||||
}
|
||||
}
|
||||
if errs := modules.Process(); len(errs) > 0 {
|
||||
return nil, errors.Join(errs...)
|
||||
}
|
||||
|
||||
// Get all root nodes defined in models with their origin. We require
|
||||
// those nodes to later resolve paths to YANG model leaf nodes...
|
||||
moduleLUT := make(map[string]*yang.Module)
|
||||
moduleRootNodes := make(map[string][]yang.Node)
|
||||
for _, m := range modules.Modules {
|
||||
// Check if we processed the module already
|
||||
if _, found := moduleLUT[m.Name]; found {
|
||||
continue
|
||||
}
|
||||
// Create a module mapping for easily finding modules by name
|
||||
moduleLUT[m.Name] = m
|
||||
|
||||
// Determine the origin defined in the module
|
||||
var prefix string
|
||||
for _, imp := range m.Import {
|
||||
if imp.Name == "openconfig-extensions" {
|
||||
prefix = imp.Name
|
||||
if imp.Prefix != nil {
|
||||
prefix = imp.Prefix.Name
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
var moduleOrigin string
|
||||
if prefix != "" {
|
||||
for _, e := range m.Extensions {
|
||||
if e.Keyword == prefix+":origin" || e.Keyword == "origin" {
|
||||
moduleOrigin = e.Argument
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, u := range m.Uses {
|
||||
root, err := yang.FindNode(m, u.Name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
moduleRootNodes[moduleOrigin] = append(moduleRootNodes[moduleOrigin], root)
|
||||
}
|
||||
}
|
||||
|
||||
return &Decoder{modules: moduleLUT, rootNodes: moduleRootNodes}, nil
|
||||
}
|
||||
|
||||
func (d *Decoder) FindLeaf(name, identifier string) (*yang.Leaf, error) {
|
||||
// Get module name from the element
|
||||
module, found := d.modules[name]
|
||||
if !found {
|
||||
return nil, fmt.Errorf("cannot find module %q", name)
|
||||
}
|
||||
|
||||
for _, grp := range module.Grouping {
|
||||
for _, leaf := range grp.Leaf {
|
||||
if leaf.Name == identifier {
|
||||
return leaf, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil, ErrNotFound
|
||||
}
|
||||
|
||||
func DecodeLeafValue(leaf *yang.Leaf, value interface{}) (interface{}, error) {
|
||||
schema := leaf.Type.YangType
|
||||
|
||||
// Ignore all non-string values as the types seem already converted...
|
||||
s, ok := value.(string)
|
||||
if !ok {
|
||||
return value, nil
|
||||
}
|
||||
|
||||
switch schema.Kind {
|
||||
case yang.Ybinary:
|
||||
// Binary values are encodes as base64 string, so decode the string
|
||||
raw, err := base64.StdEncoding.DecodeString(s)
|
||||
if err != nil {
|
||||
return value, err
|
||||
}
|
||||
|
||||
switch schema.Name {
|
||||
case "ieeefloat32":
|
||||
if len(raw) != 4 {
|
||||
return raw, fmt.Errorf("%w, expected 4 but got %d bytes", ErrInsufficientData, len(raw))
|
||||
}
|
||||
return math.Float32frombits(binary.BigEndian.Uint32(raw)), nil
|
||||
default:
|
||||
return raw, nil
|
||||
}
|
||||
case yang.Yint8:
|
||||
v, err := strconv.ParseInt(s, 10, 8)
|
||||
if err != nil {
|
||||
return value, fmt.Errorf("parsing %s %q failed: %w", yang.TypeKindToName[schema.Kind], s, err)
|
||||
}
|
||||
return int8(v), nil
|
||||
case yang.Yint16:
|
||||
v, err := strconv.ParseInt(s, 10, 16)
|
||||
if err != nil {
|
||||
return value, fmt.Errorf("parsing %s %q failed: %w", yang.TypeKindToName[schema.Kind], s, err)
|
||||
}
|
||||
return int16(v), nil
|
||||
case yang.Yint32:
|
||||
v, err := strconv.ParseInt(s, 10, 32)
|
||||
if err != nil {
|
||||
return value, fmt.Errorf("parsing %s %q failed: %w", yang.TypeKindToName[schema.Kind], s, err)
|
||||
}
|
||||
return int32(v), nil
|
||||
case yang.Yint64:
|
||||
v, err := strconv.ParseInt(s, 10, 64)
|
||||
if err != nil {
|
||||
return value, fmt.Errorf("parsing %s %q failed: %w", yang.TypeKindToName[schema.Kind], s, err)
|
||||
}
|
||||
return v, nil
|
||||
case yang.Yuint8:
|
||||
v, err := strconv.ParseUint(s, 10, 8)
|
||||
if err != nil {
|
||||
return value, fmt.Errorf("parsing %s %q failed: %w", yang.TypeKindToName[schema.Kind], s, err)
|
||||
}
|
||||
return uint8(v), nil
|
||||
case yang.Yuint16:
|
||||
v, err := strconv.ParseUint(s, 10, 16)
|
||||
if err != nil {
|
||||
return value, fmt.Errorf("parsing %s %q failed: %w", yang.TypeKindToName[schema.Kind], s, err)
|
||||
}
|
||||
return uint16(v), nil
|
||||
case yang.Yuint32:
|
||||
v, err := strconv.ParseUint(s, 10, 32)
|
||||
if err != nil {
|
||||
return value, fmt.Errorf("parsing %s %q failed: %w", yang.TypeKindToName[schema.Kind], s, err)
|
||||
}
|
||||
return uint32(v), nil
|
||||
case yang.Yuint64:
|
||||
v, err := strconv.ParseUint(s, 10, 64)
|
||||
if err != nil {
|
||||
return value, fmt.Errorf("parsing %s %q failed: %w", yang.TypeKindToName[schema.Kind], s, err)
|
||||
}
|
||||
return v, nil
|
||||
case yang.Ydecimal64:
|
||||
v, err := strconv.ParseFloat(s, 64)
|
||||
if err != nil {
|
||||
return value, fmt.Errorf("parsing %s %q failed: %w", yang.TypeKindToName[schema.Kind], s, err)
|
||||
}
|
||||
return v, nil
|
||||
}
|
||||
return value, nil
|
||||
}
|
||||
|
||||
func (d *Decoder) DecodeLeafElement(namespace, identifier string, value interface{}) (interface{}, error) {
|
||||
leaf, err := d.FindLeaf(namespace, identifier)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("finding %s failed: %w", identifier, err)
|
||||
}
|
||||
|
||||
return DecodeLeafValue(leaf, value)
|
||||
}
|
||||
|
||||
func (d *Decoder) DecodePathElement(origin, path string, value interface{}) (interface{}, error) {
|
||||
rootNodes, found := d.rootNodes[origin]
|
||||
if !found || len(rootNodes) == 0 {
|
||||
return value, nil
|
||||
}
|
||||
|
||||
for _, root := range rootNodes {
|
||||
node, _ := yang.FindNode(root, path)
|
||||
if node == nil {
|
||||
// The path does not exist in this root node
|
||||
continue
|
||||
}
|
||||
// We do expect a leaf node...
|
||||
if leaf, ok := node.(*yang.Leaf); ok {
|
||||
return DecodeLeafValue(leaf, value)
|
||||
}
|
||||
}
|
||||
|
||||
return value, nil
|
||||
}
|
||||
|
|
@ -129,6 +129,11 @@ details on how to use them.
|
|||
## adds component, component_id & sub_component_id as additional tags
|
||||
# vendor_specific = []
|
||||
|
||||
## YANG model paths for decoding IETF JSON payloads
|
||||
## Model files are loaded recursively from the given directories. Disabled if
|
||||
## no models are specified.
|
||||
# yang_model_paths = []
|
||||
|
||||
## Define additional aliases to map encoding paths to measurement names
|
||||
# [inputs.gnmi.aliases]
|
||||
# ifcounters = "openconfig:/interfaces/interface/state/counters"
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ import (
|
|||
"github.com/influxdata/telegraf/config"
|
||||
"github.com/influxdata/telegraf/internal/choice"
|
||||
internaltls "github.com/influxdata/telegraf/plugins/common/tls"
|
||||
"github.com/influxdata/telegraf/plugins/common/yangmodel"
|
||||
"github.com/influxdata/telegraf/plugins/inputs"
|
||||
)
|
||||
|
||||
|
|
@ -62,11 +63,13 @@ type GNMI struct {
|
|||
EnableTLS bool `toml:"enable_tls" deprecated:"1.27.0;use 'tls_enable' instead"`
|
||||
KeepaliveTime config.Duration `toml:"keepalive_time"`
|
||||
KeepaliveTimeout config.Duration `toml:"keepalive_timeout"`
|
||||
YangModelPaths []string `toml:"yang_model_paths"`
|
||||
Log telegraf.Logger `toml:"-"`
|
||||
internaltls.ClientConfig
|
||||
|
||||
// Internal state
|
||||
internalAliases map[*pathInfo]string
|
||||
decoder *yangmodel.Decoder
|
||||
cancel context.CancelFunc
|
||||
wg sync.WaitGroup
|
||||
}
|
||||
|
|
@ -219,6 +222,15 @@ func (c *GNMI) Init() error {
|
|||
return err
|
||||
}
|
||||
|
||||
// Load the YANG models if specified by the user
|
||||
if len(c.YangModelPaths) > 0 {
|
||||
decoder, err := yangmodel.NewDecoder(c.YangModelPaths...)
|
||||
if err != nil {
|
||||
return fmt.Errorf("creating YANG model decoder failed: %w", err)
|
||||
}
|
||||
c.decoder = decoder
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
@ -275,6 +287,7 @@ func (c *GNMI) Start(acc telegraf.Accumulator) error {
|
|||
trimSlash: c.TrimFieldNames,
|
||||
tagPathPrefix: c.PrefixTagKeyWithPath,
|
||||
guessPathStrategy: c.GuessPathStrategy,
|
||||
decoder: c.decoder,
|
||||
log: c.Log,
|
||||
ClientParameters: keepalive.ClientParameters{
|
||||
Time: time.Duration(c.KeepaliveTime),
|
||||
|
|
|
|||
|
|
@ -1210,7 +1210,7 @@ func TestCases(t *testing.T) {
|
|||
require.Eventually(t,
|
||||
func() bool {
|
||||
return acc.NMetrics() >= uint64(len(expected))
|
||||
}, 1*time.Second, 100*time.Millisecond)
|
||||
}, 15*time.Second, 100*time.Millisecond)
|
||||
plugin.Stop()
|
||||
grpcServer.Stop()
|
||||
wg.Wait()
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ import (
|
|||
"github.com/influxdata/telegraf"
|
||||
"github.com/influxdata/telegraf/internal/choice"
|
||||
"github.com/influxdata/telegraf/metric"
|
||||
"github.com/influxdata/telegraf/plugins/common/yangmodel"
|
||||
jnprHeader "github.com/influxdata/telegraf/plugins/inputs/gnmi/extensions/jnpr_gnmi_extention"
|
||||
"github.com/influxdata/telegraf/selfstat"
|
||||
)
|
||||
|
|
@ -44,6 +45,7 @@ type handler struct {
|
|||
trimSlash bool
|
||||
tagPathPrefix bool
|
||||
guessPathStrategy string
|
||||
decoder *yangmodel.Decoder
|
||||
log telegraf.Logger
|
||||
keepalive.ClientParameters
|
||||
}
|
||||
|
|
@ -172,7 +174,11 @@ func (h *handler) handleSubscribeResponseUpdate(acc telegraf.Accumulator, respon
|
|||
var valueFields []updateField
|
||||
for _, update := range response.Update.Update {
|
||||
fullPath := prefix.append(update.Path)
|
||||
fields, err := newFieldsFromUpdate(fullPath, update)
|
||||
if update.Path.Origin != "" {
|
||||
fullPath.origin = update.Path.Origin
|
||||
}
|
||||
|
||||
fields, err := h.newFieldsFromUpdate(fullPath, update)
|
||||
if err != nil {
|
||||
h.log.Errorf("Processing update %v failed: %v", update, err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -279,6 +279,14 @@ func (pi *pathInfo) String() string {
|
|||
return out
|
||||
}
|
||||
|
||||
func (pi *pathInfo) Path() (origin, path string) {
|
||||
if len(pi.segments) == 0 {
|
||||
return pi.origin, "/"
|
||||
}
|
||||
|
||||
return pi.origin, "/" + strings.Join(pi.segments, "/")
|
||||
}
|
||||
|
||||
func (pi *pathInfo) Tags(pathPrefix bool) map[string]string {
|
||||
tags := make(map[string]string, len(pi.keyValues))
|
||||
for _, s := range pi.keyValues {
|
||||
|
|
|
|||
|
|
@ -82,6 +82,11 @@
|
|||
## adds component, component_id & sub_component_id as additional tags
|
||||
# vendor_specific = []
|
||||
|
||||
## YANG model paths for decoding IETF JSON payloads
|
||||
## Model files are loaded recursively from the given directories. Disabled if
|
||||
## no models are specified.
|
||||
# yang_model_paths = []
|
||||
|
||||
## Define additional aliases to map encoding paths to measurement names
|
||||
# [inputs.gnmi.aliases]
|
||||
# ifcounters = "openconfig:/interfaces/interface/state/counters"
|
||||
|
|
|
|||
|
|
@ -61,6 +61,11 @@
|
|||
## adds component, component_id & sub_component_id as additional tags
|
||||
# vendor_specific = []
|
||||
|
||||
## YANG model paths for decoding IETF JSON payloads
|
||||
## Model files are loaded recursively from the given directories. Disabled if
|
||||
## no models are specified.
|
||||
# yang_model_paths = []
|
||||
|
||||
## Define additional aliases to map encoding paths to measurement names
|
||||
# [inputs.gnmi.aliases]
|
||||
# ifcounters = "openconfig:/interfaces/interface/state/counters"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,5 @@
|
|||
psu,name=PowerSupply1/A,path=openconfig:/components/component/power-supply/state,source=127.0.0.1 openconfig_platform_psu:capacity=715,openconfig_platform_psu:enabled=true,openconfig_platform_psu:input_current=0.47099998593330383,openconfig_platform_psu:input_voltage=208.5,openconfig_platform_psu:output_current=1.2029999494552612,openconfig_platform_psu:output_power=68.625,openconfig_platform_psu:output_voltage=56.367000579833984 1711178737105194000
|
||||
psu,name=PowerSupply1/B,path=openconfig:/components/component/power-supply/state,source=127.0.0.1 openconfig_platform_psu:capacity=715,openconfig_platform_psu:enabled=true,openconfig_platform_psu:input_current=0.3930000066757202,openconfig_platform_psu:input_voltage=209.75,openconfig_platform_psu:output_current=0.9380000233650208,openconfig_platform_psu:output_power=51.875,openconfig_platform_psu:output_voltage=56.367000579833984 1711178737105194000
|
||||
temp,name=InletTempSensor1,path=openconfig:/components/component/state/temperature,source=127.0.0.1 alarm_severity="openconfig-alarm-types:MINOR",alarm_status=false,alarm_threshold=0,avg=24.000000,instant=35.000000,interval=180000000000u,max=36.000000,min=0.000000 1715838159171548000
|
||||
temp,name=OutletTempSensor1,path=openconfig:/components/component/state/temperature,source=127.0.0.1 alarm_severity="openconfig-alarm-types:MINOR",alarm_status=false,alarm_threshold=0,avg=29.000000,instant=44.000000,interval=180000000000u,max=44.000000,min=0.000000 1715838159171548000
|
||||
temp,name=HotSpotTempSensor1,path=openconfig:/components/component/state/temperature,source=127.0.0.1 alarm_severity="openconfig-alarm-types:MINOR",alarm_status=false,alarm_threshold=0,avg=39.000000,instant=58.000000,interval=180000000000u,max=59.000000,min=0.000000 1715838159171548000
|
||||
|
|
@ -0,0 +1,202 @@
|
|||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "{}"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright {yyyy} {name of copyright owner}
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
<!-- markdownlint-disable -->
|
||||
Files extracted from https://github.com/openconfig/public
|
||||
|
|
@ -0,0 +1,109 @@
|
|||
module openconfig-alarm-types {
|
||||
|
||||
yang-version "1";
|
||||
|
||||
// namespace
|
||||
namespace "http://openconfig.net/yang/alarms/types";
|
||||
|
||||
prefix "oc-alarm-types";
|
||||
|
||||
// import some basic types
|
||||
import openconfig-extensions { prefix oc-ext; }
|
||||
|
||||
// meta
|
||||
organization "OpenConfig working group";
|
||||
|
||||
contact
|
||||
"OpenConfig working group
|
||||
www.openconfig.net";
|
||||
|
||||
description
|
||||
"This module defines operational state data related to alarms
|
||||
that the device is reporting.
|
||||
|
||||
This model reuses some data items defined in the draft IETF
|
||||
YANG Alarm Module:
|
||||
https://tools.ietf.org/html/draft-vallin-netmod-alarm-module-02
|
||||
|
||||
Portions of this code were derived from the draft IETF YANG Alarm
|
||||
Module. Please reproduce this note if possible.
|
||||
|
||||
IETF code is subject to the following copyright and license:
|
||||
Copyright (c) IETF Trust and the persons identified as authors of
|
||||
the code.
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, is permitted pursuant to, and subject to the license
|
||||
terms contained in, the Simplified BSD License set forth in
|
||||
Section 4.c of the IETF Trust's Legal Provisions Relating
|
||||
to IETF Documents (http://trustee.ietf.org/license-info).";
|
||||
|
||||
oc-ext:openconfig-version "0.2.1";
|
||||
|
||||
// OpenConfig specific extensions for module metadata.
|
||||
oc-ext:regexp-posix;
|
||||
oc-ext:catalog-organization "openconfig";
|
||||
oc-ext:origin "openconfig";
|
||||
|
||||
// identity statements
|
||||
|
||||
identity OPENCONFIG_ALARM_SEVERITY {
|
||||
description
|
||||
"Base identity for alarm severity profiles. Derived
|
||||
identities are based on contents of the draft
|
||||
IETF YANG Alarm Module";
|
||||
reference
|
||||
"IETF YANG Alarm Module: Draft - typedef severity
|
||||
https://tools.ietf.org/html/draft-vallin-netmod-alarm-module-02";
|
||||
|
||||
}
|
||||
|
||||
identity UNKNOWN {
|
||||
base OPENCONFIG_ALARM_SEVERITY;
|
||||
description
|
||||
"Indicates that the severity level could not be determined.
|
||||
This level SHOULD be avoided.";
|
||||
}
|
||||
|
||||
identity MINOR {
|
||||
base OPENCONFIG_ALARM_SEVERITY;
|
||||
description
|
||||
"Indicates the existence of a non-service affecting fault
|
||||
condition and that corrective action should be taken in
|
||||
order to prevent a more serious (for example, service
|
||||
affecting) fault. Such a severity can be reported, for
|
||||
example, when the detected alarm condition is not currently
|
||||
degrading the capacity of the resource";
|
||||
}
|
||||
|
||||
identity WARNING {
|
||||
base OPENCONFIG_ALARM_SEVERITY;
|
||||
description
|
||||
"Indicates the detection of a potential or impending service
|
||||
affecting fault, before any significant effects have been felt.
|
||||
Action should be taken to further diagnose (if necessary) and
|
||||
correct the problem in order to prevent it from becoming a more
|
||||
serious service affecting fault.";
|
||||
}
|
||||
|
||||
identity MAJOR {
|
||||
base OPENCONFIG_ALARM_SEVERITY;
|
||||
description
|
||||
"Indicates that a service affecting condition has developed
|
||||
and an urgent corrective action is required. Such a severity
|
||||
can be reported, for example, when there is a severe
|
||||
degradation in the capability of the resource and its full
|
||||
capability must be restored.";
|
||||
}
|
||||
|
||||
identity CRITICAL {
|
||||
base OPENCONFIG_ALARM_SEVERITY;
|
||||
description
|
||||
"Indicates that a service affecting condition has occurred
|
||||
and an immediate corrective action is required. Such a
|
||||
severity can be reported, for example, when a resource becomes
|
||||
totally out of service and its capability must be restored.";
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,110 @@
|
|||
module openconfig-extensions {
|
||||
|
||||
yang-version "1";
|
||||
|
||||
// namespace
|
||||
namespace "http://openconfig.net/yang/openconfig-ext";
|
||||
|
||||
prefix "oc-ext";
|
||||
|
||||
// meta
|
||||
organization "OpenConfig working group";
|
||||
|
||||
contact
|
||||
"OpenConfig working group
|
||||
www.openconfig.net";
|
||||
|
||||
description
|
||||
"This module provides extensions to the YANG language to allow
|
||||
OpenConfig specific functionality and meta-data to be defined.";
|
||||
|
||||
oc-ext:openconfig-version "0.5.1";
|
||||
|
||||
// extension statements
|
||||
extension openconfig-version {
|
||||
argument "semver" {
|
||||
yin-element false;
|
||||
}
|
||||
description
|
||||
"The OpenConfig version number for the module. This is
|
||||
expressed as a semantic version number of the form:
|
||||
x.y.z
|
||||
where:
|
||||
* x corresponds to the major version,
|
||||
* y corresponds to a minor version,
|
||||
* z corresponds to a patch version.
|
||||
This version corresponds to the model file within which it is
|
||||
defined, and does not cover the whole set of OpenConfig models.
|
||||
|
||||
Individual YANG modules are versioned independently -- the
|
||||
semantic version is generally incremented only when there is a
|
||||
change in the corresponding file. Submodules should always
|
||||
have the same semantic version as their parent modules.
|
||||
|
||||
A major version number of 0 indicates that this model is still
|
||||
in development (whether within OpenConfig or with industry
|
||||
partners), and is potentially subject to change.
|
||||
|
||||
Following a release of major version 1, all modules will
|
||||
increment major revision number where backwards incompatible
|
||||
changes to the model are made.
|
||||
|
||||
The minor version is changed when features are added to the
|
||||
model that do not impact current clients use of the model.
|
||||
|
||||
The patch-level version is incremented when non-feature changes
|
||||
(such as bugfixes or clarifications to human-readable
|
||||
descriptions that do not impact model functionality) are made
|
||||
that maintain backwards compatibility.
|
||||
|
||||
The version number is stored in the module meta-data.";
|
||||
}
|
||||
|
||||
extension regexp-posix {
|
||||
description
|
||||
"This extension indicates that the regular expressions included
|
||||
within the YANG module specified are conformant with the POSIX
|
||||
regular expression format rather than the W3C standard that is
|
||||
specified by RFC6020 and RFC7950.";
|
||||
}
|
||||
|
||||
extension operational {
|
||||
description
|
||||
"The operational annotation is specified in the context of a
|
||||
grouping, leaf, or leaf-list within a YANG module. It indicates
|
||||
that the nodes within the context are derived state on the device.
|
||||
|
||||
OpenConfig data models divide nodes into the following three categories:
|
||||
|
||||
- intended configuration - these are leaves within a container named
|
||||
'config', and are the writable configuration of a target.
|
||||
- applied configuration - these are leaves within a container named
|
||||
'state' and are the currently running value of the intended configuration.
|
||||
- derived state - these are the values within the 'state' container which
|
||||
are not part of the applied configuration of the device. Typically, they
|
||||
represent state values reflecting underlying operational counters, or
|
||||
protocol statuses.";
|
||||
}
|
||||
|
||||
extension catalog-organization {
|
||||
argument "org" {
|
||||
yin-element false;
|
||||
}
|
||||
description
|
||||
"This extension specifies the organization name that should be used within
|
||||
the module catalogue on the device for the specified YANG module. It stores
|
||||
a pithy string where the YANG organization statement may contain more
|
||||
details.";
|
||||
}
|
||||
|
||||
extension origin {
|
||||
argument "origin" {
|
||||
yin-element false;
|
||||
}
|
||||
description
|
||||
"This extension specifies the name of the origin that the YANG module
|
||||
falls within. This allows multiple overlapping schema trees to be used
|
||||
on a single network element without requiring module based prefixing
|
||||
of paths.";
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,92 @@
|
|||
module openconfig-platform-psu {
|
||||
|
||||
yang-version "1";
|
||||
|
||||
// namespace
|
||||
namespace "http://openconfig.net/yang/platform/psu";
|
||||
|
||||
prefix "oc-platform-psu";
|
||||
|
||||
// import some basic types
|
||||
import openconfig-extensions { prefix oc-ext; }
|
||||
import openconfig-types { prefix oc-types; }
|
||||
|
||||
|
||||
// meta
|
||||
organization "OpenConfig working group";
|
||||
|
||||
contact
|
||||
"OpenConfig working group
|
||||
www.openconfig.net";
|
||||
|
||||
description
|
||||
"This module defines a schema for power supply components in
|
||||
the OpenConfig platform model.";
|
||||
|
||||
oc-ext:openconfig-version "0.2.1";
|
||||
|
||||
// OpenConfig specific extensions for module metadata.
|
||||
oc-ext:regexp-posix;
|
||||
oc-ext:catalog-organization "openconfig";
|
||||
oc-ext:origin "openconfig";
|
||||
|
||||
grouping psu-config {
|
||||
description
|
||||
"Configuration data for power supply components";
|
||||
|
||||
leaf enabled {
|
||||
type boolean;
|
||||
default true;
|
||||
description
|
||||
"Adminsitrative control on the on/off state of the power
|
||||
supply unit.";
|
||||
}
|
||||
}
|
||||
|
||||
grouping psu-state {
|
||||
description
|
||||
"Operational state data for power supply components";
|
||||
|
||||
leaf capacity {
|
||||
type oc-types:ieeefloat32;
|
||||
units watts;
|
||||
description
|
||||
"Maximum power capacity of the power supply.";
|
||||
}
|
||||
|
||||
leaf input-current {
|
||||
type oc-types:ieeefloat32;
|
||||
units amps;
|
||||
description
|
||||
"The input current draw of the power supply.";
|
||||
}
|
||||
|
||||
leaf input-voltage {
|
||||
type oc-types:ieeefloat32;
|
||||
units volts;
|
||||
description
|
||||
"Input voltage to the power supply.";
|
||||
}
|
||||
|
||||
leaf output-current {
|
||||
type oc-types:ieeefloat32;
|
||||
units amps;
|
||||
description
|
||||
"The output current supplied by the power supply.";
|
||||
}
|
||||
|
||||
leaf output-voltage {
|
||||
type oc-types:ieeefloat32;
|
||||
units volts;
|
||||
description
|
||||
"Output voltage supplied by the power supply.";
|
||||
}
|
||||
|
||||
leaf output-power {
|
||||
type oc-types:ieeefloat32;
|
||||
units watts;
|
||||
description
|
||||
"Output power supplied by the power supply.";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,80 @@
|
|||
module openconfig-platform-types {
|
||||
|
||||
yang-version "1";
|
||||
|
||||
// namespace
|
||||
namespace "http://openconfig.net/yang/platform-types";
|
||||
|
||||
prefix "oc-platform-types";
|
||||
|
||||
import openconfig-types { prefix oc-types; }
|
||||
import openconfig-extensions { prefix oc-ext; }
|
||||
|
||||
// meta
|
||||
organization
|
||||
"OpenConfig working group";
|
||||
|
||||
contact
|
||||
"OpenConfig working group
|
||||
www.openconfig.net";
|
||||
|
||||
description
|
||||
"This module defines data types (e.g., YANG identities)
|
||||
to support the OpenConfig component inventory model.";
|
||||
|
||||
oc-ext:openconfig-version "1.6.0";
|
||||
|
||||
// OpenConfig specific extensions for module metadata.
|
||||
oc-ext:regexp-posix;
|
||||
oc-ext:catalog-organization "openconfig";
|
||||
oc-ext:origin "openconfig";
|
||||
|
||||
// grouping statements
|
||||
grouping avg-min-max-instant-stats-precision1-celsius {
|
||||
description
|
||||
"Common grouping for recording temperature values in
|
||||
Celsius with 1 decimal precision. Values include the
|
||||
instantaneous, average, minimum, and maximum statistics";
|
||||
|
||||
leaf instant {
|
||||
type decimal64 {
|
||||
fraction-digits 1;
|
||||
}
|
||||
units celsius;
|
||||
description
|
||||
"The instantaneous value of the statistic.";
|
||||
}
|
||||
|
||||
leaf avg {
|
||||
type decimal64 {
|
||||
fraction-digits 1;
|
||||
}
|
||||
units celsius;
|
||||
description
|
||||
"The arithmetic mean value of the statistic over the
|
||||
sampling period.";
|
||||
}
|
||||
|
||||
leaf min {
|
||||
type decimal64 {
|
||||
fraction-digits 1;
|
||||
}
|
||||
units celsius;
|
||||
description
|
||||
"The minimum value of the statistic over the sampling
|
||||
period";
|
||||
}
|
||||
|
||||
leaf max {
|
||||
type decimal64 {
|
||||
fraction-digits 1;
|
||||
}
|
||||
units celsius;
|
||||
description
|
||||
"The maximum value of the statistic over the sampling
|
||||
period";
|
||||
}
|
||||
|
||||
uses oc-types:stat-interval-state;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,162 @@
|
|||
module openconfig-platform {
|
||||
|
||||
yang-version "1";
|
||||
|
||||
// namespace
|
||||
namespace "http://openconfig.net/yang/platform";
|
||||
|
||||
prefix "oc-platform";
|
||||
|
||||
import openconfig-platform-types { prefix oc-platform-types; }
|
||||
import openconfig-extensions { prefix oc-ext; }
|
||||
import openconfig-alarm-types { prefix oc-alarm-types; }
|
||||
|
||||
// meta
|
||||
organization "OpenConfig working group";
|
||||
|
||||
contact
|
||||
"OpenConfig working group
|
||||
www.openconfig.net";
|
||||
|
||||
description
|
||||
"This module defines a data model for representing a system
|
||||
component inventory, which can include hardware or software
|
||||
elements arranged in an arbitrary structure. The primary
|
||||
relationship supported by the model is containment, e.g.,
|
||||
components containing subcomponents.
|
||||
|
||||
It is expected that this model reflects every field replacable
|
||||
unit on the device at a minimum (i.e., additional information
|
||||
may be supplied about non-replacable components).
|
||||
|
||||
Every element in the inventory is termed a 'component' with each
|
||||
component expected to have a unique name and type, and optionally
|
||||
a unique system-assigned identifier and FRU number. The
|
||||
uniqueness is guaranteed by the system within the device.
|
||||
|
||||
Components may have properties defined by the system that are
|
||||
modeled as a list of key-value pairs. These may or may not be
|
||||
user-configurable. The model provides a flag for the system
|
||||
to optionally indicate which properties are user configurable.
|
||||
|
||||
Each component also has a list of 'subcomponents' which are
|
||||
references to other components. Appearance in a list of
|
||||
subcomponents indicates a containment relationship as described
|
||||
above. For example, a linecard component may have a list of
|
||||
references to port components that reside on the linecard.
|
||||
|
||||
This schema is generic to allow devices to express their own
|
||||
platform-specific structure. It may be augmented by additional
|
||||
component type-specific schemas that provide a common structure
|
||||
for well-known component types. In these cases, the system is
|
||||
expected to populate the common component schema, and may
|
||||
optionally also represent the component and its properties in the
|
||||
generic structure.
|
||||
|
||||
The properties for each component may include dynamic values,
|
||||
e.g., in the 'state' part of the schema. For example, a CPU
|
||||
component may report its utilization, temperature, or other
|
||||
physical properties. The intent is to capture all platform-
|
||||
specific physical data in one location, including inventory
|
||||
(presence or absence of a component) and state (physical
|
||||
attributes or status).";
|
||||
|
||||
oc-ext:openconfig-version "0.24.0";
|
||||
|
||||
// OpenConfig specific extensions for module metadata.
|
||||
oc-ext:regexp-posix;
|
||||
oc-ext:catalog-organization "openconfig";
|
||||
oc-ext:origin "openconfig";
|
||||
|
||||
// grouping statements
|
||||
|
||||
grouping platform-component-temp-alarm-state {
|
||||
description
|
||||
"Temperature alarm data for platform components";
|
||||
|
||||
// TODO(aashaikh): consider if these leaves could be in a
|
||||
// reusable grouping (not temperature-specific); threshold
|
||||
// may always need to be units specific.
|
||||
|
||||
leaf alarm-status {
|
||||
type boolean;
|
||||
description
|
||||
"A value of true indicates the alarm has been raised or
|
||||
asserted. The value should be false when the alarm is
|
||||
cleared.";
|
||||
}
|
||||
|
||||
leaf alarm-threshold {
|
||||
type uint32;
|
||||
description
|
||||
"The threshold value that was crossed for this alarm.";
|
||||
}
|
||||
|
||||
leaf alarm-severity {
|
||||
type identityref {
|
||||
base oc-alarm-types:OPENCONFIG_ALARM_SEVERITY;
|
||||
}
|
||||
description
|
||||
"The severity of the current alarm.";
|
||||
}
|
||||
}
|
||||
|
||||
grouping platform-component-temp-state {
|
||||
description
|
||||
"Temperature state data for device components";
|
||||
|
||||
container temperature {
|
||||
description
|
||||
"Temperature in degrees Celsius of the component. Values include
|
||||
the instantaneous, average, minimum, and maximum statistics. If
|
||||
avg/min/max statistics are not supported, the target is expected
|
||||
to just supply the instant value";
|
||||
|
||||
uses oc-platform-types:avg-min-max-instant-stats-precision1-celsius;
|
||||
uses platform-component-temp-alarm-state;
|
||||
}
|
||||
}
|
||||
|
||||
grouping platform-component-top {
|
||||
description
|
||||
"Top-level grouping for components in the device inventory";
|
||||
|
||||
container components {
|
||||
description
|
||||
"Enclosing container for the components in the system.";
|
||||
|
||||
list component {
|
||||
key "name";
|
||||
description
|
||||
"List of components, keyed by component name.";
|
||||
|
||||
leaf name {
|
||||
type leafref {
|
||||
path "../config/name";
|
||||
}
|
||||
description
|
||||
"References the component name";
|
||||
}
|
||||
|
||||
container state {
|
||||
config false;
|
||||
|
||||
description
|
||||
"Operational state data for each component";
|
||||
|
||||
uses platform-component-temp-state;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// data definition statements
|
||||
|
||||
uses platform-component-top;
|
||||
|
||||
|
||||
// augments
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
module openconfig-types {
|
||||
yang-version "1";
|
||||
|
||||
namespace "http://openconfig.net/yang/openconfig-types";
|
||||
|
||||
prefix "oc-types";
|
||||
|
||||
// import statements
|
||||
import openconfig-extensions { prefix oc-ext; }
|
||||
|
||||
// meta
|
||||
organization
|
||||
"OpenConfig working group";
|
||||
|
||||
contact
|
||||
"OpenConfig working group
|
||||
netopenconfig@googlegroups.com";
|
||||
|
||||
description
|
||||
"This module contains a set of general type definitions that
|
||||
are used across OpenConfig models. It can be imported by modules
|
||||
that make use of these types.";
|
||||
|
||||
oc-ext:openconfig-version "1.0.0";
|
||||
|
||||
// OpenConfig specific extensions for module metadata.
|
||||
oc-ext:regexp-posix;
|
||||
oc-ext:catalog-organization "openconfig";
|
||||
oc-ext:origin "openconfig";
|
||||
|
||||
typedef stat-interval {
|
||||
type uint64;
|
||||
units nanoseconds;
|
||||
description
|
||||
"A time interval over which a set of statistics is computed.
|
||||
A common usage is to report the interval over which
|
||||
avg/min/max stats are computed and reported.";
|
||||
}
|
||||
|
||||
grouping stat-interval-state {
|
||||
description
|
||||
"Reusable leaf definition for stats computation interval";
|
||||
|
||||
leaf interval {
|
||||
type oc-types:stat-interval;
|
||||
description
|
||||
"If supported by the system, this reports the time interval
|
||||
over which the min/max/average statistics are computed by
|
||||
the system.";
|
||||
}
|
||||
}
|
||||
|
||||
typedef ieeefloat32 {
|
||||
type binary {
|
||||
length "4";
|
||||
}
|
||||
description
|
||||
"An IEEE 32-bit floating point number. The format of this number
|
||||
is of the form:
|
||||
1-bit sign
|
||||
8-bit exponent
|
||||
23-bit fraction
|
||||
The floating point value is calculated using:
|
||||
(-1)**S * 2**(Exponent-127) * (1+Fraction)";
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,141 @@
|
|||
[
|
||||
{
|
||||
"update": {
|
||||
"timestamp": "1711178737105194000",
|
||||
"update": [
|
||||
{
|
||||
"path": {
|
||||
"origin": "openconfig",
|
||||
"elem": [
|
||||
{
|
||||
"name": "components"
|
||||
},
|
||||
{
|
||||
"name": "component",
|
||||
"key": {
|
||||
"name": "PowerSupply1/A"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "power-supply"
|
||||
},
|
||||
{
|
||||
"name": "state"
|
||||
}
|
||||
]
|
||||
},
|
||||
"val": {
|
||||
"jsonIetfVal": "eyJvcGVuY29uZmlnLXBsYXRmb3JtLXBzdTplbmFibGVkIjp0cnVlLCJvcGVuY29uZmlnLXBsYXRmb3JtLXBzdTpjYXBhY2l0eSI6IlJETEFBQT09Iiwib3BlbmNvbmZpZy1wbGF0Zm9ybS1wc3U6aW5wdXQtY3VycmVudCI6IlB2RW02UT09Iiwib3BlbmNvbmZpZy1wbGF0Zm9ybS1wc3U6aW5wdXQtdm9sdGFnZSI6IlExQ0FBQT09Iiwib3BlbmNvbmZpZy1wbGF0Zm9ybS1wc3U6b3V0cHV0LWN1cnJlbnQiOiJQNW43NXc9PSIsIm9wZW5jb25maWctcGxhdGZvcm0tcHN1Om91dHB1dC12b2x0YWdlIjoiUW1GM3p3PT0iLCJvcGVuY29uZmlnLXBsYXRmb3JtLXBzdTpvdXRwdXQtcG93ZXIiOiJRb2xBQUE9PSJ9"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": {
|
||||
"origin": "openconfig",
|
||||
"elem": [
|
||||
{
|
||||
"name": "components"
|
||||
},
|
||||
{
|
||||
"name": "component",
|
||||
"key": {
|
||||
"name": "PowerSupply1/B"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "power-supply"
|
||||
},
|
||||
{
|
||||
"name": "state"
|
||||
}
|
||||
]
|
||||
},
|
||||
"val": {
|
||||
"jsonIetfVal": "eyJvcGVuY29uZmlnLXBsYXRmb3JtLXBzdTplbmFibGVkIjp0cnVlLCJvcGVuY29uZmlnLXBsYXRmb3JtLXBzdTpjYXBhY2l0eSI6IlJETEFBQT09Iiwib3BlbmNvbmZpZy1wbGF0Zm9ybS1wc3U6aW5wdXQtY3VycmVudCI6IlBzazNUQT09Iiwib3BlbmNvbmZpZy1wbGF0Zm9ybS1wc3U6aW5wdXQtdm9sdGFnZSI6IlExSEFBQT09Iiwib3BlbmNvbmZpZy1wbGF0Zm9ybS1wc3U6b3V0cHV0LWN1cnJlbnQiOiJQM0FneFE9PSIsIm9wZW5jb25maWctcGxhdGZvcm0tcHN1Om91dHB1dC12b2x0YWdlIjoiUW1GM3p3PT0iLCJvcGVuY29uZmlnLXBsYXRmb3JtLXBzdTpvdXRwdXQtcG93ZXIiOiJRaytBQUE9PSJ9"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"update": {
|
||||
"timestamp": "1715838159171548000",
|
||||
"update": [
|
||||
{
|
||||
"path": {
|
||||
"origin": "openconfig",
|
||||
"elem": [
|
||||
{
|
||||
"name": "components"
|
||||
},
|
||||
{
|
||||
"name": "component",
|
||||
"key": {
|
||||
"name": "InletTempSensor1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "state"
|
||||
},
|
||||
{
|
||||
"name": "temperature"
|
||||
}
|
||||
]
|
||||
},
|
||||
"val": {
|
||||
"jsonIetfVal": "eyJpbnN0YW50IjoiMzUuMDAwMDAwIiwiYXZnIjoiMjQuMDAwMDAwIiwibWluIjoiMC4wMDAwMDAiLCJtYXgiOiIzNi4wMDAwMDAiLCJpbnRlcnZhbCI6IjE4MDAwMDAwMDAwMCIsImFsYXJtLXN0YXR1cyI6ZmFsc2UsImFsYXJtLXRocmVzaG9sZCI6MCwiYWxhcm0tc2V2ZXJpdHkiOiJvcGVuY29uZmlnLWFsYXJtLXR5cGVzOk1JTk9SIn0="
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": {
|
||||
"origin": "openconfig",
|
||||
"elem": [
|
||||
{
|
||||
"name": "components"
|
||||
},
|
||||
{
|
||||
"name": "component",
|
||||
"key": {
|
||||
"name": "OutletTempSensor1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "state"
|
||||
},
|
||||
{
|
||||
"name": "temperature"
|
||||
}
|
||||
]
|
||||
},
|
||||
"val": {
|
||||
"jsonIetfVal": "eyJpbnN0YW50IjoiNDQuMDAwMDAwIiwiYXZnIjoiMjkuMDAwMDAwIiwibWluIjoiMC4wMDAwMDAiLCJtYXgiOiI0NC4wMDAwMDAiLCJpbnRlcnZhbCI6IjE4MDAwMDAwMDAwMCIsImFsYXJtLXN0YXR1cyI6ZmFsc2UsImFsYXJtLXRocmVzaG9sZCI6MCwiYWxhcm0tc2V2ZXJpdHkiOiJvcGVuY29uZmlnLWFsYXJtLXR5cGVzOk1JTk9SIn0="
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": {
|
||||
"origin": "openconfig",
|
||||
"elem": [
|
||||
{
|
||||
"name": "components"
|
||||
},
|
||||
{
|
||||
"name": "component",
|
||||
"key": {
|
||||
"name": "HotSpotTempSensor1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "state"
|
||||
},
|
||||
{
|
||||
"name": "temperature"
|
||||
}
|
||||
]
|
||||
},
|
||||
"val": {
|
||||
"jsonIetfVal": "eyJpbnN0YW50IjoiNTguMDAwMDAwIiwiYXZnIjoiMzkuMDAwMDAwIiwibWluIjoiMC4wMDAwMDAiLCJtYXgiOiI1OS4wMDAwMDAiLCJpbnRlcnZhbCI6IjE4MDAwMDAwMDAwMCIsImFsYXJtLXN0YXR1cyI6ZmFsc2UsImFsYXJtLXRocmVzaG9sZCI6MCwiYWxhcm0tc2V2ZXJpdHkiOiJvcGVuY29uZmlnLWFsYXJtLXR5cGVzOk1JTk9SIn0="
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
[[inputs.gnmi]]
|
||||
addresses = ["dummy"]
|
||||
path_guessing_strategy = "subscription"
|
||||
yang_model_paths = ["testcases/issue_15046/models"]
|
||||
|
||||
[[inputs.gnmi.subscription]]
|
||||
name = "psu"
|
||||
origin = "openconfig"
|
||||
path = "/components/component/power-supply/state"
|
||||
subscription_mode = "sample"
|
||||
sample_interval = "60s"
|
||||
|
||||
[[inputs.gnmi.subscription]]
|
||||
name = "temp"
|
||||
origin = "openconfig"
|
||||
path = "/components/component/state/temperature"
|
||||
subscription_mode = "sample"
|
||||
sample_interval = "60s"
|
||||
|
|
@ -4,17 +4,23 @@ import (
|
|||
"encoding/json"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
gnmiLib "github.com/openconfig/gnmi/proto/gnmi"
|
||||
gnmiValue "github.com/openconfig/gnmi/value"
|
||||
)
|
||||
|
||||
type keyValuePair struct {
|
||||
key []string
|
||||
value interface{}
|
||||
}
|
||||
|
||||
type updateField struct {
|
||||
path *pathInfo
|
||||
value interface{}
|
||||
}
|
||||
|
||||
func newFieldsFromUpdate(path *pathInfo, update *gnmiLib.Update) ([]updateField, error) {
|
||||
func (h *handler) newFieldsFromUpdate(path *pathInfo, update *gnmiLib.Update) ([]updateField, error) {
|
||||
if update.Val == nil || update.Val.Value == nil {
|
||||
return []updateField{{path: path}}, nil
|
||||
}
|
||||
|
|
@ -26,7 +32,7 @@ func newFieldsFromUpdate(path *pathInfo, update *gnmiLib.Update) ([]updateField,
|
|||
case *gnmiLib.TypedValue_JsonVal: // requires special path handling
|
||||
return processJSON(path, v.JsonVal)
|
||||
case *gnmiLib.TypedValue_JsonIetfVal: // requires special path handling
|
||||
return processJSON(path, v.JsonIetfVal)
|
||||
return h.processJSONIETF(path, v.JsonIetfVal)
|
||||
}
|
||||
|
||||
// Convert the protobuf "oneof" data to a Golang type.
|
||||
|
|
@ -48,45 +54,105 @@ func processJSON(path *pathInfo, data []byte) ([]updateField, error) {
|
|||
|
||||
// Create an update-field with the complete path for all entries
|
||||
fields := make([]updateField, 0, len(entries))
|
||||
for key, v := range entries {
|
||||
for _, entry := range entries {
|
||||
fields = append(fields, updateField{
|
||||
path: path.appendSegments(key),
|
||||
value: v,
|
||||
path: path.appendSegments(entry.key...),
|
||||
value: entry.value,
|
||||
})
|
||||
}
|
||||
|
||||
return fields, nil
|
||||
}
|
||||
|
||||
func flatten(nested interface{}) map[string]interface{} {
|
||||
fields := make(map[string]interface{})
|
||||
func (h *handler) processJSONIETF(path *pathInfo, data []byte) ([]updateField, error) {
|
||||
var nested interface{}
|
||||
if err := json.Unmarshal(data, &nested); err != nil {
|
||||
return nil, fmt.Errorf("failed to parse JSON value: %w", err)
|
||||
}
|
||||
|
||||
// Flatten the JSON data to get a key-value map
|
||||
entries := flatten(nested)
|
||||
|
||||
// Lookup the data in the YANG model if any
|
||||
if h.decoder != nil {
|
||||
for i, e := range entries {
|
||||
var namespace, identifier string
|
||||
for _, k := range e.key {
|
||||
if n, _, found := strings.Cut(k, ":"); found {
|
||||
namespace = n
|
||||
}
|
||||
}
|
||||
|
||||
// IETF nodes referencing YANG entries require a namespace
|
||||
if namespace == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
if a, b, found := strings.Cut(e.key[len(e.key)-1], ":"); !found {
|
||||
identifier = a
|
||||
} else {
|
||||
identifier = b
|
||||
}
|
||||
|
||||
if decoded, err := h.decoder.DecodeLeafElement(namespace, identifier, e.value); err != nil {
|
||||
h.log.Debugf("Decoding %s:%s failed: %v", namespace, identifier, err)
|
||||
} else {
|
||||
entries[i].value = decoded
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fields := make([]updateField, 0, len(entries))
|
||||
for _, entry := range entries {
|
||||
p := path.appendSegments(entry.key...)
|
||||
|
||||
// Try to lookup the full path to decode the field according to the
|
||||
// YANG model if any
|
||||
if h.decoder != nil {
|
||||
origin, fieldPath := p.Path()
|
||||
if decoded, err := h.decoder.DecodePathElement(origin, fieldPath, entry.value); err != nil {
|
||||
h.log.Debugf("Decoding %s failed: %v", p, err)
|
||||
} else {
|
||||
entry.value = decoded
|
||||
}
|
||||
}
|
||||
|
||||
// Create an update-field with the complete path for all entries
|
||||
fields = append(fields, updateField{
|
||||
path: p,
|
||||
value: entry.value,
|
||||
})
|
||||
}
|
||||
|
||||
return fields, nil
|
||||
}
|
||||
|
||||
func flatten(nested interface{}) []keyValuePair {
|
||||
var values []keyValuePair
|
||||
|
||||
switch n := nested.(type) {
|
||||
case map[string]interface{}:
|
||||
for k, child := range n {
|
||||
for ck, cv := range flatten(child) {
|
||||
key := k
|
||||
if ck != "" {
|
||||
key += "/" + ck
|
||||
}
|
||||
fields[key] = cv
|
||||
for _, c := range flatten(child) {
|
||||
values = append(values, keyValuePair{
|
||||
key: append([]string{k}, c.key...),
|
||||
value: c.value,
|
||||
})
|
||||
}
|
||||
}
|
||||
case []interface{}:
|
||||
for i, child := range n {
|
||||
k := strconv.Itoa(i)
|
||||
for ck, cv := range flatten(child) {
|
||||
key := k
|
||||
if ck != "" {
|
||||
key += "/" + ck
|
||||
}
|
||||
fields[key] = cv
|
||||
for _, c := range flatten(child) {
|
||||
values = append(values, keyValuePair{
|
||||
key: append([]string{k}, c.key...),
|
||||
value: c.value,
|
||||
})
|
||||
}
|
||||
}
|
||||
case nil:
|
||||
return nil
|
||||
default:
|
||||
return map[string]interface{}{"": nested}
|
||||
values = append(values, keyValuePair{value: n})
|
||||
}
|
||||
return fields
|
||||
|
||||
return values
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue