github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/cmd/jujud/upgrade_mongo.go (about)

     1  // Copyright 2015 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package main
     5  
     6  import (
     7  	"fmt"
     8  	"io/ioutil"
     9  	"net"
    10  	"os"
    11  	"path/filepath"
    12  	"strconv"
    13  	"strings"
    14  	"time"
    15  
    16  	"github.com/juju/cmd"
    17  	"github.com/juju/errors"
    18  	"github.com/juju/names"
    19  	"github.com/juju/replicaset"
    20  	"github.com/juju/retry"
    21  	"github.com/juju/utils"
    22  	"github.com/juju/utils/clock"
    23  	"github.com/juju/utils/fs"
    24  	"github.com/juju/utils/packaging/manager"
    25  	"gopkg.in/mgo.v2"
    26  	"gopkg.in/mgo.v2/bson"
    27  	"launchpad.net/gnuflag"
    28  
    29  	"github.com/juju/juju/agent"
    30  	"github.com/juju/juju/juju/paths"
    31  	"github.com/juju/juju/mongo"
    32  	"github.com/juju/juju/service"
    33  	"github.com/juju/juju/service/common"
    34  	"github.com/juju/juju/worker/peergrouper"
    35  )
    36  
    37  // KeyUpgradeBackup is the config key used to store information about
    38  // the backup made when upgrading mongo.
    39  const KeyUpgradeBackup = "mongo-upgrade-backup"
    40  
    41  func createTempDir() (string, error) {
    42  	return ioutil.TempDir("", "")
    43  }
    44  
    45  var defaultCallArgs = retry.CallArgs{
    46  	Attempts: 60,
    47  	Delay:    time.Second,
    48  	Clock:    clock.WallClock,
    49  }
    50  
    51  // NewUpgradeMongoCommand returns a new UpgradeMongo command initialized with
    52  // the default helper functions.
    53  func NewUpgradeMongoCommand() *UpgradeMongoCommand {
    54  	return &UpgradeMongoCommand{
    55  		stat:                 os.Stat,
    56  		remove:               os.RemoveAll,
    57  		mkdir:                os.Mkdir,
    58  		runCommand:           utils.RunCommand,
    59  		dialAndLogin:         dialAndLogin,
    60  		satisfyPrerequisites: satisfyPrerequisites,
    61  		createTempDir:        createTempDir,
    62  		discoverService:      service.DiscoverService,
    63  		fsCopy:               fs.Copy,
    64  		osGetenv:             os.Getenv,
    65  
    66  		callArgs:                    defaultCallArgs,
    67  		mongoStart:                  mongo.StartService,
    68  		mongoStop:                   mongo.StopService,
    69  		mongoRestart:                mongo.ReStartService,
    70  		mongoEnsureServiceInstalled: mongo.EnsureServiceInstalled,
    71  		mongoDialInfo:               mongo.DialInfo,
    72  		initiateMongoServer:         peergrouper.InitiateMongoServer,
    73  		replicasetAdd:               replicaAddCall,
    74  		replicasetRemove:            replicaRemoveCall,
    75  	}
    76  }
    77  
    78  type statFunc func(string) (os.FileInfo, error)
    79  type removeFunc func(string) error
    80  type mkdirFunc func(string, os.FileMode) error
    81  type createTempDirFunc func() (string, error)
    82  type discoverService func(string, common.Conf) (service.Service, error)
    83  type fsCopyFunc func(string, string) error
    84  type osGetenv func(string) string
    85  
    86  type utilsRun func(command string, args ...string) (output string, err error)
    87  
    88  type mgoSession interface {
    89  	Close()
    90  }
    91  
    92  type mgoDb interface {
    93  	Run(interface{}, interface{}) error
    94  }
    95  
    96  type dialAndLogger func(*mongo.MongoInfo, retry.CallArgs) (mgoSession, mgoDb, error)
    97  
    98  type requisitesSatisfier func(string) error
    99  
   100  type mongoService func() error
   101  type mongoEnsureService func(string, int, int, bool, mongo.Version, bool) error
   102  type mongoDialInfo func(mongo.Info, mongo.DialOpts) (*mgo.DialInfo, error)
   103  
   104  type initiateMongoServerFunc func(peergrouper.InitiateMongoParams) error
   105  
   106  type replicaAddFunc func(mgoSession, ...replicaset.Member) error
   107  type replicaRemoveFunc func(mgoSession, ...string) error
   108  
   109  // UpgradeMongoCommand represents a jujud upgrade-mongo command.
   110  type UpgradeMongoCommand struct {
   111  	cmd.CommandBase
   112  	machineTag     string
   113  	series         string
   114  	configFilePath string
   115  	agentConfig    agent.ConfigSetterWriter
   116  	tmpDir         string
   117  	backupPath     string
   118  	rollback       bool
   119  	slave          bool
   120  	members        string
   121  
   122  	// utils used by this struct.
   123  	callArgs             retry.CallArgs
   124  	stat                 statFunc
   125  	remove               removeFunc
   126  	mkdir                mkdirFunc
   127  	runCommand           utilsRun
   128  	dialAndLogin         dialAndLogger
   129  	satisfyPrerequisites requisitesSatisfier
   130  	createTempDir        createTempDirFunc
   131  	discoverService      discoverService
   132  	fsCopy               fsCopyFunc
   133  	osGetenv             osGetenv
   134  
   135  	// mongo related utils.
   136  	mongoStart                  mongoService
   137  	mongoStop                   mongoService
   138  	mongoRestart                mongoService
   139  	mongoEnsureServiceInstalled mongoEnsureService
   140  	mongoDialInfo               mongoDialInfo
   141  	initiateMongoServer         initiateMongoServerFunc
   142  	replicasetAdd               replicaAddFunc
   143  	replicasetRemove            replicaRemoveFunc
   144  }
   145  
   146  // Info returns a decription of the command.
   147  func (*UpgradeMongoCommand) Info() *cmd.Info {
   148  	return &cmd.Info{
   149  		Name:    "upgrade-mongo",
   150  		Purpose: "upgrade state server to mongo 3",
   151  	}
   152  }
   153  
   154  // SetFlags adds the flags for this command to the passed gnuflag.FlagSet.
   155  func (u *UpgradeMongoCommand) SetFlags(f *gnuflag.FlagSet) {
   156  	f.StringVar(&u.machineTag, "machinetag", "machine-0", "unique tag identifier for machine to be upgraded")
   157  	f.StringVar(&u.series, "series", "", "series for the machine")
   158  	f.StringVar(&u.configFilePath, "configfile", "", "path to the config file")
   159  	f.StringVar(&u.members, "members", "", "a comma separated list of replicaset member ips")
   160  	f.BoolVar(&u.rollback, "rollback", false, "rollback a previous attempt at upgrading that was cut in the process")
   161  	f.BoolVar(&u.slave, "slave", false, "this is a slave machine in a replicaset")
   162  }
   163  
   164  // Init initializes the command for running.
   165  func (u *UpgradeMongoCommand) Init(args []string) error {
   166  	return nil
   167  }
   168  
   169  // Run migrates an environment to mongo 3.
   170  func (u *UpgradeMongoCommand) Run(ctx *cmd.Context) error {
   171  	return u.run()
   172  }
   173  
   174  func (u *UpgradeMongoCommand) run() (err error) {
   175  	dataDir, err := paths.DataDir(u.series)
   176  	if err != nil {
   177  		return errors.Annotatef(err, "cannot determine data dir for %q", u.series)
   178  	}
   179  	if u.configFilePath == "" {
   180  		machineTag, err := names.ParseMachineTag(u.machineTag)
   181  		if err != nil {
   182  			return errors.Annotatef(err, "%q is not a valid machine tag", u.machineTag)
   183  		}
   184  		u.configFilePath = agent.ConfigPath(dataDir, machineTag)
   185  	}
   186  	u.agentConfig, err = agent.ReadConfig(u.configFilePath)
   187  	if err != nil {
   188  		return errors.Annotatef(err, "cannot read config file in %q", u.configFilePath)
   189  	}
   190  
   191  	current := u.agentConfig.MongoVersion()
   192  
   193  	agentServiceName := u.agentConfig.Value(agent.AgentServiceName)
   194  	if agentServiceName == "" {
   195  		// For backwards compatibility, handle lack of AgentServiceName.
   196  		agentServiceName = u.osGetenv("UPSTART_JOB")
   197  	}
   198  	if agentServiceName == "" {
   199  		return errors.New("cannot determine juju service name")
   200  	}
   201  	svc, err := u.discoverService(agentServiceName, common.Conf{})
   202  	if err != nil {
   203  		return errors.Annotate(err, "cannot determine juju service")
   204  	}
   205  	if err := svc.Stop(); err != nil {
   206  		return errors.Annotate(err, "cannot stop juju to begin migration")
   207  	}
   208  	defer func() {
   209  		svcErr := svc.Start()
   210  		if err != nil {
   211  			err = errors.Annotatef(err, "failed upgrade and juju start after rollbacking upgrade: %v", svcErr)
   212  		} else {
   213  			err = errors.Annotate(svcErr, "could not start juju after upgrade")
   214  		}
   215  	}()
   216  	if !u.slave {
   217  		defer u.replicaAdd()
   218  	}
   219  	if u.rollback {
   220  		origin := u.agentConfig.Value(KeyUpgradeBackup)
   221  		if origin == "" {
   222  			return errors.New("no available backup")
   223  		}
   224  		return u.rollbackCopyBackup(dataDir, origin)
   225  	}
   226  
   227  	u.tmpDir, err = u.createTempDir()
   228  	if err != nil {
   229  		return errors.Annotate(err, "could not create a temporary directory for the migration")
   230  	}
   231  
   232  	logger.Infof("begin migration to mongo 3")
   233  
   234  	if err := u.satisfyPrerequisites(u.series); err != nil {
   235  		return errors.Annotate(err, "cannot satisfy pre-requisites for the migration")
   236  	}
   237  	if current == mongo.Mongo24 || current == mongo.MongoUpgrade {
   238  		if u.slave {
   239  			return u.upgradeSlave(dataDir)
   240  		}
   241  		u.replicaRemove()
   242  		if err := u.maybeUpgrade24to26(dataDir); err != nil {
   243  			defer func() {
   244  				if u.backupPath == "" {
   245  					return
   246  				}
   247  				logger.Infof("will roll back after failed 2.6 upgrade")
   248  				if err := u.rollbackCopyBackup(dataDir, u.backupPath); err != nil {
   249  					logger.Errorf("could not rollback the upgrade: %v", err)
   250  				}
   251  			}()
   252  			return errors.Annotate(err, "cannot upgrade from mongo 2.4 to 2.6")
   253  		}
   254  		current = mongo.Mongo26
   255  	}
   256  	if current == mongo.Mongo26 || current.StorageEngine != mongo.WiredTiger {
   257  		if err := u.maybeUpgrade26to3x(dataDir); err != nil {
   258  			defer func() {
   259  				if u.backupPath == "" {
   260  					return
   261  				}
   262  				logger.Infof("will roll back after failed 3.0 upgrade")
   263  				if err := u.rollbackCopyBackup(dataDir, u.backupPath); err != nil {
   264  					logger.Errorf("could not rollback the upgrade: %v", err)
   265  				}
   266  			}()
   267  			return errors.Annotate(err, "cannot upgrade from mongo 2.6 to 3")
   268  		}
   269  	}
   270  	return nil
   271  }
   272  
   273  func replicaRemoveCall(session mgoSession, addrs ...string) error {
   274  	mSession := session.(*mgo.Session)
   275  	if err := replicaset.Remove(mSession, addrs...); err != nil {
   276  		return errors.Annotate(err, "cannot resume HA")
   277  	}
   278  	return nil
   279  }
   280  
   281  func (u *UpgradeMongoCommand) replicaRemove() error {
   282  	if u.members == "" {
   283  		return nil
   284  	}
   285  	info, ok := u.agentConfig.MongoInfo()
   286  	if !ok {
   287  		return errors.New("cannot get mongo info from agent config to resume HA")
   288  	}
   289  
   290  	session, _, err := u.dialAndLogin(info, u.callArgs)
   291  	if err != nil {
   292  		return errors.Annotate(err, "error dialing mongo to resume HA")
   293  	}
   294  	defer session.Close()
   295  	addrs := strings.Split(u.members, ",")
   296  
   297  	if err := u.replicasetRemove(session, addrs...); err != nil {
   298  		return errors.Annotate(err, "cannot resume HA")
   299  	}
   300  	return nil
   301  }
   302  
   303  func replicaAddCall(session mgoSession, members ...replicaset.Member) error {
   304  	mSession := session.(*mgo.Session)
   305  	if err := replicaset.Add(mSession, members...); err != nil {
   306  		return errors.Annotate(err, "cannot resume HA")
   307  	}
   308  	return nil
   309  }
   310  
   311  func (u *UpgradeMongoCommand) replicaAdd() error {
   312  	if u.members == "" {
   313  		return nil
   314  	}
   315  	info, ok := u.agentConfig.MongoInfo()
   316  	if !ok {
   317  		return errors.New("cannot get mongo info from agent config to resume HA")
   318  	}
   319  
   320  	session, _, err := u.dialAndLogin(info, u.callArgs)
   321  	if err != nil {
   322  		return errors.Annotate(err, "error dialing mongo to resume HA")
   323  	}
   324  	defer session.Close()
   325  	addrs := strings.Split(u.members, ",")
   326  	members := make([]replicaset.Member, len(addrs))
   327  	for i, addr := range addrs {
   328  		members[i] = replicaset.Member{Address: addr}
   329  	}
   330  
   331  	if err := u.replicasetAdd(session, members...); err != nil {
   332  		return errors.Annotate(err, "cannot resume HA")
   333  	}
   334  	return nil
   335  }
   336  
   337  // UpdateService will re-write the service scripts for mongo and restart it.
   338  func (u *UpgradeMongoCommand) UpdateService(auth bool) error {
   339  	var oplogSize int
   340  	if oplogSizeString := u.agentConfig.Value(agent.MongoOplogSize); oplogSizeString != "" {
   341  		var err error
   342  		if oplogSize, err = strconv.Atoi(oplogSizeString); err != nil {
   343  			return errors.Annotatef(err, "invalid oplog size: %q", oplogSizeString)
   344  		}
   345  	}
   346  
   347  	var numaCtlPolicy bool
   348  	if numaCtlString := u.agentConfig.Value(agent.NumaCtlPreference); numaCtlString != "" {
   349  		var err error
   350  		if numaCtlPolicy, err = strconv.ParseBool(numaCtlString); err != nil {
   351  			return errors.Annotatef(err, "invalid numactl preference: %q", numaCtlString)
   352  		}
   353  	}
   354  	ssi, _ := u.agentConfig.StateServingInfo()
   355  
   356  	err := u.mongoEnsureServiceInstalled(u.agentConfig.DataDir(),
   357  		ssi.StatePort,
   358  		oplogSize,
   359  		numaCtlPolicy,
   360  		u.agentConfig.MongoVersion(),
   361  		auth)
   362  	return errors.Annotate(err, "cannot ensure mongodb service script is properly installed")
   363  }
   364  
   365  func (u *UpgradeMongoCommand) maybeUpgrade24to26(dataDir string) error {
   366  	logger.Infof("backing up 2.4 MongoDB")
   367  	var err error
   368  	u.backupPath, err = u.copyBackupMongo("24", dataDir)
   369  	if err != nil {
   370  		return errors.Annotate(err, "could not do pre migration backup")
   371  	}
   372  
   373  	logger.Infof("stopping 2.4 MongoDB")
   374  	if err := u.mongoStop(); err != nil {
   375  		return errors.Annotate(err, "cannot stop mongo to perform 2.6 upgrade step")
   376  	}
   377  
   378  	// Run the not-so-optional --upgrade step on mongodb 2.6.
   379  	if err := u.mongo26UpgradeStep(dataDir); err != nil {
   380  		return errors.Annotate(err, "cannot run mongo 2.6 with --upgrade")
   381  	}
   382  
   383  	u.agentConfig.SetMongoVersion(mongo.Mongo26)
   384  	if err := u.agentConfig.Write(); err != nil {
   385  		return errors.Annotate(err, "could not update mongo version in agent.config")
   386  	}
   387  
   388  	if err := u.UpdateService(true); err != nil {
   389  		return errors.Annotate(err, "cannot update mongo service to use mongo 2.6")
   390  	}
   391  
   392  	logger.Infof("starting 2.6 MongoDB")
   393  	if err := u.mongoStart(); err != nil {
   394  		return errors.Annotate(err, "cannot start mongo 2.6 to upgrade auth schema")
   395  	}
   396  
   397  	info, ok := u.agentConfig.MongoInfo()
   398  	if !ok {
   399  		return errors.New("cannot get mongo info from agent config")
   400  	}
   401  
   402  	session, db, err := u.dialAndLogin(info, u.callArgs)
   403  	if err != nil {
   404  		return errors.Annotate(err, "error dialing mongo to upgrade auth schema")
   405  	}
   406  	defer session.Close()
   407  
   408  	var res bson.M
   409  	res = make(bson.M)
   410  	err = db.Run("authSchemaUpgrade", &res)
   411  	if err != nil {
   412  		return errors.Annotate(err, "cannot upgrade auth schema")
   413  	}
   414  
   415  	if res["ok"].(float64) != 1 {
   416  		return errors.Errorf("cannot upgrade auth schema :%s", res["message"])
   417  	}
   418  	session.Close()
   419  	if err := u.mongoRestart(); err != nil {
   420  		return errors.Annotate(err, "cannot restart mongodb 2.6 service")
   421  	}
   422  	return nil
   423  }
   424  
   425  func (u *UpgradeMongoCommand) maybeUpgrade26to3x(dataDir string) error {
   426  	jujuMongoPath := filepath.Dir(mongo.JujuMongodPath(mongo.Mongo26))
   427  	password := u.agentConfig.OldPassword()
   428  	ssi, _ := u.agentConfig.StateServingInfo()
   429  	port := ssi.StatePort
   430  	current := u.agentConfig.MongoVersion()
   431  
   432  	logger.Infof("backing up 2.6 MongoDB")
   433  	if current == mongo.Mongo26 {
   434  		// TODO(perrito666) dont ignore out if debug-log was used.
   435  		_, err := u.mongoDump(jujuMongoPath, password, "30", port)
   436  		if err != nil {
   437  			return errors.Annotate(err, "could not do pre migration backup")
   438  		}
   439  		logger.Infof("pre 3.x migration dump ready.")
   440  		if err := u.mongoStop(); err != nil {
   441  			return errors.Annotate(err, "cannot stop mongo to update to mongo 3")
   442  		}
   443  		logger.Infof("mongo stopped")
   444  
   445  		// Initially, mongo 3, no wired tiger so we can do a pre-migration dump.
   446  		mongoNoWiredTiger := mongo.Mongo32wt
   447  		mongoNoWiredTiger.StorageEngine = mongo.MMAPV1
   448  		u.agentConfig.SetMongoVersion(mongoNoWiredTiger)
   449  		if err := u.agentConfig.Write(); err != nil {
   450  			return errors.Annotate(err, "could not update mongo version in agent.config")
   451  		}
   452  		logger.Infof("new mongo version set-up to %q", mongoNoWiredTiger.String())
   453  
   454  		if err := u.UpdateService(true); err != nil {
   455  			return errors.Annotate(err, "cannot update service script")
   456  		}
   457  		logger.Infof("service startup scripts up to date")
   458  
   459  		if err := u.mongoStart(); err != nil {
   460  			return errors.Annotate(err, "cannot start mongo 3 to do a pre-tiger migration dump")
   461  		}
   462  		logger.Infof("started mongo")
   463  		current = mongo.Mongo32wt
   464  	}
   465  
   466  	if current.Major > 2 {
   467  		jujuMongoPath = filepath.Dir(mongo.JujuMongodPath(current))
   468  		_, err := u.mongoDump(jujuMongoPath, password, "Tiger", port)
   469  		if err != nil {
   470  			return errors.Annotate(err, "could not do the tiger migration export")
   471  		}
   472  		logger.Infof("dumped to change storage")
   473  
   474  		if err := u.mongoStop(); err != nil {
   475  			return errors.Annotate(err, "cannot stop mongo to update to wired tiger")
   476  		}
   477  		logger.Infof("mongo stopped before storage migration")
   478  		if err := u.removeOldDb(u.agentConfig.DataDir()); err != nil {
   479  			return errors.Annotate(err, "cannot prepare the new db location for wired tiger")
   480  		}
   481  		logger.Infof("old db files removed")
   482  
   483  		// Mongo, with wired tiger
   484  		u.agentConfig.SetMongoVersion(mongo.Mongo32wt)
   485  		if err := u.agentConfig.Write(); err != nil {
   486  			return errors.Annotate(err, "could not update mongo version in agent.config")
   487  		}
   488  		logger.Infof("wired tiger set in agent.config")
   489  
   490  		if err := u.UpdateService(false); err != nil {
   491  			return errors.Annotate(err, "cannot update service script to use wired tiger")
   492  		}
   493  		logger.Infof("service startup script up to date")
   494  
   495  		info, ok := u.agentConfig.MongoInfo()
   496  		if !ok {
   497  			return errors.New("cannot get mongo info from agent config")
   498  		}
   499  
   500  		logger.Infof("will create dialinfo for new mongo")
   501  		//TODO(perrito666) make this into its own function
   502  		dialOpts := mongo.DialOpts{}
   503  		dialInfo, err := u.mongoDialInfo(info.Info, dialOpts)
   504  		if err != nil {
   505  			return errors.Annotate(err, "cannot obtain dial info")
   506  		}
   507  
   508  		if err := u.mongoStart(); err != nil {
   509  			return errors.Annotate(err, "cannot start mongo 3 to restart replicaset")
   510  		}
   511  		logger.Infof("mongo started")
   512  
   513  		// perhaps statehost port?
   514  		// we need localhost, since there is no admin user
   515  		peerHostPort := net.JoinHostPort("localhost", fmt.Sprint(ssi.StatePort))
   516  		err = u.initiateMongoServer(peergrouper.InitiateMongoParams{
   517  			DialInfo:       dialInfo,
   518  			MemberHostPort: peerHostPort,
   519  		})
   520  		if err != nil {
   521  			return errors.Annotate(err, "cannot initiate replicaset")
   522  		}
   523  		logger.Infof("mongo initiated")
   524  
   525  		// blobstorage might fail to restore in certain versions of
   526  		// mongorestore because of a bug in mongorestore
   527  		// that breaks gridfs restoration https://jira.mongodb.org/browse/TOOLS-939
   528  		err = u.mongoRestore(jujuMongoPath, "", "Tiger", nil, port, true, 100)
   529  		if err != nil {
   530  			return errors.Annotate(err, "cannot restore the db.")
   531  		}
   532  		logger.Infof("mongo restored into the new storage")
   533  
   534  		if err := u.UpdateService(true); err != nil {
   535  			return errors.Annotate(err, "cannot update service script post wired tiger migration")
   536  		}
   537  		logger.Infof("service scripts up to date")
   538  
   539  		if err := u.mongoRestart(); err != nil {
   540  			return errors.Annotate(err, "cannot restart mongo service after upgrade")
   541  		}
   542  		logger.Infof("mongo restarted")
   543  	}
   544  	return nil
   545  }
   546  
   547  // dialAndLogin returns a mongo session logged in as a user with administrative
   548  // privileges
   549  func dialAndLogin(mongoInfo *mongo.MongoInfo, callArgs retry.CallArgs) (mgoSession, mgoDb, error) {
   550  	var session *mgo.Session
   551  	opts := mongo.DefaultDialOpts()
   552  	callArgs.Func = func() error {
   553  		// Try to connect, retry a few times until the db comes up.
   554  		var err error
   555  		session, err = mongo.DialWithInfo(mongoInfo.Info, opts)
   556  		if err == nil {
   557  			return nil
   558  		}
   559  		logger.Errorf("cannot open mongo connection: %v", err)
   560  		return err
   561  	}
   562  	if err := retry.Call(callArgs); err != nil {
   563  		return nil, nil, errors.Annotate(err, "error dialing mongo to resume HA")
   564  	}
   565  	admin := session.DB("admin")
   566  	if mongoInfo.Tag != nil {
   567  		if err := admin.Login(mongoInfo.Tag.String(), mongoInfo.Password); err != nil {
   568  			return nil, nil, errors.Annotatef(err, "cannot log in to admin database as %q", mongoInfo.Tag)
   569  		}
   570  	} else if mongoInfo.Password != "" {
   571  		if err := admin.Login(mongo.AdminUser, mongoInfo.Password); err != nil {
   572  			return nil, nil, errors.Annotate(err, "cannot log in to admin database")
   573  		}
   574  	}
   575  	return session, admin, nil
   576  }
   577  
   578  func mongo26UpgradeStepCall(runCommand utilsRun, dataDir string) error {
   579  	updateArgs := []string{"--dbpath", mongo.DbDir(dataDir), "--replSet", "juju", "--upgrade"}
   580  	out, err := runCommand(mongo.JujuMongod24Path, updateArgs...)
   581  	logger.Infof(out)
   582  	if err != nil {
   583  		return errors.Annotate(err, "cannot upgrade mongo 2.4 data")
   584  	}
   585  	return nil
   586  }
   587  
   588  func (u *UpgradeMongoCommand) mongo26UpgradeStep(dataDir string) error {
   589  	return mongo26UpgradeStepCall(u.runCommand, dataDir)
   590  }
   591  
   592  func removeOldDbCall(dataDir string, stat statFunc, remove removeFunc, mkdir mkdirFunc) error {
   593  	dbPath := filepath.Join(dataDir, "db")
   594  
   595  	fi, err := stat(dbPath)
   596  	if err != nil {
   597  		return errors.Annotatef(err, "cannot stat %q", dbPath)
   598  	}
   599  
   600  	if err := remove(dbPath); err != nil {
   601  		return errors.Annotatef(err, "cannot recursively remove %q", dbPath)
   602  	}
   603  	if err := mkdir(dbPath, fi.Mode()); err != nil {
   604  		return errors.Annotatef(err, "cannot re-create %q", dbPath)
   605  	}
   606  	return nil
   607  }
   608  
   609  func (u *UpgradeMongoCommand) removeOldDb(dataDir string) error {
   610  	return removeOldDbCall(dataDir, u.stat, u.remove, u.mkdir)
   611  }
   612  
   613  func satisfyPrerequisites(operatingsystem string) error {
   614  	// CentOS is not currently supported by our mongo package.
   615  	if operatingsystem == "centos7" {
   616  		return errors.New("centos7 is still not suported by this upgrade")
   617  	}
   618  
   619  	pacman, err := manager.NewPackageManager(operatingsystem)
   620  	if err != nil {
   621  		return errors.Annotatef(err, "cannot obtain package manager for %q", operatingsystem)
   622  	}
   623  
   624  	if err := pacman.InstallPrerequisite(); err != nil {
   625  		return err
   626  	}
   627  
   628  	if err := pacman.Install("juju-mongodb2.6"); err != nil {
   629  		return errors.Annotate(err, "cannot install juju-mongodb2.6")
   630  	}
   631  	// JujuMongoPackage represents a version of mongo > 3.1 .
   632  	if err := pacman.Install(mongo.JujuMongoPackage); err != nil {
   633  		return errors.Annotatef(err, "cannot install %v", mongo.JujuMongoPackage)
   634  	}
   635  	return nil
   636  }
   637  
   638  func mongoDumpCall(
   639  	runCommand utilsRun, tmpDir, mongoPath, adminPassword, migrationName string,
   640  	statePort int, callArgs retry.CallArgs,
   641  ) (string, error) {
   642  	mongodump := filepath.Join(mongoPath, "mongodump")
   643  	dumpParams := []string{
   644  		"--ssl",
   645  		"-u", "admin",
   646  		"-p", adminPassword,
   647  		"--port", strconv.Itoa(statePort),
   648  		"--host", "localhost",
   649  		"--out", filepath.Join(tmpDir, fmt.Sprintf("migrateTo%sdump", migrationName)),
   650  	}
   651  	var out string
   652  	callArgs.Func = func() error {
   653  		var err error
   654  		out, err = runCommand(mongodump, dumpParams...)
   655  		if err == nil {
   656  			return nil
   657  		}
   658  		logger.Errorf("cannot dump db %v: %s", err, out)
   659  		return err
   660  	}
   661  	if err := retry.Call(callArgs); err != nil {
   662  		logger.Errorf(out)
   663  		return out, errors.Annotate(err, "cannot dump mongo db")
   664  	}
   665  	return out, nil
   666  }
   667  
   668  func (u *UpgradeMongoCommand) mongoDump(mongoPath, adminPassword, migrationName string, statePort int) (string, error) {
   669  	return mongoDumpCall(u.runCommand, u.tmpDir, mongoPath, adminPassword, migrationName, statePort, u.callArgs)
   670  }
   671  
   672  func mongoRestoreCall(runCommand utilsRun, tmpDir, mongoPath, adminPassword, migrationName string,
   673  	dbs []string, statePort int, invalidSSL bool, batchSize int, callArgs retry.CallArgs) error {
   674  	mongorestore := filepath.Join(mongoPath, "mongorestore")
   675  	restoreParams := []string{
   676  		"--ssl",
   677  		"--port", strconv.Itoa(statePort),
   678  		"--host", "localhost",
   679  	}
   680  
   681  	if invalidSSL {
   682  		restoreParams = append(restoreParams, "--sslAllowInvalidCertificates")
   683  	}
   684  	if batchSize > 0 {
   685  		restoreParams = append(restoreParams, "--batchSize", strconv.Itoa(batchSize))
   686  	}
   687  	if adminPassword != "" {
   688  		restoreParams = append(restoreParams, "-u", "admin", "-p", adminPassword)
   689  	}
   690  	var out string
   691  	if len(dbs) == 0 || dbs == nil {
   692  		restoreParams = append(restoreParams, filepath.Join(tmpDir, fmt.Sprintf("migrateTo%sdump", migrationName)))
   693  		restoreCallArgs := callArgs
   694  		restoreCallArgs.Func = func() error {
   695  			var err error
   696  			out, err = runCommand(mongorestore, restoreParams...)
   697  			if err == nil {
   698  				return nil
   699  			}
   700  			logger.Errorf("cannot restore %v: %s", err, out)
   701  			return err
   702  		}
   703  		if err := retry.Call(restoreCallArgs); err != nil {
   704  			err := errors.Annotatef(err, "cannot restore dbs got: %s", out)
   705  			logger.Errorf("%#v", err)
   706  			return err
   707  		}
   708  	}
   709  	for i := range dbs {
   710  		restoreDbParams := append(restoreParams,
   711  			fmt.Sprintf("--db=%s", dbs[i]),
   712  			filepath.Join(tmpDir, fmt.Sprintf("migrateTo%sdump", migrationName), dbs[i]))
   713  		restoreCallArgs := callArgs
   714  		restoreCallArgs.Func = func() error {
   715  			var err error
   716  			out, err = runCommand(mongorestore, restoreDbParams...)
   717  			if err == nil {
   718  				return nil
   719  			}
   720  			logger.Errorf("cannot restore db %q: %v: got %s", dbs[i], err, out)
   721  			return err
   722  		}
   723  		if err := retry.Call(restoreCallArgs); err != nil {
   724  			return errors.Annotatef(err, "cannot restore db %q got: %s", dbs[i], out)
   725  		}
   726  		logger.Infof("Succesfully restored db %q", dbs[i])
   727  	}
   728  	return nil
   729  }
   730  
   731  func (u *UpgradeMongoCommand) mongoRestore(mongoPath, adminPassword, migrationName string, dbs []string, statePort int, invalidSSL bool, batchSize int) error {
   732  	return mongoRestoreCall(
   733  		u.runCommand, u.tmpDir, mongoPath, adminPassword, migrationName, dbs,
   734  		statePort, invalidSSL, batchSize, u.callArgs,
   735  	)
   736  }
   737  
   738  // copyBackupMongo will make a copy of mongo db by copying the db
   739  // directory, this is safer than a dump.
   740  func (u *UpgradeMongoCommand) copyBackupMongo(targetVersion, dataDir string) (string, error) {
   741  	tmpDir, err := u.createTempDir()
   742  	if err != nil {
   743  		return "", errors.Annotate(err, "cannot create a working directory for backing up mongo")
   744  	}
   745  	if err := u.mongoStop(); err != nil {
   746  		return "", errors.Annotate(err, "cannot stop mongo to backup")
   747  	}
   748  	defer u.mongoStart()
   749  
   750  	dbPath := filepath.Join(dataDir, "db")
   751  	fi, err := u.stat(dbPath)
   752  
   753  	target := filepath.Join(tmpDir, targetVersion)
   754  	if err := u.mkdir(target, fi.Mode()); err != nil {
   755  		return "", errors.Annotate(err, "cannot create target folder for backup")
   756  	}
   757  	targetDb := filepath.Join(target, "db")
   758  
   759  	u.agentConfig.SetValue(KeyUpgradeBackup, targetDb)
   760  	if err := u.agentConfig.Write(); err != nil {
   761  		return "", errors.Annotate(err, "cannot write agent config backup information")
   762  	}
   763  
   764  	if err := u.fsCopy(dbPath, targetDb); err != nil {
   765  		// TODO, delete what was copied
   766  		return "", errors.Annotate(err, "cannot backup mongo database")
   767  	}
   768  	return targetDb, nil
   769  }
   770  
   771  func (u *UpgradeMongoCommand) rollbackCopyBackup(dataDir, origin string) error {
   772  	if err := u.mongoStop(); err != nil {
   773  		return errors.Annotate(err, "cannot stop mongo to rollback")
   774  	}
   775  	defer u.mongoStart()
   776  
   777  	dbDir := filepath.Join(dataDir, "db")
   778  	if err := u.remove(dbDir); err != nil {
   779  		return errors.Annotate(err, "could not remove the existing folder to rollback")
   780  	}
   781  
   782  	if err := fs.Copy(origin, dbDir); err != nil {
   783  		return errors.Annotate(err, "cannot rollback mongo database")
   784  	}
   785  	if err := u.rollbackAgentConfig(); err != nil {
   786  		return errors.Annotate(err, "cannot roo back agent configuration")
   787  	}
   788  	return errors.Annotate(u.UpdateService(true), "cannot rollback service script")
   789  }
   790  
   791  // rollbackAgentconfig rolls back the config value for mongo version
   792  // to its original one and corrects the entry in stop mongo until.
   793  func (u *UpgradeMongoCommand) rollbackAgentConfig() error {
   794  	u.agentConfig.SetMongoVersion(mongo.Mongo24)
   795  	return errors.Annotate(u.agentConfig.Write(), "could not rollback mongo version in agent.config")
   796  }
   797  
   798  func (u *UpgradeMongoCommand) upgradeSlave(dataDir string) error {
   799  	if err := u.satisfyPrerequisites(u.series); err != nil {
   800  		return errors.Annotate(err, "cannot satisfy pre-requisites for the migration")
   801  	}
   802  	if err := u.mongoStop(); err != nil {
   803  		return errors.Annotate(err, "cannot stop mongo to upgrade mongo slave")
   804  	}
   805  	defer u.mongoStart()
   806  	if err := u.removeOldDb(dataDir); err != nil {
   807  		return errors.Annotate(err, "cannot remove existing slave db")
   808  	}
   809  	// Mongo 3, with wired tiger
   810  	u.agentConfig.SetMongoVersion(mongo.Mongo32wt)
   811  	if err := u.agentConfig.Write(); err != nil {
   812  		return errors.Annotate(err, "could not update mongo version in agent.config")
   813  	}
   814  	logger.Infof("wired tiger set in agent.config")
   815  
   816  	if err := u.UpdateService(false); err != nil {
   817  		return errors.Annotate(err, "cannot update service script to use wired tiger")
   818  	}
   819  	logger.Infof("service startup script up to date")
   820  	return nil
   821  }