[VOL-2312] Logging - Integrate voltctl with new etcd-based dynamic loglevel mechanism. Testing is in progress
Change-Id: I2e13bb79008c9a49ebb6f58e575f51efebe6dbfd
diff --git a/vendor/go.etcd.io/etcd/pkg/logutil/discard_logger.go b/vendor/go.etcd.io/etcd/pkg/logutil/discard_logger.go
new file mode 100644
index 0000000..81b0a9d
--- /dev/null
+++ b/vendor/go.etcd.io/etcd/pkg/logutil/discard_logger.go
@@ -0,0 +1,46 @@
+// Copyright 2018 The etcd Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package logutil
+
+import (
+ "log"
+
+ "google.golang.org/grpc/grpclog"
+)
+
+// assert that "discardLogger" satisfy "Logger" interface
+var _ Logger = &discardLogger{}
+
+// NewDiscardLogger returns a new Logger that discards everything except "fatal".
+func NewDiscardLogger() Logger { return &discardLogger{} }
+
+type discardLogger struct{}
+
+func (l *discardLogger) Info(args ...interface{}) {}
+func (l *discardLogger) Infoln(args ...interface{}) {}
+func (l *discardLogger) Infof(format string, args ...interface{}) {}
+func (l *discardLogger) Warning(args ...interface{}) {}
+func (l *discardLogger) Warningln(args ...interface{}) {}
+func (l *discardLogger) Warningf(format string, args ...interface{}) {}
+func (l *discardLogger) Error(args ...interface{}) {}
+func (l *discardLogger) Errorln(args ...interface{}) {}
+func (l *discardLogger) Errorf(format string, args ...interface{}) {}
+func (l *discardLogger) Fatal(args ...interface{}) { log.Fatal(args...) }
+func (l *discardLogger) Fatalln(args ...interface{}) { log.Fatalln(args...) }
+func (l *discardLogger) Fatalf(format string, args ...interface{}) { log.Fatalf(format, args...) }
+func (l *discardLogger) V(lvl int) bool {
+ return false
+}
+func (l *discardLogger) Lvl(lvl int) grpclog.LoggerV2 { return l }
diff --git a/vendor/go.etcd.io/etcd/pkg/logutil/doc.go b/vendor/go.etcd.io/etcd/pkg/logutil/doc.go
new file mode 100644
index 0000000..e919f24
--- /dev/null
+++ b/vendor/go.etcd.io/etcd/pkg/logutil/doc.go
@@ -0,0 +1,16 @@
+// Copyright 2018 The etcd Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Package logutil includes utilities to facilitate logging.
+package logutil
diff --git a/vendor/go.etcd.io/etcd/pkg/logutil/log_level.go b/vendor/go.etcd.io/etcd/pkg/logutil/log_level.go
new file mode 100644
index 0000000..d57e173
--- /dev/null
+++ b/vendor/go.etcd.io/etcd/pkg/logutil/log_level.go
@@ -0,0 +1,70 @@
+// Copyright 2019 The etcd Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package logutil
+
+import (
+ "fmt"
+
+ "github.com/coreos/pkg/capnslog"
+ "go.uber.org/zap"
+ "go.uber.org/zap/zapcore"
+)
+
+var DefaultLogLevel = "info"
+
+// ConvertToZapLevel converts log level string to zapcore.Level.
+func ConvertToZapLevel(lvl string) zapcore.Level {
+ switch lvl {
+ case "debug":
+ return zap.DebugLevel
+ case "info":
+ return zap.InfoLevel
+ case "warn":
+ return zap.WarnLevel
+ case "error":
+ return zap.ErrorLevel
+ case "dpanic":
+ return zap.DPanicLevel
+ case "panic":
+ return zap.PanicLevel
+ case "fatal":
+ return zap.FatalLevel
+ default:
+ panic(fmt.Sprintf("unknown level %q", lvl))
+ }
+}
+
+// ConvertToCapnslogLogLevel convert log level string to capnslog.LogLevel.
+// TODO: deprecate this in 3.5
+func ConvertToCapnslogLogLevel(lvl string) capnslog.LogLevel {
+ switch lvl {
+ case "debug":
+ return capnslog.DEBUG
+ case "info":
+ return capnslog.INFO
+ case "warn":
+ return capnslog.WARNING
+ case "error":
+ return capnslog.ERROR
+ case "dpanic":
+ return capnslog.CRITICAL
+ case "panic":
+ return capnslog.CRITICAL
+ case "fatal":
+ return capnslog.CRITICAL
+ default:
+ panic(fmt.Sprintf("unknown level %q", lvl))
+ }
+}
diff --git a/vendor/go.etcd.io/etcd/pkg/logutil/logger.go b/vendor/go.etcd.io/etcd/pkg/logutil/logger.go
new file mode 100644
index 0000000..e7da80e
--- /dev/null
+++ b/vendor/go.etcd.io/etcd/pkg/logutil/logger.go
@@ -0,0 +1,64 @@
+// Copyright 2018 The etcd Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package logutil
+
+import "google.golang.org/grpc/grpclog"
+
+// Logger defines logging interface.
+// TODO: deprecate in v3.5.
+type Logger interface {
+ grpclog.LoggerV2
+
+ // Lvl returns logger if logger's verbosity level >= "lvl".
+ // Otherwise, logger that discards everything.
+ Lvl(lvl int) grpclog.LoggerV2
+}
+
+// assert that "defaultLogger" satisfy "Logger" interface
+var _ Logger = &defaultLogger{}
+
+// NewLogger wraps "grpclog.LoggerV2" that implements "Logger" interface.
+//
+// For example:
+//
+// var defaultLogger Logger
+// g := grpclog.NewLoggerV2WithVerbosity(os.Stderr, os.Stderr, os.Stderr, 4)
+// defaultLogger = NewLogger(g)
+//
+func NewLogger(g grpclog.LoggerV2) Logger { return &defaultLogger{g: g} }
+
+type defaultLogger struct {
+ g grpclog.LoggerV2
+}
+
+func (l *defaultLogger) Info(args ...interface{}) { l.g.Info(args...) }
+func (l *defaultLogger) Infoln(args ...interface{}) { l.g.Info(args...) }
+func (l *defaultLogger) Infof(format string, args ...interface{}) { l.g.Infof(format, args...) }
+func (l *defaultLogger) Warning(args ...interface{}) { l.g.Warning(args...) }
+func (l *defaultLogger) Warningln(args ...interface{}) { l.g.Warning(args...) }
+func (l *defaultLogger) Warningf(format string, args ...interface{}) { l.g.Warningf(format, args...) }
+func (l *defaultLogger) Error(args ...interface{}) { l.g.Error(args...) }
+func (l *defaultLogger) Errorln(args ...interface{}) { l.g.Error(args...) }
+func (l *defaultLogger) Errorf(format string, args ...interface{}) { l.g.Errorf(format, args...) }
+func (l *defaultLogger) Fatal(args ...interface{}) { l.g.Fatal(args...) }
+func (l *defaultLogger) Fatalln(args ...interface{}) { l.g.Fatal(args...) }
+func (l *defaultLogger) Fatalf(format string, args ...interface{}) { l.g.Fatalf(format, args...) }
+func (l *defaultLogger) V(lvl int) bool { return l.g.V(lvl) }
+func (l *defaultLogger) Lvl(lvl int) grpclog.LoggerV2 {
+ if l.g.V(lvl) {
+ return l
+ }
+ return &discardLogger{}
+}
diff --git a/vendor/go.etcd.io/etcd/pkg/logutil/merge_logger.go b/vendor/go.etcd.io/etcd/pkg/logutil/merge_logger.go
new file mode 100644
index 0000000..866b6f7
--- /dev/null
+++ b/vendor/go.etcd.io/etcd/pkg/logutil/merge_logger.go
@@ -0,0 +1,194 @@
+// Copyright 2015 The etcd Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package logutil
+
+import (
+ "fmt"
+ "sync"
+ "time"
+
+ "github.com/coreos/pkg/capnslog"
+)
+
+var (
+ defaultMergePeriod = time.Second
+ defaultTimeOutputScale = 10 * time.Millisecond
+
+ outputInterval = time.Second
+)
+
+// line represents a log line that can be printed out
+// through capnslog.PackageLogger.
+type line struct {
+ level capnslog.LogLevel
+ str string
+}
+
+func (l line) append(s string) line {
+ return line{
+ level: l.level,
+ str: l.str + " " + s,
+ }
+}
+
+// status represents the merge status of a line.
+type status struct {
+ period time.Duration
+
+ start time.Time // start time of latest merge period
+ count int // number of merged lines from starting
+}
+
+func (s *status) isInMergePeriod(now time.Time) bool {
+ return s.period == 0 || s.start.Add(s.period).After(now)
+}
+
+func (s *status) isEmpty() bool { return s.count == 0 }
+
+func (s *status) summary(now time.Time) string {
+ ts := s.start.Round(defaultTimeOutputScale)
+ took := now.Round(defaultTimeOutputScale).Sub(ts)
+ return fmt.Sprintf("[merged %d repeated lines in %s]", s.count, took)
+}
+
+func (s *status) reset(now time.Time) {
+ s.start = now
+ s.count = 0
+}
+
+// MergeLogger supports merge logging, which merges repeated log lines
+// and prints summary log lines instead.
+//
+// For merge logging, MergeLogger prints out the line when the line appears
+// at the first time. MergeLogger holds the same log line printed within
+// defaultMergePeriod, and prints out summary log line at the end of defaultMergePeriod.
+// It stops merging when the line doesn't appear within the
+// defaultMergePeriod.
+type MergeLogger struct {
+ *capnslog.PackageLogger
+
+ mu sync.Mutex // protect statusm
+ statusm map[line]*status
+}
+
+func NewMergeLogger(logger *capnslog.PackageLogger) *MergeLogger {
+ l := &MergeLogger{
+ PackageLogger: logger,
+ statusm: make(map[line]*status),
+ }
+ go l.outputLoop()
+ return l
+}
+
+func (l *MergeLogger) MergeInfo(entries ...interface{}) {
+ l.merge(line{
+ level: capnslog.INFO,
+ str: fmt.Sprint(entries...),
+ })
+}
+
+func (l *MergeLogger) MergeInfof(format string, args ...interface{}) {
+ l.merge(line{
+ level: capnslog.INFO,
+ str: fmt.Sprintf(format, args...),
+ })
+}
+
+func (l *MergeLogger) MergeNotice(entries ...interface{}) {
+ l.merge(line{
+ level: capnslog.NOTICE,
+ str: fmt.Sprint(entries...),
+ })
+}
+
+func (l *MergeLogger) MergeNoticef(format string, args ...interface{}) {
+ l.merge(line{
+ level: capnslog.NOTICE,
+ str: fmt.Sprintf(format, args...),
+ })
+}
+
+func (l *MergeLogger) MergeWarning(entries ...interface{}) {
+ l.merge(line{
+ level: capnslog.WARNING,
+ str: fmt.Sprint(entries...),
+ })
+}
+
+func (l *MergeLogger) MergeWarningf(format string, args ...interface{}) {
+ l.merge(line{
+ level: capnslog.WARNING,
+ str: fmt.Sprintf(format, args...),
+ })
+}
+
+func (l *MergeLogger) MergeError(entries ...interface{}) {
+ l.merge(line{
+ level: capnslog.ERROR,
+ str: fmt.Sprint(entries...),
+ })
+}
+
+func (l *MergeLogger) MergeErrorf(format string, args ...interface{}) {
+ l.merge(line{
+ level: capnslog.ERROR,
+ str: fmt.Sprintf(format, args...),
+ })
+}
+
+func (l *MergeLogger) merge(ln line) {
+ l.mu.Lock()
+
+ // increase count if the logger is merging the line
+ if status, ok := l.statusm[ln]; ok {
+ status.count++
+ l.mu.Unlock()
+ return
+ }
+
+ // initialize status of the line
+ l.statusm[ln] = &status{
+ period: defaultMergePeriod,
+ start: time.Now(),
+ }
+ // release the lock before IO operation
+ l.mu.Unlock()
+ // print out the line at its first time
+ l.PackageLogger.Logf(ln.level, ln.str)
+}
+
+func (l *MergeLogger) outputLoop() {
+ for now := range time.Tick(outputInterval) {
+ var outputs []line
+
+ l.mu.Lock()
+ for ln, status := range l.statusm {
+ if status.isInMergePeriod(now) {
+ continue
+ }
+ if status.isEmpty() {
+ delete(l.statusm, ln)
+ continue
+ }
+ outputs = append(outputs, ln.append(status.summary(now)))
+ status.reset(now)
+ }
+ l.mu.Unlock()
+
+ for _, o := range outputs {
+ l.PackageLogger.Logf(o.level, o.str)
+ }
+ }
+}
diff --git a/vendor/go.etcd.io/etcd/pkg/logutil/package_logger.go b/vendor/go.etcd.io/etcd/pkg/logutil/package_logger.go
new file mode 100644
index 0000000..729cbdb
--- /dev/null
+++ b/vendor/go.etcd.io/etcd/pkg/logutil/package_logger.go
@@ -0,0 +1,60 @@
+// Copyright 2018 The etcd Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package logutil
+
+import (
+ "github.com/coreos/pkg/capnslog"
+ "google.golang.org/grpc/grpclog"
+)
+
+// assert that "packageLogger" satisfy "Logger" interface
+var _ Logger = &packageLogger{}
+
+// NewPackageLogger wraps "*capnslog.PackageLogger" that implements "Logger" interface.
+//
+// For example:
+//
+// var defaultLogger Logger
+// defaultLogger = NewPackageLogger("go.etcd.io/etcd", "snapshot")
+//
+func NewPackageLogger(repo, pkg string) Logger {
+ return &packageLogger{p: capnslog.NewPackageLogger(repo, pkg)}
+}
+
+type packageLogger struct {
+ p *capnslog.PackageLogger
+}
+
+func (l *packageLogger) Info(args ...interface{}) { l.p.Info(args...) }
+func (l *packageLogger) Infoln(args ...interface{}) { l.p.Info(args...) }
+func (l *packageLogger) Infof(format string, args ...interface{}) { l.p.Infof(format, args...) }
+func (l *packageLogger) Warning(args ...interface{}) { l.p.Warning(args...) }
+func (l *packageLogger) Warningln(args ...interface{}) { l.p.Warning(args...) }
+func (l *packageLogger) Warningf(format string, args ...interface{}) { l.p.Warningf(format, args...) }
+func (l *packageLogger) Error(args ...interface{}) { l.p.Error(args...) }
+func (l *packageLogger) Errorln(args ...interface{}) { l.p.Error(args...) }
+func (l *packageLogger) Errorf(format string, args ...interface{}) { l.p.Errorf(format, args...) }
+func (l *packageLogger) Fatal(args ...interface{}) { l.p.Fatal(args...) }
+func (l *packageLogger) Fatalln(args ...interface{}) { l.p.Fatal(args...) }
+func (l *packageLogger) Fatalf(format string, args ...interface{}) { l.p.Fatalf(format, args...) }
+func (l *packageLogger) V(lvl int) bool {
+ return l.p.LevelAt(capnslog.LogLevel(lvl))
+}
+func (l *packageLogger) Lvl(lvl int) grpclog.LoggerV2 {
+ if l.p.LevelAt(capnslog.LogLevel(lvl)) {
+ return l
+ }
+ return &discardLogger{}
+}
diff --git a/vendor/go.etcd.io/etcd/pkg/logutil/zap.go b/vendor/go.etcd.io/etcd/pkg/logutil/zap.go
new file mode 100644
index 0000000..8fc6e03
--- /dev/null
+++ b/vendor/go.etcd.io/etcd/pkg/logutil/zap.go
@@ -0,0 +1,91 @@
+// Copyright 2019 The etcd Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package logutil
+
+import (
+ "sort"
+
+ "go.uber.org/zap"
+ "go.uber.org/zap/zapcore"
+)
+
+// DefaultZapLoggerConfig defines default zap logger configuration.
+var DefaultZapLoggerConfig = zap.Config{
+ Level: zap.NewAtomicLevelAt(ConvertToZapLevel(DefaultLogLevel)),
+
+ Development: false,
+ Sampling: &zap.SamplingConfig{
+ Initial: 100,
+ Thereafter: 100,
+ },
+
+ Encoding: "json",
+
+ // copied from "zap.NewProductionEncoderConfig" with some updates
+ EncoderConfig: zapcore.EncoderConfig{
+ TimeKey: "ts",
+ LevelKey: "level",
+ NameKey: "logger",
+ CallerKey: "caller",
+ MessageKey: "msg",
+ StacktraceKey: "stacktrace",
+ LineEnding: zapcore.DefaultLineEnding,
+ EncodeLevel: zapcore.LowercaseLevelEncoder,
+ EncodeTime: zapcore.ISO8601TimeEncoder,
+ EncodeDuration: zapcore.StringDurationEncoder,
+ EncodeCaller: zapcore.ShortCallerEncoder,
+ },
+
+ // Use "/dev/null" to discard all
+ OutputPaths: []string{"stderr"},
+ ErrorOutputPaths: []string{"stderr"},
+}
+
+// MergeOutputPaths merges logging output paths, resolving conflicts.
+func MergeOutputPaths(cfg zap.Config) zap.Config {
+ outputs := make(map[string]struct{})
+ for _, v := range cfg.OutputPaths {
+ outputs[v] = struct{}{}
+ }
+ outputSlice := make([]string, 0)
+ if _, ok := outputs["/dev/null"]; ok {
+ // "/dev/null" to discard all
+ outputSlice = []string{"/dev/null"}
+ } else {
+ for k := range outputs {
+ outputSlice = append(outputSlice, k)
+ }
+ }
+ cfg.OutputPaths = outputSlice
+ sort.Strings(cfg.OutputPaths)
+
+ errOutputs := make(map[string]struct{})
+ for _, v := range cfg.ErrorOutputPaths {
+ errOutputs[v] = struct{}{}
+ }
+ errOutputSlice := make([]string, 0)
+ if _, ok := errOutputs["/dev/null"]; ok {
+ // "/dev/null" to discard all
+ errOutputSlice = []string{"/dev/null"}
+ } else {
+ for k := range errOutputs {
+ errOutputSlice = append(errOutputSlice, k)
+ }
+ }
+ cfg.ErrorOutputPaths = errOutputSlice
+ sort.Strings(cfg.ErrorOutputPaths)
+
+ return cfg
+}
diff --git a/vendor/go.etcd.io/etcd/pkg/logutil/zap_grpc.go b/vendor/go.etcd.io/etcd/pkg/logutil/zap_grpc.go
new file mode 100644
index 0000000..3f48d81
--- /dev/null
+++ b/vendor/go.etcd.io/etcd/pkg/logutil/zap_grpc.go
@@ -0,0 +1,111 @@
+// Copyright 2018 The etcd Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package logutil
+
+import (
+ "go.uber.org/zap"
+ "go.uber.org/zap/zapcore"
+ "google.golang.org/grpc/grpclog"
+)
+
+// NewGRPCLoggerV2 converts "*zap.Logger" to "grpclog.LoggerV2".
+// It discards all INFO level logging in gRPC, if debug level
+// is not enabled in "*zap.Logger".
+func NewGRPCLoggerV2(lcfg zap.Config) (grpclog.LoggerV2, error) {
+ lg, err := lcfg.Build(zap.AddCallerSkip(1)) // to annotate caller outside of "logutil"
+ if err != nil {
+ return nil, err
+ }
+ return &zapGRPCLogger{lg: lg, sugar: lg.Sugar()}, nil
+}
+
+// NewGRPCLoggerV2FromZapCore creates "grpclog.LoggerV2" from "zap.Core"
+// and "zapcore.WriteSyncer". It discards all INFO level logging in gRPC,
+// if debug level is not enabled in "*zap.Logger".
+func NewGRPCLoggerV2FromZapCore(cr zapcore.Core, syncer zapcore.WriteSyncer) grpclog.LoggerV2 {
+ // "AddCallerSkip" to annotate caller outside of "logutil"
+ lg := zap.New(cr, zap.AddCaller(), zap.AddCallerSkip(1), zap.ErrorOutput(syncer))
+ return &zapGRPCLogger{lg: lg, sugar: lg.Sugar()}
+}
+
+type zapGRPCLogger struct {
+ lg *zap.Logger
+ sugar *zap.SugaredLogger
+}
+
+func (zl *zapGRPCLogger) Info(args ...interface{}) {
+ if !zl.lg.Core().Enabled(zapcore.DebugLevel) {
+ return
+ }
+ zl.sugar.Info(args...)
+}
+
+func (zl *zapGRPCLogger) Infoln(args ...interface{}) {
+ if !zl.lg.Core().Enabled(zapcore.DebugLevel) {
+ return
+ }
+ zl.sugar.Info(args...)
+}
+
+func (zl *zapGRPCLogger) Infof(format string, args ...interface{}) {
+ if !zl.lg.Core().Enabled(zapcore.DebugLevel) {
+ return
+ }
+ zl.sugar.Infof(format, args...)
+}
+
+func (zl *zapGRPCLogger) Warning(args ...interface{}) {
+ zl.sugar.Warn(args...)
+}
+
+func (zl *zapGRPCLogger) Warningln(args ...interface{}) {
+ zl.sugar.Warn(args...)
+}
+
+func (zl *zapGRPCLogger) Warningf(format string, args ...interface{}) {
+ zl.sugar.Warnf(format, args...)
+}
+
+func (zl *zapGRPCLogger) Error(args ...interface{}) {
+ zl.sugar.Error(args...)
+}
+
+func (zl *zapGRPCLogger) Errorln(args ...interface{}) {
+ zl.sugar.Error(args...)
+}
+
+func (zl *zapGRPCLogger) Errorf(format string, args ...interface{}) {
+ zl.sugar.Errorf(format, args...)
+}
+
+func (zl *zapGRPCLogger) Fatal(args ...interface{}) {
+ zl.sugar.Fatal(args...)
+}
+
+func (zl *zapGRPCLogger) Fatalln(args ...interface{}) {
+ zl.sugar.Fatal(args...)
+}
+
+func (zl *zapGRPCLogger) Fatalf(format string, args ...interface{}) {
+ zl.sugar.Fatalf(format, args...)
+}
+
+func (zl *zapGRPCLogger) V(l int) bool {
+ // infoLog == 0
+ if l <= 0 { // debug level, then we ignore info level in gRPC
+ return !zl.lg.Core().Enabled(zapcore.DebugLevel)
+ }
+ return true
+}
diff --git a/vendor/go.etcd.io/etcd/pkg/logutil/zap_journal.go b/vendor/go.etcd.io/etcd/pkg/logutil/zap_journal.go
new file mode 100644
index 0000000..fcd3903
--- /dev/null
+++ b/vendor/go.etcd.io/etcd/pkg/logutil/zap_journal.go
@@ -0,0 +1,92 @@
+// Copyright 2018 The etcd Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// +build !windows
+
+package logutil
+
+import (
+ "bytes"
+ "encoding/json"
+ "fmt"
+ "io"
+ "os"
+ "path/filepath"
+
+ "go.etcd.io/etcd/pkg/systemd"
+
+ "github.com/coreos/go-systemd/journal"
+ "go.uber.org/zap/zapcore"
+)
+
+// NewJournalWriter wraps "io.Writer" to redirect log output
+// to the local systemd journal. If journald send fails, it fails
+// back to writing to the original writer.
+// The decode overhead is only <30µs per write.
+// Reference: https://github.com/coreos/pkg/blob/master/capnslog/journald_formatter.go
+func NewJournalWriter(wr io.Writer) (io.Writer, error) {
+ return &journalWriter{Writer: wr}, systemd.DialJournal()
+}
+
+type journalWriter struct {
+ io.Writer
+}
+
+// WARN: assume that etcd uses default field names in zap encoder config
+// make sure to keep this up-to-date!
+type logLine struct {
+ Level string `json:"level"`
+ Caller string `json:"caller"`
+}
+
+func (w *journalWriter) Write(p []byte) (int, error) {
+ line := &logLine{}
+ if err := json.NewDecoder(bytes.NewReader(p)).Decode(line); err != nil {
+ return 0, err
+ }
+
+ var pri journal.Priority
+ switch line.Level {
+ case zapcore.DebugLevel.String():
+ pri = journal.PriDebug
+ case zapcore.InfoLevel.String():
+ pri = journal.PriInfo
+
+ case zapcore.WarnLevel.String():
+ pri = journal.PriWarning
+ case zapcore.ErrorLevel.String():
+ pri = journal.PriErr
+
+ case zapcore.DPanicLevel.String():
+ pri = journal.PriCrit
+ case zapcore.PanicLevel.String():
+ pri = journal.PriCrit
+ case zapcore.FatalLevel.String():
+ pri = journal.PriCrit
+
+ default:
+ panic(fmt.Errorf("unknown log level: %q", line.Level))
+ }
+
+ err := journal.Send(string(p), pri, map[string]string{
+ "PACKAGE": filepath.Dir(line.Caller),
+ "SYSLOG_IDENTIFIER": filepath.Base(os.Args[0]),
+ })
+ if err != nil {
+ // "journal" also falls back to stderr
+ // "fmt.Fprintln(os.Stderr, s)"
+ return w.Writer.Write(p)
+ }
+ return 0, nil
+}
diff --git a/vendor/go.etcd.io/etcd/pkg/logutil/zap_raft.go b/vendor/go.etcd.io/etcd/pkg/logutil/zap_raft.go
new file mode 100644
index 0000000..f016b30
--- /dev/null
+++ b/vendor/go.etcd.io/etcd/pkg/logutil/zap_raft.go
@@ -0,0 +1,102 @@
+// Copyright 2018 The etcd Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package logutil
+
+import (
+ "errors"
+
+ "go.etcd.io/etcd/raft"
+
+ "go.uber.org/zap"
+ "go.uber.org/zap/zapcore"
+)
+
+// NewRaftLogger builds "raft.Logger" from "*zap.Config".
+func NewRaftLogger(lcfg *zap.Config) (raft.Logger, error) {
+ if lcfg == nil {
+ return nil, errors.New("nil zap.Config")
+ }
+ lg, err := lcfg.Build(zap.AddCallerSkip(1)) // to annotate caller outside of "logutil"
+ if err != nil {
+ return nil, err
+ }
+ return &zapRaftLogger{lg: lg, sugar: lg.Sugar()}, nil
+}
+
+// NewRaftLoggerZap converts "*zap.Logger" to "raft.Logger".
+func NewRaftLoggerZap(lg *zap.Logger) raft.Logger {
+ return &zapRaftLogger{lg: lg, sugar: lg.Sugar()}
+}
+
+// NewRaftLoggerFromZapCore creates "raft.Logger" from "zap.Core"
+// and "zapcore.WriteSyncer".
+func NewRaftLoggerFromZapCore(cr zapcore.Core, syncer zapcore.WriteSyncer) raft.Logger {
+ // "AddCallerSkip" to annotate caller outside of "logutil"
+ lg := zap.New(cr, zap.AddCaller(), zap.AddCallerSkip(1), zap.ErrorOutput(syncer))
+ return &zapRaftLogger{lg: lg, sugar: lg.Sugar()}
+}
+
+type zapRaftLogger struct {
+ lg *zap.Logger
+ sugar *zap.SugaredLogger
+}
+
+func (zl *zapRaftLogger) Debug(args ...interface{}) {
+ zl.sugar.Debug(args...)
+}
+
+func (zl *zapRaftLogger) Debugf(format string, args ...interface{}) {
+ zl.sugar.Debugf(format, args...)
+}
+
+func (zl *zapRaftLogger) Error(args ...interface{}) {
+ zl.sugar.Error(args...)
+}
+
+func (zl *zapRaftLogger) Errorf(format string, args ...interface{}) {
+ zl.sugar.Errorf(format, args...)
+}
+
+func (zl *zapRaftLogger) Info(args ...interface{}) {
+ zl.sugar.Info(args...)
+}
+
+func (zl *zapRaftLogger) Infof(format string, args ...interface{}) {
+ zl.sugar.Infof(format, args...)
+}
+
+func (zl *zapRaftLogger) Warning(args ...interface{}) {
+ zl.sugar.Warn(args...)
+}
+
+func (zl *zapRaftLogger) Warningf(format string, args ...interface{}) {
+ zl.sugar.Warnf(format, args...)
+}
+
+func (zl *zapRaftLogger) Fatal(args ...interface{}) {
+ zl.sugar.Fatal(args...)
+}
+
+func (zl *zapRaftLogger) Fatalf(format string, args ...interface{}) {
+ zl.sugar.Fatalf(format, args...)
+}
+
+func (zl *zapRaftLogger) Panic(args ...interface{}) {
+ zl.sugar.Panic(args...)
+}
+
+func (zl *zapRaftLogger) Panicf(format string, args ...interface{}) {
+ zl.sugar.Panicf(format, args...)
+}
diff --git a/vendor/go.etcd.io/etcd/pkg/systemd/doc.go b/vendor/go.etcd.io/etcd/pkg/systemd/doc.go
new file mode 100644
index 0000000..30e77ce
--- /dev/null
+++ b/vendor/go.etcd.io/etcd/pkg/systemd/doc.go
@@ -0,0 +1,16 @@
+// Copyright 2018 The etcd Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Package systemd provides utility functions for systemd.
+package systemd
diff --git a/vendor/go.etcd.io/etcd/pkg/systemd/journal.go b/vendor/go.etcd.io/etcd/pkg/systemd/journal.go
new file mode 100644
index 0000000..b861c69
--- /dev/null
+++ b/vendor/go.etcd.io/etcd/pkg/systemd/journal.go
@@ -0,0 +1,29 @@
+// Copyright 2018 The etcd Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package systemd
+
+import "net"
+
+// DialJournal returns no error if the process can dial journal socket.
+// Returns an error if dial failed, whichi indicates journald is not available
+// (e.g. run embedded etcd as docker daemon).
+// Reference: https://github.com/coreos/go-systemd/blob/master/journal/journal.go.
+func DialJournal() error {
+ conn, err := net.Dial("unixgram", "/run/systemd/journal/socket")
+ if conn != nil {
+ defer conn.Close()
+ }
+ return err
+}
diff --git a/vendor/go.etcd.io/etcd/pkg/types/doc.go b/vendor/go.etcd.io/etcd/pkg/types/doc.go
new file mode 100644
index 0000000..de8ef0b
--- /dev/null
+++ b/vendor/go.etcd.io/etcd/pkg/types/doc.go
@@ -0,0 +1,17 @@
+// Copyright 2015 The etcd Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Package types declares various data types and implements type-checking
+// functions.
+package types
diff --git a/vendor/go.etcd.io/etcd/pkg/types/id.go b/vendor/go.etcd.io/etcd/pkg/types/id.go
new file mode 100644
index 0000000..ae00388
--- /dev/null
+++ b/vendor/go.etcd.io/etcd/pkg/types/id.go
@@ -0,0 +1,39 @@
+// Copyright 2015 The etcd Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package types
+
+import "strconv"
+
+// ID represents a generic identifier which is canonically
+// stored as a uint64 but is typically represented as a
+// base-16 string for input/output
+type ID uint64
+
+func (i ID) String() string {
+ return strconv.FormatUint(uint64(i), 16)
+}
+
+// IDFromString attempts to create an ID from a base-16 string.
+func IDFromString(s string) (ID, error) {
+ i, err := strconv.ParseUint(s, 16, 64)
+ return ID(i), err
+}
+
+// IDSlice implements the sort interface
+type IDSlice []ID
+
+func (p IDSlice) Len() int { return len(p) }
+func (p IDSlice) Less(i, j int) bool { return uint64(p[i]) < uint64(p[j]) }
+func (p IDSlice) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
diff --git a/vendor/go.etcd.io/etcd/pkg/types/set.go b/vendor/go.etcd.io/etcd/pkg/types/set.go
new file mode 100644
index 0000000..e7a3cdc
--- /dev/null
+++ b/vendor/go.etcd.io/etcd/pkg/types/set.go
@@ -0,0 +1,195 @@
+// Copyright 2015 The etcd Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package types
+
+import (
+ "reflect"
+ "sort"
+ "sync"
+)
+
+type Set interface {
+ Add(string)
+ Remove(string)
+ Contains(string) bool
+ Equals(Set) bool
+ Length() int
+ Values() []string
+ Copy() Set
+ Sub(Set) Set
+}
+
+func NewUnsafeSet(values ...string) *unsafeSet {
+ set := &unsafeSet{make(map[string]struct{})}
+ for _, v := range values {
+ set.Add(v)
+ }
+ return set
+}
+
+func NewThreadsafeSet(values ...string) *tsafeSet {
+ us := NewUnsafeSet(values...)
+ return &tsafeSet{us, sync.RWMutex{}}
+}
+
+type unsafeSet struct {
+ d map[string]struct{}
+}
+
+// Add adds a new value to the set (no-op if the value is already present)
+func (us *unsafeSet) Add(value string) {
+ us.d[value] = struct{}{}
+}
+
+// Remove removes the given value from the set
+func (us *unsafeSet) Remove(value string) {
+ delete(us.d, value)
+}
+
+// Contains returns whether the set contains the given value
+func (us *unsafeSet) Contains(value string) (exists bool) {
+ _, exists = us.d[value]
+ return exists
+}
+
+// ContainsAll returns whether the set contains all given values
+func (us *unsafeSet) ContainsAll(values []string) bool {
+ for _, s := range values {
+ if !us.Contains(s) {
+ return false
+ }
+ }
+ return true
+}
+
+// Equals returns whether the contents of two sets are identical
+func (us *unsafeSet) Equals(other Set) bool {
+ v1 := sort.StringSlice(us.Values())
+ v2 := sort.StringSlice(other.Values())
+ v1.Sort()
+ v2.Sort()
+ return reflect.DeepEqual(v1, v2)
+}
+
+// Length returns the number of elements in the set
+func (us *unsafeSet) Length() int {
+ return len(us.d)
+}
+
+// Values returns the values of the Set in an unspecified order.
+func (us *unsafeSet) Values() (values []string) {
+ values = make([]string, 0)
+ for val := range us.d {
+ values = append(values, val)
+ }
+ return values
+}
+
+// Copy creates a new Set containing the values of the first
+func (us *unsafeSet) Copy() Set {
+ cp := NewUnsafeSet()
+ for val := range us.d {
+ cp.Add(val)
+ }
+
+ return cp
+}
+
+// Sub removes all elements in other from the set
+func (us *unsafeSet) Sub(other Set) Set {
+ oValues := other.Values()
+ result := us.Copy().(*unsafeSet)
+
+ for _, val := range oValues {
+ if _, ok := result.d[val]; !ok {
+ continue
+ }
+ delete(result.d, val)
+ }
+
+ return result
+}
+
+type tsafeSet struct {
+ us *unsafeSet
+ m sync.RWMutex
+}
+
+func (ts *tsafeSet) Add(value string) {
+ ts.m.Lock()
+ defer ts.m.Unlock()
+ ts.us.Add(value)
+}
+
+func (ts *tsafeSet) Remove(value string) {
+ ts.m.Lock()
+ defer ts.m.Unlock()
+ ts.us.Remove(value)
+}
+
+func (ts *tsafeSet) Contains(value string) (exists bool) {
+ ts.m.RLock()
+ defer ts.m.RUnlock()
+ return ts.us.Contains(value)
+}
+
+func (ts *tsafeSet) Equals(other Set) bool {
+ ts.m.RLock()
+ defer ts.m.RUnlock()
+
+ // If ts and other represent the same variable, avoid calling
+ // ts.us.Equals(other), to avoid double RLock bug
+ if _other, ok := other.(*tsafeSet); ok {
+ if _other == ts {
+ return true
+ }
+ }
+ return ts.us.Equals(other)
+}
+
+func (ts *tsafeSet) Length() int {
+ ts.m.RLock()
+ defer ts.m.RUnlock()
+ return ts.us.Length()
+}
+
+func (ts *tsafeSet) Values() (values []string) {
+ ts.m.RLock()
+ defer ts.m.RUnlock()
+ return ts.us.Values()
+}
+
+func (ts *tsafeSet) Copy() Set {
+ ts.m.RLock()
+ defer ts.m.RUnlock()
+ usResult := ts.us.Copy().(*unsafeSet)
+ return &tsafeSet{usResult, sync.RWMutex{}}
+}
+
+func (ts *tsafeSet) Sub(other Set) Set {
+ ts.m.RLock()
+ defer ts.m.RUnlock()
+
+ // If ts and other represent the same variable, avoid calling
+ // ts.us.Sub(other), to avoid double RLock bug
+ if _other, ok := other.(*tsafeSet); ok {
+ if _other == ts {
+ usResult := NewUnsafeSet()
+ return &tsafeSet{usResult, sync.RWMutex{}}
+ }
+ }
+ usResult := ts.us.Sub(other).(*unsafeSet)
+ return &tsafeSet{usResult, sync.RWMutex{}}
+}
diff --git a/vendor/go.etcd.io/etcd/pkg/types/slice.go b/vendor/go.etcd.io/etcd/pkg/types/slice.go
new file mode 100644
index 0000000..0dd9ca7
--- /dev/null
+++ b/vendor/go.etcd.io/etcd/pkg/types/slice.go
@@ -0,0 +1,22 @@
+// Copyright 2015 The etcd Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package types
+
+// Uint64Slice implements sort interface
+type Uint64Slice []uint64
+
+func (p Uint64Slice) Len() int { return len(p) }
+func (p Uint64Slice) Less(i, j int) bool { return p[i] < p[j] }
+func (p Uint64Slice) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
diff --git a/vendor/go.etcd.io/etcd/pkg/types/urls.go b/vendor/go.etcd.io/etcd/pkg/types/urls.go
new file mode 100644
index 0000000..9e5d03f
--- /dev/null
+++ b/vendor/go.etcd.io/etcd/pkg/types/urls.go
@@ -0,0 +1,82 @@
+// Copyright 2015 The etcd Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package types
+
+import (
+ "errors"
+ "fmt"
+ "net"
+ "net/url"
+ "sort"
+ "strings"
+)
+
+type URLs []url.URL
+
+func NewURLs(strs []string) (URLs, error) {
+ all := make([]url.URL, len(strs))
+ if len(all) == 0 {
+ return nil, errors.New("no valid URLs given")
+ }
+ for i, in := range strs {
+ in = strings.TrimSpace(in)
+ u, err := url.Parse(in)
+ if err != nil {
+ return nil, err
+ }
+ if u.Scheme != "http" && u.Scheme != "https" && u.Scheme != "unix" && u.Scheme != "unixs" {
+ return nil, fmt.Errorf("URL scheme must be http, https, unix, or unixs: %s", in)
+ }
+ if _, _, err := net.SplitHostPort(u.Host); err != nil {
+ return nil, fmt.Errorf(`URL address does not have the form "host:port": %s`, in)
+ }
+ if u.Path != "" {
+ return nil, fmt.Errorf("URL must not contain a path: %s", in)
+ }
+ all[i] = *u
+ }
+ us := URLs(all)
+ us.Sort()
+
+ return us, nil
+}
+
+func MustNewURLs(strs []string) URLs {
+ urls, err := NewURLs(strs)
+ if err != nil {
+ panic(err)
+ }
+ return urls
+}
+
+func (us URLs) String() string {
+ return strings.Join(us.StringSlice(), ",")
+}
+
+func (us *URLs) Sort() {
+ sort.Sort(us)
+}
+func (us URLs) Len() int { return len(us) }
+func (us URLs) Less(i, j int) bool { return us[i].String() < us[j].String() }
+func (us URLs) Swap(i, j int) { us[i], us[j] = us[j], us[i] }
+
+func (us URLs) StringSlice() []string {
+ out := make([]string, len(us))
+ for i := range us {
+ out[i] = us[i].String()
+ }
+
+ return out
+}
diff --git a/vendor/go.etcd.io/etcd/pkg/types/urlsmap.go b/vendor/go.etcd.io/etcd/pkg/types/urlsmap.go
new file mode 100644
index 0000000..47690cc
--- /dev/null
+++ b/vendor/go.etcd.io/etcd/pkg/types/urlsmap.go
@@ -0,0 +1,107 @@
+// Copyright 2015 The etcd Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package types
+
+import (
+ "fmt"
+ "sort"
+ "strings"
+)
+
+// URLsMap is a map from a name to its URLs.
+type URLsMap map[string]URLs
+
+// NewURLsMap returns a URLsMap instantiated from the given string,
+// which consists of discovery-formatted names-to-URLs, like:
+// mach0=http://1.1.1.1:2380,mach0=http://2.2.2.2::2380,mach1=http://3.3.3.3:2380,mach2=http://4.4.4.4:2380
+func NewURLsMap(s string) (URLsMap, error) {
+ m := parse(s)
+
+ cl := URLsMap{}
+ for name, urls := range m {
+ us, err := NewURLs(urls)
+ if err != nil {
+ return nil, err
+ }
+ cl[name] = us
+ }
+ return cl, nil
+}
+
+// NewURLsMapFromStringMap takes a map of strings and returns a URLsMap. The
+// string values in the map can be multiple values separated by the sep string.
+func NewURLsMapFromStringMap(m map[string]string, sep string) (URLsMap, error) {
+ var err error
+ um := URLsMap{}
+ for k, v := range m {
+ um[k], err = NewURLs(strings.Split(v, sep))
+ if err != nil {
+ return nil, err
+ }
+ }
+ return um, nil
+}
+
+// String turns URLsMap into discovery-formatted name-to-URLs sorted by name.
+func (c URLsMap) String() string {
+ var pairs []string
+ for name, urls := range c {
+ for _, url := range urls {
+ pairs = append(pairs, fmt.Sprintf("%s=%s", name, url.String()))
+ }
+ }
+ sort.Strings(pairs)
+ return strings.Join(pairs, ",")
+}
+
+// URLs returns a list of all URLs.
+// The returned list is sorted in ascending lexicographical order.
+func (c URLsMap) URLs() []string {
+ var urls []string
+ for _, us := range c {
+ for _, u := range us {
+ urls = append(urls, u.String())
+ }
+ }
+ sort.Strings(urls)
+ return urls
+}
+
+// Len returns the size of URLsMap.
+func (c URLsMap) Len() int {
+ return len(c)
+}
+
+// parse parses the given string and returns a map listing the values specified for each key.
+func parse(s string) map[string][]string {
+ m := make(map[string][]string)
+ for s != "" {
+ key := s
+ if i := strings.IndexAny(key, ","); i >= 0 {
+ key, s = key[:i], key[i+1:]
+ } else {
+ s = ""
+ }
+ if key == "" {
+ continue
+ }
+ value := ""
+ if i := strings.Index(key, "="); i >= 0 {
+ key, value = key[:i], key[i+1:]
+ }
+ m[key] = append(m[key], value)
+ }
+ return m
+}