Skip to content
This repository has been archived by the owner on Nov 8, 2022. It is now read-only.

Commit

Permalink
Merge pull request #84 from jcooklin/fb/adds-standalone-support
Browse files Browse the repository at this point in the history
Adds standalone and diagnostic modes
  • Loading branch information
jcooklin authored May 9, 2017
2 parents 3527311 + 416a5cc commit d1d32a0
Show file tree
Hide file tree
Showing 17 changed files with 1,165 additions and 391 deletions.
38 changes: 37 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,40 @@ A Snap plugin is a program that responds to a set of well defined [gRPC](http://
You will find [example plugins](examples) that cover the basics for writing collector, processor, and publisher plugins in the examples folder.



## Snap Diagnostics
Snap plugins using plugin-lib-go can be run independent of Snap to show their current running diagnostics. This diagnostic information includes:
* Warning if dependencies not met
* Config policy
* Warning if config items required and not provided
* Collectable metrics
* Runtime details
* Plugin version
* RPC type and version
* OS, architecture
* Golang version
* How long it took to run each of these diagnostics

### Running Diagnostics
Running plugin diagnostics is easy! Simply build the plugin, then run the executable `$./build/${GOOS}/${GOARCH}/<plugin binary>`. When ran on its own, it will show a warning if a config is required for the plugin to load.

### Global Flags
For specific details and to see all the options when running, run the plugin with the `-help` flag. The flag options are:
```
GLOBAL OPTIONS:
--config value config to use in JSON format
--port value port GRPC will listen on
--pprof enable pprof
--tls enable TLS
--cert-path value necessary to provide when TLS enabled
--key-path value necessary to provide when TLS enabled
--root-cert-paths value root paths separated by ':'
--stand-alone enable stand alone plugin
--stand-alone-port value specify http port when stand-alone is set (default: 8181)
--log-level value log level - 0:panic 1:fatal 2:error 3:warn 4:info 5:debug (default: 2)
--required-config Plugin requires config passed in
--help, -h show help
--version, -v print the version
```

### Config flag
When `-config` is set, it expects a parameter in the form of a JSON. This is of the form `'{}'`. An example config is: `-config '{\"key\":\"kelly\", \"spirit-animal\":\"coatimundi\"}'`.
12 changes: 12 additions & 0 deletions examples/snap-plugin-collector-rand/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -140,3 +140,15 @@ package rand
You've made a plugin! Now it's time to share it. Create a release by following these [steps](https://help.github.com/articles/creating-releases/). We recommend that your release version match your plugin version, see example [here](https://github.com/intelsdi-x/snap-plugin-lib-go/blob/master/examples/snap-plugin-collector-rand/main.go#L29).

Don't forget to announce your plugin release on [slack](https://intelsdi-x.herokuapp.com/) and get your plugin added to the [Plugin Catalog](https://github.com/intelsdi-x/snap/blob/master/docs/PLUGIN_CATALOG.md)!

## Notice
This plugin is also used for testing snap-plugin-lib-go. For that reason, we have added some additional capabilities to it for testing the following situations:
- mock situation when a plugin requires a config to load
- **How to test:**
When running plugin set the `-required-config` flag. Ex: `./snap-plugin-collector-rand -required-config`
When this is set, the plugin will expect you to pass in a config item with the key `value`. If you don't pass in a config item or if that config doesn't contain the `value` key, an error will be thrown saying missing config item. Example: `./snap-plugin-collector-rand -required-config -config '{"value":"something"}'`

- mock situation when a plugin does not have required dependencies
- **How to test:**
When running the plugin, pass the `depsReq` key in through the config. Example: `./snap-plugin-collector-rand -required-config -config '{"depsReq":true}'`
This will always throw an error, as it is simply testing how the plugin-lib handles the error when thrown.
54 changes: 50 additions & 4 deletions examples/snap-plugin-collector-rand/rand/rand.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import (
"errors"

"github.com/intelsdi-x/snap-plugin-lib-go/v1/plugin"
"github.com/urfave/cli"
)

var (
Expand Down Expand Up @@ -54,8 +55,23 @@ var (
}
)

var req bool = false

func init() {
rand.Seed(42)

//The required-config flag is added for testing plugin-lib-go.
//When the flag is set, an additional policy will be added in GetConfigPolicy().
//This additional policy has a required field. This simulates
//the situation when a plugin requires a config to load.
plugin.AddFlag(
cli.BoolFlag{
Name: "required-config",
Hidden: false,
Usage: "Plugin requires config passed in",
Destination: &req,
},
)
}

// Mock collector implementation used for testing
Expand All @@ -79,7 +95,15 @@ func (RandCollector) CollectMetrics(mts []plugin.Metric) ([]plugin.Metric, error
if val, err := mt.Config.GetBool("testbool"); err == nil && val {
continue
}
if mt.Namespace[len(mt.Namespace)-1].Value == "integer" {

if mt.Namespace[0].Value == "static" {
var err error
mts[idx].Data, err = mts[idx].Config.GetString("value")
if err != nil {
return nil, fmt.Errorf("Invalid or missing value key.")
}
metrics = append(metrics, mts[idx])
} else if mt.Namespace[len(mt.Namespace)-1].Value == "integer" {
if val, err := mt.Config.GetInt("testint"); err == nil {
mts[idx].Data = val
} else {
Expand Down Expand Up @@ -117,8 +141,16 @@ func (RandCollector) CollectMetrics(mts []plugin.Metric) ([]plugin.Metric, error
The metrics returned will be advertised to users who list all the metrics and will become targetable by tasks.
*/
func (RandCollector) GetMetricTypes(cfg plugin.Config) ([]plugin.Metric, error) {
metrics := []plugin.Metric{}
//Simulate throwing error when a config is required but not passed in
if req && len(cfg) < 1 {
return nil, fmt.Errorf("! When -required-config is set you must provide a config. Example: -config '{\"key\":\"kelly\", \"spirit-animal\":\"coatimundi\"}'\n")
}
//Simulate throwing error when dependencies not met
if _, ok := cfg["depsReq"]; ok {
return nil, fmt.Errorf("! Dependency XX required. Run `make deps` to resolve.")
}

metrics := []plugin.Metric{}
vals := []string{"integer", "float", "string"}
for _, val := range vals {
metric := plugin.Metric{
Expand All @@ -127,7 +159,12 @@ func (RandCollector) GetMetricTypes(cfg plugin.Config) ([]plugin.Metric, error)
}
metrics = append(metrics, metric)
}

if req {
metrics = append(metrics, plugin.Metric{
Namespace: plugin.NewNamespace("static", "string"),
Version: 1,
})
}
return metrics, nil
}

Expand All @@ -141,6 +178,14 @@ func (RandCollector) GetMetricTypes(cfg plugin.Config) ([]plugin.Metric, error)
func (RandCollector) GetConfigPolicy() (plugin.ConfigPolicy, error) {
policy := plugin.NewConfigPolicy()

//The required-config flag is used for testing plugin-lib-go
if req {
policy.AddNewStringRule([]string{"static", "string"},
"value",
true,
)
}

policy.AddNewIntRule([]string{"random", "integer"},
"testint",
false,
Expand All @@ -159,6 +204,7 @@ func (RandCollector) GetConfigPolicy() (plugin.ConfigPolicy, error) {

policy.AddNewBoolRule([]string{"random"},
"testbool",
false)
false,
plugin.SetDefaultBool(false))
return *policy, nil
}
2 changes: 2 additions & 0 deletions glide.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,5 @@ import:
- naming
- peer
- transport
- package: github.com/Sirupsen/logrus
version: ^0.11.5
4 changes: 2 additions & 2 deletions v1/plugin/collector_proxy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,8 @@ func TestGetMetricTypes(t *testing.T) {
for _, m := range r.GetMetrics() {
tm := fromProtoMetric(m)
idx := fmt.Sprintf("%s.%d", tm.Namespace, tm.Version)
So(tm.Namespace.Strings(), ShouldResemble, metricMap[idx].Namespace.Strings())
So(tm.Tags, ShouldEqual, metricMap[idx].Tags)
So(tm.Namespace.Strings(), ShouldResemble, getMockMetricDataMap()[idx].Namespace.Strings())
So(tm.Tags, ShouldResemble, getMockMetricDataMap()[idx].Tags)
}
})
Convey("invalid metric types", func() {
Expand Down
56 changes: 56 additions & 0 deletions v1/plugin/flags.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package plugin

import (
"fmt"
"path/filepath"

"github.com/urfave/cli"
)

var (
flConfig = cli.StringFlag{
Name: "config",
Usage: "config to use in JSON format",
}
// If no port was provided we let the OS select a port for us.
// This is safe as address is returned in the Response and keep
// alive prevents unattended plugins.
flPort = cli.StringFlag{
Name: "port",
Usage: "port GRPC will listen on",
}
flLogLevel = cli.IntFlag{
Name: "log-level",
Usage: "log level - 0:panic 1:fatal 2:error 3:warn 4:info 5:debug",
Value: 2,
}
flPprof = cli.BoolFlag{
Name: "pprof",
Usage: "enable pprof",
}
flTLS = cli.BoolFlag{
Name: "tls",
Usage: "enable TLS",
}
flCertPath = cli.StringFlag{
Name: "cert-path",
Usage: "necessary to provide when TLS enabled",
}
flKeyPath = cli.StringFlag{
Name: "key-path",
Usage: "necessary to provide when TLS enabled",
}
flRootCertPaths = cli.StringFlag{
Name: "root-cert-paths",
Usage: fmt.Sprintf("root paths separated by '%c'", filepath.ListSeparator),
}
flStandAlone = cli.BoolFlag{
Name: "stand-alone",
Usage: "enable stand alone plugin",
}
flHTTPPort = cli.IntFlag{
Name: "stand-alone-port",
Usage: "specify http port when stand-alone is set",
Value: 8181,
}
)
14 changes: 14 additions & 0 deletions v1/plugin/grpc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ package plugin
import (
"fmt"
"net"
"os"
"reflect"
"strings"
"sync"
Expand All @@ -37,6 +38,19 @@ import (
. "github.com/smartystreets/goconvey/convey"
)

func init() {
// Filter out go test flags
getOSArgs = func() []string {
args := []string{}
for _, v := range os.Args {
if !strings.HasPrefix(v, "-test") {
args = append(args, v)
}
}
return args
}
}

const GrpcTimeoutDefault = 2 * time.Second

var (
Expand Down
55 changes: 37 additions & 18 deletions v1/plugin/grpc_tls_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,31 +25,42 @@ import (
"crypto/tls"
"crypto/x509"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strings"
"sync/atomic"
"testing"

. "github.com/smartystreets/goconvey/convey"
"github.com/urfave/cli"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"

"github.com/intelsdi-x/snap-plugin-lib-go/v1/plugin/rpc"
)

const (
maxPingTimeoutLimit = 65535
)

var (
mockInputOutputInUse *mockInputOutput
mockInputRootCerts []string
prevPingTimeoutLimit int
grpcOptsBuilderInUse *grpcOptsBuilder
)

func init() {
// Filter out go test flags
getOSArgs = func() []string {
args := []string{}
for _, v := range os.Args {
if !strings.HasPrefix(v, "-test") {
args = append(args, v)
}
}
return args
}
}

type testProxyCtor struct {
prevProxyCtor pluginProxyConstructor
onCreateCallback func(Plugin, *pluginProxy)
Expand Down Expand Up @@ -128,36 +139,45 @@ func newGrpcOptsBuilder() *grpcOptsBuilder {
}

func TestIncorrectPluginArgsFail(t *testing.T) {
// The tests below are designed to recover from a panic so we interpret
// the return code and panic if the grpc server could not be created.
cli.OsExiter = func(ret int) {
// a return code of 2 indicates a failure to build the grpc server
if ret == 2 {
panic(errors.New("failed to create grpc server in test"))
}
os.Exit(ret)
}
Convey("Intending to start secure plugin server", t, func() {
setUpSecureTestcase(true, true)
Convey("omitting Cert Path from arguments will make plugin fail", func() {
mockInputOutputInUse.mockArgs = strings.Fields(fmt.Sprintf(`mock
mockInputOutputInUse.mockArg = fmt.Sprintf(`
{"KeyPath":"%s","TLSEnabled":true}`,
tlsTestSrv+keyFileExt))
tlsTestSrv+keyFileExt)
So(func() {
startSecureGrpcPlugin(t, &mockCollector{}, collectorType, "mock-coll")
}, ShouldPanic)
})
Convey("omitting Key Path from arguments will make plugin fail", func() {
mockInputOutputInUse.mockArgs = strings.Fields(fmt.Sprintf(`mock
mockInputOutputInUse.mockArg = fmt.Sprintf(`
{"CertPath":"%s","TLSEnabled":true}`,
tlsTestSrv+crtFileExt))
tlsTestSrv+crtFileExt)
So(func() {
startSecureGrpcPlugin(t, &mockCollector{}, collectorType, "mock-coll")
}, ShouldPanic)
})
Convey("omitting TLSEnabled flag from arguments will make plugin fail", func() {
mockInputOutputInUse.mockArgs = strings.Fields(fmt.Sprintf(`mock
mockInputOutputInUse.mockArg = fmt.Sprintf(`
{"CertPath":"%s","KeyPath":"%s"}`,
tlsTestSrv+crtFileExt, tlsTestSrv+keyFileExt))
tlsTestSrv+crtFileExt, tlsTestSrv+keyFileExt)
So(func() {
startSecureGrpcPlugin(t, &mockCollector{}, collectorType, "mock-coll")
}, ShouldPanic)
})
Convey("adding mismatched certificate and key in arguments will make plugin fail", func() {
mockInputOutputInUse.mockArgs = strings.Fields(fmt.Sprintf(`mock
mockInputOutputInUse.mockArg = fmt.Sprintf(`
{"CertPath":"%s","KeyPath":"%s","TLSEnabled":true}`,
tlsTestSrv+crtFileExt, tlsTestCli+keyFileExt))
tlsTestSrv+crtFileExt, tlsTestCli+keyFileExt)
So(func() {
startSecureGrpcPlugin(t, &mockCollector{}, collectorType, "mock-coll")
}, ShouldPanic)
Expand Down Expand Up @@ -363,17 +383,16 @@ func setUpSecureTestcase(serverTLSUp, clientTLSUp bool) {
libInputOutput = mockInputOutputInUse
grpcOptsBuilderInUse = newGrpcOptsBuilder()
if serverTLSUp {
rootCertPathsArg := ""
var certPaths string
if len(mockInputRootCerts) > 0 {
certPaths = strings.Join(mockInputRootCerts, string(filepath.ListSeparator))
} else {
certPaths = tlsTestCA + crtFileExt
}
rootCertPathsArg = fmt.Sprintf(`,"RootCertPaths":"%s"`, certPaths)
mockInputOutputInUse.mockArgs = strings.Fields(fmt.Sprintf(`mock
{"CertPath":"%s","KeyPath":"%s","TLSEnabled":true%s}`,
tlsTestSrv+crtFileExt, tlsTestSrv+keyFileExt, rootCertPathsArg))
rootCertPathsArg := fmt.Sprintf(`"RootCertPaths":"%s"`, certPaths)
mockInputOutputInUse.mockArg = fmt.Sprintf(`
{"CertPath":"%s","KeyPath":"%s","TLSEnabled":true,"LogLevel":5,%s}`,
tlsTestSrv+crtFileExt, tlsTestSrv+keyFileExt, rootCertPathsArg)
}
if clientTLSUp {
grpcOptsBuilderInUse.
Expand Down
Loading

0 comments on commit d1d32a0

Please sign in to comment.