github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/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/gnuflag" 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/juju/names.v2" 26 "gopkg.in/mgo.v2" 27 "gopkg.in/mgo.v2/bson" 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 if err := pacman.Install(mongo.JujuMongoToolsPackage); err != nil { 636 return errors.Annotatef(err, "cannot install %v", mongo.JujuMongoToolsPackage) 637 } 638 return nil 639 } 640 641 func mongoDumpCall( 642 runCommand utilsRun, tmpDir, mongoPath, adminPassword, migrationName string, 643 statePort int, callArgs retry.CallArgs, 644 ) (string, error) { 645 mongodump := filepath.Join(mongoPath, "mongodump") 646 dumpParams := []string{ 647 "--ssl", 648 "-u", "admin", 649 "-p", adminPassword, 650 "--port", strconv.Itoa(statePort), 651 "--host", "localhost", 652 "--out", filepath.Join(tmpDir, fmt.Sprintf("migrateTo%sdump", migrationName)), 653 } 654 var out string 655 callArgs.Func = func() error { 656 var err error 657 out, err = runCommand(mongodump, dumpParams...) 658 if err == nil { 659 return nil 660 } 661 logger.Errorf("cannot dump db %v: %s", err, out) 662 return err 663 } 664 if err := retry.Call(callArgs); err != nil { 665 logger.Errorf(out) 666 return out, errors.Annotate(err, "cannot dump mongo db") 667 } 668 return out, nil 669 } 670 671 func (u *UpgradeMongoCommand) mongoDump(mongoPath, adminPassword, migrationName string, statePort int) (string, error) { 672 return mongoDumpCall(u.runCommand, u.tmpDir, mongoPath, adminPassword, migrationName, statePort, u.callArgs) 673 } 674 675 func mongoRestoreCall(runCommand utilsRun, tmpDir, mongoPath, adminPassword, migrationName string, 676 dbs []string, statePort int, invalidSSL bool, batchSize int, callArgs retry.CallArgs) error { 677 mongorestore := filepath.Join(mongoPath, "mongorestore") 678 restoreParams := []string{ 679 "--ssl", 680 "--port", strconv.Itoa(statePort), 681 "--host", "localhost", 682 } 683 684 if invalidSSL { 685 restoreParams = append(restoreParams, "--sslAllowInvalidCertificates") 686 } 687 if batchSize > 0 { 688 restoreParams = append(restoreParams, "--batchSize", strconv.Itoa(batchSize)) 689 } 690 if adminPassword != "" { 691 restoreParams = append(restoreParams, "-u", "admin", "-p", adminPassword) 692 } 693 var out string 694 if len(dbs) == 0 || dbs == nil { 695 restoreParams = append(restoreParams, filepath.Join(tmpDir, fmt.Sprintf("migrateTo%sdump", migrationName))) 696 restoreCallArgs := callArgs 697 restoreCallArgs.Func = func() error { 698 var err error 699 out, err = runCommand(mongorestore, restoreParams...) 700 if err == nil { 701 return nil 702 } 703 logger.Errorf("cannot restore %v: %s", err, out) 704 return err 705 } 706 if err := retry.Call(restoreCallArgs); err != nil { 707 err := errors.Annotatef(err, "cannot restore dbs got: %s", out) 708 logger.Errorf("%#v", err) 709 return err 710 } 711 } 712 for i := range dbs { 713 restoreDbParams := append(restoreParams, 714 fmt.Sprintf("--db=%s", dbs[i]), 715 filepath.Join(tmpDir, fmt.Sprintf("migrateTo%sdump", migrationName), dbs[i])) 716 restoreCallArgs := callArgs 717 restoreCallArgs.Func = func() error { 718 var err error 719 out, err = runCommand(mongorestore, restoreDbParams...) 720 if err == nil { 721 return nil 722 } 723 logger.Errorf("cannot restore db %q: %v: got %s", dbs[i], err, out) 724 return err 725 } 726 if err := retry.Call(restoreCallArgs); err != nil { 727 return errors.Annotatef(err, "cannot restore db %q got: %s", dbs[i], out) 728 } 729 logger.Infof("Succesfully restored db %q", dbs[i]) 730 } 731 return nil 732 } 733 734 func (u *UpgradeMongoCommand) mongoRestore(mongoPath, adminPassword, migrationName string, dbs []string, statePort int, invalidSSL bool, batchSize int) error { 735 return mongoRestoreCall( 736 u.runCommand, u.tmpDir, mongoPath, adminPassword, migrationName, dbs, 737 statePort, invalidSSL, batchSize, u.callArgs, 738 ) 739 } 740 741 // copyBackupMongo will make a copy of mongo db by copying the db 742 // directory, this is safer than a dump. 743 func (u *UpgradeMongoCommand) copyBackupMongo(targetVersion, dataDir string) (string, error) { 744 tmpDir, err := u.createTempDir() 745 if err != nil { 746 return "", errors.Annotate(err, "cannot create a working directory for backing up mongo") 747 } 748 if err := u.mongoStop(); err != nil { 749 return "", errors.Annotate(err, "cannot stop mongo to backup") 750 } 751 defer u.mongoStart() 752 753 dbPath := filepath.Join(dataDir, "db") 754 fi, err := u.stat(dbPath) 755 756 target := filepath.Join(tmpDir, targetVersion) 757 if err := u.mkdir(target, fi.Mode()); err != nil { 758 return "", errors.Annotate(err, "cannot create target folder for backup") 759 } 760 targetDb := filepath.Join(target, "db") 761 762 u.agentConfig.SetValue(KeyUpgradeBackup, targetDb) 763 if err := u.agentConfig.Write(); err != nil { 764 return "", errors.Annotate(err, "cannot write agent config backup information") 765 } 766 767 if err := u.fsCopy(dbPath, targetDb); err != nil { 768 // TODO, delete what was copied 769 return "", errors.Annotate(err, "cannot backup mongo database") 770 } 771 return targetDb, nil 772 } 773 774 func (u *UpgradeMongoCommand) rollbackCopyBackup(dataDir, origin string) error { 775 if err := u.mongoStop(); err != nil { 776 return errors.Annotate(err, "cannot stop mongo to rollback") 777 } 778 defer u.mongoStart() 779 780 dbDir := filepath.Join(dataDir, "db") 781 if err := u.remove(dbDir); err != nil { 782 return errors.Annotate(err, "could not remove the existing folder to rollback") 783 } 784 785 if err := fs.Copy(origin, dbDir); err != nil { 786 return errors.Annotate(err, "cannot rollback mongo database") 787 } 788 if err := u.rollbackAgentConfig(); err != nil { 789 return errors.Annotate(err, "cannot roo back agent configuration") 790 } 791 return errors.Annotate(u.UpdateService(true), "cannot rollback service script") 792 } 793 794 // rollbackAgentconfig rolls back the config value for mongo version 795 // to its original one and corrects the entry in stop mongo until. 796 func (u *UpgradeMongoCommand) rollbackAgentConfig() error { 797 u.agentConfig.SetMongoVersion(mongo.Mongo24) 798 return errors.Annotate(u.agentConfig.Write(), "could not rollback mongo version in agent.config") 799 } 800 801 func (u *UpgradeMongoCommand) upgradeSlave(dataDir string) error { 802 if err := u.satisfyPrerequisites(u.series); err != nil { 803 return errors.Annotate(err, "cannot satisfy pre-requisites for the migration") 804 } 805 if err := u.mongoStop(); err != nil { 806 return errors.Annotate(err, "cannot stop mongo to upgrade mongo slave") 807 } 808 defer u.mongoStart() 809 if err := u.removeOldDb(dataDir); err != nil { 810 return errors.Annotate(err, "cannot remove existing slave db") 811 } 812 // Mongo 3, with wired tiger 813 u.agentConfig.SetMongoVersion(mongo.Mongo32wt) 814 if err := u.agentConfig.Write(); err != nil { 815 return errors.Annotate(err, "could not update mongo version in agent.config") 816 } 817 logger.Infof("wired tiger set in agent.config") 818 819 if err := u.UpdateService(false); err != nil { 820 return errors.Annotate(err, "cannot update service script to use wired tiger") 821 } 822 logger.Infof("service startup script up to date") 823 return nil 824 }