Skip to content

Commit

Permalink
Merge pull request #1 from yseto/fix-some
Browse files Browse the repository at this point in the history
some fix
  • Loading branch information
yseto committed Mar 22, 2023
2 parents be8a48c + 9f2dc92 commit 63f458d
Show file tree
Hide file tree
Showing 29 changed files with 1,791 additions and 588 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ jobs:
- name: Setup Go
uses: actions/setup-go@v3
with:
go-version: 1.18
go-version: 1.20

- name: Run GoReleaser
uses: goreleaser/goreleaser-action@v3
Expand Down
32 changes: 32 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
---
name: test

on:
push:
branches:
- main
tags:
- v*
pull_request:
env:
DEBIAN_FRONTEND: noninteractive
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: golangci/golangci-lint-action@v3
test:
runs-on: ubuntu-latest
steps:
- uses: actions/setup-go@v3
with:
go-version: "1.19.x"
- uses: actions/checkout@v3
- uses: actions/cache@v3
with:
path: ~/go/pkg/mod
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-
- run: make deps-test test
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
config.yaml
2 changes: 2 additions & 0 deletions .goreleaser.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ builds:
archives:
- format: zip
name_template: "{{ .ProjectName }}_{{ .Os }}_{{ .Arch }}"
files:
- config.yaml.sample
release:
github:
owner: yseto
Expand Down
9 changes: 8 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,12 @@

all: build

deps:
deps-lint:
go install github.com/golangci/golangci-lint/cmd/[email protected]

deps-test:
go install github.com/tenntenn/testtime/cmd/testtime@latest

fmt:
go fmt ./...

Expand All @@ -13,3 +16,7 @@ lint:

build:
go build

test:
go test -overlay=`testtime` -v ./...

68 changes: 67 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,75 @@
# switch-traffic-to-mackerel

## usage
**これは趣味プロダクトです**

ネットワークスイッチなどに対してSNMPで問い合わせを行い、インターフェイスの通信量などの統計情報を取得するプログラムです。

レポジトリ名にある通り、[mackerel.io](https://ja.mackerel.io/)に情報を送ることを主な目的としています。

## 特徴

- GETBULKを用いて値を取得するので、比較的速く動作します。
- 取り込むインターフェイス名を正規表現で指定することができるので、取り込みたくないインターフェイスを除外できます。
- mackerelに対して、通信量をシステムメトリックとして投稿するため、このプログラムが異常終了した場合など送信が失敗している状態に、死活監視で気づくことができます。
- mackerelとの通信が途絶えた場合でもプログラム内部でキャッシュし、通信が再開できたときに一斉に送信します。

## 使い方

1. config.yaml.sample を config.yaml という名前でコピーします
2. config.yaml を開き加工します
3. `switch-traffic-to-mackerel -config config.yaml` で起動する

## 設定ファイルの内容

```yaml
community: public # (必須)取得する対象のスイッチなどの SNMP コミュニティ名を設定する
target: 192.2.0.1 # (必須)取得する対象のスイッチなどの IPアドレスを設定する
interface: # (オプション)取り込むインターフェイスをインターフェイス名を使って絞り込むことができます。includeとexcludeはそれぞれ排他です。
include: "" # 取得時に取り込みたいインターフェイス名を正規表現で指定します
exclude: "" # 取得時に取り込みたくないインターフェイス名を正規表現で指定します
mibs: # (オプション)取り込みたい情報を設定できます。無指定時は、以下に示されるMIBについての情報が取り込まれます
- ifHCInOctets
- ifHCOutOctets
- ifInDiscards
- ifOutDiscards
- ifInErrors
- ifOutErrors
# 機器によっては ifHCInOctets、ifHCOutOctets への対応ができない場合があります。その場合は、以下を明示的に指定する必要があります
# - ifInOctets
# - ifOutOctets
debug: false # (オプション) true時、デバッグ表示を有効にします。取り込むインターフェイス名およびその値を表示します
dry-run: false # (オプション) true時、mackerel への送信を抑制します。mackerel についての情報が設定ファイルに含まれてない場合は、強制的に true となります。
skip-linkdown: false # (オプション) downしているインターフェイスについては取り込みをスキップするオプションです
mackerel: # (オプション)Mackerel に送信する時のパラメータ
name: "" # (オプション)Mackerel に登録するホスト名
x-api-key: xxxxx # (必須) Mackerel の APIキー
host-id: xxxxx # (オプション) Mackerel でのホストID、無指定時の場合、プログラム内で自動的に取得し、設定ファイルを更新します。
```

## v0.0.1 からの移行

v0.0.1 までは設定ファイルを基本的に使用していませんでした。そのため設定ファイルを作成する必要があります。

以下のようなコマンドラインで起動させていた場合は、後述するようなYAMLになります

```
export MACKEREL_API_KEY=xxxx
./switch-traffic-to-mackerel -target 192.0.2.1 -mibs ifHCInOctets,ifHCOutOctets -include-interface 'ge-0/0/\d+$|ae0$' -skip-down-link-state -name sw1
```

```yaml
community: public
target: 192.0.2.1
mibs:
- ifHCInOctets
- ifHCOutOctets
interface:
include: ge-0/0/\d+$|ae0$
skip-linkdown: true
mackerel:
host-id: <192.0.2.1.id.txt というような ${target}.id.txt というファイルにホストIDが記録されているので転記してください>
x-api-key: xxxx # 環境変数を読まなくなりましたので、直接記述してください
name: sw1
```


76 changes: 76 additions & 0 deletions collector/collector.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package collector

import (
"context"

"github.com/yseto/switch-traffic-to-mackerel/config"
"github.com/yseto/switch-traffic-to-mackerel/mib"
"github.com/yseto/switch-traffic-to-mackerel/snmp"
)

type snmpClientImpl interface {
BulkWalk(oid string, length uint64) (map[uint64]uint64, error)
BulkWalkGetInterfaceName(length uint64) (map[uint64]string, error)
BulkWalkGetInterfaceState(length uint64) (map[uint64]bool, error)
Close() error
GetInterfaceNumber() (uint64, error)
}

func Do(ctx context.Context, c *config.Config) ([]MetricsDutum, error) {
snmpClient, err := snmp.Init(ctx, c.Target, c.Community)
if err != nil {
return nil, err
}
defer snmpClient.Close()
return do(ctx, snmpClient, c)
}

func do(ctx context.Context, snmpClient snmpClientImpl, c *config.Config) ([]MetricsDutum, error) {
ifNumber, err := snmpClient.GetInterfaceNumber()
if err != nil {
return nil, err
}
ifDescr, err := snmpClient.BulkWalkGetInterfaceName(ifNumber)
if err != nil {
return nil, err
}

var ifOperStatus map[uint64]bool
if c.SkipDownLinkState {
ifOperStatus, err = snmpClient.BulkWalkGetInterfaceState(ifNumber)
if err != nil {
return nil, err
}
}

metrics := make([]MetricsDutum, 0)

for _, mibName := range c.MIBs {
values, err := snmpClient.BulkWalk(mib.Oidmapping[mibName], ifNumber)
if err != nil {
return nil, err
}

for ifIndex, value := range values {
ifName := ifDescr[ifIndex]
if c.IncludeRegexp != nil && !c.IncludeRegexp.MatchString(ifName) {
continue
}

if c.ExcludeRegexp != nil && c.ExcludeRegexp.MatchString(ifName) {
continue
}

// skip when down(2)
if c.SkipDownLinkState && !ifOperStatus[ifIndex] {
continue
}

metrics = append(metrics, MetricsDutum{IfIndex: ifIndex, Mib: mibName, IfName: ifName, Value: value})
}
}
if c.Debug {
debugPrint(metrics)
}
return metrics, nil
}
164 changes: 164 additions & 0 deletions collector/collector_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
package collector

import (
"context"
"errors"
"regexp"
"testing"

"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"

"github.com/yseto/switch-traffic-to-mackerel/config"
)

type mockSnmpClient struct {
}

var errInvalid = errors.New("invalid error")

func (m *mockSnmpClient) BulkWalk(oid string, length uint64) (map[uint64]uint64, error) {
switch oid {
case "1.3.6.1.2.1.31.1.1.1.6":
return map[uint64]uint64{
1: 60,
2: 60,
3: 60,
4: 60,
}, nil
case "1.3.6.1.2.1.31.1.1.1.10":
return map[uint64]uint64{
1: 120,
2: 120,
3: 120,
4: 120,
}, nil
default:
return nil, errInvalid
}
}
func (m *mockSnmpClient) BulkWalkGetInterfaceName(length uint64) (map[uint64]string, error) {
return map[uint64]string{
1: "lo0",
2: "eth0",
3: "eth1",
4: "eth2",
}, nil
}
func (m *mockSnmpClient) BulkWalkGetInterfaceState(length uint64) (map[uint64]bool, error) {
return map[uint64]bool{
1: true,
2: false,
3: true,
4: true,
}, nil
}
func (m *mockSnmpClient) Close() error {
return nil
}
func (m *mockSnmpClient) GetInterfaceNumber() (uint64, error) {
return 4, nil
}

func TestDo(t *testing.T) {
ctx := context.Background()

t.Run("non skip", func(t *testing.T) {
c := &config.Config{
MIBs: []string{"ifHCInOctets", "ifHCOutOctets"},
}
actual, err := do(ctx, &mockSnmpClient{}, c)
if err != nil {
t.Error("invalid raised error")
}
expected := []MetricsDutum{
{IfIndex: 1, Mib: "ifHCInOctets", IfName: "lo0", Value: 60},
{IfIndex: 2, Mib: "ifHCInOctets", IfName: "eth0", Value: 60},
{IfIndex: 3, Mib: "ifHCInOctets", IfName: "eth1", Value: 60},
{IfIndex: 4, Mib: "ifHCInOctets", IfName: "eth2", Value: 60},
{IfIndex: 1, Mib: "ifHCOutOctets", IfName: "lo0", Value: 120},
{IfIndex: 2, Mib: "ifHCOutOctets", IfName: "eth0", Value: 120},
{IfIndex: 3, Mib: "ifHCOutOctets", IfName: "eth1", Value: 120},
{IfIndex: 4, Mib: "ifHCOutOctets", IfName: "eth2", Value: 120},
}
if d := cmp.Diff(
actual,
expected,
cmpopts.SortSlices(func(i, j MetricsDutum) bool { return i.String() < j.String() }),
); d != "" {
t.Errorf("invalid result %s", d)
}
})

t.Run("skip include", func(t *testing.T) {
c := &config.Config{
MIBs: []string{"ifHCInOctets", "ifHCOutOctets"},
IncludeRegexp: regexp.MustCompile("lo?"),
}
actual, err := do(ctx, &mockSnmpClient{}, c)
if err != nil {
t.Error("invalid raised error")
}
expected := []MetricsDutum{
{IfIndex: 1, Mib: "ifHCInOctets", IfName: "lo0", Value: 60},
{IfIndex: 1, Mib: "ifHCOutOctets", IfName: "lo0", Value: 120},
}
if d := cmp.Diff(
actual,
expected,
cmpopts.SortSlices(func(i, j MetricsDutum) bool { return i.String() < j.String() }),
); d != "" {
t.Errorf("invalid result %s", d)
}
})
t.Run("skip exclude", func(t *testing.T) {
c := &config.Config{
MIBs: []string{"ifHCInOctets", "ifHCOutOctets"},
ExcludeRegexp: regexp.MustCompile("0$"),
}
actual, err := do(ctx, &mockSnmpClient{}, c)
if err != nil {
t.Error("invalid raised error")
}
expected := []MetricsDutum{
{IfIndex: 3, Mib: "ifHCInOctets", IfName: "eth1", Value: 60},
{IfIndex: 4, Mib: "ifHCInOctets", IfName: "eth2", Value: 60},
{IfIndex: 3, Mib: "ifHCOutOctets", IfName: "eth1", Value: 120},
{IfIndex: 4, Mib: "ifHCOutOctets", IfName: "eth2", Value: 120},
}
if d := cmp.Diff(
actual,
expected,
cmpopts.SortSlices(func(i, j MetricsDutum) bool { return i.String() < j.String() }),
); d != "" {
t.Errorf("invalid result %s", d)
}
})

t.Run("non skip", func(t *testing.T) {
c := &config.Config{
MIBs: []string{"ifHCInOctets", "ifHCOutOctets"},
SkipDownLinkState: true,
}
actual, err := do(ctx, &mockSnmpClient{}, c)
if err != nil {
t.Error("invalid raised error")
}
expected := []MetricsDutum{
{IfIndex: 1, Mib: "ifHCInOctets", IfName: "lo0", Value: 60},
{IfIndex: 3, Mib: "ifHCInOctets", IfName: "eth1", Value: 60},
{IfIndex: 4, Mib: "ifHCInOctets", IfName: "eth2", Value: 60},
{IfIndex: 1, Mib: "ifHCOutOctets", IfName: "lo0", Value: 120},
{IfIndex: 3, Mib: "ifHCOutOctets", IfName: "eth1", Value: 120},
{IfIndex: 4, Mib: "ifHCOutOctets", IfName: "eth2", Value: 120},
}
if d := cmp.Diff(
actual,
expected,
cmpopts.SortSlices(func(i, j MetricsDutum) bool { return i.String() < j.String() }),
); d != "" {
t.Errorf("invalid result %s", d)
}
})

}
Loading

0 comments on commit 63f458d

Please sign in to comment.