Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable passing input file, output file, and mibs dir on command line … #1028

Merged
merged 7 commits into from
Nov 10, 2023
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
15 changes: 13 additions & 2 deletions generator/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ make generator mibs
```
## Preparation

It is recommended to have a directory per device family which contains the mibs dir for the device family,
It is recommended to have a directory per device family which contains the mibs dir for the device family,
a logical link to the generator executable and the generator.yml configuration file. This is to avoid name space collisions
in the MIB definition. Keep only the required MIBS in the mibs directory for the devices.
Then merge all the resulting snmp.yml files into one main file that will be used by the snmp_exporter collector.
Expand All @@ -35,6 +35,17 @@ by the snmp_exporter executable to collect data from the snmp enabled devices.

Additional command are available for debugging, use the `help` command to see them.

After building, you can pass a directories of mibs, a path to the `generator.yml`
file and the intended path of your output file e.g. `snmp.yml` to the `generate`
command like so,
```bash
./generator generate \
-m /tmp/deviceFamilyMibs \
-m /tmp/sharedMibs \
-g /tmp/generator.yml \
-o /tmp/snmp.yml
```

### MIB Parsing options

The parsing of MIBs can be controlled using the `--snmp.mibopts` flag. The available values depend on the net-snmp version used to build the generator.
Expand Down Expand Up @@ -173,7 +184,7 @@ modules:
# If one of the target OIDs is used in a lookup, the filter will apply ALL tables using this lookup
# For a network switch, this could be used to collect a subset of interfaces such as uplinks
# For a router, this could be used to collect all real ports but not vlans and other virtual interfaces
# Specifying ifAlias or ifName if they are used in lookups with ifIndex will apply to the filter to
# Specifying ifAlias or ifName if they are used in lookups with ifIndex will apply to the filter to
# all the OIDs that depend on the lookup, such as ifSpeed, ifInHcOctets, etc.
# This feature applies to any table(s) OIDs using a common index
- targets:
Expand Down
21 changes: 16 additions & 5 deletions generator/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ func generateConfig(nodes *Node, nameToNode map[string]*Node, logger log.Logger)
return fmt.Errorf("unable to determine absolute path for output")
}

content, err := os.ReadFile("generator.yml")
content, err := os.ReadFile(*generatorYmlPath)
if err != nil {
return fmt.Errorf("error reading yml config: %s", err)
}
Expand All @@ -50,7 +50,10 @@ func generateConfig(nodes *Node, nameToNode map[string]*Node, logger log.Logger)
outputConfig.Auths = cfg.Auths
outputConfig.Modules = make(map[string]*config.Module, len(cfg.Modules))
for name, m := range cfg.Modules {
level.Info(logger).Log("msg", "Generating config for module", "module", name)
err := level.Debug(logger).Log("msg", "Generating config for module", "module", name)
if err != nil {
return err
}
// Give each module a copy of the tree so that it can be modified.
mNodes := nodes.Copy()
// Build the map with new pointers.
Expand All @@ -65,7 +68,10 @@ func generateConfig(nodes *Node, nameToNode map[string]*Node, logger log.Logger)
}
outputConfig.Modules[name] = out
outputConfig.Modules[name].WalkParams = m.WalkParams
level.Info(logger).Log("msg", "Generated metrics", "module", name, "metrics", len(outputConfig.Modules[name].Metrics))
err = level.Debug(logger).Log("msg", "Generated metrics", "module", name, "metrics", len(outputConfig.Modules[name].Metrics))
if err != nil {
return err
}
}

config.DoNotHideSecrets = true
Expand All @@ -90,15 +96,20 @@ func generateConfig(nodes *Node, nameToNode map[string]*Node, logger log.Logger)
if err != nil {
return fmt.Errorf("error writing to output file: %s", err)
}
level.Info(logger).Log("msg", "Config written", "file", outputPath)
err = level.Debug(logger).Log("msg", "Config written", "file", outputPath)
if err != nil {
return err
}
return nil
}

var (
failOnParseErrors = kingpin.Flag("fail-on-parse-errors", "Exit with a non-zero status if there are MIB parsing errors").Default("false").Bool()
snmpMIBOpts = kingpin.Flag("snmp.mibopts", "Toggle various defaults controlling MIB parsing, see snmpwalk --help").String()
generateCommand = kingpin.Command("generate", "Generate snmp.yml from generator.yml")
outputPath = generateCommand.Flag("output-path", "Path to to write resulting config file").Default("snmp.yml").Short('o').String()
userMibsDir = generateCommand.Flag("mibs-dir", "Paths to mibs directory").Default("").Short('m').Strings()
generatorYmlPath = generateCommand.Flag("generator-path", "Path to the input generator.yml file").Default("generator.yml").Short('g').String()
outputPath = generateCommand.Flag("output-path", "Path to write the snmp_exporter's config file").Default("snmp.yml").Short('o').String()
parseErrorsCommand = kingpin.Command("parse_errors", "Debug: Print the parse errors output by NetSNMP")
dumpCommand = kingpin.Command("dump", "Debug: Dump the parsed and prepared MIBs")
)
Expand Down
40 changes: 33 additions & 7 deletions generator/net_snmp.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@
"io"
"os"
"sort"
"strings"

"github.com/go-kit/log"
"github.com/go-kit/log/level"
Expand Down Expand Up @@ -152,29 +153,51 @@
}
)

// getMibsDir joins the user-specified MIB directories into a single string; if the user didn't pass any,
// the default netsnmp mibs directory is returned.
func getMibsDir(paths []string) string {
if len(paths) == 1 && paths[0] == "" {
return C.GoString(C.netsnmp_get_mib_directory())
}
return strings.Join(paths, ":")
}

// Initialize NetSNMP. Returns MIB parse errors.
//
// Warning: This function plays with the stderr file descriptor.
func initSNMP(logger log.Logger) (string, error) {
// Load all the MIBs.
os.Setenv("MIBS", "ALL")
// Help the user find their MIB directories.
level.Info(logger).Log("msg", "Loading MIBs", "from", C.GoString(C.netsnmp_get_mib_directory()))
err := os.Setenv("MIBS", "ALL")
if err != nil {
return "", err
}
mibsDir := getMibsDir(*userMibsDir)
level.Info(logger).Log("msg", "Loading MIBs", "from", mibsDir)
C.netsnmp_set_mib_directory(C.CString(mibsDir))
if *snmpMIBOpts != "" {
C.snmp_mib_toggle_options(C.CString(*snmpMIBOpts))
}
// We want the descriptions.
C.snmp_set_save_descriptions(1)

// Make stderr go to a pipe, as netsnmp tends to spew a
// lot of errors on startup that there's no apparent
// way to disable or redirect.
r, w, err := os.Pipe()
if err != nil {
return "", fmt.Errorf("error creating pipe: %s", err)
}
defer r.Close()
defer w.Close()
defer func(r *os.File) {
err := r.Close()
if err != nil {

Check failure on line 191 in generator/net_snmp.go

View workflow job for this annotation

GitHub Actions / lint

SA9003: empty branch (staticcheck)

}
}(r)
defer func(w *os.File) {
err := w.Close()
if err != nil {

Check failure on line 197 in generator/net_snmp.go

View workflow job for this annotation

GitHub Actions / lint

SA9003: empty branch (staticcheck)

}
}(w)
savedStderrFd := C.dup(2)
C.close(2)
C.dup2(C.int(w.Fd()), 2)
Expand All @@ -194,7 +217,10 @@
C.netsnmp_init_mib()

// Restore stderr to normal.
w.Close()
err = w.Close()
if err != nil {
return "", err
}
C.close(2)
C.dup2(savedStderrFd, 2)
C.close(savedStderrFd)
Expand Down
20 changes: 16 additions & 4 deletions generator/tree.go
Original file line number Diff line number Diff line change
Expand Up @@ -284,7 +284,10 @@ func generateConfigModule(cfg *ModuleConfig, node *Node, nameToNode map[string]*
// Find node to override.
n, ok := nameToNode[name]
if !ok {
level.Warn(logger).Log("msg", "Could not find node to override type", "node", name)
err := level.Warn(logger).Log("msg", "Could not find node to override type", "node", name)
if err != nil {
return nil, err
}
continue
}
// params.Type validated at generator configuration.
Expand Down Expand Up @@ -365,12 +368,18 @@ func generateConfigModule(cfg *ModuleConfig, node *Node, nameToNode map[string]*
index := &config.Index{Labelname: i}
indexNode, ok := nameToNode[i]
if !ok {
level.Warn(logger).Log("msg", "Could not find index for node", "node", n.Label, "index", i)
err := level.Warn(logger).Log("msg", "Could not find index for node", "node", n.Label, "index", i)
if err != nil {
return
}
return
}
index.Type, ok = metricType(indexNode.Type)
if !ok {
level.Warn(logger).Log("msg", "Can't handle index type on node", "node", n.Label, "index", i, "type", indexNode.Type)
err := level.Warn(logger).Log("msg", "Can't handle index type on node", "node", n.Label, "index", i, "type", indexNode.Type)
if err != nil {
return
}
return
}
index.FixedSize = indexNode.FixedSize
Expand All @@ -384,7 +393,10 @@ func generateConfigModule(cfg *ModuleConfig, node *Node, nameToNode map[string]*
if prevType == subtype {
metric.Indexes = metric.Indexes[:len(metric.Indexes)-1]
} else {
level.Warn(logger).Log("msg", "Can't handle index type on node, missing preceding", "node", n.Label, "type", index.Type, "missing", subtype)
err := level.Warn(logger).Log("msg", "Can't handle index type on node, missing preceding", "node", n.Label, "type", index.Type, "missing", subtype)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same with these, please remove all the extraneous err checks you've added.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I got them all now

if err != nil {
return
}
return
}
}
Expand Down