Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
3bc67d6
Initialize project.
robertodauria Mar 18, 2020
dcab292
Initialize project.
robertodauria Mar 18, 2020
a5dda6f
Remove hardcoded path.
robertodauria Mar 18, 2020
e733c63
Remove hardcoded path.
robertodauria Mar 18, 2020
85cb760
Remove binary file committed by mistake. :(
robertodauria Mar 18, 2020
6e4415d
Remove binary file committed by mistake. :(
robertodauria Mar 18, 2020
7dcf428
Add newline at the end of .gitignore.
robertodauria Mar 18, 2020
3facca8
Add newline at the end of .gitignore.
robertodauria Mar 18, 2020
497094e
Move client/siteinfo packages to root folder.
robertodauria Mar 19, 2020
661bcfa
Move client/siteinfo packages to root folder.
robertodauria Mar 19, 2020
9fb31dd
Parse flags before referencing them.
robertodauria Mar 19, 2020
fb309b0
Parse flags before referencing them.
robertodauria Mar 19, 2020
0d0605b
Renames.
robertodauria Mar 19, 2020
f82678c
Renames.
robertodauria Mar 19, 2020
68ed1eb
Add a basic .travis.yml.
robertodauria Mar 19, 2020
de9b263
Add a basic .travis.yml.
robertodauria Mar 19, 2020
aeb0780
Update README.md.
robertodauria Mar 19, 2020
7561cb4
Update README.md.
robertodauria Mar 19, 2020
d0b3545
Some renames + unit tests for main.go.
robertodauria Mar 20, 2020
4cc3bbc
Some renames + unit tests for main.go.
robertodauria Mar 20, 2020
7ec30a6
Add tests for netconf and interfaces.
robertodauria Mar 20, 2020
9c08268
Add tests for netconf and interfaces.
robertodauria Mar 20, 2020
10fa372
Change GetConfigHash to GetConfig, add tests.
robertodauria Mar 20, 2020
20a3bab
Change GetConfigHash to GetConfig, add tests.
robertodauria Mar 20, 2020
b1ecd85
Add unit tests.
robertodauria Mar 24, 2020
f2acc13
Add unit tests.
robertodauria Mar 24, 2020
6fd5fd0
Actually check _something_ in GetConfig() output.
robertodauria Mar 24, 2020
79be78e
Actually check _something_ in GetConfig() output.
robertodauria Mar 24, 2020
3d5f7ef
Remove siteinfo_test.go since tests aren't implemented anyway.
robertodauria Mar 24, 2020
ff12f9f
Remove siteinfo_test.go since tests aren't implemented anyway.
robertodauria Mar 24, 2020
9aacbcf
Replace SNMP community strings.
robertodauria Mar 24, 2020
ab757e0
Replace SNMP community strings.
robertodauria Mar 24, 2020
3f0d710
Get 100% test coverage.
robertodauria Mar 24, 2020
f0aa428
Merge branch 'sandbox-init' of github.com:m-lab/switch-monitoring int…
robertodauria Mar 24, 2020
686af4d
Added go vet and go test -race.
robertodauria Mar 24, 2020
110b0e1
Simplify siteinfo's unittests.
robertodauria Mar 25, 2020
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
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,5 @@

# Dependency directories (remove the comment below to include it)
# vendor/

.vscode/
13 changes: 13 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
language: go
go:
- "1.13"

before_script:
- go get golang.org/x/tools/cmd/cover
- go get github.com/mattn/goveralls

script:
- go test -covermode=count -coverprofile=profile.cov ./...
- go vet ./...
- go test ./... -race
- goveralls -coverprofile=profile.cov -service=travis-ci
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,6 @@
# switch-monitoring
Go service to monitor the M-Lab switches for changes from the expected configuration.

| branch | travis-ci | coveralls | docs | report card |
|--------|-----------|-----------|------|-------------|
| master | [![Travis Build Status](https://travis-ci.org/m-lab/switch-monitoring.svg?branch=master)](https://travis-ci.org/m-lab/switch-monitoring) | [![Coverage Status](https://coveralls.io/repos/m-lab/switch-monitoring/badge.svg?branch=master)](https://coveralls.io/github/m-lab/switch-monitoring?branch=master) | [![GoDoc](https://godoc.org/github.com/m-lab/switch-monitoring?status.svg)](https://godoc.org/github.com/m-lab/switch-monitoring) | [![Go Report Card](https://goreportcard.com/badge/github.com/m-lab/switch-monitoring)](https://goreportcard.com/report/github.com/m-lab/switch-monitoring)
119 changes: 119 additions & 0 deletions cmd/switch-monitoring/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
package main

import (
"encoding/json"
"flag"
"fmt"
"net/http"
"os"
"time"

"github.com/apex/log"
"github.com/apex/log/handlers/text"
"github.com/scottdware/go-junos"

"github.com/m-lab/go/flagx"
"github.com/m-lab/go/rtx"
"github.com/m-lab/switch-monitoring/internal"
"github.com/m-lab/switch-monitoring/internal/netconf"
"github.com/m-lab/switch-monitoring/internal/siteinfo"
)

const (
defaultProjectID = "mlab-oti"
switchHostFormat = "s1.%s.measurement-lab.org"
httpClientTimeout = time.Second * 15
)

var (
flagProject = flag.String("project", defaultProjectID,
"Use a specific GCP Project ID.")
flagPrivateKey = flag.String("key", "",
"Path to the SSH private key to use.")
flagPassphrase = flag.String("pass", "",
"Passphrase to decrypt the private key. Can be omitted.")
flagDebug = flag.Bool("debug", true, "Show debug messages.")

osExit = os.Exit
newNetconf = func(auth *junos.AuthMethod) internal.NetconfClient {
return netconf.New(auth)
}

httpClient = func(timeout time.Duration) internal.HTTPProvider {
return &http.Client{
Timeout: timeout,
}
}
)

func main() {
flag.Parse()

if *flagDebug {
log.SetLevel(log.DebugLevel)
}
log.SetHandler(text.New(os.Stdout))

rtx.Must(flagx.ArgsFromEnv(flag.CommandLine), "Cannot parse env args")

// A private key must be provided.
if *flagPrivateKey == "" {
log.Error("The SSH private key must be provided.")
osExit(1)
}

// Initialize Siteinfo provider and the NETCONF client.
auth := &junos.AuthMethod{
Username: "root",
PrivateKey: *flagPrivateKey,
Passphrase: *flagPassphrase,
}
c := newNetconf(auth)

// Get switches list.
log.Infof("Fetching switch list for project %s", *flagProject)
_, err := switches(*flagProject)
rtx.Must(err, "Cannot fetch the switch list")

// TODO: loop over the switches list.
// This is just an example of the intended usage.
hash, err := c.GetConfig("s1.lga0t.measurement-lab.org")
if err != nil {
log.WithFields(log.Fields{
"hostname": "s1.lga0t.measurement-lab.org",
}).WithError(err).Error("Connection failed")
}

log.Info(hash)
}

// switches downloads the switches.json file from siteinfo and generates a
// list of valid switch hostnames.
func switches(projectID string) ([]string, error) {
var switches map[string]interface{}

client := siteinfo.New(projectID, httpClient(httpClientTimeout))
switchesJSON, err := client.Switches()
if err != nil {
return nil, err
}

err = json.Unmarshal(switchesJSON, &switches)
if err != nil {
return nil, err
}

if len(switches) == 0 {
return nil, fmt.Errorf("the retrieved switches list is empty")
}

hosts := make([]string, len(switches))

i := 0
for k := range switches {
hosts[i] = fmt.Sprintf(switchHostFormat, k)
i++
}

return hosts, nil
}
168 changes: 168 additions & 0 deletions cmd/switch-monitoring/main_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
package main

import (
"bytes"
"fmt"
"io/ioutil"
"net/http"
"os"
"testing"
"time"

"github.com/m-lab/go/osx"
"github.com/m-lab/switch-monitoring/internal"
"github.com/scottdware/go-junos"
"github.com/stretchr/testify/assert"
)

//
// Mocks used in the subsequent unit tests.
//

type mockNetconf struct {
// How many times GetConfigHash has been called.
getConfigCalled int
mustFail bool
}

func (n *mockNetconf) GetConfig(hostname string, section ...string) (string, error) {
n.getConfigCalled++
if n.mustFail {
return "", fmt.Errorf("error")
}
return "not implemented", nil
}

type mockHTTPProvider struct {
// How many times Get has been called.
getCalled int
mustFail bool
responseBody string
}

func (prov *mockHTTPProvider) Get(string) (*http.Response, error) {
prov.getCalled++
if prov.mustFail {
return nil, fmt.Errorf("error")
}
return &http.Response{
Body: ioutil.NopCloser(bytes.NewBufferString(prov.responseBody)),
StatusCode: http.StatusOK,
}, nil
}

//
// Tests.
//

func Test_main(t *testing.T) {
assert := assert.New(t)
netconf := &mockNetconf{}
siteinfo := &mockHTTPProvider{
responseBody: `{"abc01": {}}`,
}

oldNewNetconf := newNetconf
newNetconf = func(auth *junos.AuthMethod) internal.NetconfClient {
return netconf
}

oldHTTPClient := httpClient
httpClient = func(timeout time.Duration) internal.HTTPProvider {
return siteinfo
}

// Replace osExit so that tests don't stop running.
osExit = func(code int) {
if code != 1 {
t.Fatalf("Expected a 1 exit code, got %d.", code)
}

panic("os.Exit called")
}

defer func() {
osExit = os.Exit
}()

// If no SSH key is provided, main() shoud fail.
assert.PanicsWithValue("os.Exit called", main,
"os.Exit was not called")

restore := osx.MustSetenv("KEY", "/path/to/key")

main()
if netconf.getConfigCalled == 0 {
t.Errorf("GetConfig() has not been called.")
}

if siteinfo.getCalled == 0 {
t.Errorf("Get() has not been called.")
}

// Make GetConfig() fail.
netconf.mustFail = true
main()
netconf.mustFail = false

restore()
newNetconf = oldNewNetconf
httpClient = oldHTTPClient

}

func Test_newNetconf(t *testing.T) {
netconf := newNetconf(&junos.AuthMethod{})
if netconf == nil {
t.Errorf("newNetconf() returned nil.")
}
}

func Test_httpClient(t *testing.T) {
client := httpClient(0)
if client == nil {
t.Errorf("httpClient() returned nil.")
}
}

func Test_switches(t *testing.T) {
siteinfo := &mockHTTPProvider{}

oldHTTPClient := httpClient
httpClient = func(timeout time.Duration) internal.HTTPProvider {
return siteinfo
}

siteinfo.responseBody = `{"abc01": {}}`
res, err := switches("test")
if err != nil {
t.Errorf("switches() returned err: %v", err)
}
if len(res) != 1 {
t.Errorf("switches(): expected one string, found %v", len(res))
}

// Get() fails.
siteinfo.mustFail = true
res, err = switches("test")
if err == nil {
t.Errorf("switches(): expected err, got nil.")
}
siteinfo.mustFail = false

// No content.
siteinfo.responseBody = ``
res, err = switches("test")
if err == nil {
t.Errorf("switches(): expected err, got nil.")
}

// JSON is an empty object.
siteinfo.responseBody = `{}`
res, err = switches("test")
if err == nil {
t.Errorf("switches(): expected err, got nil.")
}

httpClient = oldHTTPClient
}
11 changes: 11 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
module github.com/m-lab/switch-monitoring

go 1.13

require (
github.com/apex/log v1.1.2
github.com/m-lab/go v1.2.2
github.com/scottdware/go-junos v0.0.0-20191101184514-da1ec4631b03
github.com/stretchr/testify v1.4.0
google.golang.org/api v0.15.0
)
Loading