18 changed files with 1545 additions and 0 deletions
@ -0,0 +1,34 @@
|
||||
- job: |
||||
name: network_exporter:image:build |
||||
parent: vexxhost-build-docker-image |
||||
provides: network_exporter:image |
||||
vars: &network_exporter_images |
||||
docker_images: |
||||
- context: . |
||||
repository: vexxhost/network-exporter |
||||
|
||||
- job: |
||||
name: network_exporter:image:upload |
||||
parent: vexxhost-upload-docker-image |
||||
provides: network_exporter:image |
||||
vars: *network_exporter_images |
||||
|
||||
- job: |
||||
name: network_exporter:image:promote |
||||
parent: vexxhost-promote-docker-image |
||||
vars: *network_exporter_images |
||||
|
||||
- project: |
||||
check: |
||||
jobs: |
||||
- golangci-lint |
||||
- golang-go-test |
||||
- network_exporter:image:build |
||||
gate: |
||||
jobs: |
||||
- golangci-lint |
||||
- golang-go-test |
||||
- network_exporter:image:upload |
||||
promote: |
||||
jobs: |
||||
- network_exporter:image:promote |
@ -0,0 +1,8 @@
|
||||
FROM golang:1.13 AS builder |
||||
WORKDIR /go/src/app |
||||
COPY . . |
||||
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build |
||||
|
||||
FROM scratch |
||||
COPY --from=builder /go/src/app/network_exporter /network_exporter |
||||
ENTRYPOINT ["/network_exporter"] |
@ -0,0 +1,28 @@
|
||||
# Network Exporter |
||||
|
||||
Prometheus exporter for a variety of network devices using their respective |
||||
APIs. The exporter implements a generic standarized way of delivering |
||||
information regardless of the device used, which makes it useful for |
||||
environments with heterogeous network infrastructure. |
||||
|
||||
## Supported Devices |
||||
|
||||
- Arista |
||||
- MikroTik |
||||
|
||||
## Supported Collectors |
||||
|
||||
- BGP |
||||
- Device Info |
||||
- Interface statistics |
||||
|
||||
## SNMP |
||||
|
||||
There is an extremely useful and powerful SNMP exporter which already exists |
||||
however we've found that we could never consistently retrieve information |
||||
across several device manufactureres (for example, some do not expose BGP |
||||
peer information over SNMP). |
||||
|
||||
The library embedded in here does the majority of the work of generalizing |
||||
that information retrieval so you can have very clear and clean metrics |
||||
regardless of the devices used in your environment. |
@ -0,0 +1,90 @@
|
||||
package collector |
||||
|
||||
import ( |
||||
"strconv" |
||||
|
||||
"github.com/prometheus/client_golang/prometheus" |
||||
"github.com/prometheus/common/log" |
||||
"opendev.org/vexxhost/network_exporter/network_api" |
||||
) |
||||
|
||||
type BgpCollector struct { |
||||
prometheus.Collector |
||||
|
||||
API network_api.API |
||||
|
||||
PrefixCount *prometheus.Desc |
||||
PeerUp *prometheus.Desc |
||||
MessagesReceived *prometheus.Desc |
||||
MessagesSent *prometheus.Desc |
||||
} |
||||
|
||||
func NewBgpCollector(device string, api network_api.API) *BgpCollector { |
||||
return &BgpCollector{ |
||||
API: api, |
||||
|
||||
PrefixCount: prometheus.NewDesc( |
||||
"network_bgp_prefix_count", |
||||
"BGP prefix count", |
||||
[]string{"as", "remote"}, prometheus.Labels{"device": device}, |
||||
), |
||||
PeerUp: prometheus.NewDesc( |
||||
"network_bgp_up", |
||||
"BGP session is established (up = 1)", |
||||
[]string{"as", "remote"}, prometheus.Labels{"device": device}, |
||||
), |
||||
MessagesReceived: prometheus.NewDesc( |
||||
"network_bgp_messages_received", |
||||
"BGP messages received", |
||||
[]string{"as", "remote"}, prometheus.Labels{"device": device}, |
||||
), |
||||
MessagesSent: prometheus.NewDesc( |
||||
"network_bgp_messages_sent", |
||||
"BGP messages sent", |
||||
[]string{"as", "remote"}, prometheus.Labels{"device": device}, |
||||
), |
||||
} |
||||
} |
||||
|
||||
func (c *BgpCollector) Describe(ch chan<- *prometheus.Desc) { |
||||
ch <- c.PrefixCount |
||||
ch <- c.PeerUp |
||||
ch <- c.MessagesReceived |
||||
ch <- c.MessagesSent |
||||
} |
||||
|
||||
func (c *BgpCollector) Collect(ch chan<- prometheus.Metric) { |
||||
peers, err := c.API.Peers() |
||||
if err != nil { |
||||
log.Errorln(err) |
||||
return |
||||
} |
||||
|
||||
for _, peer := range peers { |
||||
up := 0 |
||||
if peer.Up { |
||||
up = 1 |
||||
} |
||||
|
||||
ch <- prometheus.MustNewConstMetric( |
||||
c.PrefixCount, prometheus.GaugeValue, |
||||
peer.PrefixCount, |
||||
strconv.FormatInt(peer.AS, 10), peer.Remote, |
||||
) |
||||
ch <- prometheus.MustNewConstMetric( |
||||
c.PeerUp, prometheus.GaugeValue, |
||||
float64(up), |
||||
strconv.FormatInt(peer.AS, 10), peer.Remote, |
||||
) |
||||
ch <- prometheus.MustNewConstMetric( |
||||
c.MessagesReceived, prometheus.GaugeValue, |
||||
peer.MessagesReceived, |
||||
strconv.FormatInt(peer.AS, 10), peer.Remote, |
||||
) |
||||
ch <- prometheus.MustNewConstMetric( |
||||
c.MessagesSent, prometheus.GaugeValue, |
||||
peer.MessagesSent, |
||||
strconv.FormatInt(peer.AS, 10), peer.Remote, |
||||
) |
||||
} |
||||
} |
@ -0,0 +1,61 @@
|
||||
package collector |
||||
|
||||
import ( |
||||
"strings" |
||||
"testing" |
||||
|
||||
"github.com/prometheus/client_golang/prometheus/testutil" |
||||
"github.com/stretchr/testify/assert" |
||||
"opendev.org/vexxhost/network_exporter/network_api" |
||||
) |
||||
|
||||
var bgpExpected = ` |
||||
# HELP network_bgp_messages_received BGP messages received |
||||
# TYPE network_bgp_messages_received gauge |
||||
network_bgp_messages_received{as="65001",device="fake-dev",remote="10.10.10.1"} 1234 |
||||
network_bgp_messages_received{as="65002",device="fake-dev",remote="10.10.10.2"} 0 |
||||
# HELP network_bgp_messages_sent BGP messages sent |
||||
# TYPE network_bgp_messages_sent gauge |
||||
network_bgp_messages_sent{as="65001",device="fake-dev",remote="10.10.10.1"} 6789 |
||||
network_bgp_messages_sent{as="65002",device="fake-dev",remote="10.10.10.2"} 0 |
||||
# HELP network_bgp_prefix_count BGP prefix count |
||||
# TYPE network_bgp_prefix_count gauge |
||||
network_bgp_prefix_count{as="65001",device="fake-dev",remote="10.10.10.1"} 1337 |
||||
network_bgp_prefix_count{as="65002",device="fake-dev",remote="10.10.10.2"} 0 |
||||
# HELP network_bgp_up BGP session is established (up = 1) |
||||
# TYPE network_bgp_up gauge |
||||
network_bgp_up{as="65001",device="fake-dev",remote="10.10.10.1"} 1 |
||||
network_bgp_up{as="65002",device="fake-dev",remote="10.10.10.2"} 0 |
||||
` |
||||
|
||||
type FakeBgpApi struct { |
||||
network_api.API |
||||
} |
||||
|
||||
func (a *FakeBgpApi) Peers() ([]network_api.BgpPeer, error) { |
||||
return []network_api.BgpPeer{ |
||||
network_api.BgpPeer{ |
||||
Up: true, |
||||
AS: 65001, |
||||
Remote: "10.10.10.1", |
||||
PrefixCount: float64(1337), |
||||
MessagesReceived: float64(1234), |
||||
MessagesSent: float64(6789), |
||||
}, |
||||
network_api.BgpPeer{ |
||||
Up: false, |
||||
AS: 65002, |
||||
Remote: "10.10.10.2", |
||||
PrefixCount: float64(0), |
||||
MessagesReceived: float64(0), |
||||
MessagesSent: float64(0), |
||||
}, |
||||
}, nil |
||||
} |
||||
|
||||
func TestBGPCollect(t *testing.T) { |
||||
collector := NewBgpCollector("fake-dev", &FakeBgpApi{}) |
||||
|
||||
err := testutil.CollectAndCompare(collector, strings.NewReader(bgpExpected)) |
||||
assert.NoError(t, err) |
||||
} |
@ -0,0 +1,101 @@
|
||||
package collector |
||||
|
||||
import ( |
||||
"github.com/prometheus/client_golang/prometheus" |
||||
"github.com/prometheus/common/log" |
||||
"opendev.org/vexxhost/network_exporter/network_api" |
||||
) |
||||
|
||||
type DeviceCollector struct { |
||||
prometheus.Collector |
||||
|
||||
API network_api.API |
||||
|
||||
Up *prometheus.Desc |
||||
Info *prometheus.Desc |
||||
|
||||
Uptime *prometheus.Desc |
||||
|
||||
FreeMemory *prometheus.Desc |
||||
TotalMemory *prometheus.Desc |
||||
} |
||||
|
||||
func NewDeviceCollector(device string, api network_api.API) *DeviceCollector { |
||||
return &DeviceCollector{ |
||||
API: api, |
||||
|
||||
Up: prometheus.NewDesc( |
||||
"network_device_up", |
||||
"Network device up", |
||||
[]string{}, prometheus.Labels{"device": device}, |
||||
), |
||||
Info: prometheus.NewDesc( |
||||
"network_device_info", |
||||
"Network device info", |
||||
[]string{"model", "serial", "version"}, prometheus.Labels{"device": device}, |
||||
), |
||||
|
||||
Uptime: prometheus.NewDesc( |
||||
"network_device_uptime", |
||||
"Network device uptime", |
||||
[]string{}, prometheus.Labels{"device": device}, |
||||
), |
||||
|
||||
FreeMemory: prometheus.NewDesc( |
||||
"network_device_free_memory", |
||||
"Network device free memory", |
||||
[]string{}, prometheus.Labels{"device": device}, |
||||
), |
||||
TotalMemory: prometheus.NewDesc( |
||||
"network_device_total_memory", |
||||
"Network device total memory", |
||||
[]string{}, prometheus.Labels{"device": device}, |
||||
), |
||||
} |
||||
} |
||||
|
||||
func (c *DeviceCollector) Describe(ch chan<- *prometheus.Desc) { |
||||
ch <- c.Up |
||||
ch <- c.Info |
||||
ch <- c.Uptime |
||||
ch <- c.FreeMemory |
||||
ch <- c.TotalMemory |
||||
} |
||||
|
||||
func (c *DeviceCollector) Collect(ch chan<- prometheus.Metric) { |
||||
info, err := c.API.Info() |
||||
if err != nil { |
||||
ch <- prometheus.MustNewConstMetric( |
||||
c.Up, prometheus.GaugeValue, |
||||
float64(0), |
||||
) |
||||
|
||||
log.Errorln(err) |
||||
return |
||||
} |
||||
|
||||
ch <- prometheus.MustNewConstMetric( |
||||
c.Up, prometheus.GaugeValue, |
||||
float64(1), |
||||
) |
||||
|
||||
ch <- prometheus.MustNewConstMetric( |
||||
c.Info, prometheus.GaugeValue, |
||||
float64(1), |
||||
info.Model, info.Serial, info.Version, |
||||
) |
||||
|
||||
ch <- prometheus.MustNewConstMetric( |
||||
c.Uptime, prometheus.GaugeValue, |
||||
info.Uptime, |
||||
) |
||||
|
||||
ch <- prometheus.MustNewConstMetric( |
||||
c.FreeMemory, prometheus.GaugeValue, |
||||
info.FreeMemory, |
||||
) |
||||
ch <- prometheus.MustNewConstMetric( |
||||
c.TotalMemory, prometheus.GaugeValue, |
||||
info.TotalMemory, |
||||
) |
||||
} |
@ -0,0 +1,50 @@
|
||||
package collector |
||||
|
||||
import ( |
||||
"strings" |
||||
"testing" |
||||
|
||||
"github.com/prometheus/client_golang/prometheus/testutil" |
||||
"github.com/stretchr/testify/assert" |
||||
"opendev.org/vexxhost/network_exporter/network_api" |
||||
) |
||||
|
||||
var deviceExpected = ` |
||||
# HELP network_device_free_memory Network device free memory |
||||
# TYPE network_device_free_memory gauge |
||||
network_device_free_memory{device="fake-dev"} 1.024e+06 |
||||
# HELP network_device_info Network device info |
||||
# TYPE network_device_info gauge |
||||
network_device_info{device="fake-dev",model="fast",serial="1337",version="1.33.7"} 1 |
||||
# HELP network_device_total_memory Network device total memory |
||||
# TYPE network_device_total_memory gauge |
||||
network_device_total_memory{device="fake-dev"} 2.048e+06 |
||||
# HELP network_device_up Network device up |
||||
# TYPE network_device_up gauge |
||||
network_device_up{device="fake-dev"} 1 |
||||
# HELP network_device_uptime Network device uptime |
||||
# TYPE network_device_uptime gauge |
||||
network_device_uptime{device="fake-dev"} 542594 |
||||
` |
||||
|
||||
type FakeInfoApi struct { |
||||
network_api.API |
||||
} |
||||
|
||||
func (a *FakeInfoApi) Info() (*network_api.Info, error) { |
||||
return &network_api.Info{ |
||||
Uptime: 542594, |
||||
FreeMemory: 1024000, |
||||
TotalMemory: 2048000, |
||||
Model: "fast", |
||||
Serial: "1337", |
||||
Version: "1.33.7", |
||||
}, nil |
||||
} |
||||
|
||||
func TestDeviceCollect(t *testing.T) { |
||||
collector := NewDeviceCollector("fake-dev", &FakeInfoApi{}) |
||||
|
||||
err := testutil.CollectAndCompare(collector, strings.NewReader(deviceExpected)) |
||||
assert.NoError(t, err) |
||||
} |
@ -0,0 +1,151 @@
|
||||
package collector |
||||
|
||||
import ( |
||||
"github.com/prometheus/client_golang/prometheus" |
||||
"github.com/prometheus/common/log" |
||||
"opendev.org/vexxhost/network_exporter/network_api" |
||||
) |
||||
|
||||
type InterfaceCollector struct { |
||||
prometheus.Collector |
||||
|
||||
API network_api.API |
||||
|
||||
Info *prometheus.Desc |
||||
|
||||
RxPackets *prometheus.Desc |
||||
RxBits *prometheus.Desc |
||||
RxDrops *prometheus.Desc |
||||
RxErrors *prometheus.Desc |
||||
|
||||
TxPackets *prometheus.Desc |
||||
TxBits *prometheus.Desc |
||||
TxDrops *prometheus.Desc |
||||
TxErrors *prometheus.Desc |
||||
} |
||||
|
||||
func NewInterfaceCollector(device string, api network_api.API) *InterfaceCollector { |
||||
return &InterfaceCollector{ |
||||
API: api, |
||||
|
||||
Info: prometheus.NewDesc( |
||||
"network_interface_info", |
||||
"Interface information", |
||||
[]string{"interface", "description"}, prometheus.Labels{"device": device}, |
||||
), |
||||
|
||||
RxPackets: prometheus.NewDesc( |
||||
"network_interface_rx_packets", |
||||
"Number of packets received", |
||||
[]string{"interface"}, prometheus.Labels{"device": device}, |
||||
), |
||||
RxBits: prometheus.NewDesc( |
||||
"network_interface_rx_bits", |
||||
"Number of bits received", |
||||
[]string{"interface"}, prometheus.Labels{"device": device}, |
||||
), |
||||
RxDrops: prometheus.NewDesc( |
||||
"network_interface_rx_drops", |
||||
"Number of receive packet drops", |
||||
[]string{"interface"}, prometheus.Labels{"device": device}, |
||||
), |
||||
RxErrors: prometheus.NewDesc( |
||||
"network_interface_rx_errors", |
||||
"Number of receive packet errors", |
||||
[]string{"interface"}, prometheus.Labels{"device": device}, |
||||
), |
||||
|
||||
TxPackets: prometheus.NewDesc( |
||||
"network_interface_tx_packets", |
||||
"Number of packets transmitted", |
||||
[]string{"interface"}, prometheus.Labels{"device": device}, |
||||
), |
||||
TxBits: prometheus.NewDesc( |
||||
"network_interface_tx_bits", |
||||
"Number of bits transmitted", |
||||
[]string{"interface"}, prometheus.Labels{"device": device}, |
||||
), |
||||
TxDrops: prometheus.NewDesc( |
||||
"network_interface_tx_drops", |
||||
"Number of transmit packet drops", |
||||
[]string{"interface"}, prometheus.Labels{"device": device}, |
||||
), |
||||
TxErrors: prometheus.NewDesc( |
||||
"network_interface_tx_errors", |
||||
"Number of transmit packet errors", |
||||
[]string{"interface"}, prometheus.Labels{"device": device}, |
||||
), |
||||
} |
||||
} |
||||
|
||||
func (c *InterfaceCollector) Describe(ch chan<- *prometheus.Desc) { |
||||
ch <- c.Info |
||||
|
||||
ch <- c.RxPackets |
||||
ch <- c.RxBits |
||||
ch <- c.RxDrops |
||||
ch <- c.RxErrors |
||||
|
||||
ch <- c.TxPackets |
||||
ch <- c.TxBits |
||||
ch <- c.TxDrops |
||||
ch <- c.TxErrors |
||||
} |
||||
|
||||
func (c *InterfaceCollector) Collect(ch chan<- prometheus.Metric) { |
||||
interfaces, err := c.API.Interfaces() |
||||
if err != nil { |
||||
log.Errorln(err) |
||||
return |
||||
} |
||||
|
||||
for _, iface := range interfaces { |
||||
ch <- prometheus.MustNewConstMetric( |
||||
c.Info, prometheus.GaugeValue, |
||||
float64(1), |
||||
iface.Name, iface.Description, |
||||
) |
||||
|
||||
ch <- prometheus.MustNewConstMetric( |
||||
c.RxPackets, prometheus.CounterValue, |
||||
iface.RxPackets, |
||||
iface.Name, |
||||
) |
||||
ch <- prometheus.MustNewConstMetric( |
||||
c.RxBits, prometheus.CounterValue, |
||||
iface.RxBits, |
||||
iface.Name, |
||||
) |
||||
ch <- prometheus.MustNewConstMetric( |
||||
c.RxDrops, prometheus.CounterValue, |
||||
iface.RxDrops, |
||||
iface.Name, |
||||
) |
||||
ch <- prometheus.MustNewConstMetric( |
||||
c.RxErrors, prometheus.CounterValue, |
||||
iface.RxErrors, |
||||
iface.Name, |
||||
) |
||||
|
||||
ch <- prometheus.MustNewConstMetric( |
||||
c.TxPackets, prometheus.CounterValue, |
||||
iface.TxPackets, |
||||
iface.Name, |
||||
) |
||||
ch <- prometheus.MustNewConstMetric( |
||||
c.TxBits, prometheus.CounterValue, |
||||
iface.TxBits, |
||||
iface.Name, |
||||
) |
||||
ch <- prometheus.MustNewConstMetric( |
||||
c.TxDrops, prometheus.CounterValue, |
||||
iface.TxDrops, |
||||
iface.Name, |
||||
) |
||||
ch <- prometheus.MustNewConstMetric( |
||||
c.TxErrors, prometheus.CounterValue, |
||||
iface.TxErrors, |
||||
iface.Name, |
||||
) |
||||
} |
||||
} |
@ -0,0 +1,89 @@
|
||||
package collector |
||||
|
||||
import ( |
||||
"strings" |
||||
"testing" |
||||
|
||||
"github.com/prometheus/client_golang/prometheus/testutil" |
||||
"github.com/stretchr/testify/assert" |
||||
"opendev.org/vexxhost/network_exporter/network_api" |
||||
) |
||||
|
||||
var interfaceExpected = ` |
||||
# HELP network_interface_info Interface information |
||||
# TYPE network_interface_info gauge |
||||
network_interface_info{description="bad server",device="fake-dev",interface="Ethernet2"} 1 |
||||
network_interface_info{description="uplink",device="fake-dev",interface="Ethernet1"} 1 |
||||
# HELP network_interface_rx_bits Number of bits received |
||||
# TYPE network_interface_rx_bits counter |
||||
network_interface_rx_bits{device="fake-dev",interface="Ethernet1"} 6452523 |
||||
network_interface_rx_bits{device="fake-dev",interface="Ethernet2"} 2345 |
||||
# HELP network_interface_rx_drops Number of receive packet drops |
||||
# TYPE network_interface_rx_drops counter |
||||
network_interface_rx_drops{device="fake-dev",interface="Ethernet1"} 0 |
||||
network_interface_rx_drops{device="fake-dev",interface="Ethernet2"} 1 |
||||
# HELP network_interface_rx_errors Number of receive packet errors |
||||
# TYPE network_interface_rx_errors counter |
||||
network_interface_rx_errors{device="fake-dev",interface="Ethernet1"} 0 |
||||
network_interface_rx_errors{device="fake-dev",interface="Ethernet2"} 9 |
||||
# HELP network_interface_rx_packets Number of packets received |
||||
# TYPE network_interface_rx_packets counter |
||||
network_interface_rx_packets{device="fake-dev",interface="Ethernet1"} 54325 |
||||
network_interface_rx_packets{device="fake-dev",interface="Ethernet2"} 1234 |
||||
# HELP network_interface_tx_bits Number of bits transmitted |
||||
# TYPE network_interface_tx_bits counter |
||||
network_interface_tx_bits{device="fake-dev",interface="Ethernet1"} 7443533453 |
||||
network_interface_tx_bits{device="fake-dev",interface="Ethernet2"} 6345 |
||||
# HELP network_interface_tx_drops Number of transmit packet drops |
||||
# TYPE network_interface_tx_drops counter |
||||
network_interface_tx_drops{device="fake-dev",interface="Ethernet1"} 0 |
||||
network_interface_tx_drops{device="fake-dev",interface="Ethernet2"} 4 |
||||
# HELP network_interface_tx_errors Number of transmit packet errors |
||||
# TYPE network_interface_tx_errors counter |
||||
network_interface_tx_errors{device="fake-dev",interface="Ethernet1"} 0 |
||||
network_interface_tx_errors{device="fake-dev",interface="Ethernet2"} 6 |
||||
# HELP network_interface_tx_packets Number of packets transmitted |
||||
# TYPE network_interface_tx_packets counter |
||||
network_interface_tx_packets{device="fake-dev",interface="Ethernet1"} 3454534 |
||||
network_interface_tx_packets{device="fake-dev",interface="Ethernet2"} 9876 |
||||
` |
||||
|
||||
type FakeInterfaceApi struct { |
||||
network_api.API |
||||
} |
||||
|
||||
func (a *FakeInterfaceApi) Interfaces() ([]network_api.Interface, error) { |
||||
return []network_api.Interface{ |
||||
network_api.Interface{ |
||||
Name: "Ethernet1", |
||||
Description: "uplink", |
||||
RxPackets: 54325, |
||||
RxBits: 6452523, |
||||
RxDrops: 0, |
||||
RxErrors: 0, |
||||
TxPackets: 3454534, |
||||
TxBits: 7443533453, |
||||
TxDrops: 0, |
||||
TxErrors: 0, |
||||
}, |
||||
network_api.Interface{ |
||||
Name: "Ethernet2", |
||||
Description: "bad server", |
||||
RxPackets: 1234, |
||||
RxBits: 2345, |
||||
RxDrops: 1, |
||||
RxErrors: 9, |
||||
TxPackets: 9876, |
||||
TxBits: 6345, |
||||
TxDrops: 4, |
||||
TxErrors: 6, |
||||
}, |
||||
}, nil |
||||
} |
||||
|
||||
func TestInterfaceCollect(t *testing.T) { |
||||
collector := NewInterfaceCollector("fake-dev", &FakeInterfaceApi{}) |
||||
|
||||
err := testutil.CollectAndCompare(collector, strings.NewReader(interfaceExpected)) |
||||
assert.NoError(t, err) |
||||
} |
@ -0,0 +1,77 @@
|
||||
package config |
||||
|
||||
import ( |
||||
"fmt" |
||||
"io" |
||||
"io/ioutil" |
||||
|
||||
"opendev.org/vexxhost/network_exporter/network_api" |
||||
"gopkg.in/yaml.v2" |
||||
) |
||||
|
||||
type Config struct { |
||||
Devices []DeviceConfig `yaml:"devices"` |
||||
} |
||||
|
||||
type DeviceConfig struct { |
||||
Type string `yaml:"type"` |
||||
Name string `yaml:"name"` |
||||
Community string `yaml:"community"` |
||||
Transport string `yaml:"transport"` |
||||
Hostname string `yaml:"hostname"` |
||||
Port int `yaml:"port"` |
||||
Username string `yaml:"username"` |
||||
Password string `yaml:"password"` |
||||
} |
||||
|
||||
func Load(handle io.Reader) (*Config, error) { |
||||
config := &Config{} |
||||
|
||||
data, err := ioutil.ReadAll(handle) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
err = yaml.Unmarshal(data, config) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
return config, nil |
||||
} |
||||
|
||||
func (c *DeviceConfig) String() string { |
||||
return fmt.Sprintf( |
||||
"[%s %s://%s@%s:%d]", |
||||
c.Name, |
||||
c.Transport, c.Username, c.Hostname, c.Port, |
||||
) |
||||
} |
||||
|
||||
func (c *DeviceConfig) API() network_api.API { |
||||
|
||||
switch c.Type { |
||||
case "mikrotik": |
||||
return network_api.NewMikrotikAPI( |
||||
c.Hostname, |
||||
c.Port, |
||||
c.Username, |
||||
c.Password, |
||||
) |
||||
case "arista": |
||||
return network_api.NewAristaAPI( |
||||
c.Transport, |
||||
c.Hostname, |
||||
c.Username, |
||||
c.Password, |
||||
c.Port, |
||||
) |
||||
case "snmp": |
||||
return network_api.NewSnmpAPI( |
||||
c.Hostname, |
||||
c.Community, |
||||
) |
||||
} |
||||
|
||||
return nil |
||||
} |
@ -0,0 +1,52 @@
|
||||
package config |
||||
|
||||
import ( |
||||
"strings" |
||||
"testing" |
||||
|
||||
"github.com/stretchr/testify/assert" |
||||
) |
||||
|
||||
func TestLoadConfigWithNoDevices(t *testing.T) { |
||||
configFile := ` |
||||
--- |
||||
devices: [] |
||||
` |
||||
|
||||
config, err := Load(strings.NewReader(configFile)) |
||||
assert.NoError(t, err) |
||||
|
||||
assert.Empty(t, config.Devices) |
||||
} |
||||
|
||||
func TestLoadConfig(t *testing.T) { |
||||
configFile := ` |
||||
--- |
||||
devices: |
||||
- type: mikrotik |
||||
name: switch-1 |
||||
community: prometheus |
||||
transport: https |
||||
hostname: switch.ip |
||||
port: 443 |
||||
username: foo |
||||
password: bar |
||||
` |
||||
|
||||
config, err := Load(strings.NewReader(configFile)) |
||||
assert.NoError(t, err) |
||||
|
||||
if assert.Len(t, config.Devices, 1) { |
||||
assert.Equal(t, "mikrotik", config.Devices[0].Type) |
||||
assert.Equal(t, "switch-1", config.Devices[0].Name) |
||||
assert.Equal(t, "prometheus", config.Devices[0].Community) |
||||
assert.Equal(t, "https", config.Devices[0].Transport) |
||||
assert.Equal(t, "switch.ip", config.Devices[0].Hostname) |
||||
assert.Equal(t, 443, config.Devices[0].Port) |
||||
assert.Equal(t, "foo", config.Devices[0].Username) |
||||
assert.Equal(t, "bar", config.Devices[0].Password) |
||||
|
||||
assert.Equal(t, "[switch-1 https://foo@switch.ip:443]", config.Devices[0].String()) |
||||
|
||||
} |
||||
} |
@ -0,0 +1,20 @@
|
||||
module opendev.org/vexxhost/network_exporter |
||||
|
||||
go 1.13 |
||||
|
||||
require ( |
||||
github.com/aristanetworks/goeapi v0.3.1-0.20191106033133-fe66bef98ad5 |
||||
github.com/go-routeros/routeros v0.0.0-20190905230431-4e69e5fc3b22 |
||||
github.com/google/martian v2.1.0+incompatible |
||||
github.com/karrick/tparse v2.4.2+incompatible |
||||
github.com/mitchellh/mapstructure v1.1.2 // indirect |
||||
github.com/prometheus/client_golang v1.3.0 |
||||
github.com/prometheus/common v0.7.0 |
||||
github.com/soniah/gosnmp v1.22.0 |
||||
github.com/stretchr/testify v1.4.0 |
||||
github.com/vaughan0/go-ini v0.0.0-20130923145212-a98ad7ee00ec // indirect |
||||
golang.org/x/tools v0.0.0-20200110213125-a7a6caa82ab2 // indirect |
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6 |
||||
gopkg.in/yaml.v2 v2.2.2 |
||||
honnef.co/go/tools v0.0.1-2019.2.3 |
||||
) |
@ -0,0 +1,130 @@
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= |
||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= |
||||
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM= |
||||
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= |
||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= |
||||
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4 h1:Hs82Z41s6SdL1CELW+XaDYmOH4hkBN4/N9og/AsOv7E= |
||||
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= |
||||
github.com/aristanetworks/goeapi v0.3.0 h1:iu4VRdBE2NZ5jWz8MsJUrIaFpM5lQ3SItrc/8i3GRv8= |
||||
github.com/aristanetworks/goeapi v0.3.0/go.mod h1:l2NklnkGMAycH+WLsjBpjGikgTKtqaSKi43EbiY87i0= |
||||
github.com/aristanetworks/goeapi v0.3.1-0.20191106033133-fe66bef98ad5 h1:hnG7tKO/SA9DYX3JNQ24b0SyjmFznhNA1B8g7nzWvDo= |
||||
github.com/aristanetworks/goeapi v0.3.1-0.20191106033133-fe66bef98ad5/go.mod h1:l2NklnkGMAycH+WLsjBpjGikgTKtqaSKi43EbiY87i0= |
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= |
||||
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= |
||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= |
||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= |
||||
github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY= |
||||
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= |
||||
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= |
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= |
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= |
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= |
||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= |
||||
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= |
||||
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= |
||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= |
||||
github.com/go-routeros/routeros v0.0.0-20190905230431-4e69e5fc3b22 h1:C6c62mxsyTBNZVXQ5nuWfNkYCsfklS7Ji0IR5+f1KQQ= |
||||
github.com/go-routeros/routeros v0.0.0-20190905230431-4e69e5fc3b22/go.mod h1:em1mEqFKnoeQuQP9Sg7i26yaW8o05WwcNj7yLhrXxSQ= |
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= |
||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= |
||||
github.com/golang/mock v1.2.0 h1:28o5sBqPkBsMGnC6b4MvE2TzSr5/AT4c/1fLqVGIwlk= |
||||
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= |
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= |
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= |
||||
github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= |
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= |
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= |
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= |
||||
github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no= |
||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= |
||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= |
||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= |
||||
github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= |
||||
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= |
||||
github.com/karrick/tparse v2.4.2+incompatible h1:+cW306qKAzrASC5XieHkgN7/vPaGKIuK62Q7nI7DIRc= |
||||
github.com/karrick/tparse v2.4.2+incompatible/go.mod h1:ASPA+vrIcN1uEW6BZg8vfWbzm69ODPSYZPU6qJyfdK0= |
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= |
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= |
||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= |
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= |
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= |
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= |
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= |
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= |
||||
github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= |
||||
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= |
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= |
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= |
||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= |
||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= |
||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= |
||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= |
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= |
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= |
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= |
||||
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= |
||||
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= |
||||
github.com/prometheus/client_golang v1.3.0 h1:miYCvYqFXtl/J9FIy8eNpBfYthAEFg+Ys0XyUVEcDsc= |
||||
github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og= |
||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= |
||||
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= |
||||
github.com/prometheus/client_model v0.1.0 h1:ElTg5tNp4DqfV7UQjDqv2+RJlNzsDtvNAWccbItceIE= |
||||
github.com/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= |
||||
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= |
||||
github.com/prometheus/common v0.7.0 h1:L+1lyG48J1zAQXA3RBX/nG/B3gjlHq0zTt2tlbJLyCY= |
||||
github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA= |
||||
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= |
||||
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= |
||||
github.com/prometheus/procfs v0.0.8 h1:+fpWZdT24pJBiqJdAwYBjPSk+5YmQzYNPYzQsdzLkt8= |
||||
github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= |
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= |
||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= |
||||
github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= |
||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= |
||||
github.com/soniah/gosnmp v1.22.0 h1:jVJi8+OGvR+JHIaZKMmnyNP0akJd2vEgNatybwhZvxg= |
||||
github.com/soniah/gosnmp v1.22.0/go.mod h1:DuEpAS0az51+DyVBQwITDsoq4++e3LTNckp2GoasF2I= |
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= |
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= |
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= |
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= |
||||
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= |
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= |
||||
github.com/vaughan0/go-ini v0.0.0-20130923145212-a98ad7ee00ec h1:DGmKwyZwEB8dI7tbLt/I/gQuP559o/0FrAkHKlQM/Ks= |
||||
github.com/vaughan0/go-ini v0.0.0-20130923145212-a98ad7ee00ec/go.mod h1:owBmyHYMLkxyrugmfwE/DLJyW8Ro9mkphwuVErQ0iUw= |
||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= |
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= |
||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= |
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= |
||||
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= |
||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= |
||||
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= |
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= |
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= |
||||
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= |
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= |
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= |
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= |
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= |
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= |
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= |
||||
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= |
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= |
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= |
||||
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= |
||||
golang.org/x/sys v0.0.0-20191220142924-d4481acd189f h1:68K/z8GLUxV76xGSqwTWw2gyk/jwn79LUL43rES2g8o= |
||||
golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= |
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= |
||||
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= |
||||
golang.org/x/tools v0.0.0-20200110213125-a7a6caa82ab2 h1:V9r/14uGBqLgNlHRYWdVqjMdWkcOHnE2KG8DwVqQSEc= |
||||
golang.org/x/tools v0.0.0-20200110213125-a7a6caa82ab2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= |
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= |
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc= |
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= |
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= |
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= |
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= |
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= |
||||
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= |
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= |
||||
honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM= |
||||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= |
@ -0,0 +1,76 @@
|
||||
package main |
||||
|
||||
import ( |
||||
"net/http" |
||||
"os" |
||||
|
||||
"opendev.org/vexxhost/network_exporter/collector" |
||||
"opendev.org/vexxhost/network_exporter/config" |
||||
|
||||
"github.com/prometheus/client_golang/prometheus" |
||||
"github.com/prometheus/client_golang/prometheus/promhttp" |
||||
"github.com/prometheus/common/log" |
||||
"github.com/prometheus/common/version" |
||||
"gopkg.in/alecthomas/kingpin.v2" |
||||
) |
||||
|
||||
func main() { |
||||
var ( |
||||
listenAddress = kingpin.Flag( |
||||
"web.listen-address", |
||||
"Address on which to expose metrics and web interface.", |
||||
).Default(":9615").String() |
||||
metricsPath = kingpin.Flag( |
||||
"web.telemetry-path", |
||||
"Path under which to expose metrics.", |
||||
).Default("/metrics").String() |
||||
configFile = kingpin.Flag( |
||||
"config.file", |
||||
"Configuration file path", |
||||
).Default("config.yaml").String() |
||||
) |
||||
|
||||
kingpin.Version(version.Print("network-exporter")) |
||||
kingpin.HelpFlag.Short('h') |
||||
kingpin.Parse() |
||||
|
||||
file, err := os.Open(*configFile) |
||||
if err != nil { |
||||
log.Fatalln(err) |
||||
} |
||||
|
||||
config, err := config.Load(file) |
||||
if err != nil { |
||||
log.Fatalln(err) |
||||
} |
||||
|
||||
for _, device := range config.Devices { |
||||
api := device.API() |
||||
|
||||
bgp := collector.NewBgpCollector(device.Name, api) |
||||
prometheus.MustRegister(bgp) |
||||
|
||||
deviceInfo := collector.NewDeviceCollector(device.Name, api) |
||||
prometheus.MustRegister(deviceInfo) |
||||
|
||||
iface := collector.NewInterfaceCollector(device.Name, api) |
||||
prometheus.MustRegister(iface) |
||||
} |
||||
|
||||
http.Handle("/metrics", promhttp.Handler()) |
||||
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { |
||||
_, err := w.Write([]byte(`<html> |
||||
<head><title>Network Exporter</title></head> |
||||
<body> |
||||
<h1>Network Exporter</h1> |
||||
<p><a href="` + *metricsPath + `">Metrics</a></p> |
||||
</body> |
||||
</html>`)) |
||||
if err != nil { |
||||
log.Errorln(err) |
||||
} |
||||
}) |
||||
|
||||
log.Infoln("Listening on", *listenAddress) |
||||
log.Fatal(http.ListenAndServe(*listenAddress, nil)) |
||||
} |
@ -0,0 +1,133 @@
|
||||
package network_api |
||||
|
||||
import ( |
||||
"time" |
||||
|
||||
"github.com/aristanetworks/goeapi" |
||||
"github.com/aristanetworks/goeapi/module" |
||||
) |
||||
|
||||
type AristaAPI struct { |
||||
API |
||||
|
||||
Transport string |
||||
Host string |
||||
Username string |
||||
Password string |
||||
Port int |
||||
} |
||||
|
||||
func NewAristaAPI(transport string, host string, username string, password string, port int) API { |
||||
return &AristaAPI{ |
||||
Transport: transport, |
||||
Host: host, |
||||
Username: username, |
||||
Password: password, |
||||
Port: port, |
||||
} |
||||
} |
||||
|
||||
func (a *AristaAPI) getNode() (*goeapi.Node, error) { |
||||
return goeapi.Connect(a.Transport, a.Host, a.Username, a.Password, a.Port) |
||||
} |
||||
|
||||
func (a *AristaAPI) Info() (*Info, error) { |
||||
node, err := a.getNode() |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
var version module.ShowVersion |
||||
handle, _ := node.GetHandle("json") |
||||
if err := handle.Enable(&version); err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
return &Info{ |
||||
Uptime: float64(time.Now().Unix()) - version.BootupTimestamp, |
||||
|
||||
FreeMemory: float64(version.MemFree), |
||||
TotalMemory: float64(version.MemTotal), |
||||
|
||||
Model: version.ModelName, |
||||
Serial: version.SerialNumber, |
||||
Version: version.Version, |
||||
}, nil |
||||
} |
||||
|
||||
func (a *AristaAPI) Interfaces() ([]Interface, error) { |
||||
node, err := a.getNode() |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
interfaces := []Interface{} |
||||
|
||||
show := module.Show(node) |
||||
ifaceData := show.ShowInterfaces() |
||||
|
||||
for _, iface := range ifaceData.Interfaces { |
||||
counters := iface.InterfaceCounters |
||||
|
||||
rxPackets := float64(counters.InBroadcastPkts) + float64(counters.InMulticastPkts) + float64(counters.InUcastPkts) |
||||
rxBits := float64(counters.InOctets) * 8 |
||||
rxDrops := float64(counters.InDiscards) |
||||
rxErrors := float64(counters.TotalInErrors) |
||||
|
||||
txPackets := float64(counters.OutBroadcastPkts) + float64(counters.OutMulticastPkts) + float64(counters.OutUcastPkts) |
||||
txBits := float64(counters.OutOctets) * 8 |
||||
txDrops := float64(counters.OutDiscards) |
||||
txErrors := float64(counters.TotalOutErrors) |
||||
|
||||
iface := Interface{ |
||||
Name: iface.Name, |
||||
Description: iface.Description, |
||||
|
||||
RxPackets: rxPackets, |
||||
RxBits: rxBits, |
||||
RxDrops: rxDrops, |
||||
RxErrors: rxErrors, |
||||
|
||||
TxPackets: txPackets, |
||||
TxBits: txBits, |
||||
TxDrops: txDrops, |
||||
TxErrors: txErrors, |
||||
} |
||||
|
||||
interfaces = append(interfaces, iface) |
||||
} |
||||
|
||||
return interfaces, nil |
||||
} |
||||
|
||||
func (a *AristaAPI) Peers() ([]BgpPeer, error) { |
||||
node, err := a.getNode() |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
peers := []BgpPeer{} |
||||
|
||||
show := module.Show(node) |
||||
summary, err := show.ShowIPBGPSummary() |
||||
if err != nil { |
||||
return peers, err |
||||
} |
||||
|
||||
for _, vrf := range summary.VRFs { |
||||
for peerId, peer := range vrf.Peers { |
||||
peer := BgpPeer{ |
||||
Up: (peer.PeerState == "Established"), |
||||
AS: peer.ASN, |
||||
Remote: peerId, |
||||
PrefixCount: float64(peer.PrefixAccepted), |
||||
MessagesReceived: float64(peer.MsgReceived), |
||||
MessagesSent: float64(peer.MsgSent), |
||||
} |
||||
|
||||
peers = append(peers, peer) |
||||
} |
||||
} |
||||
|
||||
return peers, nil |
||||
} |
@ -0,0 +1,45 @@
|
||||
package network_api |
||||
|
||||
type API interface { |
||||
Info() (*Info, error) |
||||
Peers() ([]BgpPeer, error) |
||||
Interfaces() ([]Interface, error) |
||||
} |
||||
|
||||
type Info struct { |
||||
Uptime float64 |
||||
|
||||
FreeMemory float64 |
||||
TotalMemory float64 |
||||
|
||||
Model string |
||||
Serial string |
||||
Version string |
||||
} |
||||
|
||||
type BgpPeer struct { |
||||
Up bool |
||||
|
||||
AS int64 |
||||
Remote string |
||||
|
||||
PrefixCount float64 |
||||
|
||||
MessagesReceived float64 |
||||
MessagesSent float64 |
||||
} |
||||
|
||||
type Interface struct { |
||||
Name string |
||||
Description string |
||||
|
||||
RxPackets float64 |
||||
RxBits float64 |
||||
RxDrops float64 |
||||
RxErrors float64 |
||||
|
||||
TxPackets float64 |
||||
TxBits float64 |
||||
TxDrops float64 |
||||
TxErrors float64 |
||||
} |
@ -0,0 +1,163 @@
|
||||
package network_api |
||||
|
||||
import ( |
||||
"fmt" |
||||
"strconv" |
||||
"time" |
||||
|
||||
"github.com/go-routeros/routeros" |
||||
"github.com/karrick/tparse" |
||||
) |
||||
|
||||
type MikrotikAPI struct { |
||||
API |
||||
|
||||
Address string |
||||
Username string |
||||
Password string |
||||
} |
||||
|
||||
func NewMikrotikAPI(hostname string, port int, username string, password string) API { |
||||
address := fmt.Sprintf("%s:%d", hostname, port) |
||||
|
||||
return &MikrotikAPI{ |
||||
Address: address, |
||||
Username: username, |
||||
Password: password, |
||||
} |
||||
} |
||||
|
||||
func (a *MikrotikAPI) getClient() (*routeros.Client, error) { |
||||
return routeros.Dial(a.Address, a.Username, a.Password) |
||||
} |
||||
|
||||
func (a *MikrotikAPI) Info() (*Info, error) { |
||||
client, err := a.getClient() |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
systemData, err := client.Run( |
||||
"/system/resource/print", |
||||
"=.proplist=uptime,free-memory,total-memory,board-name,version", |
||||
) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
boardData, err := client.Run( |
||||
"/system/routerboard/print", |
||||
"=.proplist=serial-number", |
||||
) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
bootTimestamp, _ := tparse.ParseNow(time.RFC3339, "now-"+systemData.Re[0].Map["uptime"]) |
||||
|
||||
freeMemory, _ := strconv.ParseFloat(systemData.Re[0].Map["free-memory"], 64) |
||||
totalMemory, _ := strconv.ParseFloat(systemData.Re[0].Map["total-memory"], 64) |
||||
|
||||
return &Info{ |
||||
Uptime: float64(time.Now().Unix()) - float64(bootTimestamp.Unix()), |
||||
|
||||
FreeMemory: freeMemory, |
||||
TotalMemory: totalMemory, |
||||
|
||||
Model: systemData.Re[0].Map["board-name"], |
||||
Serial: boardData.Re[0].Map["serial-number"], |
||||
Version: systemData.Re[0].Map["version"], |
||||
}, nil |
||||
} |
||||
|
||||
func (a *MikrotikAPI) Interfaces() ([]Interface, error) { |
||||
client, err := a.getClient() |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
interfaces := []Interface{} |
||||
|
||||
interfaceData, err := client.Run( |
||||
"/interface/print", |
||||
"?type=ether", |
||||
"=.proplist=name,rx-packet,rx-byte,rx-drop,rx-error,tx-packet,tx-byte,tx-drop,tx-error", |
||||
) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
for _, iface := range interfaceData.Re { |
||||
rxPackets, _ := strconv.ParseFloat(iface.Map["rx-packet"], 64) |
||||
rxBytes, _ := strconv.ParseFloat(iface.Map["rx-byte"], 64) |
||||
rxDrops, _ := strconv.ParseFloat(iface.Map["rx-drop"], 64) |
||||
rxErrors, _ := strconv.ParseFloat(iface.Map["rx-error"], 64) |
||||
|
||||
txPackets, _ := strconv.ParseFloat(iface.Map["tx-packet"], 64) |
||||
txBytes, _ := strconv.ParseFloat(iface.Map["tx-byte"], 64) |
||||
txDrops, _ := strconv.ParseFloat(iface.Map["tx-drop"], 64) |
||||
txErrors, _ := strconv.ParseFloat(iface.Map["tx-error"], 64) |
||||
|
||||
iface := Interface{ |
||||
Name: iface.Map["name"], |
||||
Description: iface.Map["comment"], |
||||
|
||||
RxPackets: rxPackets, |
||||
RxBits: rxBytes * 8, |
||||
RxDrops: rxDrops, |
||||
RxErrors: rxErrors, |
||||
|
||||
TxPackets: txPackets, |
||||
TxBits: txBytes * 8, |
||||
TxDrops: txDrops, |
||||
TxErrors: txErrors, |
||||
} |
||||
|
||||
interfaces = append(interfaces, iface) |
||||
} |
||||
|
||||
return interfaces, nil |
||||
} |
||||
|
||||
func (a *MikrotikAPI) Peers() ([]BgpPeer, error) { |
||||
client, err := a.getClient() |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
peers := []BgpPeer{} |
||||
|
||||
peerData, err := client.Run( |
||||
"/routing/bgp/peer/print", |
||||
"=.proplist=state,remote-as,remote-address,prefix-count,updates-sent,updates-received,withdrawn-sent,withdrawn-received", |
||||
) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
for _, peer := range peerData.Re { |
||||
asn, _ := strconv.ParseInt(peer.Map["remote-as"], 10, 64) |
||||
prefixCount, _ := strconv.ParseFloat(peer.Map["prefix-count"], 64) |
||||
|
||||
updatesSent, _ := strconv.ParseFloat(peer.Map["updates-sent"], 64) |
||||
updatesReceived, _ := strconv.ParseFloat(peer.Map["updates-received"], 64) |
||||
withdrawnSent, _ := strconv.ParseFloat(peer.Map["withdrawn-sent"], 64) |
||||
withdrawnReceived, _ := strconv.ParseFloat(peer.Map["withdrawn-received"], 64) |
||||
|
||||
messagesSent := updatesSent + withdrawnSent |
||||
messagesReceived := updatesReceived + withdrawnReceived |
||||
|
||||
peer := BgpPeer{ |
||||
Up: (peer.Map["state"] == "established"), |
||||
AS: asn, |
||||
Remote: peer.Map["remote-address"], |
||||
PrefixCount: prefixCount, |
||||
MessagesReceived: messagesSent, |
||||
MessagesSent: messagesReceived, |
||||
} |
||||
|
||||
peers = append(peers, peer) |
||||
} |
||||
|
||||
return peers, nil |
||||
} |
@ -0,0 +1,237 @@
|
||||
package network_api |
||||
|
||||
import ( |
||||
"errors" |
||||
"fmt" |
||||
"strings" |
||||
"sync" |
||||
"time" |
||||
|
||||
"github.com/soniah/gosnmp" |
||||
) |
||||
|
||||
type SnmpAPI struct { |
||||
API |
||||
|
||||
Target string |
||||
Community string |
||||
|
||||
GoSnmp *gosnmp.GoSNMP |
||||
} |
||||
|
||||
func NewSnmpAPI(target string, community string) *SnmpAPI { |
||||
return &SnmpAPI{ |
||||
Target: target, |
||||
Community: community, |
||||
GoSnmp: &gosnmp.GoSNMP{ |
||||
Target: target, |
||||
Port: 161, |
||||
Transport: "udp", |
||||
Community: community, |
||||
Version: gosnmp.Version2c, |
||||
Timeout: time.Duration(2) * time.Second, |
||||
Retries: 3, |
||||
ExponentialTimeout: true, |
||||
MaxOids: gosnmp.MaxOids, |
||||
}, |
||||
} |
||||
} |
||||
|
||||
func (a *SnmpAPI) getClient() *gosnmp.GoSNMP { |
||||
return &gosnmp.GoSNMP{ |
||||
Target: a.Target, |
||||
Port: 161, |
||||
Transport: "udp", |
||||
Community: a.Community, |
||||
Version: gosnmp.Version2c, |
||||
Timeout: time.Duration(2) * time.Second, |
||||
Retries: 3, |
||||
ExponentialTimeout: true, |
||||
MaxOids: gosnmp.MaxOids, |
||||
} |
||||
} |
||||
|
||||
func (a *SnmpAPI) Info() (*Info, error) { |
||||
info := &Info{} |
||||
|
||||
// Uptime: 1.3.6.1.2.1.1.3
|
||||
walks, err := a.walk([]string{ |
||||
"1.3.6.1.2.1.1", |
||||
"1.0.8802.1.1.2.1.3.4", |
||||
}) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
for _, pdus := range walks { |
||||
for _, pdu := range pdus { |
||||
fmt.Println(pdu.Name) |
||||
|
||||
// Uptime
|
||||
if pdu.Name == ".1.3.6.1.2.1.1.3.0" { |
||||
info.Uptime = float64(pdu.Value.(uint)) |
||||
} |
||||
|
||||
// Model
|
||||
if pdu.Name == ".1.0.8802.1.1.2.1.3.4.0" { |
||||
info.Model = string(pdu.Value.([]byte)) |
||||
} |
||||
} |
||||
} |
||||
|
||||
return info, nil |
||||
} |
||||
|
||||
func (a *SnmpAPI) Peers() ([]BgpPeer, error) { |
||||
return nil, errors.New("Unimplemented") |
||||
} |
||||
|
||||
type SnmpWalkResult struct { |
||||
OID string |
||||
PDUs []gosnmp.SnmpPDU |
||||
Error error |
||||
} |
||||
|
||||
func (a *SnmpAPI) walk(oids []string) (map[string][]gosnmp.SnmpPDU, error) { |
||||
var err error |
||||
walks := map[string][]gosnmp.SnmpPDU{} |
||||
|
||||
pdus := make(chan SnmpWalkResult, len(oids)) |
||||
errors := make(chan error, len(oids)) |
||||
|
||||
wg := sync.WaitGroup{} |
||||
for _, oid := range oids { |
||||
wg.Add(1) |
||||
go func(oid string) { |
||||
defer wg.Done() |
||||
|
||||
client := a.getClient() |
||||
err = client.Connect() |
||||
if err != nil { |
||||
errors <- err |
||||
return |
||||
} |
||||
|
||||
pduData, err := client.WalkAll(oid) |
||||
errors <- err |
||||
pdus <- SnmpWalkResult{ |
||||
OID: oid, |
||||
PDUs: pduData, |
||||
Error: err, |
||||
} |
||||
}(oid) |
||||
} |
||||
|
||||
wg.Wait() |
||||
|
||||
close(pdus) |
||||