// Open connects to the server described by the given // info, waits for it to be initialized, and returns a new State // representing the environment connected to. // // A policy may be provided, which will be used to validate and // modify behaviour of certain operations in state. A nil policy // may be provided. // // Open returns unauthorizedError if access is unauthorized. func Open(info *Info, opts mongo.DialOpts, policy Policy) (*State, error) { logger.Infof("opening state, mongo addresses: %q; entity %q", info.Addrs, info.Tag) di, err := mongo.DialInfo(info.Info, opts) if err != nil { return nil, err } logger.Debugf("dialing mongo") session, err := mgo.DialWithInfo(di) if err != nil { return nil, err } logger.Debugf("connection established") _, err = replicaset.CurrentConfig(session) safe := &mgo.Safe{J: true} if err == nil { // set mongo to write-majority (writes only returned after replicated // to a majority of replica-set members) safe.WMode = "majority" } session.SetSafe(safe) st, err := newState(session, info, policy) if err != nil { session.Close() return nil, err } session.SetSocketTimeout(mongo.SocketTimeout) return st, nil }
// newDialInfo returns mgo.DialInfo with the given address using the minimal // possible setup. func newDialInfo(privateAddr string, conf agent.Config) (*mgo.DialInfo, error) { dialOpts := mongo.DialOpts{Direct: true} ssi, ok := conf.StateServingInfo() if !ok { return nil, errors.Errorf("cannot get state serving info to dial") } info := mongo.Info{ Addrs: []string{net.JoinHostPort(privateAddr, strconv.Itoa(ssi.StatePort))}, CACert: conf.CACert(), } dialInfo, err := mongo.DialInfo(info, dialOpts) if err != nil { return nil, errors.Annotate(err, "cannot produce a dial info") } oldPassword := conf.OldPassword() if oldPassword != "" { dialInfo.Username = "******" dialInfo.Password = conf.OldPassword() } else { dialInfo.Username, dialInfo.Password, err = tagUserCredentials(conf) if err != nil { return nil, errors.Trace(err) } } return dialInfo, nil }
func (c *BootstrapCommand) startMongo(addrs []network.Address, agentConfig agent.Config) error { logger.Debugf("starting mongo") info, ok := agentConfig.MongoInfo() if !ok { return fmt.Errorf("no state info available") } // When bootstrapping, we need to allow enough time for mongo // to start as there's no retry loop in place. // 5 minutes should suffice. mongoDialOpts := mongo.DialOpts{Timeout: 5 * time.Minute} dialInfo, err := mongo.DialInfo(info.Info, mongoDialOpts) if err != nil { return err } servingInfo, ok := agentConfig.StateServingInfo() if !ok { return fmt.Errorf("agent config has no state serving info") } // Use localhost to dial the mongo server, because it's running in // auth mode and will refuse to perform any operations unless // we dial that address. dialInfo.Addrs = []string{ net.JoinHostPort("127.0.0.1", fmt.Sprint(servingInfo.StatePort)), } logger.Debugf("calling ensureMongoServer") ensureServerParams, err := cmdutil.NewEnsureServerParams(agentConfig) if err != nil { return err } err = cmdutil.EnsureMongoServer(ensureServerParams) if err != nil { return err } peerAddr := mongo.SelectPeerAddress(addrs) if peerAddr == "" { return fmt.Errorf("no appropriate peer address found in %q", addrs) } peerHostPort := net.JoinHostPort(peerAddr, fmt.Sprint(servingInfo.StatePort)) if err := initiateMongoServer(peergrouper.InitiateMongoParams{ DialInfo: dialInfo, MemberHostPort: peerHostPort, }); err != nil { return err } logger.Infof("started mongo") return nil }
// newDialInfo returns mgo.DialInfo with the given address using the minimal // possible setup. func newDialInfo(privateAddr string, conf agent.Config) (*mgo.DialInfo, error) { dialOpts := mongo.DialOpts{Direct: true} ssi, ok := conf.StateServingInfo() if !ok { return nil, errors.Errorf("cannot get state serving info to dial") } info := mongo.Info{ Addrs: []string{fmt.Sprintf("%s:%d", privateAddr, ssi.StatePort)}, CACert: conf.CACert(), } dialInfo, err := mongo.DialInfo(info, dialOpts) if err != nil { return nil, errors.Annotate(err, "cannot produce a dial info") } dialInfo.Username = "******" dialInfo.Password = conf.OldPassword() return dialInfo, nil }
func (a *MachineAgent) ensureMongoAdminUser(agentConfig agent.Config) (added bool, err error) { stateInfo, ok1 := agentConfig.MongoInfo() servingInfo, ok2 := agentConfig.StateServingInfo() if !ok1 || !ok2 { return false, fmt.Errorf("no state serving info configuration") } dialInfo, err := mongo.DialInfo(stateInfo.Info, mongo.DefaultDialOpts()) if err != nil { return false, err } if len(dialInfo.Addrs) > 1 { logger.Infof("more than one state server; admin user must exist") return false, nil } return ensureMongoAdminUser(mongo.EnsureAdminUserParams{ DialInfo: dialInfo, Namespace: agentConfig.Value(agent.Namespace), DataDir: agentConfig.DataDir(), Port: servingInfo.StatePort, User: stateInfo.Tag.String(), Password: stateInfo.Password, }) }
// newDialInfo returns mgo.DialInfo with the given address using the minimal // possible setup. func newDialInfo(privateAddr string, conf agent.Config) (*mgo.DialInfo, error) { dialOpts := mongo.DialOpts{Direct: true} ssi, ok := conf.StateServingInfo() if !ok { return nil, errors.Errorf("cannot get state serving info to dial") } info := mongo.Info{ Addrs: []string{net.JoinHostPort(privateAddr, strconv.Itoa(ssi.StatePort))}, CACert: conf.CACert(), } dialInfo, err := mongo.DialInfo(info, dialOpts) if err != nil { return nil, errors.Annotate(err, "cannot produce a dial info") } oldPassword := conf.OldPassword() if oldPassword != "" { dialInfo.Username = "******" dialInfo.Password = conf.OldPassword() } else { dialInfo.Username = conf.Tag().String() // TODO(perrito) we might need an accessor for the actual state password // just in case it ever changes from the same as api password. apiInfo, ok := conf.APIInfo() if ok { dialInfo.Password = apiInfo.Password logger.Infof("using API password to access state server.") } else { // There seems to be no way to reach this inconsistence other than making a // backup on a machine where these fields are corrupted and even so I find // no reasonable way to reach this state, yet since APIInfo has it as a // possibility I prefer to handle it, we cannot recover from this since // it would mean that the agent.conf is corrupted. return nil, errors.New("cannot obtain password to access the state server") } } return dialInfo, nil }
// ensureMongoServer ensures that mongo is installed and running, // and ready for opening a state connection. func (a *MachineAgent) ensureMongoServer(agentConfig agent.Config) (err error) { a.mongoInitMutex.Lock() defer a.mongoInitMutex.Unlock() if a.mongoInitialized { logger.Debugf("mongo is already initialized") return nil } defer func() { if err == nil { a.mongoInitialized = true } }() servingInfo, ok := agentConfig.StateServingInfo() if !ok { return fmt.Errorf("state worker was started with no state serving info") } // When upgrading from a pre-HA-capable environment, // we must add machine-0 to the admin database and // initiate its replicaset. // // TODO(axw) remove this when we no longer need // to upgrade from pre-HA-capable environments. var shouldInitiateMongoServer bool var addrs []network.Address if isPreHAVersion(a.previousAgentVersion) { _, err := a.ensureMongoAdminUser(agentConfig) if err != nil { return err } if servingInfo.SharedSecret == "" { servingInfo.SharedSecret, err = mongo.GenerateSharedSecret() if err != nil { return err } if err = a.ChangeConfig(func(config agent.ConfigSetter) error { config.SetStateServingInfo(servingInfo) return nil }); err != nil { return err } agentConfig = a.CurrentConfig() } // Note: we set Direct=true in the mongo options because it's // possible that we've previously upgraded the mongo server's // configuration to form a replicaset, but failed to initiate it. st, m, err := openState(agentConfig, mongo.DialOpts{Direct: true}) if err != nil { return err } ssi := paramsStateServingInfoToStateStateServingInfo(servingInfo) if err := st.SetStateServingInfo(ssi); err != nil { st.Close() return fmt.Errorf("cannot set state serving info: %v", err) } st.Close() addrs = m.Addresses() shouldInitiateMongoServer = true } // ensureMongoServer installs/upgrades the upstart config as necessary. ensureServerParams, err := newEnsureServerParams(agentConfig) if err != nil { return err } if err := ensureMongoServer(ensureServerParams); err != nil { return err } if !shouldInitiateMongoServer { return nil } // Initiate the replicaset for upgraded environments. // // TODO(axw) remove this when we no longer need // to upgrade from pre-HA-capable environments. stateInfo, ok := agentConfig.MongoInfo() if !ok { return fmt.Errorf("state worker was started with no state serving info") } dialInfo, err := mongo.DialInfo(stateInfo.Info, mongo.DefaultDialOpts()) if err != nil { return err } peerAddr := mongo.SelectPeerAddress(addrs) if peerAddr == "" { return fmt.Errorf("no appropriate peer address found in %q", addrs) } if err := maybeInitiateMongoServer(peergrouper.InitiateMongoParams{ DialInfo: dialInfo, MemberHostPort: net.JoinHostPort(peerAddr, fmt.Sprint(servingInfo.StatePort)), // TODO(dfc) InitiateMongoParams should take a Tag User: stateInfo.Tag.String(), Password: stateInfo.Password, }); err != nil { return err } return nil }
// ensureMongoServer ensures that mongo is installed and running, // and ready for opening a state connection. func (a *MachineAgent) ensureMongoServer(agentConfig agent.Config) error { servingInfo, ok := agentConfig.StateServingInfo() if !ok { return fmt.Errorf("state worker was started with no state serving info") } namespace := agentConfig.Value(agent.Namespace) // When upgrading from a pre-HA-capable environment, // we must add machine-0 to the admin database and // initiate its replicaset. // // TODO(axw) remove this when we no longer need // to upgrade from pre-HA-capable environments. var shouldInitiateMongoServer bool var addrs []network.Address if isPreHAVersion(agentConfig.UpgradedToVersion()) { _, err := a.ensureMongoAdminUser(agentConfig) if err != nil { return err } if servingInfo.SharedSecret == "" { servingInfo.SharedSecret, err = mongo.GenerateSharedSecret() if err != nil { return err } if err = a.ChangeConfig(func(config agent.ConfigSetter) { config.SetStateServingInfo(servingInfo) }); err != nil { return err } agentConfig = a.CurrentConfig() } st, m, err := openState(agentConfig) if err != nil { return err } if err := st.SetStateServingInfo(servingInfo); err != nil { st.Close() return fmt.Errorf("cannot set state serving info: %v", err) } st.Close() addrs = m.Addresses() shouldInitiateMongoServer = true } // ensureMongoServer installs/upgrades the upstart config as necessary. if err := ensureMongoServer( agentConfig.DataDir(), namespace, servingInfo, ); err != nil { return err } if !shouldInitiateMongoServer { return nil } // Initiate the replicaset for upgraded environments. // // TODO(axw) remove this when we no longer need // to upgrade from pre-HA-capable environments. stateInfo, ok := agentConfig.StateInfo() if !ok { return fmt.Errorf("state worker was started with no state serving info") } dialInfo, err := mongo.DialInfo(stateInfo.Info, mongo.DefaultDialOpts()) if err != nil { return err } peerAddr := mongo.SelectPeerAddress(addrs) if peerAddr == "" { return fmt.Errorf("no appropriate peer address found in %q", addrs) } return maybeInitiateMongoServer(peergrouper.InitiateMongoParams{ DialInfo: dialInfo, MemberHostPort: net.JoinHostPort(peerAddr, fmt.Sprint(servingInfo.StatePort)), User: stateInfo.Tag, Password: stateInfo.Password, }) }