Ziti probe (#81)

* initial ziti-probe implementation
* load config from commandline, fix usage message
* fix readme to match code
This commit is contained in:
ekoby
2020-04-08 11:08:45 -04:00
committed by GitHub
parent 3a28e9b5e0
commit f710ab2bc6
5 changed files with 298 additions and 0 deletions
+1
View File
@@ -21,6 +21,7 @@ require (
github.com/golang/protobuf v1.3.5
github.com/google/uuid v1.1.1
github.com/gorilla/mux v1.7.3
github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0
github.com/keybase/go-ps v0.0.0-20190827175125-91aafc93ba19
github.com/michaelquigley/pfxlog v0.0.0-20190813191113-2be43bd0dccc
+61
View File
@@ -0,0 +1,61 @@
Ziti Probe
----------
`ziti-probe` is utility application allowing to collect network metrics from the _edge_.
## Setup
create config type for metric collection service with the following schema (this is optional)
```json
{
"$id": "http://ziti-edge.netfoundry.io/schemas/ziti-probe-config.v1.json",
"type":"object",
"additionalProperties": false,
"required": [],
"properties":{
"dbType": {
"type":"string"
},
"dbName": {
"type": "string"
},
"interval": {
"type": "integer",
"minimum": 1,
"maximum": 3600
}
}
}
```
create service configuration `probe-service-cfg` (exact name does not matter,
it wil be used to configure the service in the next step), This is optional
```js
{
"dbType": "influxdb", // only influxdb is currently supported
"dbName": "ziti", // name of database in Influxdb, ziti is the default
"interval": 60 // how often metrics are uploaded (in seconds), 60 is the default
}
```
create service with a router termination
```shell script
$ ziti edge controller create service probe-service probe-service-cfg
$ ziti edge controller create terminator probe-service router1 tcp:127.0.0.1:8086
```
in this example `router1` is the name of the fabric router that has access to influxDB server,
and `tcp:127.0.0.1:8086` is the address of influxDB server. Adjust as needed.
create ziti identity(ies) for your probe(s). `ziti-probe` is a Ziti-SDK-enabled application,
so standard enrollment procedure should be followed.
## Running `ziti-probe`
```shell script
$ ziti-probe run <identity-file>
```
#### What's happening?
1. `ziti-probe` authenticates to Ziti controller with provided identity
1. it checks that `probe-service` is available and get configuration. If configuration is not assigned default values are used.
1. it opens connections to all edge routers configured for `probe-service` (refer to ziti policies)
1. each connection performs a round-trip latency probe to each of edge routers every 30 seconds
1. `ziti-probe` then submits collected metrics to `probe-service`.
metric names are in the following format: `latency.<edgeRouterAddress>`, e.g `latency.tls://54.145.23.14:3022`
+60
View File
@@ -0,0 +1,60 @@
/*
Copyright NetFoundry, Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
https://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package subcmd
import (
"fmt"
"github.com/michaelquigley/pfxlog"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
)
func init() {
root.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "Enable verbose logging")
root.PersistentFlags().StringVar(&logFormatter, "log-formatter", "", "Specify log formatter [json|pfxlog|text]")
}
var root = &cobra.Command{
Use: "ziti-probe",
Short: "Ziti Probe",
PersistentPreRun: func(cmd *cobra.Command, args []string) {
if verbose {
logrus.SetLevel(logrus.DebugLevel)
}
switch logFormatter {
case "pfxlog":
logrus.SetFormatter(pfxlog.NewFormatterStartingToday())
case "json":
logrus.SetFormatter(&logrus.JSONFormatter{})
case "text":
logrus.SetFormatter(&logrus.TextFormatter{})
default:
// let logrus do its own thing
}
},
}
var verbose bool
var logFormatter string
func Execute() {
if err := root.Execute(); err != nil {
fmt.Printf("error: %s\n", err)
}
}
+158
View File
@@ -0,0 +1,158 @@
package subcmd
import (
"context"
influxdb "github.com/influxdata/influxdb1-client"
"github.com/michaelquigley/pfxlog"
"github.com/netfoundry/ziti-foundation/metrics"
"github.com/netfoundry/ziti-foundation/metrics/metrics_pb"
"github.com/netfoundry/ziti-sdk-golang/ziti"
"github.com/netfoundry/ziti-sdk-golang/ziti/config"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"net"
"net/http"
"net/url"
"os"
"reflect"
"sync/atomic"
"time"
"unsafe"
)
var runCmd = &cobra.Command{
Use: "run <config>",
Short: "Runs ziti probe",
Long: "runs ziti probe: captures latency to edge routers and submits them to probe-service",
Args: cobra.MaximumNArgs(1),
Run: theProbe.run,
}
func init() {
root.AddCommand(runCmd)
}
var log = pfxlog.Logger()
type probeCfg struct {
dbName string
dbType string
interval int
}
var defaultConfig = probeCfg{
dbName: "ziti",
dbType: "influxdb",
interval: 5 * 60, // 5 minutes
}
type probe struct{
cfg *probeCfg
indb *influxdb.Client
ctx ziti.Context
latest atomic.Value
closer chan interface{}
}
var theProbe = &probe{
cfg: &defaultConfig,
closer: make(chan interface{}),
}
func (p *probe) AcceptMetrics(message *metrics_pb.MetricsMessage) {
p.latest.Store(message)
}
func (p *probe) sendMetrics() {
for {
select {
case <-time.After(time.Duration(p.cfg.interval) * time.Second):
message := p.latest.Load().(*metrics_pb.MetricsMessage)
if message == nil {
continue
}
bp, err := metrics.AsBatch(message)
if err != nil {
logrus.Errorln(err)
return
}
bp.Database = p.cfg.dbName
_, err = p.indb.Write(*bp)
if err != nil {
logrus.Errorln(err)
}
}
}
}
func (p *probe) run(cmd *cobra.Command, args []string) {
var err error
if len(args) == 0 {
log.Infof("loading ziti identity from env.ZITI_SDK_CONFIG[%s]", os.Getenv("ZITI_SDK_CONFIG"))
p.ctx = ziti.NewContext()
} else {
if cfg, err := config.NewFromFile(args[0]); err != nil {
log.Fatalf("failed to load config from file[%s]", args[0], err)
} else {
p.ctx = ziti.NewContextWithConfig(cfg)
}
}
_, _ = p.ctx.GetServices()
_, found := p.ctx.GetService("probe-service")
if !found {
log.Fatal("required service was not found")
}
p.indb, err = p.createInfluxDbClient()
if err != nil {
log.Error(err)
return
}
_, res, err := p.indb.Ping()
if err != nil {
log.Fatalf("failed to get server info", err)
}
log.Info("connected to influx version = ", res)
p.ctx.Metrics().EventController().AddHandler(p)
go p.sendMetrics()
select {
case <-p.closer:
}
p.ctx.Close()
}
func (p *probe) createInfluxDbClient() (*influxdb.Client, error) {
httpC := &http.Client{
Transport: &http.Transport{
DialContext: func (c context.Context, network, addr string) (net.Conn, error) {
log.Infof("connecting to %s:%s", network, addr)
return p.ctx.Dial("probe-service")
},
},
}
config := influxdb.NewConfig()
u, _ := url.Parse("http://probe-service")
config.URL = *u
clt, _ := influxdb.NewClient(config)
ic := reflect.ValueOf(clt)
ict := ic.Type()
hcf, _ := ict.Elem().FieldByName("httpClient")
hcp := unsafe.Pointer(uintptr(unsafe.Pointer(clt)) + hcf.Offset)
hcpp := (**http.Client)(hcp)
*hcpp = httpC
return clt, nil
}
+18
View File
@@ -0,0 +1,18 @@
package main
import (
"github.com/michaelquigley/pfxlog"
"github.com/netfoundry/ziti-cmd/ziti-probe/subcmd"
"github.com/netfoundry/ziti-foundation/util/debugz"
"github.com/sirupsen/logrus"
)
func init() {
pfxlog.Global(logrus.InfoLevel)
pfxlog.SetPrefix("github.com/netfoundry/")
}
func main() {
debugz.AddStackDumpHandler()
subcmd.Execute()
}