-
Notifications
You must be signed in to change notification settings - Fork 5.6k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(inputs.gnmi): Add yang-model decoding of JSON IETF payloads (#15201
- Loading branch information
Showing
23 changed files
with
1,386 additions
and
25 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.