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