From 3b42558b095c27ed9c69144d984fafe3a5b615f2 Mon Sep 17 00:00:00 2001 From: mars Date: Tue, 8 Jan 2019 09:40:22 +0800 Subject: [PATCH] add package --- vendor/github.com/hashicorp/go-hclog/.gitignore | 1 + vendor/github.com/hashicorp/go-hclog/LICENSE | 21 + vendor/github.com/hashicorp/go-hclog/README.md | 133 +++ vendor/github.com/hashicorp/go-hclog/global.go | 34 + vendor/github.com/hashicorp/go-hclog/int.go | 457 +++++++ vendor/github.com/hashicorp/go-hclog/log.go | 161 +++ .../github.com/hashicorp/go-hclog/logger_test.go | 513 ++++++++ vendor/github.com/hashicorp/go-hclog/nulllogger.go | 47 + .../hashicorp/go-hclog/nulllogger_test.go | 44 + vendor/github.com/hashicorp/go-hclog/stacktrace.go | 108 ++ vendor/github.com/hashicorp/go-hclog/stdlog.go | 62 + .../github.com/hashicorp/go-hclog/stdlog_test.go | 63 + vendor/github.com/hashicorp/go-plugin/.gitignore | 1 + vendor/github.com/hashicorp/go-plugin/LICENSE | 353 ++++++ vendor/github.com/hashicorp/go-plugin/README.md | 168 +++ vendor/github.com/hashicorp/go-plugin/client.go | 792 ++++++++++++ .../hashicorp/go-plugin/client_posix_test.go | 104 ++ .../github.com/hashicorp/go-plugin/client_test.go | 942 +++++++++++++++ vendor/github.com/hashicorp/go-plugin/discover.go | 28 + .../github.com/hashicorp/go-plugin/docs/README.md | 12 + .../go-plugin/docs/guide-plugin-write-non-go.md | 150 +++ .../hashicorp/go-plugin/docs/internals.md | 63 + vendor/github.com/hashicorp/go-plugin/error.go | 24 + .../github.com/hashicorp/go-plugin/error_test.go | 26 + .../hashicorp/go-plugin/examples/basic/.gitignore | 3 + .../hashicorp/go-plugin/examples/basic/README.md | 14 + .../examples/basic/commons/greeter_interface.go | 62 + .../hashicorp/go-plugin/examples/basic/main.go | 62 + .../examples/basic/plugin/greeter_impl.go | 52 + .../go-plugin/examples/bidirectional/README.md | 48 + .../go-plugin/examples/bidirectional/main.go | 81 ++ .../examples/bidirectional/plugin-go-grpc/main.go | 61 + .../examples/bidirectional/proto/kv.pb.go | 350 ++++++ .../examples/bidirectional/proto/kv.proto | 36 + .../examples/bidirectional/shared/grpc.go | 103 ++ .../examples/bidirectional/shared/interface.go | 59 + .../hashicorp/go-plugin/examples/grpc/.gitignore | 5 + .../hashicorp/go-plugin/examples/grpc/README.md | 71 ++ .../hashicorp/go-plugin/examples/grpc/main.go | 67 ++ .../go-plugin/examples/grpc/plugin-go-grpc/main.go | 34 + .../examples/grpc/plugin-go-netrpc/main.go | 31 + .../examples/grpc/plugin-python/kv_pb2.py | 317 +++++ .../examples/grpc/plugin-python/kv_pb2_grpc.py | 55 + .../examples/grpc/plugin-python/plugin.py | 54 + .../go-plugin/examples/grpc/proto/kv.pb.go | 229 ++++ .../go-plugin/examples/grpc/proto/kv.proto | 22 + .../go-plugin/examples/grpc/shared/grpc.go | 47 + .../go-plugin/examples/grpc/shared/interface.go | 56 + .../go-plugin/examples/grpc/shared/rpc.go | 42 + .../github.com/hashicorp/go-plugin/grpc_broker.go | 455 +++++++ .../hashicorp/go-plugin/grpc_broker.pb.go | 190 +++ .../hashicorp/go-plugin/grpc_broker.proto | 14 + .../github.com/hashicorp/go-plugin/grpc_client.go | 107 ++ .../hashicorp/go-plugin/grpc_client_test.go | 106 ++ .../github.com/hashicorp/go-plugin/grpc_server.go | 132 ++ vendor/github.com/hashicorp/go-plugin/log_entry.go | 73 ++ .../github.com/hashicorp/go-plugin/mux_broker.go | 204 ++++ vendor/github.com/hashicorp/go-plugin/plugin.go | 58 + .../github.com/hashicorp/go-plugin/plugin_test.go | 611 ++++++++++ vendor/github.com/hashicorp/go-plugin/process.go | 24 + .../hashicorp/go-plugin/process_posix.go | 19 + .../hashicorp/go-plugin/process_windows.go | 29 + vendor/github.com/hashicorp/go-plugin/protocol.go | 45 + .../github.com/hashicorp/go-plugin/rpc_client.go | 170 +++ .../hashicorp/go-plugin/rpc_client_test.go | 115 ++ .../github.com/hashicorp/go-plugin/rpc_server.go | 197 +++ vendor/github.com/hashicorp/go-plugin/server.go | 317 +++++ .../github.com/hashicorp/go-plugin/server_mux.go | 31 + .../github.com/hashicorp/go-plugin/server_test.go | 44 + vendor/github.com/hashicorp/go-plugin/stream.go | 18 + .../hashicorp/go-plugin/test/grpc/gen.go | 3 + .../hashicorp/go-plugin/test/grpc/test.pb.go | 562 +++++++++ .../hashicorp/go-plugin/test/grpc/test.proto | 48 + vendor/github.com/hashicorp/go-plugin/testing.go | 175 +++ vendor/github.com/hashicorp/golang-lru/.gitignore | 23 + vendor/github.com/hashicorp/golang-lru/2q.go | 223 ++++ vendor/github.com/hashicorp/golang-lru/2q_test.go | 306 +++++ vendor/github.com/hashicorp/golang-lru/LICENSE | 362 ++++++ vendor/github.com/hashicorp/golang-lru/README.md | 25 + vendor/github.com/hashicorp/golang-lru/arc.go | 257 ++++ vendor/github.com/hashicorp/golang-lru/arc_test.go | 377 ++++++ vendor/github.com/hashicorp/golang-lru/doc.go | 21 + vendor/github.com/hashicorp/golang-lru/go.mod | 1 + vendor/github.com/hashicorp/golang-lru/lru.go | 110 ++ vendor/github.com/hashicorp/golang-lru/lru_test.go | 221 ++++ .../hashicorp/golang-lru/simplelru/lru.go | 161 +++ .../golang-lru/simplelru/lru_interface.go | 36 + .../hashicorp/golang-lru/simplelru/lru_test.go | 167 +++ vendor/github.com/hashicorp/yamux/.gitignore | 23 + vendor/github.com/hashicorp/yamux/LICENSE | 362 ++++++ vendor/github.com/hashicorp/yamux/README.md | 86 ++ vendor/github.com/hashicorp/yamux/addr.go | 60 + vendor/github.com/hashicorp/yamux/bench_test.go | 123 ++ vendor/github.com/hashicorp/yamux/const.go | 157 +++ vendor/github.com/hashicorp/yamux/const_test.go | 72 ++ vendor/github.com/hashicorp/yamux/mux.go | 87 ++ vendor/github.com/hashicorp/yamux/session.go | 648 ++++++++++ vendor/github.com/hashicorp/yamux/session_test.go | 1256 ++++++++++++++++++++ vendor/github.com/hashicorp/yamux/spec.md | 140 +++ vendor/github.com/hashicorp/yamux/stream.go | 470 ++++++++ vendor/github.com/hashicorp/yamux/util.go | 43 + vendor/github.com/hashicorp/yamux/util_test.go | 50 + 102 files changed, 15882 insertions(+) create mode 100644 vendor/github.com/hashicorp/go-hclog/.gitignore create mode 100644 vendor/github.com/hashicorp/go-hclog/LICENSE create mode 100644 vendor/github.com/hashicorp/go-hclog/README.md create mode 100644 vendor/github.com/hashicorp/go-hclog/global.go create mode 100644 vendor/github.com/hashicorp/go-hclog/int.go create mode 100644 vendor/github.com/hashicorp/go-hclog/log.go create mode 100644 vendor/github.com/hashicorp/go-hclog/logger_test.go create mode 100644 vendor/github.com/hashicorp/go-hclog/nulllogger.go create mode 100644 vendor/github.com/hashicorp/go-hclog/nulllogger_test.go create mode 100644 vendor/github.com/hashicorp/go-hclog/stacktrace.go create mode 100644 vendor/github.com/hashicorp/go-hclog/stdlog.go create mode 100644 vendor/github.com/hashicorp/go-hclog/stdlog_test.go create mode 100644 vendor/github.com/hashicorp/go-plugin/.gitignore create mode 100644 vendor/github.com/hashicorp/go-plugin/LICENSE create mode 100644 vendor/github.com/hashicorp/go-plugin/README.md create mode 100644 vendor/github.com/hashicorp/go-plugin/client.go create mode 100644 vendor/github.com/hashicorp/go-plugin/client_posix_test.go create mode 100644 vendor/github.com/hashicorp/go-plugin/client_test.go create mode 100644 vendor/github.com/hashicorp/go-plugin/discover.go create mode 100644 vendor/github.com/hashicorp/go-plugin/docs/README.md create mode 100644 vendor/github.com/hashicorp/go-plugin/docs/guide-plugin-write-non-go.md create mode 100644 vendor/github.com/hashicorp/go-plugin/docs/internals.md create mode 100644 vendor/github.com/hashicorp/go-plugin/error.go create mode 100644 vendor/github.com/hashicorp/go-plugin/error_test.go create mode 100644 vendor/github.com/hashicorp/go-plugin/examples/basic/.gitignore create mode 100644 vendor/github.com/hashicorp/go-plugin/examples/basic/README.md create mode 100644 vendor/github.com/hashicorp/go-plugin/examples/basic/commons/greeter_interface.go create mode 100644 vendor/github.com/hashicorp/go-plugin/examples/basic/main.go create mode 100644 vendor/github.com/hashicorp/go-plugin/examples/basic/plugin/greeter_impl.go create mode 100644 vendor/github.com/hashicorp/go-plugin/examples/bidirectional/README.md create mode 100644 vendor/github.com/hashicorp/go-plugin/examples/bidirectional/main.go create mode 100644 vendor/github.com/hashicorp/go-plugin/examples/bidirectional/plugin-go-grpc/main.go create mode 100644 vendor/github.com/hashicorp/go-plugin/examples/bidirectional/proto/kv.pb.go create mode 100644 vendor/github.com/hashicorp/go-plugin/examples/bidirectional/proto/kv.proto create mode 100644 vendor/github.com/hashicorp/go-plugin/examples/bidirectional/shared/grpc.go create mode 100644 vendor/github.com/hashicorp/go-plugin/examples/bidirectional/shared/interface.go create mode 100644 vendor/github.com/hashicorp/go-plugin/examples/grpc/.gitignore create mode 100644 vendor/github.com/hashicorp/go-plugin/examples/grpc/README.md create mode 100644 vendor/github.com/hashicorp/go-plugin/examples/grpc/main.go create mode 100644 vendor/github.com/hashicorp/go-plugin/examples/grpc/plugin-go-grpc/main.go create mode 100644 vendor/github.com/hashicorp/go-plugin/examples/grpc/plugin-go-netrpc/main.go create mode 100644 vendor/github.com/hashicorp/go-plugin/examples/grpc/plugin-python/kv_pb2.py create mode 100644 vendor/github.com/hashicorp/go-plugin/examples/grpc/plugin-python/kv_pb2_grpc.py create mode 100644 vendor/github.com/hashicorp/go-plugin/examples/grpc/plugin-python/plugin.py create mode 100644 vendor/github.com/hashicorp/go-plugin/examples/grpc/proto/kv.pb.go create mode 100644 vendor/github.com/hashicorp/go-plugin/examples/grpc/proto/kv.proto create mode 100644 vendor/github.com/hashicorp/go-plugin/examples/grpc/shared/grpc.go create mode 100644 vendor/github.com/hashicorp/go-plugin/examples/grpc/shared/interface.go create mode 100644 vendor/github.com/hashicorp/go-plugin/examples/grpc/shared/rpc.go create mode 100644 vendor/github.com/hashicorp/go-plugin/grpc_broker.go create mode 100644 vendor/github.com/hashicorp/go-plugin/grpc_broker.pb.go create mode 100644 vendor/github.com/hashicorp/go-plugin/grpc_broker.proto create mode 100644 vendor/github.com/hashicorp/go-plugin/grpc_client.go create mode 100644 vendor/github.com/hashicorp/go-plugin/grpc_client_test.go create mode 100644 vendor/github.com/hashicorp/go-plugin/grpc_server.go create mode 100644 vendor/github.com/hashicorp/go-plugin/log_entry.go create mode 100644 vendor/github.com/hashicorp/go-plugin/mux_broker.go create mode 100644 vendor/github.com/hashicorp/go-plugin/plugin.go create mode 100644 vendor/github.com/hashicorp/go-plugin/plugin_test.go create mode 100644 vendor/github.com/hashicorp/go-plugin/process.go create mode 100644 vendor/github.com/hashicorp/go-plugin/process_posix.go create mode 100644 vendor/github.com/hashicorp/go-plugin/process_windows.go create mode 100644 vendor/github.com/hashicorp/go-plugin/protocol.go create mode 100644 vendor/github.com/hashicorp/go-plugin/rpc_client.go create mode 100644 vendor/github.com/hashicorp/go-plugin/rpc_client_test.go create mode 100644 vendor/github.com/hashicorp/go-plugin/rpc_server.go create mode 100644 vendor/github.com/hashicorp/go-plugin/server.go create mode 100644 vendor/github.com/hashicorp/go-plugin/server_mux.go create mode 100644 vendor/github.com/hashicorp/go-plugin/server_test.go create mode 100644 vendor/github.com/hashicorp/go-plugin/stream.go create mode 100644 vendor/github.com/hashicorp/go-plugin/test/grpc/gen.go create mode 100644 vendor/github.com/hashicorp/go-plugin/test/grpc/test.pb.go create mode 100644 vendor/github.com/hashicorp/go-plugin/test/grpc/test.proto create mode 100644 vendor/github.com/hashicorp/go-plugin/testing.go create mode 100644 vendor/github.com/hashicorp/golang-lru/.gitignore create mode 100644 vendor/github.com/hashicorp/golang-lru/2q.go create mode 100644 vendor/github.com/hashicorp/golang-lru/2q_test.go create mode 100644 vendor/github.com/hashicorp/golang-lru/LICENSE create mode 100644 vendor/github.com/hashicorp/golang-lru/README.md create mode 100644 vendor/github.com/hashicorp/golang-lru/arc.go create mode 100644 vendor/github.com/hashicorp/golang-lru/arc_test.go create mode 100644 vendor/github.com/hashicorp/golang-lru/doc.go create mode 100644 vendor/github.com/hashicorp/golang-lru/go.mod create mode 100644 vendor/github.com/hashicorp/golang-lru/lru.go create mode 100644 vendor/github.com/hashicorp/golang-lru/lru_test.go create mode 100644 vendor/github.com/hashicorp/golang-lru/simplelru/lru.go create mode 100644 vendor/github.com/hashicorp/golang-lru/simplelru/lru_interface.go create mode 100644 vendor/github.com/hashicorp/golang-lru/simplelru/lru_test.go create mode 100644 vendor/github.com/hashicorp/yamux/.gitignore create mode 100644 vendor/github.com/hashicorp/yamux/LICENSE create mode 100644 vendor/github.com/hashicorp/yamux/README.md create mode 100644 vendor/github.com/hashicorp/yamux/addr.go create mode 100644 vendor/github.com/hashicorp/yamux/bench_test.go create mode 100644 vendor/github.com/hashicorp/yamux/const.go create mode 100644 vendor/github.com/hashicorp/yamux/const_test.go create mode 100644 vendor/github.com/hashicorp/yamux/mux.go create mode 100644 vendor/github.com/hashicorp/yamux/session.go create mode 100644 vendor/github.com/hashicorp/yamux/session_test.go create mode 100644 vendor/github.com/hashicorp/yamux/spec.md create mode 100644 vendor/github.com/hashicorp/yamux/stream.go create mode 100644 vendor/github.com/hashicorp/yamux/util.go create mode 100644 vendor/github.com/hashicorp/yamux/util_test.go diff --git a/vendor/github.com/hashicorp/go-hclog/.gitignore b/vendor/github.com/hashicorp/go-hclog/.gitignore new file mode 100644 index 00000000..42cc4105 --- /dev/null +++ b/vendor/github.com/hashicorp/go-hclog/.gitignore @@ -0,0 +1 @@ +.idea* \ No newline at end of file diff --git a/vendor/github.com/hashicorp/go-hclog/LICENSE b/vendor/github.com/hashicorp/go-hclog/LICENSE new file mode 100644 index 00000000..abaf1e45 --- /dev/null +++ b/vendor/github.com/hashicorp/go-hclog/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2017 HashiCorp + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/github.com/hashicorp/go-hclog/README.md b/vendor/github.com/hashicorp/go-hclog/README.md new file mode 100644 index 00000000..1153e285 --- /dev/null +++ b/vendor/github.com/hashicorp/go-hclog/README.md @@ -0,0 +1,133 @@ +# go-hclog + +[![Go Documentation](http://img.shields.io/badge/go-documentation-blue.svg?style=flat-square)][godocs] + +[godocs]: https://godoc.org/github.com/hashicorp/go-hclog + +`go-hclog` is a package for Go that provides a simple key/value logging +interface for use in development and production environments. + +It provides logging levels that provide decreased output based upon the +desired amount of output, unlike the standard library `log` package. + +It provides `Printf` style logging of values via `hclog.Fmt()`. + +It provides a human readable output mode for use in development as well as +JSON output mode for production. + +## Stability Note + +While this library is fully open source and HashiCorp will be maintaining it +(since we are and will be making extensive use of it), the API and output +format is subject to minor changes as we fully bake and vet it in our projects. +This notice will be removed once it's fully integrated into our major projects +and no further changes are anticipated. + +## Installation and Docs + +Install using `go get github.com/hashicorp/go-hclog`. + +Full documentation is available at +http://godoc.org/github.com/hashicorp/go-hclog + +## Usage + +### Use the global logger + +```go +hclog.Default().Info("hello world") +``` + +```text +2017-07-05T16:15:55.167-0700 [INFO ] hello world +``` + +(Note timestamps are removed in future examples for brevity.) + +### Create a new logger + +```go +appLogger := hclog.New(&hclog.LoggerOptions{ + Name: "my-app", + Level: hclog.LevelFromString("DEBUG"), +}) +``` + +### Emit an Info level message with 2 key/value pairs + +```go +input := "5.5" +_, err := strconv.ParseInt(input, 10, 32) +if err != nil { + appLogger.Info("Invalid input for ParseInt", "input", input, "error", err) +} +``` + +```text +... [INFO ] my-app: Invalid input for ParseInt: input=5.5 error="strconv.ParseInt: parsing "5.5": invalid syntax" +``` + +### Create a new Logger for a major subsystem + +```go +subsystemLogger := appLogger.Named("transport") +subsystemLogger.Info("we are transporting something") +``` + +```text +... [INFO ] my-app.transport: we are transporting something +``` + +Notice that logs emitted by `subsystemLogger` contain `my-app.transport`, +reflecting both the application and subsystem names. + +### Create a new Logger with fixed key/value pairs + +Using `With()` will include a specific key-value pair in all messages emitted +by that logger. + +```go +requestID := "5fb446b6-6eba-821d-df1b-cd7501b6a363" +requestLogger := subsystemLogger.With("request", requestID) +requestLogger.Info("we are transporting a request") +``` + +```text +... [INFO ] my-app.transport: we are transporting a request: request=5fb446b6-6eba-821d-df1b-cd7501b6a363 +``` + +This allows sub Loggers to be context specific without having to thread that +into all the callers. + +### Using `hclog.Fmt()` + +```go +var int totalBandwidth = 200 +appLogger.Info("total bandwidth exceeded", "bandwidth", hclog.Fmt("%d GB/s", totalBandwidth)) +``` + +```text +... [INFO ] my-app: total bandwidth exceeded: bandwidth="200 GB/s" +``` + +### Use this with code that uses the standard library logger + +If you want to use the standard library's `log.Logger` interface you can wrap +`hclog.Logger` by calling the `StandardLogger()` method. This allows you to use +it with the familiar `Println()`, `Printf()`, etc. For example: + +```go +stdLogger := appLogger.StandardLogger(&hclog.StandardLoggerOptions{ + InferLevels: true, +}) +// Printf() is provided by stdlib log.Logger interface, not hclog.Logger +stdLogger.Printf("[DEBUG] %+v", stdLogger) +``` + +```text +... [DEBUG] my-app: &{mu:{state:0 sema:0} prefix: flag:0 out:0xc42000a0a0 buf:[]} +``` + +Notice that if `appLogger` is initialized with the `INFO` log level _and_ you +specify `InferLevels: true`, you will not see any output here. You must change +`appLogger` to `DEBUG` to see output. See the docs for more information. diff --git a/vendor/github.com/hashicorp/go-hclog/global.go b/vendor/github.com/hashicorp/go-hclog/global.go new file mode 100644 index 00000000..55ce4396 --- /dev/null +++ b/vendor/github.com/hashicorp/go-hclog/global.go @@ -0,0 +1,34 @@ +package hclog + +import ( + "sync" +) + +var ( + protect sync.Once + def Logger + + // The options used to create the Default logger. These are + // read only when the Default logger is created, so set them + // as soon as the process starts. + DefaultOptions = &LoggerOptions{ + Level: DefaultLevel, + Output: DefaultOutput, + } +) + +// Return a logger that is held globally. This can be a good starting +// place, and then you can use .With() and .Name() to create sub-loggers +// to be used in more specific contexts. +func Default() Logger { + protect.Do(func() { + def = New(DefaultOptions) + }) + + return def +} + +// A short alias for Default() +func L() Logger { + return Default() +} diff --git a/vendor/github.com/hashicorp/go-hclog/int.go b/vendor/github.com/hashicorp/go-hclog/int.go new file mode 100644 index 00000000..7d17d81c --- /dev/null +++ b/vendor/github.com/hashicorp/go-hclog/int.go @@ -0,0 +1,457 @@ +package hclog + +import ( + "bufio" + "encoding" + "encoding/json" + "fmt" + "log" + "os" + "runtime" + "sort" + "strconv" + "strings" + "sync" + "sync/atomic" + "time" +) + +var ( + _levelToBracket = map[Level]string{ + Debug: "[DEBUG]", + Trace: "[TRACE]", + Info: "[INFO ]", + Warn: "[WARN ]", + Error: "[ERROR]", + } +) + +// Given the options (nil for defaults), create a new Logger +func New(opts *LoggerOptions) Logger { + if opts == nil { + opts = &LoggerOptions{} + } + + output := opts.Output + if output == nil { + output = os.Stderr + } + + level := opts.Level + if level == NoLevel { + level = DefaultLevel + } + + mtx := opts.Mutex + if mtx == nil { + mtx = new(sync.Mutex) + } + + ret := &intLogger{ + m: mtx, + json: opts.JSONFormat, + caller: opts.IncludeLocation, + name: opts.Name, + timeFormat: TimeFormat, + w: bufio.NewWriter(output), + level: new(int32), + } + if opts.TimeFormat != "" { + ret.timeFormat = opts.TimeFormat + } + atomic.StoreInt32(ret.level, int32(level)) + return ret +} + +// The internal logger implementation. Internal in that it is defined entirely +// by this package. +type intLogger struct { + json bool + caller bool + name string + timeFormat string + + // this is a pointer so that it's shared by any derived loggers, since + // those derived loggers share the bufio.Writer as well. + m *sync.Mutex + w *bufio.Writer + level *int32 + + implied []interface{} +} + +// Make sure that intLogger is a Logger +var _ Logger = &intLogger{} + +// The time format to use for logging. This is a version of RFC3339 that +// contains millisecond precision +const TimeFormat = "2006-01-02T15:04:05.000Z0700" + +// Log a message and a set of key/value pairs if the given level is at +// or more severe that the threshold configured in the Logger. +func (z *intLogger) Log(level Level, msg string, args ...interface{}) { + if level < Level(atomic.LoadInt32(z.level)) { + return + } + + t := time.Now() + + z.m.Lock() + defer z.m.Unlock() + + if z.json { + z.logJson(t, level, msg, args...) + } else { + z.log(t, level, msg, args...) + } + + z.w.Flush() +} + +// Cleanup a path by returning the last 2 segments of the path only. +func trimCallerPath(path string) string { + // lovely borrowed from zap + // nb. To make sure we trim the path correctly on Windows too, we + // counter-intuitively need to use '/' and *not* os.PathSeparator here, + // because the path given originates from Go stdlib, specifically + // runtime.Caller() which (as of Mar/17) returns forward slashes even on + // Windows. + // + // See https://github.com/golang/go/issues/3335 + // and https://github.com/golang/go/issues/18151 + // + // for discussion on the issue on Go side. + // + + // Find the last separator. + // + idx := strings.LastIndexByte(path, '/') + if idx == -1 { + return path + } + + // Find the penultimate separator. + idx = strings.LastIndexByte(path[:idx], '/') + if idx == -1 { + return path + } + + return path[idx+1:] +} + +// Non-JSON logging format function +func (z *intLogger) log(t time.Time, level Level, msg string, args ...interface{}) { + z.w.WriteString(t.Format(z.timeFormat)) + z.w.WriteByte(' ') + + s, ok := _levelToBracket[level] + if ok { + z.w.WriteString(s) + } else { + z.w.WriteString("[UNKN ]") + } + + if z.caller { + if _, file, line, ok := runtime.Caller(3); ok { + z.w.WriteByte(' ') + z.w.WriteString(trimCallerPath(file)) + z.w.WriteByte(':') + z.w.WriteString(strconv.Itoa(line)) + z.w.WriteByte(':') + } + } + + z.w.WriteByte(' ') + + if z.name != "" { + z.w.WriteString(z.name) + z.w.WriteString(": ") + } + + z.w.WriteString(msg) + + args = append(z.implied, args...) + + var stacktrace CapturedStacktrace + + if args != nil && len(args) > 0 { + if len(args)%2 != 0 { + cs, ok := args[len(args)-1].(CapturedStacktrace) + if ok { + args = args[:len(args)-1] + stacktrace = cs + } else { + args = append(args, "") + } + } + + z.w.WriteByte(':') + + FOR: + for i := 0; i < len(args); i = i + 2 { + var val string + + switch st := args[i+1].(type) { + case string: + val = st + case int: + val = strconv.FormatInt(int64(st), 10) + case int64: + val = strconv.FormatInt(int64(st), 10) + case int32: + val = strconv.FormatInt(int64(st), 10) + case int16: + val = strconv.FormatInt(int64(st), 10) + case int8: + val = strconv.FormatInt(int64(st), 10) + case uint: + val = strconv.FormatUint(uint64(st), 10) + case uint64: + val = strconv.FormatUint(uint64(st), 10) + case uint32: + val = strconv.FormatUint(uint64(st), 10) + case uint16: + val = strconv.FormatUint(uint64(st), 10) + case uint8: + val = strconv.FormatUint(uint64(st), 10) + case CapturedStacktrace: + stacktrace = st + continue FOR + case Format: + val = fmt.Sprintf(st[0].(string), st[1:]...) + default: + val = fmt.Sprintf("%v", st) + } + + z.w.WriteByte(' ') + z.w.WriteString(args[i].(string)) + z.w.WriteByte('=') + + if strings.ContainsAny(val, " \t\n\r") { + z.w.WriteByte('"') + z.w.WriteString(val) + z.w.WriteByte('"') + } else { + z.w.WriteString(val) + } + } + } + + z.w.WriteString("\n") + + if stacktrace != "" { + z.w.WriteString(string(stacktrace)) + } +} + +// JSON logging function +func (z *intLogger) logJson(t time.Time, level Level, msg string, args ...interface{}) { + vals := map[string]interface{}{ + "@message": msg, + "@timestamp": t.Format("2006-01-02T15:04:05.000000Z07:00"), + } + + var levelStr string + switch level { + case Error: + levelStr = "error" + case Warn: + levelStr = "warn" + case Info: + levelStr = "info" + case Debug: + levelStr = "debug" + case Trace: + levelStr = "trace" + default: + levelStr = "all" + } + + vals["@level"] = levelStr + + if z.name != "" { + vals["@module"] = z.name + } + + if z.caller { + if _, file, line, ok := runtime.Caller(3); ok { + vals["@caller"] = fmt.Sprintf("%s:%d", file, line) + } + } + + args = append(z.implied, args...) + + if args != nil && len(args) > 0 { + if len(args)%2 != 0 { + cs, ok := args[len(args)-1].(CapturedStacktrace) + if ok { + args = args[:len(args)-1] + vals["stacktrace"] = cs + } else { + args = append(args, "") + } + } + + for i := 0; i < len(args); i = i + 2 { + if _, ok := args[i].(string); !ok { + // As this is the logging function not much we can do here + // without injecting into logs... + continue + } + val := args[i+1] + switch sv := val.(type) { + case error: + // Check if val is of type error. If error type doesn't + // implement json.Marshaler or encoding.TextMarshaler + // then set val to err.Error() so that it gets marshaled + switch sv.(type) { + case json.Marshaler, encoding.TextMarshaler: + default: + val = sv.Error() + } + case Format: + val = fmt.Sprintf(sv[0].(string), sv[1:]...) + } + + vals[args[i].(string)] = val + } + } + + err := json.NewEncoder(z.w).Encode(vals) + if err != nil { + panic(err) + } +} + +// Emit the message and args at DEBUG level +func (z *intLogger) Debug(msg string, args ...interface{}) { + z.Log(Debug, msg, args...) +} + +// Emit the message and args at TRACE level +func (z *intLogger) Trace(msg string, args ...interface{}) { + z.Log(Trace, msg, args...) +} + +// Emit the message and args at INFO level +func (z *intLogger) Info(msg string, args ...interface{}) { + z.Log(Info, msg, args...) +} + +// Emit the message and args at WARN level +func (z *intLogger) Warn(msg string, args ...interface{}) { + z.Log(Warn, msg, args...) +} + +// Emit the message and args at ERROR level +func (z *intLogger) Error(msg string, args ...interface{}) { + z.Log(Error, msg, args...) +} + +// Indicate that the logger would emit TRACE level logs +func (z *intLogger) IsTrace() bool { + return Level(atomic.LoadInt32(z.level)) == Trace +} + +// Indicate that the logger would emit DEBUG level logs +func (z *intLogger) IsDebug() bool { + return Level(atomic.LoadInt32(z.level)) <= Debug +} + +// Indicate that the logger would emit INFO level logs +func (z *intLogger) IsInfo() bool { + return Level(atomic.LoadInt32(z.level)) <= Info +} + +// Indicate that the logger would emit WARN level logs +func (z *intLogger) IsWarn() bool { + return Level(atomic.LoadInt32(z.level)) <= Warn +} + +// Indicate that the logger would emit ERROR level logs +func (z *intLogger) IsError() bool { + return Level(atomic.LoadInt32(z.level)) <= Error +} + +// Return a sub-Logger for which every emitted log message will contain +// the given key/value pairs. This is used to create a context specific +// Logger. +func (z *intLogger) With(args ...interface{}) Logger { + if len(args)%2 != 0 { + panic("With() call requires paired arguments") + } + + var nz intLogger = *z + + result := make(map[string]interface{}, len(z.implied)+len(args)) + keys := make([]string, 0, len(z.implied)+len(args)) + + // Read existing args, store map and key for consistent sorting + for i := 0; i < len(z.implied); i += 2 { + key := z.implied[i].(string) + keys = append(keys, key) + result[key] = z.implied[i+1] + } + // Read new args, store map and key for consistent sorting + for i := 0; i < len(args); i += 2 { + key := args[i].(string) + _, exists := result[key] + if !exists { + keys = append(keys, key) + } + result[key] = args[i+1] + } + + // Sort keys to be consistent + sort.Strings(keys) + + nz.implied = make([]interface{}, 0, len(z.implied)+len(args)) + for _, k := range keys { + nz.implied = append(nz.implied, k) + nz.implied = append(nz.implied, result[k]) + } + + return &nz +} + +// Create a new sub-Logger that a name decending from the current name. +// This is used to create a subsystem specific Logger. +func (z *intLogger) Named(name string) Logger { + var nz intLogger = *z + + if nz.name != "" { + nz.name = nz.name + "." + name + } else { + nz.name = name + } + + return &nz +} + +// Create a new sub-Logger with an explicit name. This ignores the current +// name. This is used to create a standalone logger that doesn't fall +// within the normal hierarchy. +func (z *intLogger) ResetNamed(name string) Logger { + var nz intLogger = *z + + nz.name = name + + return &nz +} + +// Update the logging level on-the-fly. This will affect all subloggers as +// well. +func (z *intLogger) SetLevel(level Level) { + atomic.StoreInt32(z.level, int32(level)) +} + +// Create a *log.Logger that will send it's data through this Logger. This +// allows packages that expect to be using the standard library log to actually +// use this logger. +func (z *intLogger) StandardLogger(opts *StandardLoggerOptions) *log.Logger { + if opts == nil { + opts = &StandardLoggerOptions{} + } + + return log.New(&stdlogAdapter{z, opts.InferLevels}, "", 0) +} diff --git a/vendor/github.com/hashicorp/go-hclog/log.go b/vendor/github.com/hashicorp/go-hclog/log.go new file mode 100644 index 00000000..894e8461 --- /dev/null +++ b/vendor/github.com/hashicorp/go-hclog/log.go @@ -0,0 +1,161 @@ +package hclog + +import ( + "io" + "log" + "os" + "strings" + "sync" +) + +var ( + DefaultOutput = os.Stderr + DefaultLevel = Info +) + +type Level int32 + +const ( + // This is a special level used to indicate that no level has been + // set and allow for a default to be used. + NoLevel Level = 0 + + // The most verbose level. Intended to be used for the tracing of actions + // in code, such as function enters/exits, etc. + Trace Level = 1 + + // For programmer lowlevel analysis. + Debug Level = 2 + + // For information about steady state operations. + Info Level = 3 + + // For information about rare but handled events. + Warn Level = 4 + + // For information about unrecoverable events. + Error Level = 5 +) + +// When processing a value of this type, the logger automatically treats the first +// argument as a Printf formatting string and passes the rest as the values to be +// formatted. For example: L.Info(Fmt{"%d beans/day", beans}). This is a simple +// convience type for when formatting is required. +type Format []interface{} + +// Fmt returns a Format type. This is a convience function for creating a Format +// type. +func Fmt(str string, args ...interface{}) Format { + return append(Format{str}, args...) +} + +// LevelFromString returns a Level type for the named log level, or "NoLevel" if +// the level string is invalid. This facilitates setting the log level via +// config or environment variable by name in a predictable way. +func LevelFromString(levelStr string) Level { + // We don't care about case. Accept "INFO" or "info" + levelStr = strings.ToLower(strings.TrimSpace(levelStr)) + switch levelStr { + case "trace": + return Trace + case "debug": + return Debug + case "info": + return Info + case "warn": + return Warn + case "error": + return Error + default: + return NoLevel + } +} + +// The main Logger interface. All code should code against this interface only. +type Logger interface { + // Args are alternating key, val pairs + // keys must be strings + // vals can be any type, but display is implementation specific + // Emit a message and key/value pairs at the TRACE level + Trace(msg string, args ...interface{}) + + // Emit a message and key/value pairs at the DEBUG level + Debug(msg string, args ...interface{}) + + // Emit a message and key/value pairs at the INFO level + Info(msg string, args ...interface{}) + + // Emit a message and key/value pairs at the WARN level + Warn(msg string, args ...interface{}) + + // Emit a message and key/value pairs at the ERROR level + Error(msg string, args ...interface{}) + + // Indicate if TRACE logs would be emitted. This and the other Is* guards + // are used to elide expensive logging code based on the current level. + IsTrace() bool + + // Indicate if DEBUG logs would be emitted. This and the other Is* guards + IsDebug() bool + + // Indicate if INFO logs would be emitted. This and the other Is* guards + IsInfo() bool + + // Indicate if WARN logs would be emitted. This and the other Is* guards + IsWarn() bool + + // Indicate if ERROR logs would be emitted. This and the other Is* guards + IsError() bool + + // Creates a sublogger that will always have the given key/value pairs + With(args ...interface{}) Logger + + // Create a logger that will prepend the name string on the front of all messages. + // If the logger already has a name, the new value will be appended to the current + // name. That way, a major subsystem can use this to decorate all it's own logs + // without losing context. + Named(name string) Logger + + // Create a logger that will prepend the name string on the front of all messages. + // This sets the name of the logger to the value directly, unlike Named which honor + // the current name as well. + ResetNamed(name string) Logger + + // Updates the level. This should affect all sub-loggers as well. If an + // implementation cannot update the level on the fly, it should no-op. + SetLevel(level Level) + + // Return a value that conforms to the stdlib log.Logger interface + StandardLogger(opts *StandardLoggerOptions) *log.Logger +} + +type StandardLoggerOptions struct { + // Indicate that some minimal parsing should be done on strings to try + // and detect their level and re-emit them. + // This supports the strings like [ERROR], [ERR] [TRACE], [WARN], [INFO], + // [DEBUG] and strip it off before reapplying it. + InferLevels bool +} + +type LoggerOptions struct { + // Name of the subsystem to prefix logs with + Name string + + // The threshold for the logger. Anything less severe is supressed + Level Level + + // Where to write the logs to. Defaults to os.Stdout if nil + Output io.Writer + + // An optional mutex pointer in case Output is shared + Mutex *sync.Mutex + + // Control if the output should be in JSON. + JSONFormat bool + + // Include file and line information in each log line + IncludeLocation bool + + // The time format to use instead of the default + TimeFormat string +} diff --git a/vendor/github.com/hashicorp/go-hclog/logger_test.go b/vendor/github.com/hashicorp/go-hclog/logger_test.go new file mode 100644 index 00000000..b82ed12e --- /dev/null +++ b/vendor/github.com/hashicorp/go-hclog/logger_test.go @@ -0,0 +1,513 @@ +package hclog + +import ( + "bytes" + "encoding/json" + "errors" + "fmt" + "strconv" + "strings" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestLogger(t *testing.T) { + t.Run("formats log entries", func(t *testing.T) { + var buf bytes.Buffer + + logger := New(&LoggerOptions{ + Name: "test", + Output: &buf, + }) + + logger.Info("this is test", "who", "programmer", "why", "testing") + + str := buf.String() + + dataIdx := strings.IndexByte(str, ' ') + + // ts := str[:dataIdx] + rest := str[dataIdx+1:] + + assert.Equal(t, "[INFO ] test: this is test: who=programmer why=testing\n", rest) + }) + + t.Run("quotes values with spaces", func(t *testing.T) { + var buf bytes.Buffer + + logger := New(&LoggerOptions{ + Name: "test", + Output: &buf, + }) + + logger.Info("this is test", "who", "programmer", "why", "testing is fun") + + str := buf.String() + + dataIdx := strings.IndexByte(str, ' ') + + // ts := str[:dataIdx] + rest := str[dataIdx+1:] + + assert.Equal(t, "[INFO ] test: this is test: who=programmer why=\"testing is fun\"\n", rest) + }) + + t.Run("outputs stack traces", func(t *testing.T) { + var buf bytes.Buffer + + logger := New(&LoggerOptions{ + Name: "test", + Output: &buf, + }) + + logger.Info("who", "programmer", "why", "testing", Stacktrace()) + + lines := strings.Split(buf.String(), "\n") + + require.True(t, len(lines) > 1) + + assert.Equal(t, "github.com/hashicorp/go-hclog.Stacktrace", lines[1]) + }) + + t.Run("outputs stack traces with it's given a name", func(t *testing.T) { + var buf bytes.Buffer + + logger := New(&LoggerOptions{ + Name: "test", + Output: &buf, + }) + + logger.Info("who", "programmer", "why", "testing", "foo", Stacktrace()) + + lines := strings.Split(buf.String(), "\n") + + require.True(t, len(lines) > 1) + + assert.Equal(t, "github.com/hashicorp/go-hclog.Stacktrace", lines[1]) + }) + + t.Run("includes the caller location", func(t *testing.T) { + var buf bytes.Buffer + + logger := New(&LoggerOptions{ + Name: "test", + Output: &buf, + IncludeLocation: true, + }) + + logger.Info("this is test", "who", "programmer", "why", "testing is fun") + + str := buf.String() + + dataIdx := strings.IndexByte(str, ' ') + + // ts := str[:dataIdx] + rest := str[dataIdx+1:] + + // This test will break if you move this around, it's line dependent, just fyi + assert.Equal(t, "[INFO ] go-hclog/logger_test.go:101: test: this is test: who=programmer why=\"testing is fun\"\n", rest) + }) + + t.Run("prefixes the name", func(t *testing.T) { + var buf bytes.Buffer + + logger := New(&LoggerOptions{ + // No name! + Output: &buf, + }) + + logger.Info("this is test") + str := buf.String() + dataIdx := strings.IndexByte(str, ' ') + rest := str[dataIdx+1:] + assert.Equal(t, "[INFO ] this is test\n", rest) + + buf.Reset() + + another := logger.Named("sublogger") + another.Info("this is test") + str = buf.String() + dataIdx = strings.IndexByte(str, ' ') + rest = str[dataIdx+1:] + assert.Equal(t, "[INFO ] sublogger: this is test\n", rest) + }) + + t.Run("use a different time format", func(t *testing.T) { + var buf bytes.Buffer + + logger := New(&LoggerOptions{ + Name: "test", + Output: &buf, + TimeFormat: time.Kitchen, + }) + + logger.Info("this is test", "who", "programmer", "why", "testing is fun") + + str := buf.String() + + dataIdx := strings.IndexByte(str, ' ') + + assert.Equal(t, str[:dataIdx], time.Now().Format(time.Kitchen)) + }) + + t.Run("use with", func(t *testing.T) { + var buf bytes.Buffer + + rootLogger := New(&LoggerOptions{ + Name: "with_test", + Output: &buf, + }) + + // Build the root logger in two steps, which triggers a slice capacity increase + // and is part of the test for inadvertant slice aliasing. + rootLogger = rootLogger.With("a", 1, "b", 2) + rootLogger = rootLogger.With("c", 3) + + // Derive two new loggers which should be completely independent + derived1 := rootLogger.With("cat", 30) + derived2 := rootLogger.With("dog", 40) + + derived1.Info("test1") + output := buf.String() + dataIdx := strings.IndexByte(output, ' ') + assert.Equal(t, "[INFO ] with_test: test1: a=1 b=2 c=3 cat=30\n", output[dataIdx+1:]) + + buf.Reset() + + derived2.Info("test2") + output = buf.String() + dataIdx = strings.IndexByte(output, ' ') + assert.Equal(t, "[INFO ] with_test: test2: a=1 b=2 c=3 dog=40\n", output[dataIdx+1:]) + }) + + t.Run("unpaired with", func(t *testing.T) { + defer func() { + if r := recover(); r == nil { + t.Fatal("expected panic") + } + }() + + var buf bytes.Buffer + + rootLogger := New(&LoggerOptions{ + Name: "with_test", + Output: &buf, + }) + + rootLogger = rootLogger.With("a") + }) + + t.Run("use with and log", func(t *testing.T) { + var buf bytes.Buffer + + rootLogger := New(&LoggerOptions{ + Name: "with_test", + Output: &buf, + }) + + // Build the root logger in two steps, which triggers a slice capacity increase + // and is part of the test for inadvertant slice aliasing. + rootLogger = rootLogger.With("a", 1, "b", 2) + // This line is here to test that when calling With with the same key, + // only the last value remains (see issue #21) + rootLogger = rootLogger.With("c", 4) + rootLogger = rootLogger.With("c", 3) + + // Derive another logger which should be completely independent of rootLogger + derived := rootLogger.With("cat", 30) + + rootLogger.Info("root_test", "bird", 10) + output := buf.String() + dataIdx := strings.IndexByte(output, ' ') + assert.Equal(t, "[INFO ] with_test: root_test: a=1 b=2 c=3 bird=10\n", output[dataIdx+1:]) + + buf.Reset() + + derived.Info("derived_test") + output = buf.String() + dataIdx = strings.IndexByte(output, ' ') + assert.Equal(t, "[INFO ] with_test: derived_test: a=1 b=2 c=3 cat=30\n", output[dataIdx+1:]) + }) + + t.Run("use with and log and change levels", func(t *testing.T) { + var buf bytes.Buffer + + rootLogger := New(&LoggerOptions{ + Name: "with_test", + Output: &buf, + Level: Warn, + }) + + // Build the root logger in two steps, which triggers a slice capacity increase + // and is part of the test for inadvertant slice aliasing. + rootLogger = rootLogger.With("a", 1, "b", 2) + rootLogger = rootLogger.With("c", 3) + + // Derive another logger which should be completely independent of rootLogger + derived := rootLogger.With("cat", 30) + + rootLogger.Info("root_test", "bird", 10) + output := buf.String() + if output != "" { + t.Fatalf("unexpected output: %s", output) + } + + buf.Reset() + + derived.Info("derived_test") + output = buf.String() + if output != "" { + t.Fatalf("unexpected output: %s", output) + } + + derived.SetLevel(Info) + + rootLogger.Info("root_test", "bird", 10) + output = buf.String() + dataIdx := strings.IndexByte(output, ' ') + assert.Equal(t, "[INFO ] with_test: root_test: a=1 b=2 c=3 bird=10\n", output[dataIdx+1:]) + + buf.Reset() + + derived.Info("derived_test") + output = buf.String() + dataIdx = strings.IndexByte(output, ' ') + assert.Equal(t, "[INFO ] with_test: derived_test: a=1 b=2 c=3 cat=30\n", output[dataIdx+1:]) + }) + + t.Run("supports Printf style expansions when requested", func(t *testing.T) { + var buf bytes.Buffer + + logger := New(&LoggerOptions{ + Name: "test", + Output: &buf, + }) + + logger.Info("this is test", "production", Fmt("%d beans/day", 12)) + + str := buf.String() + + dataIdx := strings.IndexByte(str, ' ') + + // ts := str[:dataIdx] + rest := str[dataIdx+1:] + + assert.Equal(t, "[INFO ] test: this is test: production=\"12 beans/day\"\n", rest) + }) +} + +func TestLogger_JSON(t *testing.T) { + t.Run("json formatting", func(t *testing.T) { + var buf bytes.Buffer + logger := New(&LoggerOptions{ + Name: "test", + Output: &buf, + JSONFormat: true, + }) + + logger.Info("this is test", "who", "programmer", "why", "testing is fun") + + b := buf.Bytes() + + var raw map[string]interface{} + if err := json.Unmarshal(b, &raw); err != nil { + t.Fatal(err) + } + + assert.Equal(t, "this is test", raw["@message"]) + assert.Equal(t, "programmer", raw["who"]) + assert.Equal(t, "testing is fun", raw["why"]) + }) + + t.Run("json formatting with", func(t *testing.T) { + var buf bytes.Buffer + logger := New(&LoggerOptions{ + Name: "test", + Output: &buf, + JSONFormat: true, + }) + logger = logger.With("cat", "in the hat", "dog", 42) + + logger.Info("this is test", "who", "programmer", "why", "testing is fun") + + b := buf.Bytes() + + var raw map[string]interface{} + if err := json.Unmarshal(b, &raw); err != nil { + t.Fatal(err) + } + + assert.Equal(t, "this is test", raw["@message"]) + assert.Equal(t, "programmer", raw["who"]) + assert.Equal(t, "testing is fun", raw["why"]) + assert.Equal(t, "in the hat", raw["cat"]) + assert.Equal(t, float64(42), raw["dog"]) + }) + + t.Run("json formatting error type", func(t *testing.T) { + var buf bytes.Buffer + + logger := New(&LoggerOptions{ + Name: "test", + Output: &buf, + JSONFormat: true, + }) + + errMsg := errors.New("this is an error") + logger.Info("this is test", "who", "programmer", "err", errMsg) + + b := buf.Bytes() + + var raw map[string]interface{} + if err := json.Unmarshal(b, &raw); err != nil { + t.Fatal(err) + } + + assert.Equal(t, "this is test", raw["@message"]) + assert.Equal(t, "programmer", raw["who"]) + assert.Equal(t, errMsg.Error(), raw["err"]) + }) + + t.Run("json formatting custom error type json marshaler", func(t *testing.T) { + var buf bytes.Buffer + + logger := New(&LoggerOptions{ + Name: "test", + Output: &buf, + JSONFormat: true, + }) + + errMsg := &customErrJSON{"this is an error"} + rawMsg, err := errMsg.MarshalJSON() + if err != nil { + t.Fatal(err) + } + expectedMsg, err := strconv.Unquote(string(rawMsg)) + if err != nil { + t.Fatal(err) + } + + logger.Info("this is test", "who", "programmer", "err", errMsg) + + b := buf.Bytes() + + var raw map[string]interface{} + if err := json.Unmarshal(b, &raw); err != nil { + t.Fatal(err) + } + + assert.Equal(t, "this is test", raw["@message"]) + assert.Equal(t, "programmer", raw["who"]) + assert.Equal(t, expectedMsg, raw["err"]) + }) + + t.Run("json formatting custom error type text marshaler", func(t *testing.T) { + var buf bytes.Buffer + + logger := New(&LoggerOptions{ + Name: "test", + Output: &buf, + JSONFormat: true, + }) + + errMsg := &customErrText{"this is an error"} + rawMsg, err := errMsg.MarshalText() + if err != nil { + t.Fatal(err) + } + expectedMsg := string(rawMsg) + + logger.Info("this is test", "who", "programmer", "err", errMsg) + + b := buf.Bytes() + + var raw map[string]interface{} + if err := json.Unmarshal(b, &raw); err != nil { + t.Fatal(err) + } + + assert.Equal(t, "this is test", raw["@message"]) + assert.Equal(t, "programmer", raw["who"]) + assert.Equal(t, expectedMsg, raw["err"]) + }) + + t.Run("supports Printf style expansions when requested", func(t *testing.T) { + var buf bytes.Buffer + + logger := New(&LoggerOptions{ + Name: "test", + Output: &buf, + JSONFormat: true, + }) + + logger.Info("this is test", "production", Fmt("%d beans/day", 12)) + + b := buf.Bytes() + + var raw map[string]interface{} + if err := json.Unmarshal(b, &raw); err != nil { + t.Fatal(err) + } + + assert.Equal(t, "this is test", raw["@message"]) + assert.Equal(t, "12 beans/day", raw["production"]) + }) +} + +type customErrJSON struct { + Message string +} + +// error impl. +func (c *customErrJSON) Error() string { + return c.Message +} + +// json.Marshaler impl. +func (c customErrJSON) MarshalJSON() ([]byte, error) { + return []byte(strconv.Quote(fmt.Sprintf("json-marshaler: %s", c.Message))), nil +} + +type customErrText struct { + Message string +} + +// error impl. +func (c *customErrText) Error() string { + return c.Message +} + +// text.Marshaler impl. +func (c customErrText) MarshalText() ([]byte, error) { + return []byte(fmt.Sprintf("text-marshaler: %s", c.Message)), nil +} + +func BenchmarkLogger(b *testing.B) { + b.Run("info with 10 pairs", func(b *testing.B) { + var buf bytes.Buffer + + logger := New(&LoggerOptions{ + Name: "test", + Output: &buf, + IncludeLocation: true, + }) + + for i := 0; i < b.N; i++ { + logger.Info("this is some message", + "name", "foo", + "what", "benchmarking yourself", + "why", "to see what's slow", + "k4", "value", + "k5", "value", + "k6", "value", + "k7", "value", + "k8", "value", + "k9", "value", + "k10", "value", + ) + } + }) +} diff --git a/vendor/github.com/hashicorp/go-hclog/nulllogger.go b/vendor/github.com/hashicorp/go-hclog/nulllogger.go new file mode 100644 index 00000000..0942361a --- /dev/null +++ b/vendor/github.com/hashicorp/go-hclog/nulllogger.go @@ -0,0 +1,47 @@ +package hclog + +import ( + "io/ioutil" + "log" +) + +// NewNullLogger instantiates a Logger for which all calls +// will succeed without doing anything. +// Useful for testing purposes. +func NewNullLogger() Logger { + return &nullLogger{} +} + +type nullLogger struct{} + +func (l *nullLogger) Trace(msg string, args ...interface{}) {} + +func (l *nullLogger) Debug(msg string, args ...interface{}) {} + +func (l *nullLogger) Info(msg string, args ...interface{}) {} + +func (l *nullLogger) Warn(msg string, args ...interface{}) {} + +func (l *nullLogger) Error(msg string, args ...interface{}) {} + +func (l *nullLogger) IsTrace() bool { return false } + +func (l *nullLogger) IsDebug() bool { return false } + +func (l *nullLogger) IsInfo() bool { return false } + +func (l *nullLogger) IsWarn() bool { return false } + +func (l *nullLogger) IsError() bool { return false } + +func (l *nullLogger) With(args ...interface{}) Logger { return l } + +func (l *nullLogger) Named(name string) Logger { return l } + +func (l *nullLogger) ResetNamed(name string) Logger { return l } + +func (l *nullLogger) SetLevel(level Level) {} + +func (l *nullLogger) StandardLogger(opts *StandardLoggerOptions) *log.Logger { + return log.New(ioutil.Discard, "", log.LstdFlags) +} diff --git a/vendor/github.com/hashicorp/go-hclog/nulllogger_test.go b/vendor/github.com/hashicorp/go-hclog/nulllogger_test.go new file mode 100644 index 00000000..f9433cc2 --- /dev/null +++ b/vendor/github.com/hashicorp/go-hclog/nulllogger_test.go @@ -0,0 +1,44 @@ +package hclog + +import ( + "testing" + "github.com/stretchr/testify/assert" +) + +var logger = NewNullLogger() + +func TestNullLoggerIsEfficient(t *testing.T) { + // Since statements like "IsWarn()", "IsError()", etc. are used to gate + // actually writing warning and error statements, the null logger will + // be faster and more efficient if it always returns false for these calls. + assert.False(t, logger.IsTrace()) + assert.False(t, logger.IsDebug()) + assert.False(t, logger.IsInfo()) + assert.False(t, logger.IsWarn()) + assert.False(t, logger.IsError()) +} + +func TestNullLoggerReturnsNullLoggers(t *testing.T) { + + // Sometimes the logger is asked to return subloggers. + // These should also be a nullLogger. + + subLogger := logger.With() + _, ok := subLogger.(*nullLogger) + assert.True(t, ok) + + subLogger = logger.Named("") + _, ok = subLogger.(*nullLogger) + assert.True(t, ok) + + subLogger = logger.ResetNamed("") + _, ok = subLogger.(*nullLogger) + assert.True(t, ok) +} + +func TestStandardLoggerIsntNil(t *testing.T) { + // Don't return a nil pointer for the standard logger, + // lest it cause a panic. + stdLogger := logger.StandardLogger(nil) + assert.NotEqual(t, nil, stdLogger) +} diff --git a/vendor/github.com/hashicorp/go-hclog/stacktrace.go b/vendor/github.com/hashicorp/go-hclog/stacktrace.go new file mode 100644 index 00000000..8af1a3be --- /dev/null +++ b/vendor/github.com/hashicorp/go-hclog/stacktrace.go @@ -0,0 +1,108 @@ +// Copyright (c) 2016 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package hclog + +import ( + "bytes" + "runtime" + "strconv" + "strings" + "sync" +) + +var ( + _stacktraceIgnorePrefixes = []string{ + "runtime.goexit", + "runtime.main", + } + _stacktracePool = sync.Pool{ + New: func() interface{} { + return newProgramCounters(64) + }, + } +) + +// A stacktrace gathered by a previous call to log.Stacktrace. If passed +// to a logging function, the stacktrace will be appended. +type CapturedStacktrace string + +// Gather a stacktrace of the current goroutine and return it to be passed +// to a logging function. +func Stacktrace() CapturedStacktrace { + return CapturedStacktrace(takeStacktrace()) +} + +func takeStacktrace() string { + programCounters := _stacktracePool.Get().(*programCounters) + defer _stacktracePool.Put(programCounters) + + var buffer bytes.Buffer + + for { + // Skip the call to runtime.Counters and takeStacktrace so that the + // program counters start at the caller of takeStacktrace. + n := runtime.Callers(2, programCounters.pcs) + if n < cap(programCounters.pcs) { + programCounters.pcs = programCounters.pcs[:n] + break + } + // Don't put the too-short counter slice back into the pool; this lets + // the pool adjust if we consistently take deep stacktraces. + programCounters = newProgramCounters(len(programCounters.pcs) * 2) + } + + i := 0 + frames := runtime.CallersFrames(programCounters.pcs) + for frame, more := frames.Next(); more; frame, more = frames.Next() { + if shouldIgnoreStacktraceFunction(frame.Function) { + continue + } + if i != 0 { + buffer.WriteByte('\n') + } + i++ + buffer.WriteString(frame.Function) + buffer.WriteByte('\n') + buffer.WriteByte('\t') + buffer.WriteString(frame.File) + buffer.WriteByte(':') + buffer.WriteString(strconv.Itoa(int(frame.Line))) + } + + return buffer.String() +} + +func shouldIgnoreStacktraceFunction(function string) bool { + for _, prefix := range _stacktraceIgnorePrefixes { + if strings.HasPrefix(function, prefix) { + return true + } + } + return false +} + +type programCounters struct { + pcs []uintptr +} + +func newProgramCounters(size int) *programCounters { + return &programCounters{make([]uintptr, size)} +} diff --git a/vendor/github.com/hashicorp/go-hclog/stdlog.go b/vendor/github.com/hashicorp/go-hclog/stdlog.go new file mode 100644 index 00000000..2bb927fc --- /dev/null +++ b/vendor/github.com/hashicorp/go-hclog/stdlog.go @@ -0,0 +1,62 @@ +package hclog + +import ( + "bytes" + "strings" +) + +// Provides a io.Writer to shim the data out of *log.Logger +// and back into our Logger. This is basically the only way to +// build upon *log.Logger. +type stdlogAdapter struct { + hl Logger + inferLevels bool +} + +// Take the data, infer the levels if configured, and send it through +// a regular Logger +func (s *stdlogAdapter) Write(data []byte) (int, error) { + str := string(bytes.TrimRight(data, " \t\n")) + + if s.inferLevels { + level, str := s.pickLevel(str) + switch level { + case Trace: + s.hl.Trace(str) + case Debug: + s.hl.Debug(str) + case Info: + s.hl.Info(str) + case Warn: + s.hl.Warn(str) + case Error: + s.hl.Error(str) + default: + s.hl.Info(str) + } + } else { + s.hl.Info(str) + } + + return len(data), nil +} + +// Detect, based on conventions, what log level this is +func (s *stdlogAdapter) pickLevel(str string) (Level, string) { + switch { + case strings.HasPrefix(str, "[DEBUG]"): + return Debug, strings.TrimSpace(str[7:]) + case strings.HasPrefix(str, "[TRACE]"): + return Trace, strings.TrimSpace(str[7:]) + case strings.HasPrefix(str, "[INFO]"): + return Info, strings.TrimSpace(str[6:]) + case strings.HasPrefix(str, "[WARN]"): + return Warn, strings.TrimSpace(str[7:]) + case strings.HasPrefix(str, "[ERROR]"): + return Error, strings.TrimSpace(str[7:]) + case strings.HasPrefix(str, "[ERR]"): + return Error, strings.TrimSpace(str[5:]) + default: + return Info, str + } +} diff --git a/vendor/github.com/hashicorp/go-hclog/stdlog_test.go b/vendor/github.com/hashicorp/go-hclog/stdlog_test.go new file mode 100644 index 00000000..8de180cc --- /dev/null +++ b/vendor/github.com/hashicorp/go-hclog/stdlog_test.go @@ -0,0 +1,63 @@ +package hclog + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestStdlogAdapter(t *testing.T) { + t.Run("picks debug level", func(t *testing.T) { + var s stdlogAdapter + + level, rest := s.pickLevel("[DEBUG] coffee?") + + assert.Equal(t, Debug, level) + assert.Equal(t, "coffee?", rest) + }) + + t.Run("picks trace level", func(t *testing.T) { + var s stdlogAdapter + + level, rest := s.pickLevel("[TRACE] coffee?") + + assert.Equal(t, Trace, level) + assert.Equal(t, "coffee?", rest) + }) + + t.Run("picks info level", func(t *testing.T) { + var s stdlogAdapter + + level, rest := s.pickLevel("[INFO] coffee?") + + assert.Equal(t, Info, level) + assert.Equal(t, "coffee?", rest) + }) + + t.Run("picks warn level", func(t *testing.T) { + var s stdlogAdapter + + level, rest := s.pickLevel("[WARN] coffee?") + + assert.Equal(t, Warn, level) + assert.Equal(t, "coffee?", rest) + }) + + t.Run("picks error level", func(t *testing.T) { + var s stdlogAdapter + + level, rest := s.pickLevel("[ERROR] coffee?") + + assert.Equal(t, Error, level) + assert.Equal(t, "coffee?", rest) + }) + + t.Run("picks error as err level", func(t *testing.T) { + var s stdlogAdapter + + level, rest := s.pickLevel("[ERR] coffee?") + + assert.Equal(t, Error, level) + assert.Equal(t, "coffee?", rest) + }) +} diff --git a/vendor/github.com/hashicorp/go-plugin/.gitignore b/vendor/github.com/hashicorp/go-plugin/.gitignore new file mode 100644 index 00000000..e43b0f98 --- /dev/null +++ b/vendor/github.com/hashicorp/go-plugin/.gitignore @@ -0,0 +1 @@ +.DS_Store diff --git a/vendor/github.com/hashicorp/go-plugin/LICENSE b/vendor/github.com/hashicorp/go-plugin/LICENSE new file mode 100644 index 00000000..82b4de97 --- /dev/null +++ b/vendor/github.com/hashicorp/go-plugin/LICENSE @@ -0,0 +1,353 @@ +Mozilla Public License, version 2.0 + +1. Definitions + +1.1. “Contributor” + + means each individual or legal entity that creates, contributes to the + creation of, or owns Covered Software. + +1.2. “Contributor Version” + + means the combination of the Contributions of others (if any) used by a + Contributor and that particular Contributor’s Contribution. + +1.3. “Contribution” + + means Covered Software of a particular Contributor. + +1.4. “Covered Software” + + means Source Code Form to which the initial Contributor has attached the + notice in Exhibit A, the Executable Form of such Source Code Form, and + Modifications of such Source Code Form, in each case including portions + thereof. + +1.5. “Incompatible With Secondary Licenses” + means + + a. that the initial Contributor has attached the notice described in + Exhibit B to the Covered Software; or + + b. that the Covered Software was made available under the terms of version + 1.1 or earlier of the License, but not also under the terms of a + Secondary License. + +1.6. “Executable Form” + + means any form of the work other than Source Code Form. + +1.7. “Larger Work” + + means a work that combines Covered Software with other material, in a separate + file or files, that is not Covered Software. + +1.8. “License” + + means this document. + +1.9. “Licensable” + + means having the right to grant, to the maximum extent possible, whether at the + time of the initial grant or subsequently, any and all of the rights conveyed by + this License. + +1.10. “Modifications” + + means any of the following: + + a. any file in Source Code Form that results from an addition to, deletion + from, or modification of the contents of Covered Software; or + + b. any new file in Source Code Form that contains any Covered Software. + +1.11. “Patent Claims” of a Contributor + + means any patent claim(s), including without limitation, method, process, + and apparatus claims, in any patent Licensable by such Contributor that + would be infringed, but for the grant of the License, by the making, + using, selling, offering for sale, having made, import, or transfer of + either its Contributions or its Contributor Version. + +1.12. “Secondary License” + + means either the GNU General Public License, Version 2.0, the GNU Lesser + General Public License, Version 2.1, the GNU Affero General Public + License, Version 3.0, or any later versions of those licenses. + +1.13. “Source Code Form” + + means the form of the work preferred for making modifications. + +1.14. “You” (or “Your”) + + means an individual or a legal entity exercising rights under this + License. For legal entities, “You” includes any entity that controls, is + controlled by, or is under common control with You. For purposes of this + definition, “control” means (a) the power, direct or indirect, to cause + the direction or management of such entity, whether by contract or + otherwise, or (b) ownership of more than fifty percent (50%) of the + outstanding shares or beneficial ownership of such entity. + + +2. License Grants and Conditions + +2.1. Grants + + Each Contributor hereby grants You a world-wide, royalty-free, + non-exclusive license: + + a. under intellectual property rights (other than patent or trademark) + Licensable by such Contributor to use, reproduce, make available, + modify, display, perform, distribute, and otherwise exploit its + Contributions, either on an unmodified basis, with Modifications, or as + part of a Larger Work; and + + b. under Patent Claims of such Contributor to make, use, sell, offer for + sale, have made, import, and otherwise transfer either its Contributions + or its Contributor Version. + +2.2. Effective Date + + The licenses granted in Section 2.1 with respect to any Contribution become + effective for each Contribution on the date the Contributor first distributes + such Contribution. + +2.3. Limitations on Grant Scope + + The licenses granted in this Section 2 are the only rights granted under this + License. No additional rights or licenses will be implied from the distribution + or licensing of Covered Software under this License. Notwithstanding Section + 2.1(b) above, no patent license is granted by a Contributor: + + a. for any code that a Contributor has removed from Covered Software; or + + b. for infringements caused by: (i) Your and any other third party’s + modifications of Covered Software, or (ii) the combination of its + Contributions with other software (except as part of its Contributor + Version); or + + c. under Patent Claims infringed by Covered Software in the absence of its + Contributions. + + This License does not grant any rights in the trademarks, service marks, or + logos of any Contributor (except as may be necessary to comply with the + notice requirements in Section 3.4). + +2.4. Subsequent Licenses + + No Contributor makes additional grants as a result of Your choice to + distribute the Covered Software under a subsequent version of this License + (see Section 10.2) or under the terms of a Secondary License (if permitted + under the terms of Section 3.3). + +2.5. Representation + + Each Contributor represents that the Contributor believes its Contributions + are its original creation(s) or it has sufficient rights to grant the + rights to its Contributions conveyed by this License. + +2.6. Fair Use + + This License is not intended to limit any rights You have under applicable + copyright doctrines of fair use, fair dealing, or other equivalents. + +2.7. Conditions + + Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in + Section 2.1. + + +3. Responsibilities + +3.1. Distribution of Source Form + + All distribution of Covered Software in Source Code Form, including any + Modifications that You create or to which You contribute, must be under the + terms of this License. You must inform recipients that the Source Code Form + of the Covered Software is governed by the terms of this License, and how + they can obtain a copy of this License. You may not attempt to alter or + restrict the recipients’ rights in the Source Code Form. + +3.2. Distribution of Executable Form + + If You distribute Covered Software in Executable Form then: + + a. such Covered Software must also be made available in Source Code Form, + as described in Section 3.1, and You must inform recipients of the + Executable Form how they can obtain a copy of such Source Code Form by + reasonable means in a timely manner, at a charge no more than the cost + of distribution to the recipient; and + + b. You may distribute such Executable Form under the terms of this License, + or sublicense it under different terms, provided that the license for + the Executable Form does not attempt to limit or alter the recipients’ + rights in the Source Code Form under this License. + +3.3. Distribution of a Larger Work + + You may create and distribute a Larger Work under terms of Your choice, + provided that You also comply with the requirements of this License for the + Covered Software. If the Larger Work is a combination of Covered Software + with a work governed by one or more Secondary Licenses, and the Covered + Software is not Incompatible With Secondary Licenses, this License permits + You to additionally distribute such Covered Software under the terms of + such Secondary License(s), so that the recipient of the Larger Work may, at + their option, further distribute the Covered Software under the terms of + either this License or such Secondary License(s). + +3.4. Notices + + You may not remove or alter the substance of any license notices (including + copyright notices, patent notices, disclaimers of warranty, or limitations + of liability) contained within the Source Code Form of the Covered + Software, except that You may alter any license notices to the extent + required to remedy known factual inaccuracies. + +3.5. Application of Additional Terms + + You may choose to offer, and to charge a fee for, warranty, support, + indemnity or liability obligations to one or more recipients of Covered + Software. However, You may do so only on Your own behalf, and not on behalf + of any Contributor. You must make it absolutely clear that any such + warranty, support, indemnity, or liability obligation is offered by You + alone, and You hereby agree to indemnify every Contributor for any + liability incurred by such Contributor as a result of warranty, support, + indemnity or liability terms You offer. You may include additional + disclaimers of warranty and limitations of liability specific to any + jurisdiction. + +4. Inability to Comply Due to Statute or Regulation + + If it is impossible for You to comply with any of the terms of this License + with respect to some or all of the Covered Software due to statute, judicial + order, or regulation then You must: (a) comply with the terms of this License + to the maximum extent possible; and (b) describe the limitations and the code + they affect. Such description must be placed in a text file included with all + distributions of the Covered Software under this License. Except to the + extent prohibited by statute or regulation, such description must be + sufficiently detailed for a recipient of ordinary skill to be able to + understand it. + +5. Termination + +5.1. The rights granted under this License will terminate automatically if You + fail to comply with any of its terms. However, if You become compliant, + then the rights granted under this License from a particular Contributor + are reinstated (a) provisionally, unless and until such Contributor + explicitly and finally terminates Your grants, and (b) on an ongoing basis, + if such Contributor fails to notify You of the non-compliance by some + reasonable means prior to 60 days after You have come back into compliance. + Moreover, Your grants from a particular Contributor are reinstated on an + ongoing basis if such Contributor notifies You of the non-compliance by + some reasonable means, this is the first time You have received notice of + non-compliance with this License from such Contributor, and You become + compliant prior to 30 days after Your receipt of the notice. + +5.2. If You initiate litigation against any entity by asserting a patent + infringement claim (excluding declaratory judgment actions, counter-claims, + and cross-claims) alleging that a Contributor Version directly or + indirectly infringes any patent, then the rights granted to You by any and + all Contributors for the Covered Software under Section 2.1 of this License + shall terminate. + +5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user + license agreements (excluding distributors and resellers) which have been + validly granted by You or Your distributors under this License prior to + termination shall survive termination. + +6. Disclaimer of Warranty + + Covered Software is provided under this License on an “as is” basis, without + warranty of any kind, either expressed, implied, or statutory, including, + without limitation, warranties that the Covered Software is free of defects, + merchantable, fit for a particular purpose or non-infringing. The entire + risk as to the quality and performance of the Covered Software is with You. + Should any Covered Software prove defective in any respect, You (not any + Contributor) assume the cost of any necessary servicing, repair, or + correction. This disclaimer of warranty constitutes an essential part of this + License. No use of any Covered Software is authorized under this License + except under this disclaimer. + +7. Limitation of Liability + + Under no circumstances and under no legal theory, whether tort (including + negligence), contract, or otherwise, shall any Contributor, or anyone who + distributes Covered Software as permitted above, be liable to You for any + direct, indirect, special, incidental, or consequential damages of any + character including, without limitation, damages for lost profits, loss of + goodwill, work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses, even if such party shall have been + informed of the possibility of such damages. This limitation of liability + shall not apply to liability for death or personal injury resulting from such + party’s negligence to the extent applicable law prohibits such limitation. + Some jurisdictions do not allow the exclusion or limitation of incidental or + consequential damages, so this exclusion and limitation may not apply to You. + +8. Litigation + + Any litigation relating to this License may be brought only in the courts of + a jurisdiction where the defendant maintains its principal place of business + and such litigation shall be governed by laws of that jurisdiction, without + reference to its conflict-of-law provisions. Nothing in this Section shall + prevent a party’s ability to bring cross-claims or counter-claims. + +9. Miscellaneous + + This License represents the complete agreement concerning the subject matter + hereof. If any provision of this License is held to be unenforceable, such + provision shall be reformed only to the extent necessary to make it + enforceable. Any law or regulation which provides that the language of a + contract shall be construed against the drafter shall not be used to construe + this License against a Contributor. + + +10. Versions of the License + +10.1. New Versions + + Mozilla Foundation is the license steward. Except as provided in Section + 10.3, no one other than the license steward has the right to modify or + publish new versions of this License. Each version will be given a + distinguishing version number. + +10.2. Effect of New Versions + + You may distribute the Covered Software under the terms of the version of + the License under which You originally received the Covered Software, or + under the terms of any subsequent version published by the license + steward. + +10.3. Modified Versions + + If you create software not governed by this License, and you want to + create a new license for such software, you may create and use a modified + version of this License if you rename the license and remove any + references to the name of the license steward (except to note that such + modified license differs from this License). + +10.4. Distributing Source Code Form that is Incompatible With Secondary Licenses + If You choose to distribute Source Code Form that is Incompatible With + Secondary Licenses under the terms of this version of the License, the + notice described in Exhibit B of this License must be attached. + +Exhibit A - Source Code Form License Notice + + This Source Code Form is subject to the + terms of the Mozilla Public License, v. + 2.0. If a copy of the MPL was not + distributed with this file, You can + obtain one at + http://mozilla.org/MPL/2.0/. + +If it is not possible or desirable to put the notice in a particular file, then +You may include the notice in a location (such as a LICENSE file in a relevant +directory) where a recipient would be likely to look for such a notice. + +You may add additional accurate notices of copyright ownership. + +Exhibit B - “Incompatible With Secondary Licenses” Notice + + This Source Code Form is “Incompatible + With Secondary Licenses”, as defined by + the Mozilla Public License, v. 2.0. diff --git a/vendor/github.com/hashicorp/go-plugin/README.md b/vendor/github.com/hashicorp/go-plugin/README.md new file mode 100644 index 00000000..e4558dbc --- /dev/null +++ b/vendor/github.com/hashicorp/go-plugin/README.md @@ -0,0 +1,168 @@ +# Go Plugin System over RPC + +`go-plugin` is a Go (golang) plugin system over RPC. It is the plugin system +that has been in use by HashiCorp tooling for over 4 years. While initially +created for [Packer](https://www.packer.io), it is additionally in use by +[Terraform](https://www.terraform.io), [Nomad](https://www.nomadproject.io), and +[Vault](https://www.vaultproject.io). + +While the plugin system is over RPC, it is currently only designed to work +over a local [reliable] network. Plugins over a real network are not supported +and will lead to unexpected behavior. + +This plugin system has been used on millions of machines across many different +projects and has proven to be battle hardened and ready for production use. + +## Features + +The HashiCorp plugin system supports a number of features: + +**Plugins are Go interface implementations.** This makes writing and consuming +plugins feel very natural. To a plugin author: you just implement an +interface as if it were going to run in the same process. For a plugin user: +you just use and call functions on an interface as if it were in the same +process. This plugin system handles the communication in between. + +**Cross-language support.** Plugins can be written (and consumed) by +almost every major language. This library supports serving plugins via +[gRPC](http://www.grpc.io). gRPC-based plugins enable plugins to be written +in any language. + +**Complex arguments and return values are supported.** This library +provides APIs for handling complex arguments and return values such +as interfaces, `io.Reader/Writer`, etc. We do this by giving you a library +(`MuxBroker`) for creating new connections between the client/server to +serve additional interfaces or transfer raw data. + +**Bidirectional communication.** Because the plugin system supports +complex arguments, the host process can send it interface implementations +and the plugin can call back into the host process. + +**Built-in Logging.** Any plugins that use the `log` standard library +will have log data automatically sent to the host process. The host +process will mirror this output prefixed with the path to the plugin +binary. This makes debugging with plugins simple. If the host system +uses [hclog](https://github.com/hashicorp/go-hclog) then the log data +will be structured. If the plugin also uses hclog, logs from the plugin +will be sent to the host hclog and be structured. + +**Protocol Versioning.** A very basic "protocol version" is supported that +can be incremented to invalidate any previous plugins. This is useful when +interface signatures are changing, protocol level changes are necessary, +etc. When a protocol version is incompatible, a human friendly error +message is shown to the end user. + +**Stdout/Stderr Syncing.** While plugins are subprocesses, they can continue +to use stdout/stderr as usual and the output will get mirrored back to +the host process. The host process can control what `io.Writer` these +streams go to to prevent this from happening. + +**TTY Preservation.** Plugin subprocesses are connected to the identical +stdin file descriptor as the host process, allowing software that requires +a TTY to work. For example, a plugin can execute `ssh` and even though there +are multiple subprocesses and RPC happening, it will look and act perfectly +to the end user. + +**Host upgrade while a plugin is running.** Plugins can be "reattached" +so that the host process can be upgraded while the plugin is still running. +This requires the host/plugin to know this is possible and daemonize +properly. `NewClient` takes a `ReattachConfig` to determine if and how to +reattach. + +**Cryptographically Secure Plugins.** Plugins can be verified with an expected +checksum and RPC communications can be configured to use TLS. The host process +must be properly secured to protect this configuration. + +## Architecture + +The HashiCorp plugin system works by launching subprocesses and communicating +over RPC (using standard `net/rpc` or [gRPC](http://www.grpc.io)). A single +connection is made between any plugin and the host process. For net/rpc-based +plugins, we use a [connection multiplexing](https://github.com/hashicorp/yamux) +library to multiplex any other connections on top. For gRPC-based plugins, +the HTTP2 protocol handles multiplexing. + +This architecture has a number of benefits: + + * Plugins can't crash your host process: A panic in a plugin doesn't + panic the plugin user. + + * Plugins are very easy to write: just write a Go application and `go build`. + Or use any other language to write a gRPC server with a tiny amount of + boilerplate to support go-plugin. + + * Plugins are very easy to install: just put the binary in a location where + the host will find it (depends on the host but this library also provides + helpers), and the plugin host handles the rest. + + * Plugins can be relatively secure: The plugin only has access to the + interfaces and args given to it, not to the entire memory space of the + process. Additionally, go-plugin can communicate with the plugin over + TLS. + +## Usage + +To use the plugin system, you must take the following steps. These are +high-level steps that must be done. Examples are available in the +`examples/` directory. + + 1. Choose the interface(s) you want to expose for plugins. + + 2. For each interface, implement an implementation of that interface + that communicates over a `net/rpc` connection or other a + [gRPC](http://www.grpc.io) connection or both. You'll have to implement + both a client and server implementation. + + 3. Create a `Plugin` implementation that knows how to create the RPC + client/server for a given plugin type. + + 4. Plugin authors call `plugin.Serve` to serve a plugin from the + `main` function. + + 5. Plugin users use `plugin.Client` to launch a subprocess and request + an interface implementation over RPC. + +That's it! In practice, step 2 is the most tedious and time consuming step. +Even so, it isn't very difficult and you can see examples in the `examples/` +directory as well as throughout our various open source projects. + +For complete API documentation, see [GoDoc](https://godoc.org/github.com/hashicorp/go-plugin). + +## Roadmap + +Our plugin system is constantly evolving. As we use the plugin system for +new projects or for new features in existing projects, we constantly find +improvements we can make. + +At this point in time, the roadmap for the plugin system is: + +**Semantic Versioning.** Plugins will be able to implement a semantic version. +This plugin system will give host processes a system for constraining +versions. This is in addition to the protocol versioning already present +which is more for larger underlying changes. + +**Plugin fetching.** We will integrate with [go-getter](https://github.com/hashicorp/go-getter) +to support automatic download + install of plugins. Paired with cryptographically +secure plugins (above), we can make this a safe operation for an amazing +user experience. + +## What About Shared Libraries? + +When we started using plugins (late 2012, early 2013), plugins over RPC +were the only option since Go didn't support dynamic library loading. Today, +Go still doesn't support dynamic library loading, but they do intend to. +Since 2012, our plugin system has stabilized from millions of users using it, +and has many benefits we've come to value greatly. + +For example, we intend to use this plugin system in +[Vault](https://www.vaultproject.io), and dynamic library loading will +simply never be acceptable in Vault for security reasons. That is an extreme +example, but we believe our library system has more upsides than downsides +over dynamic library loading and since we've had it built and tested for years, +we'll likely continue to use it. + +Shared libraries have one major advantage over our system which is much +higher performance. In real world scenarios across our various tools, +we've never required any more performance out of our plugin system and it +has seen very high throughput, so this isn't a concern for us at the moment. + diff --git a/vendor/github.com/hashicorp/go-plugin/client.go b/vendor/github.com/hashicorp/go-plugin/client.go new file mode 100644 index 00000000..fce0614f --- /dev/null +++ b/vendor/github.com/hashicorp/go-plugin/client.go @@ -0,0 +1,792 @@ +package plugin + +import ( + "bufio" + "context" + "crypto/subtle" + "crypto/tls" + "errors" + "fmt" + "hash" + "io" + "io/ioutil" + "net" + "os" + "os/exec" + "path/filepath" + "strconv" + "strings" + "sync" + "sync/atomic" + "time" + "unicode" + + hclog "github.com/hashicorp/go-hclog" +) + +// If this is 1, then we've called CleanupClients. This can be used +// by plugin RPC implementations to change error behavior since you +// can expected network connection errors at this point. This should be +// read by using sync/atomic. +var Killed uint32 = 0 + +// This is a slice of the "managed" clients which are cleaned up when +// calling Cleanup +var managedClients = make([]*Client, 0, 5) +var managedClientsLock sync.Mutex + +// Error types +var ( + // ErrProcessNotFound is returned when a client is instantiated to + // reattach to an existing process and it isn't found. + ErrProcessNotFound = errors.New("Reattachment process not found") + + // ErrChecksumsDoNotMatch is returned when binary's checksum doesn't match + // the one provided in the SecureConfig. + ErrChecksumsDoNotMatch = errors.New("checksums did not match") + + // ErrSecureNoChecksum is returned when an empty checksum is provided to the + // SecureConfig. + ErrSecureConfigNoChecksum = errors.New("no checksum provided") + + // ErrSecureNoHash is returned when a nil Hash object is provided to the + // SecureConfig. + ErrSecureConfigNoHash = errors.New("no hash implementation provided") + + // ErrSecureConfigAndReattach is returned when both Reattach and + // SecureConfig are set. + ErrSecureConfigAndReattach = errors.New("only one of Reattach or SecureConfig can be set") +) + +// Client handles the lifecycle of a plugin application. It launches +// plugins, connects to them, dispenses interface implementations, and handles +// killing the process. +// +// Plugin hosts should use one Client for each plugin executable. To +// dispense a plugin type, use the `Client.Client` function, and then +// cal `Dispense`. This awkward API is mostly historical but is used to split +// the client that deals with subprocess management and the client that +// does RPC management. +// +// See NewClient and ClientConfig for using a Client. +type Client struct { + config *ClientConfig + exited bool + doneLogging chan struct{} + l sync.Mutex + address net.Addr + process *os.Process + client ClientProtocol + protocol Protocol + logger hclog.Logger + doneCtx context.Context +} + +// ClientConfig is the configuration used to initialize a new +// plugin client. After being used to initialize a plugin client, +// that configuration must not be modified again. +type ClientConfig struct { + // HandshakeConfig is the configuration that must match servers. + HandshakeConfig + + // Plugins are the plugins that can be consumed. + Plugins map[string]Plugin + + // One of the following must be set, but not both. + // + // Cmd is the unstarted subprocess for starting the plugin. If this is + // set, then the Client starts the plugin process on its own and connects + // to it. + // + // Reattach is configuration for reattaching to an existing plugin process + // that is already running. This isn't common. + Cmd *exec.Cmd + Reattach *ReattachConfig + + // SecureConfig is configuration for verifying the integrity of the + // executable. It can not be used with Reattach. + SecureConfig *SecureConfig + + // TLSConfig is used to enable TLS on the RPC client. + TLSConfig *tls.Config + + // Managed represents if the client should be managed by the + // plugin package or not. If true, then by calling CleanupClients, + // it will automatically be cleaned up. Otherwise, the client + // user is fully responsible for making sure to Kill all plugin + // clients. By default the client is _not_ managed. + Managed bool + + // The minimum and maximum port to use for communicating with + // the subprocess. If not set, this defaults to 10,000 and 25,000 + // respectively. + MinPort, MaxPort uint + + // StartTimeout is the timeout to wait for the plugin to say it + // has started successfully. + StartTimeout time.Duration + + // If non-nil, then the stderr of the client will be written to here + // (as well as the log). This is the original os.Stderr of the subprocess. + // This isn't the output of synced stderr. + Stderr io.Writer + + // SyncStdout, SyncStderr can be set to override the + // respective os.Std* values in the plugin. Care should be taken to + // avoid races here. If these are nil, then this will automatically be + // hooked up to os.Stdin, Stdout, and Stderr, respectively. + // + // If the default values (nil) are used, then this package will not + // sync any of these streams. + SyncStdout io.Writer + SyncStderr io.Writer + + // AllowedProtocols is a list of allowed protocols. If this isn't set, + // then only netrpc is allowed. This is so that older go-plugin systems + // can show friendly errors if they see a plugin with an unknown + // protocol. + // + // By setting this, you can cause an error immediately on plugin start + // if an unsupported protocol is used with a good error message. + // + // If this isn't set at all (nil value), then only net/rpc is accepted. + // This is done for legacy reasons. You must explicitly opt-in to + // new protocols. + AllowedProtocols []Protocol + + // Logger is the logger that the client will used. If none is provided, + // it will default to hclog's default logger. + Logger hclog.Logger +} + +// ReattachConfig is used to configure a client to reattach to an +// already-running plugin process. You can retrieve this information by +// calling ReattachConfig on Client. +type ReattachConfig struct { + Protocol Protocol + Addr net.Addr + Pid int +} + +// SecureConfig is used to configure a client to verify the integrity of an +// executable before running. It does this by verifying the checksum is +// expected. Hash is used to specify the hashing method to use when checksumming +// the file. The configuration is verified by the client by calling the +// SecureConfig.Check() function. +// +// The host process should ensure the checksum was provided by a trusted and +// authoritative source. The binary should be installed in such a way that it +// can not be modified by an unauthorized user between the time of this check +// and the time of execution. +type SecureConfig struct { + Checksum []byte + Hash hash.Hash +} + +// Check takes the filepath to an executable and returns true if the checksum of +// the file matches the checksum provided in the SecureConfig. +func (s *SecureConfig) Check(filePath string) (bool, error) { + if len(s.Checksum) == 0 { + return false, ErrSecureConfigNoChecksum + } + + if s.Hash == nil { + return false, ErrSecureConfigNoHash + } + + file, err := os.Open(filePath) + if err != nil { + return false, err + } + defer file.Close() + + _, err = io.Copy(s.Hash, file) + if err != nil { + return false, err + } + + sum := s.Hash.Sum(nil) + + return subtle.ConstantTimeCompare(sum, s.Checksum) == 1, nil +} + +// This makes sure all the managed subprocesses are killed and properly +// logged. This should be called before the parent process running the +// plugins exits. +// +// This must only be called _once_. +func CleanupClients() { + // Set the killed to true so that we don't get unexpected panics + atomic.StoreUint32(&Killed, 1) + + // Kill all the managed clients in parallel and use a WaitGroup + // to wait for them all to finish up. + var wg sync.WaitGroup + managedClientsLock.Lock() + for _, client := range managedClients { + wg.Add(1) + + go func(client *Client) { + client.Kill() + wg.Done() + }(client) + } + managedClientsLock.Unlock() + + wg.Wait() +} + +// Creates a new plugin client which manages the lifecycle of an external +// plugin and gets the address for the RPC connection. +// +// The client must be cleaned up at some point by calling Kill(). If +// the client is a managed client (created with NewManagedClient) you +// can just call CleanupClients at the end of your program and they will +// be properly cleaned. +func NewClient(config *ClientConfig) (c *Client) { + if config.MinPort == 0 && config.MaxPort == 0 { + config.MinPort = 10000 + config.MaxPort = 25000 + } + + if config.StartTimeout == 0 { + config.StartTimeout = 1 * time.Minute + } + + if config.Stderr == nil { + config.Stderr = ioutil.Discard + } + + if config.SyncStdout == nil { + config.SyncStdout = ioutil.Discard + } + if config.SyncStderr == nil { + config.SyncStderr = ioutil.Discard + } + + if config.AllowedProtocols == nil { + config.AllowedProtocols = []Protocol{ProtocolNetRPC} + } + + if config.Logger == nil { + config.Logger = hclog.New(&hclog.LoggerOptions{ + Output: hclog.DefaultOutput, + Level: hclog.Trace, + Name: "plugin", + }) + } + + c = &Client{ + config: config, + logger: config.Logger, + } + if config.Managed { + managedClientsLock.Lock() + managedClients = append(managedClients, c) + managedClientsLock.Unlock() + } + + return +} + +// Client returns the protocol client for this connection. +// +// Subsequent calls to this will return the same client. +func (c *Client) Client() (ClientProtocol, error) { + _, err := c.Start() + if err != nil { + return nil, err + } + + c.l.Lock() + defer c.l.Unlock() + + if c.client != nil { + return c.client, nil + } + + switch c.protocol { + case ProtocolNetRPC: + c.client, err = newRPCClient(c) + + case ProtocolGRPC: + c.client, err = newGRPCClient(c.doneCtx, c) + + default: + return nil, fmt.Errorf("unknown server protocol: %s", c.protocol) + } + + if err != nil { + c.client = nil + return nil, err + } + + return c.client, nil +} + +// Tells whether or not the underlying process has exited. +func (c *Client) Exited() bool { + c.l.Lock() + defer c.l.Unlock() + return c.exited +} + +// End the executing subprocess (if it is running) and perform any cleanup +// tasks necessary such as capturing any remaining logs and so on. +// +// This method blocks until the process successfully exits. +// +// This method can safely be called multiple times. +func (c *Client) Kill() { + // Grab a lock to read some private fields. + c.l.Lock() + process := c.process + addr := c.address + doneCh := c.doneLogging + c.l.Unlock() + + // If there is no process, we never started anything. Nothing to kill. + if process == nil { + return + } + + // We need to check for address here. It is possible that the plugin + // started (process != nil) but has no address (addr == nil) if the + // plugin failed at startup. If we do have an address, we need to close + // the plugin net connections. + graceful := false + if addr != nil { + // Close the client to cleanly exit the process. + client, err := c.Client() + if err == nil { + err = client.Close() + + // If there is no error, then we attempt to wait for a graceful + // exit. If there was an error, we assume that graceful cleanup + // won't happen and just force kill. + graceful = err == nil + if err != nil { + // If there was an error just log it. We're going to force + // kill in a moment anyways. + c.logger.Warn("error closing client during Kill", "err", err) + } + } + } + + // If we're attempting a graceful exit, then we wait for a short period + // of time to allow that to happen. To wait for this we just wait on the + // doneCh which would be closed if the process exits. + if graceful { + select { + case <-doneCh: + return + case <-time.After(250 * time.Millisecond): + } + } + + // If graceful exiting failed, just kill it + process.Kill() + + // Wait for the client to finish logging so we have a complete log + <-doneCh +} + +// Starts the underlying subprocess, communicating with it to negotiate +// a port for RPC connections, and returning the address to connect via RPC. +// +// This method is safe to call multiple times. Subsequent calls have no effect. +// Once a client has been started once, it cannot be started again, even if +// it was killed. +func (c *Client) Start() (addr net.Addr, err error) { + c.l.Lock() + defer c.l.Unlock() + + if c.address != nil { + return c.address, nil + } + + // If one of cmd or reattach isn't set, then it is an error. We wrap + // this in a {} for scoping reasons, and hopeful that the escape + // analysis will pop the stock here. + { + cmdSet := c.config.Cmd != nil + attachSet := c.config.Reattach != nil + secureSet := c.config.SecureConfig != nil + if cmdSet == attachSet { + return nil, fmt.Errorf("Only one of Cmd or Reattach must be set") + } + + if secureSet && attachSet { + return nil, ErrSecureConfigAndReattach + } + } + + // Create the logging channel for when we kill + c.doneLogging = make(chan struct{}) + // Create a context for when we kill + var ctxCancel context.CancelFunc + c.doneCtx, ctxCancel = context.WithCancel(context.Background()) + + if c.config.Reattach != nil { + // Verify the process still exists. If not, then it is an error + p, err := os.FindProcess(c.config.Reattach.Pid) + if err != nil { + return nil, err + } + + // Attempt to connect to the addr since on Unix systems FindProcess + // doesn't actually return an error if it can't find the process. + conn, err := net.Dial( + c.config.Reattach.Addr.Network(), + c.config.Reattach.Addr.String()) + if err != nil { + p.Kill() + return nil, ErrProcessNotFound + } + conn.Close() + + // Goroutine to mark exit status + go func(pid int) { + // Wait for the process to die + pidWait(pid) + + // Log so we can see it + c.logger.Debug("reattached plugin process exited") + + // Mark it + c.l.Lock() + defer c.l.Unlock() + c.exited = true + + // Close the logging channel since that doesn't work on reattach + close(c.doneLogging) + + // Cancel the context + ctxCancel() + }(p.Pid) + + // Set the address and process + c.address = c.config.Reattach.Addr + c.process = p + c.protocol = c.config.Reattach.Protocol + if c.protocol == "" { + // Default the protocol to net/rpc for backwards compatibility + c.protocol = ProtocolNetRPC + } + + return c.address, nil + } + + env := []string{ + fmt.Sprintf("%s=%s", c.config.MagicCookieKey, c.config.MagicCookieValue), + fmt.Sprintf("PLUGIN_MIN_PORT=%d", c.config.MinPort), + fmt.Sprintf("PLUGIN_MAX_PORT=%d", c.config.MaxPort), + } + + stdout_r, stdout_w := io.Pipe() + stderr_r, stderr_w := io.Pipe() + + cmd := c.config.Cmd + cmd.Env = append(cmd.Env, os.Environ()...) + cmd.Env = append(cmd.Env, env...) + cmd.Stdin = os.Stdin + cmd.Stderr = stderr_w + cmd.Stdout = stdout_w + + if c.config.SecureConfig != nil { + if ok, err := c.config.SecureConfig.Check(cmd.Path); err != nil { + return nil, fmt.Errorf("error verifying checksum: %s", err) + } else if !ok { + return nil, ErrChecksumsDoNotMatch + } + } + + c.logger.Debug("starting plugin", "path", cmd.Path, "args", cmd.Args) + err = cmd.Start() + if err != nil { + return + } + + // Set the process + c.process = cmd.Process + + // Make sure the command is properly cleaned up if there is an error + defer func() { + r := recover() + + if err != nil || r != nil { + cmd.Process.Kill() + } + + if r != nil { + panic(r) + } + }() + + // Start goroutine to wait for process to exit + exitCh := make(chan struct{}) + go func() { + // Make sure we close the write end of our stderr/stdout so + // that the readers send EOF properly. + defer stderr_w.Close() + defer stdout_w.Close() + + // Wait for the command to end. + cmd.Wait() + + // Log and make sure to flush the logs write away + c.logger.Debug("plugin process exited", "path", cmd.Path) + os.Stderr.Sync() + + // Mark that we exited + close(exitCh) + + // Cancel the context, marking that we exited + ctxCancel() + + // Set that we exited, which takes a lock + c.l.Lock() + defer c.l.Unlock() + c.exited = true + }() + + // Start goroutine that logs the stderr + go c.logStderr(stderr_r) + + // Start a goroutine that is going to be reading the lines + // out of stdout + linesCh := make(chan []byte) + go func() { + defer close(linesCh) + + buf := bufio.NewReader(stdout_r) + for { + line, err := buf.ReadBytes('\n') + if line != nil { + linesCh <- line + } + + if err == io.EOF { + return + } + } + }() + + // Make sure after we exit we read the lines from stdout forever + // so they don't block since it is an io.Pipe + defer func() { + go func() { + for _ = range linesCh { + } + }() + }() + + // Some channels for the next step + timeout := time.After(c.config.StartTimeout) + + // Start looking for the address + c.logger.Debug("waiting for RPC address", "path", cmd.Path) + select { + case <-timeout: + err = errors.New("timeout while waiting for plugin to start") + case <-exitCh: + err = errors.New("plugin exited before we could connect") + case lineBytes := <-linesCh: + // Trim the line and split by "|" in order to get the parts of + // the output. + line := strings.TrimSpace(string(lineBytes)) + parts := strings.SplitN(line, "|", 6) + if len(parts) < 4 { + err = fmt.Errorf( + "Unrecognized remote plugin message: %s\n\n"+ + "This usually means that the plugin is either invalid or simply\n"+ + "needs to be recompiled to support the latest protocol.", line) + return + } + + // Check the core protocol. Wrapped in a {} for scoping. + { + var coreProtocol int64 + coreProtocol, err = strconv.ParseInt(parts[0], 10, 0) + if err != nil { + err = fmt.Errorf("Error parsing core protocol version: %s", err) + return + } + + if int(coreProtocol) != CoreProtocolVersion { + err = fmt.Errorf("Incompatible core API version with plugin. "+ + "Plugin version: %s, Core version: %d\n\n"+ + "To fix this, the plugin usually only needs to be recompiled.\n"+ + "Please report this to the plugin author.", parts[0], CoreProtocolVersion) + return + } + } + + // Parse the protocol version + var protocol int64 + protocol, err = strconv.ParseInt(parts[1], 10, 0) + if err != nil { + err = fmt.Errorf("Error parsing protocol version: %s", err) + return + } + + // Test the API version + if uint(protocol) != c.config.ProtocolVersion { + err = fmt.Errorf("Incompatible API version with plugin. "+ + "Plugin version: %s, Core version: %d", parts[1], c.config.ProtocolVersion) + return + } + + switch parts[2] { + case "tcp": + addr, err = net.ResolveTCPAddr("tcp", parts[3]) + case "unix": + addr, err = net.ResolveUnixAddr("unix", parts[3]) + default: + err = fmt.Errorf("Unknown address type: %s", parts[3]) + } + + // If we have a server type, then record that. We default to net/rpc + // for backwards compatibility. + c.protocol = ProtocolNetRPC + if len(parts) >= 5 { + c.protocol = Protocol(parts[4]) + } + + found := false + for _, p := range c.config.AllowedProtocols { + if p == c.protocol { + found = true + break + } + } + if !found { + err = fmt.Errorf("Unsupported plugin protocol %q. Supported: %v", + c.protocol, c.config.AllowedProtocols) + return + } + + } + + c.address = addr + return +} + +// ReattachConfig returns the information that must be provided to NewClient +// to reattach to the plugin process that this client started. This is +// useful for plugins that detach from their parent process. +// +// If this returns nil then the process hasn't been started yet. Please +// call Start or Client before calling this. +func (c *Client) ReattachConfig() *ReattachConfig { + c.l.Lock() + defer c.l.Unlock() + + if c.address == nil { + return nil + } + + if c.config.Cmd != nil && c.config.Cmd.Process == nil { + return nil + } + + // If we connected via reattach, just return the information as-is + if c.config.Reattach != nil { + return c.config.Reattach + } + + return &ReattachConfig{ + Protocol: c.protocol, + Addr: c.address, + Pid: c.config.Cmd.Process.Pid, + } +} + +// Protocol returns the protocol of server on the remote end. This will +// start the plugin process if it isn't already started. Errors from +// starting the plugin are surpressed and ProtocolInvalid is returned. It +// is recommended you call Start explicitly before calling Protocol to ensure +// no errors occur. +func (c *Client) Protocol() Protocol { + _, err := c.Start() + if err != nil { + return ProtocolInvalid + } + + return c.protocol +} + +func netAddrDialer(addr net.Addr) func(string, time.Duration) (net.Conn, error) { + return func(_ string, _ time.Duration) (net.Conn, error) { + // Connect to the client + conn, err := net.Dial(addr.Network(), addr.String()) + if err != nil { + return nil, err + } + if tcpConn, ok := conn.(*net.TCPConn); ok { + // Make sure to set keep alive so that the connection doesn't die + tcpConn.SetKeepAlive(true) + } + + return conn, nil + } +} + +// dialer is compatible with grpc.WithDialer and creates the connection +// to the plugin. +func (c *Client) dialer(_ string, timeout time.Duration) (net.Conn, error) { + conn, err := netAddrDialer(c.address)("", timeout) + if err != nil { + return nil, err + } + + // If we have a TLS config we wrap our connection. We only do this + // for net/rpc since gRPC uses its own mechanism for TLS. + if c.protocol == ProtocolNetRPC && c.config.TLSConfig != nil { + conn = tls.Client(conn, c.config.TLSConfig) + } + + return conn, nil +} + +func (c *Client) logStderr(r io.Reader) { + bufR := bufio.NewReader(r) + l := c.logger.Named(filepath.Base(c.config.Cmd.Path)) + + for { + line, err := bufR.ReadString('\n') + if line != "" { + c.config.Stderr.Write([]byte(line)) + line = strings.TrimRightFunc(line, unicode.IsSpace) + + entry, err := parseJSON(line) + // If output is not JSON format, print directly to Debug + if err != nil { + l.Debug(line) + } else { + out := flattenKVPairs(entry.KVPairs) + + out = append(out, "timestamp", entry.Timestamp.Format(hclog.TimeFormat)) + switch hclog.LevelFromString(entry.Level) { + case hclog.Trace: + l.Trace(entry.Message, out...) + case hclog.Debug: + l.Debug(entry.Message, out...) + case hclog.Info: + l.Info(entry.Message, out...) + case hclog.Warn: + l.Warn(entry.Message, out...) + case hclog.Error: + l.Error(entry.Message, out...) + } + } + } + + if err == io.EOF { + break + } + } + + // Flag that we've completed logging for others + close(c.doneLogging) +} diff --git a/vendor/github.com/hashicorp/go-plugin/client_posix_test.go b/vendor/github.com/hashicorp/go-plugin/client_posix_test.go new file mode 100644 index 00000000..523ec119 --- /dev/null +++ b/vendor/github.com/hashicorp/go-plugin/client_posix_test.go @@ -0,0 +1,104 @@ +// +build !windows + +package plugin + +import ( + "os" + "reflect" + "syscall" + "testing" + "time" +) + +func TestClient_testInterfaceReattach(t *testing.T) { + // Setup the process for daemonization + process := helperProcess("test-interface-daemon") + if process.SysProcAttr == nil { + process.SysProcAttr = &syscall.SysProcAttr{} + } + process.SysProcAttr.Setsid = true + syscall.Umask(0) + + c := NewClient(&ClientConfig{ + Cmd: process, + HandshakeConfig: testHandshake, + Plugins: testPluginMap, + }) + + // Start it so we can get the reattach info + if _, err := c.Start(); err != nil { + t.Fatalf("err should be nil, got %s", err) + } + + // New client with reattach info + reattach := c.ReattachConfig() + if reattach == nil { + c.Kill() + t.Fatal("reattach config should be non-nil") + } + + // Find the process and defer a kill so we know it is gone + p, err := os.FindProcess(reattach.Pid) + if err != nil { + c.Kill() + t.Fatalf("couldn't find process: %s", err) + } + defer p.Kill() + + // Reattach + c = NewClient(&ClientConfig{ + Reattach: reattach, + HandshakeConfig: testHandshake, + Plugins: testPluginMap, + }) + + // Start shouldn't error + if _, err := c.Start(); err != nil { + t.Fatalf("err: %s", err) + } + + // It should still be alive + time.Sleep(1 * time.Second) + if c.Exited() { + t.Fatal("should not be exited") + } + + // Grab the RPC client + client, err := c.Client() + if err != nil { + t.Fatalf("err should be nil, got %s", err) + } + + // Grab the impl + raw, err := client.Dispense("test") + if err != nil { + t.Fatalf("err should be nil, got %s", err) + } + + impl, ok := raw.(testInterface) + if !ok { + t.Fatalf("bad: %#v", raw) + } + + result := impl.Double(21) + if result != 42 { + t.Fatalf("bad: %#v", result) + } + + // Test the resulting reattach config + reattach2 := c.ReattachConfig() + if reattach2 == nil { + t.Fatal("reattach from reattached should not be nil") + } + if !reflect.DeepEqual(reattach, reattach2) { + t.Fatalf("bad: %#v", reattach) + } + + // Kill it + c.Kill() + + // Test that it knows it is exited + if !c.Exited() { + t.Fatal("should say client has exited") + } +} diff --git a/vendor/github.com/hashicorp/go-plugin/client_test.go b/vendor/github.com/hashicorp/go-plugin/client_test.go new file mode 100644 index 00000000..6807bcc0 --- /dev/null +++ b/vendor/github.com/hashicorp/go-plugin/client_test.go @@ -0,0 +1,942 @@ +package plugin + +import ( + "bytes" + "crypto/sha256" + "io" + "io/ioutil" + "net" + "os" + "path/filepath" + "strings" + "sync" + "testing" + "time" + + hclog "github.com/hashicorp/go-hclog" +) + +func TestClient(t *testing.T) { + process := helperProcess("mock") + c := NewClient(&ClientConfig{Cmd: process, HandshakeConfig: testHandshake}) + defer c.Kill() + + // Test that it parses the proper address + addr, err := c.Start() + if err != nil { + t.Fatalf("err should be nil, got %s", err) + } + + if addr.Network() != "tcp" { + t.Fatalf("bad: %#v", addr) + } + + if addr.String() != ":1234" { + t.Fatalf("bad: %#v", addr) + } + + // Test that it exits properly if killed + c.Kill() + + if process.ProcessState == nil { + t.Fatal("should have process state") + } + + // Test that it knows it is exited + if !c.Exited() { + t.Fatal("should say client has exited") + } +} + +// This tests a bug where Kill would start +func TestClient_killStart(t *testing.T) { + // Create a temporary dir to store the result file + td, err := ioutil.TempDir("", "plugin") + if err != nil { + t.Fatalf("err: %s", err) + } + defer os.RemoveAll(td) + + // Start the client + path := filepath.Join(td, "booted") + process := helperProcess("bad-version", path) + c := NewClient(&ClientConfig{Cmd: process, HandshakeConfig: testHandshake}) + defer c.Kill() + + // Verify our path doesn't exist + if _, err := os.Stat(path); err == nil || !os.IsNotExist(err) { + t.Fatalf("bad: %s", err) + } + + // Test that it parses the proper address + if _, err := c.Start(); err == nil { + t.Fatal("expected error") + } + + // Verify we started + if _, err := os.Stat(path); err != nil { + t.Fatalf("bad: %s", err) + } + if err := os.Remove(path); err != nil { + t.Fatalf("bad: %s", err) + } + + // Test that Kill does nothing really + c.Kill() + + // Test that it knows it is exited + if !c.Exited() { + t.Fatal("should say client has exited") + } + + if process.ProcessState == nil { + t.Fatal("should have no process state") + } + + // Verify our path doesn't exist + if _, err := os.Stat(path); err == nil || !os.IsNotExist(err) { + t.Fatalf("bad: %s", err) + } +} + +func TestClient_testCleanup(t *testing.T) { + // Create a temporary dir to store the result file + td, err := ioutil.TempDir("", "plugin") + if err != nil { + t.Fatalf("err: %s", err) + } + defer os.RemoveAll(td) + + // Create a path that the helper process will write on cleanup + path := filepath.Join(td, "output") + + // Test the cleanup + process := helperProcess("cleanup", path) + c := NewClient(&ClientConfig{ + Cmd: process, + HandshakeConfig: testHandshake, + Plugins: testPluginMap, + }) + + // Grab the client so the process starts + if _, err := c.Client(); err != nil { + c.Kill() + t.Fatalf("err: %s", err) + } + + // Kill it gracefully + c.Kill() + + // Test for the file + if _, err := os.Stat(path); err != nil { + t.Fatalf("err: %s", err) + } +} + +func TestClient_testInterface(t *testing.T) { + process := helperProcess("test-interface") + c := NewClient(&ClientConfig{ + Cmd: process, + HandshakeConfig: testHandshake, + Plugins: testPluginMap, + }) + defer c.Kill() + + // Grab the RPC client + client, err := c.Client() + if err != nil { + t.Fatalf("err should be nil, got %s", err) + } + + // Grab the impl + raw, err := client.Dispense("test") + if err != nil { + t.Fatalf("err should be nil, got %s", err) + } + + impl, ok := raw.(testInterface) + if !ok { + t.Fatalf("bad: %#v", raw) + } + + result := impl.Double(21) + if result != 42 { + t.Fatalf("bad: %#v", result) + } + + // Kill it + c.Kill() + + // Test that it knows it is exited + if !c.Exited() { + t.Fatal("should say client has exited") + } +} + +func TestClient_grpc_servercrash(t *testing.T) { + process := helperProcess("test-grpc") + c := NewClient(&ClientConfig{ + Cmd: process, + HandshakeConfig: testHandshake, + Plugins: testPluginMap, + AllowedProtocols: []Protocol{ProtocolGRPC}, + }) + defer c.Kill() + + if _, err := c.Start(); err != nil { + t.Fatalf("err: %s", err) + } + + if v := c.Protocol(); v != ProtocolGRPC { + t.Fatalf("bad: %s", v) + } + + // Grab the RPC client + client, err := c.Client() + if err != nil { + t.Fatalf("err should be nil, got %s", err) + } + + // Grab the impl + raw, err := client.Dispense("test") + if err != nil { + t.Fatalf("err should be nil, got %s", err) + } + + _, ok := raw.(testInterface) + if !ok { + t.Fatalf("bad: %#v", raw) + } + + c.process.Kill() + + select { + case <-c.doneCtx.Done(): + case <-time.After(time.Second * 2): + t.Fatal("Context was not closed") + } +} + +func TestClient_grpc(t *testing.T) { + process := helperProcess("test-grpc") + c := NewClient(&ClientConfig{ + Cmd: process, + HandshakeConfig: testHandshake, + Plugins: testPluginMap, + AllowedProtocols: []Protocol{ProtocolGRPC}, + }) + defer c.Kill() + + if _, err := c.Start(); err != nil { + t.Fatalf("err: %s", err) + } + + if v := c.Protocol(); v != ProtocolGRPC { + t.Fatalf("bad: %s", v) + } + + // Grab the RPC client + client, err := c.Client() + if err != nil { + t.Fatalf("err should be nil, got %s", err) + } + + // Grab the impl + raw, err := client.Dispense("test") + if err != nil { + t.Fatalf("err should be nil, got %s", err) + } + + impl, ok := raw.(testInterface) + if !ok { + t.Fatalf("bad: %#v", raw) + } + + result := impl.Double(21) + if result != 42 { + t.Fatalf("bad: %#v", result) + } + + // Kill it + c.Kill() + + // Test that it knows it is exited + if !c.Exited() { + t.Fatal("should say client has exited") + } +} + +func TestClient_grpcNotAllowed(t *testing.T) { + process := helperProcess("test-grpc") + c := NewClient(&ClientConfig{ + Cmd: process, + HandshakeConfig: testHandshake, + Plugins: testPluginMap, + }) + defer c.Kill() + + if _, err := c.Start(); err == nil { + t.Fatal("should error") + } +} + +func TestClient_cmdAndReattach(t *testing.T) { + config := &ClientConfig{ + Cmd: helperProcess("start-timeout"), + Reattach: &ReattachConfig{}, + } + + c := NewClient(config) + defer c.Kill() + + _, err := c.Start() + if err == nil { + t.Fatal("err should not be nil") + } +} + +func TestClient_reattach(t *testing.T) { + process := helperProcess("test-interface") + c := NewClient(&ClientConfig{ + Cmd: process, + HandshakeConfig: testHandshake, + Plugins: testPluginMap, + }) + defer c.Kill() + + // Grab the RPC client + _, err := c.Client() + if err != nil { + t.Fatalf("err should be nil, got %s", err) + } + + // Get the reattach configuration + reattach := c.ReattachConfig() + + // Create a new client + c = NewClient(&ClientConfig{ + Reattach: reattach, + HandshakeConfig: testHandshake, + Plugins: testPluginMap, + }) + + // Grab the RPC client + client, err := c.Client() + if err != nil { + t.Fatalf("err should be nil, got %s", err) + } + + // Grab the impl + raw, err := client.Dispense("test") + if err != nil { + t.Fatalf("err should be nil, got %s", err) + } + + impl, ok := raw.(testInterface) + if !ok { + t.Fatalf("bad: %#v", raw) + } + + result := impl.Double(21) + if result != 42 { + t.Fatalf("bad: %#v", result) + } + + // Kill it + c.Kill() + + // Test that it knows it is exited + if !c.Exited() { + t.Fatal("should say client has exited") + } +} + +func TestClient_reattachNoProtocol(t *testing.T) { + process := helperProcess("test-interface") + c := NewClient(&ClientConfig{ + Cmd: process, + HandshakeConfig: testHandshake, + Plugins: testPluginMap, + }) + defer c.Kill() + + // Grab the RPC client + _, err := c.Client() + if err != nil { + t.Fatalf("err should be nil, got %s", err) + } + + // Get the reattach configuration + reattach := c.ReattachConfig() + reattach.Protocol = "" + + // Create a new client + c = NewClient(&ClientConfig{ + Reattach: reattach, + HandshakeConfig: testHandshake, + Plugins: testPluginMap, + }) + + // Grab the RPC client + client, err := c.Client() + if err != nil { + t.Fatalf("err should be nil, got %s", err) + } + + // Grab the impl + raw, err := client.Dispense("test") + if err != nil { + t.Fatalf("err should be nil, got %s", err) + } + + impl, ok := raw.(testInterface) + if !ok { + t.Fatalf("bad: %#v", raw) + } + + result := impl.Double(21) + if result != 42 { + t.Fatalf("bad: %#v", result) + } + + // Kill it + c.Kill() + + // Test that it knows it is exited + if !c.Exited() { + t.Fatal("should say client has exited") + } +} + +func TestClient_reattachGRPC(t *testing.T) { + process := helperProcess("test-grpc") + c := NewClient(&ClientConfig{ + Cmd: process, + HandshakeConfig: testHandshake, + Plugins: testPluginMap, + AllowedProtocols: []Protocol{ProtocolGRPC}, + }) + defer c.Kill() + + // Grab the RPC client + _, err := c.Client() + if err != nil { + t.Fatalf("err should be nil, got %s", err) + } + + // Get the reattach configuration + reattach := c.ReattachConfig() + + // Create a new client + c = NewClient(&ClientConfig{ + Reattach: reattach, + HandshakeConfig: testHandshake, + Plugins: testPluginMap, + AllowedProtocols: []Protocol{ProtocolGRPC}, + }) + + // Grab the RPC client + client, err := c.Client() + if err != nil { + t.Fatalf("err should be nil, got %s", err) + } + + // Grab the impl + raw, err := client.Dispense("test") + if err != nil { + t.Fatalf("err should be nil, got %s", err) + } + + impl, ok := raw.(testInterface) + if !ok { + t.Fatalf("bad: %#v", raw) + } + + result := impl.Double(21) + if result != 42 { + t.Fatalf("bad: %#v", result) + } + + // Kill it + c.Kill() + + // Test that it knows it is exited + if !c.Exited() { + t.Fatal("should say client has exited") + } +} + +func TestClient_reattachNotFound(t *testing.T) { + // Find a bad pid + var pid int = 5000 + for i := pid; i < 32000; i++ { + if _, err := os.FindProcess(i); err != nil { + pid = i + break + } + } + + // Addr that won't work + l, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + t.Fatalf("err: %s", err) + } + addr := l.Addr() + l.Close() + + // Reattach + c := NewClient(&ClientConfig{ + Reattach: &ReattachConfig{ + Addr: addr, + Pid: pid, + }, + HandshakeConfig: testHandshake, + Plugins: testPluginMap, + }) + + // Start shouldn't error + if _, err := c.Start(); err == nil { + t.Fatal("should error") + } else if err != ErrProcessNotFound { + t.Fatalf("err: %s", err) + } +} + +func TestClientStart_badVersion(t *testing.T) { + config := &ClientConfig{ + Cmd: helperProcess("bad-version"), + StartTimeout: 50 * time.Millisecond, + HandshakeConfig: testHandshake, + } + + c := NewClient(config) + defer c.Kill() + + _, err := c.Start() + if err == nil { + t.Fatal("err should not be nil") + } +} + +func TestClient_Start_Timeout(t *testing.T) { + config := &ClientConfig{ + Cmd: helperProcess("start-timeout"), + StartTimeout: 50 * time.Millisecond, + HandshakeConfig: testHandshake, + } + + c := NewClient(config) + defer c.Kill() + + _, err := c.Start() + if err == nil { + t.Fatal("err should not be nil") + } +} + +func TestClient_Stderr(t *testing.T) { + stderr := new(bytes.Buffer) + process := helperProcess("stderr") + c := NewClient(&ClientConfig{ + Cmd: process, + Stderr: stderr, + HandshakeConfig: testHandshake, + }) + defer c.Kill() + + if _, err := c.Start(); err != nil { + t.Fatalf("err: %s", err) + } + + for !c.Exited() { + time.Sleep(10 * time.Millisecond) + } + + if !strings.Contains(stderr.String(), "HELLO\n") { + t.Fatalf("bad log data: '%s'", stderr.String()) + } + + if !strings.Contains(stderr.String(), "WORLD\n") { + t.Fatalf("bad log data: '%s'", stderr.String()) + } +} + +func TestClient_StderrJSON(t *testing.T) { + stderr := new(bytes.Buffer) + process := helperProcess("stderr-json") + c := NewClient(&ClientConfig{ + Cmd: process, + Stderr: stderr, + HandshakeConfig: testHandshake, + }) + defer c.Kill() + + if _, err := c.Start(); err != nil { + t.Fatalf("err: %s", err) + } + + for !c.Exited() { + time.Sleep(10 * time.Millisecond) + } + + if !strings.Contains(stderr.String(), "[\"HELLO\"]\n") { + t.Fatalf("bad log data: '%s'", stderr.String()) + } + + if !strings.Contains(stderr.String(), "12345\n") { + t.Fatalf("bad log data: '%s'", stderr.String()) + } +} + +func TestClient_Stdin(t *testing.T) { + // Overwrite stdin for this test with a temporary file + tf, err := ioutil.TempFile("", "terraform") + if err != nil { + t.Fatalf("err: %s", err) + } + defer os.Remove(tf.Name()) + defer tf.Close() + + if _, err = tf.WriteString("hello"); err != nil { + t.Fatalf("error: %s", err) + } + + if err = tf.Sync(); err != nil { + t.Fatalf("error: %s", err) + } + + if _, err = tf.Seek(0, 0); err != nil { + t.Fatalf("error: %s", err) + } + + oldStdin := os.Stdin + defer func() { os.Stdin = oldStdin }() + os.Stdin = tf + + process := helperProcess("stdin") + c := NewClient(&ClientConfig{Cmd: process, HandshakeConfig: testHandshake}) + defer c.Kill() + + _, err = c.Start() + if err != nil { + t.Fatalf("error: %s", err) + } + + for { + if c.Exited() { + break + } + + time.Sleep(50 * time.Millisecond) + } + + if !process.ProcessState.Success() { + t.Fatal("process didn't exit cleanly") + } +} + +func TestClient_SecureConfig(t *testing.T) { + // Test failure case + secureConfig := &SecureConfig{ + Checksum: []byte{'1'}, + Hash: sha256.New(), + } + process := helperProcess("test-interface") + c := NewClient(&ClientConfig{ + Cmd: process, + HandshakeConfig: testHandshake, + Plugins: testPluginMap, + SecureConfig: secureConfig, + }) + + // Grab the RPC client, should error + _, err := c.Client() + c.Kill() + if err != ErrChecksumsDoNotMatch { + t.Fatalf("err should be %s, got %s", ErrChecksumsDoNotMatch, err) + } + + // Get the checksum of the executable + file, err := os.Open(os.Args[0]) + if err != nil { + t.Fatal(err) + } + defer file.Close() + + hash := sha256.New() + + _, err = io.Copy(hash, file) + if err != nil { + t.Fatal(err) + } + + sum := hash.Sum(nil) + + secureConfig = &SecureConfig{ + Checksum: sum, + Hash: sha256.New(), + } + + c = NewClient(&ClientConfig{ + Cmd: process, + HandshakeConfig: testHandshake, + Plugins: testPluginMap, + SecureConfig: secureConfig, + }) + defer c.Kill() + + // Grab the RPC client + _, err = c.Client() + if err != nil { + t.Fatalf("err should be nil, got %s", err) + } +} + +func TestClient_TLS(t *testing.T) { + // Test failure case + process := helperProcess("test-interface-tls") + cBad := NewClient(&ClientConfig{ + Cmd: process, + HandshakeConfig: testHandshake, + Plugins: testPluginMap, + }) + defer cBad.Kill() + + // Grab the RPC client + clientBad, err := cBad.Client() + if err != nil { + t.Fatalf("err should be nil, got %s", err) + } + + // Grab the impl + raw, err := clientBad.Dispense("test") + if err == nil { + t.Fatal("expected error, got nil") + } + + cBad.Kill() + + // Add TLS config to client + tlsConfig, err := helperTLSProvider() + if err != nil { + t.Fatalf("err should be nil, got %s", err) + } + + process = helperProcess("test-interface-tls") + c := NewClient(&ClientConfig{ + Cmd: process, + HandshakeConfig: testHandshake, + Plugins: testPluginMap, + TLSConfig: tlsConfig, + }) + defer c.Kill() + + // Grab the RPC client + client, err := c.Client() + if err != nil { + t.Fatalf("err should be nil, got %s", err) + } + + // Grab the impl + raw, err = client.Dispense("test") + if err != nil { + t.Fatalf("err should be nil, got %s", err) + } + + impl, ok := raw.(testInterface) + if !ok { + t.Fatalf("bad: %#v", raw) + } + + result := impl.Double(21) + if result != 42 { + t.Fatalf("bad: %#v", result) + } + + // Kill it + c.Kill() + + // Test that it knows it is exited + if !c.Exited() { + t.Fatal("should say client has exited") + } +} + +func TestClient_TLS_grpc(t *testing.T) { + // Add TLS config to client + tlsConfig, err := helperTLSProvider() + if err != nil { + t.Fatalf("err should be nil, got %s", err) + } + + process := helperProcess("test-grpc-tls") + c := NewClient(&ClientConfig{ + Cmd: process, + HandshakeConfig: testHandshake, + Plugins: testPluginMap, + TLSConfig: tlsConfig, + AllowedProtocols: []Protocol{ProtocolGRPC}, + }) + defer c.Kill() + + // Grab the RPC client + client, err := c.Client() + if err != nil { + t.Fatalf("err should be nil, got %s", err) + } + + // Grab the impl + raw, err := client.Dispense("test") + if err != nil { + t.Fatalf("err should be nil, got %s", err) + } + + impl, ok := raw.(testInterface) + if !ok { + t.Fatalf("bad: %#v", raw) + } + + result := impl.Double(21) + if result != 42 { + t.Fatalf("bad: %#v", result) + } + + // Kill it + c.Kill() + + // Test that it knows it is exited + if !c.Exited() { + t.Fatal("should say client has exited") + } +} + +func TestClient_secureConfigAndReattach(t *testing.T) { + config := &ClientConfig{ + SecureConfig: &SecureConfig{}, + Reattach: &ReattachConfig{}, + } + + c := NewClient(config) + defer c.Kill() + + _, err := c.Start() + if err != ErrSecureConfigAndReattach { + t.Fatalf("err should not be %s, got %s", ErrSecureConfigAndReattach, err) + } +} + +func TestClient_ping(t *testing.T) { + process := helperProcess("test-interface") + c := NewClient(&ClientConfig{ + Cmd: process, + HandshakeConfig: testHandshake, + Plugins: testPluginMap, + }) + defer c.Kill() + + // Get the client + client, err := c.Client() + if err != nil { + t.Fatalf("err: %s", err) + } + + // Ping, should work + if err := client.Ping(); err != nil { + t.Fatalf("err: %s", err) + } + + // Kill it + c.Kill() + if err := client.Ping(); err == nil { + t.Fatal("should error") + } +} + +func TestClient_logger(t *testing.T) { + t.Run("net/rpc", func(t *testing.T) { testClient_logger(t, "netrpc") }) + t.Run("grpc", func(t *testing.T) { testClient_logger(t, "grpc") }) +} + +func testClient_logger(t *testing.T, proto string) { + var buffer bytes.Buffer + mutex := new(sync.Mutex) + stderr := io.MultiWriter(os.Stderr, &buffer) + // Custom hclog.Logger + clientLogger := hclog.New(&hclog.LoggerOptions{ + Name: "test-logger", + Level: hclog.Trace, + Output: stderr, + Mutex: mutex, + }) + + process := helperProcess("test-interface-logger-" + proto) + c := NewClient(&ClientConfig{ + Cmd: process, + HandshakeConfig: testHandshake, + Plugins: testPluginMap, + Logger: clientLogger, + AllowedProtocols: []Protocol{ProtocolNetRPC, ProtocolGRPC}, + }) + defer c.Kill() + + // Grab the RPC client + client, err := c.Client() + if err != nil { + t.Fatalf("err should be nil, got %s", err) + } + + // Grab the impl + raw, err := client.Dispense("test") + if err != nil { + t.Fatalf("err should be nil, got %s", err) + } + + impl, ok := raw.(testInterface) + if !ok { + t.Fatalf("bad: %#v", raw) + } + + { + // Discard everything else, and capture the output we care about + mutex.Lock() + buffer.Reset() + mutex.Unlock() + impl.PrintKV("foo", "bar") + time.Sleep(100 * time.Millisecond) + mutex.Lock() + line, err := buffer.ReadString('\n') + mutex.Unlock() + if err != nil { + t.Fatal(err) + } + if !strings.Contains(line, "foo=bar") { + t.Fatalf("bad: %q", line) + } + } + + { + // Try an integer type + mutex.Lock() + buffer.Reset() + mutex.Unlock() + impl.PrintKV("foo", 12) + time.Sleep(100 * time.Millisecond) + mutex.Lock() + line, err := buffer.ReadString('\n') + mutex.Unlock() + if err != nil { + t.Fatal(err) + } + if !strings.Contains(line, "foo=12") { + t.Fatalf("bad: %q", line) + } + } + + // Kill it + c.Kill() + + // Test that it knows it is exited + if !c.Exited() { + t.Fatal("should say client has exited") + } +} diff --git a/vendor/github.com/hashicorp/go-plugin/discover.go b/vendor/github.com/hashicorp/go-plugin/discover.go new file mode 100644 index 00000000..d22c566e --- /dev/null +++ b/vendor/github.com/hashicorp/go-plugin/discover.go @@ -0,0 +1,28 @@ +package plugin + +import ( + "path/filepath" +) + +// Discover discovers plugins that are in a given directory. +// +// The directory doesn't need to be absolute. For example, "." will work fine. +// +// This currently assumes any file matching the glob is a plugin. +// In the future this may be smarter about checking that a file is +// executable and so on. +// +// TODO: test +func Discover(glob, dir string) ([]string, error) { + var err error + + // Make the directory absolute if it isn't already + if !filepath.IsAbs(dir) { + dir, err = filepath.Abs(dir) + if err != nil { + return nil, err + } + } + + return filepath.Glob(filepath.Join(dir, glob)) +} diff --git a/vendor/github.com/hashicorp/go-plugin/docs/README.md b/vendor/github.com/hashicorp/go-plugin/docs/README.md new file mode 100644 index 00000000..9fbb44b7 --- /dev/null +++ b/vendor/github.com/hashicorp/go-plugin/docs/README.md @@ -0,0 +1,12 @@ +# go-plugin Documentation + +This directory contains documentation and guides for `go-plugin` and how +to integrate it into your projects. It is assumed that you know _what_ +go-plugin is and _why_ you would want to use it. If not, please see the +[README](https://github.com/hashicorp/go-plugin/blob/master/README.md). + +## Table of Contents + +**[Writing Plugins Without Go](https://github.com/hashicorp/go-plugin/blob/master/docs/guide-plugin-write-non-go.md).** +This shows how to write a plugin using a programming language other than +Go. diff --git a/vendor/github.com/hashicorp/go-plugin/docs/guide-plugin-write-non-go.md b/vendor/github.com/hashicorp/go-plugin/docs/guide-plugin-write-non-go.md new file mode 100644 index 00000000..0705912d --- /dev/null +++ b/vendor/github.com/hashicorp/go-plugin/docs/guide-plugin-write-non-go.md @@ -0,0 +1,150 @@ +# Writing Plugins Without Go + +This guide explains how to write a go-plugin compatible plugin using +a programming language other than Go. go-plugin supports plugins using +[gRPC](http://www.grpc.io). This makes it relatively simple to write plugins +using other languages! + +Minimal knowledge about gRPC is assumed. We recommend reading the +[gRPC Go Tutorial](http://www.grpc.io/docs/tutorials/basic/go.html). This +alone is enough gRPC knowledge to continue. + +This guide will implement the kv example in Python. +Full source code for the examples present in this guide +[is available in the examples/grpc folder](https://github.com/hashicorp/go-plugin/tree/master/examples/grpc). + +## 1. Implement the Service + +The first step is to implement the gRPC server for the protocol buffers +service that your plugin defines. This is a standard gRPC server. +For the KV service, the service looks like this: + +```proto +service KV { + rpc Get(GetRequest) returns (GetResponse); + rpc Put(PutRequest) returns (Empty); +} +``` + +We can implement that using Python as easily as: + +```python +class KVServicer(kv_pb2_grpc.KVServicer): + """Implementation of KV service.""" + + def Get(self, request, context): + filename = "kv_"+request.key + with open(filename, 'r') as f: + result = kv_pb2.GetResponse() + result.value = f.read() + return result + + def Put(self, request, context): + filename = "kv_"+request.key + value = "{0}\n\nWritten from plugin-python".format(request.value) + with open(filename, 'w') as f: + f.write(value) + + return kv_pb2.Empty() + +``` + +Great! With that, we have a fully functioning implementation of the service. +You can test this using standard gRPC testing mechanisms. + +## 2. Serve the Service + +Next, we need to create a gRPC server and serve the service we just made. + +In Python: + +```python +# Make the server +server = grpc.server(futures.ThreadPoolExecutor(max_workers=10)) + +# Add our service +kv_pb2_grpc.add_KVServicer_to_server(KVServicer(), server) + +# Listen on a port +server.add_insecure_port(':1234') + +# Start +server.start() +``` + +You can listen on any TCP address or Unix domain socket. go-plugin does +assume that connections are reliable (local), so you should not serve +your plugin across the network. + +## 3. Add the gRPC Health Checking Service + +go-plugin requires the +[gRPC Health Checking Service](https://github.com/grpc/grpc/blob/master/doc/health-checking.md) +to be registered on your server. You must register the status of "plugin" to be SERVING. + +The health checking service is used by go-plugin to determine if everything +is healthy with the connection. If you don't implement this service, your +process may be abruptly restarted and your plugins are likely to be unreliable. + +``` +health = HealthServicer() +health.set("plugin", health_pb2.HealthCheckResponse.ServingStatus.Value('SERVING')) +health_pb2_grpc.add_HealthServicer_to_server(health, server) +``` + +## 4. Output Handshake Information + +The final step is to output the handshake information to stdout. go-plugin +reads a single line from stdout to determine how to connect to your plugin, +what protocol it is using, etc. + + +The structure is: + +``` +CORE-PROTOCOL-VERSION | APP-PROTOCOL-VERSION | NETWORK-TYPE | NETWORK-ADDR | PROTOCOL +``` + +Where: + + * `CORE-PROTOCOL-VERSION` is the protocol version for go-plugin itself. + The current value is `1`. Please use this value. Any other value will + cause your plugin to not load. + + * `APP-PROTOCOL-VERSION` is the protocol version for the application data. + This is determined by the application. You must reference the documentation + for your application to determine the desired value. + + * `NETWORK-TYPE` and `NETWORK-ADDR` are the networking information for + connecting to this plugin. The type must be "unix" or "tcp". The address + is a path to the Unix socket for "unix" and an IP address for "tcp". + + * `PROTOCOL` is the named protocol that the connection will use. If this + is omitted (older versions), this is "netrpc" for Go net/rpc. This can + also be "grpc". This is the protocol that the plugin wants to speak to + the host process with. + +For our example that is: + +``` +1|1|tcp|127.0.0.1:1234|grpc +``` + +The only element you'll have to be careful about is the second one (the +`APP-PROTOCOL-VERISON`). This will depend on the application you're +building a plugin for. Please reference their documentation for more +information. + +## 5. Done! + +And we're done! + +Configure the host application (the application you're writing a plugin +for) to execute your Python application. Configuring plugins is specific +to the host application. + +For our example, we used an environmental variable, and it looks like this: + +```sh +$ export KV_PLUGIN="python plugin.py" +``` diff --git a/vendor/github.com/hashicorp/go-plugin/docs/internals.md b/vendor/github.com/hashicorp/go-plugin/docs/internals.md new file mode 100644 index 00000000..b6bdaad6 --- /dev/null +++ b/vendor/github.com/hashicorp/go-plugin/docs/internals.md @@ -0,0 +1,63 @@ +# go-plugin Internals + +This section discusses the internals of how go-plugin works. + +go-plugin operates by either _serving_ a plugin or being a _client_ +connecting to a remote plugin. The "client" is the host process or the +process that itself uses plugins. The "server" is the plugin process. + +For a server: + + 1. Output handshake to stdout + 2. Wait for connection on control address + 3. Serve plugins over control address + +For a client: + + 1. Launch a plugin binary + 2. Read and verify handshake from plugin stdout + 3. Connect to plugin control address using desired protocol + 4. Dispense plugins using control connection + +## Handshake + +The handshake is the initial communication between a plugin and a host +process to determine how the host process can connect and communicate to +the plugin. This handshake is done over the plugin process's stdout. + +The `go-plugin` library itself handles the handshake when using the +`Server` to serve a plugin. **You do not need to understand the internals +of the handshake,** unless you're building a go-plugin compatible plugin +in another language. + +The handshake is a single line of data terminated with a newline character +`\n`. It looks like the following: + +``` +1|3|unix|/path/to/socket|grpc +``` + +The structure is: + +``` +CORE-PROTOCOL-VERSION | APP-PROTOCOL-VERSION | NETWORK-TYPE | NETWORK-ADDR | PROTOCOL +``` + +Where: + + * `CORE-PROTOCOL-VERSION` is the protocol version for go-plugin itself. + The current value is `1`. Please use this value. Any other value will + cause your plugin to not load. + + * `APP-PROTOCOL-VERSION` is the protocol version for the application data. + This is determined by the application. You must reference the documentation + for your application to determine the desired value. + + * `NETWORK-TYPE` and `NETWORK-ADDR` are the networking information for + connecting to this plugin. The type must be "unix" or "tcp". The address + is a path to the Unix socket for "unix" and an IP address for "tcp". + + * `PROTOCOL` is the named protocol that the connection will use. If this + is omitted (older versions), this is "netrpc" for Go net/rpc. This can + also be "grpc". This is the protocol that the plugin wants to speak to + the host process with. diff --git a/vendor/github.com/hashicorp/go-plugin/error.go b/vendor/github.com/hashicorp/go-plugin/error.go new file mode 100644 index 00000000..22a7baa6 --- /dev/null +++ b/vendor/github.com/hashicorp/go-plugin/error.go @@ -0,0 +1,24 @@ +package plugin + +// This is a type that wraps error types so that they can be messaged +// across RPC channels. Since "error" is an interface, we can't always +// gob-encode the underlying structure. This is a valid error interface +// implementer that we will push across. +type BasicError struct { + Message string +} + +// NewBasicError is used to create a BasicError. +// +// err is allowed to be nil. +func NewBasicError(err error) *BasicError { + if err == nil { + return nil + } + + return &BasicError{err.Error()} +} + +func (e *BasicError) Error() string { + return e.Message +} diff --git a/vendor/github.com/hashicorp/go-plugin/error_test.go b/vendor/github.com/hashicorp/go-plugin/error_test.go new file mode 100644 index 00000000..e3aeff52 --- /dev/null +++ b/vendor/github.com/hashicorp/go-plugin/error_test.go @@ -0,0 +1,26 @@ +package plugin + +import ( + "errors" + "testing" +) + +func TestBasicError_ImplementsError(t *testing.T) { + var _ error = new(BasicError) +} + +func TestBasicError_MatchesMessage(t *testing.T) { + err := errors.New("foo") + wrapped := NewBasicError(err) + + if wrapped.Error() != err.Error() { + t.Fatalf("bad: %#v", wrapped.Error()) + } +} + +func TestNewBasicError_nil(t *testing.T) { + r := NewBasicError(nil) + if r != nil { + t.Fatalf("bad: %#v", r) + } +} diff --git a/vendor/github.com/hashicorp/go-plugin/examples/basic/.gitignore b/vendor/github.com/hashicorp/go-plugin/examples/basic/.gitignore new file mode 100644 index 00000000..ad32f9ed --- /dev/null +++ b/vendor/github.com/hashicorp/go-plugin/examples/basic/.gitignore @@ -0,0 +1,3 @@ +# Ignore binaries +plugin/greeter +basic \ No newline at end of file diff --git a/vendor/github.com/hashicorp/go-plugin/examples/basic/README.md b/vendor/github.com/hashicorp/go-plugin/examples/basic/README.md new file mode 100644 index 00000000..d83d1bad --- /dev/null +++ b/vendor/github.com/hashicorp/go-plugin/examples/basic/README.md @@ -0,0 +1,14 @@ +Plugin Example +-------------- + +Compile the plugin itself via: + + go build -o ./plugin/greeter ./plugin/greeter_impl.go + +Compile this driver via: + + go build -o basic . + +You can then launch the plugin sample via: + + ./basic diff --git a/vendor/github.com/hashicorp/go-plugin/examples/basic/commons/greeter_interface.go b/vendor/github.com/hashicorp/go-plugin/examples/basic/commons/greeter_interface.go new file mode 100644 index 00000000..cc8dcf1c --- /dev/null +++ b/vendor/github.com/hashicorp/go-plugin/examples/basic/commons/greeter_interface.go @@ -0,0 +1,62 @@ +package example + +import ( + "net/rpc" + + "github.com/hashicorp/go-plugin" +) + +// Greeter is the interface that we're exposing as a plugin. +type Greeter interface { + Greet() string +} + +// Here is an implementation that talks over RPC +type GreeterRPC struct{ client *rpc.Client } + +func (g *GreeterRPC) Greet() string { + var resp string + err := g.client.Call("Plugin.Greet", new(interface{}), &resp) + if err != nil { + // You usually want your interfaces to return errors. If they don't, + // there isn't much other choice here. + panic(err) + } + + return resp +} + +// Here is the RPC server that GreeterRPC talks to, conforming to +// the requirements of net/rpc +type GreeterRPCServer struct { + // This is the real implementation + Impl Greeter +} + +func (s *GreeterRPCServer) Greet(args interface{}, resp *string) error { + *resp = s.Impl.Greet() + return nil +} + +// This is the implementation of plugin.Plugin so we can serve/consume this +// +// This has two methods: Server must return an RPC server for this plugin +// type. We construct a GreeterRPCServer for this. +// +// Client must return an implementation of our interface that communicates +// over an RPC client. We return GreeterRPC for this. +// +// Ignore MuxBroker. That is used to create more multiplexed streams on our +// plugin connection and is a more advanced use case. +type GreeterPlugin struct { + // Impl Injection + Impl Greeter +} + +func (p *GreeterPlugin) Server(*plugin.MuxBroker) (interface{}, error) { + return &GreeterRPCServer{Impl: p.Impl}, nil +} + +func (GreeterPlugin) Client(b *plugin.MuxBroker, c *rpc.Client) (interface{}, error) { + return &GreeterRPC{client: c}, nil +} diff --git a/vendor/github.com/hashicorp/go-plugin/examples/basic/main.go b/vendor/github.com/hashicorp/go-plugin/examples/basic/main.go new file mode 100644 index 00000000..394a3fcf --- /dev/null +++ b/vendor/github.com/hashicorp/go-plugin/examples/basic/main.go @@ -0,0 +1,62 @@ +package main + +import ( + "fmt" + "log" + "os" + "os/exec" + + hclog "github.com/hashicorp/go-hclog" + "github.com/hashicorp/go-plugin" + "github.com/hashicorp/go-plugin/examples/basic/commons" +) + +func main() { + // Create an hclog.Logger + logger := hclog.New(&hclog.LoggerOptions{ + Name: "plugin", + Output: os.Stdout, + Level: hclog.Debug, + }) + + // We're a host! Start by launching the plugin process. + client := plugin.NewClient(&plugin.ClientConfig{ + HandshakeConfig: handshakeConfig, + Plugins: pluginMap, + Cmd: exec.Command("./plugin/greeter"), + Logger: logger, + }) + defer client.Kill() + + // Connect via RPC + rpcClient, err := client.Client() + if err != nil { + log.Fatal(err) + } + + // Request the plugin + raw, err := rpcClient.Dispense("greeter") + if err != nil { + log.Fatal(err) + } + + // We should have a Greeter now! This feels like a normal interface + // implementation but is in fact over an RPC connection. + greeter := raw.(example.Greeter) + fmt.Println(greeter.Greet()) +} + +// handshakeConfigs are used to just do a basic handshake between +// a plugin and host. If the handshake fails, a user friendly error is shown. +// This prevents users from executing bad plugins or executing a plugin +// directory. It is a UX feature, not a security feature. +var handshakeConfig = plugin.HandshakeConfig{ + ProtocolVersion: 1, + MagicCookieKey: "BASIC_PLUGIN", + MagicCookieValue: "hello", +} + +// pluginMap is the map of plugins we can dispense. +var pluginMap = map[string]plugin.Plugin{ + "greeter": &example.GreeterPlugin{}, +} diff --git a/vendor/github.com/hashicorp/go-plugin/examples/basic/plugin/greeter_impl.go b/vendor/github.com/hashicorp/go-plugin/examples/basic/plugin/greeter_impl.go new file mode 100644 index 00000000..d60c26cf --- /dev/null +++ b/vendor/github.com/hashicorp/go-plugin/examples/basic/plugin/greeter_impl.go @@ -0,0 +1,52 @@ +package main + +import ( + "os" + + "github.com/hashicorp/go-hclog" + "github.com/hashicorp/go-plugin" + "github.com/hashicorp/go-plugin/examples/basic/commons" +) + +// Here is a real implementation of Greeter +type GreeterHello struct { + logger hclog.Logger +} + +func (g *GreeterHello) Greet() string { + g.logger.Debug("message from GreeterHello.Greet") + return "Hello!" +} + +// handshakeConfigs are used to just do a basic handshake between +// a plugin and host. If the handshake fails, a user friendly error is shown. +// This prevents users from executing bad plugins or executing a plugin +// directory. It is a UX feature, not a security feature. +var handshakeConfig = plugin.HandshakeConfig{ + ProtocolVersion: 1, + MagicCookieKey: "BASIC_PLUGIN", + MagicCookieValue: "hello", +} + +func main() { + logger := hclog.New(&hclog.LoggerOptions{ + Level: hclog.Trace, + Output: os.Stderr, + JSONFormat: true, + }) + + greeter := &GreeterHello{ + logger: logger, + } + // pluginMap is the map of plugins we can dispense. + var pluginMap = map[string]plugin.Plugin{ + "greeter": &example.GreeterPlugin{Impl: greeter}, + } + + logger.Debug("message from plugin", "foo", "bar") + + plugin.Serve(&plugin.ServeConfig{ + HandshakeConfig: handshakeConfig, + Plugins: pluginMap, + }) +} diff --git a/vendor/github.com/hashicorp/go-plugin/examples/bidirectional/README.md b/vendor/github.com/hashicorp/go-plugin/examples/bidirectional/README.md new file mode 100644 index 00000000..f128307d --- /dev/null +++ b/vendor/github.com/hashicorp/go-plugin/examples/bidirectional/README.md @@ -0,0 +1,48 @@ +# Counter Example + +This example builds a simple key/counter store CLI where the mechanism +for storing and retrieving keys is pluggable. However, in this example we don't +trust the plugin to do the summation work. We use bi-directional plugins to +call back into the main proccess to do the sum of two numbers. To build this example: + +```sh +# This builds the main CLI +$ go build -o counter + +# This builds the plugin written in Go +$ go build -o counter-go-grpc ./plugin-go-grpc + +# This tells the Counter binary to use the "counter-go-grpc" binary +$ export COUNTER_PLUGIN="./counter-go-grpc" + +# Read and write +$ ./counter put hello 1 +$ ./counter put hello 1 + +$ ./counter get hello +2 +``` + +### Plugin: plugin-go-grpc + +This plugin uses gRPC to serve a plugin that is written in Go: + +``` +# This builds the plugin written in Go +$ go build -o counter-go-grpc ./plugin-go-grpc + +# This tells the KV binary to use the "kv-go-grpc" binary +$ export COUNTER_PLUGIN="./counter-go-grpc" +``` + +## Updating the Protocol + +If you update the protocol buffers file, you can regenerate the file +using the following command from this directory. You do not need to run +this if you're just trying the example. + +For Go: + +```sh +$ protoc -I proto/ proto/kv.proto --go_out=plugins=grpc:proto/ +``` diff --git a/vendor/github.com/hashicorp/go-plugin/examples/bidirectional/main.go b/vendor/github.com/hashicorp/go-plugin/examples/bidirectional/main.go new file mode 100644 index 00000000..92a1c8fc --- /dev/null +++ b/vendor/github.com/hashicorp/go-plugin/examples/bidirectional/main.go @@ -0,0 +1,81 @@ +package main + +import ( + "fmt" + "io/ioutil" + "log" + "os" + "os/exec" + "strconv" + + "github.com/hashicorp/go-plugin" + "github.com/hashicorp/go-plugin/examples/bidirectional/shared" +) + +type addHelper struct{} + +func (*addHelper) Sum(a, b int64) (int64, error) { + return a + b, nil +} + +func main() { + // We don't want to see the plugin logs. + log.SetOutput(ioutil.Discard) + + // We're a host. Start by launching the plugin process. + client := plugin.NewClient(&plugin.ClientConfig{ + HandshakeConfig: shared.Handshake, + Plugins: shared.PluginMap, + Cmd: exec.Command("sh", "-c", os.Getenv("COUNTER_PLUGIN")), + AllowedProtocols: []plugin.Protocol{ + plugin.ProtocolNetRPC, plugin.ProtocolGRPC}, + }) + defer client.Kill() + + // Connect via RPC + rpcClient, err := client.Client() + if err != nil { + fmt.Println("Error:", err.Error()) + os.Exit(1) + } + + // Request the plugin + raw, err := rpcClient.Dispense("counter") + if err != nil { + fmt.Println("Error:", err.Error()) + os.Exit(1) + } + + // We should have a Counter store now! This feels like a normal interface + // implementation but is in fact over an RPC connection. + counter := raw.(shared.Counter) + + os.Args = os.Args[1:] + switch os.Args[0] { + case "get": + result, err := counter.Get(os.Args[1]) + if err != nil { + fmt.Println("Error:", err.Error()) + os.Exit(1) + } + + fmt.Println(result) + + case "put": + i, err := strconv.Atoi(os.Args[2]) + if err != nil { + fmt.Println("Error:", err.Error()) + os.Exit(1) + } + + err = counter.Put(os.Args[1], int64(i), &addHelper{}) + if err != nil { + fmt.Println("Error:", err.Error()) + os.Exit(1) + } + + default: + fmt.Println("Please only use 'get' or 'put'") + os.Exit(1) + } +} diff --git a/vendor/github.com/hashicorp/go-plugin/examples/bidirectional/plugin-go-grpc/main.go b/vendor/github.com/hashicorp/go-plugin/examples/bidirectional/plugin-go-grpc/main.go new file mode 100644 index 00000000..c6beb93a --- /dev/null +++ b/vendor/github.com/hashicorp/go-plugin/examples/bidirectional/plugin-go-grpc/main.go @@ -0,0 +1,61 @@ +package main + +import ( + "encoding/json" + "io/ioutil" + + "github.com/hashicorp/go-plugin" + "github.com/hashicorp/go-plugin/examples/bidirectional/shared" +) + +// Here is a real implementation of KV that writes to a local file with +// the key name and the contents are the value of the key. +type Counter struct { +} + +type data struct { + Value int64 +} + +func (k *Counter) Put(key string, value int64, a shared.AddHelper) error { + v, _ := k.Get(key) + + r, err := a.Sum(v, value) + if err != nil { + return err + } + + buf, err := json.Marshal(&data{r}) + if err != nil { + return err + } + + return ioutil.WriteFile("kv_"+key, buf, 0644) +} + +func (k *Counter) Get(key string) (int64, error) { + dataRaw, err := ioutil.ReadFile("kv_" + key) + if err != nil { + return 0, err + } + + data := &data{} + err = json.Unmarshal(dataRaw, data) + if err != nil { + return 0, err + } + + return data.Value, nil +} + +func main() { + plugin.Serve(&plugin.ServeConfig{ + HandshakeConfig: shared.Handshake, + Plugins: map[string]plugin.Plugin{ + "counter": &shared.CounterPlugin{Impl: &Counter{}}, + }, + + // A non-nil value here enables gRPC serving for this plugin... + GRPCServer: plugin.DefaultGRPCServer, + }) +} diff --git a/vendor/github.com/hashicorp/go-plugin/examples/bidirectional/proto/kv.pb.go b/vendor/github.com/hashicorp/go-plugin/examples/bidirectional/proto/kv.pb.go new file mode 100644 index 00000000..e71ec618 --- /dev/null +++ b/vendor/github.com/hashicorp/go-plugin/examples/bidirectional/proto/kv.pb.go @@ -0,0 +1,350 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// source: kv.proto + +/* +Package proto is a generated protocol buffer package. + +It is generated from these files: + kv.proto + +It has these top-level messages: + GetRequest + GetResponse + PutRequest + Empty + SumRequest + SumResponse +*/ +package proto + +import proto1 "github.com/golang/protobuf/proto" +import fmt "fmt" +import math "math" + +import ( + context "golang.org/x/net/context" + grpc "google.golang.org/grpc" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto1.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto1.ProtoPackageIsVersion2 // please upgrade the proto package + +type GetRequest struct { + Key string `protobuf:"bytes,1,opt,name=key" json:"key,omitempty"` +} + +func (m *GetRequest) Reset() { *m = GetRequest{} } +func (m *GetRequest) String() string { return proto1.CompactTextString(m) } +func (*GetRequest) ProtoMessage() {} +func (*GetRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} } + +func (m *GetRequest) GetKey() string { + if m != nil { + return m.Key + } + return "" +} + +type GetResponse struct { + Value int64 `protobuf:"varint,1,opt,name=value" json:"value,omitempty"` +} + +func (m *GetResponse) Reset() { *m = GetResponse{} } +func (m *GetResponse) String() string { return proto1.CompactTextString(m) } +func (*GetResponse) ProtoMessage() {} +func (*GetResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{1} } + +func (m *GetResponse) GetValue() int64 { + if m != nil { + return m.Value + } + return 0 +} + +type PutRequest struct { + AddServer uint32 `protobuf:"varint,1,opt,name=add_server,json=addServer" json:"add_server,omitempty"` + Key string `protobuf:"bytes,2,opt,name=key" json:"key,omitempty"` + Value int64 `protobuf:"varint,3,opt,name=value" json:"value,omitempty"` +} + +func (m *PutRequest) Reset() { *m = PutRequest{} } +func (m *PutRequest) String() string { return proto1.CompactTextString(m) } +func (*PutRequest) ProtoMessage() {} +func (*PutRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{2} } + +func (m *PutRequest) GetAddServer() uint32 { + if m != nil { + return m.AddServer + } + return 0 +} + +func (m *PutRequest) GetKey() string { + if m != nil { + return m.Key + } + return "" +} + +func (m *PutRequest) GetValue() int64 { + if m != nil { + return m.Value + } + return 0 +} + +type Empty struct { +} + +func (m *Empty) Reset() { *m = Empty{} } +func (m *Empty) String() string { return proto1.CompactTextString(m) } +func (*Empty) ProtoMessage() {} +func (*Empty) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{3} } + +type SumRequest struct { + A int64 `protobuf:"varint,1,opt,name=a" json:"a,omitempty"` + B int64 `protobuf:"varint,2,opt,name=b" json:"b,omitempty"` +} + +func (m *SumRequest) Reset() { *m = SumRequest{} } +func (m *SumRequest) String() string { return proto1.CompactTextString(m) } +func (*SumRequest) ProtoMessage() {} +func (*SumRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{4} } + +func (m *SumRequest) GetA() int64 { + if m != nil { + return m.A + } + return 0 +} + +func (m *SumRequest) GetB() int64 { + if m != nil { + return m.B + } + return 0 +} + +type SumResponse struct { + R int64 `protobuf:"varint,1,opt,name=r" json:"r,omitempty"` +} + +func (m *SumResponse) Reset() { *m = SumResponse{} } +func (m *SumResponse) String() string { return proto1.CompactTextString(m) } +func (*SumResponse) ProtoMessage() {} +func (*SumResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{5} } + +func (m *SumResponse) GetR() int64 { + if m != nil { + return m.R + } + return 0 +} + +func init() { + proto1.RegisterType((*GetRequest)(nil), "proto.GetRequest") + proto1.RegisterType((*GetResponse)(nil), "proto.GetResponse") + proto1.RegisterType((*PutRequest)(nil), "proto.PutRequest") + proto1.RegisterType((*Empty)(nil), "proto.Empty") + proto1.RegisterType((*SumRequest)(nil), "proto.SumRequest") + proto1.RegisterType((*SumResponse)(nil), "proto.SumResponse") +} + +// Reference imports to suppress errors if they are not otherwise used. +var _ context.Context +var _ grpc.ClientConn + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +const _ = grpc.SupportPackageIsVersion4 + +// Client API for Counter service + +type CounterClient interface { + Get(ctx context.Context, in *GetRequest, opts ...grpc.CallOption) (*GetResponse, error) + Put(ctx context.Context, in *PutRequest, opts ...grpc.CallOption) (*Empty, error) +} + +type counterClient struct { + cc *grpc.ClientConn +} + +func NewCounterClient(cc *grpc.ClientConn) CounterClient { + return &counterClient{cc} +} + +func (c *counterClient) Get(ctx context.Context, in *GetRequest, opts ...grpc.CallOption) (*GetResponse, error) { + out := new(GetResponse) + err := grpc.Invoke(ctx, "/proto.Counter/Get", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *counterClient) Put(ctx context.Context, in *PutRequest, opts ...grpc.CallOption) (*Empty, error) { + out := new(Empty) + err := grpc.Invoke(ctx, "/proto.Counter/Put", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// Server API for Counter service + +type CounterServer interface { + Get(context.Context, *GetRequest) (*GetResponse, error) + Put(context.Context, *PutRequest) (*Empty, error) +} + +func RegisterCounterServer(s *grpc.Server, srv CounterServer) { + s.RegisterService(&_Counter_serviceDesc, srv) +} + +func _Counter_Get_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(CounterServer).Get(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/proto.Counter/Get", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(CounterServer).Get(ctx, req.(*GetRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Counter_Put_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(PutRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(CounterServer).Put(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/proto.Counter/Put", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(CounterServer).Put(ctx, req.(*PutRequest)) + } + return interceptor(ctx, in, info, handler) +} + +var _Counter_serviceDesc = grpc.ServiceDesc{ + ServiceName: "proto.Counter", + HandlerType: (*CounterServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "Get", + Handler: _Counter_Get_Handler, + }, + { + MethodName: "Put", + Handler: _Counter_Put_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "kv.proto", +} + +// Client API for AddHelper service + +type AddHelperClient interface { + Sum(ctx context.Context, in *SumRequest, opts ...grpc.CallOption) (*SumResponse, error) +} + +type addHelperClient struct { + cc *grpc.ClientConn +} + +func NewAddHelperClient(cc *grpc.ClientConn) AddHelperClient { + return &addHelperClient{cc} +} + +func (c *addHelperClient) Sum(ctx context.Context, in *SumRequest, opts ...grpc.CallOption) (*SumResponse, error) { + out := new(SumResponse) + err := grpc.Invoke(ctx, "/proto.AddHelper/Sum", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// Server API for AddHelper service + +type AddHelperServer interface { + Sum(context.Context, *SumRequest) (*SumResponse, error) +} + +func RegisterAddHelperServer(s *grpc.Server, srv AddHelperServer) { + s.RegisterService(&_AddHelper_serviceDesc, srv) +} + +func _AddHelper_Sum_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(SumRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(AddHelperServer).Sum(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/proto.AddHelper/Sum", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(AddHelperServer).Sum(ctx, req.(*SumRequest)) + } + return interceptor(ctx, in, info, handler) +} + +var _AddHelper_serviceDesc = grpc.ServiceDesc{ + ServiceName: "proto.AddHelper", + HandlerType: (*AddHelperServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "Sum", + Handler: _AddHelper_Sum_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "kv.proto", +} + +func init() { proto1.RegisterFile("kv.proto", fileDescriptor0) } + +var fileDescriptor0 = []byte{ + // 253 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x4c, 0x90, 0x4f, 0x4b, 0x03, 0x31, + 0x10, 0xc5, 0x89, 0x61, 0xad, 0xfb, 0xba, 0x82, 0x06, 0x0f, 0x52, 0x51, 0x24, 0x82, 0xf4, 0x20, + 0x3d, 0xd4, 0x93, 0x47, 0x11, 0xa9, 0xc7, 0x92, 0xfd, 0x00, 0x25, 0x4b, 0xe6, 0xd4, 0x6e, 0x77, + 0xcd, 0x26, 0x0b, 0xfd, 0xf6, 0xa5, 0xd9, 0x3f, 0xd9, 0x53, 0x32, 0x2f, 0x2f, 0xbf, 0x37, 0x33, + 0xb8, 0xd9, 0xb7, 0xab, 0xda, 0x56, 0xae, 0x12, 0x49, 0x38, 0xe4, 0x0b, 0xb0, 0x21, 0xa7, 0xe8, + 0xdf, 0x53, 0xe3, 0xc4, 0x1d, 0xf8, 0x9e, 0x4e, 0x8f, 0xec, 0x95, 0x2d, 0x53, 0x75, 0xb9, 0xca, + 0x37, 0xcc, 0xc3, 0x7b, 0x53, 0x57, 0xc7, 0x86, 0xc4, 0x03, 0x92, 0x56, 0x1f, 0x3c, 0x05, 0x0b, + 0x57, 0x5d, 0x21, 0x73, 0x60, 0xeb, 0x47, 0xc8, 0x33, 0xa0, 0x8d, 0xd9, 0x35, 0x64, 0x5b, 0xb2, + 0xc1, 0x78, 0xab, 0x52, 0x6d, 0x4c, 0x1e, 0x84, 0x21, 0xe3, 0x6a, 0xcc, 0x88, 0x50, 0x3e, 0x85, + 0xce, 0x90, 0xfc, 0x96, 0xb5, 0x3b, 0xc9, 0x25, 0x90, 0xfb, 0x72, 0xa0, 0x67, 0x60, 0xba, 0x4f, + 0x67, 0xfa, 0x52, 0x15, 0x01, 0xc5, 0x15, 0x2b, 0xe4, 0x13, 0xe6, 0xc1, 0xd9, 0x37, 0x9b, 0x81, + 0xd9, 0xc1, 0x6a, 0xd7, 0x3b, 0xcc, 0x7e, 0x2a, 0x7f, 0x74, 0x64, 0xc5, 0x07, 0xf8, 0x86, 0x9c, + 0xb8, 0xef, 0x56, 0xb1, 0x8a, 0x0b, 0x58, 0x88, 0xa9, 0xd4, 0x63, 0xde, 0xc1, 0xb7, 0x3e, 0xba, + 0xe3, 0xa4, 0x8b, 0xac, 0x97, 0x42, 0x9f, 0xeb, 0x2f, 0xa4, 0xdf, 0xc6, 0xfc, 0xd1, 0xa1, 0xee, + 0x22, 0x72, 0x5f, 0x8e, 0x9f, 0xe2, 0x00, 0x63, 0xc4, 0xa4, 0xd3, 0xe2, 0x3a, 0x48, 0x9f, 0xe7, + 0x00, 0x00, 0x00, 0xff, 0xff, 0x40, 0xa3, 0x85, 0x07, 0x9f, 0x01, 0x00, 0x00, +} diff --git a/vendor/github.com/hashicorp/go-plugin/examples/bidirectional/proto/kv.proto b/vendor/github.com/hashicorp/go-plugin/examples/bidirectional/proto/kv.proto new file mode 100644 index 00000000..36fa324a --- /dev/null +++ b/vendor/github.com/hashicorp/go-plugin/examples/bidirectional/proto/kv.proto @@ -0,0 +1,36 @@ +syntax = "proto3"; +package proto; + +message GetRequest { + string key = 1; +} + +message GetResponse { + int64 value = 1; +} + +message PutRequest { + uint32 add_server = 1; + string key = 2; + int64 value = 3; +} + +message Empty {} + +message SumRequest { + int64 a = 1; + int64 b = 2; +} + +message SumResponse { + int64 r = 1; +} + +service Counter { + rpc Get(GetRequest) returns (GetResponse); + rpc Put(PutRequest) returns (Empty); +} + +service AddHelper { + rpc Sum(SumRequest) returns (SumResponse); +} diff --git a/vendor/github.com/hashicorp/go-plugin/examples/bidirectional/shared/grpc.go b/vendor/github.com/hashicorp/go-plugin/examples/bidirectional/shared/grpc.go new file mode 100644 index 00000000..c2e54baf --- /dev/null +++ b/vendor/github.com/hashicorp/go-plugin/examples/bidirectional/shared/grpc.go @@ -0,0 +1,103 @@ +package shared + +import ( + hclog "github.com/hashicorp/go-hclog" + plugin "github.com/hashicorp/go-plugin" + "github.com/hashicorp/go-plugin/examples/bidirectional/proto" + "golang.org/x/net/context" + "google.golang.org/grpc" +) + +// GRPCClient is an implementation of KV that talks over RPC. +type GRPCClient struct { + broker *plugin.GRPCBroker + client proto.CounterClient +} + +func (m *GRPCClient) Put(key string, value int64, a AddHelper) error { + addHelperServer := &GRPCAddHelperServer{Impl: a} + + var s *grpc.Server + serverFunc := func(opts []grpc.ServerOption) *grpc.Server { + s = grpc.NewServer(opts...) + proto.RegisterAddHelperServer(s, addHelperServer) + + return s + } + + brokerID := m.broker.NextId() + go m.broker.AcceptAndServe(brokerID, serverFunc) + + _, err := m.client.Put(context.Background(), &proto.PutRequest{ + AddServer: brokerID, + Key: key, + Value: value, + }) + + s.Stop() + return err +} + +func (m *GRPCClient) Get(key string) (int64, error) { + resp, err := m.client.Get(context.Background(), &proto.GetRequest{ + Key: key, + }) + if err != nil { + return 0, err + } + + return resp.Value, nil +} + +// Here is the gRPC server that GRPCClient talks to. +type GRPCServer struct { + // This is the real implementation + Impl Counter + + broker *plugin.GRPCBroker +} + +func (m *GRPCServer) Put(ctx context.Context, req *proto.PutRequest) (*proto.Empty, error) { + conn, err := m.broker.Dial(req.AddServer) + if err != nil { + return nil, err + } + defer conn.Close() + + a := &GRPCAddHelperClient{proto.NewAddHelperClient(conn)} + return &proto.Empty{}, m.Impl.Put(req.Key, req.Value, a) +} + +func (m *GRPCServer) Get(ctx context.Context, req *proto.GetRequest) (*proto.GetResponse, error) { + v, err := m.Impl.Get(req.Key) + return &proto.GetResponse{Value: v}, err +} + +// GRPCClient is an implementation of KV that talks over RPC. +type GRPCAddHelperClient struct{ client proto.AddHelperClient } + +func (m *GRPCAddHelperClient) Sum(a, b int64) (int64, error) { + resp, err := m.client.Sum(context.Background(), &proto.SumRequest{ + A: a, + B: b, + }) + if err != nil { + hclog.Default().Info("add.Sum", "client", "start", "err", err) + return 0, err + } + return resp.R, err +} + +// Here is the gRPC server that GRPCClient talks to. +type GRPCAddHelperServer struct { + // This is the real implementation + Impl AddHelper +} + +func (m *GRPCAddHelperServer) Sum(ctx context.Context, req *proto.SumRequest) (resp *proto.SumResponse, err error) { + r, err := m.Impl.Sum(req.A, req.B) + if err != nil { + return nil, err + } + return &proto.SumResponse{R: r}, err +} diff --git a/vendor/github.com/hashicorp/go-plugin/examples/bidirectional/shared/interface.go b/vendor/github.com/hashicorp/go-plugin/examples/bidirectional/shared/interface.go new file mode 100644 index 00000000..f4ecb57f --- /dev/null +++ b/vendor/github.com/hashicorp/go-plugin/examples/bidirectional/shared/interface.go @@ -0,0 +1,59 @@ +// Package shared contains shared data between the host and plugins. +package shared + +import ( + "golang.org/x/net/context" + "google.golang.org/grpc" + + "github.com/hashicorp/go-plugin" + "github.com/hashicorp/go-plugin/examples/bidirectional/proto" +) + +// Handshake is a common handshake that is shared by plugin and host. +var Handshake = plugin.HandshakeConfig{ + ProtocolVersion: 1, + MagicCookieKey: "BASIC_PLUGIN", + MagicCookieValue: "hello", +} + +// PluginMap is the map of plugins we can dispense. +var PluginMap = map[string]plugin.Plugin{ + "counter": &CounterPlugin{}, +} + +type AddHelper interface { + Sum(int64, int64) (int64, error) +} + +// KV is the interface that we're exposing as a plugin. +type Counter interface { + Put(key string, value int64, a AddHelper) error + Get(key string) (int64, error) +} + +// This is the implementation of plugin.Plugin so we can serve/consume this. +// We also implement GRPCPlugin so that this plugin can be served over +// gRPC. +type CounterPlugin struct { + plugin.NetRPCUnsupportedPlugin + // Concrete implementation, written in Go. This is only used for plugins + // that are written in Go. + Impl Counter +} + +func (p *CounterPlugin) GRPCServer(broker *plugin.GRPCBroker, s *grpc.Server) error { + proto.RegisterCounterServer(s, &GRPCServer{ + Impl: p.Impl, + broker: broker, + }) + return nil +} + +func (p *CounterPlugin) GRPCClient(ctx context.Context, broker *plugin.GRPCBroker, c *grpc.ClientConn) (interface{}, error) { + return &GRPCClient{ + client: proto.NewCounterClient(c), + broker: broker, + }, nil +} + +var _ plugin.GRPCPlugin = &CounterPlugin{} diff --git a/vendor/github.com/hashicorp/go-plugin/examples/grpc/.gitignore b/vendor/github.com/hashicorp/go-plugin/examples/grpc/.gitignore new file mode 100644 index 00000000..953e359b --- /dev/null +++ b/vendor/github.com/hashicorp/go-plugin/examples/grpc/.gitignore @@ -0,0 +1,5 @@ +*.pyc +kv +kv-* +kv_* +!kv_*.py diff --git a/vendor/github.com/hashicorp/go-plugin/examples/grpc/README.md b/vendor/github.com/hashicorp/go-plugin/examples/grpc/README.md new file mode 100644 index 00000000..7b3b647a --- /dev/null +++ b/vendor/github.com/hashicorp/go-plugin/examples/grpc/README.md @@ -0,0 +1,71 @@ +# KV Example + +This example builds a simple key/value store CLI where the mechanism +for storing and retrieving keys is pluggable. To build this example: + +```sh +# This builds the main CLI +$ go build -o kv + +# This builds the plugin written in Go +$ go build -o kv-go-grpc ./plugin-go-grpc + +# This tells the KV binary to use the "kv-go-grpc" binary +$ export KV_PLUGIN="./kv-go-grpc" + +# Read and write +$ ./kv put hello world + +$ ./kv get hello +world +``` + +### Plugin: plugin-go-grpc + +This plugin uses gRPC to serve a plugin that is written in Go: + +``` +# This builds the plugin written in Go +$ go build -o kv-go-grpc ./plugin-go-grpc + +# This tells the KV binary to use the "kv-go-grpc" binary +$ export KV_PLUGIN="./kv-go-grpc" +``` + +### Plugin: plugin-go-netrpc + +This plugin uses the builtin Go net/rpc mechanism to serve the plugin: + +``` +# This builds the plugin written in Go +$ go build -o kv-go-netrpc ./plugin-go-netrpc + +# This tells the KV binary to use the "kv-go-netrpc" binary +$ export KV_PLUGIN="./kv-go-netrpc" +``` + +### Plugin: plugin-python + +This plugin is written in Python: + +``` +$ export KV_PLUGIN="python plugin-python/plugin.py" +``` + +## Updating the Protocol + +If you update the protocol buffers file, you can regenerate the file +using the following command from this directory. You do not need to run +this if you're just trying the example. + +For Go: + +```sh +$ protoc -I proto/ proto/kv.proto --go_out=plugins=grpc:proto/ +``` + +For Python: + +```sh +$ python -m grpc_tools.protoc -I ./proto/ --python_out=./plugin-python/ --grpc_python_out=./plugin-python/ ./proto/kv.proto +``` diff --git a/vendor/github.com/hashicorp/go-plugin/examples/grpc/main.go b/vendor/github.com/hashicorp/go-plugin/examples/grpc/main.go new file mode 100644 index 00000000..3ce765d5 --- /dev/null +++ b/vendor/github.com/hashicorp/go-plugin/examples/grpc/main.go @@ -0,0 +1,67 @@ +package main + +import ( + "fmt" + "io/ioutil" + "log" + "os" + "os/exec" + + "github.com/hashicorp/go-plugin" + "github.com/hashicorp/go-plugin/examples/grpc/shared" +) + +func main() { + // We don't want to see the plugin logs. + log.SetOutput(ioutil.Discard) + + // We're a host. Start by launching the plugin process. + client := plugin.NewClient(&plugin.ClientConfig{ + HandshakeConfig: shared.Handshake, + Plugins: shared.PluginMap, + Cmd: exec.Command("sh", "-c", os.Getenv("KV_PLUGIN")), + AllowedProtocols: []plugin.Protocol{ + plugin.ProtocolNetRPC, plugin.ProtocolGRPC}, + }) + defer client.Kill() + + // Connect via RPC + rpcClient, err := client.Client() + if err != nil { + fmt.Println("Error:", err.Error()) + os.Exit(1) + } + + // Request the plugin + raw, err := rpcClient.Dispense("kv") + if err != nil { + fmt.Println("Error:", err.Error()) + os.Exit(1) + } + + // We should have a KV store now! This feels like a normal interface + // implementation but is in fact over an RPC connection. + kv := raw.(shared.KV) + os.Args = os.Args[1:] + switch os.Args[0] { + case "get": + result, err := kv.Get(os.Args[1]) + if err != nil { + fmt.Println("Error:", err.Error()) + os.Exit(1) + } + + fmt.Println(string(result)) + + case "put": + err := kv.Put(os.Args[1], []byte(os.Args[2])) + if err != nil { + fmt.Println("Error:", err.Error()) + os.Exit(1) + } + + default: + fmt.Println("Please only use 'get' or 'put'") + os.Exit(1) + } +} diff --git a/vendor/github.com/hashicorp/go-plugin/examples/grpc/plugin-go-grpc/main.go b/vendor/github.com/hashicorp/go-plugin/examples/grpc/plugin-go-grpc/main.go new file mode 100644 index 00000000..3aa45b43 --- /dev/null +++ b/vendor/github.com/hashicorp/go-plugin/examples/grpc/plugin-go-grpc/main.go @@ -0,0 +1,34 @@ +package main + +import ( + "fmt" + "io/ioutil" + + "github.com/hashicorp/go-plugin" + "github.com/hashicorp/go-plugin/examples/grpc/shared" +) + +// Here is a real implementation of KV that writes to a local file with +// the key name and the contents are the value of the key. +type KV struct{} + +func (KV) Put(key string, value []byte) error { + value = []byte(fmt.Sprintf("%s\n\nWritten from plugin-go-grpc", string(value))) + return ioutil.WriteFile("kv_"+key, value, 0644) +} + +func (KV) Get(key string) ([]byte, error) { + return ioutil.ReadFile("kv_" + key) +} + +func main() { + plugin.Serve(&plugin.ServeConfig{ + HandshakeConfig: shared.Handshake, + Plugins: map[string]plugin.Plugin{ + "kv": &shared.KVPlugin{Impl: &KV{}}, + }, + + // A non-nil value here enables gRPC serving for this plugin... + GRPCServer: plugin.DefaultGRPCServer, + }) +} diff --git a/vendor/github.com/hashicorp/go-plugin/examples/grpc/plugin-go-netrpc/main.go b/vendor/github.com/hashicorp/go-plugin/examples/grpc/plugin-go-netrpc/main.go new file mode 100644 index 00000000..b3bb8435 --- /dev/null +++ b/vendor/github.com/hashicorp/go-plugin/examples/grpc/plugin-go-netrpc/main.go @@ -0,0 +1,31 @@ +package main + +import ( + "fmt" + "io/ioutil" + + "github.com/hashicorp/go-plugin" + "github.com/hashicorp/go-plugin/examples/grpc/shared" +) + +// Here is a real implementation of KV that writes to a local file with +// the key name and the contents are the value of the key. +type KV struct{} + +func (KV) Put(key string, value []byte) error { + value = []byte(fmt.Sprintf("%s\n\nWritten from plugin-go-netrpc", string(value))) + return ioutil.WriteFile("kv_"+key, value, 0644) +} + +func (KV) Get(key string) ([]byte, error) { + return ioutil.ReadFile("kv_" + key) +} + +func main() { + plugin.Serve(&plugin.ServeConfig{ + HandshakeConfig: shared.Handshake, + Plugins: map[string]plugin.Plugin{ + "kv": &shared.KVPlugin{Impl: &KV{}}, + }, + }) +} diff --git a/vendor/github.com/hashicorp/go-plugin/examples/grpc/plugin-python/kv_pb2.py b/vendor/github.com/hashicorp/go-plugin/examples/grpc/plugin-python/kv_pb2.py new file mode 100644 index 00000000..bee04c0b --- /dev/null +++ b/vendor/github.com/hashicorp/go-plugin/examples/grpc/plugin-python/kv_pb2.py @@ -0,0 +1,317 @@ +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: kv.proto + +import sys +_b=sys.version_info[0]<3 and (lambda x:x) or (lambda x:x.encode('latin1')) +from google.protobuf import descriptor as _descriptor +from google.protobuf import message as _message +from google.protobuf import reflection as _reflection +from google.protobuf import symbol_database as _symbol_database +from google.protobuf import descriptor_pb2 +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + + + +DESCRIPTOR = _descriptor.FileDescriptor( + name='kv.proto', + package='proto', + syntax='proto3', + serialized_pb=_b('\n\x08kv.proto\x12\x05proto\"\x19\n\nGetRequest\x12\x0b\n\x03key\x18\x01 \x01(\t\"\x1c\n\x0bGetResponse\x12\r\n\x05value\x18\x01 \x01(\x0c\"(\n\nPutRequest\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\x0c\"\x07\n\x05\x45mpty2Z\n\x02KV\x12,\n\x03Get\x12\x11.proto.GetRequest\x1a\x12.proto.GetResponse\x12&\n\x03Put\x12\x11.proto.PutRequest\x1a\x0c.proto.Emptyb\x06proto3') +) +_sym_db.RegisterFileDescriptor(DESCRIPTOR) + + + + +_GETREQUEST = _descriptor.Descriptor( + name='GetRequest', + full_name='proto.GetRequest', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='key', full_name='proto.GetRequest.key', index=0, + number=1, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=19, + serialized_end=44, +) + + +_GETRESPONSE = _descriptor.Descriptor( + name='GetResponse', + full_name='proto.GetResponse', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='value', full_name='proto.GetResponse.value', index=0, + number=1, type=12, cpp_type=9, label=1, + has_default_value=False, default_value=_b(""), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=46, + serialized_end=74, +) + + +_PUTREQUEST = _descriptor.Descriptor( + name='PutRequest', + full_name='proto.PutRequest', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='key', full_name='proto.PutRequest.key', index=0, + number=1, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='value', full_name='proto.PutRequest.value', index=1, + number=2, type=12, cpp_type=9, label=1, + has_default_value=False, default_value=_b(""), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=76, + serialized_end=116, +) + + +_EMPTY = _descriptor.Descriptor( + name='Empty', + full_name='proto.Empty', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=118, + serialized_end=125, +) + +DESCRIPTOR.message_types_by_name['GetRequest'] = _GETREQUEST +DESCRIPTOR.message_types_by_name['GetResponse'] = _GETRESPONSE +DESCRIPTOR.message_types_by_name['PutRequest'] = _PUTREQUEST +DESCRIPTOR.message_types_by_name['Empty'] = _EMPTY + +GetRequest = _reflection.GeneratedProtocolMessageType('GetRequest', (_message.Message,), dict( + DESCRIPTOR = _GETREQUEST, + __module__ = 'kv_pb2' + # @@protoc_insertion_point(class_scope:proto.GetRequest) + )) +_sym_db.RegisterMessage(GetRequest) + +GetResponse = _reflection.GeneratedProtocolMessageType('GetResponse', (_message.Message,), dict( + DESCRIPTOR = _GETRESPONSE, + __module__ = 'kv_pb2' + # @@protoc_insertion_point(class_scope:proto.GetResponse) + )) +_sym_db.RegisterMessage(GetResponse) + +PutRequest = _reflection.GeneratedProtocolMessageType('PutRequest', (_message.Message,), dict( + DESCRIPTOR = _PUTREQUEST, + __module__ = 'kv_pb2' + # @@protoc_insertion_point(class_scope:proto.PutRequest) + )) +_sym_db.RegisterMessage(PutRequest) + +Empty = _reflection.GeneratedProtocolMessageType('Empty', (_message.Message,), dict( + DESCRIPTOR = _EMPTY, + __module__ = 'kv_pb2' + # @@protoc_insertion_point(class_scope:proto.Empty) + )) +_sym_db.RegisterMessage(Empty) + + +try: + # THESE ELEMENTS WILL BE DEPRECATED. + # Please use the generated *_pb2_grpc.py files instead. + import grpc + from grpc.beta import implementations as beta_implementations + from grpc.beta import interfaces as beta_interfaces + from grpc.framework.common import cardinality + from grpc.framework.interfaces.face import utilities as face_utilities + + + class KVStub(object): + + def __init__(self, channel): + """Constructor. + + Args: + channel: A grpc.Channel. + """ + self.Get = channel.unary_unary( + '/proto.KV/Get', + request_serializer=GetRequest.SerializeToString, + response_deserializer=GetResponse.FromString, + ) + self.Put = channel.unary_unary( + '/proto.KV/Put', + request_serializer=PutRequest.SerializeToString, + response_deserializer=Empty.FromString, + ) + + + class KVServicer(object): + + def Get(self, request, context): + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def Put(self, request, context): + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + + def add_KVServicer_to_server(servicer, server): + rpc_method_handlers = { + 'Get': grpc.unary_unary_rpc_method_handler( + servicer.Get, + request_deserializer=GetRequest.FromString, + response_serializer=GetResponse.SerializeToString, + ), + 'Put': grpc.unary_unary_rpc_method_handler( + servicer.Put, + request_deserializer=PutRequest.FromString, + response_serializer=Empty.SerializeToString, + ), + } + generic_handler = grpc.method_handlers_generic_handler( + 'proto.KV', rpc_method_handlers) + server.add_generic_rpc_handlers((generic_handler,)) + + + class BetaKVServicer(object): + """The Beta API is deprecated for 0.15.0 and later. + + It is recommended to use the GA API (classes and functions in this + file not marked beta) for all further purposes. This class was generated + only to ease transition from grpcio<0.15.0 to grpcio>=0.15.0.""" + def Get(self, request, context): + context.code(beta_interfaces.StatusCode.UNIMPLEMENTED) + def Put(self, request, context): + context.code(beta_interfaces.StatusCode.UNIMPLEMENTED) + + + class BetaKVStub(object): + """The Beta API is deprecated for 0.15.0 and later. + + It is recommended to use the GA API (classes and functions in this + file not marked beta) for all further purposes. This class was generated + only to ease transition from grpcio<0.15.0 to grpcio>=0.15.0.""" + def Get(self, request, timeout, metadata=None, with_call=False, protocol_options=None): + raise NotImplementedError() + Get.future = None + def Put(self, request, timeout, metadata=None, with_call=False, protocol_options=None): + raise NotImplementedError() + Put.future = None + + + def beta_create_KV_server(servicer, pool=None, pool_size=None, default_timeout=None, maximum_timeout=None): + """The Beta API is deprecated for 0.15.0 and later. + + It is recommended to use the GA API (classes and functions in this + file not marked beta) for all further purposes. This function was + generated only to ease transition from grpcio<0.15.0 to grpcio>=0.15.0""" + request_deserializers = { + ('proto.KV', 'Get'): GetRequest.FromString, + ('proto.KV', 'Put'): PutRequest.FromString, + } + response_serializers = { + ('proto.KV', 'Get'): GetResponse.SerializeToString, + ('proto.KV', 'Put'): Empty.SerializeToString, + } + method_implementations = { + ('proto.KV', 'Get'): face_utilities.unary_unary_inline(servicer.Get), + ('proto.KV', 'Put'): face_utilities.unary_unary_inline(servicer.Put), + } + server_options = beta_implementations.server_options(request_deserializers=request_deserializers, response_serializers=response_serializers, thread_pool=pool, thread_pool_size=pool_size, default_timeout=default_timeout, maximum_timeout=maximum_timeout) + return beta_implementations.server(method_implementations, options=server_options) + + + def beta_create_KV_stub(channel, host=None, metadata_transformer=None, pool=None, pool_size=None): + """The Beta API is deprecated for 0.15.0 and later. + + It is recommended to use the GA API (classes and functions in this + file not marked beta) for all further purposes. This function was + generated only to ease transition from grpcio<0.15.0 to grpcio>=0.15.0""" + request_serializers = { + ('proto.KV', 'Get'): GetRequest.SerializeToString, + ('proto.KV', 'Put'): PutRequest.SerializeToString, + } + response_deserializers = { + ('proto.KV', 'Get'): GetResponse.FromString, + ('proto.KV', 'Put'): Empty.FromString, + } + cardinalities = { + 'Get': cardinality.Cardinality.UNARY_UNARY, + 'Put': cardinality.Cardinality.UNARY_UNARY, + } + stub_options = beta_implementations.stub_options(host=host, metadata_transformer=metadata_transformer, request_serializers=request_serializers, response_deserializers=response_deserializers, thread_pool=pool, thread_pool_size=pool_size) + return beta_implementations.dynamic_stub(channel, 'proto.KV', cardinalities, options=stub_options) +except ImportError: + pass +# @@protoc_insertion_point(module_scope) diff --git a/vendor/github.com/hashicorp/go-plugin/examples/grpc/plugin-python/kv_pb2_grpc.py b/vendor/github.com/hashicorp/go-plugin/examples/grpc/plugin-python/kv_pb2_grpc.py new file mode 100644 index 00000000..cc331c85 --- /dev/null +++ b/vendor/github.com/hashicorp/go-plugin/examples/grpc/plugin-python/kv_pb2_grpc.py @@ -0,0 +1,55 @@ +# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! +import grpc + +import kv_pb2 as kv__pb2 + + +class KVStub(object): + + def __init__(self, channel): + """Constructor. + + Args: + channel: A grpc.Channel. + """ + self.Get = channel.unary_unary( + '/proto.KV/Get', + request_serializer=kv__pb2.GetRequest.SerializeToString, + response_deserializer=kv__pb2.GetResponse.FromString, + ) + self.Put = channel.unary_unary( + '/proto.KV/Put', + request_serializer=kv__pb2.PutRequest.SerializeToString, + response_deserializer=kv__pb2.Empty.FromString, + ) + + +class KVServicer(object): + + def Get(self, request, context): + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def Put(self, request, context): + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + +def add_KVServicer_to_server(servicer, server): + rpc_method_handlers = { + 'Get': grpc.unary_unary_rpc_method_handler( + servicer.Get, + request_deserializer=kv__pb2.GetRequest.FromString, + response_serializer=kv__pb2.GetResponse.SerializeToString, + ), + 'Put': grpc.unary_unary_rpc_method_handler( + servicer.Put, + request_deserializer=kv__pb2.PutRequest.FromString, + response_serializer=kv__pb2.Empty.SerializeToString, + ), + } + generic_handler = grpc.method_handlers_generic_handler( + 'proto.KV', rpc_method_handlers) + server.add_generic_rpc_handlers((generic_handler,)) diff --git a/vendor/github.com/hashicorp/go-plugin/examples/grpc/plugin-python/plugin.py b/vendor/github.com/hashicorp/go-plugin/examples/grpc/plugin-python/plugin.py new file mode 100644 index 00000000..dbdf4f15 --- /dev/null +++ b/vendor/github.com/hashicorp/go-plugin/examples/grpc/plugin-python/plugin.py @@ -0,0 +1,54 @@ +from concurrent import futures +import sys +import time + +import grpc + +import kv_pb2 +import kv_pb2_grpc + +from grpc_health.v1.health import HealthServicer +from grpc_health.v1 import health_pb2, health_pb2_grpc + +class KVServicer(kv_pb2_grpc.KVServicer): + """Implementation of KV service.""" + + def Get(self, request, context): + filename = "kv_"+request.key + with open(filename, 'r+b') as f: + result = kv_pb2.GetResponse() + result.value = f.read() + return result + + def Put(self, request, context): + filename = "kv_"+request.key + value = "{0}\n\nWritten from plugin-python".format(request.value) + with open(filename, 'w') as f: + f.write(value) + + return kv_pb2.Empty() + +def serve(): + # We need to build a health service to work with go-plugin + health = HealthServicer() + health.set("plugin", health_pb2.HealthCheckResponse.ServingStatus.Value('SERVING')) + + # Start the server. + server = grpc.server(futures.ThreadPoolExecutor(max_workers=10)) + kv_pb2_grpc.add_KVServicer_to_server(KVServicer(), server) + health_pb2_grpc.add_HealthServicer_to_server(health, server) + server.add_insecure_port('127.0.0.1:1234') + server.start() + + # Output information + print("1|1|tcp|127.0.0.1:1234|grpc") + sys.stdout.flush() + + try: + while True: + time.sleep(60 * 60 * 24) + except KeyboardInterrupt: + server.stop(0) + +if __name__ == '__main__': + serve() diff --git a/vendor/github.com/hashicorp/go-plugin/examples/grpc/proto/kv.pb.go b/vendor/github.com/hashicorp/go-plugin/examples/grpc/proto/kv.pb.go new file mode 100644 index 00000000..6ddbdaf0 --- /dev/null +++ b/vendor/github.com/hashicorp/go-plugin/examples/grpc/proto/kv.pb.go @@ -0,0 +1,229 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// source: kv.proto + +/* +Package proto is a generated protocol buffer package. + +It is generated from these files: + kv.proto + +It has these top-level messages: + GetRequest + GetResponse + PutRequest + Empty +*/ +package proto + +import proto1 "github.com/golang/protobuf/proto" +import fmt "fmt" +import math "math" + +import ( + context "golang.org/x/net/context" + grpc "google.golang.org/grpc" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto1.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto1.ProtoPackageIsVersion2 // please upgrade the proto package + +type GetRequest struct { + Key string `protobuf:"bytes,1,opt,name=key" json:"key,omitempty"` +} + +func (m *GetRequest) Reset() { *m = GetRequest{} } +func (m *GetRequest) String() string { return proto1.CompactTextString(m) } +func (*GetRequest) ProtoMessage() {} +func (*GetRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} } + +func (m *GetRequest) GetKey() string { + if m != nil { + return m.Key + } + return "" +} + +type GetResponse struct { + Value []byte `protobuf:"bytes,1,opt,name=value,proto3" json:"value,omitempty"` +} + +func (m *GetResponse) Reset() { *m = GetResponse{} } +func (m *GetResponse) String() string { return proto1.CompactTextString(m) } +func (*GetResponse) ProtoMessage() {} +func (*GetResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{1} } + +func (m *GetResponse) GetValue() []byte { + if m != nil { + return m.Value + } + return nil +} + +type PutRequest struct { + Key string `protobuf:"bytes,1,opt,name=key" json:"key,omitempty"` + Value []byte `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"` +} + +func (m *PutRequest) Reset() { *m = PutRequest{} } +func (m *PutRequest) String() string { return proto1.CompactTextString(m) } +func (*PutRequest) ProtoMessage() {} +func (*PutRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{2} } + +func (m *PutRequest) GetKey() string { + if m != nil { + return m.Key + } + return "" +} + +func (m *PutRequest) GetValue() []byte { + if m != nil { + return m.Value + } + return nil +} + +type Empty struct { +} + +func (m *Empty) Reset() { *m = Empty{} } +func (m *Empty) String() string { return proto1.CompactTextString(m) } +func (*Empty) ProtoMessage() {} +func (*Empty) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{3} } + +func init() { + proto1.RegisterType((*GetRequest)(nil), "proto.GetRequest") + proto1.RegisterType((*GetResponse)(nil), "proto.GetResponse") + proto1.RegisterType((*PutRequest)(nil), "proto.PutRequest") + proto1.RegisterType((*Empty)(nil), "proto.Empty") +} + +// Reference imports to suppress errors if they are not otherwise used. +var _ context.Context +var _ grpc.ClientConn + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +const _ = grpc.SupportPackageIsVersion4 + +// Client API for KV service + +type KVClient interface { + Get(ctx context.Context, in *GetRequest, opts ...grpc.CallOption) (*GetResponse, error) + Put(ctx context.Context, in *PutRequest, opts ...grpc.CallOption) (*Empty, error) +} + +type kVClient struct { + cc *grpc.ClientConn +} + +func NewKVClient(cc *grpc.ClientConn) KVClient { + return &kVClient{cc} +} + +func (c *kVClient) Get(ctx context.Context, in *GetRequest, opts ...grpc.CallOption) (*GetResponse, error) { + out := new(GetResponse) + err := grpc.Invoke(ctx, "/proto.KV/Get", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *kVClient) Put(ctx context.Context, in *PutRequest, opts ...grpc.CallOption) (*Empty, error) { + out := new(Empty) + err := grpc.Invoke(ctx, "/proto.KV/Put", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// Server API for KV service + +type KVServer interface { + Get(context.Context, *GetRequest) (*GetResponse, error) + Put(context.Context, *PutRequest) (*Empty, error) +} + +func RegisterKVServer(s *grpc.Server, srv KVServer) { + s.RegisterService(&_KV_serviceDesc, srv) +} + +func _KV_Get_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(KVServer).Get(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/proto.KV/Get", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(KVServer).Get(ctx, req.(*GetRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _KV_Put_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(PutRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(KVServer).Put(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/proto.KV/Put", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(KVServer).Put(ctx, req.(*PutRequest)) + } + return interceptor(ctx, in, info, handler) +} + +var _KV_serviceDesc = grpc.ServiceDesc{ + ServiceName: "proto.KV", + HandlerType: (*KVServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "Get", + Handler: _KV_Get_Handler, + }, + { + MethodName: "Put", + Handler: _KV_Put_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "kv.proto", +} + +func init() { proto1.RegisterFile("kv.proto", fileDescriptor0) } + +var fileDescriptor0 = []byte{ + // 162 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xe2, 0xc8, 0x2e, 0xd3, 0x2b, + 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x62, 0x05, 0x53, 0x4a, 0x72, 0x5c, 0x5c, 0xee, 0xa9, 0x25, 0x41, + 0xa9, 0x85, 0xa5, 0xa9, 0xc5, 0x25, 0x42, 0x02, 0x5c, 0xcc, 0xd9, 0xa9, 0x95, 0x12, 0x8c, 0x0a, + 0x8c, 0x1a, 0x9c, 0x41, 0x20, 0xa6, 0x92, 0x32, 0x17, 0x37, 0x58, 0xbe, 0xb8, 0x20, 0x3f, 0xaf, + 0x38, 0x55, 0x48, 0x84, 0x8b, 0xb5, 0x2c, 0x31, 0xa7, 0x34, 0x15, 0xac, 0x84, 0x27, 0x08, 0xc2, + 0x51, 0x32, 0xe1, 0xe2, 0x0a, 0x28, 0xc5, 0x6d, 0x08, 0x42, 0x17, 0x13, 0xb2, 0x2e, 0x76, 0x2e, + 0x56, 0xd7, 0xdc, 0x82, 0x92, 0x4a, 0xa3, 0x28, 0x2e, 0x26, 0xef, 0x30, 0x21, 0x1d, 0x2e, 0x66, + 0xf7, 0xd4, 0x12, 0x21, 0x41, 0x88, 0xfb, 0xf4, 0x10, 0xae, 0x92, 0x12, 0x42, 0x16, 0x82, 0x3a, + 0x44, 0x8d, 0x8b, 0x39, 0xa0, 0x14, 0xa1, 0x1a, 0x61, 0xbd, 0x14, 0x0f, 0x54, 0x08, 0x6c, 0x76, + 0x12, 0x1b, 0x98, 0x63, 0x0c, 0x08, 0x00, 0x00, 0xff, 0xff, 0x06, 0x32, 0x05, 0x89, 0xf9, 0x00, + 0x00, 0x00, +} diff --git a/vendor/github.com/hashicorp/go-plugin/examples/grpc/proto/kv.proto b/vendor/github.com/hashicorp/go-plugin/examples/grpc/proto/kv.proto new file mode 100644 index 00000000..2d1760e3 --- /dev/null +++ b/vendor/github.com/hashicorp/go-plugin/examples/grpc/proto/kv.proto @@ -0,0 +1,22 @@ +syntax = "proto3"; +package proto; + +message GetRequest { + string key = 1; +} + +message GetResponse { + bytes value = 1; +} + +message PutRequest { + string key = 1; + bytes value = 2; +} + +message Empty {} + +service KV { + rpc Get(GetRequest) returns (GetResponse); + rpc Put(PutRequest) returns (Empty); +} diff --git a/vendor/github.com/hashicorp/go-plugin/examples/grpc/shared/grpc.go b/vendor/github.com/hashicorp/go-plugin/examples/grpc/shared/grpc.go new file mode 100644 index 00000000..4b532e87 --- /dev/null +++ b/vendor/github.com/hashicorp/go-plugin/examples/grpc/shared/grpc.go @@ -0,0 +1,47 @@ +package shared + +import ( + "github.com/hashicorp/go-plugin/examples/grpc/proto" + "golang.org/x/net/context" +) + +// GRPCClient is an implementation of KV that talks over RPC. +type GRPCClient struct{ client proto.KVClient } + +func (m *GRPCClient) Put(key string, value []byte) error { + _, err := m.client.Put(context.Background(), &proto.PutRequest{ + Key: key, + Value: value, + }) + return err +} + +func (m *GRPCClient) Get(key string) ([]byte, error) { + resp, err := m.client.Get(context.Background(), &proto.GetRequest{ + Key: key, + }) + if err != nil { + return nil, err + } + + return resp.Value, nil +} + +// Here is the gRPC server that GRPCClient talks to. +type GRPCServer struct { + // This is the real implementation + Impl KV +} + +func (m *GRPCServer) Put( + ctx context.Context, + req *proto.PutRequest) (*proto.Empty, error) { + return &proto.Empty{}, m.Impl.Put(req.Key, req.Value) +} + +func (m *GRPCServer) Get( + ctx context.Context, + req *proto.GetRequest) (*proto.GetResponse, error) { + v, err := m.Impl.Get(req.Key) + return &proto.GetResponse{Value: v}, err +} diff --git a/vendor/github.com/hashicorp/go-plugin/examples/grpc/shared/interface.go b/vendor/github.com/hashicorp/go-plugin/examples/grpc/shared/interface.go new file mode 100644 index 00000000..d7536625 --- /dev/null +++ b/vendor/github.com/hashicorp/go-plugin/examples/grpc/shared/interface.go @@ -0,0 +1,56 @@ +// Package shared contains shared data between the host and plugins. +package shared + +import ( + "context" + "net/rpc" + + "google.golang.org/grpc" + + "github.com/hashicorp/go-plugin" + "github.com/hashicorp/go-plugin/examples/grpc/proto" +) + +// Handshake is a common handshake that is shared by plugin and host. +var Handshake = plugin.HandshakeConfig{ + ProtocolVersion: 1, + MagicCookieKey: "BASIC_PLUGIN", + MagicCookieValue: "hello", +} + +// PluginMap is the map of plugins we can dispense. +var PluginMap = map[string]plugin.Plugin{ + "kv": &KVPlugin{}, +} + +// KV is the interface that we're exposing as a plugin. +type KV interface { + Put(key string, value []byte) error + Get(key string) ([]byte, error) +} + +// This is the implementation of plugin.Plugin so we can serve/consume this. +// We also implement GRPCPlugin so that this plugin can be served over +// gRPC. +type KVPlugin struct { + // Concrete implementation, written in Go. This is only used for plugins + // that are written in Go. + Impl KV +} + +func (p *KVPlugin) Server(*plugin.MuxBroker) (interface{}, error) { + return &RPCServer{Impl: p.Impl}, nil +} + +func (*KVPlugin) Client(b *plugin.MuxBroker, c *rpc.Client) (interface{}, error) { + return &RPCClient{client: c}, nil +} + +func (p *KVPlugin) GRPCServer(broker *plugin.GRPCBroker, s *grpc.Server) error { + proto.RegisterKVServer(s, &GRPCServer{Impl: p.Impl}) + return nil +} + +func (p *KVPlugin) GRPCClient(ctx context.Context, broker *plugin.GRPCBroker, c *grpc.ClientConn) (interface{}, error) { + return &GRPCClient{client: proto.NewKVClient(c)}, nil +} diff --git a/vendor/github.com/hashicorp/go-plugin/examples/grpc/shared/rpc.go b/vendor/github.com/hashicorp/go-plugin/examples/grpc/shared/rpc.go new file mode 100644 index 00000000..000e62b4 --- /dev/null +++ b/vendor/github.com/hashicorp/go-plugin/examples/grpc/shared/rpc.go @@ -0,0 +1,42 @@ +package shared + +import ( + "net/rpc" +) + +// RPCClient is an implementation of KV that talks over RPC. +type RPCClient struct{ client *rpc.Client } + +func (m *RPCClient) Put(key string, value []byte) error { + // We don't expect a response, so we can just use interface{} + var resp interface{} + + // The args are just going to be a map. A struct could be better. + return m.client.Call("Plugin.Put", map[string]interface{}{ + "key": key, + "value": value, + }, &resp) +} + +func (m *RPCClient) Get(key string) ([]byte, error) { + var resp []byte + err := m.client.Call("Plugin.Get", key, &resp) + return resp, err +} + +// Here is the RPC server that RPCClient talks to, conforming to +// the requirements of net/rpc +type RPCServer struct { + // This is the real implementation + Impl KV +} + +func (m *RPCServer) Put(args map[string]interface{}, resp *interface{}) error { + return m.Impl.Put(args["key"].(string), args["value"].([]byte)) +} + +func (m *RPCServer) Get(key string, resp *[]byte) error { + v, err := m.Impl.Get(key) + *resp = v + return err +} diff --git a/vendor/github.com/hashicorp/go-plugin/grpc_broker.go b/vendor/github.com/hashicorp/go-plugin/grpc_broker.go new file mode 100644 index 00000000..49fd21c6 --- /dev/null +++ b/vendor/github.com/hashicorp/go-plugin/grpc_broker.go @@ -0,0 +1,455 @@ +package plugin + +import ( + "context" + "crypto/tls" + "errors" + "fmt" + "log" + "net" + "sync" + "sync/atomic" + "time" + + "github.com/oklog/run" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials" +) + +// streamer interface is used in the broker to send/receive connection +// information. +type streamer interface { + Send(*ConnInfo) error + Recv() (*ConnInfo, error) + Close() +} + +// sendErr is used to pass errors back during a send. +type sendErr struct { + i *ConnInfo + ch chan error +} + +// gRPCBrokerServer is used by the plugin to start a stream and to send +// connection information to/from the plugin. Implements GRPCBrokerServer and +// streamer interfaces. +type gRPCBrokerServer struct { + // send is used to send connection info to the gRPC stream. + send chan *sendErr + + // recv is used to receive connection info from the gRPC stream. + recv chan *ConnInfo + + // quit closes down the stream. + quit chan struct{} + + // o is used to ensure we close the quit channel only once. + o sync.Once +} + +func newGRPCBrokerServer() *gRPCBrokerServer { + return &gRPCBrokerServer{ + send: make(chan *sendErr), + recv: make(chan *ConnInfo), + quit: make(chan struct{}), + } +} + +// StartStream implements the GRPCBrokerServer interface and will block until +// the quit channel is closed or the context reports Done. The stream will pass +// connection information to/from the client. +func (s *gRPCBrokerServer) StartStream(stream GRPCBroker_StartStreamServer) error { + doneCh := stream.Context().Done() + defer s.Close() + + // Proccess send stream + go func() { + for { + select { + case <-doneCh: + return + case <-s.quit: + return + case se := <-s.send: + err := stream.Send(se.i) + se.ch <- err + } + } + }() + + // Process receive stream + for { + i, err := stream.Recv() + if err != nil { + return err + } + select { + case <-doneCh: + return nil + case <-s.quit: + return nil + case s.recv <- i: + } + } + + return nil +} + +// Send is used by the GRPCBroker to pass connection information into the stream +// to the client. +func (s *gRPCBrokerServer) Send(i *ConnInfo) error { + ch := make(chan error) + defer close(ch) + + select { + case <-s.quit: + return errors.New("broker closed") + case s.send <- &sendErr{ + i: i, + ch: ch, + }: + } + + return <-ch +} + +// Recv is used by the GRPCBroker to pass connection information that has been +// sent from the client from the stream to the broker. +func (s *gRPCBrokerServer) Recv() (*ConnInfo, error) { + select { + case <-s.quit: + return nil, errors.New("broker closed") + case i := <-s.recv: + return i, nil + } +} + +// Close closes the quit channel, shutting down the stream. +func (s *gRPCBrokerServer) Close() { + s.o.Do(func() { + close(s.quit) + }) +} + +// gRPCBrokerClientImpl is used by the client to start a stream and to send +// connection information to/from the client. Implements GRPCBrokerClient and +// streamer interfaces. +type gRPCBrokerClientImpl struct { + // client is the underlying GRPC client used to make calls to the server. + client GRPCBrokerClient + + // send is used to send connection info to the gRPC stream. + send chan *sendErr + + // recv is used to receive connection info from the gRPC stream. + recv chan *ConnInfo + + // quit closes down the stream. + quit chan struct{} + + // o is used to ensure we close the quit channel only once. + o sync.Once +} + +func newGRPCBrokerClient(conn *grpc.ClientConn) *gRPCBrokerClientImpl { + return &gRPCBrokerClientImpl{ + client: NewGRPCBrokerClient(conn), + send: make(chan *sendErr), + recv: make(chan *ConnInfo), + quit: make(chan struct{}), + } +} + +// StartStream implements the GRPCBrokerClient interface and will block until +// the quit channel is closed or the context reports Done. The stream will pass +// connection information to/from the plugin. +func (s *gRPCBrokerClientImpl) StartStream() error { + ctx, cancelFunc := context.WithCancel(context.Background()) + defer cancelFunc() + defer s.Close() + + stream, err := s.client.StartStream(ctx) + if err != nil { + return err + } + doneCh := stream.Context().Done() + + go func() { + for { + select { + case <-doneCh: + return + case <-s.quit: + return + case se := <-s.send: + err := stream.Send(se.i) + se.ch <- err + } + } + }() + + for { + i, err := stream.Recv() + if err != nil { + return err + } + select { + case <-doneCh: + return nil + case <-s.quit: + return nil + case s.recv <- i: + } + } + + return nil +} + +// Send is used by the GRPCBroker to pass connection information into the stream +// to the plugin. +func (s *gRPCBrokerClientImpl) Send(i *ConnInfo) error { + ch := make(chan error) + defer close(ch) + + select { + case <-s.quit: + return errors.New("broker closed") + case s.send <- &sendErr{ + i: i, + ch: ch, + }: + } + + return <-ch +} + +// Recv is used by the GRPCBroker to pass connection information that has been +// sent from the plugin to the broker. +func (s *gRPCBrokerClientImpl) Recv() (*ConnInfo, error) { + select { + case <-s.quit: + return nil, errors.New("broker closed") + case i := <-s.recv: + return i, nil + } +} + +// Close closes the quit channel, shutting down the stream. +func (s *gRPCBrokerClientImpl) Close() { + s.o.Do(func() { + close(s.quit) + }) +} + +// GRPCBroker is responsible for brokering connections by unique ID. +// +// It is used by plugins to create multiple gRPC connections and data +// streams between the plugin process and the host process. +// +// This allows a plugin to request a channel with a specific ID to connect to +// or accept a connection from, and the broker handles the details of +// holding these channels open while they're being negotiated. +// +// The Plugin interface has access to these for both Server and Client. +// The broker can be used by either (optionally) to reserve and connect to +// new streams. This is useful for complex args and return values, +// or anything else you might need a data stream for. +type GRPCBroker struct { + nextId uint32 + streamer streamer + streams map[uint32]*gRPCBrokerPending + tls *tls.Config + doneCh chan struct{} + o sync.Once + + sync.Mutex +} + +type gRPCBrokerPending struct { + ch chan *ConnInfo + doneCh chan struct{} +} + +func newGRPCBroker(s streamer, tls *tls.Config) *GRPCBroker { + return &GRPCBroker{ + streamer: s, + streams: make(map[uint32]*gRPCBrokerPending), + tls: tls, + doneCh: make(chan struct{}), + } +} + +// Accept accepts a connection by ID. +// +// This should not be called multiple times with the same ID at one time. +func (b *GRPCBroker) Accept(id uint32) (net.Listener, error) { + listener, err := serverListener() + if err != nil { + return nil, err + } + + err = b.streamer.Send(&ConnInfo{ + ServiceId: id, + Network: listener.Addr().Network(), + Address: listener.Addr().String(), + }) + if err != nil { + return nil, err + } + + return listener, nil +} + +// AcceptAndServe is used to accept a specific stream ID and immediately +// serve a gRPC server on that stream ID. This is used to easily serve +// complex arguments. Each AcceptAndServe call opens a new listener socket and +// sends the connection info down the stream to the dialer. Since a new +// connection is opened every call, these calls should be used sparingly. +// Multiple gRPC server implementations can be registered to a single +// AcceptAndServe call. +func (b *GRPCBroker) AcceptAndServe(id uint32, s func([]grpc.ServerOption) *grpc.Server) { + listener, err := b.Accept(id) + if err != nil { + log.Printf("[ERR] plugin: plugin acceptAndServe error: %s", err) + return + } + defer listener.Close() + + var opts []grpc.ServerOption + if b.tls != nil { + opts = []grpc.ServerOption{grpc.Creds(credentials.NewTLS(b.tls))} + } + + server := s(opts) + + // Here we use a run group to close this goroutine if the server is shutdown + // or the broker is shutdown. + var g run.Group + { + // Serve on the listener, if shutting down call GracefulStop. + g.Add(func() error { + return server.Serve(listener) + }, func(err error) { + server.GracefulStop() + }) + } + { + // block on the closeCh or the doneCh. If we are shutting down close the + // closeCh. + closeCh := make(chan struct{}) + g.Add(func() error { + select { + case <-b.doneCh: + case <-closeCh: + } + return nil + }, func(err error) { + close(closeCh) + }) + } + + // Block until we are done + g.Run() +} + +// Close closes the stream and all servers. +func (b *GRPCBroker) Close() error { + b.streamer.Close() + b.o.Do(func() { + close(b.doneCh) + }) + return nil +} + +// Dial opens a connection by ID. +func (b *GRPCBroker) Dial(id uint32) (conn *grpc.ClientConn, err error) { + var c *ConnInfo + + // Open the stream + p := b.getStream(id) + select { + case c = <-p.ch: + close(p.doneCh) + case <-time.After(5 * time.Second): + return nil, fmt.Errorf("timeout waiting for connection info") + } + + var addr net.Addr + switch c.Network { + case "tcp": + addr, err = net.ResolveTCPAddr("tcp", c.Address) + case "unix": + addr, err = net.ResolveUnixAddr("unix", c.Address) + default: + err = fmt.Errorf("Unknown address type: %s", c.Address) + } + if err != nil { + return nil, err + } + + return dialGRPCConn(b.tls, netAddrDialer(addr)) +} + +// NextId returns a unique ID to use next. +// +// It is possible for very long-running plugin hosts to wrap this value, +// though it would require a very large amount of calls. In practice +// we've never seen it happen. +func (m *GRPCBroker) NextId() uint32 { + return atomic.AddUint32(&m.nextId, 1) +} + +// Run starts the brokering and should be executed in a goroutine, since it +// blocks forever, or until the session closes. +// +// Uses of GRPCBroker never need to call this. It is called internally by +// the plugin host/client. +func (m *GRPCBroker) Run() { + for { + stream, err := m.streamer.Recv() + if err != nil { + // Once we receive an error, just exit + break + } + + // Initialize the waiter + p := m.getStream(stream.ServiceId) + select { + case p.ch <- stream: + default: + } + + go m.timeoutWait(stream.ServiceId, p) + } +} + +func (m *GRPCBroker) getStream(id uint32) *gRPCBrokerPending { + m.Lock() + defer m.Unlock() + + p, ok := m.streams[id] + if ok { + return p + } + + m.streams[id] = &gRPCBrokerPending{ + ch: make(chan *ConnInfo, 1), + doneCh: make(chan struct{}), + } + return m.streams[id] +} + +func (m *GRPCBroker) timeoutWait(id uint32, p *gRPCBrokerPending) { + // Wait for the stream to either be picked up and connected, or + // for a timeout. + select { + case <-p.doneCh: + case <-time.After(5 * time.Second): + } + + m.Lock() + defer m.Unlock() + + // Delete the stream so no one else can grab it + delete(m.streams, id) +} diff --git a/vendor/github.com/hashicorp/go-plugin/grpc_broker.pb.go b/vendor/github.com/hashicorp/go-plugin/grpc_broker.pb.go new file mode 100644 index 00000000..d490dafb --- /dev/null +++ b/vendor/github.com/hashicorp/go-plugin/grpc_broker.pb.go @@ -0,0 +1,190 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// source: grpc_broker.proto + +/* +Package plugin is a generated protocol buffer package. + +It is generated from these files: + grpc_broker.proto + +It has these top-level messages: + ConnInfo +*/ +package plugin + +import proto "github.com/golang/protobuf/proto" +import fmt "fmt" +import math "math" + +import ( + context "golang.org/x/net/context" + grpc "google.golang.org/grpc" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package + +type ConnInfo struct { + ServiceId uint32 `protobuf:"varint,1,opt,name=service_id,json=serviceId" json:"service_id,omitempty"` + Network string `protobuf:"bytes,2,opt,name=network" json:"network,omitempty"` + Address string `protobuf:"bytes,3,opt,name=address" json:"address,omitempty"` +} + +func (m *ConnInfo) Reset() { *m = ConnInfo{} } +func (m *ConnInfo) String() string { return proto.CompactTextString(m) } +func (*ConnInfo) ProtoMessage() {} +func (*ConnInfo) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} } + +func (m *ConnInfo) GetServiceId() uint32 { + if m != nil { + return m.ServiceId + } + return 0 +} + +func (m *ConnInfo) GetNetwork() string { + if m != nil { + return m.Network + } + return "" +} + +func (m *ConnInfo) GetAddress() string { + if m != nil { + return m.Address + } + return "" +} + +func init() { + proto.RegisterType((*ConnInfo)(nil), "plugin.ConnInfo") +} + +// Reference imports to suppress errors if they are not otherwise used. +var _ context.Context +var _ grpc.ClientConn + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +const _ = grpc.SupportPackageIsVersion4 + +// Client API for GRPCBroker service + +type GRPCBrokerClient interface { + StartStream(ctx context.Context, opts ...grpc.CallOption) (GRPCBroker_StartStreamClient, error) +} + +type gRPCBrokerClient struct { + cc *grpc.ClientConn +} + +func NewGRPCBrokerClient(cc *grpc.ClientConn) GRPCBrokerClient { + return &gRPCBrokerClient{cc} +} + +func (c *gRPCBrokerClient) StartStream(ctx context.Context, opts ...grpc.CallOption) (GRPCBroker_StartStreamClient, error) { + stream, err := grpc.NewClientStream(ctx, &_GRPCBroker_serviceDesc.Streams[0], c.cc, "/plugin.GRPCBroker/StartStream", opts...) + if err != nil { + return nil, err + } + x := &gRPCBrokerStartStreamClient{stream} + return x, nil +} + +type GRPCBroker_StartStreamClient interface { + Send(*ConnInfo) error + Recv() (*ConnInfo, error) + grpc.ClientStream +} + +type gRPCBrokerStartStreamClient struct { + grpc.ClientStream +} + +func (x *gRPCBrokerStartStreamClient) Send(m *ConnInfo) error { + return x.ClientStream.SendMsg(m) +} + +func (x *gRPCBrokerStartStreamClient) Recv() (*ConnInfo, error) { + m := new(ConnInfo) + if err := x.ClientStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + +// Server API for GRPCBroker service + +type GRPCBrokerServer interface { + StartStream(GRPCBroker_StartStreamServer) error +} + +func RegisterGRPCBrokerServer(s *grpc.Server, srv GRPCBrokerServer) { + s.RegisterService(&_GRPCBroker_serviceDesc, srv) +} + +func _GRPCBroker_StartStream_Handler(srv interface{}, stream grpc.ServerStream) error { + return srv.(GRPCBrokerServer).StartStream(&gRPCBrokerStartStreamServer{stream}) +} + +type GRPCBroker_StartStreamServer interface { + Send(*ConnInfo) error + Recv() (*ConnInfo, error) + grpc.ServerStream +} + +type gRPCBrokerStartStreamServer struct { + grpc.ServerStream +} + +func (x *gRPCBrokerStartStreamServer) Send(m *ConnInfo) error { + return x.ServerStream.SendMsg(m) +} + +func (x *gRPCBrokerStartStreamServer) Recv() (*ConnInfo, error) { + m := new(ConnInfo) + if err := x.ServerStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + +var _GRPCBroker_serviceDesc = grpc.ServiceDesc{ + ServiceName: "plugin.GRPCBroker", + HandlerType: (*GRPCBrokerServer)(nil), + Methods: []grpc.MethodDesc{}, + Streams: []grpc.StreamDesc{ + { + StreamName: "StartStream", + Handler: _GRPCBroker_StartStream_Handler, + ServerStreams: true, + ClientStreams: true, + }, + }, + Metadata: "grpc_broker.proto", +} + +func init() { proto.RegisterFile("grpc_broker.proto", fileDescriptor0) } + +var fileDescriptor0 = []byte{ + // 170 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x12, 0x4c, 0x2f, 0x2a, 0x48, + 0x8e, 0x4f, 0x2a, 0xca, 0xcf, 0x4e, 0x2d, 0xd2, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x62, 0x2b, + 0xc8, 0x29, 0x4d, 0xcf, 0xcc, 0x53, 0x8a, 0xe5, 0xe2, 0x70, 0xce, 0xcf, 0xcb, 0xf3, 0xcc, 0x4b, + 0xcb, 0x17, 0x92, 0xe5, 0xe2, 0x2a, 0x4e, 0x2d, 0x2a, 0xcb, 0x4c, 0x4e, 0x8d, 0xcf, 0x4c, 0x91, + 0x60, 0x54, 0x60, 0xd4, 0xe0, 0x0d, 0xe2, 0x84, 0x8a, 0x78, 0xa6, 0x08, 0x49, 0x70, 0xb1, 0xe7, + 0xa5, 0x96, 0x94, 0xe7, 0x17, 0x65, 0x4b, 0x30, 0x29, 0x30, 0x6a, 0x70, 0x06, 0xc1, 0xb8, 0x20, + 0x99, 0xc4, 0x94, 0x94, 0xa2, 0xd4, 0xe2, 0x62, 0x09, 0x66, 0x88, 0x0c, 0x94, 0x6b, 0xe4, 0xcc, + 0xc5, 0xe5, 0x1e, 0x14, 0xe0, 0xec, 0x04, 0xb6, 0x5a, 0xc8, 0x94, 0x8b, 0x3b, 0xb8, 0x24, 0xb1, + 0xa8, 0x24, 0xb8, 0xa4, 0x28, 0x35, 0x31, 0x57, 0x48, 0x40, 0x0f, 0xe2, 0x08, 0x3d, 0x98, 0x0b, + 0xa4, 0x30, 0x44, 0x34, 0x18, 0x0d, 0x18, 0x93, 0xd8, 0xc0, 0x4e, 0x36, 0x06, 0x04, 0x00, 0x00, + 0xff, 0xff, 0x7b, 0x5d, 0xfb, 0xe1, 0xc7, 0x00, 0x00, 0x00, +} diff --git a/vendor/github.com/hashicorp/go-plugin/grpc_broker.proto b/vendor/github.com/hashicorp/go-plugin/grpc_broker.proto new file mode 100644 index 00000000..f5783485 --- /dev/null +++ b/vendor/github.com/hashicorp/go-plugin/grpc_broker.proto @@ -0,0 +1,14 @@ +syntax = "proto3"; +package plugin; + +message ConnInfo { + uint32 service_id = 1; + string network = 2; + string address = 3; +} + +service GRPCBroker { + rpc StartStream(stream ConnInfo) returns (stream ConnInfo); +} + + diff --git a/vendor/github.com/hashicorp/go-plugin/grpc_client.go b/vendor/github.com/hashicorp/go-plugin/grpc_client.go new file mode 100644 index 00000000..44294d0d --- /dev/null +++ b/vendor/github.com/hashicorp/go-plugin/grpc_client.go @@ -0,0 +1,107 @@ +package plugin + +import ( + "crypto/tls" + "fmt" + "net" + "time" + + "golang.org/x/net/context" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials" + "google.golang.org/grpc/health/grpc_health_v1" +) + +func dialGRPCConn(tls *tls.Config, dialer func(string, time.Duration) (net.Conn, error)) (*grpc.ClientConn, error) { + // Build dialing options. + opts := make([]grpc.DialOption, 0, 5) + + // We use a custom dialer so that we can connect over unix domain sockets + opts = append(opts, grpc.WithDialer(dialer)) + + // go-plugin expects to block the connection + opts = append(opts, grpc.WithBlock()) + + // Fail right away + opts = append(opts, grpc.FailOnNonTempDialError(true)) + + // If we have no TLS configuration set, we need to explicitly tell grpc + // that we're connecting with an insecure connection. + if tls == nil { + opts = append(opts, grpc.WithInsecure()) + } else { + opts = append(opts, grpc.WithTransportCredentials( + credentials.NewTLS(tls))) + } + + // Connect. Note the first parameter is unused because we use a custom + // dialer that has the state to see the address. + conn, err := grpc.Dial("unused", opts...) + if err != nil { + return nil, err + } + + return conn, nil +} + +// newGRPCClient creates a new GRPCClient. The Client argument is expected +// to be successfully started already with a lock held. +func newGRPCClient(doneCtx context.Context, c *Client) (*GRPCClient, error) { + conn, err := dialGRPCConn(c.config.TLSConfig, c.dialer) + if err != nil { + return nil, err + } + + // Start the broker. + brokerGRPCClient := newGRPCBrokerClient(conn) + broker := newGRPCBroker(brokerGRPCClient, c.config.TLSConfig) + go broker.Run() + go brokerGRPCClient.StartStream() + + return &GRPCClient{ + Conn: conn, + Plugins: c.config.Plugins, + doneCtx: doneCtx, + broker: broker, + }, nil +} + +// GRPCClient connects to a GRPCServer over gRPC to dispense plugin types. +type GRPCClient struct { + Conn *grpc.ClientConn + Plugins map[string]Plugin + + doneCtx context.Context + broker *GRPCBroker +} + +// ClientProtocol impl. +func (c *GRPCClient) Close() error { + c.broker.Close() + return c.Conn.Close() +} + +// ClientProtocol impl. +func (c *GRPCClient) Dispense(name string) (interface{}, error) { + raw, ok := c.Plugins[name] + if !ok { + return nil, fmt.Errorf("unknown plugin type: %s", name) + } + + p, ok := raw.(GRPCPlugin) + if !ok { + return nil, fmt.Errorf("plugin %q doesn't support gRPC", name) + } + + return p.GRPCClient(c.doneCtx, c.broker, c.Conn) +} + +// ClientProtocol impl. +func (c *GRPCClient) Ping() error { + client := grpc_health_v1.NewHealthClient(c.Conn) + _, err := client.Check(context.Background(), &grpc_health_v1.HealthCheckRequest{ + Service: GRPCServiceName, + }) + + return err +} diff --git a/vendor/github.com/hashicorp/go-plugin/grpc_client_test.go b/vendor/github.com/hashicorp/go-plugin/grpc_client_test.go new file mode 100644 index 00000000..5f762d22 --- /dev/null +++ b/vendor/github.com/hashicorp/go-plugin/grpc_client_test.go @@ -0,0 +1,106 @@ +package plugin + +import ( + "context" + "reflect" + "testing" + + "github.com/hashicorp/go-plugin/test/grpc" + "google.golang.org/grpc" +) + +func TestGRPCClient_App(t *testing.T) { + client, server := TestPluginGRPCConn(t, map[string]Plugin{ + "test": new(testInterfacePlugin), + }) + defer client.Close() + defer server.Stop() + + raw, err := client.Dispense("test") + if err != nil { + t.Fatalf("err: %s", err) + } + + impl, ok := raw.(testInterface) + if !ok { + t.Fatalf("bad: %#v", raw) + } + + result := impl.Double(21) + if result != 42 { + t.Fatalf("bad: %#v", result) + } + + err = impl.Bidirectional() + if err != nil { + t.Fatal(err) + } +} + +func TestGRPCConn_BidirectionalPing(t *testing.T) { + conn, _ := TestGRPCConn(t, func(s *grpc.Server) { + grpctest.RegisterPingPongServer(s, &pingPongServer{}) + }) + defer conn.Close() + pingPongClient := grpctest.NewPingPongClient(conn) + + pResp, err := pingPongClient.Ping(context.Background(), &grpctest.PingRequest{}) + if err != nil { + t.Fatal(err) + } + if pResp.Msg != "pong" { + t.Fatal("Bad PingPong") + } +} + +func TestGRPCC_Stream(t *testing.T) { + client, server := TestPluginGRPCConn(t, map[string]Plugin{ + "test": new(testInterfacePlugin), + }) + defer client.Close() + defer server.Stop() + + raw, err := client.Dispense("test") + if err != nil { + t.Fatalf("err: %s", err) + } + + impl, ok := raw.(testStreamer) + if !ok { + t.Fatalf("bad: %#v", raw) + } + + expected := []int32{21, 22, 23, 24, 25, 26} + result, err := impl.Stream(21, 27) + if err != nil { + t.Fatal(err) + } + + if !reflect.DeepEqual(result, expected) { + t.Fatalf("expected: %v\ngot: %v", expected, result) + } +} + +func TestGRPCClient_Ping(t *testing.T) { + client, server := TestPluginGRPCConn(t, map[string]Plugin{ + "test": new(testInterfacePlugin), + }) + defer client.Close() + defer server.Stop() + + // Run a couple pings + if err := client.Ping(); err != nil { + t.Fatalf("err: %s", err) + } + if err := client.Ping(); err != nil { + t.Fatalf("err: %s", err) + } + + // Close the remote end + server.server.Stop() + + // Test ping fails + if err := client.Ping(); err == nil { + t.Fatal("should error") + } +} diff --git a/vendor/github.com/hashicorp/go-plugin/grpc_server.go b/vendor/github.com/hashicorp/go-plugin/grpc_server.go new file mode 100644 index 00000000..3a727393 --- /dev/null +++ b/vendor/github.com/hashicorp/go-plugin/grpc_server.go @@ -0,0 +1,132 @@ +package plugin + +import ( + "bytes" + "crypto/tls" + "encoding/json" + "fmt" + "io" + "net" + + "google.golang.org/grpc" + "google.golang.org/grpc/credentials" + "google.golang.org/grpc/health" + "google.golang.org/grpc/health/grpc_health_v1" +) + +// GRPCServiceName is the name of the service that the health check should +// return as passing. +const GRPCServiceName = "plugin" + +// DefaultGRPCServer can be used with the "GRPCServer" field for Server +// as a default factory method to create a gRPC server with no extra options. +func DefaultGRPCServer(opts []grpc.ServerOption) *grpc.Server { + return grpc.NewServer(opts...) +} + +// GRPCServer is a ServerType implementation that serves plugins over +// gRPC. This allows plugins to easily be written for other languages. +// +// The GRPCServer outputs a custom configuration as a base64-encoded +// JSON structure represented by the GRPCServerConfig config structure. +type GRPCServer struct { + // Plugins are the list of plugins to serve. + Plugins map[string]Plugin + + // Server is the actual server that will accept connections. This + // will be used for plugin registration as well. + Server func([]grpc.ServerOption) *grpc.Server + + // TLS should be the TLS configuration if available. If this is nil, + // the connection will not have transport security. + TLS *tls.Config + + // DoneCh is the channel that is closed when this server has exited. + DoneCh chan struct{} + + // Stdout/StderrLis are the readers for stdout/stderr that will be copied + // to the stdout/stderr connection that is output. + Stdout io.Reader + Stderr io.Reader + + config GRPCServerConfig + server *grpc.Server + broker *GRPCBroker +} + +// ServerProtocol impl. +func (s *GRPCServer) Init() error { + // Create our server + var opts []grpc.ServerOption + if s.TLS != nil { + opts = append(opts, grpc.Creds(credentials.NewTLS(s.TLS))) + } + s.server = s.Server(opts) + + // Register the health service + healthCheck := health.NewServer() + healthCheck.SetServingStatus( + GRPCServiceName, grpc_health_v1.HealthCheckResponse_SERVING) + grpc_health_v1.RegisterHealthServer(s.server, healthCheck) + + // Register the broker service + brokerServer := newGRPCBrokerServer() + RegisterGRPCBrokerServer(s.server, brokerServer) + s.broker = newGRPCBroker(brokerServer, s.TLS) + go s.broker.Run() + + // Register all our plugins onto the gRPC server. + for k, raw := range s.Plugins { + p, ok := raw.(GRPCPlugin) + if !ok { + return fmt.Errorf("%q is not a GRPC-compatible plugin", k) + } + + if err := p.GRPCServer(s.broker, s.server); err != nil { + return fmt.Errorf("error registring %q: %s", k, err) + } + } + + return nil +} + +// Stop calls Stop on the underlying grpc.Server +func (s *GRPCServer) Stop() { + s.server.Stop() +} + +// GracefulStop calls GracefulStop on the underlying grpc.Server +func (s *GRPCServer) GracefulStop() { + s.server.GracefulStop() +} + +// Config is the GRPCServerConfig encoded as JSON then base64. +func (s *GRPCServer) Config() string { + // Create a buffer that will contain our final contents + var buf bytes.Buffer + + // Wrap the base64 encoding with JSON encoding. + if err := json.NewEncoder(&buf).Encode(s.config); err != nil { + // We panic since ths shouldn't happen under any scenario. We + // carefully control the structure being encoded here and it should + // always be successful. + panic(err) + } + + return buf.String() +} + +func (s *GRPCServer) Serve(lis net.Listener) { + // Start serving in a goroutine + go s.server.Serve(lis) + + // Wait until graceful completion + <-s.DoneCh +} + +// GRPCServerConfig is the extra configuration passed along for consumers +// to facilitate using GRPC plugins. +type GRPCServerConfig struct { + StdoutAddr string `json:"stdout_addr"` + StderrAddr string `json:"stderr_addr"` +} diff --git a/vendor/github.com/hashicorp/go-plugin/log_entry.go b/vendor/github.com/hashicorp/go-plugin/log_entry.go new file mode 100644 index 00000000..2996c14c --- /dev/null +++ b/vendor/github.com/hashicorp/go-plugin/log_entry.go @@ -0,0 +1,73 @@ +package plugin + +import ( + "encoding/json" + "time" +) + +// logEntry is the JSON payload that gets sent to Stderr from the plugin to the host +type logEntry struct { + Message string `json:"@message"` + Level string `json:"@level"` + Timestamp time.Time `json:"timestamp"` + KVPairs []*logEntryKV `json:"kv_pairs"` +} + +// logEntryKV is a key value pair within the Output payload +type logEntryKV struct { + Key string `json:"key"` + Value interface{} `json:"value"` +} + +// flattenKVPairs is used to flatten KVPair slice into []interface{} +// for hclog consumption. +func flattenKVPairs(kvs []*logEntryKV) []interface{} { + var result []interface{} + for _, kv := range kvs { + result = append(result, kv.Key) + result = append(result, kv.Value) + } + + return result +} + +// parseJSON handles parsing JSON output +func parseJSON(input string) (*logEntry, error) { + var raw map[string]interface{} + entry := &logEntry{} + + err := json.Unmarshal([]byte(input), &raw) + if err != nil { + return nil, err + } + + // Parse hclog-specific objects + if v, ok := raw["@message"]; ok { + entry.Message = v.(string) + delete(raw, "@message") + } + + if v, ok := raw["@level"]; ok { + entry.Level = v.(string) + delete(raw, "@level") + } + + if v, ok := raw["@timestamp"]; ok { + t, err := time.Parse("2006-01-02T15:04:05.000000Z07:00", v.(string)) + if err != nil { + return nil, err + } + entry.Timestamp = t + delete(raw, "@timestamp") + } + + // Parse dynamic KV args from the hclog payload. + for k, v := range raw { + entry.KVPairs = append(entry.KVPairs, &logEntryKV{ + Key: k, + Value: v, + }) + } + + return entry, nil +} diff --git a/vendor/github.com/hashicorp/go-plugin/mux_broker.go b/vendor/github.com/hashicorp/go-plugin/mux_broker.go new file mode 100644 index 00000000..01c45ad7 --- /dev/null +++ b/vendor/github.com/hashicorp/go-plugin/mux_broker.go @@ -0,0 +1,204 @@ +package plugin + +import ( + "encoding/binary" + "fmt" + "log" + "net" + "sync" + "sync/atomic" + "time" + + "github.com/hashicorp/yamux" +) + +// MuxBroker is responsible for brokering multiplexed connections by unique ID. +// +// It is used by plugins to multiplex multiple RPC connections and data +// streams on top of a single connection between the plugin process and the +// host process. +// +// This allows a plugin to request a channel with a specific ID to connect to +// or accept a connection from, and the broker handles the details of +// holding these channels open while they're being negotiated. +// +// The Plugin interface has access to these for both Server and Client. +// The broker can be used by either (optionally) to reserve and connect to +// new multiplexed streams. This is useful for complex args and return values, +// or anything else you might need a data stream for. +type MuxBroker struct { + nextId uint32 + session *yamux.Session + streams map[uint32]*muxBrokerPending + + sync.Mutex +} + +type muxBrokerPending struct { + ch chan net.Conn + doneCh chan struct{} +} + +func newMuxBroker(s *yamux.Session) *MuxBroker { + return &MuxBroker{ + session: s, + streams: make(map[uint32]*muxBrokerPending), + } +} + +// Accept accepts a connection by ID. +// +// This should not be called multiple times with the same ID at one time. +func (m *MuxBroker) Accept(id uint32) (net.Conn, error) { + var c net.Conn + p := m.getStream(id) + select { + case c = <-p.ch: + close(p.doneCh) + case <-time.After(5 * time.Second): + m.Lock() + defer m.Unlock() + delete(m.streams, id) + + return nil, fmt.Errorf("timeout waiting for accept") + } + + // Ack our connection + if err := binary.Write(c, binary.LittleEndian, id); err != nil { + c.Close() + return nil, err + } + + return c, nil +} + +// AcceptAndServe is used to accept a specific stream ID and immediately +// serve an RPC server on that stream ID. This is used to easily serve +// complex arguments. +// +// The served interface is always registered to the "Plugin" name. +func (m *MuxBroker) AcceptAndServe(id uint32, v interface{}) { + conn, err := m.Accept(id) + if err != nil { + log.Printf("[ERR] plugin: plugin acceptAndServe error: %s", err) + return + } + + serve(conn, "Plugin", v) +} + +// Close closes the connection and all sub-connections. +func (m *MuxBroker) Close() error { + return m.session.Close() +} + +// Dial opens a connection by ID. +func (m *MuxBroker) Dial(id uint32) (net.Conn, error) { + // Open the stream + stream, err := m.session.OpenStream() + if err != nil { + return nil, err + } + + // Write the stream ID onto the wire. + if err := binary.Write(stream, binary.LittleEndian, id); err != nil { + stream.Close() + return nil, err + } + + // Read the ack that we connected. Then we're off! + var ack uint32 + if err := binary.Read(stream, binary.LittleEndian, &ack); err != nil { + stream.Close() + return nil, err + } + if ack != id { + stream.Close() + return nil, fmt.Errorf("bad ack: %d (expected %d)", ack, id) + } + + return stream, nil +} + +// NextId returns a unique ID to use next. +// +// It is possible for very long-running plugin hosts to wrap this value, +// though it would require a very large amount of RPC calls. In practice +// we've never seen it happen. +func (m *MuxBroker) NextId() uint32 { + return atomic.AddUint32(&m.nextId, 1) +} + +// Run starts the brokering and should be executed in a goroutine, since it +// blocks forever, or until the session closes. +// +// Uses of MuxBroker never need to call this. It is called internally by +// the plugin host/client. +func (m *MuxBroker) Run() { + for { + stream, err := m.session.AcceptStream() + if err != nil { + // Once we receive an error, just exit + break + } + + // Read the stream ID from the stream + var id uint32 + if err := binary.Read(stream, binary.LittleEndian, &id); err != nil { + stream.Close() + continue + } + + // Initialize the waiter + p := m.getStream(id) + select { + case p.ch <- stream: + default: + } + + // Wait for a timeout + go m.timeoutWait(id, p) + } +} + +func (m *MuxBroker) getStream(id uint32) *muxBrokerPending { + m.Lock() + defer m.Unlock() + + p, ok := m.streams[id] + if ok { + return p + } + + m.streams[id] = &muxBrokerPending{ + ch: make(chan net.Conn, 1), + doneCh: make(chan struct{}), + } + return m.streams[id] +} + +func (m *MuxBroker) timeoutWait(id uint32, p *muxBrokerPending) { + // Wait for the stream to either be picked up and connected, or + // for a timeout. + timeout := false + select { + case <-p.doneCh: + case <-time.After(5 * time.Second): + timeout = true + } + + m.Lock() + defer m.Unlock() + + // Delete the stream so no one else can grab it + delete(m.streams, id) + + // If we timed out, then check if we have a channel in the buffer, + // and if so, close it. + if timeout { + select { + case s := <-p.ch: + s.Close() + } + } +} diff --git a/vendor/github.com/hashicorp/go-plugin/plugin.go b/vendor/github.com/hashicorp/go-plugin/plugin.go new file mode 100644 index 00000000..79d96746 --- /dev/null +++ b/vendor/github.com/hashicorp/go-plugin/plugin.go @@ -0,0 +1,58 @@ +// The plugin package exposes functions and helpers for communicating to +// plugins which are implemented as standalone binary applications. +// +// plugin.Client fully manages the lifecycle of executing the application, +// connecting to it, and returning the RPC client for dispensing plugins. +// +// plugin.Serve fully manages listeners to expose an RPC server from a binary +// that plugin.Client can connect to. +package plugin + +import ( + "context" + "errors" + "net/rpc" + + "google.golang.org/grpc" +) + +// Plugin is the interface that is implemented to serve/connect to an +// inteface implementation. +type Plugin interface { + // Server should return the RPC server compatible struct to serve + // the methods that the Client calls over net/rpc. + Server(*MuxBroker) (interface{}, error) + + // Client returns an interface implementation for the plugin you're + // serving that communicates to the server end of the plugin. + Client(*MuxBroker, *rpc.Client) (interface{}, error) +} + +// GRPCPlugin is the interface that is implemented to serve/connect to +// a plugin over gRPC. +type GRPCPlugin interface { + // GRPCServer should register this plugin for serving with the + // given GRPCServer. Unlike Plugin.Server, this is only called once + // since gRPC plugins serve singletons. + GRPCServer(*GRPCBroker, *grpc.Server) error + + // GRPCClient should return the interface implementation for the plugin + // you're serving via gRPC. The provided context will be canceled by + // go-plugin in the event of the plugin process exiting. + GRPCClient(context.Context, *GRPCBroker, *grpc.ClientConn) (interface{}, error) +} + +// NetRPCUnsupportedPlugin implements Plugin but returns errors for the +// Server and Client functions. This will effectively disable support for +// net/rpc based plugins. +// +// This struct can be embedded in your struct. +type NetRPCUnsupportedPlugin struct{} + +func (p NetRPCUnsupportedPlugin) Server(*MuxBroker) (interface{}, error) { + return nil, errors.New("net/rpc plugin protocol not supported") +} + +func (p NetRPCUnsupportedPlugin) Client(*MuxBroker, *rpc.Client) (interface{}, error) { + return nil, errors.New("net/rpc plugin protocol not supported") +} diff --git a/vendor/github.com/hashicorp/go-plugin/plugin_test.go b/vendor/github.com/hashicorp/go-plugin/plugin_test.go new file mode 100644 index 00000000..2d282613 --- /dev/null +++ b/vendor/github.com/hashicorp/go-plugin/plugin_test.go @@ -0,0 +1,611 @@ +package plugin + +import ( + "crypto/tls" + "crypto/x509" + "errors" + "fmt" + "io" + "io/ioutil" + "log" + "net/rpc" + "os" + "os/exec" + "testing" + "time" + + hclog "github.com/hashicorp/go-hclog" + "github.com/hashicorp/go-plugin/test/grpc" + "golang.org/x/net/context" + "google.golang.org/grpc" +) + +// Test that NetRPCUnsupportedPlugin implements the correct interfaces. +var _ Plugin = new(NetRPCUnsupportedPlugin) + +// testAPIVersion is the ProtocolVersion we use for testing. +var testHandshake = HandshakeConfig{ + ProtocolVersion: 1, + MagicCookieKey: "TEST_MAGIC_COOKIE", + MagicCookieValue: "test", +} + +// testInterface is the test interface we use for plugins. +type testInterface interface { + Double(int) int + PrintKV(string, interface{}) + Bidirectional() error +} + +// testStreamer is used to test the grpc streaming interface +type testStreamer interface { + Stream(int32, int32) ([]int32, error) +} + +// testInterfacePlugin is the implementation of Plugin to create +// RPC client/server implementations for testInterface. +type testInterfacePlugin struct { + Impl testInterface +} + +func (p *testInterfacePlugin) Server(b *MuxBroker) (interface{}, error) { + return &testInterfaceServer{Impl: p.impl()}, nil +} + +func (p *testInterfacePlugin) Client(b *MuxBroker, c *rpc.Client) (interface{}, error) { + return &testInterfaceClient{Client: c}, nil +} + +func (p *testInterfacePlugin) GRPCServer(b *GRPCBroker, s *grpc.Server) error { + grpctest.RegisterTestServer(s, &testGRPCServer{broker: b, Impl: p.impl()}) + return nil +} + +func (p *testInterfacePlugin) GRPCClient(doneCtx context.Context, b *GRPCBroker, c *grpc.ClientConn) (interface{}, error) { + return &testGRPCClient{broker: b, Client: grpctest.NewTestClient(c)}, nil +} + +func (p *testInterfacePlugin) impl() testInterface { + if p.Impl != nil { + return p.Impl + } + + return &testInterfaceImpl{ + logger: hclog.New(&hclog.LoggerOptions{ + Level: hclog.Trace, + Output: os.Stderr, + JSONFormat: true, + }), + } +} + +// testInterfaceImpl implements testInterface concretely +type testInterfaceImpl struct { + logger hclog.Logger +} + +func (i *testInterfaceImpl) Double(v int) int { return v * 2 } + +func (i *testInterfaceImpl) PrintKV(key string, value interface{}) { + i.logger.Info("PrintKV called", key, value) +} + +func (i *testInterfaceImpl) Bidirectional() error { + return nil +} + +// testInterfaceClient implements testInterface to communicate over RPC +type testInterfaceClient struct { + Client *rpc.Client +} + +func (impl *testInterfaceClient) Double(v int) int { + var resp int + err := impl.Client.Call("Plugin.Double", v, &resp) + if err != nil { + panic(err) + } + + return resp +} + +func (impl *testInterfaceClient) PrintKV(key string, value interface{}) { + err := impl.Client.Call("Plugin.PrintKV", map[string]interface{}{ + "key": key, + "value": value, + }, &struct{}{}) + if err != nil { + panic(err) + } +} + +func (impl *testInterfaceClient) Bidirectional() error { + return nil +} + +// testInterfaceServer is the RPC server for testInterfaceClient +type testInterfaceServer struct { + Broker *MuxBroker + Impl testInterface +} + +func (s *testInterfaceServer) Double(arg int, resp *int) error { + *resp = s.Impl.Double(arg) + return nil +} + +func (s *testInterfaceServer) PrintKV(args map[string]interface{}, _ *struct{}) error { + s.Impl.PrintKV(args["key"].(string), args["value"]) + return nil +} + +// testPluginMap can be used for tests as a plugin map +var testPluginMap = map[string]Plugin{ + "test": new(testInterfacePlugin), +} + +// testGRPCServer is the implementation of our GRPC service. +type testGRPCServer struct { + Impl testInterface + broker *GRPCBroker +} + +func (s *testGRPCServer) Double( + ctx context.Context, + req *grpctest.TestRequest) (*grpctest.TestResponse, error) { + return &grpctest.TestResponse{ + Output: int32(s.Impl.Double(int(req.Input))), + }, nil +} + +func (s *testGRPCServer) PrintKV( + ctx context.Context, + req *grpctest.PrintKVRequest) (*grpctest.PrintKVResponse, error) { + var v interface{} + switch rv := req.Value.(type) { + case *grpctest.PrintKVRequest_ValueString: + v = rv.ValueString + + case *grpctest.PrintKVRequest_ValueInt: + v = rv.ValueInt + + default: + panic(fmt.Sprintf("unknown value: %#v", req.Value)) + } + + s.Impl.PrintKV(req.Key, v) + return &grpctest.PrintKVResponse{}, nil +} + +func (s *testGRPCServer) Bidirectional(ctx context.Context, req *grpctest.BidirectionalRequest) (*grpctest.BidirectionalResponse, error) { + conn, err := s.broker.Dial(req.Id) + if err != nil { + return nil, err + } + + pingPongClient := grpctest.NewPingPongClient(conn) + resp, err := pingPongClient.Ping(ctx, &grpctest.PingRequest{}) + if err != nil { + return nil, err + } + if resp.Msg != "pong" { + return nil, errors.New("Bad PingPong") + } + + nextID := s.broker.NextId() + go s.broker.AcceptAndServe(nextID, func(opts []grpc.ServerOption) *grpc.Server { + s := grpc.NewServer(opts...) + grpctest.RegisterPingPongServer(s, &pingPongServer{}) + return s + }) + + return &grpctest.BidirectionalResponse{ + Id: nextID, + }, nil +} + +type pingPongServer struct{} + +func (p *pingPongServer) Ping(ctx context.Context, req *grpctest.PingRequest) (*grpctest.PongResponse, error) { + return &grpctest.PongResponse{ + Msg: "pong", + }, nil +} + +func (s testGRPCServer) Stream(stream grpctest.Test_StreamServer) error { + for { + req, err := stream.Recv() + if err != nil { + if err != io.EOF { + return err + } + return nil + } + + if err := stream.Send(&grpctest.TestResponse{Output: req.Input}); err != nil { + return err + } + } + + return nil +} + +// testGRPCClient is an implementation of TestInterface that communicates +// over gRPC. +type testGRPCClient struct { + Client grpctest.TestClient + broker *GRPCBroker +} + +func (c *testGRPCClient) Double(v int) int { + resp, err := c.Client.Double(context.Background(), &grpctest.TestRequest{ + Input: int32(v), + }) + if err != nil { + panic(err) + } + + return int(resp.Output) +} + +func (c *testGRPCClient) PrintKV(key string, value interface{}) { + req := &grpctest.PrintKVRequest{Key: key} + switch v := value.(type) { + case string: + req.Value = &grpctest.PrintKVRequest_ValueString{ + ValueString: v, + } + + case int: + req.Value = &grpctest.PrintKVRequest_ValueInt{ + ValueInt: int32(v), + } + + default: + panic(fmt.Sprintf("unknown type: %T", value)) + } + + _, err := c.Client.PrintKV(context.Background(), req) + if err != nil { + panic(err) + } +} + +func (c *testGRPCClient) Bidirectional() error { + nextID := c.broker.NextId() + go c.broker.AcceptAndServe(nextID, func(opts []grpc.ServerOption) *grpc.Server { + s := grpc.NewServer(opts...) + grpctest.RegisterPingPongServer(s, &pingPongServer{}) + return s + }) + + resp, err := c.Client.Bidirectional(context.Background(), &grpctest.BidirectionalRequest{ + Id: nextID, + }) + if err != nil { + return err + } + + conn, err := c.broker.Dial(resp.Id) + if err != nil { + return err + } + + pingPongClient := grpctest.NewPingPongClient(conn) + pResp, err := pingPongClient.Ping(context.Background(), &grpctest.PingRequest{}) + if err != nil { + return err + } + if pResp.Msg != "pong" { + return errors.New("Bad PingPong") + } + return nil +} + +// Stream sends a series of requests from [start, stop) using a bidirectional +// streaming service, and returns the streamed responses. +func (impl *testGRPCClient) Stream(start, stop int32) ([]int32, error) { + if stop <= start { + return nil, fmt.Errorf("invalid range [%d, %d)", start, stop) + } + streamClient, err := impl.Client.Stream(context.Background()) + if err != nil { + return nil, err + } + + var resp []int32 + for i := start; i < stop; i++ { + if err := streamClient.Send(&grpctest.TestRequest{i}); err != nil { + return resp, err + } + + out, err := streamClient.Recv() + if err != nil { + return resp, err + } + + resp = append(resp, out.Output) + } + + streamClient.CloseSend() + + return resp, nil +} + +func helperProcess(s ...string) *exec.Cmd { + cs := []string{"-test.run=TestHelperProcess", "--"} + cs = append(cs, s...) + env := []string{ + "GO_WANT_HELPER_PROCESS=1", + "PLUGIN_MIN_PORT=10000", + "PLUGIN_MAX_PORT=25000", + } + + cmd := exec.Command(os.Args[0], cs...) + cmd.Env = append(env, os.Environ()...) + return cmd +} + +// This is not a real test. This is just a helper process kicked off by +// tests. +func TestHelperProcess(*testing.T) { + if os.Getenv("GO_WANT_HELPER_PROCESS") != "1" { + return + } + + defer os.Exit(0) + + args := os.Args + for len(args) > 0 { + if args[0] == "--" { + args = args[1:] + break + } + + args = args[1:] + } + + if len(args) == 0 { + fmt.Fprintf(os.Stderr, "No command\n") + os.Exit(2) + } + + // override testPluginMap with one that uses + // hclog logger on its implementation + pluginLogger := hclog.New(&hclog.LoggerOptions{ + Level: hclog.Trace, + Output: os.Stderr, + JSONFormat: true, + }) + + testPlugin := &testInterfaceImpl{ + logger: pluginLogger, + } + + testPluginMap := map[string]Plugin{ + "test": &testInterfacePlugin{Impl: testPlugin}, + } + + cmd, args := args[0], args[1:] + switch cmd { + case "bad-version": + // If we have an arg, we write there on start + if len(args) > 0 { + path := args[0] + err := ioutil.WriteFile(path, []byte("foo"), 0644) + if err != nil { + panic(err) + } + } + + fmt.Printf("%d|%d1|tcp|:1234\n", CoreProtocolVersion, testHandshake.ProtocolVersion) + <-make(chan int) + case "invalid-rpc-address": + fmt.Println("lolinvalid") + case "mock": + fmt.Printf("%d|%d|tcp|:1234\n", CoreProtocolVersion, testHandshake.ProtocolVersion) + <-make(chan int) + case "start-timeout": + time.Sleep(1 * time.Minute) + os.Exit(1) + case "stderr": + fmt.Printf("%d|%d|tcp|:1234\n", CoreProtocolVersion, testHandshake.ProtocolVersion) + os.Stderr.WriteString("HELLO\n") + os.Stderr.WriteString("WORLD\n") + case "stderr-json": + // write values that might be JSON, but aren't KVs + fmt.Printf("%d|%d|tcp|:1234\n", CoreProtocolVersion, testHandshake.ProtocolVersion) + os.Stderr.WriteString("[\"HELLO\"]\n") + os.Stderr.WriteString("12345\n") + case "stdin": + fmt.Printf("%d|%d|tcp|:1234\n", CoreProtocolVersion, testHandshake.ProtocolVersion) + data := make([]byte, 5) + if _, err := os.Stdin.Read(data); err != nil { + log.Printf("stdin read error: %s", err) + os.Exit(100) + } + + if string(data) == "hello" { + os.Exit(0) + } + + os.Exit(1) + case "cleanup": + // Create a defer to write the file. This tests that we get cleaned + // up properly versus just calling os.Exit + path := args[0] + defer func() { + err := ioutil.WriteFile(path, []byte("foo"), 0644) + if err != nil { + panic(err) + } + }() + + Serve(&ServeConfig{ + HandshakeConfig: testHandshake, + Plugins: testPluginMap, + }) + + // Exit + return + case "test-grpc": + Serve(&ServeConfig{ + HandshakeConfig: testHandshake, + Plugins: testPluginMap, + GRPCServer: DefaultGRPCServer, + }) + + // Shouldn't reach here but make sure we exit anyways + os.Exit(0) + case "test-grpc-tls": + // Serve! + Serve(&ServeConfig{ + HandshakeConfig: testHandshake, + Plugins: testPluginMap, + GRPCServer: DefaultGRPCServer, + TLSProvider: helperTLSProvider, + }) + + // Shouldn't reach here but make sure we exit anyways + os.Exit(0) + case "test-interface": + Serve(&ServeConfig{ + HandshakeConfig: testHandshake, + Plugins: testPluginMap, + }) + + // Shouldn't reach here but make sure we exit anyways + os.Exit(0) + case "test-interface-logger-netrpc": + Serve(&ServeConfig{ + HandshakeConfig: testHandshake, + Plugins: testPluginMap, + }) + // Shouldn't reach here but make sure we exit anyways + os.Exit(0) + case "test-interface-logger-grpc": + Serve(&ServeConfig{ + HandshakeConfig: testHandshake, + Plugins: testPluginMap, + GRPCServer: DefaultGRPCServer, + }) + // Shouldn't reach here but make sure we exit anyways + os.Exit(0) + case "test-interface-daemon": + // Serve! + Serve(&ServeConfig{ + HandshakeConfig: testHandshake, + Plugins: testPluginMap, + }) + + // Shouldn't reach here but make sure we exit anyways + os.Exit(0) + case "test-interface-tls": + // Serve! + Serve(&ServeConfig{ + HandshakeConfig: testHandshake, + Plugins: testPluginMap, + TLSProvider: helperTLSProvider, + }) + + // Shouldn't reach here but make sure we exit anyways + os.Exit(0) + default: + fmt.Fprintf(os.Stderr, "Unknown command: %q\n", cmd) + os.Exit(2) + } +} + +func helperTLSProvider() (*tls.Config, error) { + serverCert, err := tls.X509KeyPair([]byte(TestClusterServerCert), []byte(TestClusterServerKey)) + if err != nil { + return nil, err + } + + rootCAs := x509.NewCertPool() + rootCAs.AppendCertsFromPEM([]byte(TestClusterCACert)) + tlsConfig := &tls.Config{ + Certificates: []tls.Certificate{serverCert}, + RootCAs: rootCAs, + ClientCAs: rootCAs, + ClientAuth: tls.VerifyClientCertIfGiven, + ServerName: "127.0.0.1", + } + tlsConfig.BuildNameToCertificate() + + return tlsConfig, nil +} + +const ( + TestClusterCACert = `-----BEGIN CERTIFICATE----- +MIIDPjCCAiagAwIBAgIUfIKsF2VPT7sdFcKOHJH2Ii6K4MwwDQYJKoZIhvcNAQEL +BQAwFjEUMBIGA1UEAxMLbXl2YXVsdC5jb20wIBcNMTYwNTAyMTYwNTQyWhgPMjA2 +NjA0MjAxNjA2MTJaMBYxFDASBgNVBAMTC215dmF1bHQuY29tMIIBIjANBgkqhkiG +9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuOimEXawD2qBoLCFP3Skq5zi1XzzcMAJlfdS +xz9hfymuJb+cN8rB91HOdU9wQCwVKnkUtGWxUnMp0tT0uAZj5NzhNfyinf0JGAbP +67HDzVZhGBHlHTjPX0638yaiUx90cTnucX0N20SgCYct29dMSgcPl+W78D3Jw3xE +JsHQPYS9ASe2eONxG09F/qNw7w/RO5/6WYoV2EmdarMMxq52pPe2chtNMQdSyOUb +cCcIZyk4QVFZ1ZLl6jTnUPb+JoCx1uMxXvMek4NF/5IL0Wr9dw2gKXKVKoHDr6SY +WrCONRw61A5Zwx1V+kn73YX3USRlkufQv/ih6/xThYDAXDC9cwIDAQABo4GBMH8w +DgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFOuKvPiU +G06iHkRXAOeMiUdBfHFyMB8GA1UdIwQYMBaAFOuKvPiUG06iHkRXAOeMiUdBfHFy +MBwGA1UdEQQVMBOCC215dmF1bHQuY29thwR/AAABMA0GCSqGSIb3DQEBCwUAA4IB +AQBcN/UdAMzc7UjRdnIpZvO+5keBGhL/vjltnGM1dMWYHa60Y5oh7UIXF+P1RdNW +n7g80lOyvkSR15/r1rDkqOK8/4oruXU31EcwGhDOC4hU6yMUy4ltV/nBoodHBXNh +MfKiXeOstH1vdI6G0P6W93Bcww6RyV1KH6sT2dbETCw+iq2VN9CrruGIWzd67UT/ +spe/kYttr3UYVV3O9kqgffVVgVXg/JoRZ3J7Hy2UEXfh9UtWNanDlRuXaZgE9s/d +CpA30CHpNXvKeyNeW2ktv+2nAbSpvNW+e6MecBCTBIoDSkgU8ShbrzmDKVwNN66Q +5gn6KxUPBKHEtNzs5DgGM7nq +-----END CERTIFICATE-----` + + TestClusterServerCert = `-----BEGIN CERTIFICATE----- +MIIDtzCCAp+gAwIBAgIUBLqh6ctGWVDUxFhxJX7m6S/bnrcwDQYJKoZIhvcNAQEL +BQAwFjEUMBIGA1UEAxMLbXl2YXVsdC5jb20wIBcNMTYwNTAyMTYwOTI2WhgPMjA2 +NjA0MjAxNTA5NTZaMBsxGTAXBgNVBAMTEGNlcnQubXl2YXVsdC5jb20wggEiMA0G +CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDY3gPB29kkdbu0mPO6J0efagQhSiXB +9OyDuLf5sMk6CVDWVWal5hISkyBmw/lXgF7qC2XFKivpJOrcGQd5Ep9otBqyJLzI +b0IWdXuPIrVnXDwcdWr86ybX2iC42zKWfbXgjzGijeAVpl0UJLKBj+fk5q6NvkRL +5FUL6TRV7Krn9mrmnrV9J5IqV15pTd9W2aVJ6IqWvIPCACtZKulqWn4707uy2X2W +1Stq/5qnp1pDshiGk1VPyxCwQ6yw3iEcgecbYo3vQfhWcv7Q8LpSIM9ZYpXu6OmF ++czqRZS9gERl+wipmmrN1MdYVrTuQem21C/PNZ4jo4XUk1SFx6JrcA+lAgMBAAGj +gfUwgfIwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMB0GA1UdDgQWBBSe +Cl9WV3BjGCwmS/KrDSLRjfwyqjAfBgNVHSMEGDAWgBTrirz4lBtOoh5EVwDnjIlH +QXxxcjA7BggrBgEFBQcBAQQvMC0wKwYIKwYBBQUHMAKGH2h0dHA6Ly8xMjcuMC4w +LjE6ODIwMC92MS9wa2kvY2EwIQYDVR0RBBowGIIQY2VydC5teXZhdWx0LmNvbYcE +fwAAATAxBgNVHR8EKjAoMCagJKAihiBodHRwOi8vMTI3LjAuMC4xOjgyMDAvdjEv +cGtpL2NybDANBgkqhkiG9w0BAQsFAAOCAQEAWGholPN8buDYwKbUiDavbzjsxUIX +lU4MxEqOHw7CD3qIYIauPboLvB9EldBQwhgOOy607Yvdg3rtyYwyBFwPhHo/hK3Z +6mn4hc6TF2V+AUdHBvGzp2dbYLeo8noVoWbQ/lBulggwlIHNNF6+a3kALqsqk1Ch +f/hzsjFnDhAlNcYFgG8TgfE2lE/FckvejPqBffo7Q3I+wVAw0buqiz5QL81NOT+D +Y2S9LLKLRaCsWo9wRU1Az4Rhd7vK5SEMh16jJ82GyEODWPvuxOTI1MnzfnbWyLYe +TTp6YBjGMVf1I6NEcWNur7U17uIOiQjMZ9krNvoMJ1A/cxCoZ98QHgcIPg== +-----END CERTIFICATE-----` + + TestClusterServerKey = `-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEA2N4DwdvZJHW7tJjzuidHn2oEIUolwfTsg7i3+bDJOglQ1lVm +peYSEpMgZsP5V4Be6gtlxSor6STq3BkHeRKfaLQasiS8yG9CFnV7jyK1Z1w8HHVq +/Osm19oguNsyln214I8xoo3gFaZdFCSygY/n5Oaujb5ES+RVC+k0Veyq5/Zq5p61 +fSeSKldeaU3fVtmlSeiKlryDwgArWSrpalp+O9O7stl9ltUrav+ap6daQ7IYhpNV +T8sQsEOssN4hHIHnG2KN70H4VnL+0PC6UiDPWWKV7ujphfnM6kWUvYBEZfsIqZpq +zdTHWFa07kHpttQvzzWeI6OF1JNUhceia3APpQIDAQABAoIBAQCH3vEzr+3nreug +RoPNCXcSJXXY9X+aeT0FeeGqClzIg7Wl03OwVOjVwl/2gqnhbIgK0oE8eiNwurR6 +mSPZcxV0oAJpwiKU4T/imlCDaReGXn86xUX2l82KRxthNdQH/VLKEmzij0jpx4Vh +bWx5SBPdkbmjDKX1dmTiRYWIn/KjyNPvNvmtwdi8Qluhf4eJcNEUr2BtblnGOmfL +FdSu+brPJozpoQ1QdDnbAQRgqnh7Shl0tT85whQi0uquqIj1gEOGVjmBvDDnL3GV +WOENTKqsmIIoEzdZrql1pfmYTk7WNaD92bfpN128j8BF7RmAV4/DphH0pvK05y9m +tmRhyHGxAoGBAOV2BBocsm6xup575VqmFN+EnIOiTn+haOvfdnVsyQHnth63fOQx +PNtMpTPR1OMKGpJ13e2bV0IgcYRsRkScVkUtoa/17VIgqZXffnJJ0A/HT67uKBq3 +8o7RrtyK5N20otw0lZHyqOPhyCdpSsurDhNON1kPVJVYY4N1RiIxfut/AoGBAPHz +HfsJ5ZkyELE9N/r4fce04lprxWH+mQGK0/PfjS9caXPhj/r5ZkVMvzWesF3mmnY8 +goE5S35TuTvV1+6rKGizwlCFAQlyXJiFpOryNWpLwCmDDSzLcm+sToAlML3tMgWU +jM3dWHx3C93c3ft4rSWJaUYI9JbHsMzDW6Yh+GbbAoGBANIbKwxh5Hx5XwEJP2yu +kIROYCYkMy6otHLujgBdmPyWl+suZjxoXWoMl2SIqR8vPD+Jj6mmyNJy9J6lqf3f +DRuQ+fEuBZ1i7QWfvJ+XuN0JyovJ5Iz6jC58D1pAD+p2IX3y5FXcVQs8zVJRFjzB +p0TEJOf2oqORaKWRd6ONoMKvAoGALKu6aVMWdQZtVov6/fdLIcgf0pn7Q3CCR2qe +X3Ry2L+zKJYIw0mwvDLDSt8VqQCenB3n6nvtmFFU7ds5lvM67rnhsoQcAOaAehiS +rl4xxoJd5Ewx7odRhZTGmZpEOYzFo4odxRSM9c30/u18fqV1Mm0AZtHYds4/sk6P +aUj0V+kCgYBMpGrJk8RSez5g0XZ35HfpI4ENoWbiwB59FIpWsLl2LADEh29eC455 +t9Muq7MprBVBHQo11TMLLFxDIjkuMho/gcKgpYXCt0LfiNm8EZehvLJUXH+3WqUx +we6ywrbFCs6LaxaOCtTiLsN+GbZCatITL0UJaeBmTAbiw0KQjUuZPQ== +-----END RSA PRIVATE KEY-----` +) diff --git a/vendor/github.com/hashicorp/go-plugin/process.go b/vendor/github.com/hashicorp/go-plugin/process.go new file mode 100644 index 00000000..88c999a5 --- /dev/null +++ b/vendor/github.com/hashicorp/go-plugin/process.go @@ -0,0 +1,24 @@ +package plugin + +import ( + "time" +) + +// pidAlive checks whether a pid is alive. +func pidAlive(pid int) bool { + return _pidAlive(pid) +} + +// pidWait blocks for a process to exit. +func pidWait(pid int) error { + ticker := time.NewTicker(1 * time.Second) + defer ticker.Stop() + + for range ticker.C { + if !pidAlive(pid) { + break + } + } + + return nil +} diff --git a/vendor/github.com/hashicorp/go-plugin/process_posix.go b/vendor/github.com/hashicorp/go-plugin/process_posix.go new file mode 100644 index 00000000..70ba546b --- /dev/null +++ b/vendor/github.com/hashicorp/go-plugin/process_posix.go @@ -0,0 +1,19 @@ +// +build !windows + +package plugin + +import ( + "os" + "syscall" +) + +// _pidAlive tests whether a process is alive or not by sending it Signal 0, +// since Go otherwise has no way to test this. +func _pidAlive(pid int) bool { + proc, err := os.FindProcess(pid) + if err == nil { + err = proc.Signal(syscall.Signal(0)) + } + + return err == nil +} diff --git a/vendor/github.com/hashicorp/go-plugin/process_windows.go b/vendor/github.com/hashicorp/go-plugin/process_windows.go new file mode 100644 index 00000000..9f7b0180 --- /dev/null +++ b/vendor/github.com/hashicorp/go-plugin/process_windows.go @@ -0,0 +1,29 @@ +package plugin + +import ( + "syscall" +) + +const ( + // Weird name but matches the MSDN docs + exit_STILL_ACTIVE = 259 + + processDesiredAccess = syscall.STANDARD_RIGHTS_READ | + syscall.PROCESS_QUERY_INFORMATION | + syscall.SYNCHRONIZE +) + +// _pidAlive tests whether a process is alive or not +func _pidAlive(pid int) bool { + h, err := syscall.OpenProcess(processDesiredAccess, false, uint32(pid)) + if err != nil { + return false + } + + var ec uint32 + if e := syscall.GetExitCodeProcess(h, &ec); e != nil { + return false + } + + return ec == exit_STILL_ACTIVE +} diff --git a/vendor/github.com/hashicorp/go-plugin/protocol.go b/vendor/github.com/hashicorp/go-plugin/protocol.go new file mode 100644 index 00000000..0cfc19e5 --- /dev/null +++ b/vendor/github.com/hashicorp/go-plugin/protocol.go @@ -0,0 +1,45 @@ +package plugin + +import ( + "io" + "net" +) + +// Protocol is an enum representing the types of protocols. +type Protocol string + +const ( + ProtocolInvalid Protocol = "" + ProtocolNetRPC Protocol = "netrpc" + ProtocolGRPC Protocol = "grpc" +) + +// ServerProtocol is an interface that must be implemented for new plugin +// protocols to be servers. +type ServerProtocol interface { + // Init is called once to configure and initialize the protocol, but + // not start listening. This is the point at which all validation should + // be done and errors returned. + Init() error + + // Config is extra configuration to be outputted to stdout. This will + // be automatically base64 encoded to ensure it can be parsed properly. + // This can be an empty string if additional configuration is not needed. + Config() string + + // Serve is called to serve connections on the given listener. This should + // continue until the listener is closed. + Serve(net.Listener) +} + +// ClientProtocol is an interface that must be implemented for new plugin +// protocols to be clients. +type ClientProtocol interface { + io.Closer + + // Dispense dispenses a new instance of the plugin with the given name. + Dispense(string) (interface{}, error) + + // Ping checks that the client connection is still healthy. + Ping() error +} diff --git a/vendor/github.com/hashicorp/go-plugin/rpc_client.go b/vendor/github.com/hashicorp/go-plugin/rpc_client.go new file mode 100644 index 00000000..f30a4b1d --- /dev/null +++ b/vendor/github.com/hashicorp/go-plugin/rpc_client.go @@ -0,0 +1,170 @@ +package plugin + +import ( + "crypto/tls" + "fmt" + "io" + "net" + "net/rpc" + + "github.com/hashicorp/yamux" +) + +// RPCClient connects to an RPCServer over net/rpc to dispense plugin types. +type RPCClient struct { + broker *MuxBroker + control *rpc.Client + plugins map[string]Plugin + + // These are the streams used for the various stdout/err overrides + stdout, stderr net.Conn +} + +// newRPCClient creates a new RPCClient. The Client argument is expected +// to be successfully started already with a lock held. +func newRPCClient(c *Client) (*RPCClient, error) { + // Connect to the client + conn, err := net.Dial(c.address.Network(), c.address.String()) + if err != nil { + return nil, err + } + if tcpConn, ok := conn.(*net.TCPConn); ok { + // Make sure to set keep alive so that the connection doesn't die + tcpConn.SetKeepAlive(true) + } + + if c.config.TLSConfig != nil { + conn = tls.Client(conn, c.config.TLSConfig) + } + + // Create the actual RPC client + result, err := NewRPCClient(conn, c.config.Plugins) + if err != nil { + conn.Close() + return nil, err + } + + // Begin the stream syncing so that stdin, out, err work properly + err = result.SyncStreams( + c.config.SyncStdout, + c.config.SyncStderr) + if err != nil { + result.Close() + return nil, err + } + + return result, nil +} + +// NewRPCClient creates a client from an already-open connection-like value. +// Dial is typically used instead. +func NewRPCClient(conn io.ReadWriteCloser, plugins map[string]Plugin) (*RPCClient, error) { + // Create the yamux client so we can multiplex + mux, err := yamux.Client(conn, nil) + if err != nil { + conn.Close() + return nil, err + } + + // Connect to the control stream. + control, err := mux.Open() + if err != nil { + mux.Close() + return nil, err + } + + // Connect stdout, stderr streams + stdstream := make([]net.Conn, 2) + for i, _ := range stdstream { + stdstream[i], err = mux.Open() + if err != nil { + mux.Close() + return nil, err + } + } + + // Create the broker and start it up + broker := newMuxBroker(mux) + go broker.Run() + + // Build the client using our broker and control channel. + return &RPCClient{ + broker: broker, + control: rpc.NewClient(control), + plugins: plugins, + stdout: stdstream[0], + stderr: stdstream[1], + }, nil +} + +// SyncStreams should be called to enable syncing of stdout, +// stderr with the plugin. +// +// This will return immediately and the syncing will continue to happen +// in the background. You do not need to launch this in a goroutine itself. +// +// This should never be called multiple times. +func (c *RPCClient) SyncStreams(stdout io.Writer, stderr io.Writer) error { + go copyStream("stdout", stdout, c.stdout) + go copyStream("stderr", stderr, c.stderr) + return nil +} + +// Close closes the connection. The client is no longer usable after this +// is called. +func (c *RPCClient) Close() error { + // Call the control channel and ask it to gracefully exit. If this + // errors, then we save it so that we always return an error but we + // want to try to close the other channels anyways. + var empty struct{} + returnErr := c.control.Call("Control.Quit", true, &empty) + + // Close the other streams we have + if err := c.control.Close(); err != nil { + return err + } + if err := c.stdout.Close(); err != nil { + return err + } + if err := c.stderr.Close(); err != nil { + return err + } + if err := c.broker.Close(); err != nil { + return err + } + + // Return back the error we got from Control.Quit. This is very important + // since we MUST return non-nil error if this fails so that Client.Kill + // will properly try a process.Kill. + return returnErr +} + +func (c *RPCClient) Dispense(name string) (interface{}, error) { + p, ok := c.plugins[name] + if !ok { + return nil, fmt.Errorf("unknown plugin type: %s", name) + } + + var id uint32 + if err := c.control.Call( + "Dispenser.Dispense", name, &id); err != nil { + return nil, err + } + + conn, err := c.broker.Dial(id) + if err != nil { + return nil, err + } + + return p.Client(c.broker, rpc.NewClient(conn)) +} + +// Ping pings the connection to ensure it is still alive. +// +// The error from the RPC call is returned exactly if you want to inspect +// it for further error analysis. Any error returned from here would indicate +// that the connection to the plugin is not healthy. +func (c *RPCClient) Ping() error { + var empty struct{} + return c.control.Call("Control.Ping", true, &empty) +} diff --git a/vendor/github.com/hashicorp/go-plugin/rpc_client_test.go b/vendor/github.com/hashicorp/go-plugin/rpc_client_test.go new file mode 100644 index 00000000..29302c3e --- /dev/null +++ b/vendor/github.com/hashicorp/go-plugin/rpc_client_test.go @@ -0,0 +1,115 @@ +package plugin + +import ( + "bytes" + "io" + "os" + "sync" + "testing" + "time" + + hclog "github.com/hashicorp/go-hclog" +) + +func TestClient_App(t *testing.T) { + pluginLogger := hclog.New(&hclog.LoggerOptions{ + Level: hclog.Trace, + Output: os.Stderr, + JSONFormat: true, + }) + + testPlugin := &testInterfaceImpl{ + logger: pluginLogger, + } + + client, _ := TestPluginRPCConn(t, map[string]Plugin{ + "test": &testInterfacePlugin{Impl: testPlugin}, + }, nil) + defer client.Close() + + raw, err := client.Dispense("test") + if err != nil { + t.Fatalf("err: %s", err) + } + + impl, ok := raw.(testInterface) + if !ok { + t.Fatalf("bad: %#v", raw) + } + + result := impl.Double(21) + if result != 42 { + t.Fatalf("bad: %#v", result) + } +} + +func TestClient_syncStreams(t *testing.T) { + // Create streams for the server that we can talk to + stdout_r, stdout_w := io.Pipe() + stderr_r, stderr_w := io.Pipe() + + client, _ := TestPluginRPCConn(t, map[string]Plugin{}, &TestOptions{ + ServerStdout: stdout_r, + ServerStderr: stderr_r, + }) + + // Start the data copying + var stdout_out, stderr_out safeBuffer + stdout := &safeBuffer{ + b: bytes.NewBufferString("stdouttest"), + } + stderr := &safeBuffer{ + b: bytes.NewBufferString("stderrtest"), + } + go client.SyncStreams(&stdout_out, &stderr_out) + go io.Copy(stdout_w, stdout) + go io.Copy(stderr_w, stderr) + + // Unfortunately I can't think of a better way to make sure all the + // copies above go through so let's just exit. + time.Sleep(100 * time.Millisecond) + + // Close everything, and lets test the result + client.Close() + stdout_w.Close() + stderr_w.Close() + + if v := stdout_out.String(); v != "stdouttest" { + t.Fatalf("bad: %q", v) + } + if v := stderr_out.String(); v != "stderrtest" { + t.Fatalf("bad: %q", v) + } +} + +type safeBuffer struct { + sync.Mutex + b *bytes.Buffer +} + +func (s *safeBuffer) Write(p []byte) (n int, err error) { + s.Lock() + defer s.Unlock() + if s.b == nil { + s.b = new(bytes.Buffer) + } + return s.b.Write(p) +} + +func (s *safeBuffer) Read(p []byte) (n int, err error) { + s.Lock() + defer s.Unlock() + if s.b == nil { + s.b = new(bytes.Buffer) + } + return s.b.Read(p) +} + +func (s *safeBuffer) String() string { + s.Lock() + defer s.Unlock() + if s.b == nil { + s.b = new(bytes.Buffer) + } + return s.b.String() +} diff --git a/vendor/github.com/hashicorp/go-plugin/rpc_server.go b/vendor/github.com/hashicorp/go-plugin/rpc_server.go new file mode 100644 index 00000000..5bb18dd5 --- /dev/null +++ b/vendor/github.com/hashicorp/go-plugin/rpc_server.go @@ -0,0 +1,197 @@ +package plugin + +import ( + "errors" + "fmt" + "io" + "log" + "net" + "net/rpc" + "sync" + + "github.com/hashicorp/yamux" +) + +// RPCServer listens for network connections and then dispenses interface +// implementations over net/rpc. +// +// After setting the fields below, they shouldn't be read again directly +// from the structure which may be reading/writing them concurrently. +type RPCServer struct { + Plugins map[string]Plugin + + // Stdout, Stderr are what this server will use instead of the + // normal stdin/out/err. This is because due to the multi-process nature + // of our plugin system, we can't use the normal process values so we + // make our own custom one we pipe across. + Stdout io.Reader + Stderr io.Reader + + // DoneCh should be set to a non-nil channel that will be closed + // when the control requests the RPC server to end. + DoneCh chan<- struct{} + + lock sync.Mutex +} + +// ServerProtocol impl. +func (s *RPCServer) Init() error { return nil } + +// ServerProtocol impl. +func (s *RPCServer) Config() string { return "" } + +// ServerProtocol impl. +func (s *RPCServer) Serve(lis net.Listener) { + for { + conn, err := lis.Accept() + if err != nil { + log.Printf("[ERR] plugin: plugin server: %s", err) + return + } + + go s.ServeConn(conn) + } +} + +// ServeConn runs a single connection. +// +// ServeConn blocks, serving the connection until the client hangs up. +func (s *RPCServer) ServeConn(conn io.ReadWriteCloser) { + // First create the yamux server to wrap this connection + mux, err := yamux.Server(conn, nil) + if err != nil { + conn.Close() + log.Printf("[ERR] plugin: error creating yamux server: %s", err) + return + } + + // Accept the control connection + control, err := mux.Accept() + if err != nil { + mux.Close() + if err != io.EOF { + log.Printf("[ERR] plugin: error accepting control connection: %s", err) + } + + return + } + + // Connect the stdstreams (in, out, err) + stdstream := make([]net.Conn, 2) + for i, _ := range stdstream { + stdstream[i], err = mux.Accept() + if err != nil { + mux.Close() + log.Printf("[ERR] plugin: accepting stream %d: %s", i, err) + return + } + } + + // Copy std streams out to the proper place + go copyStream("stdout", stdstream[0], s.Stdout) + go copyStream("stderr", stdstream[1], s.Stderr) + + // Create the broker and start it up + broker := newMuxBroker(mux) + go broker.Run() + + // Use the control connection to build the dispenser and serve the + // connection. + server := rpc.NewServer() + server.RegisterName("Control", &controlServer{ + server: s, + }) + server.RegisterName("Dispenser", &dispenseServer{ + broker: broker, + plugins: s.Plugins, + }) + server.ServeConn(control) +} + +// done is called internally by the control server to trigger the +// doneCh to close which is listened to by the main process to cleanly +// exit. +func (s *RPCServer) done() { + s.lock.Lock() + defer s.lock.Unlock() + + if s.DoneCh != nil { + close(s.DoneCh) + s.DoneCh = nil + } +} + +// dispenseServer dispenses variousinterface implementations for Terraform. +type controlServer struct { + server *RPCServer +} + +// Ping can be called to verify the connection (and likely the binary) +// is still alive to a plugin. +func (c *controlServer) Ping( + null bool, response *struct{}) error { + *response = struct{}{} + return nil +} + +func (c *controlServer) Quit( + null bool, response *struct{}) error { + // End the server + c.server.done() + + // Always return true + *response = struct{}{} + + return nil +} + +// dispenseServer dispenses variousinterface implementations for Terraform. +type dispenseServer struct { + broker *MuxBroker + plugins map[string]Plugin +} + +func (d *dispenseServer) Dispense( + name string, response *uint32) error { + // Find the function to create this implementation + p, ok := d.plugins[name] + if !ok { + return fmt.Errorf("unknown plugin type: %s", name) + } + + // Create the implementation first so we know if there is an error. + impl, err := p.Server(d.broker) + if err != nil { + // We turn the error into an errors error so that it works across RPC + return errors.New(err.Error()) + } + + // Reserve an ID for our implementation + id := d.broker.NextId() + *response = id + + // Run the rest in a goroutine since it can only happen once this RPC + // call returns. We wait for a connection for the plugin implementation + // and serve it. + go func() { + conn, err := d.broker.Accept(id) + if err != nil { + log.Printf("[ERR] go-plugin: plugin dispense error: %s: %s", name, err) + return + } + + serve(conn, "Plugin", impl) + }() + + return nil +} + +func serve(conn io.ReadWriteCloser, name string, v interface{}) { + server := rpc.NewServer() + if err := server.RegisterName(name, v); err != nil { + log.Printf("[ERR] go-plugin: plugin dispense error: %s", err) + return + } + + server.ServeConn(conn) +} diff --git a/vendor/github.com/hashicorp/go-plugin/server.go b/vendor/github.com/hashicorp/go-plugin/server.go new file mode 100644 index 00000000..1e808b99 --- /dev/null +++ b/vendor/github.com/hashicorp/go-plugin/server.go @@ -0,0 +1,317 @@ +package plugin + +import ( + "crypto/tls" + "encoding/base64" + "errors" + "fmt" + "io/ioutil" + "log" + "net" + "os" + "os/signal" + "runtime" + "strconv" + "sync/atomic" + + "github.com/hashicorp/go-hclog" + + "google.golang.org/grpc" +) + +// CoreProtocolVersion is the ProtocolVersion of the plugin system itself. +// We will increment this whenever we change any protocol behavior. This +// will invalidate any prior plugins but will at least allow us to iterate +// on the core in a safe way. We will do our best to do this very +// infrequently. +const CoreProtocolVersion = 1 + +// HandshakeConfig is the configuration used by client and servers to +// handshake before starting a plugin connection. This is embedded by +// both ServeConfig and ClientConfig. +// +// In practice, the plugin host creates a HandshakeConfig that is exported +// and plugins then can easily consume it. +type HandshakeConfig struct { + // ProtocolVersion is the version that clients must match on to + // agree they can communicate. This should match the ProtocolVersion + // set on ClientConfig when using a plugin. + ProtocolVersion uint + + // MagicCookieKey and value are used as a very basic verification + // that a plugin is intended to be launched. This is not a security + // measure, just a UX feature. If the magic cookie doesn't match, + // we show human-friendly output. + MagicCookieKey string + MagicCookieValue string +} + +// ServeConfig configures what sorts of plugins are served. +type ServeConfig struct { + // HandshakeConfig is the configuration that must match clients. + HandshakeConfig + + // TLSProvider is a function that returns a configured tls.Config. + TLSProvider func() (*tls.Config, error) + + // Plugins are the plugins that are served. + Plugins map[string]Plugin + + // GRPCServer should be non-nil to enable serving the plugins over + // gRPC. This is a function to create the server when needed with the + // given server options. The server options populated by go-plugin will + // be for TLS if set. You may modify the input slice. + // + // Note that the grpc.Server will automatically be registered with + // the gRPC health checking service. This is not optional since go-plugin + // relies on this to implement Ping(). + GRPCServer func([]grpc.ServerOption) *grpc.Server + + // Logger is used to pass a logger into the server. If none is provided the + // server will create a default logger. + Logger hclog.Logger +} + +// Protocol returns the protocol that this server should speak. +func (c *ServeConfig) Protocol() Protocol { + result := ProtocolNetRPC + if c.GRPCServer != nil { + result = ProtocolGRPC + } + + return result +} + +// Serve serves the plugins given by ServeConfig. +// +// Serve doesn't return until the plugin is done being executed. Any +// errors will be outputted to os.Stderr. +// +// This is the method that plugins should call in their main() functions. +func Serve(opts *ServeConfig) { + // Validate the handshake config + if opts.MagicCookieKey == "" || opts.MagicCookieValue == "" { + fmt.Fprintf(os.Stderr, + "Misconfigured ServeConfig given to serve this plugin: no magic cookie\n"+ + "key or value was set. Please notify the plugin author and report\n"+ + "this as a bug.\n") + os.Exit(1) + } + + // First check the cookie + if os.Getenv(opts.MagicCookieKey) != opts.MagicCookieValue { + fmt.Fprintf(os.Stderr, + "This binary is a plugin. These are not meant to be executed directly.\n"+ + "Please execute the program that consumes these plugins, which will\n"+ + "load any plugins automatically\n") + os.Exit(1) + } + + // Logging goes to the original stderr + log.SetOutput(os.Stderr) + + logger := opts.Logger + if logger == nil { + // internal logger to os.Stderr + logger = hclog.New(&hclog.LoggerOptions{ + Level: hclog.Trace, + Output: os.Stderr, + JSONFormat: true, + }) + } + + // Create our new stdout, stderr files. These will override our built-in + // stdout/stderr so that it works across the stream boundary. + stdout_r, stdout_w, err := os.Pipe() + if err != nil { + fmt.Fprintf(os.Stderr, "Error preparing plugin: %s\n", err) + os.Exit(1) + } + stderr_r, stderr_w, err := os.Pipe() + if err != nil { + fmt.Fprintf(os.Stderr, "Error preparing plugin: %s\n", err) + os.Exit(1) + } + + // Register a listener so we can accept a connection + listener, err := serverListener() + if err != nil { + logger.Error("plugin init error", "error", err) + return + } + + // Close the listener on return. We wrap this in a func() on purpose + // because the "listener" reference may change to TLS. + defer func() { + listener.Close() + }() + + var tlsConfig *tls.Config + if opts.TLSProvider != nil { + tlsConfig, err = opts.TLSProvider() + if err != nil { + logger.Error("plugin tls init", "error", err) + return + } + } + + // Create the channel to tell us when we're done + doneCh := make(chan struct{}) + + // Build the server type + var server ServerProtocol + switch opts.Protocol() { + case ProtocolNetRPC: + // If we have a TLS configuration then we wrap the listener + // ourselves and do it at that level. + if tlsConfig != nil { + listener = tls.NewListener(listener, tlsConfig) + } + + // Create the RPC server to dispense + server = &RPCServer{ + Plugins: opts.Plugins, + Stdout: stdout_r, + Stderr: stderr_r, + DoneCh: doneCh, + } + + case ProtocolGRPC: + // Create the gRPC server + server = &GRPCServer{ + Plugins: opts.Plugins, + Server: opts.GRPCServer, + TLS: tlsConfig, + Stdout: stdout_r, + Stderr: stderr_r, + DoneCh: doneCh, + } + + default: + panic("unknown server protocol: " + opts.Protocol()) + } + + // Initialize the servers + if err := server.Init(); err != nil { + logger.Error("protocol init", "error", err) + return + } + + // Build the extra configuration + extra := "" + if v := server.Config(); v != "" { + extra = base64.StdEncoding.EncodeToString([]byte(v)) + } + if extra != "" { + extra = "|" + extra + } + + logger.Debug("plugin address", "network", listener.Addr().Network(), "address", listener.Addr().String()) + + // Output the address and service name to stdout so that core can bring it up. + fmt.Printf("%d|%d|%s|%s|%s%s\n", + CoreProtocolVersion, + opts.ProtocolVersion, + listener.Addr().Network(), + listener.Addr().String(), + opts.Protocol(), + extra) + os.Stdout.Sync() + + // Eat the interrupts + ch := make(chan os.Signal, 1) + signal.Notify(ch, os.Interrupt) + go func() { + var count int32 = 0 + for { + <-ch + newCount := atomic.AddInt32(&count, 1) + logger.Debug("plugin received interrupt signal, ignoring", "count", newCount) + } + }() + + // Set our new out, err + os.Stdout = stdout_w + os.Stderr = stderr_w + + // Accept connections and wait for completion + go server.Serve(listener) + <-doneCh +} + +func serverListener() (net.Listener, error) { + if runtime.GOOS == "windows" { + return serverListener_tcp() + } + + return serverListener_unix() +} + +func serverListener_tcp() (net.Listener, error) { + minPort, err := strconv.ParseInt(os.Getenv("PLUGIN_MIN_PORT"), 10, 32) + if err != nil { + return nil, err + } + + maxPort, err := strconv.ParseInt(os.Getenv("PLUGIN_MAX_PORT"), 10, 32) + if err != nil { + return nil, err + } + + for port := minPort; port <= maxPort; port++ { + address := fmt.Sprintf("127.0.0.1:%d", port) + listener, err := net.Listen("tcp", address) + if err == nil { + return listener, nil + } + } + + return nil, errors.New("Couldn't bind plugin TCP listener") +} + +func serverListener_unix() (net.Listener, error) { + tf, err := ioutil.TempFile("", "plugin") + if err != nil { + return nil, err + } + path := tf.Name() + + // Close the file and remove it because it has to not exist for + // the domain socket. + if err := tf.Close(); err != nil { + return nil, err + } + if err := os.Remove(path); err != nil { + return nil, err + } + + l, err := net.Listen("unix", path) + if err != nil { + return nil, err + } + + // Wrap the listener in rmListener so that the Unix domain socket file + // is removed on close. + return &rmListener{ + Listener: l, + Path: path, + }, nil +} + +// rmListener is an implementation of net.Listener that forwards most +// calls to the listener but also removes a file as part of the close. We +// use this to cleanup the unix domain socket on close. +type rmListener struct { + net.Listener + Path string +} + +func (l *rmListener) Close() error { + // Close the listener itself + if err := l.Listener.Close(); err != nil { + return err + } + + // Remove the file + return os.Remove(l.Path) +} diff --git a/vendor/github.com/hashicorp/go-plugin/server_mux.go b/vendor/github.com/hashicorp/go-plugin/server_mux.go new file mode 100644 index 00000000..033079ea --- /dev/null +++ b/vendor/github.com/hashicorp/go-plugin/server_mux.go @@ -0,0 +1,31 @@ +package plugin + +import ( + "fmt" + "os" +) + +// ServeMuxMap is the type that is used to configure ServeMux +type ServeMuxMap map[string]*ServeConfig + +// ServeMux is like Serve, but serves multiple types of plugins determined +// by the argument given on the command-line. +// +// This command doesn't return until the plugin is done being executed. Any +// errors are logged or output to stderr. +func ServeMux(m ServeMuxMap) { + if len(os.Args) != 2 { + fmt.Fprintf(os.Stderr, + "Invoked improperly. This is an internal command that shouldn't\n"+ + "be manually invoked.\n") + os.Exit(1) + } + + opts, ok := m[os.Args[1]] + if !ok { + fmt.Fprintf(os.Stderr, "Unknown plugin: %s\n", os.Args[1]) + os.Exit(1) + } + + Serve(opts) +} diff --git a/vendor/github.com/hashicorp/go-plugin/server_test.go b/vendor/github.com/hashicorp/go-plugin/server_test.go new file mode 100644 index 00000000..7faa9211 --- /dev/null +++ b/vendor/github.com/hashicorp/go-plugin/server_test.go @@ -0,0 +1,44 @@ +package plugin + +import ( + "io/ioutil" + "net" + "os" + "testing" +) + +func TestRmListener_impl(t *testing.T) { + var _ net.Listener = new(rmListener) +} + +func TestRmListener(t *testing.T) { + l, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + t.Fatalf("err: %s", err) + } + + tf, err := ioutil.TempFile("", "plugin") + if err != nil { + t.Fatalf("err: %s", err) + } + path := tf.Name() + + // Close the file + if err := tf.Close(); err != nil { + t.Fatalf("err: %s", err) + } + + // Create the listener and test close + rmL := &rmListener{ + Listener: l, + Path: path, + } + if err := rmL.Close(); err != nil { + t.Fatalf("err: %s", err) + } + + // File should be goe + if _, err := os.Stat(path); err == nil || !os.IsNotExist(err) { + t.Fatalf("err: %s", err) + } +} diff --git a/vendor/github.com/hashicorp/go-plugin/stream.go b/vendor/github.com/hashicorp/go-plugin/stream.go new file mode 100644 index 00000000..1d547aaa --- /dev/null +++ b/vendor/github.com/hashicorp/go-plugin/stream.go @@ -0,0 +1,18 @@ +package plugin + +import ( + "io" + "log" +) + +func copyStream(name string, dst io.Writer, src io.Reader) { + if src == nil { + panic(name + ": src is nil") + } + if dst == nil { + panic(name + ": dst is nil") + } + if _, err := io.Copy(dst, src); err != nil && err != io.EOF { + log.Printf("[ERR] plugin: stream copy '%s' error: %s", name, err) + } +} diff --git a/vendor/github.com/hashicorp/go-plugin/test/grpc/gen.go b/vendor/github.com/hashicorp/go-plugin/test/grpc/gen.go new file mode 100644 index 00000000..c14618a7 --- /dev/null +++ b/vendor/github.com/hashicorp/go-plugin/test/grpc/gen.go @@ -0,0 +1,3 @@ +package grpctest + +//go:generate protoc -I ./ ./test.proto --go_out=plugins=grpc:. diff --git a/vendor/github.com/hashicorp/go-plugin/test/grpc/test.pb.go b/vendor/github.com/hashicorp/go-plugin/test/grpc/test.pb.go new file mode 100644 index 00000000..602636aa --- /dev/null +++ b/vendor/github.com/hashicorp/go-plugin/test/grpc/test.pb.go @@ -0,0 +1,562 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// source: test.proto + +/* +Package grpctest is a generated protocol buffer package. + +It is generated from these files: + test.proto + +It has these top-level messages: + TestRequest + TestResponse + PrintKVRequest + PrintKVResponse + BidirectionalRequest + BidirectionalResponse + PingRequest + PongResponse +*/ +package grpctest + +import proto "github.com/golang/protobuf/proto" +import fmt "fmt" +import math "math" + +import ( + context "golang.org/x/net/context" + grpc "google.golang.org/grpc" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package + +type TestRequest struct { + Input int32 `protobuf:"varint,1,opt,name=Input" json:"Input,omitempty"` +} + +func (m *TestRequest) Reset() { *m = TestRequest{} } +func (m *TestRequest) String() string { return proto.CompactTextString(m) } +func (*TestRequest) ProtoMessage() {} +func (*TestRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} } + +func (m *TestRequest) GetInput() int32 { + if m != nil { + return m.Input + } + return 0 +} + +type TestResponse struct { + Output int32 `protobuf:"varint,2,opt,name=Output" json:"Output,omitempty"` +} + +func (m *TestResponse) Reset() { *m = TestResponse{} } +func (m *TestResponse) String() string { return proto.CompactTextString(m) } +func (*TestResponse) ProtoMessage() {} +func (*TestResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{1} } + +func (m *TestResponse) GetOutput() int32 { + if m != nil { + return m.Output + } + return 0 +} + +type PrintKVRequest struct { + Key string `protobuf:"bytes,1,opt,name=Key" json:"Key,omitempty"` + // Types that are valid to be assigned to Value: + // *PrintKVRequest_ValueString + // *PrintKVRequest_ValueInt + Value isPrintKVRequest_Value `protobuf_oneof:"Value"` +} + +func (m *PrintKVRequest) Reset() { *m = PrintKVRequest{} } +func (m *PrintKVRequest) String() string { return proto.CompactTextString(m) } +func (*PrintKVRequest) ProtoMessage() {} +func (*PrintKVRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{2} } + +type isPrintKVRequest_Value interface{ isPrintKVRequest_Value() } + +type PrintKVRequest_ValueString struct { + ValueString string `protobuf:"bytes,2,opt,name=ValueString,oneof"` +} +type PrintKVRequest_ValueInt struct { + ValueInt int32 `protobuf:"varint,3,opt,name=ValueInt,oneof"` +} + +func (*PrintKVRequest_ValueString) isPrintKVRequest_Value() {} +func (*PrintKVRequest_ValueInt) isPrintKVRequest_Value() {} + +func (m *PrintKVRequest) GetValue() isPrintKVRequest_Value { + if m != nil { + return m.Value + } + return nil +} + +func (m *PrintKVRequest) GetKey() string { + if m != nil { + return m.Key + } + return "" +} + +func (m *PrintKVRequest) GetValueString() string { + if x, ok := m.GetValue().(*PrintKVRequest_ValueString); ok { + return x.ValueString + } + return "" +} + +func (m *PrintKVRequest) GetValueInt() int32 { + if x, ok := m.GetValue().(*PrintKVRequest_ValueInt); ok { + return x.ValueInt + } + return 0 +} + +// XXX_OneofFuncs is for the internal use of the proto package. +func (*PrintKVRequest) XXX_OneofFuncs() (func(msg proto.Message, b *proto.Buffer) error, func(msg proto.Message, tag, wire int, b *proto.Buffer) (bool, error), func(msg proto.Message) (n int), []interface{}) { + return _PrintKVRequest_OneofMarshaler, _PrintKVRequest_OneofUnmarshaler, _PrintKVRequest_OneofSizer, []interface{}{ + (*PrintKVRequest_ValueString)(nil), + (*PrintKVRequest_ValueInt)(nil), + } +} + +func _PrintKVRequest_OneofMarshaler(msg proto.Message, b *proto.Buffer) error { + m := msg.(*PrintKVRequest) + // Value + switch x := m.Value.(type) { + case *PrintKVRequest_ValueString: + b.EncodeVarint(2<<3 | proto.WireBytes) + b.EncodeStringBytes(x.ValueString) + case *PrintKVRequest_ValueInt: + b.EncodeVarint(3<<3 | proto.WireVarint) + b.EncodeVarint(uint64(x.ValueInt)) + case nil: + default: + return fmt.Errorf("PrintKVRequest.Value has unexpected type %T", x) + } + return nil +} + +func _PrintKVRequest_OneofUnmarshaler(msg proto.Message, tag, wire int, b *proto.Buffer) (bool, error) { + m := msg.(*PrintKVRequest) + switch tag { + case 2: // Value.ValueString + if wire != proto.WireBytes { + return true, proto.ErrInternalBadWireType + } + x, err := b.DecodeStringBytes() + m.Value = &PrintKVRequest_ValueString{x} + return true, err + case 3: // Value.ValueInt + if wire != proto.WireVarint { + return true, proto.ErrInternalBadWireType + } + x, err := b.DecodeVarint() + m.Value = &PrintKVRequest_ValueInt{int32(x)} + return true, err + default: + return false, nil + } +} + +func _PrintKVRequest_OneofSizer(msg proto.Message) (n int) { + m := msg.(*PrintKVRequest) + // Value + switch x := m.Value.(type) { + case *PrintKVRequest_ValueString: + n += proto.SizeVarint(2<<3 | proto.WireBytes) + n += proto.SizeVarint(uint64(len(x.ValueString))) + n += len(x.ValueString) + case *PrintKVRequest_ValueInt: + n += proto.SizeVarint(3<<3 | proto.WireVarint) + n += proto.SizeVarint(uint64(x.ValueInt)) + case nil: + default: + panic(fmt.Sprintf("proto: unexpected type %T in oneof", x)) + } + return n +} + +type PrintKVResponse struct { +} + +func (m *PrintKVResponse) Reset() { *m = PrintKVResponse{} } +func (m *PrintKVResponse) String() string { return proto.CompactTextString(m) } +func (*PrintKVResponse) ProtoMessage() {} +func (*PrintKVResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{3} } + +type BidirectionalRequest struct { + Id uint32 `protobuf:"varint,1,opt,name=id" json:"id,omitempty"` +} + +func (m *BidirectionalRequest) Reset() { *m = BidirectionalRequest{} } +func (m *BidirectionalRequest) String() string { return proto.CompactTextString(m) } +func (*BidirectionalRequest) ProtoMessage() {} +func (*BidirectionalRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{4} } + +func (m *BidirectionalRequest) GetId() uint32 { + if m != nil { + return m.Id + } + return 0 +} + +type BidirectionalResponse struct { + Id uint32 `protobuf:"varint,1,opt,name=id" json:"id,omitempty"` +} + +func (m *BidirectionalResponse) Reset() { *m = BidirectionalResponse{} } +func (m *BidirectionalResponse) String() string { return proto.CompactTextString(m) } +func (*BidirectionalResponse) ProtoMessage() {} +func (*BidirectionalResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{5} } + +func (m *BidirectionalResponse) GetId() uint32 { + if m != nil { + return m.Id + } + return 0 +} + +type PingRequest struct { +} + +func (m *PingRequest) Reset() { *m = PingRequest{} } +func (m *PingRequest) String() string { return proto.CompactTextString(m) } +func (*PingRequest) ProtoMessage() {} +func (*PingRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{6} } + +type PongResponse struct { + Msg string `protobuf:"bytes,1,opt,name=msg" json:"msg,omitempty"` +} + +func (m *PongResponse) Reset() { *m = PongResponse{} } +func (m *PongResponse) String() string { return proto.CompactTextString(m) } +func (*PongResponse) ProtoMessage() {} +func (*PongResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{7} } + +func (m *PongResponse) GetMsg() string { + if m != nil { + return m.Msg + } + return "" +} + +func init() { + proto.RegisterType((*TestRequest)(nil), "grpctest.TestRequest") + proto.RegisterType((*TestResponse)(nil), "grpctest.TestResponse") + proto.RegisterType((*PrintKVRequest)(nil), "grpctest.PrintKVRequest") + proto.RegisterType((*PrintKVResponse)(nil), "grpctest.PrintKVResponse") + proto.RegisterType((*BidirectionalRequest)(nil), "grpctest.BidirectionalRequest") + proto.RegisterType((*BidirectionalResponse)(nil), "grpctest.BidirectionalResponse") + proto.RegisterType((*PingRequest)(nil), "grpctest.PingRequest") + proto.RegisterType((*PongResponse)(nil), "grpctest.PongResponse") +} + +// Reference imports to suppress errors if they are not otherwise used. +var _ context.Context +var _ grpc.ClientConn + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +const _ = grpc.SupportPackageIsVersion4 + +// Client API for Test service + +type TestClient interface { + Double(ctx context.Context, in *TestRequest, opts ...grpc.CallOption) (*TestResponse, error) + PrintKV(ctx context.Context, in *PrintKVRequest, opts ...grpc.CallOption) (*PrintKVResponse, error) + Bidirectional(ctx context.Context, in *BidirectionalRequest, opts ...grpc.CallOption) (*BidirectionalResponse, error) + Stream(ctx context.Context, opts ...grpc.CallOption) (Test_StreamClient, error) +} + +type testClient struct { + cc *grpc.ClientConn +} + +func NewTestClient(cc *grpc.ClientConn) TestClient { + return &testClient{cc} +} + +func (c *testClient) Double(ctx context.Context, in *TestRequest, opts ...grpc.CallOption) (*TestResponse, error) { + out := new(TestResponse) + err := grpc.Invoke(ctx, "/grpctest.Test/Double", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *testClient) PrintKV(ctx context.Context, in *PrintKVRequest, opts ...grpc.CallOption) (*PrintKVResponse, error) { + out := new(PrintKVResponse) + err := grpc.Invoke(ctx, "/grpctest.Test/PrintKV", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *testClient) Bidirectional(ctx context.Context, in *BidirectionalRequest, opts ...grpc.CallOption) (*BidirectionalResponse, error) { + out := new(BidirectionalResponse) + err := grpc.Invoke(ctx, "/grpctest.Test/Bidirectional", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *testClient) Stream(ctx context.Context, opts ...grpc.CallOption) (Test_StreamClient, error) { + stream, err := grpc.NewClientStream(ctx, &_Test_serviceDesc.Streams[0], c.cc, "/grpctest.Test/Stream", opts...) + if err != nil { + return nil, err + } + x := &testStreamClient{stream} + return x, nil +} + +type Test_StreamClient interface { + Send(*TestRequest) error + Recv() (*TestResponse, error) + grpc.ClientStream +} + +type testStreamClient struct { + grpc.ClientStream +} + +func (x *testStreamClient) Send(m *TestRequest) error { + return x.ClientStream.SendMsg(m) +} + +func (x *testStreamClient) Recv() (*TestResponse, error) { + m := new(TestResponse) + if err := x.ClientStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + +// Server API for Test service + +type TestServer interface { + Double(context.Context, *TestRequest) (*TestResponse, error) + PrintKV(context.Context, *PrintKVRequest) (*PrintKVResponse, error) + Bidirectional(context.Context, *BidirectionalRequest) (*BidirectionalResponse, error) + Stream(Test_StreamServer) error +} + +func RegisterTestServer(s *grpc.Server, srv TestServer) { + s.RegisterService(&_Test_serviceDesc, srv) +} + +func _Test_Double_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(TestRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(TestServer).Double(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/grpctest.Test/Double", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(TestServer).Double(ctx, req.(*TestRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Test_PrintKV_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(PrintKVRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(TestServer).PrintKV(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/grpctest.Test/PrintKV", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(TestServer).PrintKV(ctx, req.(*PrintKVRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Test_Bidirectional_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(BidirectionalRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(TestServer).Bidirectional(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/grpctest.Test/Bidirectional", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(TestServer).Bidirectional(ctx, req.(*BidirectionalRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Test_Stream_Handler(srv interface{}, stream grpc.ServerStream) error { + return srv.(TestServer).Stream(&testStreamServer{stream}) +} + +type Test_StreamServer interface { + Send(*TestResponse) error + Recv() (*TestRequest, error) + grpc.ServerStream +} + +type testStreamServer struct { + grpc.ServerStream +} + +func (x *testStreamServer) Send(m *TestResponse) error { + return x.ServerStream.SendMsg(m) +} + +func (x *testStreamServer) Recv() (*TestRequest, error) { + m := new(TestRequest) + if err := x.ServerStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + +var _Test_serviceDesc = grpc.ServiceDesc{ + ServiceName: "grpctest.Test", + HandlerType: (*TestServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "Double", + Handler: _Test_Double_Handler, + }, + { + MethodName: "PrintKV", + Handler: _Test_PrintKV_Handler, + }, + { + MethodName: "Bidirectional", + Handler: _Test_Bidirectional_Handler, + }, + }, + Streams: []grpc.StreamDesc{ + { + StreamName: "Stream", + Handler: _Test_Stream_Handler, + ServerStreams: true, + ClientStreams: true, + }, + }, + Metadata: "test.proto", +} + +// Client API for PingPong service + +type PingPongClient interface { + Ping(ctx context.Context, in *PingRequest, opts ...grpc.CallOption) (*PongResponse, error) +} + +type pingPongClient struct { + cc *grpc.ClientConn +} + +func NewPingPongClient(cc *grpc.ClientConn) PingPongClient { + return &pingPongClient{cc} +} + +func (c *pingPongClient) Ping(ctx context.Context, in *PingRequest, opts ...grpc.CallOption) (*PongResponse, error) { + out := new(PongResponse) + err := grpc.Invoke(ctx, "/grpctest.PingPong/Ping", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// Server API for PingPong service + +type PingPongServer interface { + Ping(context.Context, *PingRequest) (*PongResponse, error) +} + +func RegisterPingPongServer(s *grpc.Server, srv PingPongServer) { + s.RegisterService(&_PingPong_serviceDesc, srv) +} + +func _PingPong_Ping_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(PingRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(PingPongServer).Ping(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/grpctest.PingPong/Ping", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(PingPongServer).Ping(ctx, req.(*PingRequest)) + } + return interceptor(ctx, in, info, handler) +} + +var _PingPong_serviceDesc = grpc.ServiceDesc{ + ServiceName: "grpctest.PingPong", + HandlerType: (*PingPongServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "Ping", + Handler: _PingPong_Ping_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "test.proto", +} + +func init() { proto.RegisterFile("test.proto", fileDescriptor0) } + +var fileDescriptor0 = []byte{ + // 355 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x92, 0xcd, 0x4e, 0xc2, 0x40, + 0x14, 0x85, 0xdb, 0x02, 0x05, 0x2e, 0x3f, 0xe2, 0x04, 0x08, 0x12, 0xa3, 0x64, 0x4c, 0x90, 0x15, + 0x31, 0xb8, 0x30, 0x2e, 0x4c, 0x0c, 0xba, 0x80, 0xb0, 0x90, 0x0c, 0x86, 0x3d, 0x3f, 0x93, 0x66, + 0x12, 0x3a, 0xad, 0x9d, 0xe9, 0xc2, 0x17, 0xf1, 0x79, 0xcd, 0x0c, 0x6d, 0x19, 0x08, 0x2e, 0xdc, + 0xdd, 0x73, 0x7b, 0x72, 0xe6, 0x9e, 0x2f, 0x05, 0x90, 0x54, 0xc8, 0x61, 0x18, 0x05, 0x32, 0x40, + 0x25, 0x2f, 0x0a, 0x37, 0x4a, 0xe3, 0x3b, 0xa8, 0x7c, 0x52, 0x21, 0x09, 0xfd, 0x8a, 0xa9, 0x90, + 0xa8, 0x09, 0x85, 0x29, 0x0f, 0x63, 0xd9, 0xb1, 0x7b, 0xf6, 0xa0, 0x40, 0xf6, 0x02, 0xf7, 0xa1, + 0xba, 0x37, 0x89, 0x30, 0xe0, 0x82, 0xa2, 0x36, 0xb8, 0x1f, 0xb1, 0x54, 0x36, 0x47, 0xdb, 0x12, + 0x85, 0x7d, 0xa8, 0xcf, 0x23, 0xc6, 0xe5, 0x6c, 0x99, 0xe6, 0x35, 0x20, 0x37, 0xa3, 0xdf, 0x3a, + 0xad, 0x4c, 0xd4, 0x88, 0x30, 0x54, 0x96, 0xab, 0x5d, 0x4c, 0x17, 0x32, 0x62, 0xdc, 0xd3, 0x01, + 0xe5, 0x89, 0x45, 0xcc, 0x25, 0xba, 0x86, 0x92, 0x96, 0x53, 0x2e, 0x3b, 0x39, 0xf5, 0xc2, 0xc4, + 0x22, 0xd9, 0x66, 0x5c, 0x84, 0x82, 0x9e, 0xf1, 0x25, 0x5c, 0x64, 0xcf, 0xed, 0x2f, 0xc3, 0x7d, + 0x68, 0x8e, 0xd9, 0x96, 0x45, 0x74, 0x23, 0x59, 0xc0, 0x57, 0xbb, 0xf4, 0x8e, 0x3a, 0x38, 0x6c, + 0xab, 0xcf, 0xa8, 0x11, 0x87, 0x6d, 0xf1, 0x3d, 0xb4, 0x4e, 0x7c, 0x49, 0xb5, 0x53, 0x63, 0x0d, + 0x2a, 0x73, 0xc6, 0xbd, 0x24, 0x07, 0xf7, 0xa0, 0x3a, 0x0f, 0x94, 0x4c, 0xec, 0x0d, 0xc8, 0xf9, + 0xc2, 0x4b, 0xfb, 0xf9, 0xc2, 0x1b, 0xfd, 0x38, 0x90, 0x57, 0xb0, 0xd0, 0x33, 0xb8, 0xef, 0x41, + 0xbc, 0xde, 0x51, 0xd4, 0x1a, 0xa6, 0xb8, 0x87, 0x06, 0xeb, 0x6e, 0xfb, 0x74, 0x9d, 0x74, 0xb0, + 0xd0, 0x2b, 0x14, 0x93, 0x62, 0xa8, 0x73, 0x30, 0x1d, 0xa3, 0xed, 0x5e, 0x9d, 0xf9, 0x92, 0x25, + 0x10, 0xa8, 0x1d, 0xf5, 0x43, 0x37, 0x07, 0xf7, 0x39, 0x40, 0xdd, 0xdb, 0x3f, 0xbf, 0x67, 0x99, + 0x2f, 0xe0, 0x2e, 0x64, 0x44, 0x57, 0xfe, 0xbf, 0x0b, 0x0d, 0xec, 0x07, 0x7b, 0xf4, 0x06, 0x25, + 0x45, 0x52, 0xe1, 0x43, 0x4f, 0x90, 0x57, 0xb3, 0x19, 0x64, 0x50, 0x36, 0x83, 0x4c, 0xda, 0xd8, + 0x5a, 0xbb, 0xfa, 0xff, 0x7d, 0xfc, 0x0d, 0x00, 0x00, 0xff, 0xff, 0xf0, 0x59, 0x20, 0xc7, 0xcd, + 0x02, 0x00, 0x00, +} diff --git a/vendor/github.com/hashicorp/go-plugin/test/grpc/test.proto b/vendor/github.com/hashicorp/go-plugin/test/grpc/test.proto new file mode 100644 index 00000000..e8470a31 --- /dev/null +++ b/vendor/github.com/hashicorp/go-plugin/test/grpc/test.proto @@ -0,0 +1,48 @@ +syntax = "proto3"; +package grpctest; + +message TestRequest { + int32 Input = 1; +} + +message TestResponse { + int32 Output = 2; +} + +message PrintKVRequest { + string Key = 1; + oneof Value { + string ValueString = 2; + int32 ValueInt = 3; + } +} + +message PrintKVResponse { + +} + +message BidirectionalRequest { + uint32 id = 1; +} + +message BidirectionalResponse { + uint32 id = 1; +} + +service Test { + rpc Double(TestRequest) returns (TestResponse) {} + rpc PrintKV(PrintKVRequest) returns (PrintKVResponse) {} + rpc Bidirectional(BidirectionalRequest) returns (BidirectionalResponse) {} + rpc Stream(stream TestRequest) returns (stream TestResponse) {} +} + +message PingRequest { +} + +message PongResponse { + string msg = 1; +} + +service PingPong { + rpc Ping(PingRequest) returns (PongResponse) {} +} diff --git a/vendor/github.com/hashicorp/go-plugin/testing.go b/vendor/github.com/hashicorp/go-plugin/testing.go new file mode 100644 index 00000000..2f541d96 --- /dev/null +++ b/vendor/github.com/hashicorp/go-plugin/testing.go @@ -0,0 +1,175 @@ +package plugin + +import ( + "bytes" + "context" + "io" + "net" + "net/rpc" + + "github.com/mitchellh/go-testing-interface" + "google.golang.org/grpc" +) + +// TestOptions allows specifying options that can affect the behavior of the +// test functions +type TestOptions struct { + //ServerStdout causes the given value to be used in place of a blank buffer + //for RPCServer's Stdout + ServerStdout io.ReadCloser + + //ServerStderr causes the given value to be used in place of a blank buffer + //for RPCServer's Stderr + ServerStderr io.ReadCloser +} + +// The testing file contains test helpers that you can use outside of +// this package for making it easier to test plugins themselves. + +// TestConn is a helper function for returning a client and server +// net.Conn connected to each other. +func TestConn(t testing.T) (net.Conn, net.Conn) { + // Listen to any local port. This listener will be closed + // after a single connection is established. + l, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + t.Fatalf("err: %s", err) + } + + // Start a goroutine to accept our client connection + var serverConn net.Conn + doneCh := make(chan struct{}) + go func() { + defer close(doneCh) + defer l.Close() + var err error + serverConn, err = l.Accept() + if err != nil { + t.Fatalf("err: %s", err) + } + }() + + // Connect to the server + clientConn, err := net.Dial("tcp", l.Addr().String()) + if err != nil { + t.Fatalf("err: %s", err) + } + + // Wait for the server side to acknowledge it has connected + <-doneCh + + return clientConn, serverConn +} + +// TestRPCConn returns a rpc client and server connected to each other. +func TestRPCConn(t testing.T) (*rpc.Client, *rpc.Server) { + clientConn, serverConn := TestConn(t) + + server := rpc.NewServer() + go server.ServeConn(serverConn) + + client := rpc.NewClient(clientConn) + return client, server +} + +// TestPluginRPCConn returns a plugin RPC client and server that are connected +// together and configured. +func TestPluginRPCConn(t testing.T, ps map[string]Plugin, opts *TestOptions) (*RPCClient, *RPCServer) { + // Create two net.Conns we can use to shuttle our control connection + clientConn, serverConn := TestConn(t) + + // Start up the server + server := &RPCServer{Plugins: ps, Stdout: new(bytes.Buffer), Stderr: new(bytes.Buffer)} + if opts != nil { + if opts.ServerStdout != nil { + server.Stdout = opts.ServerStdout + } + if opts.ServerStderr != nil { + server.Stderr = opts.ServerStderr + } + } + go server.ServeConn(serverConn) + + // Connect the client to the server + client, err := NewRPCClient(clientConn, ps) + if err != nil { + t.Fatalf("err: %s", err) + } + + return client, server +} + +// TestGRPCConn returns a gRPC client conn and grpc server that are connected +// together and configured. The register function is used to register services +// prior to the Serve call. This is used to test gRPC connections. +func TestGRPCConn(t testing.T, register func(*grpc.Server)) (*grpc.ClientConn, *grpc.Server) { + // Create a listener + l, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + t.Fatalf("err: %s", err) + } + + server := grpc.NewServer() + register(server) + go server.Serve(l) + + // Connect to the server + conn, err := grpc.Dial( + l.Addr().String(), + grpc.WithBlock(), + grpc.WithInsecure()) + if err != nil { + t.Fatalf("err: %s", err) + } + + // Connection successful, close the listener + l.Close() + + return conn, server +} + +// TestPluginGRPCConn returns a plugin gRPC client and server that are connected +// together and configured. This is used to test gRPC connections. +func TestPluginGRPCConn(t testing.T, ps map[string]Plugin) (*GRPCClient, *GRPCServer) { + // Create a listener + l, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + t.Fatalf("err: %s", err) + } + + // Start up the server + server := &GRPCServer{ + Plugins: ps, + Server: DefaultGRPCServer, + Stdout: new(bytes.Buffer), + Stderr: new(bytes.Buffer), + } + if err := server.Init(); err != nil { + t.Fatalf("err: %s", err) + } + go server.Serve(l) + + // Connect to the server + conn, err := grpc.Dial( + l.Addr().String(), + grpc.WithBlock(), + grpc.WithInsecure()) + if err != nil { + t.Fatalf("err: %s", err) + } + + brokerGRPCClient := newGRPCBrokerClient(conn) + broker := newGRPCBroker(brokerGRPCClient, nil) + go broker.Run() + go brokerGRPCClient.StartStream() + + // Create the client + client := &GRPCClient{ + Conn: conn, + Plugins: ps, + broker: broker, + doneCtx: context.Background(), + } + + return client, server +} diff --git a/vendor/github.com/hashicorp/golang-lru/.gitignore b/vendor/github.com/hashicorp/golang-lru/.gitignore new file mode 100644 index 00000000..83656241 --- /dev/null +++ b/vendor/github.com/hashicorp/golang-lru/.gitignore @@ -0,0 +1,23 @@ +# Compiled Object files, Static and Dynamic libs (Shared Objects) +*.o +*.a +*.so + +# Folders +_obj +_test + +# Architecture specific extensions/prefixes +*.[568vq] +[568vq].out + +*.cgo1.go +*.cgo2.c +_cgo_defun.c +_cgo_gotypes.go +_cgo_export.* + +_testmain.go + +*.exe +*.test diff --git a/vendor/github.com/hashicorp/golang-lru/2q.go b/vendor/github.com/hashicorp/golang-lru/2q.go new file mode 100644 index 00000000..e474cd07 --- /dev/null +++ b/vendor/github.com/hashicorp/golang-lru/2q.go @@ -0,0 +1,223 @@ +package lru + +import ( + "fmt" + "sync" + + "github.com/hashicorp/golang-lru/simplelru" +) + +const ( + // Default2QRecentRatio is the ratio of the 2Q cache dedicated + // to recently added entries that have only been accessed once. + Default2QRecentRatio = 0.25 + + // Default2QGhostEntries is the default ratio of ghost + // entries kept to track entries recently evicted + Default2QGhostEntries = 0.50 +) + +// TwoQueueCache is a thread-safe fixed size 2Q cache. +// 2Q is an enhancement over the standard LRU cache +// in that it tracks both frequently and recently used +// entries separately. This avoids a burst in access to new +// entries from evicting frequently used entries. It adds some +// additional tracking overhead to the standard LRU cache, and is +// computationally about 2x the cost, and adds some metadata over +// head. The ARCCache is similar, but does not require setting any +// parameters. +type TwoQueueCache struct { + size int + recentSize int + + recent simplelru.LRUCache + frequent simplelru.LRUCache + recentEvict simplelru.LRUCache + lock sync.RWMutex +} + +// New2Q creates a new TwoQueueCache using the default +// values for the parameters. +func New2Q(size int) (*TwoQueueCache, error) { + return New2QParams(size, Default2QRecentRatio, Default2QGhostEntries) +} + +// New2QParams creates a new TwoQueueCache using the provided +// parameter values. +func New2QParams(size int, recentRatio float64, ghostRatio float64) (*TwoQueueCache, error) { + if size <= 0 { + return nil, fmt.Errorf("invalid size") + } + if recentRatio < 0.0 || recentRatio > 1.0 { + return nil, fmt.Errorf("invalid recent ratio") + } + if ghostRatio < 0.0 || ghostRatio > 1.0 { + return nil, fmt.Errorf("invalid ghost ratio") + } + + // Determine the sub-sizes + recentSize := int(float64(size) * recentRatio) + evictSize := int(float64(size) * ghostRatio) + + // Allocate the LRUs + recent, err := simplelru.NewLRU(size, nil) + if err != nil { + return nil, err + } + frequent, err := simplelru.NewLRU(size, nil) + if err != nil { + return nil, err + } + recentEvict, err := simplelru.NewLRU(evictSize, nil) + if err != nil { + return nil, err + } + + // Initialize the cache + c := &TwoQueueCache{ + size: size, + recentSize: recentSize, + recent: recent, + frequent: frequent, + recentEvict: recentEvict, + } + return c, nil +} + +// Get looks up a key's value from the cache. +func (c *TwoQueueCache) Get(key interface{}) (value interface{}, ok bool) { + c.lock.Lock() + defer c.lock.Unlock() + + // Check if this is a frequent value + if val, ok := c.frequent.Get(key); ok { + return val, ok + } + + // If the value is contained in recent, then we + // promote it to frequent + if val, ok := c.recent.Peek(key); ok { + c.recent.Remove(key) + c.frequent.Add(key, val) + return val, ok + } + + // No hit + return nil, false +} + +// Add adds a value to the cache. +func (c *TwoQueueCache) Add(key, value interface{}) { + c.lock.Lock() + defer c.lock.Unlock() + + // Check if the value is frequently used already, + // and just update the value + if c.frequent.Contains(key) { + c.frequent.Add(key, value) + return + } + + // Check if the value is recently used, and promote + // the value into the frequent list + if c.recent.Contains(key) { + c.recent.Remove(key) + c.frequent.Add(key, value) + return + } + + // If the value was recently evicted, add it to the + // frequently used list + if c.recentEvict.Contains(key) { + c.ensureSpace(true) + c.recentEvict.Remove(key) + c.frequent.Add(key, value) + return + } + + // Add to the recently seen list + c.ensureSpace(false) + c.recent.Add(key, value) + return +} + +// ensureSpace is used to ensure we have space in the cache +func (c *TwoQueueCache) ensureSpace(recentEvict bool) { + // If we have space, nothing to do + recentLen := c.recent.Len() + freqLen := c.frequent.Len() + if recentLen+freqLen < c.size { + return + } + + // If the recent buffer is larger than + // the target, evict from there + if recentLen > 0 && (recentLen > c.recentSize || (recentLen == c.recentSize && !recentEvict)) { + k, _, _ := c.recent.RemoveOldest() + c.recentEvict.Add(k, nil) + return + } + + // Remove from the frequent list otherwise + c.frequent.RemoveOldest() +} + +// Len returns the number of items in the cache. +func (c *TwoQueueCache) Len() int { + c.lock.RLock() + defer c.lock.RUnlock() + return c.recent.Len() + c.frequent.Len() +} + +// Keys returns a slice of the keys in the cache. +// The frequently used keys are first in the returned slice. +func (c *TwoQueueCache) Keys() []interface{} { + c.lock.RLock() + defer c.lock.RUnlock() + k1 := c.frequent.Keys() + k2 := c.recent.Keys() + return append(k1, k2...) +} + +// Remove removes the provided key from the cache. +func (c *TwoQueueCache) Remove(key interface{}) { + c.lock.Lock() + defer c.lock.Unlock() + if c.frequent.Remove(key) { + return + } + if c.recent.Remove(key) { + return + } + if c.recentEvict.Remove(key) { + return + } +} + +// Purge is used to completely clear the cache. +func (c *TwoQueueCache) Purge() { + c.lock.Lock() + defer c.lock.Unlock() + c.recent.Purge() + c.frequent.Purge() + c.recentEvict.Purge() +} + +// Contains is used to check if the cache contains a key +// without updating recency or frequency. +func (c *TwoQueueCache) Contains(key interface{}) bool { + c.lock.RLock() + defer c.lock.RUnlock() + return c.frequent.Contains(key) || c.recent.Contains(key) +} + +// Peek is used to inspect the cache value of a key +// without updating recency or frequency. +func (c *TwoQueueCache) Peek(key interface{}) (value interface{}, ok bool) { + c.lock.RLock() + defer c.lock.RUnlock() + if val, ok := c.frequent.Peek(key); ok { + return val, ok + } + return c.recent.Peek(key) +} diff --git a/vendor/github.com/hashicorp/golang-lru/2q_test.go b/vendor/github.com/hashicorp/golang-lru/2q_test.go new file mode 100644 index 00000000..1b0f3518 --- /dev/null +++ b/vendor/github.com/hashicorp/golang-lru/2q_test.go @@ -0,0 +1,306 @@ +package lru + +import ( + "math/rand" + "testing" +) + +func Benchmark2Q_Rand(b *testing.B) { + l, err := New2Q(8192) + if err != nil { + b.Fatalf("err: %v", err) + } + + trace := make([]int64, b.N*2) + for i := 0; i < b.N*2; i++ { + trace[i] = rand.Int63() % 32768 + } + + b.ResetTimer() + + var hit, miss int + for i := 0; i < 2*b.N; i++ { + if i%2 == 0 { + l.Add(trace[i], trace[i]) + } else { + _, ok := l.Get(trace[i]) + if ok { + hit++ + } else { + miss++ + } + } + } + b.Logf("hit: %d miss: %d ratio: %f", hit, miss, float64(hit)/float64(miss)) +} + +func Benchmark2Q_Freq(b *testing.B) { + l, err := New2Q(8192) + if err != nil { + b.Fatalf("err: %v", err) + } + + trace := make([]int64, b.N*2) + for i := 0; i < b.N*2; i++ { + if i%2 == 0 { + trace[i] = rand.Int63() % 16384 + } else { + trace[i] = rand.Int63() % 32768 + } + } + + b.ResetTimer() + + for i := 0; i < b.N; i++ { + l.Add(trace[i], trace[i]) + } + var hit, miss int + for i := 0; i < b.N; i++ { + _, ok := l.Get(trace[i]) + if ok { + hit++ + } else { + miss++ + } + } + b.Logf("hit: %d miss: %d ratio: %f", hit, miss, float64(hit)/float64(miss)) +} + +func Test2Q_RandomOps(t *testing.T) { + size := 128 + l, err := New2Q(128) + if err != nil { + t.Fatalf("err: %v", err) + } + + n := 200000 + for i := 0; i < n; i++ { + key := rand.Int63() % 512 + r := rand.Int63() + switch r % 3 { + case 0: + l.Add(key, key) + case 1: + l.Get(key) + case 2: + l.Remove(key) + } + + if l.recent.Len()+l.frequent.Len() > size { + t.Fatalf("bad: recent: %d freq: %d", + l.recent.Len(), l.frequent.Len()) + } + } +} + +func Test2Q_Get_RecentToFrequent(t *testing.T) { + l, err := New2Q(128) + if err != nil { + t.Fatalf("err: %v", err) + } + + // Touch all the entries, should be in t1 + for i := 0; i < 128; i++ { + l.Add(i, i) + } + if n := l.recent.Len(); n != 128 { + t.Fatalf("bad: %d", n) + } + if n := l.frequent.Len(); n != 0 { + t.Fatalf("bad: %d", n) + } + + // Get should upgrade to t2 + for i := 0; i < 128; i++ { + _, ok := l.Get(i) + if !ok { + t.Fatalf("missing: %d", i) + } + } + if n := l.recent.Len(); n != 0 { + t.Fatalf("bad: %d", n) + } + if n := l.frequent.Len(); n != 128 { + t.Fatalf("bad: %d", n) + } + + // Get be from t2 + for i := 0; i < 128; i++ { + _, ok := l.Get(i) + if !ok { + t.Fatalf("missing: %d", i) + } + } + if n := l.recent.Len(); n != 0 { + t.Fatalf("bad: %d", n) + } + if n := l.frequent.Len(); n != 128 { + t.Fatalf("bad: %d", n) + } +} + +func Test2Q_Add_RecentToFrequent(t *testing.T) { + l, err := New2Q(128) + if err != nil { + t.Fatalf("err: %v", err) + } + + // Add initially to recent + l.Add(1, 1) + if n := l.recent.Len(); n != 1 { + t.Fatalf("bad: %d", n) + } + if n := l.frequent.Len(); n != 0 { + t.Fatalf("bad: %d", n) + } + + // Add should upgrade to frequent + l.Add(1, 1) + if n := l.recent.Len(); n != 0 { + t.Fatalf("bad: %d", n) + } + if n := l.frequent.Len(); n != 1 { + t.Fatalf("bad: %d", n) + } + + // Add should remain in frequent + l.Add(1, 1) + if n := l.recent.Len(); n != 0 { + t.Fatalf("bad: %d", n) + } + if n := l.frequent.Len(); n != 1 { + t.Fatalf("bad: %d", n) + } +} + +func Test2Q_Add_RecentEvict(t *testing.T) { + l, err := New2Q(4) + if err != nil { + t.Fatalf("err: %v", err) + } + + // Add 1,2,3,4,5 -> Evict 1 + l.Add(1, 1) + l.Add(2, 2) + l.Add(3, 3) + l.Add(4, 4) + l.Add(5, 5) + if n := l.recent.Len(); n != 4 { + t.Fatalf("bad: %d", n) + } + if n := l.recentEvict.Len(); n != 1 { + t.Fatalf("bad: %d", n) + } + if n := l.frequent.Len(); n != 0 { + t.Fatalf("bad: %d", n) + } + + // Pull in the recently evicted + l.Add(1, 1) + if n := l.recent.Len(); n != 3 { + t.Fatalf("bad: %d", n) + } + if n := l.recentEvict.Len(); n != 1 { + t.Fatalf("bad: %d", n) + } + if n := l.frequent.Len(); n != 1 { + t.Fatalf("bad: %d", n) + } + + // Add 6, should cause another recent evict + l.Add(6, 6) + if n := l.recent.Len(); n != 3 { + t.Fatalf("bad: %d", n) + } + if n := l.recentEvict.Len(); n != 2 { + t.Fatalf("bad: %d", n) + } + if n := l.frequent.Len(); n != 1 { + t.Fatalf("bad: %d", n) + } +} + +func Test2Q(t *testing.T) { + l, err := New2Q(128) + if err != nil { + t.Fatalf("err: %v", err) + } + + for i := 0; i < 256; i++ { + l.Add(i, i) + } + if l.Len() != 128 { + t.Fatalf("bad len: %v", l.Len()) + } + + for i, k := range l.Keys() { + if v, ok := l.Get(k); !ok || v != k || v != i+128 { + t.Fatalf("bad key: %v", k) + } + } + for i := 0; i < 128; i++ { + _, ok := l.Get(i) + if ok { + t.Fatalf("should be evicted") + } + } + for i := 128; i < 256; i++ { + _, ok := l.Get(i) + if !ok { + t.Fatalf("should not be evicted") + } + } + for i := 128; i < 192; i++ { + l.Remove(i) + _, ok := l.Get(i) + if ok { + t.Fatalf("should be deleted") + } + } + + l.Purge() + if l.Len() != 0 { + t.Fatalf("bad len: %v", l.Len()) + } + if _, ok := l.Get(200); ok { + t.Fatalf("should contain nothing") + } +} + +// Test that Contains doesn't update recent-ness +func Test2Q_Contains(t *testing.T) { + l, err := New2Q(2) + if err != nil { + t.Fatalf("err: %v", err) + } + + l.Add(1, 1) + l.Add(2, 2) + if !l.Contains(1) { + t.Errorf("1 should be contained") + } + + l.Add(3, 3) + if l.Contains(1) { + t.Errorf("Contains should not have updated recent-ness of 1") + } +} + +// Test that Peek doesn't update recent-ness +func Test2Q_Peek(t *testing.T) { + l, err := New2Q(2) + if err != nil { + t.Fatalf("err: %v", err) + } + + l.Add(1, 1) + l.Add(2, 2) + if v, ok := l.Peek(1); !ok || v != 1 { + t.Errorf("1 should be set to 1: %v, %v", v, ok) + } + + l.Add(3, 3) + if l.Contains(1) { + t.Errorf("should not have updated recent-ness of 1") + } +} diff --git a/vendor/github.com/hashicorp/golang-lru/LICENSE b/vendor/github.com/hashicorp/golang-lru/LICENSE new file mode 100644 index 00000000..be2cc4df --- /dev/null +++ b/vendor/github.com/hashicorp/golang-lru/LICENSE @@ -0,0 +1,362 @@ +Mozilla Public License, version 2.0 + +1. Definitions + +1.1. "Contributor" + + means each individual or legal entity that creates, contributes to the + creation of, or owns Covered Software. + +1.2. "Contributor Version" + + means the combination of the Contributions of others (if any) used by a + Contributor and that particular Contributor's Contribution. + +1.3. "Contribution" + + means Covered Software of a particular Contributor. + +1.4. "Covered Software" + + means Source Code Form to which the initial Contributor has attached the + notice in Exhibit A, the Executable Form of such Source Code Form, and + Modifications of such Source Code Form, in each case including portions + thereof. + +1.5. "Incompatible With Secondary Licenses" + means + + a. that the initial Contributor has attached the notice described in + Exhibit B to the Covered Software; or + + b. that the Covered Software was made available under the terms of + version 1.1 or earlier of the License, but not also under the terms of + a Secondary License. + +1.6. "Executable Form" + + means any form of the work other than Source Code Form. + +1.7. "Larger Work" + + means a work that combines Covered Software with other material, in a + separate file or files, that is not Covered Software. + +1.8. "License" + + means this document. + +1.9. "Licensable" + + means having the right to grant, to the maximum extent possible, whether + at the time of the initial grant or subsequently, any and all of the + rights conveyed by this License. + +1.10. "Modifications" + + means any of the following: + + a. any file in Source Code Form that results from an addition to, + deletion from, or modification of the contents of Covered Software; or + + b. any new file in Source Code Form that contains any Covered Software. + +1.11. "Patent Claims" of a Contributor + + means any patent claim(s), including without limitation, method, + process, and apparatus claims, in any patent Licensable by such + Contributor that would be infringed, but for the grant of the License, + by the making, using, selling, offering for sale, having made, import, + or transfer of either its Contributions or its Contributor Version. + +1.12. "Secondary License" + + means either the GNU General Public License, Version 2.0, the GNU Lesser + General Public License, Version 2.1, the GNU Affero General Public + License, Version 3.0, or any later versions of those licenses. + +1.13. "Source Code Form" + + means the form of the work preferred for making modifications. + +1.14. "You" (or "Your") + + means an individual or a legal entity exercising rights under this + License. For legal entities, "You" includes any entity that controls, is + controlled by, or is under common control with You. For purposes of this + definition, "control" means (a) the power, direct or indirect, to cause + the direction or management of such entity, whether by contract or + otherwise, or (b) ownership of more than fifty percent (50%) of the + outstanding shares or beneficial ownership of such entity. + + +2. License Grants and Conditions + +2.1. Grants + + Each Contributor hereby grants You a world-wide, royalty-free, + non-exclusive license: + + a. under intellectual property rights (other than patent or trademark) + Licensable by such Contributor to use, reproduce, make available, + modify, display, perform, distribute, and otherwise exploit its + Contributions, either on an unmodified basis, with Modifications, or + as part of a Larger Work; and + + b. under Patent Claims of such Contributor to make, use, sell, offer for + sale, have made, import, and otherwise transfer either its + Contributions or its Contributor Version. + +2.2. Effective Date + + The licenses granted in Section 2.1 with respect to any Contribution + become effective for each Contribution on the date the Contributor first + distributes such Contribution. + +2.3. Limitations on Grant Scope + + The licenses granted in this Section 2 are the only rights granted under + this License. No additional rights or licenses will be implied from the + distribution or licensing of Covered Software under this License. + Notwithstanding Section 2.1(b) above, no patent license is granted by a + Contributor: + + a. for any code that a Contributor has removed from Covered Software; or + + b. for infringements caused by: (i) Your and any other third party's + modifications of Covered Software, or (ii) the combination of its + Contributions with other software (except as part of its Contributor + Version); or + + c. under Patent Claims infringed by Covered Software in the absence of + its Contributions. + + This License does not grant any rights in the trademarks, service marks, + or logos of any Contributor (except as may be necessary to comply with + the notice requirements in Section 3.4). + +2.4. Subsequent Licenses + + No Contributor makes additional grants as a result of Your choice to + distribute the Covered Software under a subsequent version of this + License (see Section 10.2) or under the terms of a Secondary License (if + permitted under the terms of Section 3.3). + +2.5. Representation + + Each Contributor represents that the Contributor believes its + Contributions are its original creation(s) or it has sufficient rights to + grant the rights to its Contributions conveyed by this License. + +2.6. Fair Use + + This License is not intended to limit any rights You have under + applicable copyright doctrines of fair use, fair dealing, or other + equivalents. + +2.7. Conditions + + Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in + Section 2.1. + + +3. Responsibilities + +3.1. Distribution of Source Form + + All distribution of Covered Software in Source Code Form, including any + Modifications that You create or to which You contribute, must be under + the terms of this License. You must inform recipients that the Source + Code Form of the Covered Software is governed by the terms of this + License, and how they can obtain a copy of this License. You may not + attempt to alter or restrict the recipients' rights in the Source Code + Form. + +3.2. Distribution of Executable Form + + If You distribute Covered Software in Executable Form then: + + a. such Covered Software must also be made available in Source Code Form, + as described in Section 3.1, and You must inform recipients of the + Executable Form how they can obtain a copy of such Source Code Form by + reasonable means in a timely manner, at a charge no more than the cost + of distribution to the recipient; and + + b. You may distribute such Executable Form under the terms of this + License, or sublicense it under different terms, provided that the + license for the Executable Form does not attempt to limit or alter the + recipients' rights in the Source Code Form under this License. + +3.3. Distribution of a Larger Work + + You may create and distribute a Larger Work under terms of Your choice, + provided that You also comply with the requirements of this License for + the Covered Software. If the Larger Work is a combination of Covered + Software with a work governed by one or more Secondary Licenses, and the + Covered Software is not Incompatible With Secondary Licenses, this + License permits You to additionally distribute such Covered Software + under the terms of such Secondary License(s), so that the recipient of + the Larger Work may, at their option, further distribute the Covered + Software under the terms of either this License or such Secondary + License(s). + +3.4. Notices + + You may not remove or alter the substance of any license notices + (including copyright notices, patent notices, disclaimers of warranty, or + limitations of liability) contained within the Source Code Form of the + Covered Software, except that You may alter any license notices to the + extent required to remedy known factual inaccuracies. + +3.5. Application of Additional Terms + + You may choose to offer, and to charge a fee for, warranty, support, + indemnity or liability obligations to one or more recipients of Covered + Software. However, You may do so only on Your own behalf, and not on + behalf of any Contributor. You must make it absolutely clear that any + such warranty, support, indemnity, or liability obligation is offered by + You alone, and You hereby agree to indemnify every Contributor for any + liability incurred by such Contributor as a result of warranty, support, + indemnity or liability terms You offer. You may include additional + disclaimers of warranty and limitations of liability specific to any + jurisdiction. + +4. Inability to Comply Due to Statute or Regulation + + If it is impossible for You to comply with any of the terms of this License + with respect to some or all of the Covered Software due to statute, + judicial order, or regulation then You must: (a) comply with the terms of + this License to the maximum extent possible; and (b) describe the + limitations and the code they affect. Such description must be placed in a + text file included with all distributions of the Covered Software under + this License. Except to the extent prohibited by statute or regulation, + such description must be sufficiently detailed for a recipient of ordinary + skill to be able to understand it. + +5. Termination + +5.1. The rights granted under this License will terminate automatically if You + fail to comply with any of its terms. However, if You become compliant, + then the rights granted under this License from a particular Contributor + are reinstated (a) provisionally, unless and until such Contributor + explicitly and finally terminates Your grants, and (b) on an ongoing + basis, if such Contributor fails to notify You of the non-compliance by + some reasonable means prior to 60 days after You have come back into + compliance. Moreover, Your grants from a particular Contributor are + reinstated on an ongoing basis if such Contributor notifies You of the + non-compliance by some reasonable means, this is the first time You have + received notice of non-compliance with this License from such + Contributor, and You become compliant prior to 30 days after Your receipt + of the notice. + +5.2. If You initiate litigation against any entity by asserting a patent + infringement claim (excluding declaratory judgment actions, + counter-claims, and cross-claims) alleging that a Contributor Version + directly or indirectly infringes any patent, then the rights granted to + You by any and all Contributors for the Covered Software under Section + 2.1 of this License shall terminate. + +5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user + license agreements (excluding distributors and resellers) which have been + validly granted by You or Your distributors under this License prior to + termination shall survive termination. + +6. Disclaimer of Warranty + + Covered Software is provided under this License on an "as is" basis, + without warranty of any kind, either expressed, implied, or statutory, + including, without limitation, warranties that the Covered Software is free + of defects, merchantable, fit for a particular purpose or non-infringing. + The entire risk as to the quality and performance of the Covered Software + is with You. Should any Covered Software prove defective in any respect, + You (not any Contributor) assume the cost of any necessary servicing, + repair, or correction. This disclaimer of warranty constitutes an essential + part of this License. No use of any Covered Software is authorized under + this License except under this disclaimer. + +7. Limitation of Liability + + Under no circumstances and under no legal theory, whether tort (including + negligence), contract, or otherwise, shall any Contributor, or anyone who + distributes Covered Software as permitted above, be liable to You for any + direct, indirect, special, incidental, or consequential damages of any + character including, without limitation, damages for lost profits, loss of + goodwill, work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses, even if such party shall have been + informed of the possibility of such damages. This limitation of liability + shall not apply to liability for death or personal injury resulting from + such party's negligence to the extent applicable law prohibits such + limitation. Some jurisdictions do not allow the exclusion or limitation of + incidental or consequential damages, so this exclusion and limitation may + not apply to You. + +8. Litigation + + Any litigation relating to this License may be brought only in the courts + of a jurisdiction where the defendant maintains its principal place of + business and such litigation shall be governed by laws of that + jurisdiction, without reference to its conflict-of-law provisions. Nothing + in this Section shall prevent a party's ability to bring cross-claims or + counter-claims. + +9. Miscellaneous + + This License represents the complete agreement concerning the subject + matter hereof. If any provision of this License is held to be + unenforceable, such provision shall be reformed only to the extent + necessary to make it enforceable. Any law or regulation which provides that + the language of a contract shall be construed against the drafter shall not + be used to construe this License against a Contributor. + + +10. Versions of the License + +10.1. New Versions + + Mozilla Foundation is the license steward. Except as provided in Section + 10.3, no one other than the license steward has the right to modify or + publish new versions of this License. Each version will be given a + distinguishing version number. + +10.2. Effect of New Versions + + You may distribute the Covered Software under the terms of the version + of the License under which You originally received the Covered Software, + or under the terms of any subsequent version published by the license + steward. + +10.3. Modified Versions + + If you create software not governed by this License, and you want to + create a new license for such software, you may create and use a + modified version of this License if you rename the license and remove + any references to the name of the license steward (except to note that + such modified license differs from this License). + +10.4. Distributing Source Code Form that is Incompatible With Secondary + Licenses If You choose to distribute Source Code Form that is + Incompatible With Secondary Licenses under the terms of this version of + the License, the notice described in Exhibit B of this License must be + attached. + +Exhibit A - Source Code Form License Notice + + This Source Code Form is subject to the + terms of the Mozilla Public License, v. + 2.0. If a copy of the MPL was not + distributed with this file, You can + obtain one at + http://mozilla.org/MPL/2.0/. + +If it is not possible or desirable to put the notice in a particular file, +then You may include the notice in a location (such as a LICENSE file in a +relevant directory) where a recipient would be likely to look for such a +notice. + +You may add additional accurate notices of copyright ownership. + +Exhibit B - "Incompatible With Secondary Licenses" Notice + + This Source Code Form is "Incompatible + With Secondary Licenses", as defined by + the Mozilla Public License, v. 2.0. diff --git a/vendor/github.com/hashicorp/golang-lru/README.md b/vendor/github.com/hashicorp/golang-lru/README.md new file mode 100644 index 00000000..33e58cfa --- /dev/null +++ b/vendor/github.com/hashicorp/golang-lru/README.md @@ -0,0 +1,25 @@ +golang-lru +========== + +This provides the `lru` package which implements a fixed-size +thread safe LRU cache. It is based on the cache in Groupcache. + +Documentation +============= + +Full docs are available on [Godoc](http://godoc.org/github.com/hashicorp/golang-lru) + +Example +======= + +Using the LRU is very simple: + +```go +l, _ := New(128) +for i := 0; i < 256; i++ { + l.Add(i, nil) +} +if l.Len() != 128 { + panic(fmt.Sprintf("bad len: %v", l.Len())) +} +``` diff --git a/vendor/github.com/hashicorp/golang-lru/arc.go b/vendor/github.com/hashicorp/golang-lru/arc.go new file mode 100644 index 00000000..555225a2 --- /dev/null +++ b/vendor/github.com/hashicorp/golang-lru/arc.go @@ -0,0 +1,257 @@ +package lru + +import ( + "sync" + + "github.com/hashicorp/golang-lru/simplelru" +) + +// ARCCache is a thread-safe fixed size Adaptive Replacement Cache (ARC). +// ARC is an enhancement over the standard LRU cache in that tracks both +// frequency and recency of use. This avoids a burst in access to new +// entries from evicting the frequently used older entries. It adds some +// additional tracking overhead to a standard LRU cache, computationally +// it is roughly 2x the cost, and the extra memory overhead is linear +// with the size of the cache. ARC has been patented by IBM, but is +// similar to the TwoQueueCache (2Q) which requires setting parameters. +type ARCCache struct { + size int // Size is the total capacity of the cache + p int // P is the dynamic preference towards T1 or T2 + + t1 simplelru.LRUCache // T1 is the LRU for recently accessed items + b1 simplelru.LRUCache // B1 is the LRU for evictions from t1 + + t2 simplelru.LRUCache // T2 is the LRU for frequently accessed items + b2 simplelru.LRUCache // B2 is the LRU for evictions from t2 + + lock sync.RWMutex +} + +// NewARC creates an ARC of the given size +func NewARC(size int) (*ARCCache, error) { + // Create the sub LRUs + b1, err := simplelru.NewLRU(size, nil) + if err != nil { + return nil, err + } + b2, err := simplelru.NewLRU(size, nil) + if err != nil { + return nil, err + } + t1, err := simplelru.NewLRU(size, nil) + if err != nil { + return nil, err + } + t2, err := simplelru.NewLRU(size, nil) + if err != nil { + return nil, err + } + + // Initialize the ARC + c := &ARCCache{ + size: size, + p: 0, + t1: t1, + b1: b1, + t2: t2, + b2: b2, + } + return c, nil +} + +// Get looks up a key's value from the cache. +func (c *ARCCache) Get(key interface{}) (value interface{}, ok bool) { + c.lock.Lock() + defer c.lock.Unlock() + + // If the value is contained in T1 (recent), then + // promote it to T2 (frequent) + if val, ok := c.t1.Peek(key); ok { + c.t1.Remove(key) + c.t2.Add(key, val) + return val, ok + } + + // Check if the value is contained in T2 (frequent) + if val, ok := c.t2.Get(key); ok { + return val, ok + } + + // No hit + return nil, false +} + +// Add adds a value to the cache. +func (c *ARCCache) Add(key, value interface{}) { + c.lock.Lock() + defer c.lock.Unlock() + + // Check if the value is contained in T1 (recent), and potentially + // promote it to frequent T2 + if c.t1.Contains(key) { + c.t1.Remove(key) + c.t2.Add(key, value) + return + } + + // Check if the value is already in T2 (frequent) and update it + if c.t2.Contains(key) { + c.t2.Add(key, value) + return + } + + // Check if this value was recently evicted as part of the + // recently used list + if c.b1.Contains(key) { + // T1 set is too small, increase P appropriately + delta := 1 + b1Len := c.b1.Len() + b2Len := c.b2.Len() + if b2Len > b1Len { + delta = b2Len / b1Len + } + if c.p+delta >= c.size { + c.p = c.size + } else { + c.p += delta + } + + // Potentially need to make room in the cache + if c.t1.Len()+c.t2.Len() >= c.size { + c.replace(false) + } + + // Remove from B1 + c.b1.Remove(key) + + // Add the key to the frequently used list + c.t2.Add(key, value) + return + } + + // Check if this value was recently evicted as part of the + // frequently used list + if c.b2.Contains(key) { + // T2 set is too small, decrease P appropriately + delta := 1 + b1Len := c.b1.Len() + b2Len := c.b2.Len() + if b1Len > b2Len { + delta = b1Len / b2Len + } + if delta >= c.p { + c.p = 0 + } else { + c.p -= delta + } + + // Potentially need to make room in the cache + if c.t1.Len()+c.t2.Len() >= c.size { + c.replace(true) + } + + // Remove from B2 + c.b2.Remove(key) + + // Add the key to the frequently used list + c.t2.Add(key, value) + return + } + + // Potentially need to make room in the cache + if c.t1.Len()+c.t2.Len() >= c.size { + c.replace(false) + } + + // Keep the size of the ghost buffers trim + if c.b1.Len() > c.size-c.p { + c.b1.RemoveOldest() + } + if c.b2.Len() > c.p { + c.b2.RemoveOldest() + } + + // Add to the recently seen list + c.t1.Add(key, value) + return +} + +// replace is used to adaptively evict from either T1 or T2 +// based on the current learned value of P +func (c *ARCCache) replace(b2ContainsKey bool) { + t1Len := c.t1.Len() + if t1Len > 0 && (t1Len > c.p || (t1Len == c.p && b2ContainsKey)) { + k, _, ok := c.t1.RemoveOldest() + if ok { + c.b1.Add(k, nil) + } + } else { + k, _, ok := c.t2.RemoveOldest() + if ok { + c.b2.Add(k, nil) + } + } +} + +// Len returns the number of cached entries +func (c *ARCCache) Len() int { + c.lock.RLock() + defer c.lock.RUnlock() + return c.t1.Len() + c.t2.Len() +} + +// Keys returns all the cached keys +func (c *ARCCache) Keys() []interface{} { + c.lock.RLock() + defer c.lock.RUnlock() + k1 := c.t1.Keys() + k2 := c.t2.Keys() + return append(k1, k2...) +} + +// Remove is used to purge a key from the cache +func (c *ARCCache) Remove(key interface{}) { + c.lock.Lock() + defer c.lock.Unlock() + if c.t1.Remove(key) { + return + } + if c.t2.Remove(key) { + return + } + if c.b1.Remove(key) { + return + } + if c.b2.Remove(key) { + return + } +} + +// Purge is used to clear the cache +func (c *ARCCache) Purge() { + c.lock.Lock() + defer c.lock.Unlock() + c.t1.Purge() + c.t2.Purge() + c.b1.Purge() + c.b2.Purge() +} + +// Contains is used to check if the cache contains a key +// without updating recency or frequency. +func (c *ARCCache) Contains(key interface{}) bool { + c.lock.RLock() + defer c.lock.RUnlock() + return c.t1.Contains(key) || c.t2.Contains(key) +} + +// Peek is used to inspect the cache value of a key +// without updating recency or frequency. +func (c *ARCCache) Peek(key interface{}) (value interface{}, ok bool) { + c.lock.RLock() + defer c.lock.RUnlock() + if val, ok := c.t1.Peek(key); ok { + return val, ok + } + return c.t2.Peek(key) +} diff --git a/vendor/github.com/hashicorp/golang-lru/arc_test.go b/vendor/github.com/hashicorp/golang-lru/arc_test.go new file mode 100644 index 00000000..e2d9b68c --- /dev/null +++ b/vendor/github.com/hashicorp/golang-lru/arc_test.go @@ -0,0 +1,377 @@ +package lru + +import ( + "math/rand" + "testing" + "time" +) + +func init() { + rand.Seed(time.Now().Unix()) +} + +func BenchmarkARC_Rand(b *testing.B) { + l, err := NewARC(8192) + if err != nil { + b.Fatalf("err: %v", err) + } + + trace := make([]int64, b.N*2) + for i := 0; i < b.N*2; i++ { + trace[i] = rand.Int63() % 32768 + } + + b.ResetTimer() + + var hit, miss int + for i := 0; i < 2*b.N; i++ { + if i%2 == 0 { + l.Add(trace[i], trace[i]) + } else { + _, ok := l.Get(trace[i]) + if ok { + hit++ + } else { + miss++ + } + } + } + b.Logf("hit: %d miss: %d ratio: %f", hit, miss, float64(hit)/float64(miss)) +} + +func BenchmarkARC_Freq(b *testing.B) { + l, err := NewARC(8192) + if err != nil { + b.Fatalf("err: %v", err) + } + + trace := make([]int64, b.N*2) + for i := 0; i < b.N*2; i++ { + if i%2 == 0 { + trace[i] = rand.Int63() % 16384 + } else { + trace[i] = rand.Int63() % 32768 + } + } + + b.ResetTimer() + + for i := 0; i < b.N; i++ { + l.Add(trace[i], trace[i]) + } + var hit, miss int + for i := 0; i < b.N; i++ { + _, ok := l.Get(trace[i]) + if ok { + hit++ + } else { + miss++ + } + } + b.Logf("hit: %d miss: %d ratio: %f", hit, miss, float64(hit)/float64(miss)) +} + +func TestARC_RandomOps(t *testing.T) { + size := 128 + l, err := NewARC(128) + if err != nil { + t.Fatalf("err: %v", err) + } + + n := 200000 + for i := 0; i < n; i++ { + key := rand.Int63() % 512 + r := rand.Int63() + switch r % 3 { + case 0: + l.Add(key, key) + case 1: + l.Get(key) + case 2: + l.Remove(key) + } + + if l.t1.Len()+l.t2.Len() > size { + t.Fatalf("bad: t1: %d t2: %d b1: %d b2: %d p: %d", + l.t1.Len(), l.t2.Len(), l.b1.Len(), l.b2.Len(), l.p) + } + if l.b1.Len()+l.b2.Len() > size { + t.Fatalf("bad: t1: %d t2: %d b1: %d b2: %d p: %d", + l.t1.Len(), l.t2.Len(), l.b1.Len(), l.b2.Len(), l.p) + } + } +} + +func TestARC_Get_RecentToFrequent(t *testing.T) { + l, err := NewARC(128) + if err != nil { + t.Fatalf("err: %v", err) + } + + // Touch all the entries, should be in t1 + for i := 0; i < 128; i++ { + l.Add(i, i) + } + if n := l.t1.Len(); n != 128 { + t.Fatalf("bad: %d", n) + } + if n := l.t2.Len(); n != 0 { + t.Fatalf("bad: %d", n) + } + + // Get should upgrade to t2 + for i := 0; i < 128; i++ { + _, ok := l.Get(i) + if !ok { + t.Fatalf("missing: %d", i) + } + } + if n := l.t1.Len(); n != 0 { + t.Fatalf("bad: %d", n) + } + if n := l.t2.Len(); n != 128 { + t.Fatalf("bad: %d", n) + } + + // Get be from t2 + for i := 0; i < 128; i++ { + _, ok := l.Get(i) + if !ok { + t.Fatalf("missing: %d", i) + } + } + if n := l.t1.Len(); n != 0 { + t.Fatalf("bad: %d", n) + } + if n := l.t2.Len(); n != 128 { + t.Fatalf("bad: %d", n) + } +} + +func TestARC_Add_RecentToFrequent(t *testing.T) { + l, err := NewARC(128) + if err != nil { + t.Fatalf("err: %v", err) + } + + // Add initially to t1 + l.Add(1, 1) + if n := l.t1.Len(); n != 1 { + t.Fatalf("bad: %d", n) + } + if n := l.t2.Len(); n != 0 { + t.Fatalf("bad: %d", n) + } + + // Add should upgrade to t2 + l.Add(1, 1) + if n := l.t1.Len(); n != 0 { + t.Fatalf("bad: %d", n) + } + if n := l.t2.Len(); n != 1 { + t.Fatalf("bad: %d", n) + } + + // Add should remain in t2 + l.Add(1, 1) + if n := l.t1.Len(); n != 0 { + t.Fatalf("bad: %d", n) + } + if n := l.t2.Len(); n != 1 { + t.Fatalf("bad: %d", n) + } +} + +func TestARC_Adaptive(t *testing.T) { + l, err := NewARC(4) + if err != nil { + t.Fatalf("err: %v", err) + } + + // Fill t1 + for i := 0; i < 4; i++ { + l.Add(i, i) + } + if n := l.t1.Len(); n != 4 { + t.Fatalf("bad: %d", n) + } + + // Move to t2 + l.Get(0) + l.Get(1) + if n := l.t2.Len(); n != 2 { + t.Fatalf("bad: %d", n) + } + + // Evict from t1 + l.Add(4, 4) + if n := l.b1.Len(); n != 1 { + t.Fatalf("bad: %d", n) + } + + // Current state + // t1 : (MRU) [4, 3] (LRU) + // t2 : (MRU) [1, 0] (LRU) + // b1 : (MRU) [2] (LRU) + // b2 : (MRU) [] (LRU) + + // Add 2, should cause hit on b1 + l.Add(2, 2) + if n := l.b1.Len(); n != 1 { + t.Fatalf("bad: %d", n) + } + if l.p != 1 { + t.Fatalf("bad: %d", l.p) + } + if n := l.t2.Len(); n != 3 { + t.Fatalf("bad: %d", n) + } + + // Current state + // t1 : (MRU) [4] (LRU) + // t2 : (MRU) [2, 1, 0] (LRU) + // b1 : (MRU) [3] (LRU) + // b2 : (MRU) [] (LRU) + + // Add 4, should migrate to t2 + l.Add(4, 4) + if n := l.t1.Len(); n != 0 { + t.Fatalf("bad: %d", n) + } + if n := l.t2.Len(); n != 4 { + t.Fatalf("bad: %d", n) + } + + // Current state + // t1 : (MRU) [] (LRU) + // t2 : (MRU) [4, 2, 1, 0] (LRU) + // b1 : (MRU) [3] (LRU) + // b2 : (MRU) [] (LRU) + + // Add 4, should evict to b2 + l.Add(5, 5) + if n := l.t1.Len(); n != 1 { + t.Fatalf("bad: %d", n) + } + if n := l.t2.Len(); n != 3 { + t.Fatalf("bad: %d", n) + } + if n := l.b2.Len(); n != 1 { + t.Fatalf("bad: %d", n) + } + + // Current state + // t1 : (MRU) [5] (LRU) + // t2 : (MRU) [4, 2, 1] (LRU) + // b1 : (MRU) [3] (LRU) + // b2 : (MRU) [0] (LRU) + + // Add 0, should decrease p + l.Add(0, 0) + if n := l.t1.Len(); n != 0 { + t.Fatalf("bad: %d", n) + } + if n := l.t2.Len(); n != 4 { + t.Fatalf("bad: %d", n) + } + if n := l.b1.Len(); n != 2 { + t.Fatalf("bad: %d", n) + } + if n := l.b2.Len(); n != 0 { + t.Fatalf("bad: %d", n) + } + if l.p != 0 { + t.Fatalf("bad: %d", l.p) + } + + // Current state + // t1 : (MRU) [] (LRU) + // t2 : (MRU) [0, 4, 2, 1] (LRU) + // b1 : (MRU) [5, 3] (LRU) + // b2 : (MRU) [0] (LRU) +} + +func TestARC(t *testing.T) { + l, err := NewARC(128) + if err != nil { + t.Fatalf("err: %v", err) + } + + for i := 0; i < 256; i++ { + l.Add(i, i) + } + if l.Len() != 128 { + t.Fatalf("bad len: %v", l.Len()) + } + + for i, k := range l.Keys() { + if v, ok := l.Get(k); !ok || v != k || v != i+128 { + t.Fatalf("bad key: %v", k) + } + } + for i := 0; i < 128; i++ { + _, ok := l.Get(i) + if ok { + t.Fatalf("should be evicted") + } + } + for i := 128; i < 256; i++ { + _, ok := l.Get(i) + if !ok { + t.Fatalf("should not be evicted") + } + } + for i := 128; i < 192; i++ { + l.Remove(i) + _, ok := l.Get(i) + if ok { + t.Fatalf("should be deleted") + } + } + + l.Purge() + if l.Len() != 0 { + t.Fatalf("bad len: %v", l.Len()) + } + if _, ok := l.Get(200); ok { + t.Fatalf("should contain nothing") + } +} + +// Test that Contains doesn't update recent-ness +func TestARC_Contains(t *testing.T) { + l, err := NewARC(2) + if err != nil { + t.Fatalf("err: %v", err) + } + + l.Add(1, 1) + l.Add(2, 2) + if !l.Contains(1) { + t.Errorf("1 should be contained") + } + + l.Add(3, 3) + if l.Contains(1) { + t.Errorf("Contains should not have updated recent-ness of 1") + } +} + +// Test that Peek doesn't update recent-ness +func TestARC_Peek(t *testing.T) { + l, err := NewARC(2) + if err != nil { + t.Fatalf("err: %v", err) + } + + l.Add(1, 1) + l.Add(2, 2) + if v, ok := l.Peek(1); !ok || v != 1 { + t.Errorf("1 should be set to 1: %v, %v", v, ok) + } + + l.Add(3, 3) + if l.Contains(1) { + t.Errorf("should not have updated recent-ness of 1") + } +} diff --git a/vendor/github.com/hashicorp/golang-lru/doc.go b/vendor/github.com/hashicorp/golang-lru/doc.go new file mode 100644 index 00000000..2547df97 --- /dev/null +++ b/vendor/github.com/hashicorp/golang-lru/doc.go @@ -0,0 +1,21 @@ +// Package lru provides three different LRU caches of varying sophistication. +// +// Cache is a simple LRU cache. It is based on the +// LRU implementation in groupcache: +// https://github.com/golang/groupcache/tree/master/lru +// +// TwoQueueCache tracks frequently used and recently used entries separately. +// This avoids a burst of accesses from taking out frequently used entries, +// at the cost of about 2x computational overhead and some extra bookkeeping. +// +// ARCCache is an adaptive replacement cache. It tracks recent evictions as +// well as recent usage in both the frequent and recent caches. Its +// computational overhead is comparable to TwoQueueCache, but the memory +// overhead is linear with the size of the cache. +// +// ARC has been patented by IBM, so do not use it if that is problematic for +// your program. +// +// All caches in this package take locks while operating, and are therefore +// thread-safe for consumers. +package lru diff --git a/vendor/github.com/hashicorp/golang-lru/go.mod b/vendor/github.com/hashicorp/golang-lru/go.mod new file mode 100644 index 00000000..824cb97e --- /dev/null +++ b/vendor/github.com/hashicorp/golang-lru/go.mod @@ -0,0 +1 @@ +module github.com/hashicorp/golang-lru diff --git a/vendor/github.com/hashicorp/golang-lru/lru.go b/vendor/github.com/hashicorp/golang-lru/lru.go new file mode 100644 index 00000000..c8d9b0a2 --- /dev/null +++ b/vendor/github.com/hashicorp/golang-lru/lru.go @@ -0,0 +1,110 @@ +package lru + +import ( + "sync" + + "github.com/hashicorp/golang-lru/simplelru" +) + +// Cache is a thread-safe fixed size LRU cache. +type Cache struct { + lru simplelru.LRUCache + lock sync.RWMutex +} + +// New creates an LRU of the given size. +func New(size int) (*Cache, error) { + return NewWithEvict(size, nil) +} + +// NewWithEvict constructs a fixed size cache with the given eviction +// callback. +func NewWithEvict(size int, onEvicted func(key interface{}, value interface{})) (*Cache, error) { + lru, err := simplelru.NewLRU(size, simplelru.EvictCallback(onEvicted)) + if err != nil { + return nil, err + } + c := &Cache{ + lru: lru, + } + return c, nil +} + +// Purge is used to completely clear the cache. +func (c *Cache) Purge() { + c.lock.Lock() + c.lru.Purge() + c.lock.Unlock() +} + +// Add adds a value to the cache. Returns true if an eviction occurred. +func (c *Cache) Add(key, value interface{}) (evicted bool) { + c.lock.Lock() + defer c.lock.Unlock() + return c.lru.Add(key, value) +} + +// Get looks up a key's value from the cache. +func (c *Cache) Get(key interface{}) (value interface{}, ok bool) { + c.lock.Lock() + defer c.lock.Unlock() + return c.lru.Get(key) +} + +// Contains checks if a key is in the cache, without updating the +// recent-ness or deleting it for being stale. +func (c *Cache) Contains(key interface{}) bool { + c.lock.RLock() + defer c.lock.RUnlock() + return c.lru.Contains(key) +} + +// Peek returns the key value (or undefined if not found) without updating +// the "recently used"-ness of the key. +func (c *Cache) Peek(key interface{}) (value interface{}, ok bool) { + c.lock.RLock() + defer c.lock.RUnlock() + return c.lru.Peek(key) +} + +// ContainsOrAdd checks if a key is in the cache without updating the +// recent-ness or deleting it for being stale, and if not, adds the value. +// Returns whether found and whether an eviction occurred. +func (c *Cache) ContainsOrAdd(key, value interface{}) (ok, evicted bool) { + c.lock.Lock() + defer c.lock.Unlock() + + if c.lru.Contains(key) { + return true, false + } + evicted = c.lru.Add(key, value) + return false, evicted +} + +// Remove removes the provided key from the cache. +func (c *Cache) Remove(key interface{}) { + c.lock.Lock() + c.lru.Remove(key) + c.lock.Unlock() +} + +// RemoveOldest removes the oldest item from the cache. +func (c *Cache) RemoveOldest() { + c.lock.Lock() + c.lru.RemoveOldest() + c.lock.Unlock() +} + +// Keys returns a slice of the keys in the cache, from oldest to newest. +func (c *Cache) Keys() []interface{} { + c.lock.RLock() + defer c.lock.RUnlock() + return c.lru.Keys() +} + +// Len returns the number of items in the cache. +func (c *Cache) Len() int { + c.lock.RLock() + defer c.lock.RUnlock() + return c.lru.Len() +} diff --git a/vendor/github.com/hashicorp/golang-lru/lru_test.go b/vendor/github.com/hashicorp/golang-lru/lru_test.go new file mode 100644 index 00000000..e7e23505 --- /dev/null +++ b/vendor/github.com/hashicorp/golang-lru/lru_test.go @@ -0,0 +1,221 @@ +package lru + +import ( + "math/rand" + "testing" +) + +func BenchmarkLRU_Rand(b *testing.B) { + l, err := New(8192) + if err != nil { + b.Fatalf("err: %v", err) + } + + trace := make([]int64, b.N*2) + for i := 0; i < b.N*2; i++ { + trace[i] = rand.Int63() % 32768 + } + + b.ResetTimer() + + var hit, miss int + for i := 0; i < 2*b.N; i++ { + if i%2 == 0 { + l.Add(trace[i], trace[i]) + } else { + _, ok := l.Get(trace[i]) + if ok { + hit++ + } else { + miss++ + } + } + } + b.Logf("hit: %d miss: %d ratio: %f", hit, miss, float64(hit)/float64(miss)) +} + +func BenchmarkLRU_Freq(b *testing.B) { + l, err := New(8192) + if err != nil { + b.Fatalf("err: %v", err) + } + + trace := make([]int64, b.N*2) + for i := 0; i < b.N*2; i++ { + if i%2 == 0 { + trace[i] = rand.Int63() % 16384 + } else { + trace[i] = rand.Int63() % 32768 + } + } + + b.ResetTimer() + + for i := 0; i < b.N; i++ { + l.Add(trace[i], trace[i]) + } + var hit, miss int + for i := 0; i < b.N; i++ { + _, ok := l.Get(trace[i]) + if ok { + hit++ + } else { + miss++ + } + } + b.Logf("hit: %d miss: %d ratio: %f", hit, miss, float64(hit)/float64(miss)) +} + +func TestLRU(t *testing.T) { + evictCounter := 0 + onEvicted := func(k interface{}, v interface{}) { + if k != v { + t.Fatalf("Evict values not equal (%v!=%v)", k, v) + } + evictCounter++ + } + l, err := NewWithEvict(128, onEvicted) + if err != nil { + t.Fatalf("err: %v", err) + } + + for i := 0; i < 256; i++ { + l.Add(i, i) + } + if l.Len() != 128 { + t.Fatalf("bad len: %v", l.Len()) + } + + if evictCounter != 128 { + t.Fatalf("bad evict count: %v", evictCounter) + } + + for i, k := range l.Keys() { + if v, ok := l.Get(k); !ok || v != k || v != i+128 { + t.Fatalf("bad key: %v", k) + } + } + for i := 0; i < 128; i++ { + _, ok := l.Get(i) + if ok { + t.Fatalf("should be evicted") + } + } + for i := 128; i < 256; i++ { + _, ok := l.Get(i) + if !ok { + t.Fatalf("should not be evicted") + } + } + for i := 128; i < 192; i++ { + l.Remove(i) + _, ok := l.Get(i) + if ok { + t.Fatalf("should be deleted") + } + } + + l.Get(192) // expect 192 to be last key in l.Keys() + + for i, k := range l.Keys() { + if (i < 63 && k != i+193) || (i == 63 && k != 192) { + t.Fatalf("out of order key: %v", k) + } + } + + l.Purge() + if l.Len() != 0 { + t.Fatalf("bad len: %v", l.Len()) + } + if _, ok := l.Get(200); ok { + t.Fatalf("should contain nothing") + } +} + +// test that Add returns true/false if an eviction occurred +func TestLRUAdd(t *testing.T) { + evictCounter := 0 + onEvicted := func(k interface{}, v interface{}) { + evictCounter++ + } + + l, err := NewWithEvict(1, onEvicted) + if err != nil { + t.Fatalf("err: %v", err) + } + + if l.Add(1, 1) == true || evictCounter != 0 { + t.Errorf("should not have an eviction") + } + if l.Add(2, 2) == false || evictCounter != 1 { + t.Errorf("should have an eviction") + } +} + +// test that Contains doesn't update recent-ness +func TestLRUContains(t *testing.T) { + l, err := New(2) + if err != nil { + t.Fatalf("err: %v", err) + } + + l.Add(1, 1) + l.Add(2, 2) + if !l.Contains(1) { + t.Errorf("1 should be contained") + } + + l.Add(3, 3) + if l.Contains(1) { + t.Errorf("Contains should not have updated recent-ness of 1") + } +} + +// test that Contains doesn't update recent-ness +func TestLRUContainsOrAdd(t *testing.T) { + l, err := New(2) + if err != nil { + t.Fatalf("err: %v", err) + } + + l.Add(1, 1) + l.Add(2, 2) + contains, evict := l.ContainsOrAdd(1, 1) + if !contains { + t.Errorf("1 should be contained") + } + if evict { + t.Errorf("nothing should be evicted here") + } + + l.Add(3, 3) + contains, evict = l.ContainsOrAdd(1, 1) + if contains { + t.Errorf("1 should not have been contained") + } + if !evict { + t.Errorf("an eviction should have occurred") + } + if !l.Contains(1) { + t.Errorf("now 1 should be contained") + } +} + +// test that Peek doesn't update recent-ness +func TestLRUPeek(t *testing.T) { + l, err := New(2) + if err != nil { + t.Fatalf("err: %v", err) + } + + l.Add(1, 1) + l.Add(2, 2) + if v, ok := l.Peek(1); !ok || v != 1 { + t.Errorf("1 should be set to 1: %v, %v", v, ok) + } + + l.Add(3, 3) + if l.Contains(1) { + t.Errorf("should not have updated recent-ness of 1") + } +} diff --git a/vendor/github.com/hashicorp/golang-lru/simplelru/lru.go b/vendor/github.com/hashicorp/golang-lru/simplelru/lru.go new file mode 100644 index 00000000..5673773b --- /dev/null +++ b/vendor/github.com/hashicorp/golang-lru/simplelru/lru.go @@ -0,0 +1,161 @@ +package simplelru + +import ( + "container/list" + "errors" +) + +// EvictCallback is used to get a callback when a cache entry is evicted +type EvictCallback func(key interface{}, value interface{}) + +// LRU implements a non-thread safe fixed size LRU cache +type LRU struct { + size int + evictList *list.List + items map[interface{}]*list.Element + onEvict EvictCallback +} + +// entry is used to hold a value in the evictList +type entry struct { + key interface{} + value interface{} +} + +// NewLRU constructs an LRU of the given size +func NewLRU(size int, onEvict EvictCallback) (*LRU, error) { + if size <= 0 { + return nil, errors.New("Must provide a positive size") + } + c := &LRU{ + size: size, + evictList: list.New(), + items: make(map[interface{}]*list.Element), + onEvict: onEvict, + } + return c, nil +} + +// Purge is used to completely clear the cache. +func (c *LRU) Purge() { + for k, v := range c.items { + if c.onEvict != nil { + c.onEvict(k, v.Value.(*entry).value) + } + delete(c.items, k) + } + c.evictList.Init() +} + +// Add adds a value to the cache. Returns true if an eviction occurred. +func (c *LRU) Add(key, value interface{}) (evicted bool) { + // Check for existing item + if ent, ok := c.items[key]; ok { + c.evictList.MoveToFront(ent) + ent.Value.(*entry).value = value + return false + } + + // Add new item + ent := &entry{key, value} + entry := c.evictList.PushFront(ent) + c.items[key] = entry + + evict := c.evictList.Len() > c.size + // Verify size not exceeded + if evict { + c.removeOldest() + } + return evict +} + +// Get looks up a key's value from the cache. +func (c *LRU) Get(key interface{}) (value interface{}, ok bool) { + if ent, ok := c.items[key]; ok { + c.evictList.MoveToFront(ent) + return ent.Value.(*entry).value, true + } + return +} + +// Contains checks if a key is in the cache, without updating the recent-ness +// or deleting it for being stale. +func (c *LRU) Contains(key interface{}) (ok bool) { + _, ok = c.items[key] + return ok +} + +// Peek returns the key value (or undefined if not found) without updating +// the "recently used"-ness of the key. +func (c *LRU) Peek(key interface{}) (value interface{}, ok bool) { + var ent *list.Element + if ent, ok = c.items[key]; ok { + return ent.Value.(*entry).value, true + } + return nil, ok +} + +// Remove removes the provided key from the cache, returning if the +// key was contained. +func (c *LRU) Remove(key interface{}) (present bool) { + if ent, ok := c.items[key]; ok { + c.removeElement(ent) + return true + } + return false +} + +// RemoveOldest removes the oldest item from the cache. +func (c *LRU) RemoveOldest() (key interface{}, value interface{}, ok bool) { + ent := c.evictList.Back() + if ent != nil { + c.removeElement(ent) + kv := ent.Value.(*entry) + return kv.key, kv.value, true + } + return nil, nil, false +} + +// GetOldest returns the oldest entry +func (c *LRU) GetOldest() (key interface{}, value interface{}, ok bool) { + ent := c.evictList.Back() + if ent != nil { + kv := ent.Value.(*entry) + return kv.key, kv.value, true + } + return nil, nil, false +} + +// Keys returns a slice of the keys in the cache, from oldest to newest. +func (c *LRU) Keys() []interface{} { + keys := make([]interface{}, len(c.items)) + i := 0 + for ent := c.evictList.Back(); ent != nil; ent = ent.Prev() { + keys[i] = ent.Value.(*entry).key + i++ + } + return keys +} + +// Len returns the number of items in the cache. +func (c *LRU) Len() int { + return c.evictList.Len() +} + +// removeOldest removes the oldest item from the cache. +func (c *LRU) removeOldest() { + ent := c.evictList.Back() + if ent != nil { + c.removeElement(ent) + } +} + +// removeElement is used to remove a given list element from the cache +func (c *LRU) removeElement(e *list.Element) { + c.evictList.Remove(e) + kv := e.Value.(*entry) + delete(c.items, kv.key) + if c.onEvict != nil { + c.onEvict(kv.key, kv.value) + } +} diff --git a/vendor/github.com/hashicorp/golang-lru/simplelru/lru_interface.go b/vendor/github.com/hashicorp/golang-lru/simplelru/lru_interface.go new file mode 100644 index 00000000..74c70774 --- /dev/null +++ b/vendor/github.com/hashicorp/golang-lru/simplelru/lru_interface.go @@ -0,0 +1,36 @@ +package simplelru + +// LRUCache is the interface for simple LRU cache. +type LRUCache interface { + // Adds a value to the cache, returns true if an eviction occurred and + // updates the "recently used"-ness of the key. + Add(key, value interface{}) bool + + // Returns key's value from the cache and + // updates the "recently used"-ness of the key. #value, isFound + Get(key interface{}) (value interface{}, ok bool) + + // Check if a key exsists in cache without updating the recent-ness. + Contains(key interface{}) (ok bool) + + // Returns key's value without updating the "recently used"-ness of the key. + Peek(key interface{}) (value interface{}, ok bool) + + // Removes a key from the cache. + Remove(key interface{}) bool + + // Removes the oldest entry from cache. + RemoveOldest() (interface{}, interface{}, bool) + + // Returns the oldest entry from the cache. #key, value, isFound + GetOldest() (interface{}, interface{}, bool) + + // Returns a slice of the keys in the cache, from oldest to newest. + Keys() []interface{} + + // Returns the number of items in the cache. + Len() int + + // Clear all cache entries + Purge() +} diff --git a/vendor/github.com/hashicorp/golang-lru/simplelru/lru_test.go b/vendor/github.com/hashicorp/golang-lru/simplelru/lru_test.go new file mode 100644 index 00000000..ca5676e1 --- /dev/null +++ b/vendor/github.com/hashicorp/golang-lru/simplelru/lru_test.go @@ -0,0 +1,167 @@ +package simplelru + +import "testing" + +func TestLRU(t *testing.T) { + evictCounter := 0 + onEvicted := func(k interface{}, v interface{}) { + if k != v { + t.Fatalf("Evict values not equal (%v!=%v)", k, v) + } + evictCounter++ + } + l, err := NewLRU(128, onEvicted) + if err != nil { + t.Fatalf("err: %v", err) + } + + for i := 0; i < 256; i++ { + l.Add(i, i) + } + if l.Len() != 128 { + t.Fatalf("bad len: %v", l.Len()) + } + + if evictCounter != 128 { + t.Fatalf("bad evict count: %v", evictCounter) + } + + for i, k := range l.Keys() { + if v, ok := l.Get(k); !ok || v != k || v != i+128 { + t.Fatalf("bad key: %v", k) + } + } + for i := 0; i < 128; i++ { + _, ok := l.Get(i) + if ok { + t.Fatalf("should be evicted") + } + } + for i := 128; i < 256; i++ { + _, ok := l.Get(i) + if !ok { + t.Fatalf("should not be evicted") + } + } + for i := 128; i < 192; i++ { + ok := l.Remove(i) + if !ok { + t.Fatalf("should be contained") + } + ok = l.Remove(i) + if ok { + t.Fatalf("should not be contained") + } + _, ok = l.Get(i) + if ok { + t.Fatalf("should be deleted") + } + } + + l.Get(192) // expect 192 to be last key in l.Keys() + + for i, k := range l.Keys() { + if (i < 63 && k != i+193) || (i == 63 && k != 192) { + t.Fatalf("out of order key: %v", k) + } + } + + l.Purge() + if l.Len() != 0 { + t.Fatalf("bad len: %v", l.Len()) + } + if _, ok := l.Get(200); ok { + t.Fatalf("should contain nothing") + } +} + +func TestLRU_GetOldest_RemoveOldest(t *testing.T) { + l, err := NewLRU(128, nil) + if err != nil { + t.Fatalf("err: %v", err) + } + for i := 0; i < 256; i++ { + l.Add(i, i) + } + k, _, ok := l.GetOldest() + if !ok { + t.Fatalf("missing") + } + if k.(int) != 128 { + t.Fatalf("bad: %v", k) + } + + k, _, ok = l.RemoveOldest() + if !ok { + t.Fatalf("missing") + } + if k.(int) != 128 { + t.Fatalf("bad: %v", k) + } + + k, _, ok = l.RemoveOldest() + if !ok { + t.Fatalf("missing") + } + if k.(int) != 129 { + t.Fatalf("bad: %v", k) + } +} + +// Test that Add returns true/false if an eviction occurred +func TestLRU_Add(t *testing.T) { + evictCounter := 0 + onEvicted := func(k interface{}, v interface{}) { + evictCounter++ + } + + l, err := NewLRU(1, onEvicted) + if err != nil { + t.Fatalf("err: %v", err) + } + + if l.Add(1, 1) == true || evictCounter != 0 { + t.Errorf("should not have an eviction") + } + if l.Add(2, 2) == false || evictCounter != 1 { + t.Errorf("should have an eviction") + } +} + +// Test that Contains doesn't update recent-ness +func TestLRU_Contains(t *testing.T) { + l, err := NewLRU(2, nil) + if err != nil { + t.Fatalf("err: %v", err) + } + + l.Add(1, 1) + l.Add(2, 2) + if !l.Contains(1) { + t.Errorf("1 should be contained") + } + + l.Add(3, 3) + if l.Contains(1) { + t.Errorf("Contains should not have updated recent-ness of 1") + } +} + +// Test that Peek doesn't update recent-ness +func TestLRU_Peek(t *testing.T) { + l, err := NewLRU(2, nil) + if err != nil { + t.Fatalf("err: %v", err) + } + + l.Add(1, 1) + l.Add(2, 2) + if v, ok := l.Peek(1); !ok || v != 1 { + t.Errorf("1 should be set to 1: %v, %v", v, ok) + } + + l.Add(3, 3) + if l.Contains(1) { + t.Errorf("should not have updated recent-ness of 1") + } +} diff --git a/vendor/github.com/hashicorp/yamux/.gitignore b/vendor/github.com/hashicorp/yamux/.gitignore new file mode 100644 index 00000000..83656241 --- /dev/null +++ b/vendor/github.com/hashicorp/yamux/.gitignore @@ -0,0 +1,23 @@ +# Compiled Object files, Static and Dynamic libs (Shared Objects) +*.o +*.a +*.so + +# Folders +_obj +_test + +# Architecture specific extensions/prefixes +*.[568vq] +[568vq].out + +*.cgo1.go +*.cgo2.c +_cgo_defun.c +_cgo_gotypes.go +_cgo_export.* + +_testmain.go + +*.exe +*.test diff --git a/vendor/github.com/hashicorp/yamux/LICENSE b/vendor/github.com/hashicorp/yamux/LICENSE new file mode 100644 index 00000000..f0e5c79e --- /dev/null +++ b/vendor/github.com/hashicorp/yamux/LICENSE @@ -0,0 +1,362 @@ +Mozilla Public License, version 2.0 + +1. Definitions + +1.1. "Contributor" + + means each individual or legal entity that creates, contributes to the + creation of, or owns Covered Software. + +1.2. "Contributor Version" + + means the combination of the Contributions of others (if any) used by a + Contributor and that particular Contributor's Contribution. + +1.3. "Contribution" + + means Covered Software of a particular Contributor. + +1.4. "Covered Software" + + means Source Code Form to which the initial Contributor has attached the + notice in Exhibit A, the Executable Form of such Source Code Form, and + Modifications of such Source Code Form, in each case including portions + thereof. + +1.5. "Incompatible With Secondary Licenses" + means + + a. that the initial Contributor has attached the notice described in + Exhibit B to the Covered Software; or + + b. that the Covered Software was made available under the terms of + version 1.1 or earlier of the License, but not also under the terms of + a Secondary License. + +1.6. "Executable Form" + + means any form of the work other than Source Code Form. + +1.7. "Larger Work" + + means a work that combines Covered Software with other material, in a + separate file or files, that is not Covered Software. + +1.8. "License" + + means this document. + +1.9. "Licensable" + + means having the right to grant, to the maximum extent possible, whether + at the time of the initial grant or subsequently, any and all of the + rights conveyed by this License. + +1.10. "Modifications" + + means any of the following: + + a. any file in Source Code Form that results from an addition to, + deletion from, or modification of the contents of Covered Software; or + + b. any new file in Source Code Form that contains any Covered Software. + +1.11. "Patent Claims" of a Contributor + + means any patent claim(s), including without limitation, method, + process, and apparatus claims, in any patent Licensable by such + Contributor that would be infringed, but for the grant of the License, + by the making, using, selling, offering for sale, having made, import, + or transfer of either its Contributions or its Contributor Version. + +1.12. "Secondary License" + + means either the GNU General Public License, Version 2.0, the GNU Lesser + General Public License, Version 2.1, the GNU Affero General Public + License, Version 3.0, or any later versions of those licenses. + +1.13. "Source Code Form" + + means the form of the work preferred for making modifications. + +1.14. "You" (or "Your") + + means an individual or a legal entity exercising rights under this + License. For legal entities, "You" includes any entity that controls, is + controlled by, or is under common control with You. For purposes of this + definition, "control" means (a) the power, direct or indirect, to cause + the direction or management of such entity, whether by contract or + otherwise, or (b) ownership of more than fifty percent (50%) of the + outstanding shares or beneficial ownership of such entity. + + +2. License Grants and Conditions + +2.1. Grants + + Each Contributor hereby grants You a world-wide, royalty-free, + non-exclusive license: + + a. under intellectual property rights (other than patent or trademark) + Licensable by such Contributor to use, reproduce, make available, + modify, display, perform, distribute, and otherwise exploit its + Contributions, either on an unmodified basis, with Modifications, or + as part of a Larger Work; and + + b. under Patent Claims of such Contributor to make, use, sell, offer for + sale, have made, import, and otherwise transfer either its + Contributions or its Contributor Version. + +2.2. Effective Date + + The licenses granted in Section 2.1 with respect to any Contribution + become effective for each Contribution on the date the Contributor first + distributes such Contribution. + +2.3. Limitations on Grant Scope + + The licenses granted in this Section 2 are the only rights granted under + this License. No additional rights or licenses will be implied from the + distribution or licensing of Covered Software under this License. + Notwithstanding Section 2.1(b) above, no patent license is granted by a + Contributor: + + a. for any code that a Contributor has removed from Covered Software; or + + b. for infringements caused by: (i) Your and any other third party's + modifications of Covered Software, or (ii) the combination of its + Contributions with other software (except as part of its Contributor + Version); or + + c. under Patent Claims infringed by Covered Software in the absence of + its Contributions. + + This License does not grant any rights in the trademarks, service marks, + or logos of any Contributor (except as may be necessary to comply with + the notice requirements in Section 3.4). + +2.4. Subsequent Licenses + + No Contributor makes additional grants as a result of Your choice to + distribute the Covered Software under a subsequent version of this + License (see Section 10.2) or under the terms of a Secondary License (if + permitted under the terms of Section 3.3). + +2.5. Representation + + Each Contributor represents that the Contributor believes its + Contributions are its original creation(s) or it has sufficient rights to + grant the rights to its Contributions conveyed by this License. + +2.6. Fair Use + + This License is not intended to limit any rights You have under + applicable copyright doctrines of fair use, fair dealing, or other + equivalents. + +2.7. Conditions + + Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in + Section 2.1. + + +3. Responsibilities + +3.1. Distribution of Source Form + + All distribution of Covered Software in Source Code Form, including any + Modifications that You create or to which You contribute, must be under + the terms of this License. You must inform recipients that the Source + Code Form of the Covered Software is governed by the terms of this + License, and how they can obtain a copy of this License. You may not + attempt to alter or restrict the recipients' rights in the Source Code + Form. + +3.2. Distribution of Executable Form + + If You distribute Covered Software in Executable Form then: + + a. such Covered Software must also be made available in Source Code Form, + as described in Section 3.1, and You must inform recipients of the + Executable Form how they can obtain a copy of such Source Code Form by + reasonable means in a timely manner, at a charge no more than the cost + of distribution to the recipient; and + + b. You may distribute such Executable Form under the terms of this + License, or sublicense it under different terms, provided that the + license for the Executable Form does not attempt to limit or alter the + recipients' rights in the Source Code Form under this License. + +3.3. Distribution of a Larger Work + + You may create and distribute a Larger Work under terms of Your choice, + provided that You also comply with the requirements of this License for + the Covered Software. If the Larger Work is a combination of Covered + Software with a work governed by one or more Secondary Licenses, and the + Covered Software is not Incompatible With Secondary Licenses, this + License permits You to additionally distribute such Covered Software + under the terms of such Secondary License(s), so that the recipient of + the Larger Work may, at their option, further distribute the Covered + Software under the terms of either this License or such Secondary + License(s). + +3.4. Notices + + You may not remove or alter the substance of any license notices + (including copyright notices, patent notices, disclaimers of warranty, or + limitations of liability) contained within the Source Code Form of the + Covered Software, except that You may alter any license notices to the + extent required to remedy known factual inaccuracies. + +3.5. Application of Additional Terms + + You may choose to offer, and to charge a fee for, warranty, support, + indemnity or liability obligations to one or more recipients of Covered + Software. However, You may do so only on Your own behalf, and not on + behalf of any Contributor. You must make it absolutely clear that any + such warranty, support, indemnity, or liability obligation is offered by + You alone, and You hereby agree to indemnify every Contributor for any + liability incurred by such Contributor as a result of warranty, support, + indemnity or liability terms You offer. You may include additional + disclaimers of warranty and limitations of liability specific to any + jurisdiction. + +4. Inability to Comply Due to Statute or Regulation + + If it is impossible for You to comply with any of the terms of this License + with respect to some or all of the Covered Software due to statute, + judicial order, or regulation then You must: (a) comply with the terms of + this License to the maximum extent possible; and (b) describe the + limitations and the code they affect. Such description must be placed in a + text file included with all distributions of the Covered Software under + this License. Except to the extent prohibited by statute or regulation, + such description must be sufficiently detailed for a recipient of ordinary + skill to be able to understand it. + +5. Termination + +5.1. The rights granted under this License will terminate automatically if You + fail to comply with any of its terms. However, if You become compliant, + then the rights granted under this License from a particular Contributor + are reinstated (a) provisionally, unless and until such Contributor + explicitly and finally terminates Your grants, and (b) on an ongoing + basis, if such Contributor fails to notify You of the non-compliance by + some reasonable means prior to 60 days after You have come back into + compliance. Moreover, Your grants from a particular Contributor are + reinstated on an ongoing basis if such Contributor notifies You of the + non-compliance by some reasonable means, this is the first time You have + received notice of non-compliance with this License from such + Contributor, and You become compliant prior to 30 days after Your receipt + of the notice. + +5.2. If You initiate litigation against any entity by asserting a patent + infringement claim (excluding declaratory judgment actions, + counter-claims, and cross-claims) alleging that a Contributor Version + directly or indirectly infringes any patent, then the rights granted to + You by any and all Contributors for the Covered Software under Section + 2.1 of this License shall terminate. + +5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user + license agreements (excluding distributors and resellers) which have been + validly granted by You or Your distributors under this License prior to + termination shall survive termination. + +6. Disclaimer of Warranty + + Covered Software is provided under this License on an "as is" basis, + without warranty of any kind, either expressed, implied, or statutory, + including, without limitation, warranties that the Covered Software is free + of defects, merchantable, fit for a particular purpose or non-infringing. + The entire risk as to the quality and performance of the Covered Software + is with You. Should any Covered Software prove defective in any respect, + You (not any Contributor) assume the cost of any necessary servicing, + repair, or correction. This disclaimer of warranty constitutes an essential + part of this License. No use of any Covered Software is authorized under + this License except under this disclaimer. + +7. Limitation of Liability + + Under no circumstances and under no legal theory, whether tort (including + negligence), contract, or otherwise, shall any Contributor, or anyone who + distributes Covered Software as permitted above, be liable to You for any + direct, indirect, special, incidental, or consequential damages of any + character including, without limitation, damages for lost profits, loss of + goodwill, work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses, even if such party shall have been + informed of the possibility of such damages. This limitation of liability + shall not apply to liability for death or personal injury resulting from + such party's negligence to the extent applicable law prohibits such + limitation. Some jurisdictions do not allow the exclusion or limitation of + incidental or consequential damages, so this exclusion and limitation may + not apply to You. + +8. Litigation + + Any litigation relating to this License may be brought only in the courts + of a jurisdiction where the defendant maintains its principal place of + business and such litigation shall be governed by laws of that + jurisdiction, without reference to its conflict-of-law provisions. Nothing + in this Section shall prevent a party's ability to bring cross-claims or + counter-claims. + +9. Miscellaneous + + This License represents the complete agreement concerning the subject + matter hereof. If any provision of this License is held to be + unenforceable, such provision shall be reformed only to the extent + necessary to make it enforceable. Any law or regulation which provides that + the language of a contract shall be construed against the drafter shall not + be used to construe this License against a Contributor. + + +10. Versions of the License + +10.1. New Versions + + Mozilla Foundation is the license steward. Except as provided in Section + 10.3, no one other than the license steward has the right to modify or + publish new versions of this License. Each version will be given a + distinguishing version number. + +10.2. Effect of New Versions + + You may distribute the Covered Software under the terms of the version + of the License under which You originally received the Covered Software, + or under the terms of any subsequent version published by the license + steward. + +10.3. Modified Versions + + If you create software not governed by this License, and you want to + create a new license for such software, you may create and use a + modified version of this License if you rename the license and remove + any references to the name of the license steward (except to note that + such modified license differs from this License). + +10.4. Distributing Source Code Form that is Incompatible With Secondary + Licenses If You choose to distribute Source Code Form that is + Incompatible With Secondary Licenses under the terms of this version of + the License, the notice described in Exhibit B of this License must be + attached. + +Exhibit A - Source Code Form License Notice + + This Source Code Form is subject to the + terms of the Mozilla Public License, v. + 2.0. If a copy of the MPL was not + distributed with this file, You can + obtain one at + http://mozilla.org/MPL/2.0/. + +If it is not possible or desirable to put the notice in a particular file, +then You may include the notice in a location (such as a LICENSE file in a +relevant directory) where a recipient would be likely to look for such a +notice. + +You may add additional accurate notices of copyright ownership. + +Exhibit B - "Incompatible With Secondary Licenses" Notice + + This Source Code Form is "Incompatible + With Secondary Licenses", as defined by + the Mozilla Public License, v. 2.0. \ No newline at end of file diff --git a/vendor/github.com/hashicorp/yamux/README.md b/vendor/github.com/hashicorp/yamux/README.md new file mode 100644 index 00000000..d4db7fc9 --- /dev/null +++ b/vendor/github.com/hashicorp/yamux/README.md @@ -0,0 +1,86 @@ +# Yamux + +Yamux (Yet another Multiplexer) is a multiplexing library for Golang. +It relies on an underlying connection to provide reliability +and ordering, such as TCP or Unix domain sockets, and provides +stream-oriented multiplexing. It is inspired by SPDY but is not +interoperable with it. + +Yamux features include: + +* Bi-directional streams + * Streams can be opened by either client or server + * Useful for NAT traversal + * Server-side push support +* Flow control + * Avoid starvation + * Back-pressure to prevent overwhelming a receiver +* Keep Alives + * Enables persistent connections over a load balancer +* Efficient + * Enables thousands of logical streams with low overhead + +## Documentation + +For complete documentation, see the associated [Godoc](http://godoc.org/github.com/hashicorp/yamux). + +## Specification + +The full specification for Yamux is provided in the `spec.md` file. +It can be used as a guide to implementors of interoperable libraries. + +## Usage + +Using Yamux is remarkably simple: + +```go + +func client() { + // Get a TCP connection + conn, err := net.Dial(...) + if err != nil { + panic(err) + } + + // Setup client side of yamux + session, err := yamux.Client(conn, nil) + if err != nil { + panic(err) + } + + // Open a new stream + stream, err := session.Open() + if err != nil { + panic(err) + } + + // Stream implements net.Conn + stream.Write([]byte("ping")) +} + +func server() { + // Accept a TCP connection + conn, err := listener.Accept() + if err != nil { + panic(err) + } + + // Setup server side of yamux + session, err := yamux.Server(conn, nil) + if err != nil { + panic(err) + } + + // Accept a stream + stream, err := session.Accept() + if err != nil { + panic(err) + } + + // Listen for a message + buf := make([]byte, 4) + stream.Read(buf) +} + +``` + diff --git a/vendor/github.com/hashicorp/yamux/addr.go b/vendor/github.com/hashicorp/yamux/addr.go new file mode 100644 index 00000000..be6ebca9 --- /dev/null +++ b/vendor/github.com/hashicorp/yamux/addr.go @@ -0,0 +1,60 @@ +package yamux + +import ( + "fmt" + "net" +) + +// hasAddr is used to get the address from the underlying connection +type hasAddr interface { + LocalAddr() net.Addr + RemoteAddr() net.Addr +} + +// yamuxAddr is used when we cannot get the underlying address +type yamuxAddr struct { + Addr string +} + +func (*yamuxAddr) Network() string { + return "yamux" +} + +func (y *yamuxAddr) String() string { + return fmt.Sprintf("yamux:%s", y.Addr) +} + +// Addr is used to get the address of the listener. +func (s *Session) Addr() net.Addr { + return s.LocalAddr() +} + +// LocalAddr is used to get the local address of the +// underlying connection. +func (s *Session) LocalAddr() net.Addr { + addr, ok := s.conn.(hasAddr) + if !ok { + return &yamuxAddr{"local"} + } + return addr.LocalAddr() +} + +// RemoteAddr is used to get the address of remote end +// of the underlying connection +func (s *Session) RemoteAddr() net.Addr { + addr, ok := s.conn.(hasAddr) + if !ok { + return &yamuxAddr{"remote"} + } + return addr.RemoteAddr() +} + +// LocalAddr returns the local address +func (s *Stream) LocalAddr() net.Addr { + return s.session.LocalAddr() +} + +// LocalAddr returns the remote address +func (s *Stream) RemoteAddr() net.Addr { + return s.session.RemoteAddr() +} diff --git a/vendor/github.com/hashicorp/yamux/bench_test.go b/vendor/github.com/hashicorp/yamux/bench_test.go new file mode 100644 index 00000000..5fc1c550 --- /dev/null +++ b/vendor/github.com/hashicorp/yamux/bench_test.go @@ -0,0 +1,123 @@ +package yamux + +import ( + "testing" +) + +func BenchmarkPing(b *testing.B) { + client, server := testClientServer() + defer client.Close() + defer server.Close() + + for i := 0; i < b.N; i++ { + rtt, err := client.Ping() + if err != nil { + b.Fatalf("err: %v", err) + } + if rtt == 0 { + b.Fatalf("bad: %v", rtt) + } + } +} + +func BenchmarkAccept(b *testing.B) { + client, server := testClientServer() + defer client.Close() + defer server.Close() + + go func() { + for i := 0; i < b.N; i++ { + stream, err := server.AcceptStream() + if err != nil { + return + } + stream.Close() + } + }() + + for i := 0; i < b.N; i++ { + stream, err := client.Open() + if err != nil { + b.Fatalf("err: %v", err) + } + stream.Close() + } +} + +func BenchmarkSendRecv(b *testing.B) { + client, server := testClientServer() + defer client.Close() + defer server.Close() + + sendBuf := make([]byte, 512) + recvBuf := make([]byte, 512) + + doneCh := make(chan struct{}) + go func() { + stream, err := server.AcceptStream() + if err != nil { + return + } + defer stream.Close() + for i := 0; i < b.N; i++ { + if _, err := stream.Read(recvBuf); err != nil { + b.Fatalf("err: %v", err) + } + } + close(doneCh) + }() + + stream, err := client.Open() + if err != nil { + b.Fatalf("err: %v", err) + } + defer stream.Close() + for i := 0; i < b.N; i++ { + if _, err := stream.Write(sendBuf); err != nil { + b.Fatalf("err: %v", err) + } + } + <-doneCh +} + +func BenchmarkSendRecvLarge(b *testing.B) { + client, server := testClientServer() + defer client.Close() + defer server.Close() + const sendSize = 512 * 1024 * 1024 + const recvSize = 4 * 1024 + + sendBuf := make([]byte, sendSize) + recvBuf := make([]byte, recvSize) + + b.ResetTimer() + recvDone := make(chan struct{}) + + go func() { + stream, err := server.AcceptStream() + if err != nil { + return + } + defer stream.Close() + for i := 0; i < b.N; i++ { + for j := 0; j < sendSize/recvSize; j++ { + if _, err := stream.Read(recvBuf); err != nil { + b.Fatalf("err: %v", err) + } + } + } + close(recvDone) + }() + + stream, err := client.Open() + if err != nil { + b.Fatalf("err: %v", err) + } + defer stream.Close() + for i := 0; i < b.N; i++ { + if _, err := stream.Write(sendBuf); err != nil { + b.Fatalf("err: %v", err) + } + } + <-recvDone +} diff --git a/vendor/github.com/hashicorp/yamux/const.go b/vendor/github.com/hashicorp/yamux/const.go new file mode 100644 index 00000000..4f529382 --- /dev/null +++ b/vendor/github.com/hashicorp/yamux/const.go @@ -0,0 +1,157 @@ +package yamux + +import ( + "encoding/binary" + "fmt" +) + +var ( + // ErrInvalidVersion means we received a frame with an + // invalid version + ErrInvalidVersion = fmt.Errorf("invalid protocol version") + + // ErrInvalidMsgType means we received a frame with an + // invalid message type + ErrInvalidMsgType = fmt.Errorf("invalid msg type") + + // ErrSessionShutdown is used if there is a shutdown during + // an operation + ErrSessionShutdown = fmt.Errorf("session shutdown") + + // ErrStreamsExhausted is returned if we have no more + // stream ids to issue + ErrStreamsExhausted = fmt.Errorf("streams exhausted") + + // ErrDuplicateStream is used if a duplicate stream is + // opened inbound + ErrDuplicateStream = fmt.Errorf("duplicate stream initiated") + + // ErrReceiveWindowExceeded indicates the window was exceeded + ErrRecvWindowExceeded = fmt.Errorf("recv window exceeded") + + // ErrTimeout is used when we reach an IO deadline + ErrTimeout = fmt.Errorf("i/o deadline reached") + + // ErrStreamClosed is returned when using a closed stream + ErrStreamClosed = fmt.Errorf("stream closed") + + // ErrUnexpectedFlag is set when we get an unexpected flag + ErrUnexpectedFlag = fmt.Errorf("unexpected flag") + + // ErrRemoteGoAway is used when we get a go away from the other side + ErrRemoteGoAway = fmt.Errorf("remote end is not accepting connections") + + // ErrConnectionReset is sent if a stream is reset. This can happen + // if the backlog is exceeded, or if there was a remote GoAway. + ErrConnectionReset = fmt.Errorf("connection reset") + + // ErrConnectionWriteTimeout indicates that we hit the "safety valve" + // timeout writing to the underlying stream connection. + ErrConnectionWriteTimeout = fmt.Errorf("connection write timeout") + + // ErrKeepAliveTimeout is sent if a missed keepalive caused the stream close + ErrKeepAliveTimeout = fmt.Errorf("keepalive timeout") +) + +const ( + // protoVersion is the only version we support + protoVersion uint8 = 0 +) + +const ( + // Data is used for data frames. They are followed + // by length bytes worth of payload. + typeData uint8 = iota + + // WindowUpdate is used to change the window of + // a given stream. The length indicates the delta + // update to the window. + typeWindowUpdate + + // Ping is sent as a keep-alive or to measure + // the RTT. The StreamID and Length value are echoed + // back in the response. + typePing + + // GoAway is sent to terminate a session. The StreamID + // should be 0 and the length is an error code. + typeGoAway +) + +const ( + // SYN is sent to signal a new stream. May + // be sent with a data payload + flagSYN uint16 = 1 << iota + + // ACK is sent to acknowledge a new stream. May + // be sent with a data payload + flagACK + + // FIN is sent to half-close the given stream. + // May be sent with a data payload. + flagFIN + + // RST is used to hard close a given stream. + flagRST +) + +const ( + // initialStreamWindow is the initial stream window size + initialStreamWindow uint32 = 256 * 1024 +) + +const ( + // goAwayNormal is sent on a normal termination + goAwayNormal uint32 = iota + + // goAwayProtoErr sent on a protocol error + goAwayProtoErr + + // goAwayInternalErr sent on an internal error + goAwayInternalErr +) + +const ( + sizeOfVersion = 1 + sizeOfType = 1 + sizeOfFlags = 2 + sizeOfStreamID = 4 + sizeOfLength = 4 + headerSize = sizeOfVersion + sizeOfType + sizeOfFlags + + sizeOfStreamID + sizeOfLength +) + +type header []byte + +func (h header) Version() uint8 { + return h[0] +} + +func (h header) MsgType() uint8 { + return h[1] +} + +func (h header) Flags() uint16 { + return binary.BigEndian.Uint16(h[2:4]) +} + +func (h header) StreamID() uint32 { + return binary.BigEndian.Uint32(h[4:8]) +} + +func (h header) Length() uint32 { + return binary.BigEndian.Uint32(h[8:12]) +} + +func (h header) String() string { + return fmt.Sprintf("Vsn:%d Type:%d Flags:%d StreamID:%d Length:%d", + h.Version(), h.MsgType(), h.Flags(), h.StreamID(), h.Length()) +} + +func (h header) encode(msgType uint8, flags uint16, streamID uint32, length uint32) { + h[0] = protoVersion + h[1] = msgType + binary.BigEndian.PutUint16(h[2:4], flags) + binary.BigEndian.PutUint32(h[4:8], streamID) + binary.BigEndian.PutUint32(h[8:12], length) +} diff --git a/vendor/github.com/hashicorp/yamux/const_test.go b/vendor/github.com/hashicorp/yamux/const_test.go new file mode 100644 index 00000000..153da18b --- /dev/null +++ b/vendor/github.com/hashicorp/yamux/const_test.go @@ -0,0 +1,72 @@ +package yamux + +import ( + "testing" +) + +func TestConst(t *testing.T) { + if protoVersion != 0 { + t.Fatalf("bad: %v", protoVersion) + } + + if typeData != 0 { + t.Fatalf("bad: %v", typeData) + } + if typeWindowUpdate != 1 { + t.Fatalf("bad: %v", typeWindowUpdate) + } + if typePing != 2 { + t.Fatalf("bad: %v", typePing) + } + if typeGoAway != 3 { + t.Fatalf("bad: %v", typeGoAway) + } + + if flagSYN != 1 { + t.Fatalf("bad: %v", flagSYN) + } + if flagACK != 2 { + t.Fatalf("bad: %v", flagACK) + } + if flagFIN != 4 { + t.Fatalf("bad: %v", flagFIN) + } + if flagRST != 8 { + t.Fatalf("bad: %v", flagRST) + } + + if goAwayNormal != 0 { + t.Fatalf("bad: %v", goAwayNormal) + } + if goAwayProtoErr != 1 { + t.Fatalf("bad: %v", goAwayProtoErr) + } + if goAwayInternalErr != 2 { + t.Fatalf("bad: %v", goAwayInternalErr) + } + + if headerSize != 12 { + t.Fatalf("bad header size") + } +} + +func TestEncodeDecode(t *testing.T) { + hdr := header(make([]byte, headerSize)) + hdr.encode(typeWindowUpdate, flagACK|flagRST, 1234, 4321) + + if hdr.Version() != protoVersion { + t.Fatalf("bad: %v", hdr) + } + if hdr.MsgType() != typeWindowUpdate { + t.Fatalf("bad: %v", hdr) + } + if hdr.Flags() != flagACK|flagRST { + t.Fatalf("bad: %v", hdr) + } + if hdr.StreamID() != 1234 { + t.Fatalf("bad: %v", hdr) + } + if hdr.Length() != 4321 { + t.Fatalf("bad: %v", hdr) + } +} diff --git a/vendor/github.com/hashicorp/yamux/mux.go b/vendor/github.com/hashicorp/yamux/mux.go new file mode 100644 index 00000000..7abc7c74 --- /dev/null +++ b/vendor/github.com/hashicorp/yamux/mux.go @@ -0,0 +1,87 @@ +package yamux + +import ( + "fmt" + "io" + "os" + "time" +) + +// Config is used to tune the Yamux session +type Config struct { + // AcceptBacklog is used to limit how many streams may be + // waiting an accept. + AcceptBacklog int + + // EnableKeepalive is used to do a period keep alive + // messages using a ping. + EnableKeepAlive bool + + // KeepAliveInterval is how often to perform the keep alive + KeepAliveInterval time.Duration + + // ConnectionWriteTimeout is meant to be a "safety valve" timeout after + // we which will suspect a problem with the underlying connection and + // close it. This is only applied to writes, where's there's generally + // an expectation that things will move along quickly. + ConnectionWriteTimeout time.Duration + + // MaxStreamWindowSize is used to control the maximum + // window size that we allow for a stream. + MaxStreamWindowSize uint32 + + // LogOutput is used to control the log destination + LogOutput io.Writer +} + +// DefaultConfig is used to return a default configuration +func DefaultConfig() *Config { + return &Config{ + AcceptBacklog: 256, + EnableKeepAlive: true, + KeepAliveInterval: 30 * time.Second, + ConnectionWriteTimeout: 10 * time.Second, + MaxStreamWindowSize: initialStreamWindow, + LogOutput: os.Stderr, + } +} + +// VerifyConfig is used to verify the sanity of configuration +func VerifyConfig(config *Config) error { + if config.AcceptBacklog <= 0 { + return fmt.Errorf("backlog must be positive") + } + if config.KeepAliveInterval == 0 { + return fmt.Errorf("keep-alive interval must be positive") + } + if config.MaxStreamWindowSize < initialStreamWindow { + return fmt.Errorf("MaxStreamWindowSize must be larger than %d", initialStreamWindow) + } + return nil +} + +// Server is used to initialize a new server-side connection. +// There must be at most one server-side connection. If a nil config is +// provided, the DefaultConfiguration will be used. +func Server(conn io.ReadWriteCloser, config *Config) (*Session, error) { + if config == nil { + config = DefaultConfig() + } + if err := VerifyConfig(config); err != nil { + return nil, err + } + return newSession(config, conn, false), nil +} + +// Client is used to initialize a new client-side connection. +// There must be at most one client-side connection. +func Client(conn io.ReadWriteCloser, config *Config) (*Session, error) { + if config == nil { + config = DefaultConfig() + } + + if err := VerifyConfig(config); err != nil { + return nil, err + } + return newSession(config, conn, true), nil +} diff --git a/vendor/github.com/hashicorp/yamux/session.go b/vendor/github.com/hashicorp/yamux/session.go new file mode 100644 index 00000000..32ba02e0 --- /dev/null +++ b/vendor/github.com/hashicorp/yamux/session.go @@ -0,0 +1,648 @@ +package yamux + +import ( + "bufio" + "fmt" + "io" + "io/ioutil" + "log" + "math" + "net" + "strings" + "sync" + "sync/atomic" + "time" +) + +// Session is used to wrap a reliable ordered connection and to +// multiplex it into multiple streams. +type Session struct { + // remoteGoAway indicates the remote side does + // not want futher connections. Must be first for alignment. + remoteGoAway int32 + + // localGoAway indicates that we should stop + // accepting futher connections. Must be first for alignment. + localGoAway int32 + + // nextStreamID is the next stream we should + // send. This depends if we are a client/server. + nextStreamID uint32 + + // config holds our configuration + config *Config + + // logger is used for our logs + logger *log.Logger + + // conn is the underlying connection + conn io.ReadWriteCloser + + // bufRead is a buffered reader + bufRead *bufio.Reader + + // pings is used to track inflight pings + pings map[uint32]chan struct{} + pingID uint32 + pingLock sync.Mutex + + // streams maps a stream id to a stream, and inflight has an entry + // for any outgoing stream that has not yet been established. Both are + // protected by streamLock. + streams map[uint32]*Stream + inflight map[uint32]struct{} + streamLock sync.Mutex + + // synCh acts like a semaphore. It is sized to the AcceptBacklog which + // is assumed to be symmetric between the client and server. This allows + // the client to avoid exceeding the backlog and instead blocks the open. + synCh chan struct{} + + // acceptCh is used to pass ready streams to the client + acceptCh chan *Stream + + // sendCh is used to mark a stream as ready to send, + // or to send a header out directly. + sendCh chan sendReady + + // recvDoneCh is closed when recv() exits to avoid a race + // between stream registration and stream shutdown + recvDoneCh chan struct{} + + // shutdown is used to safely close a session + shutdown bool + shutdownErr error + shutdownCh chan struct{} + shutdownLock sync.Mutex +} + +// sendReady is used to either mark a stream as ready +// or to directly send a header +type sendReady struct { + Hdr []byte + Body io.Reader + Err chan error +} + +// newSession is used to construct a new session +func newSession(config *Config, conn io.ReadWriteCloser, client bool) *Session { + s := &Session{ + config: config, + logger: log.New(config.LogOutput, "", log.LstdFlags), + conn: conn, + bufRead: bufio.NewReader(conn), + pings: make(map[uint32]chan struct{}), + streams: make(map[uint32]*Stream), + inflight: make(map[uint32]struct{}), + synCh: make(chan struct{}, config.AcceptBacklog), + acceptCh: make(chan *Stream, config.AcceptBacklog), + sendCh: make(chan sendReady, 64), + recvDoneCh: make(chan struct{}), + shutdownCh: make(chan struct{}), + } + if client { + s.nextStreamID = 1 + } else { + s.nextStreamID = 2 + } + go s.recv() + go s.send() + if config.EnableKeepAlive { + go s.keepalive() + } + return s +} + +// IsClosed does a safe check to see if we have shutdown +func (s *Session) IsClosed() bool { + select { + case <-s.shutdownCh: + return true + default: + return false + } +} + +// CloseChan returns a read-only channel which is closed as +// soon as the session is closed. +func (s *Session) CloseChan() <-chan struct{} { + return s.shutdownCh +} + +// NumStreams returns the number of currently open streams +func (s *Session) NumStreams() int { + s.streamLock.Lock() + num := len(s.streams) + s.streamLock.Unlock() + return num +} + +// Open is used to create a new stream as a net.Conn +func (s *Session) Open() (net.Conn, error) { + conn, err := s.OpenStream() + if err != nil { + return nil, err + } + return conn, nil +} + +// OpenStream is used to create a new stream +func (s *Session) OpenStream() (*Stream, error) { + if s.IsClosed() { + return nil, ErrSessionShutdown + } + if atomic.LoadInt32(&s.remoteGoAway) == 1 { + return nil, ErrRemoteGoAway + } + + // Block if we have too many inflight SYNs + select { + case s.synCh <- struct{}{}: + case <-s.shutdownCh: + return nil, ErrSessionShutdown + } + +GET_ID: + // Get an ID, and check for stream exhaustion + id := atomic.LoadUint32(&s.nextStreamID) + if id >= math.MaxUint32-1 { + return nil, ErrStreamsExhausted + } + if !atomic.CompareAndSwapUint32(&s.nextStreamID, id, id+2) { + goto GET_ID + } + + // Register the stream + stream := newStream(s, id, streamInit) + s.streamLock.Lock() + s.streams[id] = stream + s.inflight[id] = struct{}{} + s.streamLock.Unlock() + + // Send the window update to create + if err := stream.sendWindowUpdate(); err != nil { + select { + case <-s.synCh: + default: + s.logger.Printf("[ERR] yamux: aborted stream open without inflight syn semaphore") + } + return nil, err + } + return stream, nil +} + +// Accept is used to block until the next available stream +// is ready to be accepted. +func (s *Session) Accept() (net.Conn, error) { + conn, err := s.AcceptStream() + if err != nil { + return nil, err + } + return conn, err +} + +// AcceptStream is used to block until the next available stream +// is ready to be accepted. +func (s *Session) AcceptStream() (*Stream, error) { + select { + case stream := <-s.acceptCh: + if err := stream.sendWindowUpdate(); err != nil { + return nil, err + } + return stream, nil + case <-s.shutdownCh: + return nil, s.shutdownErr + } +} + +// Close is used to close the session and all streams. +// Attempts to send a GoAway before closing the connection. +func (s *Session) Close() error { + s.shutdownLock.Lock() + defer s.shutdownLock.Unlock() + + if s.shutdown { + return nil + } + s.shutdown = true + if s.shutdownErr == nil { + s.shutdownErr = ErrSessionShutdown + } + close(s.shutdownCh) + s.conn.Close() + <-s.recvDoneCh + + s.streamLock.Lock() + defer s.streamLock.Unlock() + for _, stream := range s.streams { + stream.forceClose() + } + return nil +} + +// exitErr is used to handle an error that is causing the +// session to terminate. +func (s *Session) exitErr(err error) { + s.shutdownLock.Lock() + if s.shutdownErr == nil { + s.shutdownErr = err + } + s.shutdownLock.Unlock() + s.Close() +} + +// GoAway can be used to prevent accepting further +// connections. It does not close the underlying conn. +func (s *Session) GoAway() error { + return s.waitForSend(s.goAway(goAwayNormal), nil) +} + +// goAway is used to send a goAway message +func (s *Session) goAway(reason uint32) header { + atomic.SwapInt32(&s.localGoAway, 1) + hdr := header(make([]byte, headerSize)) + hdr.encode(typeGoAway, 0, 0, reason) + return hdr +} + +// Ping is used to measure the RTT response time +func (s *Session) Ping() (time.Duration, error) { + // Get a channel for the ping + ch := make(chan struct{}) + + // Get a new ping id, mark as pending + s.pingLock.Lock() + id := s.pingID + s.pingID++ + s.pings[id] = ch + s.pingLock.Unlock() + + // Send the ping request + hdr := header(make([]byte, headerSize)) + hdr.encode(typePing, flagSYN, 0, id) + if err := s.waitForSend(hdr, nil); err != nil { + return 0, err + } + + // Wait for a response + start := time.Now() + select { + case <-ch: + case <-time.After(s.config.ConnectionWriteTimeout): + s.pingLock.Lock() + delete(s.pings, id) // Ignore it if a response comes later. + s.pingLock.Unlock() + return 0, ErrTimeout + case <-s.shutdownCh: + return 0, ErrSessionShutdown + } + + // Compute the RTT + return time.Now().Sub(start), nil +} + +// keepalive is a long running goroutine that periodically does +// a ping to keep the connection alive. +func (s *Session) keepalive() { + for { + select { + case <-time.After(s.config.KeepAliveInterval): + _, err := s.Ping() + if err != nil { + if err != ErrSessionShutdown { + s.logger.Printf("[ERR] yamux: keepalive failed: %v", err) + s.exitErr(ErrKeepAliveTimeout) + } + return + } + case <-s.shutdownCh: + return + } + } +} + +// waitForSendErr waits to send a header, checking for a potential shutdown +func (s *Session) waitForSend(hdr header, body io.Reader) error { + errCh := make(chan error, 1) + return s.waitForSendErr(hdr, body, errCh) +} + +// waitForSendErr waits to send a header with optional data, checking for a +// potential shutdown. Since there's the expectation that sends can happen +// in a timely manner, we enforce the connection write timeout here. +func (s *Session) waitForSendErr(hdr header, body io.Reader, errCh chan error) error { + t := timerPool.Get() + timer := t.(*time.Timer) + timer.Reset(s.config.ConnectionWriteTimeout) + defer func() { + timer.Stop() + select { + case <-timer.C: + default: + } + timerPool.Put(t) + }() + + ready := sendReady{Hdr: hdr, Body: body, Err: errCh} + select { + case s.sendCh <- ready: + case <-s.shutdownCh: + return ErrSessionShutdown + case <-timer.C: + return ErrConnectionWriteTimeout + } + + select { + case err := <-errCh: + return err + case <-s.shutdownCh: + return ErrSessionShutdown + case <-timer.C: + return ErrConnectionWriteTimeout + } +} + +// sendNoWait does a send without waiting. Since there's the expectation that +// the send happens right here, we enforce the connection write timeout if we +// can't queue the header to be sent. +func (s *Session) sendNoWait(hdr header) error { + t := timerPool.Get() + timer := t.(*time.Timer) + timer.Reset(s.config.ConnectionWriteTimeout) + defer func() { + timer.Stop() + select { + case <-timer.C: + default: + } + timerPool.Put(t) + }() + + select { + case s.sendCh <- sendReady{Hdr: hdr}: + return nil + case <-s.shutdownCh: + return ErrSessionShutdown + case <-timer.C: + return ErrConnectionWriteTimeout + } +} + +// send is a long running goroutine that sends data +func (s *Session) send() { + for { + select { + case ready := <-s.sendCh: + // Send a header if ready + if ready.Hdr != nil { + sent := 0 + for sent < len(ready.Hdr) { + n, err := s.conn.Write(ready.Hdr[sent:]) + if err != nil { + s.logger.Printf("[ERR] yamux: Failed to write header: %v", err) + asyncSendErr(ready.Err, err) + s.exitErr(err) + return + } + sent += n + } + } + + // Send data from a body if given + if ready.Body != nil { + _, err := io.Copy(s.conn, ready.Body) + if err != nil { + s.logger.Printf("[ERR] yamux: Failed to write body: %v", err) + asyncSendErr(ready.Err, err) + s.exitErr(err) + return + } + } + + // No error, successful send + asyncSendErr(ready.Err, nil) + case <-s.shutdownCh: + return + } + } +} + +// recv is a long running goroutine that accepts new data +func (s *Session) recv() { + if err := s.recvLoop(); err != nil { + s.exitErr(err) + } +} + +// Ensure that the index of the handler (typeData/typeWindowUpdate/etc) matches the message type +var ( + handlers = []func(*Session, header) error{ + typeData: (*Session).handleStreamMessage, + typeWindowUpdate: (*Session).handleStreamMessage, + typePing: (*Session).handlePing, + typeGoAway: (*Session).handleGoAway, + } +) + +// recvLoop continues to receive data until a fatal error is encountered +func (s *Session) recvLoop() error { + defer close(s.recvDoneCh) + hdr := header(make([]byte, headerSize)) + for { + // Read the header + if _, err := io.ReadFull(s.bufRead, hdr); err != nil { + if err != io.EOF && !strings.Contains(err.Error(), "closed") && !strings.Contains(err.Error(), "reset by peer") { + s.logger.Printf("[ERR] yamux: Failed to read header: %v", err) + } + return err + } + + // Verify the version + if hdr.Version() != protoVersion { + s.logger.Printf("[ERR] yamux: Invalid protocol version: %d", hdr.Version()) + return ErrInvalidVersion + } + + mt := hdr.MsgType() + if mt < typeData || mt > typeGoAway { + return ErrInvalidMsgType + } + + if err := handlers[mt](s, hdr); err != nil { + return err + } + } +} + +// handleStreamMessage handles either a data or window update frame +func (s *Session) handleStreamMessage(hdr header) error { + // Check for a new stream creation + id := hdr.StreamID() + flags := hdr.Flags() + if flags&flagSYN == flagSYN { + if err := s.incomingStream(id); err != nil { + return err + } + } + + // Get the stream + s.streamLock.Lock() + stream := s.streams[id] + s.streamLock.Unlock() + + // If we do not have a stream, likely we sent a RST + if stream == nil { + // Drain any data on the wire + if hdr.MsgType() == typeData && hdr.Length() > 0 { + s.logger.Printf("[WARN] yamux: Discarding data for stream: %d", id) + if _, err := io.CopyN(ioutil.Discard, s.bufRead, int64(hdr.Length())); err != nil { + s.logger.Printf("[ERR] yamux: Failed to discard data: %v", err) + return nil + } + } else { + s.logger.Printf("[WARN] yamux: frame for missing stream: %v", hdr) + } + return nil + } + + // Check if this is a window update + if hdr.MsgType() == typeWindowUpdate { + if err := stream.incrSendWindow(hdr, flags); err != nil { + if sendErr := s.sendNoWait(s.goAway(goAwayProtoErr)); sendErr != nil { + s.logger.Printf("[WARN] yamux: failed to send go away: %v", sendErr) + } + return err + } + return nil + } + + // Read the new data + if err := stream.readData(hdr, flags, s.bufRead); err != nil { + if sendErr := s.sendNoWait(s.goAway(goAwayProtoErr)); sendErr != nil { + s.logger.Printf("[WARN] yamux: failed to send go away: %v", sendErr) + } + return err + } + return nil +} + +// handlePing is invokde for a typePing frame +func (s *Session) handlePing(hdr header) error { + flags := hdr.Flags() + pingID := hdr.Length() + + // Check if this is a query, respond back in a separate context so we + // don't interfere with the receiving thread blocking for the write. + if flags&flagSYN == flagSYN { + go func() { + hdr := header(make([]byte, headerSize)) + hdr.encode(typePing, flagACK, 0, pingID) + if err := s.sendNoWait(hdr); err != nil { + s.logger.Printf("[WARN] yamux: failed to send ping reply: %v", err) + } + }() + return nil + } + + // Handle a response + s.pingLock.Lock() + ch := s.pings[pingID] + if ch != nil { + delete(s.pings, pingID) + close(ch) + } + s.pingLock.Unlock() + return nil +} + +// handleGoAway is invokde for a typeGoAway frame +func (s *Session) handleGoAway(hdr header) error { + code := hdr.Length() + switch code { + case goAwayNormal: + atomic.SwapInt32(&s.remoteGoAway, 1) + case goAwayProtoErr: + s.logger.Printf("[ERR] yamux: received protocol error go away") + return fmt.Errorf("yamux protocol error") + case goAwayInternalErr: + s.logger.Printf("[ERR] yamux: received internal error go away") + return fmt.Errorf("remote yamux internal error") + default: + s.logger.Printf("[ERR] yamux: received unexpected go away") + return fmt.Errorf("unexpected go away received") + } + return nil +} + +// incomingStream is used to create a new incoming stream +func (s *Session) incomingStream(id uint32) error { + // Reject immediately if we are doing a go away + if atomic.LoadInt32(&s.localGoAway) == 1 { + hdr := header(make([]byte, headerSize)) + hdr.encode(typeWindowUpdate, flagRST, id, 0) + return s.sendNoWait(hdr) + } + + // Allocate a new stream + stream := newStream(s, id, streamSYNReceived) + + s.streamLock.Lock() + defer s.streamLock.Unlock() + + // Check if stream already exists + if _, ok := s.streams[id]; ok { + s.logger.Printf("[ERR] yamux: duplicate stream declared") + if sendErr := s.sendNoWait(s.goAway(goAwayProtoErr)); sendErr != nil { + s.logger.Printf("[WARN] yamux: failed to send go away: %v", sendErr) + } + return ErrDuplicateStream + } + + // Register the stream + s.streams[id] = stream + + // Check if we've exceeded the backlog + select { + case s.acceptCh <- stream: + return nil + default: + // Backlog exceeded! RST the stream + s.logger.Printf("[WARN] yamux: backlog exceeded, forcing connection reset") + delete(s.streams, id) + stream.sendHdr.encode(typeWindowUpdate, flagRST, id, 0) + return s.sendNoWait(stream.sendHdr) + } +} + +// closeStream is used to close a stream once both sides have +// issued a close. If there was an in-flight SYN and the stream +// was not yet established, then this will give the credit back. +func (s *Session) closeStream(id uint32) { + s.streamLock.Lock() + if _, ok := s.inflight[id]; ok { + select { + case <-s.synCh: + default: + s.logger.Printf("[ERR] yamux: SYN tracking out of sync") + } + } + delete(s.streams, id) + s.streamLock.Unlock() +} + +// establishStream is used to mark a stream that was in the +// SYN Sent state as established. +func (s *Session) establishStream(id uint32) { + s.streamLock.Lock() + if _, ok := s.inflight[id]; ok { + delete(s.inflight, id) + } else { + s.logger.Printf("[ERR] yamux: established stream without inflight SYN (no tracking entry)") + } + select { + case <-s.synCh: + default: + s.logger.Printf("[ERR] yamux: established stream without inflight SYN (didn't have semaphore)") + } + s.streamLock.Unlock() +} diff --git a/vendor/github.com/hashicorp/yamux/session_test.go b/vendor/github.com/hashicorp/yamux/session_test.go new file mode 100644 index 00000000..1645e2b7 --- /dev/null +++ b/vendor/github.com/hashicorp/yamux/session_test.go @@ -0,0 +1,1256 @@ +package yamux + +import ( + "bytes" + "fmt" + "io" + "io/ioutil" + "log" + "reflect" + "runtime" + "strings" + "sync" + "testing" + "time" +) + +type logCapture struct{ bytes.Buffer } + +func (l *logCapture) logs() []string { + return strings.Split(strings.TrimSpace(l.String()), "\n") +} + +func (l *logCapture) match(expect []string) bool { + return reflect.DeepEqual(l.logs(), expect) +} + +func captureLogs(s *Session) *logCapture { + buf := new(logCapture) + s.logger = log.New(buf, "", 0) + return buf +} + +type pipeConn struct { + reader *io.PipeReader + writer *io.PipeWriter + writeBlocker sync.Mutex +} + +func (p *pipeConn) Read(b []byte) (int, error) { + return p.reader.Read(b) +} + +func (p *pipeConn) Write(b []byte) (int, error) { + p.writeBlocker.Lock() + defer p.writeBlocker.Unlock() + return p.writer.Write(b) +} + +func (p *pipeConn) Close() error { + p.reader.Close() + return p.writer.Close() +} + +func testConn() (io.ReadWriteCloser, io.ReadWriteCloser) { + read1, write1 := io.Pipe() + read2, write2 := io.Pipe() + conn1 := &pipeConn{reader: read1, writer: write2} + conn2 := &pipeConn{reader: read2, writer: write1} + return conn1, conn2 +} + +func testConf() *Config { + conf := DefaultConfig() + conf.AcceptBacklog = 64 + conf.KeepAliveInterval = 100 * time.Millisecond + conf.ConnectionWriteTimeout = 250 * time.Millisecond + return conf +} + +func testConfNoKeepAlive() *Config { + conf := testConf() + conf.EnableKeepAlive = false + return conf +} + +func testClientServer() (*Session, *Session) { + return testClientServerConfig(testConf()) +} + +func testClientServerConfig(conf *Config) (*Session, *Session) { + conn1, conn2 := testConn() + client, _ := Client(conn1, conf) + server, _ := Server(conn2, conf) + return client, server +} + +func TestPing(t *testing.T) { + client, server := testClientServer() + defer client.Close() + defer server.Close() + + rtt, err := client.Ping() + if err != nil { + t.Fatalf("err: %v", err) + } + if rtt == 0 { + t.Fatalf("bad: %v", rtt) + } + + rtt, err = server.Ping() + if err != nil { + t.Fatalf("err: %v", err) + } + if rtt == 0 { + t.Fatalf("bad: %v", rtt) + } +} + +func TestPing_Timeout(t *testing.T) { + client, server := testClientServerConfig(testConfNoKeepAlive()) + defer client.Close() + defer server.Close() + + // Prevent the client from responding + clientConn := client.conn.(*pipeConn) + clientConn.writeBlocker.Lock() + + errCh := make(chan error, 1) + go func() { + _, err := server.Ping() // Ping via the server session + errCh <- err + }() + + select { + case err := <-errCh: + if err != ErrTimeout { + t.Fatalf("err: %v", err) + } + case <-time.After(client.config.ConnectionWriteTimeout * 2): + t.Fatalf("failed to timeout within expected %v", client.config.ConnectionWriteTimeout) + } + + // Verify that we recover, even if we gave up + clientConn.writeBlocker.Unlock() + + go func() { + _, err := server.Ping() // Ping via the server session + errCh <- err + }() + + select { + case err := <-errCh: + if err != nil { + t.Fatalf("err: %v", err) + } + case <-time.After(client.config.ConnectionWriteTimeout): + t.Fatalf("timeout") + } +} + +func TestCloseBeforeAck(t *testing.T) { + cfg := testConf() + cfg.AcceptBacklog = 8 + client, server := testClientServerConfig(cfg) + + defer client.Close() + defer server.Close() + + for i := 0; i < 8; i++ { + s, err := client.OpenStream() + if err != nil { + t.Fatal(err) + } + s.Close() + } + + for i := 0; i < 8; i++ { + s, err := server.AcceptStream() + if err != nil { + t.Fatal(err) + } + s.Close() + } + + done := make(chan struct{}) + go func() { + defer close(done) + s, err := client.OpenStream() + if err != nil { + t.Fatal(err) + } + s.Close() + }() + + select { + case <-done: + case <-time.After(time.Second * 5): + t.Fatal("timed out trying to open stream") + } +} + +func TestAccept(t *testing.T) { + client, server := testClientServer() + defer client.Close() + defer server.Close() + + if client.NumStreams() != 0 { + t.Fatalf("bad") + } + if server.NumStreams() != 0 { + t.Fatalf("bad") + } + + wg := &sync.WaitGroup{} + wg.Add(4) + + go func() { + defer wg.Done() + stream, err := server.AcceptStream() + if err != nil { + t.Fatalf("err: %v", err) + } + if id := stream.StreamID(); id != 1 { + t.Fatalf("bad: %v", id) + } + if err := stream.Close(); err != nil { + t.Fatalf("err: %v", err) + } + }() + + go func() { + defer wg.Done() + stream, err := client.AcceptStream() + if err != nil { + t.Fatalf("err: %v", err) + } + if id := stream.StreamID(); id != 2 { + t.Fatalf("bad: %v", id) + } + if err := stream.Close(); err != nil { + t.Fatalf("err: %v", err) + } + }() + + go func() { + defer wg.Done() + stream, err := server.OpenStream() + if err != nil { + t.Fatalf("err: %v", err) + } + if id := stream.StreamID(); id != 2 { + t.Fatalf("bad: %v", id) + } + if err := stream.Close(); err != nil { + t.Fatalf("err: %v", err) + } + }() + + go func() { + defer wg.Done() + stream, err := client.OpenStream() + if err != nil { + t.Fatalf("err: %v", err) + } + if id := stream.StreamID(); id != 1 { + t.Fatalf("bad: %v", id) + } + if err := stream.Close(); err != nil { + t.Fatalf("err: %v", err) + } + }() + + doneCh := make(chan struct{}) + go func() { + wg.Wait() + close(doneCh) + }() + + select { + case <-doneCh: + case <-time.After(time.Second): + panic("timeout") + } +} + +func TestNonNilInterface(t *testing.T) { + _, server := testClientServer() + server.Close() + + conn, err := server.Accept() + if err != nil && conn != nil { + t.Error("bad: accept should return a connection of nil value") + } + + conn, err = server.Open() + if err != nil && conn != nil { + t.Error("bad: open should return a connection of nil value") + } +} + +func TestSendData_Small(t *testing.T) { + client, server := testClientServer() + defer client.Close() + defer server.Close() + + wg := &sync.WaitGroup{} + wg.Add(2) + + go func() { + defer wg.Done() + stream, err := server.AcceptStream() + if err != nil { + t.Fatalf("err: %v", err) + } + + if server.NumStreams() != 1 { + t.Fatalf("bad") + } + + buf := make([]byte, 4) + for i := 0; i < 1000; i++ { + n, err := stream.Read(buf) + if err != nil { + t.Fatalf("err: %v", err) + } + if n != 4 { + t.Fatalf("short read: %d", n) + } + if string(buf) != "test" { + t.Fatalf("bad: %s", buf) + } + } + + if err := stream.Close(); err != nil { + t.Fatalf("err: %v", err) + } + }() + + go func() { + defer wg.Done() + stream, err := client.Open() + if err != nil { + t.Fatalf("err: %v", err) + } + + if client.NumStreams() != 1 { + t.Fatalf("bad") + } + + for i := 0; i < 1000; i++ { + n, err := stream.Write([]byte("test")) + if err != nil { + t.Fatalf("err: %v", err) + } + if n != 4 { + t.Fatalf("short write %d", n) + } + } + + if err := stream.Close(); err != nil { + t.Fatalf("err: %v", err) + } + }() + + doneCh := make(chan struct{}) + go func() { + wg.Wait() + close(doneCh) + }() + select { + case <-doneCh: + case <-time.After(time.Second): + panic("timeout") + } + + if client.NumStreams() != 0 { + t.Fatalf("bad") + } + if server.NumStreams() != 0 { + t.Fatalf("bad") + } +} + +func TestSendData_Large(t *testing.T) { + client, server := testClientServer() + defer client.Close() + defer server.Close() + + const ( + sendSize = 250 * 1024 * 1024 + recvSize = 4 * 1024 + ) + + data := make([]byte, sendSize) + for idx := range data { + data[idx] = byte(idx % 256) + } + + wg := &sync.WaitGroup{} + wg.Add(2) + + go func() { + defer wg.Done() + stream, err := server.AcceptStream() + if err != nil { + t.Fatalf("err: %v", err) + } + var sz int + buf := make([]byte, recvSize) + for i := 0; i < sendSize/recvSize; i++ { + n, err := stream.Read(buf) + if err != nil { + t.Fatalf("err: %v", err) + } + if n != recvSize { + t.Fatalf("short read: %d", n) + } + sz += n + for idx := range buf { + if buf[idx] != byte(idx%256) { + t.Fatalf("bad: %v %v %v", i, idx, buf[idx]) + } + } + } + + if err := stream.Close(); err != nil { + t.Fatalf("err: %v", err) + } + + t.Logf("cap=%d, n=%d\n", stream.recvBuf.Cap(), sz) + }() + + go func() { + defer wg.Done() + stream, err := client.Open() + if err != nil { + t.Fatalf("err: %v", err) + } + + n, err := stream.Write(data) + if err != nil { + t.Fatalf("err: %v", err) + } + if n != len(data) { + t.Fatalf("short write %d", n) + } + + if err := stream.Close(); err != nil { + t.Fatalf("err: %v", err) + } + }() + + doneCh := make(chan struct{}) + go func() { + wg.Wait() + close(doneCh) + }() + select { + case <-doneCh: + case <-time.After(5 * time.Second): + panic("timeout") + } +} + +func TestGoAway(t *testing.T) { + client, server := testClientServer() + defer client.Close() + defer server.Close() + + if err := server.GoAway(); err != nil { + t.Fatalf("err: %v", err) + } + + _, err := client.Open() + if err != ErrRemoteGoAway { + t.Fatalf("err: %v", err) + } +} + +func TestManyStreams(t *testing.T) { + client, server := testClientServer() + defer client.Close() + defer server.Close() + + wg := &sync.WaitGroup{} + + acceptor := func(i int) { + defer wg.Done() + stream, err := server.AcceptStream() + if err != nil { + t.Fatalf("err: %v", err) + } + defer stream.Close() + + buf := make([]byte, 512) + for { + n, err := stream.Read(buf) + if err == io.EOF { + return + } + if err != nil { + t.Fatalf("err: %v", err) + } + if n == 0 { + t.Fatalf("err: %v", err) + } + } + } + sender := func(i int) { + defer wg.Done() + stream, err := client.Open() + if err != nil { + t.Fatalf("err: %v", err) + } + defer stream.Close() + + msg := fmt.Sprintf("%08d", i) + for i := 0; i < 1000; i++ { + n, err := stream.Write([]byte(msg)) + if err != nil { + t.Fatalf("err: %v", err) + } + if n != len(msg) { + t.Fatalf("short write %d", n) + } + } + } + + for i := 0; i < 50; i++ { + wg.Add(2) + go acceptor(i) + go sender(i) + } + + wg.Wait() +} + +func TestManyStreams_PingPong(t *testing.T) { + client, server := testClientServer() + defer client.Close() + defer server.Close() + + wg := &sync.WaitGroup{} + + ping := []byte("ping") + pong := []byte("pong") + + acceptor := func(i int) { + defer wg.Done() + stream, err := server.AcceptStream() + if err != nil { + t.Fatalf("err: %v", err) + } + defer stream.Close() + + buf := make([]byte, 4) + for { + // Read the 'ping' + n, err := stream.Read(buf) + if err == io.EOF { + return + } + if err != nil { + t.Fatalf("err: %v", err) + } + if n != 4 { + t.Fatalf("err: %v", err) + } + if !bytes.Equal(buf, ping) { + t.Fatalf("bad: %s", buf) + } + + // Shrink the internal buffer! + stream.Shrink() + + // Write out the 'pong' + n, err = stream.Write(pong) + if err != nil { + t.Fatalf("err: %v", err) + } + if n != 4 { + t.Fatalf("err: %v", err) + } + } + } + sender := func(i int) { + defer wg.Done() + stream, err := client.OpenStream() + if err != nil { + t.Fatalf("err: %v", err) + } + defer stream.Close() + + buf := make([]byte, 4) + for i := 0; i < 1000; i++ { + // Send the 'ping' + n, err := stream.Write(ping) + if err != nil { + t.Fatalf("err: %v", err) + } + if n != 4 { + t.Fatalf("short write %d", n) + } + + // Read the 'pong' + n, err = stream.Read(buf) + if err != nil { + t.Fatalf("err: %v", err) + } + if n != 4 { + t.Fatalf("err: %v", err) + } + if !bytes.Equal(buf, pong) { + t.Fatalf("bad: %s", buf) + } + + // Shrink the buffer + stream.Shrink() + } + } + + for i := 0; i < 50; i++ { + wg.Add(2) + go acceptor(i) + go sender(i) + } + + wg.Wait() +} + +func TestHalfClose(t *testing.T) { + client, server := testClientServer() + defer client.Close() + defer server.Close() + + stream, err := client.Open() + if err != nil { + t.Fatalf("err: %v", err) + } + if _, err = stream.Write([]byte("a")); err != nil { + t.Fatalf("err: %v", err) + } + + stream2, err := server.Accept() + if err != nil { + t.Fatalf("err: %v", err) + } + stream2.Close() // Half close + + buf := make([]byte, 4) + n, err := stream2.Read(buf) + if err != nil { + t.Fatalf("err: %v", err) + } + if n != 1 { + t.Fatalf("bad: %v", n) + } + + // Send more + if _, err = stream.Write([]byte("bcd")); err != nil { + t.Fatalf("err: %v", err) + } + stream.Close() + + // Read after close + n, err = stream2.Read(buf) + if err != nil { + t.Fatalf("err: %v", err) + } + if n != 3 { + t.Fatalf("bad: %v", n) + } + + // EOF after close + n, err = stream2.Read(buf) + if err != io.EOF { + t.Fatalf("err: %v", err) + } + if n != 0 { + t.Fatalf("bad: %v", n) + } +} + +func TestReadDeadline(t *testing.T) { + client, server := testClientServer() + defer client.Close() + defer server.Close() + + stream, err := client.Open() + if err != nil { + t.Fatalf("err: %v", err) + } + defer stream.Close() + + stream2, err := server.Accept() + if err != nil { + t.Fatalf("err: %v", err) + } + defer stream2.Close() + + if err := stream.SetReadDeadline(time.Now().Add(5 * time.Millisecond)); err != nil { + t.Fatalf("err: %v", err) + } + + buf := make([]byte, 4) + if _, err := stream.Read(buf); err != ErrTimeout { + t.Fatalf("err: %v", err) + } +} + +func TestWriteDeadline(t *testing.T) { + client, server := testClientServer() + defer client.Close() + defer server.Close() + + stream, err := client.Open() + if err != nil { + t.Fatalf("err: %v", err) + } + defer stream.Close() + + stream2, err := server.Accept() + if err != nil { + t.Fatalf("err: %v", err) + } + defer stream2.Close() + + if err := stream.SetWriteDeadline(time.Now().Add(50 * time.Millisecond)); err != nil { + t.Fatalf("err: %v", err) + } + + buf := make([]byte, 512) + for i := 0; i < int(initialStreamWindow); i++ { + _, err := stream.Write(buf) + if err != nil && err == ErrTimeout { + return + } else if err != nil { + t.Fatalf("err: %v", err) + } + } + t.Fatalf("Expected timeout") +} + +func TestBacklogExceeded(t *testing.T) { + client, server := testClientServer() + defer client.Close() + defer server.Close() + + // Fill the backlog + max := client.config.AcceptBacklog + for i := 0; i < max; i++ { + stream, err := client.Open() + if err != nil { + t.Fatalf("err: %v", err) + } + defer stream.Close() + + if _, err := stream.Write([]byte("foo")); err != nil { + t.Fatalf("err: %v", err) + } + } + + // Attempt to open a new stream + errCh := make(chan error, 1) + go func() { + _, err := client.Open() + errCh <- err + }() + + // Shutdown the server + go func() { + time.Sleep(10 * time.Millisecond) + server.Close() + }() + + select { + case err := <-errCh: + if err == nil { + t.Fatalf("open should fail") + } + case <-time.After(time.Second): + t.Fatalf("timeout") + } +} + +func TestKeepAlive(t *testing.T) { + client, server := testClientServer() + defer client.Close() + defer server.Close() + + time.Sleep(200 * time.Millisecond) + + // Ping value should increase + client.pingLock.Lock() + defer client.pingLock.Unlock() + if client.pingID == 0 { + t.Fatalf("should ping") + } + + server.pingLock.Lock() + defer server.pingLock.Unlock() + if server.pingID == 0 { + t.Fatalf("should ping") + } +} + +func TestKeepAlive_Timeout(t *testing.T) { + conn1, conn2 := testConn() + + clientConf := testConf() + clientConf.ConnectionWriteTimeout = time.Hour // We're testing keep alives, not connection writes + clientConf.EnableKeepAlive = false // Just test one direction, so it's deterministic who hangs up on whom + client, _ := Client(conn1, clientConf) + defer client.Close() + + server, _ := Server(conn2, testConf()) + defer server.Close() + + _ = captureLogs(client) // Client logs aren't part of the test + serverLogs := captureLogs(server) + + errCh := make(chan error, 1) + go func() { + _, err := server.Accept() // Wait until server closes + errCh <- err + }() + + // Prevent the client from responding + clientConn := client.conn.(*pipeConn) + clientConn.writeBlocker.Lock() + + select { + case err := <-errCh: + if err != ErrKeepAliveTimeout { + t.Fatalf("unexpected error: %v", err) + } + case <-time.After(1 * time.Second): + t.Fatalf("timeout waiting for timeout") + } + + if !server.IsClosed() { + t.Fatalf("server should have closed") + } + + if !serverLogs.match([]string{"[ERR] yamux: keepalive failed: i/o deadline reached"}) { + t.Fatalf("server log incorect: %v", serverLogs.logs()) + } +} + +func TestLargeWindow(t *testing.T) { + conf := DefaultConfig() + conf.MaxStreamWindowSize *= 2 + + client, server := testClientServerConfig(conf) + defer client.Close() + defer server.Close() + + stream, err := client.Open() + if err != nil { + t.Fatalf("err: %v", err) + } + defer stream.Close() + + stream2, err := server.Accept() + if err != nil { + t.Fatalf("err: %v", err) + } + defer stream2.Close() + + stream.SetWriteDeadline(time.Now().Add(10 * time.Millisecond)) + buf := make([]byte, conf.MaxStreamWindowSize) + n, err := stream.Write(buf) + if err != nil { + t.Fatalf("err: %v", err) + } + if n != len(buf) { + t.Fatalf("short write: %d", n) + } +} + +type UnlimitedReader struct{} + +func (u *UnlimitedReader) Read(p []byte) (int, error) { + runtime.Gosched() + return len(p), nil +} + +func TestSendData_VeryLarge(t *testing.T) { + client, server := testClientServer() + defer client.Close() + defer server.Close() + + var n int64 = 1 * 1024 * 1024 * 1024 + var workers int = 16 + + wg := &sync.WaitGroup{} + wg.Add(workers * 2) + + for i := 0; i < workers; i++ { + go func() { + defer wg.Done() + stream, err := server.AcceptStream() + if err != nil { + t.Fatalf("err: %v", err) + } + defer stream.Close() + + buf := make([]byte, 4) + _, err = stream.Read(buf) + if err != nil { + t.Fatalf("err: %v", err) + } + if !bytes.Equal(buf, []byte{0, 1, 2, 3}) { + t.Fatalf("bad header") + } + + recv, err := io.Copy(ioutil.Discard, stream) + if err != nil { + t.Fatalf("err: %v", err) + } + if recv != n { + t.Fatalf("bad: %v", recv) + } + }() + } + for i := 0; i < workers; i++ { + go func() { + defer wg.Done() + stream, err := client.Open() + if err != nil { + t.Fatalf("err: %v", err) + } + defer stream.Close() + + _, err = stream.Write([]byte{0, 1, 2, 3}) + if err != nil { + t.Fatalf("err: %v", err) + } + + unlimited := &UnlimitedReader{} + sent, err := io.Copy(stream, io.LimitReader(unlimited, n)) + if err != nil { + t.Fatalf("err: %v", err) + } + if sent != n { + t.Fatalf("bad: %v", sent) + } + }() + } + + doneCh := make(chan struct{}) + go func() { + wg.Wait() + close(doneCh) + }() + select { + case <-doneCh: + case <-time.After(20 * time.Second): + panic("timeout") + } +} + +func TestBacklogExceeded_Accept(t *testing.T) { + client, server := testClientServer() + defer client.Close() + defer server.Close() + + max := 5 * client.config.AcceptBacklog + go func() { + for i := 0; i < max; i++ { + stream, err := server.Accept() + if err != nil { + t.Fatalf("err: %v", err) + } + defer stream.Close() + } + }() + + // Fill the backlog + for i := 0; i < max; i++ { + stream, err := client.Open() + if err != nil { + t.Fatalf("err: %v", err) + } + defer stream.Close() + + if _, err := stream.Write([]byte("foo")); err != nil { + t.Fatalf("err: %v", err) + } + } +} + +func TestSession_WindowUpdateWriteDuringRead(t *testing.T) { + client, server := testClientServerConfig(testConfNoKeepAlive()) + defer client.Close() + defer server.Close() + + var wg sync.WaitGroup + wg.Add(2) + + // Choose a huge flood size that we know will result in a window update. + flood := int64(client.config.MaxStreamWindowSize) - 1 + + // The server will accept a new stream and then flood data to it. + go func() { + defer wg.Done() + + stream, err := server.AcceptStream() + if err != nil { + t.Fatalf("err: %v", err) + } + defer stream.Close() + + n, err := stream.Write(make([]byte, flood)) + if err != nil { + t.Fatalf("err: %v", err) + } + if int64(n) != flood { + t.Fatalf("short write: %d", n) + } + }() + + // The client will open a stream, block outbound writes, and then + // listen to the flood from the server, which should time out since + // it won't be able to send the window update. + go func() { + defer wg.Done() + + stream, err := client.OpenStream() + if err != nil { + t.Fatalf("err: %v", err) + } + defer stream.Close() + + conn := client.conn.(*pipeConn) + conn.writeBlocker.Lock() + + _, err = stream.Read(make([]byte, flood)) + if err != ErrConnectionWriteTimeout { + t.Fatalf("err: %v", err) + } + }() + + wg.Wait() +} + +func TestSession_PartialReadWindowUpdate(t *testing.T) { + client, server := testClientServerConfig(testConfNoKeepAlive()) + defer client.Close() + defer server.Close() + + var wg sync.WaitGroup + wg.Add(1) + + // Choose a huge flood size that we know will result in a window update. + flood := int64(client.config.MaxStreamWindowSize) + var wr *Stream + + // The server will accept a new stream and then flood data to it. + go func() { + defer wg.Done() + + var err error + wr, err = server.AcceptStream() + if err != nil { + t.Fatalf("err: %v", err) + } + defer wr.Close() + + if wr.sendWindow != client.config.MaxStreamWindowSize { + t.Fatalf("sendWindow: exp=%d, got=%d", client.config.MaxStreamWindowSize, wr.sendWindow) + } + + n, err := wr.Write(make([]byte, flood)) + if err != nil { + t.Fatalf("err: %v", err) + } + if int64(n) != flood { + t.Fatalf("short write: %d", n) + } + if wr.sendWindow != 0 { + t.Fatalf("sendWindow: exp=%d, got=%d", 0, wr.sendWindow) + } + }() + + stream, err := client.OpenStream() + if err != nil { + t.Fatalf("err: %v", err) + } + defer stream.Close() + + wg.Wait() + + _, err = stream.Read(make([]byte, flood/2+1)) + + if exp := uint32(flood/2 + 1); wr.sendWindow != exp { + t.Errorf("sendWindow: exp=%d, got=%d", exp, wr.sendWindow) + } +} + +func TestSession_sendNoWait_Timeout(t *testing.T) { + client, server := testClientServerConfig(testConfNoKeepAlive()) + defer client.Close() + defer server.Close() + + var wg sync.WaitGroup + wg.Add(2) + + go func() { + defer wg.Done() + + stream, err := server.AcceptStream() + if err != nil { + t.Fatalf("err: %v", err) + } + defer stream.Close() + }() + + // The client will open the stream and then block outbound writes, we'll + // probe sendNoWait once it gets into that state. + go func() { + defer wg.Done() + + stream, err := client.OpenStream() + if err != nil { + t.Fatalf("err: %v", err) + } + defer stream.Close() + + conn := client.conn.(*pipeConn) + conn.writeBlocker.Lock() + + hdr := header(make([]byte, headerSize)) + hdr.encode(typePing, flagACK, 0, 0) + for { + err = client.sendNoWait(hdr) + if err == nil { + continue + } else if err == ErrConnectionWriteTimeout { + break + } else { + t.Fatalf("err: %v", err) + } + } + }() + + wg.Wait() +} + +func TestSession_PingOfDeath(t *testing.T) { + client, server := testClientServerConfig(testConfNoKeepAlive()) + defer client.Close() + defer server.Close() + + var wg sync.WaitGroup + wg.Add(2) + + var doPingOfDeath sync.Mutex + doPingOfDeath.Lock() + + // This is used later to block outbound writes. + conn := server.conn.(*pipeConn) + + // The server will accept a stream, block outbound writes, and then + // flood its send channel so that no more headers can be queued. + go func() { + defer wg.Done() + + stream, err := server.AcceptStream() + if err != nil { + t.Fatalf("err: %v", err) + } + defer stream.Close() + + conn.writeBlocker.Lock() + for { + hdr := header(make([]byte, headerSize)) + hdr.encode(typePing, 0, 0, 0) + err = server.sendNoWait(hdr) + if err == nil { + continue + } else if err == ErrConnectionWriteTimeout { + break + } else { + t.Fatalf("err: %v", err) + } + } + + doPingOfDeath.Unlock() + }() + + // The client will open a stream and then send the server a ping once it + // can no longer write. This makes sure the server doesn't deadlock reads + // while trying to reply to the ping with no ability to write. + go func() { + defer wg.Done() + + stream, err := client.OpenStream() + if err != nil { + t.Fatalf("err: %v", err) + } + defer stream.Close() + + // This ping will never unblock because the ping id will never + // show up in a response. + doPingOfDeath.Lock() + go func() { client.Ping() }() + + // Wait for a while to make sure the previous ping times out, + // then turn writes back on and make sure a ping works again. + time.Sleep(2 * server.config.ConnectionWriteTimeout) + conn.writeBlocker.Unlock() + if _, err = client.Ping(); err != nil { + t.Fatalf("err: %v", err) + } + }() + + wg.Wait() +} + +func TestSession_ConnectionWriteTimeout(t *testing.T) { + client, server := testClientServerConfig(testConfNoKeepAlive()) + defer client.Close() + defer server.Close() + + var wg sync.WaitGroup + wg.Add(2) + + go func() { + defer wg.Done() + + stream, err := server.AcceptStream() + if err != nil { + t.Fatalf("err: %v", err) + } + defer stream.Close() + }() + + // The client will open the stream and then block outbound writes, we'll + // tee up a write and make sure it eventually times out. + go func() { + defer wg.Done() + + stream, err := client.OpenStream() + if err != nil { + t.Fatalf("err: %v", err) + } + defer stream.Close() + + conn := client.conn.(*pipeConn) + conn.writeBlocker.Lock() + + // Since the write goroutine is blocked then this will return a + // timeout since it can't get feedback about whether the write + // worked. + n, err := stream.Write([]byte("hello")) + if err != ErrConnectionWriteTimeout { + t.Fatalf("err: %v", err) + } + if n != 0 { + t.Fatalf("lied about writes: %d", n) + } + }() + + wg.Wait() +} diff --git a/vendor/github.com/hashicorp/yamux/spec.md b/vendor/github.com/hashicorp/yamux/spec.md new file mode 100644 index 00000000..183d797b --- /dev/null +++ b/vendor/github.com/hashicorp/yamux/spec.md @@ -0,0 +1,140 @@ +# Specification + +We use this document to detail the internal specification of Yamux. +This is used both as a guide for implementing Yamux, but also for +alternative interoperable libraries to be built. + +# Framing + +Yamux uses a streaming connection underneath, but imposes a message +framing so that it can be shared between many logical streams. Each +frame contains a header like: + +* Version (8 bits) +* Type (8 bits) +* Flags (16 bits) +* StreamID (32 bits) +* Length (32 bits) + +This means that each header has a 12 byte overhead. +All fields are encoded in network order (big endian). +Each field is described below: + +## Version Field + +The version field is used for future backward compatibility. At the +current time, the field is always set to 0, to indicate the initial +version. + +## Type Field + +The type field is used to switch the frame message type. The following +message types are supported: + +* 0x0 Data - Used to transmit data. May transmit zero length payloads + depending on the flags. + +* 0x1 Window Update - Used to updated the senders receive window size. + This is used to implement per-session flow control. + +* 0x2 Ping - Used to measure RTT. It can also be used to heart-beat + and do keep-alives over TCP. + +* 0x3 Go Away - Used to close a session. + +## Flag Field + +The flags field is used to provide additional information related +to the message type. The following flags are supported: + +* 0x1 SYN - Signals the start of a new stream. May be sent with a data or + window update message. Also sent with a ping to indicate outbound. + +* 0x2 ACK - Acknowledges the start of a new stream. May be sent with a data + or window update message. Also sent with a ping to indicate response. + +* 0x4 FIN - Performs a half-close of a stream. May be sent with a data + message or window update. + +* 0x8 RST - Reset a stream immediately. May be sent with a data or + window update message. + +## StreamID Field + +The StreamID field is used to identify the logical stream the frame +is addressing. The client side should use odd ID's, and the server even. +This prevents any collisions. Additionally, the 0 ID is reserved to represent +the session. + +Both Ping and Go Away messages should always use the 0 StreamID. + +## Length Field + +The meaning of the length field depends on the message type: + +* Data - provides the length of bytes following the header +* Window update - provides a delta update to the window size +* Ping - Contains an opaque value, echoed back +* Go Away - Contains an error code + +# Message Flow + +There is no explicit connection setup, as Yamux relies on an underlying +transport to be provided. However, there is a distinction between client +and server side of the connection. + +## Opening a stream + +To open a stream, an initial data or window update frame is sent +with a new StreamID. The SYN flag should be set to signal a new stream. + +The receiver must then reply with either a data or window update frame +with the StreamID along with the ACK flag to accept the stream or with +the RST flag to reject the stream. + +Because we are relying on the reliable stream underneath, a connection +can begin sending data once the SYN flag is sent. The corresponding +ACK does not need to be received. This is particularly well suited +for an RPC system where a client wants to open a stream and immediately +fire a request without waiting for the RTT of the ACK. + +This does introduce the possibility of a connection being rejected +after data has been sent already. This is a slight semantic difference +from TCP, where the conection cannot be refused after it is opened. +Clients should be prepared to handle this by checking for an error +that indicates a RST was received. + +## Closing a stream + +To close a stream, either side sends a data or window update frame +along with the FIN flag. This does a half-close indicating the sender +will send no further data. + +Once both sides have closed the connection, the stream is closed. + +Alternatively, if an error occurs, the RST flag can be used to +hard close a stream immediately. + +## Flow Control + +When Yamux is initially starts each stream with a 256KB window size. +There is no window size for the session. + +To prevent the streams from stalling, window update frames should be +sent regularly. Yamux can be configured to provide a larger limit for +windows sizes. Both sides assume the initial 256KB window, but can +immediately send a window update as part of the SYN/ACK indicating a +larger window. + +Both sides should track the number of bytes sent in Data frames +only, as only they are tracked as part of the window size. + +## Session termination + +When a session is being terminated, the Go Away message should +be sent. The Length should be set to one of the following to +provide an error code: + +* 0x0 Normal termination +* 0x1 Protocol error +* 0x2 Internal error diff --git a/vendor/github.com/hashicorp/yamux/stream.go b/vendor/github.com/hashicorp/yamux/stream.go new file mode 100644 index 00000000..aa239197 --- /dev/null +++ b/vendor/github.com/hashicorp/yamux/stream.go @@ -0,0 +1,470 @@ +package yamux + +import ( + "bytes" + "io" + "sync" + "sync/atomic" + "time" +) + +type streamState int + +const ( + streamInit streamState = iota + streamSYNSent + streamSYNReceived + streamEstablished + streamLocalClose + streamRemoteClose + streamClosed + streamReset +) + +// Stream is used to represent a logical stream +// within a session. +type Stream struct { + recvWindow uint32 + sendWindow uint32 + + id uint32 + session *Session + + state streamState + stateLock sync.Mutex + + recvBuf *bytes.Buffer + recvLock sync.Mutex + + controlHdr header + controlErr chan error + controlHdrLock sync.Mutex + + sendHdr header + sendErr chan error + sendLock sync.Mutex + + recvNotifyCh chan struct{} + sendNotifyCh chan struct{} + + readDeadline atomic.Value // time.Time + writeDeadline atomic.Value // time.Time +} + +// newStream is used to construct a new stream within +// a given session for an ID +func newStream(session *Session, id uint32, state streamState) *Stream { + s := &Stream{ + id: id, + session: session, + state: state, + controlHdr: header(make([]byte, headerSize)), + controlErr: make(chan error, 1), + sendHdr: header(make([]byte, headerSize)), + sendErr: make(chan error, 1), + recvWindow: initialStreamWindow, + sendWindow: initialStreamWindow, + recvNotifyCh: make(chan struct{}, 1), + sendNotifyCh: make(chan struct{}, 1), + } + s.readDeadline.Store(time.Time{}) + s.writeDeadline.Store(time.Time{}) + return s +} + +// Session returns the associated stream session +func (s *Stream) Session() *Session { + return s.session +} + +// StreamID returns the ID of this stream +func (s *Stream) StreamID() uint32 { + return s.id +} + +// Read is used to read from the stream +func (s *Stream) Read(b []byte) (n int, err error) { + defer asyncNotify(s.recvNotifyCh) +START: + s.stateLock.Lock() + switch s.state { + case streamLocalClose: + fallthrough + case streamRemoteClose: + fallthrough + case streamClosed: + s.recvLock.Lock() + if s.recvBuf == nil || s.recvBuf.Len() == 0 { + s.recvLock.Unlock() + s.stateLock.Unlock() + return 0, io.EOF + } + s.recvLock.Unlock() + case streamReset: + s.stateLock.Unlock() + return 0, ErrConnectionReset + } + s.stateLock.Unlock() + + // If there is no data available, block + s.recvLock.Lock() + if s.recvBuf == nil || s.recvBuf.Len() == 0 { + s.recvLock.Unlock() + goto WAIT + } + + // Read any bytes + n, _ = s.recvBuf.Read(b) + s.recvLock.Unlock() + + // Send a window update potentially + err = s.sendWindowUpdate() + return n, err + +WAIT: + var timeout <-chan time.Time + var timer *time.Timer + readDeadline := s.readDeadline.Load().(time.Time) + if !readDeadline.IsZero() { + delay := readDeadline.Sub(time.Now()) + timer = time.NewTimer(delay) + timeout = timer.C + } + select { + case <-s.recvNotifyCh: + if timer != nil { + timer.Stop() + } + goto START + case <-timeout: + return 0, ErrTimeout + } +} + +// Write is used to write to the stream +func (s *Stream) Write(b []byte) (n int, err error) { + s.sendLock.Lock() + defer s.sendLock.Unlock() + total := 0 + for total < len(b) { + n, err := s.write(b[total:]) + total += n + if err != nil { + return total, err + } + } + return total, nil +} + +// write is used to write to the stream, may return on +// a short write. +func (s *Stream) write(b []byte) (n int, err error) { + var flags uint16 + var max uint32 + var body io.Reader +START: + s.stateLock.Lock() + switch s.state { + case streamLocalClose: + fallthrough + case streamClosed: + s.stateLock.Unlock() + return 0, ErrStreamClosed + case streamReset: + s.stateLock.Unlock() + return 0, ErrConnectionReset + } + s.stateLock.Unlock() + + // If there is no data available, block + window := atomic.LoadUint32(&s.sendWindow) + if window == 0 { + goto WAIT + } + + // Determine the flags if any + flags = s.sendFlags() + + // Send up to our send window + max = min(window, uint32(len(b))) + body = bytes.NewReader(b[:max]) + + // Send the header + s.sendHdr.encode(typeData, flags, s.id, max) + if err = s.session.waitForSendErr(s.sendHdr, body, s.sendErr); err != nil { + return 0, err + } + + // Reduce our send window + atomic.AddUint32(&s.sendWindow, ^uint32(max-1)) + + // Unlock + return int(max), err + +WAIT: + var timeout <-chan time.Time + writeDeadline := s.writeDeadline.Load().(time.Time) + if !writeDeadline.IsZero() { + delay := writeDeadline.Sub(time.Now()) + timeout = time.After(delay) + } + select { + case <-s.sendNotifyCh: + goto START + case <-timeout: + return 0, ErrTimeout + } + return 0, nil +} + +// sendFlags determines any flags that are appropriate +// based on the current stream state +func (s *Stream) sendFlags() uint16 { + s.stateLock.Lock() + defer s.stateLock.Unlock() + var flags uint16 + switch s.state { + case streamInit: + flags |= flagSYN + s.state = streamSYNSent + case streamSYNReceived: + flags |= flagACK + s.state = streamEstablished + } + return flags +} + +// sendWindowUpdate potentially sends a window update enabling +// further writes to take place. Must be invoked with the lock. +func (s *Stream) sendWindowUpdate() error { + s.controlHdrLock.Lock() + defer s.controlHdrLock.Unlock() + + // Determine the delta update + max := s.session.config.MaxStreamWindowSize + var bufLen uint32 + s.recvLock.Lock() + if s.recvBuf != nil { + bufLen = uint32(s.recvBuf.Len()) + } + delta := (max - bufLen) - s.recvWindow + + // Determine the flags if any + flags := s.sendFlags() + + // Check if we can omit the update + if delta < (max/2) && flags == 0 { + s.recvLock.Unlock() + return nil + } + + // Update our window + s.recvWindow += delta + s.recvLock.Unlock() + + // Send the header + s.controlHdr.encode(typeWindowUpdate, flags, s.id, delta) + if err := s.session.waitForSendErr(s.controlHdr, nil, s.controlErr); err != nil { + return err + } + return nil +} + +// sendClose is used to send a FIN +func (s *Stream) sendClose() error { + s.controlHdrLock.Lock() + defer s.controlHdrLock.Unlock() + + flags := s.sendFlags() + flags |= flagFIN + s.controlHdr.encode(typeWindowUpdate, flags, s.id, 0) + if err := s.session.waitForSendErr(s.controlHdr, nil, s.controlErr); err != nil { + return err + } + return nil +} + +// Close is used to close the stream +func (s *Stream) Close() error { + closeStream := false + s.stateLock.Lock() + switch s.state { + // Opened means we need to signal a close + case streamSYNSent: + fallthrough + case streamSYNReceived: + fallthrough + case streamEstablished: + s.state = streamLocalClose + goto SEND_CLOSE + + case streamLocalClose: + case streamRemoteClose: + s.state = streamClosed + closeStream = true + goto SEND_CLOSE + + case streamClosed: + case streamReset: + default: + panic("unhandled state") + } + s.stateLock.Unlock() + return nil +SEND_CLOSE: + s.stateLock.Unlock() + s.sendClose() + s.notifyWaiting() + if closeStream { + s.session.closeStream(s.id) + } + return nil +} + +// forceClose is used for when the session is exiting +func (s *Stream) forceClose() { + s.stateLock.Lock() + s.state = streamClosed + s.stateLock.Unlock() + s.notifyWaiting() +} + +// processFlags is used to update the state of the stream +// based on set flags, if any. Lock must be held +func (s *Stream) processFlags(flags uint16) error { + // Close the stream without holding the state lock + closeStream := false + defer func() { + if closeStream { + s.session.closeStream(s.id) + } + }() + + s.stateLock.Lock() + defer s.stateLock.Unlock() + if flags&flagACK == flagACK { + if s.state == streamSYNSent { + s.state = streamEstablished + } + s.session.establishStream(s.id) + } + if flags&flagFIN == flagFIN { + switch s.state { + case streamSYNSent: + fallthrough + case streamSYNReceived: + fallthrough + case streamEstablished: + s.state = streamRemoteClose + s.notifyWaiting() + case streamLocalClose: + s.state = streamClosed + closeStream = true + s.notifyWaiting() + default: + s.session.logger.Printf("[ERR] yamux: unexpected FIN flag in state %d", s.state) + return ErrUnexpectedFlag + } + } + if flags&flagRST == flagRST { + s.state = streamReset + closeStream = true + s.notifyWaiting() + } + return nil +} + +// notifyWaiting notifies all the waiting channels +func (s *Stream) notifyWaiting() { + asyncNotify(s.recvNotifyCh) + asyncNotify(s.sendNotifyCh) +} + +// incrSendWindow updates the size of our send window +func (s *Stream) incrSendWindow(hdr header, flags uint16) error { + if err := s.processFlags(flags); err != nil { + return err + } + + // Increase window, unblock a sender + atomic.AddUint32(&s.sendWindow, hdr.Length()) + asyncNotify(s.sendNotifyCh) + return nil +} + +// readData is used to handle a data frame +func (s *Stream) readData(hdr header, flags uint16, conn io.Reader) error { + if err := s.processFlags(flags); err != nil { + return err + } + + // Check that our recv window is not exceeded + length := hdr.Length() + if length == 0 { + return nil + } + + // Wrap in a limited reader + conn = &io.LimitedReader{R: conn, N: int64(length)} + + // Copy into buffer + s.recvLock.Lock() + + if length > s.recvWindow { + s.session.logger.Printf("[ERR] yamux: receive window exceeded (stream: %d, remain: %d, recv: %d)", s.id, s.recvWindow, length) + return ErrRecvWindowExceeded + } + + if s.recvBuf == nil { + // Allocate the receive buffer just-in-time to fit the full data frame. + // This way we can read in the whole packet without further allocations. + s.recvBuf = bytes.NewBuffer(make([]byte, 0, length)) + } + if _, err := io.Copy(s.recvBuf, conn); err != nil { + s.session.logger.Printf("[ERR] yamux: Failed to read stream data: %v", err) + s.recvLock.Unlock() + return err + } + + // Decrement the receive window + s.recvWindow -= length + s.recvLock.Unlock() + + // Unblock any readers + asyncNotify(s.recvNotifyCh) + return nil +} + +// SetDeadline sets the read and write deadlines +func (s *Stream) SetDeadline(t time.Time) error { + if err := s.SetReadDeadline(t); err != nil { + return err + } + if err := s.SetWriteDeadline(t); err != nil { + return err + } + return nil +} + +// SetReadDeadline sets the deadline for future Read calls. +func (s *Stream) SetReadDeadline(t time.Time) error { + s.readDeadline.Store(t) + return nil +} + +// SetWriteDeadline sets the deadline for future Write calls +func (s *Stream) SetWriteDeadline(t time.Time) error { + s.writeDeadline.Store(t) + return nil +} + +// Shrink is used to compact the amount of buffers utilized +// This is useful when using Yamux in a connection pool to reduce +// the idle memory utilization. +func (s *Stream) Shrink() { + s.recvLock.Lock() + if s.recvBuf != nil && s.recvBuf.Len() == 0 { + s.recvBuf = nil + } + s.recvLock.Unlock() +} diff --git a/vendor/github.com/hashicorp/yamux/util.go b/vendor/github.com/hashicorp/yamux/util.go new file mode 100644 index 00000000..8a73e924 --- /dev/null +++ b/vendor/github.com/hashicorp/yamux/util.go @@ -0,0 +1,43 @@ +package yamux + +import ( + "sync" + "time" +) + +var ( + timerPool = &sync.Pool{ + New: func() interface{} { + timer := time.NewTimer(time.Hour * 1e6) + timer.Stop() + return timer + }, + } +) + +// asyncSendErr is used to try an async send of an error +func asyncSendErr(ch chan error, err error) { + if ch == nil { + return + } + select { + case ch <- err: + default: + } +} + +// asyncNotify is used to signal a waiting goroutine +func asyncNotify(ch chan struct{}) { + select { + case ch <- struct{}{}: + default: + } +} + +// min computes the minimum of two values +func min(a, b uint32) uint32 { + if a < b { + return a + } + return b +} diff --git a/vendor/github.com/hashicorp/yamux/util_test.go b/vendor/github.com/hashicorp/yamux/util_test.go new file mode 100644 index 00000000..dd14623a --- /dev/null +++ b/vendor/github.com/hashicorp/yamux/util_test.go @@ -0,0 +1,50 @@ +package yamux + +import ( + "testing" +) + +func TestAsyncSendErr(t *testing.T) { + ch := make(chan error) + asyncSendErr(ch, ErrTimeout) + select { + case <-ch: + t.Fatalf("should not get") + default: + } + + ch = make(chan error, 1) + asyncSendErr(ch, ErrTimeout) + select { + case <-ch: + default: + t.Fatalf("should get") + } +} + +func TestAsyncNotify(t *testing.T) { + ch := make(chan struct{}) + asyncNotify(ch) + select { + case <-ch: + t.Fatalf("should not get") + default: + } + + ch = make(chan struct{}, 1) + asyncNotify(ch) + select { + case <-ch: + default: + t.Fatalf("should get") + } +} + +func TestMin(t *testing.T) { + if min(1, 2) != 1 { + t.Fatalf("bad") + } + if min(2, 1) != 1 { + t.Fatalf("bad") + } +} -- 2.11.0