Skip to content

Add regexes for interface selection #9

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

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,9 @@ devices:
- 100
interfaces: # optional: Some devices (BNGs) might have thousands of interfaces
- HundredGigE0/0/0 # you can specify the interfaces to be scraped
- GigabitEthernet0
- GigabitEthernet.* # all interfaces are interpreted as regexes, that must match the whole interface name
excluded_interfaces: # optional: to easy limit to broad interface regexes
- GigabitEthernet0/1 # entries are also regexes thet must match full interface names
username: monitoring # required: Username to use for SSH auth
key_file: /path/to/a/private.key # optional: Private key to use for SSH auth
password: correcthorsebatterystaple # optional: Password for SSH auth
Expand Down
81 changes: 68 additions & 13 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import (
"github.com/gobwas/glob"
"io"
"io/ioutil"
"regexp"
"strings"

"gopkg.in/yaml.v2"
)
Expand Down Expand Up @@ -55,18 +57,62 @@ func (o OSVersion) String() string {
// DeviceGroupConfig describe how to connect to a remote device and what metrics
// to extract from the remote device.
type DeviceGroupConfig struct {
OSVersion OSVersion
StaticName *string `yaml:"-"`
Matcher glob.Glob `yaml:"-"`
Port int `yaml:"port,omitempty"`
Username string `yaml:"username"`
KeyFile string `yaml:"key_file,omitempty"`
Password string `yaml:"password,omitempty"`
ConnectTimeout int `yaml:"connect_timeout,omitempty"`
CommandTimeout int `yaml:"command_timeout,omitempty"`
EnabledCollectors []string `yaml:"enabled_collectors,flow"`
Interfaces []string `yaml:"interfaces,flow"`
EnabledVLANs []string `yaml:"enabled_vlans,flow"`
OSVersion OSVersion
StaticName *string `yaml:"-"`
Matcher glob.Glob `yaml:"-"`
Port int `yaml:"port,omitempty"`
Username string `yaml:"username"`
KeyFile string `yaml:"key_file,omitempty"`
Password string `yaml:"password,omitempty"`
ConnectTimeout int `yaml:"connect_timeout,omitempty"`
CommandTimeout int `yaml:"command_timeout,omitempty"`
EnabledCollectors []string `yaml:"enabled_collectors,flow"`
Interfaces []string `yaml:"interfaces,flow"`
InterfacesRegexp []*regexp.Regexp `yaml:"-"`
ExcludedInterfaces []string `yaml:"excluded_interfaces,flow"`
ExcludedInterfacesRegexp []*regexp.Regexp `yaml:"-"`
EnabledVLANs []string `yaml:"enabled_vlans,flow"`
}

func normalizeRegex(str string) string {
return "^" + strings.TrimRight(strings.TrimLeft(str, "^"), "$") + "$"
}

func (dgc *DeviceGroupConfig) createInterfaceRegexp() error {
dgc.InterfacesRegexp = make([]*regexp.Regexp, len(dgc.Interfaces))
for i, str := range dgc.Interfaces {
rgx, err := regexp.Compile(normalizeRegex(str))
if err != nil {
return err
}
dgc.InterfacesRegexp[i] = rgx
}
dgc.ExcludedInterfacesRegexp = make([]*regexp.Regexp, len(dgc.ExcludedInterfaces))
for i, str := range dgc.ExcludedInterfaces {
rgx, err := regexp.Compile(normalizeRegex(str))
if err != nil {
return err
}
dgc.ExcludedInterfacesRegexp[i] = rgx
}
return nil
}

func (dgc *DeviceGroupConfig) MatchInterface(ifName string) bool {
match := false
for _, r := range dgc.InterfacesRegexp {
if r.MatchString(ifName) {
match = true
break
}
}
for _, r := range dgc.ExcludedInterfacesRegexp {
if r.MatchString(ifName) {
match = false
break
}
}
return match
}

func newConfig() *Config {
Expand Down Expand Up @@ -118,7 +164,11 @@ func Load(reader io.Reader) (*Config, error) {

for matchStr, groupConfig := range config.DeviceGroups {

groupConfig.Matcher = glob.MustCompile(matchStr)
rgx, err := glob.Compile(matchStr)
if err != nil {
return nil, err
}
groupConfig.Matcher = rgx

// A glob is static, if there are no special meta signs to quote.
// Therefore, QuoteMeta should be a no op for static strings.
Expand All @@ -136,6 +186,11 @@ func Load(reader io.Reader) (*Config, error) {
if groupConfig.Port == 0 {
groupConfig.Port = defaultPort
}

err = groupConfig.createInterfaceRegexp()
if err != nil {
return nil, err
}
}

return config, nil
Expand Down
25 changes: 23 additions & 2 deletions interfaces/collector.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package interfaces

import (
"regexp"

"gitlab.com/wobcom/cisco-exporter/collector"
"gitlab.com/wobcom/cisco-exporter/connector"

Expand All @@ -21,6 +23,8 @@ var (
adminStatusDesc *prometheus.Desc
operStatusDesc *prometheus.Desc
errorStatusDesc *prometheus.Desc

interfaceLineRegexp *regexp.Regexp
)

// Collector gathers counters for remote device's interfaces.
Expand Down Expand Up @@ -48,6 +52,9 @@ func init() {
adminStatusDesc = prometheus.NewDesc(prefix+"admin_up_info", "Admin operational status", l, nil)
operStatusDesc = prometheus.NewDesc(prefix+"up_info", "Interface operational status", l, nil)
errorStatusDesc = prometheus.NewDesc(prefix+"error_status_info", "Admin and operational status differ", l, nil)

// Regex expression to match the interface name, for example `GigabitEthernet0/5` or a variation of such in the interface summary
interfaceLineRegexp = regexp.MustCompile(`^\s*\*?\s(\S+)[\s\d-]*$`)
}

// Describe implements the collector.Collector interface's Describe function
Expand All @@ -70,8 +77,22 @@ func (c *Collector) Collect(ctx *collector.CollectContext) {
}()

if len(ctx.Connection.Device.Interfaces) > 0 {
for _, interfaceName := range ctx.Connection.Device.Interfaces {
c.collect(ctx, interfaceName)
sshCtx := connector.NewSSHCommandContext("show interface summary")
go ctx.Connection.RunCommand(sshCtx)

IfCollection: for {
select {
case <-sshCtx.Done:
break IfCollection
case line := <-sshCtx.Output:
match := interfaceLineRegexp.FindStringSubmatch(line)
if len(match) > 0 {
ifName := match[1]
if ctx.Connection.Device.MatchInterface(ifName) {
c.collect(ctx, ifName)
}
}
}
}
} else {
c.collect(ctx, "")
Expand Down