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 }