diff options
author | Ramon Fernandez <ramon@mongodb.com> | 2017-06-27 12:47:20 -0400 |
---|---|---|
committer | Ramon Fernandez <ramon@mongodb.com> | 2017-06-27 12:47:20 -0400 |
commit | a2773b8e23f78522d30546e748b6110519239edb (patch) | |
tree | d33c0f4733ff1da7d77c80a5c039cecc3da343a8 /src/mongo | |
parent | 62f52d2eba138a8729ac31b589d87bde6f1b5cf5 (diff) | |
download | mongo-a2773b8e23f78522d30546e748b6110519239edb.tar.gz |
Import tools: 29b8883c560319b016f8bd4927807fa36f1a682f from branch v3.4
ref: 4d4d96583c..29b8883c56
for: 3.4.6
TOOLS-1567 Support MongoURI for connections
TOOLS-1676 Mongodump fails with OpenSSL 0.9.8
TOOLS-1694 Fix hidden options defaults
TOOLS-1699 3.4.5 Server causing crash in extended_json_metadata.js
Diffstat (limited to 'src/mongo')
31 files changed, 1232 insertions, 122 deletions
diff --git a/src/mongo/gotools/Godeps b/src/mongo/gotools/Godeps index 509c3865580..d3b757426d3 100644 --- a/src/mongo/gotools/Godeps +++ b/src/mongo/gotools/Godeps @@ -6,7 +6,7 @@ github.com/smartystreets/assertions 287b4346dc4e71a038c346375a9d572453bc469b github.com/smartystreets/goconvey bf58a9a1291224109919756b4dcc469c670cc7e4 github.com/jessevdk/go-flags 97448c91aac742cbca3d020b3e769013a420a06f github.com/3rf/mongo-lint 3550fdcf1f43b89aaeabaa4559eaae6dc4407e42 -github.com/spacemonkeygo/openssl 5be686e264d836e7a01ca7fc7c53acdb8edbe768 github.com/10gen/openssl +github.com/spacemonkeygo/openssl 2869e8ca1a6eb35fb727f41611fd52b55cd0f49c github.com/10gen/openssl github.com/spacemonkeygo/spacelog ae95ccc1eb0c8ce2496c43177430efd61930f7e4 github.com/howeyc/gopass 44476384cd4721b68705e72f19e95d1a3a504370 github.com/nsf/termbox-go 0723e7c3d0a317dea811f0fbe4d6edd81908c971 diff --git a/src/mongo/gotools/bsondump/main/bsondump.go b/src/mongo/gotools/bsondump/main/bsondump.go index 2613d3a014a..60fa0de5461 100644 --- a/src/mongo/gotools/bsondump/main/bsondump.go +++ b/src/mongo/gotools/bsondump/main/bsondump.go @@ -17,7 +17,7 @@ func main() { bsonDumpOpts := &bsondump.BSONDumpOptions{} opts.AddOptions(bsonDumpOpts) - args, err := opts.Parse() + args, err := opts.ParseArgs(os.Args[1:]) if err != nil { log.Logvf(log.Always, "error parsing command line options: %v", err) log.Logvf(log.Always, "try 'bsondump --help' for more information") diff --git a/src/mongo/gotools/common/connstring/connstring.go b/src/mongo/gotools/common/connstring/connstring.go new file mode 100644 index 00000000000..d1da3e87514 --- /dev/null +++ b/src/mongo/gotools/common/connstring/connstring.go @@ -0,0 +1,398 @@ +package connstring + +import ( + "fmt" + "net" + "net/url" + "strconv" + "strings" + "time" +) + +// Parse parses the provided uri and returns a URI object. +func ParseURIConnectionString(s string) (ConnString, error) { + var p parser + err := p.parse(s) + if err != nil { + err = fmt.Errorf("error parsing uri (%s): %s", s, err) + } + return p.ConnString, err +} + +// ConnString represents a connection string to mongodb. +type ConnString struct { + Original string + AppName string + AuthMechanism string + AuthMechanismProperties map[string]string + AuthSource string + Connect ConnectMode + ConnectTimeout time.Duration + Database string + FSync bool + HeartbeatInterval time.Duration + Hosts []string + Journal bool + KerberosService string + KerberosServiceHost string + MaxConnIdleTime time.Duration + MaxConnLifeTime time.Duration + MaxConnsPerHost uint16 + MaxConnsPerHostSet bool + MaxIdleConnsPerHost uint16 + MaxIdleConnsPerHostSet bool + Password string + PasswordSet bool + ReadPreference string + ReadPreferenceTagSets []map[string]string + ReplicaSet string + ServerSelectionTimeout time.Duration + SocketTimeout time.Duration + Username string + UseSSL bool + W string + WTimeout time.Duration + + Options map[string][]string + UnknownOptions map[string][]string +} + +func (u *ConnString) String() string { + return u.Original +} + +// ConnectMode informs the driver on how to connect +// to the server. +type ConnectMode uint8 + +// ConnectMode constants. +const ( + AutoConnect ConnectMode = iota + SingleConnect +) + +type parser struct { + ConnString + + haveWTimeoutMS bool +} + +func (p *parser) parse(original string) error { + p.Original = original + + uri := original + var err error + // scheme + if !strings.HasPrefix(uri, "mongodb://") { + return fmt.Errorf("scheme must be \"mongodb\"") + } + + // user info + uri = uri[10:] + + if idx := strings.Index(uri, "@"); idx != -1 { + userInfo := uri[:idx] + uri = uri[idx+1:] + + username := userInfo + var password string + + if idx := strings.Index(userInfo, ":"); idx != -1 { + username = userInfo[:idx] + password = userInfo[idx+1:] + } + + p.Username, err = url.QueryUnescape(username) + if err != nil { + return fmt.Errorf("invalid username: %s", err) + } + if len(password) > 1 { + if strings.Contains(password, ":") { + return fmt.Errorf("unescaped colon in password") + } + p.Password, err = url.QueryUnescape(password) + if err != nil { + return fmt.Errorf("invalid password: %s", err) + } + p.PasswordSet = true + } + + } + + // hosts + hosts := uri + if idx := strings.IndexAny(uri, "/?@"); idx != -1 { + if uri[idx] == '@' { + return fmt.Errorf("unescaped @ sign in user info") + } + if uri[idx] == '?' { + return fmt.Errorf("must have a / before the query ?") + } + + hosts = uri[:idx] + } + + for _, host := range strings.Split(hosts, ",") { + err = p.addHost(host) + if err != nil { + return fmt.Errorf("invalid host \"%s\": %s", host, err) + } + } + + if len(p.Hosts) == 0 { + return fmt.Errorf("must have at least 1 host") + } + + uri = uri[len(hosts):] + + if len(uri) == 0 { + return nil + } + + if uri[0] != '/' { + return fmt.Errorf("must have a / separator between hosts and path") + } + + uri = uri[1:] + if len(uri) == 0 { + return nil + } + + database := uri + if idx := strings.IndexAny(uri, "?"); idx != -1 { + database = uri[:idx] + } + + p.Database, err = url.QueryUnescape(database) + if err != nil { + return fmt.Errorf("invalid database \"%s\": %s", database, err) + } + + uri = uri[len(database):] + + if len(uri) == 0 { + return nil + } + + if uri[0] != '?' { + return fmt.Errorf("must have a ? separator between path and query") + } + + uri = uri[1:] + if len(uri) == 0 { + return nil + } + + for _, pair := range strings.FieldsFunc(uri, func(r rune) bool { return r == ';' || r == '&' }) { + err = p.addOption(pair) + if err != nil { + return err + } + } + + return nil +} + +func (p *parser) addHost(host string) error { + if host == "" { + return nil + } + host, err := url.QueryUnescape(host) + if err != nil { + return fmt.Errorf("invalid host \"%s\": %s", host, err) + } + + _, port, err := net.SplitHostPort(host) + // this is unfortunate that SplitHostPort actually requires + // a port to exist. + if err != nil { + if addrError, ok := err.(*net.AddrError); !ok || addrError.Err != "missing port in address" { + return err + } + } + + if port != "" { + d, err := strconv.Atoi(port) + if err != nil { + return fmt.Errorf("port must be an integer: %s", err) + } + if d <= 0 || d >= 65536 { + return fmt.Errorf("port must be in the range [1, 65535]") + } + } + p.Hosts = append(p.Hosts, host) + return nil +} + +func (p *parser) addOption(pair string) error { + kv := strings.SplitN(pair, "=", 2) + if len(kv) != 2 || kv[0] == "" { + return fmt.Errorf("invalid option") + } + + key, err := url.QueryUnescape(kv[0]) + if err != nil { + return fmt.Errorf("invalid option key \"%s\": %s", kv[0], err) + } + + value, err := url.QueryUnescape(kv[1]) + if err != nil { + return fmt.Errorf("invalid option value \"%s\": %s", kv[1], err) + } + + lowerKey := strings.ToLower(key) + switch lowerKey { + case "appname": + p.AppName = value + case "authmechanism": + p.AuthMechanism = value + case "authmechanismproperties": + p.AuthMechanismProperties = make(map[string]string) + pairs := strings.Split(value, ",") + for _, pair := range pairs { + kv := strings.SplitN(pair, ":", 2) + if len(kv) != 2 || kv[0] == "" { + return fmt.Errorf("invalid authMechanism property") + } + p.AuthMechanismProperties[kv[0]] = kv[1] + } + case "authsource": + p.AuthSource = value + case "connect": + switch strings.ToLower(value) { + case "auto", "automatic": + p.Connect = AutoConnect + case "direct", "single": + p.Connect = SingleConnect + default: + return fmt.Errorf("invalid 'connect' value: %s", value) + } + case "connecttimeoutms": + n, err := strconv.Atoi(value) + if err != nil || n < 0 { + return fmt.Errorf("invalid value for %s: %s", key, value) + } + p.ConnectTimeout = time.Duration(n) * time.Millisecond + case "heartbeatintervalms", "heartbeatfrequencyms": + n, err := strconv.Atoi(value) + if err != nil || n < 0 { + return fmt.Errorf("invalid value for %s: %s", key, value) + } + p.HeartbeatInterval = time.Duration(n) * time.Millisecond + case "fsync": + f, err := strconv.ParseBool(value) + if err != nil { + return fmt.Errorf("invalid value for %s: %s", key, value) + } + p.FSync = f + case "j": + j, err := strconv.ParseBool(value) + if err != nil { + return fmt.Errorf("invalid value for %s: %s", key, value) + } + p.Journal = j + case "gssapiservicename": + p.KerberosService = value + case "gssapihostname": + p.KerberosServiceHost = value + case "maxconnsperhost": + n, err := strconv.Atoi(value) + if err != nil || n < 0 { + return fmt.Errorf("invalid value for %s: %s", key, value) + } + p.MaxConnsPerHost = uint16(n) + p.MaxConnsPerHostSet = true + case "maxidleconnsperhost": + n, err := strconv.Atoi(value) + if err != nil || n < 0 { + return fmt.Errorf("invalid value for %s: %s", key, value) + } + p.MaxIdleConnsPerHost = uint16(n) + p.MaxIdleConnsPerHostSet = true + case "maxidletimems": + n, err := strconv.Atoi(value) + if err != nil || n < 0 { + return fmt.Errorf("invalid value for %s: %s", key, value) + } + p.MaxConnIdleTime = time.Duration(n) * time.Millisecond + case "maxlifetimems": + n, err := strconv.Atoi(value) + if err != nil || n < 0 { + return fmt.Errorf("invalid value for %s: %s", key, value) + } + p.MaxConnLifeTime = time.Duration(n) * time.Millisecond + case "maxpoolsize": + n, err := strconv.Atoi(value) + if err != nil || n < 0 { + return fmt.Errorf("invalid value for %s: %s", key, value) + } + p.MaxConnsPerHost = uint16(n) + p.MaxConnsPerHostSet = true + p.MaxIdleConnsPerHost = uint16(n) + p.MaxIdleConnsPerHostSet = true + case "readpreference": + p.ReadPreference = value + case "readpreferencetags": + tags := make(map[string]string) + items := strings.Split(value, ",") + for _, item := range items { + parts := strings.Split(item, ":") + if len(parts) != 2 { + return fmt.Errorf("invalid value for %s: %s", key, value) + } + tags[parts[0]] = parts[1] + } + p.ReadPreferenceTagSets = append(p.ReadPreferenceTagSets, tags) + case "replicaset": + p.ReplicaSet = value + case "serverselectiontimeoutms": + n, err := strconv.Atoi(value) + if err != nil || n < 0 { + return fmt.Errorf("invalid value for %s: %s", key, value) + } + p.ServerSelectionTimeout = time.Duration(n) * time.Millisecond + case "sockettimeoutms": + n, err := strconv.Atoi(value) + if err != nil || n < 0 { + return fmt.Errorf("invalid value for %s: %s", key, value) + } + p.SocketTimeout = time.Duration(n) * time.Millisecond + case "ssl": + b, err := strconv.ParseBool(value) + if err != nil { + return fmt.Errorf("invalid value for %s: %s", key, value) + } + p.UseSSL = b + case "w": + p.W = value + case "wtimeoutms": + n, err := strconv.Atoi(value) + if err != nil || n < 0 { + return fmt.Errorf("invalid value for %s: %s", key, value) + } + p.WTimeout = time.Duration(n) * time.Millisecond + p.haveWTimeoutMS = true + case "wtimeout": + if p.haveWTimeoutMS { + // use wtimeoutMS if it exists + break + } + n, err := strconv.Atoi(value) + if err != nil || n < 0 { + return fmt.Errorf("invalid value for %s: %s", key, value) + } + p.WTimeout = time.Duration(n) * time.Millisecond + default: + if p.UnknownOptions == nil { + p.UnknownOptions = make(map[string][]string) + } + p.UnknownOptions[lowerKey] = append(p.UnknownOptions[lowerKey], value) + } + + if p.Options == nil { + p.Options = make(map[string][]string) + } + p.Options[lowerKey] = append(p.Options[lowerKey], value) + + return nil +} diff --git a/src/mongo/gotools/common/db/connector.go b/src/mongo/gotools/common/db/connector.go index c3e50ddfa5d..87b6a830fc9 100644 --- a/src/mongo/gotools/common/db/connector.go +++ b/src/mongo/gotools/common/db/connector.go @@ -27,23 +27,28 @@ type VanillaDBConnector struct { // connection string and then sets up the dial information using the default // dial timeout. func (self *VanillaDBConnector) Configure(opts options.ToolOptions) error { - // create the addresses to be used to connect - connectionAddrs := util.CreateConnectionAddrs(opts.Host, opts.Port) - timeout := time.Duration(opts.Timeout) * time.Second // set up the dial info self.dialInfo = &mgo.DialInfo{ - Addrs: connectionAddrs, - Timeout: timeout, Direct: opts.Direct, ReplicaSetName: opts.ReplicaSetName, Username: opts.Auth.Username, Password: opts.Auth.Password, Source: opts.GetAuthenticationDatabase(), Mechanism: opts.Auth.Mechanism, + Timeout: timeout, } + + // create or fetch the addresses to be used to connect + if opts.URI != nil && opts.URI.ConnectionString != "" { + self.dialInfo.Addrs = opts.URI.GetConnectionAddrs() + } else { + self.dialInfo.Addrs = util.CreateConnectionAddrs(opts.Host, opts.Port) + } + kerberos.AddKerberosOpts(opts, self.dialInfo) + return nil } diff --git a/src/mongo/gotools/common/db/openssl/openssl.go b/src/mongo/gotools/common/db/openssl/openssl.go index 1cc4c2ccd1f..d938cf5d532 100644 --- a/src/mongo/gotools/common/db/openssl/openssl.go +++ b/src/mongo/gotools/common/db/openssl/openssl.go @@ -24,8 +24,6 @@ type SSLDBConnector struct { // connection string, and sets up the correct function to dial the server // based on the ssl options passed in. func (self *SSLDBConnector) Configure(opts options.ToolOptions) error { - // create the addresses to be used to connect - connectionAddrs := util.CreateConnectionAddrs(opts.Host, opts.Port) var err error self.ctx, err = setupCtx(opts) @@ -49,7 +47,6 @@ func (self *SSLDBConnector) Configure(opts options.ToolOptions) error { // set up the dial info self.dialInfo = &mgo.DialInfo{ - Addrs: connectionAddrs, Timeout: timeout, Direct: opts.Direct, ReplicaSetName: opts.ReplicaSetName, @@ -59,6 +56,13 @@ func (self *SSLDBConnector) Configure(opts options.ToolOptions) error { Source: opts.GetAuthenticationDatabase(), Mechanism: opts.Auth.Mechanism, } + + // create or fetch the addresses to be used to connect + if opts.URI != nil && opts.URI.ConnectionString != "" { + self.dialInfo.Addrs = opts.URI.GetConnectionAddrs() + } else { + self.dialInfo.Addrs = util.CreateConnectionAddrs(opts.Host, opts.Port) + } kerberos.AddKerberosOpts(opts, self.dialInfo) return nil diff --git a/src/mongo/gotools/common/db/write_concern.go b/src/mongo/gotools/common/db/write_concern.go index 0a9a16214c8..cf7a8e98d56 100644 --- a/src/mongo/gotools/common/db/write_concern.go +++ b/src/mongo/gotools/common/db/write_concern.go @@ -1,12 +1,15 @@ package db import ( - "fmt" + "github.com/mongodb/mongo-tools/common/connstring" "github.com/mongodb/mongo-tools/common/json" "github.com/mongodb/mongo-tools/common/log" "github.com/mongodb/mongo-tools/common/util" "gopkg.in/mgo.v2" + + "fmt" "strconv" + "time" ) // write concern fields @@ -85,13 +88,54 @@ func constructWCObject(writeConcern string) (sessionSafety *mgo.Safe, err error) return sessionSafety, nil } +// constructSafetyFromConnString takes in a parsed connection string and attempts +// to construct an mgo.Safe object from it. It returns an error if it is unable +// to parse the write concern value. +func constructSafetyFromConnString(cs *connstring.ConnString) (*mgo.Safe, error) { + safe := &mgo.Safe{} + + wValue, err := strconv.Atoi(cs.W) + if err != nil { + safe.WMode = cs.W + } else { + safe.W = wValue + if wValue < 0 { + return nil, fmt.Errorf("invalid '%v' argument: %v", w, wValue) + } + } + + safe.WTimeout = int(cs.WTimeout / time.Second) + safe.FSync = cs.FSync + safe.J = cs.Journal + + if safe.WMode == "" && safe.W == 0 && !safe.J { + return nil, nil + } + + return safe, nil +} + // BuildWriteConcern takes a string and a NodeType indicating the type of node the write concern // is intended to be used against, and converts the write concern string argument into an // mgo.Safe object that's usable on sessions for that node type. -func BuildWriteConcern(writeConcern string, nodeType NodeType) (*mgo.Safe, error) { - sessionSafety, err := constructWCObject(writeConcern) - if err != nil { - return nil, err +func BuildWriteConcern(writeConcern string, nodeType NodeType, cs *connstring.ConnString) (*mgo.Safe, error) { + var sessionSafety *mgo.Safe + var err error + + if cs != nil && writeConcern != "" { + return nil, fmt.Errorf("cannot specify writeConcern string and connectionString object") + } + + if cs != nil { + sessionSafety, err = constructSafetyFromConnString(cs) + if err != nil { + return nil, err + } + } else if writeConcern != "" { + sessionSafety, err = constructWCObject(writeConcern) + if err != nil { + return nil, err + } } if sessionSafety == nil { diff --git a/src/mongo/gotools/common/db/write_concern_test.go b/src/mongo/gotools/common/db/write_concern_test.go index 96bd8e0ed89..bdc6ef69018 100644 --- a/src/mongo/gotools/common/db/write_concern_test.go +++ b/src/mongo/gotools/common/db/write_concern_test.go @@ -1,53 +1,87 @@ package db import ( + "github.com/mongodb/mongo-tools/common/connstring" . "github.com/smartystreets/goconvey/convey" + + "fmt" "testing" + "time" ) func TestBuildWriteConcern(t *testing.T) { - Convey("Given a write concern string value, and a boolean indicating if the "+ - "write concern is to be used on a replica set, on calling BuildWriteConcern...", t, func() { - Convey("no error should be returned if the write concern is valid", func() { - writeConcern, err := BuildWriteConcern(`{w:34}`, ReplSet) - So(err, ShouldBeNil) - So(writeConcern.W, ShouldEqual, 34) - writeConcern, err = BuildWriteConcern(`{w:"majority"}`, ReplSet) - So(err, ShouldBeNil) - So(writeConcern.WMode, ShouldEqual, "majority") - writeConcern, err = BuildWriteConcern(`majority`, ReplSet) - So(err, ShouldBeNil) - So(writeConcern.WMode, ShouldEqual, "majority") - writeConcern, err = BuildWriteConcern(`tagset`, ReplSet) - So(err, ShouldBeNil) - So(writeConcern.WMode, ShouldEqual, "tagset") + Convey("When building write concern object", t, func() { + Convey("and given a write concern string value, and a boolean indicating if the "+ + "write concern is to be used on a replica set, on calling BuildWriteConcern...", func() { + Convey("no error should be returned if the write concern is valid", func() { + writeConcern, err := BuildWriteConcern(`{w:34}`, ReplSet, nil) + So(err, ShouldBeNil) + So(writeConcern.W, ShouldEqual, 34) + writeConcern, err = BuildWriteConcern(`{w:"majority"}`, ReplSet, nil) + So(err, ShouldBeNil) + So(writeConcern.WMode, ShouldEqual, "majority") + writeConcern, err = BuildWriteConcern(`majority`, ReplSet, nil) + So(err, ShouldBeNil) + So(writeConcern.WMode, ShouldEqual, "majority") + writeConcern, err = BuildWriteConcern(`tagset`, ReplSet, nil) + So(err, ShouldBeNil) + So(writeConcern.WMode, ShouldEqual, "tagset") + }) + Convey("on replica sets, only a write concern of 1 or 0 should be returned", func() { + writeConcern, err := BuildWriteConcern(`{w:34}`, Standalone, nil) + So(err, ShouldBeNil) + So(writeConcern.W, ShouldEqual, 1) + writeConcern, err = BuildWriteConcern(`{w:"majority"}`, Standalone, nil) + So(err, ShouldBeNil) + So(writeConcern.W, ShouldEqual, 1) + writeConcern, err = BuildWriteConcern(`tagset`, Standalone, nil) + So(err, ShouldBeNil) + So(writeConcern.W, ShouldEqual, 1) + }) + Convey("with a w value of 0, without j set, a nil write concern should be returned", func() { + writeConcern, err := BuildWriteConcern(`{w:0}`, Standalone, nil) + So(err, ShouldBeNil) + So(writeConcern, ShouldBeNil) + }) + Convey("with a negative w value, an error should be returned", func() { + _, err := BuildWriteConcern(`{w:-1}`, ReplSet, nil) + So(err, ShouldNotBeNil) + _, err = BuildWriteConcern(`{w:-2}`, ReplSet, nil) + So(err, ShouldNotBeNil) + }) + Convey("with a w value of 0, with j set, a non-nil write concern should be returned", func() { + writeConcern, err := BuildWriteConcern(`{w:0, j:true}`, Standalone, nil) + So(err, ShouldBeNil) + So(writeConcern.J, ShouldBeTrue) + }) }) - Convey("on replica sets, only a write concern of 1 or 0 should be returned", func() { - writeConcern, err := BuildWriteConcern(`{w:34}`, Standalone) - So(err, ShouldBeNil) - So(writeConcern.W, ShouldEqual, 1) - writeConcern, err = BuildWriteConcern(`{w:"majority"}`, Standalone) - So(err, ShouldBeNil) - So(writeConcern.W, ShouldEqual, 1) - writeConcern, err = BuildWriteConcern(`tagset`, Standalone) - So(err, ShouldBeNil) - So(writeConcern.W, ShouldEqual, 1) - }) - Convey("with a w value of 0, without j set, a nil write concern should be returned", func() { - writeConcern, err := BuildWriteConcern(`{w:0}`, Standalone) - So(err, ShouldBeNil) - So(writeConcern, ShouldBeNil) + Convey("and given a connection string", func() { + Convey("with a w value of 0, without j set, a nil write concern should be returned", func() { + writeConcern, err := BuildWriteConcern(``, Standalone, &connstring.ConnString{W: "0"}) + So(err, ShouldBeNil) + So(writeConcern, ShouldBeNil) + }) + Convey("with a negative w value, an error should be returned", func() { + _, err := BuildWriteConcern(``, ReplSet, &connstring.ConnString{W: "-1"}) + So(err, ShouldNotBeNil) + _, err = BuildWriteConcern(``, ReplSet, &connstring.ConnString{W: "-2"}) + So(err, ShouldNotBeNil) + }) + Convey("on replica sets, only a write concern of 1 or 0 should be returned", func() { + writeConcern, err := BuildWriteConcern(``, Standalone, &connstring.ConnString{W: "34"}) + So(err, ShouldBeNil) + So(writeConcern.W, ShouldEqual, 1) + writeConcern, err = BuildWriteConcern(``, Standalone, &connstring.ConnString{W: "majority"}) + So(err, ShouldBeNil) + So(writeConcern.W, ShouldEqual, 1) + writeConcern, err = BuildWriteConcern(``, Standalone, &connstring.ConnString{W: "tagset"}) + So(err, ShouldBeNil) + So(writeConcern.W, ShouldEqual, 1) + }) }) - Convey("with a negative w value, an error should be returned", func() { - _, err := BuildWriteConcern(`{w:-1}`, ReplSet) + Convey("and given both, should error", func() { + _, err := BuildWriteConcern(`ab`, ReplSet, &connstring.ConnString{W: "-1"}) So(err, ShouldNotBeNil) - _, err = BuildWriteConcern(`{w:-2}`, ReplSet) - So(err, ShouldNotBeNil) - }) - Convey("with a w value of 0, with j set, a non-nil write concern should be returned", func() { - writeConcern, err := BuildWriteConcern(`{w:0, j:true}`, Standalone) - So(err, ShouldBeNil) - So(writeConcern.J, ShouldBeTrue) }) }) } @@ -164,3 +198,57 @@ func TestConstructWCObject(t *testing.T) { }) }) } + +func TestConstructSafetyFromConnString(t *testing.T) { + Convey("Given a parsed &connstring, on calling constructSafetyFromConnString...", t, func() { + + Convey("non string values should be assigned to the 'WMode' "+ + "field in their entirety", func() { + writeConcernString := "majority" + cs := &connstring.ConnString{ + W: writeConcernString, + } + writeConcern, err := constructSafetyFromConnString(cs) + So(err, ShouldBeNil) + So(writeConcern.WMode, ShouldEqual, writeConcernString) + }) + + Convey("Int values should be assigned to the 'w' field ", func() { + cs := &connstring.ConnString{ + W: "4", + } + writeConcern, err := constructSafetyFromConnString(cs) + So(err, ShouldBeNil) + So(writeConcern.W, ShouldEqual, 4) + }) + + Convey("&connstrings with valid j, wtimeout, fsync and w, should be "+ + "assigned accordingly", func() { + expectedW := 3 + expectedWTimeout := 43 + cs := &connstring.ConnString{ + W: "3", + Journal: true, + FSync: false, + WTimeout: time.Second * 43, + } + writeConcern, err := constructSafetyFromConnString(cs) + So(err, ShouldBeNil) + So(writeConcern.W, ShouldEqual, expectedW) + So(writeConcern.J, ShouldBeTrue) + So(writeConcern.FSync, ShouldBeFalse) + So(writeConcern.WTimeout, ShouldEqual, expectedWTimeout) + }) + + Convey("Unacknowledge write concern strings should return a nil object "+ + "if journaling is not required", func() { + cs := &connstring.ConnString{ + W: "0", + } + writeConcern, err := constructSafetyFromConnString(cs) + fmt.Println(writeConcern) + So(err, ShouldBeNil) + So(writeConcern, ShouldBeNil) + }) + }) +} diff --git a/src/mongo/gotools/common/options/options.go b/src/mongo/gotools/common/options/options.go index 5e52c733a7f..1e7cb8c6ca3 100644 --- a/src/mongo/gotools/common/options/options.go +++ b/src/mongo/gotools/common/options/options.go @@ -3,16 +3,18 @@ package options import ( + "fmt" "github.com/jessevdk/go-flags" + "github.com/mongodb/mongo-tools/common/connstring" "github.com/mongodb/mongo-tools/common/failpoint" "github.com/mongodb/mongo-tools/common/log" - - "fmt" + "github.com/mongodb/mongo-tools/common/util" "os" "regexp" "runtime" "strconv" "strings" + "time" ) // Gitspec that the tool was built with. Needs to be set using -ldflags @@ -21,6 +23,23 @@ var ( Gitspec = "built-without-git-spec" ) +var ( + KnownURIOptionsAuth = []string{"authsource", "authmechanism"} + KnownURIOptionsConnection = []string{"connecttimeoutms"} + KnownURIOptionsSSL = []string{"ssl"} + KnownURIOptionsReadPreference = []string{"readpreference"} + KnownURIOptionsKerberos = []string{"gssapiservicename", "gssapihostname"} + KnownURIOptionsWriteConcern = []string{"wtimeout", "w", "j", "fsync"} + KnownURIOptionsReplicaSet = []string{"replicaset"} +) + +var ( + BuiltWithSSL bool + BuiltWithGSSAPI bool +) + +const IncompatibleArgsErrorFormat = "illegal argument combination: cannot specfy %s and --uri" + // Struct encompassing all of the options that are reused across tools: "help", // "version", verbosity settings, ssl settings, etc. type ToolOptions struct { @@ -32,6 +51,7 @@ type ToolOptions struct { VersionStr string // Sub-option types + *URI *General *Verbosity *Connection @@ -52,6 +72,9 @@ type ToolOptions struct { // for caching the parser parser *flags.Parser + + // for checking which options were enabled on this tool + enabledOptions EnabledOptions } type Namespace struct { @@ -65,7 +88,7 @@ type General struct { Help bool `long:"help" description:"print usage"` Version bool `long:"version" description:"print the tool version and exit"` - MaxProcs int `long:"numThreads" default:"0" hidden:"true"` + MaxProcs int `long:"numThreads" hidden:"true"` Failpoints string `long:"failpoints" hidden:"true"` } @@ -84,6 +107,14 @@ func (v Verbosity) IsQuiet() bool { return v.Quiet } +type URI struct { + ConnectionString string `long:"uri" value-name:"mongodb-uri" description:"mongodb uri connection string"` + + knownURIParameters []string + extraOptionsRegistry []ExtraOptions + connString connstring.ConnString +} + // Struct holding connection-related options type Connection struct { Host string `short:"h" long:"host" value-name:"<hostname>" description:"mongodb host to connect to (setname/host1,host2 for replica sets)"` @@ -117,6 +148,16 @@ type Kerberos struct { Service string `long:"gssapiServiceName" value-name:"<service-name>" description:"service name to use when authenticating using GSSAPI/Kerberos ('mongodb' by default)"` ServiceHost string `long:"gssapiHostName" value-name:"<host-name>" description:"hostname to use when authenticating using GSSAPI/Kerberos (remote server's address by default)"` } +type WriteConcern struct { + // Specifies the write concern for each write operation that mongofiles writes to the target database. + // By default, mongofiles waits for a majority of members from the replica set to respond before returning. + WriteConcern string `long:"writeConcern" value-name:"<write-concern>" default:"majority" default-mask:"-" description:"write concern options e.g. --writeConcern majority, --writeConcern '{w: 3, wtimeout: 500, fsync: true, j: true}' (defaults to 'majority')"` + + w int + wtimeout int + fsync bool + journal bool +} type OptionRegistrationFunction func(o *ToolOptions) error @@ -126,6 +167,7 @@ type EnabledOptions struct { Auth bool Connection bool Namespace bool + URI bool } func parseVal(val string) int { @@ -146,12 +188,14 @@ func New(appName, usageStr string, enabled EnabledOptions) *ToolOptions { General: &General{}, Verbosity: &Verbosity{}, Connection: &Connection{}, + URI: &URI{}, SSL: &SSL{}, Auth: &Auth{}, Namespace: &Namespace{}, Kerberos: &Kerberos{}, parser: flags.NewNamedParser( fmt.Sprintf("%v %v", appName, usageStr), flags.None), + enabledOptions: enabled, } // Called when -v or --verbose is parsed @@ -172,6 +216,8 @@ func New(appName, usageStr string, enabled EnabledOptions) *ToolOptions { opts.parser.UnknownOptionHandler = opts.handleUnknownOption + opts.URI.AddKnownURIParameters(KnownURIOptionsReplicaSet) + if _, err := opts.parser.AddGroup("general options", "", opts.General); err != nil { panic(fmt.Errorf("couldn't register general options: %v", err)) } @@ -183,6 +229,7 @@ func New(appName, usageStr string, enabled EnabledOptions) *ToolOptions { EnableFailpoints(opts) if enabled.Connection { + opts.URI.AddKnownURIParameters(KnownURIOptionsConnection) if _, err := opts.parser.AddGroup("connection options", "", opts.Connection); err != nil { panic(fmt.Errorf("couldn't register connection options: %v", err)) } @@ -196,6 +243,7 @@ func New(appName, usageStr string, enabled EnabledOptions) *ToolOptions { } if enabled.Auth { + opts.URI.AddKnownURIParameters(KnownURIOptionsAuth) if _, err := opts.parser.AddGroup("authentication options", "", opts.Auth); err != nil { panic(fmt.Errorf("couldn't register auth options")) } @@ -205,7 +253,11 @@ func New(appName, usageStr string, enabled EnabledOptions) *ToolOptions { panic(fmt.Errorf("couldn't register namespace options")) } } - + if enabled.URI { + if _, err := opts.parser.AddGroup("uri options", "", opts.URI); err != nil { + panic(fmt.Errorf("couldn't register URI options")) + } + } if opts.MaxProcs <= 0 { opts.MaxProcs = runtime.NumCPU() } @@ -266,6 +318,13 @@ type ExtraOptions interface { Name() string } +type URISetter interface { + // SetOptionsFromURI provides a way for tools to fetch any options that were + // set in the URI and set them on the ExtraOptions that they pass to the options + // package. + SetOptionsFromURI(connstring.ConnString) error +} + func (auth *Auth) RequiresExternalDB() bool { return auth.Mechanism == "GSSAPI" || auth.Mechanism == "PLAIN" || auth.Mechanism == "MONGODB-X509" } @@ -277,6 +336,52 @@ func (auth *Auth) ShouldAskForPassword() bool { !(auth.Mechanism == "MONGODB-X509" || auth.Mechanism == "GSSAPI") } +func (uri *URI) GetConnectionAddrs() []string { + return uri.connString.Hosts +} +func (uri *URI) ParsedConnString() *connstring.ConnString { + if uri.ConnectionString == "" { + return nil + } + return &uri.connString +} +func (uri *URI) AddKnownURIParameters(uriFieldNames []string) { + uri.knownURIParameters = append(uri.knownURIParameters, uriFieldNames...) +} + +func (opts *ToolOptions) EnabledToolOptions() EnabledOptions { + return opts.enabledOptions +} + +func (uri *URI) LogUnsupportedOptions() { + allOptionsFromURI := map[string]struct{}{} + + for optName := range uri.connString.Options { + allOptionsFromURI[optName] = struct{}{} + } + + for optName := range uri.connString.UnknownOptions { + allOptionsFromURI[optName] = struct{}{} + } + + for _, optName := range uri.knownURIParameters { + if _, ok := allOptionsFromURI[optName]; ok { + delete(allOptionsFromURI, optName) + } + } + + unsupportedOptions := make([]string, len(allOptionsFromURI)) + optionIndex := 0 + for optionName := range allOptionsFromURI { + unsupportedOptions[optionIndex] = optionName + optionIndex++ + } + + for _, optName := range unsupportedOptions { + log.Logvf(log.Always, "WARNING: ignoring unsupported URI parameter '%v'", optName) + } +} + // Get the authentication database to use. Should be the value of // --authenticationDatabase if it's provided, otherwise, the database that's // specified in the tool's --db arg. @@ -298,13 +403,39 @@ func (o *ToolOptions) AddOptions(opts ExtraOptions) { panic(fmt.Sprintf("error setting command line options for %v: %v", opts.Name(), err)) } + + if o.enabledOptions.URI { + o.URI.extraOptionsRegistry = append(o.URI.extraOptionsRegistry, opts) + } } // Parse the command line args. Returns any extra args not accounted for by // parsing, as well as an error if the parsing returns an error. -func (o *ToolOptions) Parse() ([]string, error) { - args, err := o.parser.Parse() +func (o *ToolOptions) ParseArgs(args []string) ([]string, error) { + args, err := o.parser.ParseArgs(args) + if err != nil { + return []string{}, err + } + + // connect directly, unless a replica set name is explicitly specified + if o.Host != "" { + _, o.ReplicaSetName = util.ParseConnectionString(o.Host) + o.Direct = (o.ReplicaSetName == "") + } + failpoint.ParseFailpoints(o.Failpoints) + + if o.URI != nil && o.URI.ConnectionString != "" { + cs, err := connstring.ParseURIConnectionString(o.URI.ConnectionString) + if err != nil { + return []string{}, err + } + err = o.setOptionsFromURI(cs) + if err != nil { + return []string{}, err + } + } + return args, err } @@ -317,6 +448,76 @@ func (opts *ToolOptions) handleUnknownOption(option string, arg flags.SplitArgum return args, fmt.Errorf(`unknown option "%v"`, option) } +func (opts *ToolOptions) setOptionsFromURI(cs connstring.ConnString) error { + opts.URI.connString = cs + + // if Connection settings are enabled, then verify that other methods + // of specifying weren't used and set timeout + if opts.enabledOptions.Connection { + switch { + case opts.Connection.Host != "": + return fmt.Errorf(IncompatibleArgsErrorFormat, "--host") + case opts.Connection.Port != "": + return fmt.Errorf(IncompatibleArgsErrorFormat, "--port") + case opts.Connection.Timeout != 3: + return fmt.Errorf(IncompatibleArgsErrorFormat, "--dialTimeout") + } + opts.Connection.Timeout = int(cs.ConnectTimeout / time.Millisecond) + } + + if opts.enabledOptions.Auth { + switch { + case opts.Username != "": + return fmt.Errorf(IncompatibleArgsErrorFormat, "--username") + case opts.Password != "" && cs.Password != "": + return fmt.Errorf(IncompatibleArgsErrorFormat, + "illegal argument combination: cannot specify password in uri and --password") + case opts.Source != "": + return fmt.Errorf(IncompatibleArgsErrorFormat, "--authenticationDatabase") + case opts.Auth.Mechanism != "": + return fmt.Errorf(IncompatibleArgsErrorFormat, "--authenticationMechanism") + } + opts.Username = cs.Username + opts.Password = cs.Password + opts.Source = cs.AuthSource + opts.Auth.Mechanism = cs.AuthMechanism + } + if opts.enabledOptions.Namespace { + if opts.Namespace != nil && opts.Namespace.DB != "" { + return fmt.Errorf(IncompatibleArgsErrorFormat, "--db") + } + } + + opts.Namespace.DB = cs.Database + opts.Direct = (cs.Connect == connstring.SingleConnect) + opts.ReplicaSetName = cs.ReplicaSet + + if cs.UseSSL && !BuiltWithSSL { + return fmt.Errorf("cannot use ssl: tool not built with SSL support") + } + opts.SSL.UseSSL = cs.UseSSL + + if cs.KerberosService != "" && !BuiltWithGSSAPI { + return fmt.Errorf("cannot specify gssapiservicename: tool not built with kerberos support") + } + if cs.KerberosServiceHost != "" && !BuiltWithGSSAPI { + return fmt.Errorf("cannot specify gssapihostname: tool not built with kerberos support") + } + + opts.Kerberos.Service = cs.KerberosService + opts.Kerberos.ServiceHost = cs.KerberosServiceHost + + for _, extraOpts := range opts.URI.extraOptionsRegistry { + if uriSetter, ok := extraOpts.(URISetter); ok { + err := uriSetter.SetOptionsFromURI(cs) + if err != nil { + return err + } + } + } + return nil +} + // getIntArg returns 3 args: the parsed int value, a bool set to true if a value // was consumed from the incoming args array during parsing, and an error // value if parsing failed diff --git a/src/mongo/gotools/common/options/options_gssapi.go b/src/mongo/gotools/common/options/options_gssapi.go index f10aa3a2b3b..93d3e9fa930 100644 --- a/src/mongo/gotools/common/options/options_gssapi.go +++ b/src/mongo/gotools/common/options/options_gssapi.go @@ -8,5 +8,10 @@ func init() { func registerGSSAPIOptions(self *ToolOptions) error { _, err := self.parser.AddGroup("kerberos options", "", self.Kerberos) - return err + if err != nil { + return err + } + self.URI.AddKnownURIParameters(KnownURIOptionsKerberos) + BuiltWithGSSAPI = true + return nil } diff --git a/src/mongo/gotools/common/options/options_ssl.go b/src/mongo/gotools/common/options/options_ssl.go index e8b8f27171f..6fcd4da13b3 100644 --- a/src/mongo/gotools/common/options/options_ssl.go +++ b/src/mongo/gotools/common/options/options_ssl.go @@ -14,5 +14,12 @@ func init() { func registerSSLOptions(self *ToolOptions) error { _, err := self.parser.AddGroup("ssl options", "", self.SSL) - return err + if err != nil { + return err + } + if self.enabledOptions.URI { + self.URI.AddKnownURIParameters(KnownURIOptionsSSL) + } + BuiltWithSSL = true + return nil } diff --git a/src/mongo/gotools/common/options/options_test.go b/src/mongo/gotools/common/options/options_test.go index cc18af21506..39579ea36c5 100644 --- a/src/mongo/gotools/common/options/options_test.go +++ b/src/mongo/gotools/common/options/options_test.go @@ -1,13 +1,17 @@ package options import ( + "github.com/mongodb/mongo-tools/common/connstring" . "github.com/smartystreets/goconvey/convey" + + "runtime" "testing" + "time" ) func TestVerbosityFlag(t *testing.T) { Convey("With a new ToolOptions", t, func() { - enabled := EnabledOptions{false, false, false} + enabled := EnabledOptions{false, false, false, false} optPtr := New("", "", enabled) So(optPtr, ShouldNotBeNil) So(optPtr.parser, ShouldNotBeNil) @@ -73,3 +77,303 @@ func TestVerbosityFlag(t *testing.T) { }) }) } + +type uriTester struct { + Name string + CS connstring.ConnString + OptsIn *ToolOptions + OptsExpected *ToolOptions + WithSSL bool + WithGSSAPI bool + ShouldError bool + ShouldAskForPassword bool +} + +func TestParseAndSetOptions(t *testing.T) { + Convey("With a matrix of URIs and expected results", t, func() { + enabledURIOnly := EnabledOptions{false, false, false, true} + testCases := []uriTester{ + { + Name: "not built with ssl", + CS: connstring.ConnString{ + UseSSL: true, + }, + WithSSL: false, + OptsIn: New("", "", enabledURIOnly), + OptsExpected: New("", "", enabledURIOnly), + ShouldError: true, + }, + { + Name: "built with ssl", + CS: connstring.ConnString{ + UseSSL: true, + }, + WithSSL: true, OptsIn: New("", "", enabledURIOnly), + OptsExpected: &ToolOptions{ + General: &General{}, + Verbosity: &Verbosity{}, + Connection: &Connection{}, + URI: &URI{}, + SSL: &SSL{ + UseSSL: true, + }, + Auth: &Auth{}, + Namespace: &Namespace{}, + Kerberos: &Kerberos{}, + enabledOptions: enabledURIOnly, + }, + ShouldError: false, + }, + { + Name: "not built with gssapi", + CS: connstring.ConnString{ + KerberosService: "service", + }, + WithGSSAPI: false, + OptsIn: New("", "", enabledURIOnly), + OptsExpected: New("", "", enabledURIOnly), + ShouldError: true, + }, + { + Name: "built with gssapi", + CS: connstring.ConnString{ + KerberosService: "service", + KerberosServiceHost: "servicehost", + }, + WithGSSAPI: true, + OptsIn: New("", "", enabledURIOnly), + OptsExpected: &ToolOptions{ + General: &General{}, + Verbosity: &Verbosity{}, + Connection: &Connection{}, + URI: &URI{}, + SSL: &SSL{}, + Auth: &Auth{}, + Namespace: &Namespace{}, + Kerberos: &Kerberos{ + Service: "service", + ServiceHost: "servicehost", + }, + enabledOptions: enabledURIOnly, + }, + ShouldError: false, + }, + { + Name: "connection fields set", + CS: connstring.ConnString{ + ConnectTimeout: time.Duration(100) * time.Millisecond, + }, + OptsIn: &ToolOptions{ + General: &General{}, + Verbosity: &Verbosity{}, + Connection: &Connection{ + Timeout: 3, + }, + URI: &URI{}, + SSL: &SSL{}, + Auth: &Auth{}, + Namespace: &Namespace{}, + Kerberos: &Kerberos{}, + enabledOptions: EnabledOptions{Connection: true, URI: true}, + }, + OptsExpected: &ToolOptions{ + General: &General{}, + Verbosity: &Verbosity{}, + Connection: &Connection{ + Timeout: 100, + }, + URI: &URI{}, + SSL: &SSL{}, + Auth: &Auth{}, + Namespace: &Namespace{}, + Kerberos: &Kerberos{}, + enabledOptions: EnabledOptions{Connection: true, URI: true}, + }, + ShouldError: false, + }, + { + Name: "auth fields set", + CS: connstring.ConnString{ + AuthMechanism: "MONGODB-X509", + AuthSource: "authSource", + Username: "user", + Password: "password", + }, + OptsIn: &ToolOptions{ + General: &General{}, + Verbosity: &Verbosity{}, + Connection: &Connection{}, + URI: &URI{}, + SSL: &SSL{}, + Auth: &Auth{}, + Namespace: &Namespace{}, + Kerberos: &Kerberos{}, + enabledOptions: EnabledOptions{Auth: true, URI: true}, + }, + OptsExpected: &ToolOptions{ + General: &General{}, + Verbosity: &Verbosity{}, + Connection: &Connection{}, + URI: &URI{}, + SSL: &SSL{}, + Auth: &Auth{ + Username: "user", + Password: "password", + Source: "authSource", + Mechanism: "MONGODB-X509", + }, + Namespace: &Namespace{}, + Kerberos: &Kerberos{}, + enabledOptions: EnabledOptions{Connection: true, URI: true}, + }, + ShouldError: false, + }, + { + Name: "should ask for password", + CS: connstring.ConnString{ + AuthMechanism: "MONGODB-X509", + AuthSource: "authSource", + Username: "user", + }, + OptsIn: &ToolOptions{ + General: &General{}, + Verbosity: &Verbosity{}, + Connection: &Connection{}, + URI: &URI{}, + SSL: &SSL{}, + Auth: &Auth{}, + Namespace: &Namespace{}, + Kerberos: &Kerberos{}, + enabledOptions: EnabledOptions{Auth: true, URI: true}, + }, + OptsExpected: &ToolOptions{ + General: &General{}, + Verbosity: &Verbosity{}, + Connection: &Connection{}, + URI: &URI{}, + SSL: &SSL{}, + Auth: &Auth{ + Username: "user", + Source: "authSource", + Mechanism: "MONGODB-X509", + }, + Namespace: &Namespace{}, + Kerberos: &Kerberos{}, + enabledOptions: EnabledOptions{Connection: true, URI: true}, + }, + ShouldError: false, + ShouldAskForPassword: true, + }, + { + Name: "single connect sets 'Direct'", + CS: connstring.ConnString{ + Connect: connstring.SingleConnect, + }, + OptsIn: New("", "", enabledURIOnly), + OptsExpected: &ToolOptions{ + General: &General{}, + Verbosity: &Verbosity{}, + Connection: &Connection{}, + URI: &URI{}, + SSL: &SSL{}, + Auth: &Auth{}, + Direct: true, + Namespace: &Namespace{}, + Kerberos: &Kerberos{}, + enabledOptions: EnabledOptions{URI: true}, + }, + ShouldError: false, + }, + { + Name: "ReplSetName is set when CS contains it", + CS: connstring.ConnString{ + ReplicaSet: "replset", + }, + OptsIn: New("", "", enabledURIOnly), + OptsExpected: &ToolOptions{ + General: &General{}, + Verbosity: &Verbosity{}, + Connection: &Connection{}, + URI: &URI{}, + SSL: &SSL{}, + Auth: &Auth{}, + Namespace: &Namespace{}, + Kerberos: &Kerberos{}, + enabledOptions: EnabledOptions{URI: true}, + ReplicaSetName: "replset", + }, + ShouldError: false, + }, + { + Name: "fail when uri and options set", + CS: connstring.ConnString{ + Hosts: []string{"host"}, + }, + OptsIn: &ToolOptions{ + General: &General{}, + Verbosity: &Verbosity{}, + Connection: &Connection{ + Host: "host", + }, + URI: &URI{}, + SSL: &SSL{}, + Auth: &Auth{}, + Namespace: &Namespace{}, + Kerberos: &Kerberos{}, + enabledOptions: EnabledOptions{Connection: true, URI: true}, + }, + OptsExpected: New("", "", EnabledOptions{Connection: true, URI: true}), + ShouldError: true, + }, + } + + Convey("results should match expected", func() { + for _, testCase := range testCases { + t.Log("Test Case:", testCase.Name) + + testCase.OptsIn.URI.ConnectionString = "mongodb://dummy" + testCase.OptsExpected.URI.ConnectionString = "mongodb://dummy" + + BuiltWithSSL = testCase.WithSSL + BuiltWithGSSAPI = testCase.WithGSSAPI + + testCase.OptsIn.URI.connString = testCase.CS + + err := testCase.OptsIn.setOptionsFromURI(testCase.CS) + + if testCase.ShouldError { + So(err, ShouldNotBeNil) + } else { + So(err, ShouldBeNil) + } + + So(testCase.OptsIn.Connection.Timeout, ShouldResemble, testCase.OptsExpected.Connection.Timeout) + So(testCase.OptsIn.Username, ShouldResemble, testCase.OptsExpected.Username) + So(testCase.OptsIn.Password, ShouldResemble, testCase.OptsExpected.Password) + So(testCase.OptsIn.Source, ShouldResemble, testCase.OptsExpected.Source) + So(testCase.OptsIn.Auth.Mechanism, ShouldResemble, testCase.OptsExpected.Auth.Mechanism) + So(testCase.OptsIn.Direct, ShouldResemble, testCase.OptsExpected.Direct) + So(testCase.OptsIn.ReplicaSetName, ShouldResemble, testCase.OptsExpected.ReplicaSetName) + So(testCase.OptsIn.SSL.UseSSL, ShouldResemble, testCase.OptsExpected.SSL.UseSSL) + So(testCase.OptsIn.Kerberos.Service, ShouldResemble, testCase.OptsExpected.Kerberos.Service) + So(testCase.OptsIn.Kerberos.ServiceHost, ShouldResemble, testCase.OptsExpected.Kerberos.ServiceHost) + So(testCase.OptsIn.Auth.ShouldAskForPassword(), ShouldEqual, testCase.OptsIn.ShouldAskForPassword()) + } + }) + }) +} + +// Regression test for TOOLS-1694 to prevent issue from TOOLS-1115 +func TestHiddenOptionsDefaults(t *testing.T) { + Convey("With a ToolOptions parsed", t, func() { + enabled := EnabledOptions{Connection: true} + opts := New("", "", enabled) + _, err := opts.parser.ParseArgs([]string{}) + So(err, ShouldBeNil) + Convey("hidden options should have expected values", func() { + So(opts.MaxProcs, ShouldEqual, runtime.NumCPU()) + So(opts.Timeout, ShouldEqual, 3) + }) + }) + +} diff --git a/src/mongo/gotools/import.data b/src/mongo/gotools/import.data index 55561b11c0d..4c780b2ed0b 100644 --- a/src/mongo/gotools/import.data +++ b/src/mongo/gotools/import.data @@ -1,5 +1,5 @@ { - "commit": "4d4d96583c40a25a4ee7e2d038d75181a300ec3c", + "commit": "29b8883c560319b016f8bd4927807fa36f1a682f", "github": "mongodb/mongo-tools.git", "vendor": "tools", "branch": "v3.4" diff --git a/src/mongo/gotools/mongodump/main/mongodump.go b/src/mongo/gotools/mongodump/main/mongodump.go index 1845c4afdb1..9f6945b9e01 100644 --- a/src/mongo/gotools/mongodump/main/mongodump.go +++ b/src/mongo/gotools/mongodump/main/mongodump.go @@ -20,14 +20,15 @@ const ( func main() { // initialize command-line opts - opts := options.New("mongodump", mongodump.Usage, options.EnabledOptions{true, true, true}) + opts := options.New("mongodump", mongodump.Usage, options.EnabledOptions{Auth: true, Connection: true, Namespace: true, URI: true}) inputOpts := &mongodump.InputOptions{} opts.AddOptions(inputOpts) outputOpts := &mongodump.OutputOptions{} opts.AddOptions(outputOpts) + opts.URI.AddKnownURIParameters(options.KnownURIOptionsReadPreference) - args, err := opts.Parse() + args, err := opts.ParseArgs(os.Args[1:]) if err != nil { log.Logvf(log.Always, "error parsing command line options: %v", err) log.Logvf(log.Always, "try 'mongodump --help' for more information") @@ -53,10 +54,8 @@ func main() { // init logger log.SetVerbosity(opts.Verbosity) - // connect directly, unless a replica set name is explicitly specified - _, setName := util.ParseConnectionString(opts.Host) - opts.Direct = (setName == "") - opts.ReplicaSetName = setName + // verify uri options and log them + opts.URI.LogUnsupportedOptions() // kick off the progress bar manager progressManager := progress.NewBarWriter(log.Writer(0), progressBarWaitTime, progressBarLength, false) diff --git a/src/mongo/gotools/mongodump/options.go b/src/mongo/gotools/mongodump/options.go index 9de83c3358f..80396119fe0 100644 --- a/src/mongo/gotools/mongodump/options.go +++ b/src/mongo/gotools/mongodump/options.go @@ -1,6 +1,9 @@ package mongodump import ( + "github.com/mongodb/mongo-tools/common/connstring" + "github.com/mongodb/mongo-tools/common/options" + "fmt" "io/ioutil" ) @@ -26,6 +29,14 @@ func (*InputOptions) Name() string { return "query" } +func (inputOpts *InputOptions) SetOptionsFromURI(cs connstring.ConnString) error { + if inputOpts.ReadPreference != "" { + return fmt.Errorf(options.IncompatibleArgsErrorFormat, "--readPreference") + } + inputOpts.ReadPreference = cs.ReadPreference + return nil +} + func (inputOptions *InputOptions) HasQuery() bool { return inputOptions.Query != "" || inputOptions.QueryFile != "" } diff --git a/src/mongo/gotools/mongoexport/main/mongoexport.go b/src/mongo/gotools/mongoexport/main/mongoexport.go index d7c7dba3e06..87407a3b246 100644 --- a/src/mongo/gotools/mongoexport/main/mongoexport.go +++ b/src/mongo/gotools/mongoexport/main/mongoexport.go @@ -24,14 +24,14 @@ const ( func main() { // initialize command-line opts opts := options.New("mongoexport", mongoexport.Usage, - options.EnabledOptions{Auth: true, Connection: true, Namespace: true}) + options.EnabledOptions{Auth: true, Connection: true, Namespace: true, URI: true}) outputOpts := &mongoexport.OutputFormatOptions{} opts.AddOptions(outputOpts) inputOpts := &mongoexport.InputOptions{} opts.AddOptions(inputOpts) - args, err := opts.Parse() + args, err := opts.ParseArgs(os.Args[1:]) if err != nil { log.Logvf(log.Always, "error parsing command line options: %v", err) log.Logvf(log.Always, "try 'mongoexport --help' for more information") @@ -56,10 +56,8 @@ func main() { return } - // connect directly, unless a replica set name is explicitly specified - _, setName := util.ParseConnectionString(opts.Host) - opts.Direct = (setName == "") - opts.ReplicaSetName = setName + // verify uri options and log them + opts.URI.LogUnsupportedOptions() provider, err := db.NewSessionProvider(*opts) if err != nil { diff --git a/src/mongo/gotools/mongofiles/main/mongofiles.go b/src/mongo/gotools/mongofiles/main/mongofiles.go index 028dc940302..bff5748c8b3 100644 --- a/src/mongo/gotools/mongofiles/main/mongofiles.go +++ b/src/mongo/gotools/mongofiles/main/mongofiles.go @@ -2,26 +2,28 @@ package main import ( - "fmt" "github.com/mongodb/mongo-tools/common/db" "github.com/mongodb/mongo-tools/common/log" "github.com/mongodb/mongo-tools/common/options" "github.com/mongodb/mongo-tools/common/signals" "github.com/mongodb/mongo-tools/common/util" "github.com/mongodb/mongo-tools/mongofiles" + + "fmt" "os" ) func main() { // initialize command-line opts - opts := options.New("mongofiles", mongofiles.Usage, options.EnabledOptions{Auth: true, Connection: true, Namespace: false}) + opts := options.New("mongofiles", mongofiles.Usage, options.EnabledOptions{Auth: true, Connection: true, Namespace: false, URI: true}) storageOpts := &mongofiles.StorageOptions{} opts.AddOptions(storageOpts) inputOpts := &mongofiles.InputOptions{} opts.AddOptions(inputOpts) + opts.URI.AddKnownURIParameters(options.KnownURIOptionsReadPreference) - args, err := opts.Parse() + args, err := opts.ParseArgs(os.Args[1:]) if err != nil { log.Logvf(log.Always, "error parsing command line options: %v", err) log.Logvf(log.Always, "try 'mongofiles --help' for more information") @@ -40,14 +42,12 @@ func main() { log.SetVerbosity(opts.Verbosity) signals.Handle() + // verify uri options and log them + opts.URI.LogUnsupportedOptions() + // add the specified database to the namespace options struct opts.Namespace.DB = storageOpts.DB - // connect directly, unless a replica set name is explicitly specified - _, setName := util.ParseConnectionString(opts.Host) - opts.Direct = (setName == "") - opts.ReplicaSetName = setName - // create a session provider to connect to the db provider, err := db.NewSessionProvider(*opts) if err != nil { diff --git a/src/mongo/gotools/mongofiles/mongofiles.go b/src/mongo/gotools/mongofiles/mongofiles.go index c2063185735..6f3f50ff43c 100644 --- a/src/mongo/gotools/mongofiles/mongofiles.go +++ b/src/mongo/gotools/mongofiles/mongofiles.go @@ -312,7 +312,9 @@ func (mf *MongoFiles) Run(displayHost bool) (string, error) { log.Logvf(log.DebugLow, "connected to node type: %v", nodeType) - safety, err := db.BuildWriteConcern(mf.StorageOptions.WriteConcern, nodeType) + safety, err := db.BuildWriteConcern(mf.StorageOptions.WriteConcern, nodeType, + mf.ToolOptions.URI.ParsedConnString()) + if err != nil { return "", fmt.Errorf("error parsing write concern: %v", err) } diff --git a/src/mongo/gotools/mongofiles/mongofiles_test.go b/src/mongo/gotools/mongofiles/mongofiles_test.go index 2e0ad431ef8..9bb001defd6 100644 --- a/src/mongo/gotools/mongofiles/mongofiles_test.go +++ b/src/mongo/gotools/mongofiles/mongofiles_test.go @@ -33,6 +33,7 @@ var ( Connection: connection, Auth: &auth, Verbosity: &options.Verbosity{}, + URI: &options.URI{}, } ) diff --git a/src/mongo/gotools/mongoimport/main/mongoimport.go b/src/mongo/gotools/mongoimport/main/mongoimport.go index 837e6d0ac26..6d63df2e99e 100644 --- a/src/mongo/gotools/mongoimport/main/mongoimport.go +++ b/src/mongo/gotools/mongoimport/main/mongoimport.go @@ -16,14 +16,15 @@ import ( func main() { // initialize command-line opts opts := options.New("mongoimport", mongoimport.Usage, - options.EnabledOptions{Auth: true, Connection: true, Namespace: true}) + options.EnabledOptions{Auth: true, Connection: true, Namespace: true, URI: true}) inputOpts := &mongoimport.InputOptions{} opts.AddOptions(inputOpts) ingestOpts := &mongoimport.IngestOptions{} opts.AddOptions(ingestOpts) + opts.URI.AddKnownURIParameters(options.KnownURIOptionsWriteConcern) - args, err := opts.Parse() + args, err := opts.ParseArgs(os.Args[1:]) if err != nil { log.Logvf(log.Always, "error parsing command line options: %v", err) log.Logvf(log.Always, "try 'mongoimport --help' for more information") @@ -43,10 +44,8 @@ func main() { return } - // connect directly, unless a replica set name is explicitly specified - _, setName := util.ParseConnectionString(opts.Host) - opts.Direct = (setName == "") - opts.ReplicaSetName = setName + // verify uri options and log them + opts.URI.LogUnsupportedOptions() // create a session provider to connect to the db sessionProvider, err := db.NewSessionProvider(*opts) diff --git a/src/mongo/gotools/mongoimport/mongoimport.go b/src/mongo/gotools/mongoimport/mongoimport.go index 7fcdf91a990..2b2fde270b6 100644 --- a/src/mongo/gotools/mongoimport/mongoimport.go +++ b/src/mongo/gotools/mongoimport/mongoimport.go @@ -441,7 +441,8 @@ func (imp *MongoImport) ingestDocuments(readDocs chan bson.D) (retErr error) { func (imp *MongoImport) configureSession(session *mgo.Session) error { // sockets to the database will never be forcibly closed session.SetSocketTimeout(0) - sessionSafety, err := db.BuildWriteConcern(imp.IngestOptions.WriteConcern, imp.nodeType) + sessionSafety, err := db.BuildWriteConcern(imp.IngestOptions.WriteConcern, imp.nodeType, + imp.ToolOptions.ParsedConnString()) if err != nil { return fmt.Errorf("write concern error: %v", err) } diff --git a/src/mongo/gotools/mongoimport/mongoimport_test.go b/src/mongo/gotools/mongoimport/mongoimport_test.go index abc3d167975..248a4a45104 100644 --- a/src/mongo/gotools/mongoimport/mongoimport_test.go +++ b/src/mongo/gotools/mongoimport/mongoimport_test.go @@ -6,6 +6,7 @@ import ( "io/ioutil" "os" "reflect" + "runtime" "strings" "testing" @@ -63,12 +64,14 @@ func getBasicToolOptions() *options.ToolOptions { Host: "localhost", Port: db.DefaultTestPort, } + return &options.ToolOptions{ General: general, SSL: &ssl, Namespace: namespace, Connection: connection, Auth: &auth, + URI: &options.URI{}, } } @@ -774,3 +777,23 @@ func TestImportDocuments(t *testing.T) { }) }) } + +// Regression test for TOOLS-1694 to prevent issue from TOOLS-1115 +func TestHiddenOptionsDefaults(t *testing.T) { + Convey("With a new mongoimport with empty options", t, func() { + imp, err := NewMongoImport() + imp.ToolOptions = options.New("", "", options.EnabledOptions{}) + So(err, ShouldBeNil) + Convey("Then parsing should fill args with expected defaults", func() { + _, err := imp.ToolOptions.ParseArgs([]string{}) + So(err, ShouldBeNil) + + // collection cannot be empty in validate + imp.ToolOptions.Collection = "col" + So(imp.ValidateSettings([]string{}), ShouldBeNil) + So(imp.IngestOptions.NumDecodingWorkers, ShouldEqual, runtime.NumCPU()) + So(imp.IngestOptions.BulkBufferSize, ShouldEqual, 1000) + }) + }) + +} diff --git a/src/mongo/gotools/mongooplog/main/mongooplog.go b/src/mongo/gotools/mongooplog/main/mongooplog.go index a9215a64c36..c038db057da 100644 --- a/src/mongo/gotools/mongooplog/main/mongooplog.go +++ b/src/mongo/gotools/mongooplog/main/mongooplog.go @@ -14,7 +14,7 @@ import ( func main() { // initialize command line options opts := options.New("mongooplog", mongooplog.Usage, - options.EnabledOptions{Auth: true, Connection: true, Namespace: false}) + options.EnabledOptions{Auth: true, Connection: true, Namespace: false, URI: true}) // add the mongooplog-specific options sourceOpts := &mongooplog.SourceOptions{} @@ -23,7 +23,7 @@ func main() { log.Logvf(log.Always, "warning: mongooplog is deprecated, and will be removed completely in a future release") // parse the command line options - args, err := opts.Parse() + args, err := opts.ParseArgs(os.Args[1:]) if err != nil { log.Logvf(log.Always, "error parsing command line options: %v", err) log.Logvf(log.Always, "try 'mongooplog --help' for more information") @@ -50,10 +50,8 @@ func main() { log.SetVerbosity(opts.Verbosity) signals.Handle() - // connect directly, unless a replica set name is explicitly specified - _, setName := util.ParseConnectionString(opts.Host) - opts.Direct = (setName == "") - opts.ReplicaSetName = setName + // verify uri options and log them + opts.URI.LogUnsupportedOptions() // validate the mongooplog options if sourceOpts.From == "" { diff --git a/src/mongo/gotools/mongorestore/main/mongorestore.go b/src/mongo/gotools/mongorestore/main/mongorestore.go index fa6b6d84caa..d5832bcc51d 100644 --- a/src/mongo/gotools/mongorestore/main/mongorestore.go +++ b/src/mongo/gotools/mongorestore/main/mongorestore.go @@ -23,15 +23,16 @@ const ( func main() { // initialize command-line opts opts := options.New("mongorestore", mongorestore.Usage, - options.EnabledOptions{Auth: true, Connection: true}) + options.EnabledOptions{Auth: true, Connection: true, URI: true}) nsOpts := &mongorestore.NSOptions{} opts.AddOptions(nsOpts) inputOpts := &mongorestore.InputOptions{} opts.AddOptions(inputOpts) outputOpts := &mongorestore.OutputOptions{} opts.AddOptions(outputOpts) + opts.URI.AddKnownURIParameters(options.KnownURIOptionsWriteConcern) - extraArgs, err := opts.Parse() + extraArgs, err := opts.ParseArgs(os.Args[1:]) if err != nil { log.Logvf(log.Always, "error parsing command line options: %v", err) log.Logvf(log.Always, "try 'mongorestore --help' for more information") @@ -53,6 +54,9 @@ func main() { log.SetVerbosity(opts.Verbosity) + // verify uri options and log them + opts.URI.LogUnsupportedOptions() + targetDir, err := getTargetDirFromArgs(extraArgs, inputOpts.Directory) if err != nil { log.Logvf(log.Always, "%v", err) @@ -61,11 +65,6 @@ func main() { } targetDir = util.ToUniversalPath(targetDir) - // connect directly, unless a replica set name is explicitly specified - _, setName := util.ParseConnectionString(opts.Host) - opts.Direct = (setName == "") - opts.ReplicaSetName = setName - provider, err := db.NewSessionProvider(*opts) if err != nil { log.Logvf(log.Always, "error connecting to host: %v", err) diff --git a/src/mongo/gotools/mongorestore/mongorestore.go b/src/mongo/gotools/mongorestore/mongorestore.go index c79b04e5acf..41ac27560d5 100644 --- a/src/mongo/gotools/mongorestore/mongorestore.go +++ b/src/mongo/gotools/mongorestore/mongorestore.go @@ -136,7 +136,8 @@ func (restore *MongoRestore) ParseAndValidateOptions() error { } log.Logvf(log.DebugLow, "connected to node type: %v", nodeType) - restore.safety, err = db.BuildWriteConcern(restore.OutputOptions.WriteConcern, nodeType) + restore.safety, err = db.BuildWriteConcern(restore.OutputOptions.WriteConcern, nodeType, + restore.ToolOptions.URI.ParsedConnString()) if err != nil { return fmt.Errorf("error parsing write concern: %v", err) } diff --git a/src/mongo/gotools/mongorestore/mongorestore_archive_test.go b/src/mongo/gotools/mongorestore/mongorestore_archive_test.go index cbcefe40696..8398e749854 100644 --- a/src/mongo/gotools/mongorestore/mongorestore_archive_test.go +++ b/src/mongo/gotools/mongorestore/mongorestore_archive_test.go @@ -38,6 +38,7 @@ func TestMongorestoreShortArchive(t *testing.T) { Host: testServer, Port: testPort, }, + URI: &options.URI{}, Auth: &auth, SSL: &ssl, } diff --git a/src/mongo/gotools/mongorestore/mongorestore_test.go b/src/mongo/gotools/mongorestore/mongorestore_test.go index 2221645c14c..3063fb1c541 100644 --- a/src/mongo/gotools/mongorestore/mongorestore_test.go +++ b/src/mongo/gotools/mongorestore/mongorestore_test.go @@ -35,6 +35,7 @@ func TestMongorestore(t *testing.T) { Host: testServer, Port: testPort, }, + URI: &options.URI{}, Auth: &auth, SSL: &ssl, } diff --git a/src/mongo/gotools/mongostat/main/mongostat.go b/src/mongo/gotools/mongostat/main/mongostat.go index 97c79f0ca4f..d6175d03d0f 100644 --- a/src/mongo/gotools/mongostat/main/mongostat.go +++ b/src/mongo/gotools/mongostat/main/mongostat.go @@ -50,7 +50,7 @@ func main() { opts := options.New( "mongostat", mongostat.Usage, - options.EnabledOptions{Connection: true, Auth: true, Namespace: false}) + options.EnabledOptions{Connection: true, Auth: true, Namespace: false, URI: true}) opts.UseReadOnlyHostDescription() // add mongostat-specific options @@ -64,7 +64,7 @@ func main() { interactiveOption.ShortName = 0 } - args, err := opts.Parse() + args, err := opts.ParseArgs(os.Args[1:]) if err != nil { log.Logvf(log.Always, "error parsing command line options: %v", err) log.Logvf(log.Always, "try 'mongostat --help' for more information") @@ -102,7 +102,16 @@ func main() { return } - if opts.Auth.Username != "" && opts.Auth.Source == "" && !opts.Auth.RequiresExternalDB() { + // verify uri options and log them + opts.URI.LogUnsupportedOptions() + + if opts.Auth.Username != "" && opts.GetAuthenticationDatabase() == "" && !opts.Auth.RequiresExternalDB() { + // add logic to have different error if using uri + if opts.URI != nil && opts.URI.ConnectionString != "" { + log.Logvf(log.Always, "authSource is required when authenticating against a non $external database") + os.Exit(util.ExitBadOptions) + } + log.Logvf(log.Always, "--authenticationDatabase is required when authenticating against a non $external database") os.Exit(util.ExitBadOptions) } @@ -212,8 +221,6 @@ func main() { } opts.Direct = true - _, setName := util.ParseConnectionString(opts.Host) - opts.ReplicaSetName = setName stat := &mongostat.MongoStat{ Options: opts, StatOptions: statOpts, diff --git a/src/mongo/gotools/mongotop/main/mongotop.go b/src/mongo/gotools/mongotop/main/mongotop.go index fe06f0e0d64..6a4317dbc8f 100644 --- a/src/mongo/gotools/mongotop/main/mongotop.go +++ b/src/mongo/gotools/mongotop/main/mongotop.go @@ -17,14 +17,14 @@ import ( func main() { // initialize command-line opts opts := options.New("mongotop", mongotop.Usage, - options.EnabledOptions{Auth: true, Connection: true, Namespace: false}) + options.EnabledOptions{Auth: true, Connection: true, Namespace: false, URI: true}) opts.UseReadOnlyHostDescription() // add mongotop-specific options outputOpts := &mongotop.Output{} opts.AddOptions(outputOpts) - args, err := opts.Parse() + args, err := opts.ParseArgs(os.Args[1:]) if err != nil { log.Logvf(log.Always, "error parsing command line options: %v", err) log.Logvf(log.Always, "try 'mongotop --help' for more information") @@ -44,6 +44,9 @@ func main() { log.SetVerbosity(opts.Verbosity) signals.Handle() + // verify uri options and log them + opts.URI.LogUnsupportedOptions() + if len(args) > 1 { log.Logvf(log.Always, "too many positional arguments") log.Logvf(log.Always, "try 'mongotop --help' for more information") @@ -64,15 +67,14 @@ func main() { } if opts.Auth.Username != "" && opts.Auth.Source == "" && !opts.Auth.RequiresExternalDB() { + if opts.URI != nil && opts.URI.ConnectionString != "" { + log.Logvf(log.Always, "authSource is required when authenticating against a non $external database") + os.Exit(util.ExitBadOptions) + } log.Logvf(log.Always, "--authenticationDatabase is required when authenticating against a non $external database") os.Exit(util.ExitBadOptions) } - // connect directly, unless a replica set name is explicitly specified - _, setName := util.ParseConnectionString(opts.Host) - opts.Direct = (setName == "") - opts.ReplicaSetName = setName - // create a session provider to connect to the db sessionProvider, err := db.NewSessionProvider(*opts) if err != nil { @@ -80,7 +82,7 @@ func main() { os.Exit(util.ExitError) } - if setName == "" { + if opts.ReplicaSetName == "" { sessionProvider.SetReadPreference(mgo.PrimaryPreferred) } diff --git a/src/mongo/gotools/vendor/src/github.com/spacemonkeygo/openssl/init.go b/src/mongo/gotools/vendor/src/github.com/spacemonkeygo/openssl/init.go index 01ad6fefe97..314e5415c18 100644 --- a/src/mongo/gotools/vendor/src/github.com/spacemonkeygo/openssl/init.go +++ b/src/mongo/gotools/vendor/src/github.com/spacemonkeygo/openssl/init.go @@ -94,16 +94,17 @@ package openssl #include <openssl/engine.h> extern int Goopenssl_init_locks(); +extern unsigned long Goopenssl_thread_id_callback(); extern void Goopenssl_thread_locking_callback(int, int, const char*, int); static int Goopenssl_init_threadsafety() { - // Set up OPENSSL thread safety callbacks. We only set the locking - // callback because the default id callback implementation is good - // enough for us. + // Set up OPENSSL thread safety callbacks. + // TOOLS-1694 added setting of thread id callback for compatibility with openssl 0.9.8 int rc = Goopenssl_init_locks(); if (rc == 0) { CRYPTO_set_locking_callback(Goopenssl_thread_locking_callback); } + CRYPTO_set_id_callback(Goopenssl_thread_id_callback); return rc; } diff --git a/src/mongo/gotools/vendor/src/github.com/spacemonkeygo/openssl/init_posix.go b/src/mongo/gotools/vendor/src/github.com/spacemonkeygo/openssl/init_posix.go index 03ed0f01bd0..99558298e3a 100644 --- a/src/mongo/gotools/vendor/src/github.com/spacemonkeygo/openssl/init_posix.go +++ b/src/mongo/gotools/vendor/src/github.com/spacemonkeygo/openssl/init_posix.go @@ -52,6 +52,7 @@ int Goopenssl_init_locks() { return rc; } +#if OPENSSL_VERSION_NUMBER < 0x10100000L void Goopenssl_thread_locking_callback(int mode, int n, const char *file, int line) { if (mode & CRYPTO_LOCK) { @@ -60,5 +61,9 @@ void Goopenssl_thread_locking_callback(int mode, int n, const char *file, pthread_mutex_unlock(&goopenssl_locks[n]); } } +unsigned long Goopenssl_thread_id_callback() { + return (unsigned long) pthread_self(); +} +#endif */ import "C" diff --git a/src/mongo/gotools/vendor/src/github.com/spacemonkeygo/openssl/init_windows.go b/src/mongo/gotools/vendor/src/github.com/spacemonkeygo/openssl/init_windows.go index 5eca9fa0eac..ec817926b7a 100644 --- a/src/mongo/gotools/vendor/src/github.com/spacemonkeygo/openssl/init_windows.go +++ b/src/mongo/gotools/vendor/src/github.com/spacemonkeygo/openssl/init_windows.go @@ -56,5 +56,10 @@ void Goopenssl_thread_locking_callback(int mode, int n, const char *file, LeaveCriticalSection(&goopenssl_locks[n]); } } +#if OPENSSL_VERSION_NUMBER < 0x10100000L +unsigned long Goopenssl_thread_id_callback() { + return (unsigned long) GetCurrentThreadId(); +} +#endif */ import "C" |