diff --git a/glide.lock b/glide.lock index 07f8f056..5487a6fa 100644 --- a/glide.lock +++ b/glide.lock @@ -1,5 +1,5 @@ hash: 2ff7c989fb0fde1375999fded74ae44e10be513a21416571f026390b679924e4 -updated: 2017-02-15T13:56:15.338996189-03:00 +updated: 2017-02-21T14:44:44.812460227-03:00 imports: - name: github.com/bradfitz/slice version: d9036e2120b5ddfa53f3ebccd618c4af275f47da @@ -23,8 +23,10 @@ imports: version: eeaced052adbcfeea372c749c281099ed7fdaa38 - name: github.com/pborman/getopt version: 7148bc3a4c3008adfcab60cbebfd0576018f330b + subpackages: + - v2 - name: github.com/percona/pmgo - version: 27d979df6c6885ff16abe375aead061a86da6df8 + version: 9fce66aa289ba956854ea42a8615128982b5a85e subpackages: - pmgomock - name: github.com/pkg/errors diff --git a/src/go/glide.lock b/src/go/glide.lock deleted file mode 100644 index 9040818f..00000000 --- a/src/go/glide.lock +++ /dev/null @@ -1,75 +0,0 @@ -hash: 2ff7c989fb0fde1375999fded74ae44e10be513a21416571f026390b679924e4 -updated: 2017-02-16T13:05:08.118382254-03:00 -imports: -- name: github.com/bradfitz/slice - version: d9036e2120b5ddfa53f3ebccd618c4af275f47da -- name: github.com/go-ole/go-ole - version: de8695c8edbf8236f30d6e1376e20b198a028d42 - subpackages: - - oleutil -- name: github.com/golang/mock - version: bd3c8e81be01eef76d4b503f5e687d2d1354d2d9 - subpackages: - - gomock -- name: github.com/hashicorp/go-version - version: 03c5bf6be031b6dd45afec16b1cf94fc8938bc77 -- name: github.com/howeyc/gopass - version: bf9dde6d0d2c004a008c27aaee91170c786f6db8 -- name: github.com/kr/pretty - version: cfb55aafdaf3ec08f0db22699ab822c50091b1c4 -- name: github.com/kr/text - version: 7cafcd837844e784b526369c9bce262804aebc60 -- name: github.com/montanaflynn/stats - version: eeaced052adbcfeea372c749c281099ed7fdaa38 -- name: github.com/pborman/getopt - version: 7148bc3a4c3008adfcab60cbebfd0576018f330b -- name: github.com/percona/pmgo - version: 27d979df6c6885ff16abe375aead061a86da6df8 - subpackages: - - pmgomock -- name: github.com/pkg/errors - version: 645ef00459ed84a119197bfb8d8205042c6df63d -- name: github.com/satori/go.uuid - version: 879c5887cd475cd7864858769793b2ceb0d44feb -- name: github.com/shirou/gopsutil - version: 70a1b78fe69202d93d6718fc9e3a4d6f81edfd58 - subpackages: - - cpu - - host - - internal/common - - mem - - net - - process -- name: github.com/shirou/w32 - version: bb4de0191aa41b5507caa14b0650cdbddcd9280b -- name: github.com/sirupsen/logrus - version: c078b1e43f58d563c74cebe63c85789e76ddb627 -- name: github.com/StackExchange/wmi - version: e542ed97d15e640bdc14b5c12162d59e8fc67324 -- name: go4.org - version: 7ce08ca145dbe0e66a127c447b80ee7914f3e4f9 - subpackages: - - reflectutil -- name: golang.org/x/crypto - version: 453249f01cfeb54c3d549ddb75ff152ca243f9d8 - subpackages: - - ssh/terminal -- name: golang.org/x/net - version: 61557ac0112b576429a0df080e1c2cef5dfbb642 - subpackages: - - context -- name: golang.org/x/sys - version: e24f485414aeafb646f6fca458b0bf869c0880a1 - subpackages: - - unix -- name: gopkg.in/mgo.v2 - version: 3f83fa5005286a7fe593b055f0d7771a7dce4655 - subpackages: - - bson - - dbtest - - internal/json - - internal/sasl - - internal/scram -- name: gopkg.in/tomb.v2 - version: d5d1b5820637886def9eef33e03a27a9f166942c -testImports: [] diff --git a/src/go/mongolib/util/util.go b/src/go/mongolib/util/util.go index 3c724222..a1ba3a1e 100644 --- a/src/go/mongolib/util/util.go +++ b/src/go/mongolib/util/util.go @@ -12,7 +12,7 @@ import ( "gopkg.in/mgo.v2/bson" ) -func GetReplicasetMembers(dialer pmgo.Dialer, di *mgo.DialInfo) ([]proto.Members, error) { +func GetReplicasetMembers(dialer pmgo.Dialer, di *pmgo.DialInfo) ([]proto.Members, error) { hostnames, err := GetHostnames(dialer, di) if err != nil { return nil, err @@ -75,7 +75,7 @@ func GetReplicasetMembers(dialer pmgo.Dialer, di *mgo.DialInfo) ([]proto.Members return members, nil } -func GetHostnames(dialer pmgo.Dialer, di *mgo.DialInfo) ([]string, error) { +func GetHostnames(dialer pmgo.Dialer, di *pmgo.DialInfo) ([]string, error) { hostnames := []string{di.Addrs[0]} di.Direct = true di.Timeout = 2 * time.Second @@ -182,7 +182,7 @@ func buildHostsListFromShardMap(shardsMap proto.ShardsMap) []string { // This function is like GetHostnames but it uses listShards instead of getShardMap // so it won't include config servers in the returned list -func GetShardedHosts(dialer pmgo.Dialer, di *mgo.DialInfo) ([]string, error) { +func GetShardedHosts(dialer pmgo.Dialer, di *pmgo.DialInfo) ([]string, error) { hostnames := []string{di.Addrs[0]} session, err := dialer.DialWithInfo(di) if err != nil { @@ -206,7 +206,7 @@ func GetShardedHosts(dialer pmgo.Dialer, di *mgo.DialInfo) ([]string, error) { return hostnames, nil } -func getTmpDI(di *mgo.DialInfo, hostname string) *mgo.DialInfo { +func getTmpDI(di *pmgo.DialInfo, hostname string) *pmgo.DialInfo { tmpdi := *di tmpdi.Addrs = []string{hostname} tmpdi.Direct = true @@ -215,7 +215,7 @@ func getTmpDI(di *mgo.DialInfo, hostname string) *mgo.DialInfo { return &tmpdi } -func GetServerStatus(dialer pmgo.Dialer, di *mgo.DialInfo, hostname string) (proto.ServerStatus, error) { +func GetServerStatus(dialer pmgo.Dialer, di *pmgo.DialInfo, hostname string) (proto.ServerStatus, error) { ss := proto.ServerStatus{} tmpdi := getTmpDI(di, hostname) diff --git a/src/go/pt-mongodb-query-digest/main.go b/src/go/pt-mongodb-query-digest/main.go index bc987612..b808300d 100644 --- a/src/go/pt-mongodb-query-digest/main.go +++ b/src/go/pt-mongodb-query-digest/main.go @@ -71,6 +71,8 @@ type options struct { OrderBy []string Password string SkipCollections []string + SSLCAFile string + SSLPEMKeyFile string User string Version bool } @@ -153,8 +155,7 @@ func main() { log.Errorf("error processing commad line arguments: %s", err) os.Exit(1) } - if opts.Help { - getopt.Usage() + if opts == nil && err == nil { return } @@ -527,6 +528,8 @@ func getOptions() (*options, error) { gop.StringVarLong(&opts.LogLevel, "log-level", 'l', "Log level: error", "panic, fatal, error, warn, info, debug. Default: error") gop.StringVarLong(&opts.Password, "password", 'p', "", "Password to use for optional MongoDB authentication").SetOptional() gop.StringVarLong(&opts.User, "username", 'u', "Username to use for optional MongoDB authentication") + gop.StringVarLong(&opts.SSLCAFile, "sslCAFile", 0, "SSL CA cert file used for authentication") + gop.StringVarLong(&opts.SSLPEMKeyFile, "sslPEMKeyFile", 0, "SSL client PEM file used for authentication") gop.SetParameters("host[:port]/database") @@ -536,7 +539,8 @@ func getOptions() (*options, error) { gop.Parse(gop.Args()) } if opts.Help { - return opts, nil + gop.PrintUsage(os.Stdout) + return nil, nil } if gop.IsSet("order-by") { @@ -566,25 +570,34 @@ func getOptions() (*options, error) { return opts, nil } -func getDialInfo(opts *options) *mgo.DialInfo { +func getDialInfo(opts *options) *pmgo.DialInfo { di, _ := mgo.ParseURL(opts.Host) di.FailFast = true - if getopt.IsSet("username") { + if di.Username != "" { di.Username = opts.User } - if getopt.IsSet("password") { + if di.Password != "" { di.Password = opts.Password } - if getopt.IsSet("authenticationDatabase") { + if opts.AuthDB != "" { di.Source = opts.AuthDB } - - if getopt.IsSet("database") { + if opts.Database != "" { di.Database = opts.Database } - return di + pmgoDialInfo := pmgo.NewDialInfo(di) + + if opts.SSLCAFile != "" { + pmgoDialInfo.SSLCAFile = opts.SSLCAFile + } + + if opts.SSLPEMKeyFile != "" { + pmgoDialInfo.SSLPEMKeyFile = opts.SSLPEMKeyFile + } + + return pmgoDialInfo } func getQueryField(query map[string]interface{}) (map[string]interface{}, error) { @@ -891,7 +904,7 @@ func sortQueries(queries []stat, orderby []string) []stat { } -func isProfilerEnabled(dialer pmgo.Dialer, di *mgo.DialInfo) (bool, error) { +func isProfilerEnabled(dialer pmgo.Dialer, di *pmgo.DialInfo) (bool, error) { var ps proto.ProfilerStatus replicaMembers, err := util.GetReplicasetMembers(dialer, di) if err != nil { diff --git a/src/go/pt-mongodb-summary/main.go b/src/go/pt-mongodb-summary/main.go index 574512ac..e272b396 100644 --- a/src/go/pt-mongodb-summary/main.go +++ b/src/go/pt-mongodb-summary/main.go @@ -132,11 +132,20 @@ type options struct { NoRunningOps bool RunningOpsSamples int RunningOpsInterval int + SSLCAFile string + SSLPEMKeyFile string } func main() { - opts := parseFlags() + opts, err := parseFlags() + if err != nil { + log.Errorf("cannot get parameters: %s", err.Error()) + os.Exit(2) + } + if opts == nil && err == nil { + return + } if opts.Help { getopt.Usage() @@ -169,22 +178,14 @@ func main() { } } - if getopt.IsSet("password") && opts.Password == "" { - print("Password: ") - pass, err := gopass.GetPasswd() - if err != nil { - fmt.Println(err) - os.Exit(2) - } - opts.Password = string(pass) - } - - di := &mgo.DialInfo{ - Username: opts.User, - Password: opts.Password, - Addrs: []string{opts.Host}, - FailFast: true, - Source: opts.AuthDB, + di := &pmgo.DialInfo{ + Username: opts.User, + Password: opts.Password, + Addrs: []string{opts.Host}, + FailFast: true, + Source: opts.AuthDB, + SSLCAFile: opts.SSLCAFile, + SSLPEMKeyFile: opts.SSLPEMKeyFile, } log.Debugf("Connecting to the db using:\n%+v", di) @@ -211,7 +212,7 @@ func main() { hostInfo, err := GetHostinfo(session) if err != nil { - message := fmt.Sprintf("Cannot connect to %q: %s", di.Addrs[0], err.Error()) + message := fmt.Sprintf("Cannot get host info for %q: %s", di.Addrs[0], err.Error()) log.Errorf(message) os.Exit(2) } @@ -466,19 +467,36 @@ func GetSecuritySettings(session pmgo.SessionManager, ver string) (*security, er } } - s.Users, _ = session.DB("admin").C("system.users").Count() - if err != nil { - return nil, errors.Wrap(err, "cannot get users count") - } + // On some servers, like a mongos with config servers, this fails if session mode is Monotonic + // On some other servers like a secondary in a replica set, this fails if the session mode is Strong. + // Lets try both + newSession := session.Clone() + defer newSession.Close() + newSession.SetMode(mgo.Strong, true) - s.Roles, err = session.DB("admin").C("system.roles").Count() - if err != nil { - return nil, errors.Wrap(err, "cannot get roles count") + if s.Users, s.Roles, err = getUserRolesCount(newSession); err != nil { + newSession.SetMode(mgo.Monotonic, true) + if s.Users, s.Roles, err = getUserRolesCount(newSession); err != nil { + return nil, errors.Wrap(err, "cannot get security settings.") + } } return &s, nil } +func getUserRolesCount(session pmgo.SessionManager) (int, int, error) { + users, err := session.DB("admin").C("system.users").Count() + if err != nil { + return 0, 0, errors.Wrap(err, "cannot get users count") + } + + roles, err := session.DB("admin").C("system.roles").Count() + if err != nil { + return 0, 0, errors.Wrap(err, "cannot get roles count") + } + return users, roles, nil +} + func getNodeType(session pmgo.SessionManager) (string, error) { md := proto.MasterDoc{} err := session.Run("isMaster", &md) @@ -786,8 +804,8 @@ func externalIP() (string, error) { return "", errors.New("are you connected to the network?") } -func parseFlags() options { - opts := options{ +func parseFlags() (*options, error) { + opts := &options{ Host: DEFAULT_HOST, LogLevel: DEFAULT_LOGLEVEL, RunningOpsSamples: DEFAULT_RUNNINGOPSSAMPLES, @@ -795,31 +813,47 @@ func parseFlags() options { AuthDB: DEFAULT_AUTHDB, } - getopt.BoolVarLong(&opts.Help, "help", 'h', "Show help") - getopt.BoolVarLong(&opts.Version, "version", 'v', "", "Show version & exit") - getopt.BoolVarLong(&opts.NoVersionCheck, "no-version-check", 'c', "", "Default: Don't check for updates") + gop := getopt.New() + gop.BoolVarLong(&opts.Help, "help", 'h', "Show help") + gop.BoolVarLong(&opts.Version, "version", 'v', "", "Show version & exit") + gop.BoolVarLong(&opts.NoVersionCheck, "no-version-check", 'c', "", "Default: Don't check for updates") - getopt.StringVarLong(&opts.User, "username", 'u', "", "Username to use for optional MongoDB authentication") - getopt.StringVarLong(&opts.Password, "password", 'p', "", "Password to use for optional MongoDB authentication").SetOptional() - getopt.StringVarLong(&opts.AuthDB, "authenticationDatabase", 'a', "admin", + gop.StringVarLong(&opts.User, "username", 'u', "", "Username to use for optional MongoDB authentication") + gop.StringVarLong(&opts.Password, "password", 'p', "", "Password to use for optional MongoDB authentication").SetOptional() + gop.StringVarLong(&opts.AuthDB, "authenticationDatabase", 'a', "admin", "Databaae to use for optional MongoDB authentication. Default: admin") - getopt.StringVarLong(&opts.LogLevel, "log-level", 'l', "error", "Log level: panic, fatal, error, warn, info, debug. Default: error") + gop.StringVarLong(&opts.LogLevel, "log-level", 'l', "error", "Log level: panic, fatal, error, warn, info, debug. Default: error") - getopt.IntVarLong(&opts.RunningOpsSamples, "running-ops-samples", 's', + gop.IntVarLong(&opts.RunningOpsSamples, "running-ops-samples", 's', fmt.Sprintf("Number of samples to collect for running ops. Default: %d", opts.RunningOpsSamples)) - getopt.IntVarLong(&opts.RunningOpsInterval, "running-ops-interval", 'i', + gop.IntVarLong(&opts.RunningOpsInterval, "running-ops-interval", 'i', fmt.Sprintf("Interval to wait betwwen running ops samples in milliseconds. Default %d milliseconds", opts.RunningOpsInterval)) - getopt.SetParameters("host[:port]") + gop.StringVarLong(&opts.SSLCAFile, "sslCAFile", 0, "SSL CA cert file used for authentication") + gop.StringVarLong(&opts.SSLPEMKeyFile, "sslPEMKeyFile", 0, "SSL client PEM file used for authentication") + + gop.SetParameters("host[:port]") - var gop = getopt.CommandLine gop.Parse(os.Args) if gop.NArgs() > 0 { opts.Host = gop.Arg(0) gop.Parse(gop.Args()) } - return opts + if gop.IsSet("password") && opts.Password == "" { + print("Password: ") + pass, err := gopass.GetPasswd() + if err != nil { + return opts, err + } + opts.Password = string(pass) + } + if opts.Help { + gop.PrintUsage(os.Stdout) + return nil, nil + } + + return opts, nil } func getChunksCount(session pmgo.SessionManager) ([]proto.ChunksByCollection, error) { diff --git a/src/go/pt-mongodb-summary/main_test.go b/src/go/pt-mongodb-summary/main_test.go index cfcdb154..28b04ae3 100644 --- a/src/go/pt-mongodb-summary/main_test.go +++ b/src/go/pt-mongodb-summary/main_test.go @@ -5,6 +5,7 @@ import ( "io/ioutil" "os" "reflect" + "strings" "testing" "time" @@ -12,6 +13,7 @@ import ( "gopkg.in/mgo.v2/dbtest" "github.com/golang/mock/gomock" + "github.com/pborman/getopt" "github.com/percona/percona-toolkit/src/go/lib/tutil" "github.com/percona/percona-toolkit/src/go/mongolib/proto" "github.com/percona/pmgo" @@ -382,3 +384,44 @@ func addToCounters(ss proto.ServerStatus, increment int64) proto.ServerStatus { ss.Opcounters.Update += increment return ss } + +func TestParseArgs(t *testing.T) { + tests := []struct { + args []string + want *options + }{ + { + args: []string{TOOLNAME}, // arg[0] is the command itself + want: &options{ + Host: DEFAULT_HOST, + LogLevel: DEFAULT_LOGLEVEL, + OrderBy: strings.Split(DEFAULT_ORDERBY, ","), + SkipCollections: strings.Split(DEFAULT_SKIPCOLLECTIONS, ","), + AuthDB: DEFAULT_AUTHDB, + }, + }, + { + args: []string{TOOLNAME, "zapp.brannigan.net:27018/samples", "--help"}, + want: &options{ + Host: "zapp.brannigan.net:27018/samples", + LogLevel: DEFAULT_LOGLEVEL, + OrderBy: strings.Split(DEFAULT_ORDERBY, ","), + SkipCollections: strings.Split(DEFAULT_SKIPCOLLECTIONS, ","), + AuthDB: DEFAULT_AUTHDB, + Help: true, + }, + }, + } + for i, test := range tests { + getopt.Reset() + os.Args = test.args + got, err := getOptions() + if err != nil { + t.Errorf("error parsing command line arguments: %s", err.Error()) + } + if !reflect.DeepEqual(got, test.want) { + t.Errorf("invalid command line options test %d\ngot %+v\nwant %+v\n", i, got, test.want) + } + } + +} diff --git a/src/go/pt-mongodb-summary/oplog/oplog.go b/src/go/pt-mongodb-summary/oplog/oplog.go index 4130b2c8..943883f1 100644 --- a/src/go/pt-mongodb-summary/oplog/oplog.go +++ b/src/go/pt-mongodb-summary/oplog/oplog.go @@ -6,12 +6,12 @@ import ( "time" "github.com/percona/percona-toolkit/src/go/mongolib/proto" + "github.com/percona/pmgo" "github.com/pkg/errors" - "gopkg.in/mgo.v2" "gopkg.in/mgo.v2/bson" ) -func GetOplogInfo(hostnames []string, di *mgo.DialInfo) ([]proto.OplogInfo, error) { +func GetOplogInfo(hostnames []string, di *pmgo.DialInfo) ([]proto.OplogInfo, error) { results := proto.OpLogs{} @@ -20,7 +20,8 @@ func GetOplogInfo(hostnames []string, di *mgo.DialInfo) ([]proto.OplogInfo, erro Hostname: hostname, } di.Addrs = []string{hostname} - session, err := mgo.DialWithInfo(di) + dialer := pmgo.NewDialer() + session, err := dialer.DialWithInfo(di) if err != nil { continue } @@ -91,7 +92,7 @@ func GetOplogInfo(hostnames []string, di *mgo.DialInfo) ([]proto.OplogInfo, erro } -func getOplogCollection(session *mgo.Session) (string, error) { +func getOplogCollection(session pmgo.SessionManager) (string, error) { oplog := "oplog.rs" db := session.DB("local") @@ -110,7 +111,7 @@ func getOplogCollection(session *mgo.Session) (string, error) { return oplog, nil } -func getOplogEntry(session *mgo.Session, oplogCol string) (*proto.OplogEntry, error) { +func getOplogEntry(session pmgo.SessionManager, oplogCol string) (*proto.OplogEntry, error) { olEntry := &proto.OplogEntry{} err := session.DB("local").C("system.namespaces").Find(bson.M{"name": "local." + oplogCol}).One(&olEntry)