github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/state/modelmigration.go (about) 1 // Copyright 2016 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package state 5 6 import ( 7 "fmt" 8 "strconv" 9 "time" 10 11 "github.com/juju/errors" 12 "github.com/juju/names" 13 "gopkg.in/mgo.v2" 14 "gopkg.in/mgo.v2/bson" 15 "gopkg.in/mgo.v2/txn" 16 17 "github.com/juju/juju/core/migration" 18 ) 19 20 // This file contains functionality for managing the state documents 21 // used by Juju to track model migrations. 22 23 // ModelMigration represents the state of an migration attempt for a 24 // model. 25 type ModelMigration interface { 26 // Id returns a unique identifier for the model migration. 27 Id() string 28 29 // ModelUUID returns the UUID for the model being migrated. 30 ModelUUID() string 31 32 // Attempt returns the migration attempt identifier. This 33 // increments for each migration attempt for the model. 34 Attempt() (int, error) 35 36 // StartTime returns the time when the migration was started. 37 StartTime() time.Time 38 39 // SuccessTime returns the time when the migration reached 40 // SUCCESS. 41 SuccessTime() time.Time 42 43 // EndTime returns the time when the migration reached DONE or 44 // REAPFAILED. 45 EndTime() time.Time 46 47 // Phase returns the migration's phase. 48 Phase() (migration.Phase, error) 49 50 // PhaseChangedTime returns the time when the migration's phase 51 // last changed. 52 PhaseChangedTime() time.Time 53 54 // StatusMessage returns human readable text about the current 55 // progress of the migration. 56 StatusMessage() string 57 58 // InitiatedBy returns username the initiated the migration. 59 InitiatedBy() string 60 61 // TargetInfo returns the details required to connect to the 62 // migration's target controller. 63 TargetInfo() (*migration.TargetInfo, error) 64 65 // SetPhase sets the phase of the migration. An error will be 66 // returned if the new phase does not follow the current phase or 67 // if the migration is no longer active. 68 SetPhase(nextPhase migration.Phase) error 69 70 // SetStatusMessage sets some human readable text about the 71 // current progress of the migration. 72 SetStatusMessage(text string) error 73 74 // Refresh updates the contents of the ModelMigration from the 75 // underlying state. 76 Refresh() error 77 } 78 79 // modelMigration is an implementation of ModelMigration. 80 type modelMigration struct { 81 st *State 82 doc modelMigDoc 83 statusDoc modelMigStatusDoc 84 } 85 86 // modelMigDoc holds parameters of a migration attempt for a 87 // model. These are written into migrationsC. 88 type modelMigDoc struct { 89 // Id holds migration document key. It has the format 90 // "uuid:sequence". 91 Id string `bson:"_id"` 92 93 // The UUID of the model being migrated. 94 ModelUUID string `bson:"model-uuid"` 95 96 // InitiatedBy holds the username of the user that triggered the 97 // migration. It should be in "user@domain" format. 98 InitiatedBy string `bson:"initiated-by"` 99 100 // TargetController holds the UUID of the target controller. 101 TargetController string `bson:"target-controller"` 102 103 // TargetAddrs holds the host:port values for the target API 104 // server. 105 TargetAddrs []string `bson:"target-addrs"` 106 107 // TargetCACert holds the certificate to validate the target API 108 // server's TLS certificate. 109 TargetCACert string `bson:"target-cacert"` 110 111 // TargetAuthTag holds a string representation of the tag to 112 // authenticate to the target controller with. 113 TargetAuthTag string `bson:"target-entity"` 114 115 // TargetPassword holds the password to use with TargetAuthTag 116 // when authenticating. 117 TargetPassword string `bson:"target-password"` 118 } 119 120 // modelMigStatusDoc tracks the progress of a migration attempt for a 121 // model. These are written into migrationsStatusC. 122 // 123 // There is exactly one document in migrationsStatusC for each 124 // document in migrationsC. Separating them allows for watching 125 // for new model migrations without being woken up for each model 126 // migration status change. 127 type modelMigStatusDoc struct { 128 // These are the same as the ids as migrationsC. 129 // "uuid:sequence". 130 Id string `bson:"_id"` 131 132 // StartTime holds the time the migration started (stored as per 133 // UnixNano). 134 StartTime int64 `bson:"start-time"` 135 136 // StartTime holds the time the migration reached the SUCCESS 137 // phase (stored as per UnixNano). 138 SuccessTime int64 `bson:"success-time"` 139 140 // EndTime holds the time the migration reached a terminal (end) 141 // phase (stored as per UnixNano). 142 EndTime int64 `bson:"end-time"` 143 144 // Phase holds the current migration phase. This should be one of 145 // the string representations of the core/migrations.Phase 146 // constants. 147 Phase string `bson:"phase"` 148 149 // PhaseChangedTime holds the time that Phase last changed (stored 150 // as per UnixNano). 151 PhaseChangedTime int64 `bson:"phase-changed-time"` 152 153 // StatusMessage holds a human readable message about the 154 // migration's progress. 155 StatusMessage string `bson:"status-message"` 156 } 157 158 // Id implements ModelMigration. 159 func (mig *modelMigration) Id() string { 160 return mig.doc.Id 161 } 162 163 // ModelUUID implements ModelMigration. 164 func (mig *modelMigration) ModelUUID() string { 165 return mig.doc.ModelUUID 166 } 167 168 // Attempt implements ModelMigration. 169 func (mig *modelMigration) Attempt() (int, error) { 170 attempt, err := strconv.Atoi(mig.st.localID(mig.doc.Id)) 171 if err != nil { 172 // This really shouldn't happen. 173 return -1, errors.Errorf("invalid migration id: %v", mig.doc.Id) 174 } 175 return attempt, nil 176 } 177 178 // StartTime implements ModelMigration. 179 func (mig *modelMigration) StartTime() time.Time { 180 return unixNanoToTime0(mig.statusDoc.StartTime) 181 } 182 183 // SuccessTime implements ModelMigration. 184 func (mig *modelMigration) SuccessTime() time.Time { 185 return unixNanoToTime0(mig.statusDoc.SuccessTime) 186 } 187 188 // EndTime implements ModelMigration. 189 func (mig *modelMigration) EndTime() time.Time { 190 return unixNanoToTime0(mig.statusDoc.EndTime) 191 } 192 193 // Phase implements ModelMigration. 194 func (mig *modelMigration) Phase() (migration.Phase, error) { 195 phase, ok := migration.ParsePhase(mig.statusDoc.Phase) 196 if !ok { 197 return phase, errors.Errorf("invalid phase in DB: %v", mig.statusDoc.Phase) 198 } 199 return phase, nil 200 } 201 202 // PhaseChangedTime implements ModelMigration. 203 func (mig *modelMigration) PhaseChangedTime() time.Time { 204 return unixNanoToTime0(mig.statusDoc.PhaseChangedTime) 205 } 206 207 // StatusMessage implements ModelMigration. 208 func (mig *modelMigration) StatusMessage() string { 209 return mig.statusDoc.StatusMessage 210 } 211 212 // InitiatedBy implements ModelMigration. 213 func (mig *modelMigration) InitiatedBy() string { 214 return mig.doc.InitiatedBy 215 } 216 217 // TargetInfo implements ModelMigration. 218 func (mig *modelMigration) TargetInfo() (*migration.TargetInfo, error) { 219 authTag, err := names.ParseUserTag(mig.doc.TargetAuthTag) 220 if err != nil { 221 return nil, errors.Trace(err) 222 } 223 return &migration.TargetInfo{ 224 ControllerTag: names.NewModelTag(mig.doc.TargetController), 225 Addrs: mig.doc.TargetAddrs, 226 CACert: mig.doc.TargetCACert, 227 AuthTag: authTag, 228 Password: mig.doc.TargetPassword, 229 }, nil 230 } 231 232 // SetPhase implements ModelMigration. 233 func (mig *modelMigration) SetPhase(nextPhase migration.Phase) error { 234 now := GetClock().Now().UnixNano() 235 236 phase, err := mig.Phase() 237 if err != nil { 238 return errors.Trace(err) 239 } 240 241 if nextPhase == phase { 242 return nil // Already at that phase. Nothing to do. 243 } 244 if !phase.CanTransitionTo(nextPhase) { 245 return errors.Errorf("illegal phase change: %s -> %s", phase, nextPhase) 246 } 247 248 nextDoc := mig.statusDoc 249 nextDoc.Phase = nextPhase.String() 250 nextDoc.PhaseChangedTime = now 251 update := bson.M{ 252 "phase": nextDoc.Phase, 253 "phase-changed-time": now, 254 } 255 if nextPhase == migration.SUCCESS { 256 nextDoc.SuccessTime = now 257 update["success-time"] = now 258 } 259 var ops []txn.Op 260 if nextPhase.IsTerminal() { 261 nextDoc.EndTime = now 262 update["end-time"] = now 263 ops = append(ops, txn.Op{ 264 C: migrationsActiveC, 265 Id: mig.doc.ModelUUID, 266 Assert: txn.DocExists, 267 Remove: true, 268 }) 269 } 270 271 ops = append(ops, txn.Op{ 272 C: migrationsStatusC, 273 Id: mig.statusDoc.Id, 274 Update: bson.M{"$set": update}, 275 // Ensure phase hasn't changed underneath us 276 Assert: bson.M{"phase": mig.statusDoc.Phase}, 277 }) 278 279 if err := mig.st.runTransaction(ops); err == txn.ErrAborted { 280 return errors.New("phase already changed") 281 } else if err != nil { 282 return errors.Annotate(err, "failed to update phase") 283 } 284 285 mig.statusDoc = nextDoc 286 return nil 287 } 288 289 // SetStatusMessage implements ModelMigration. 290 func (mig *modelMigration) SetStatusMessage(text string) error { 291 ops := []txn.Op{{ 292 C: migrationsStatusC, 293 Id: mig.statusDoc.Id, 294 Update: bson.M{"$set": bson.M{"status-message": text}}, 295 Assert: txn.DocExists, 296 }} 297 if err := mig.st.runTransaction(ops); err != nil { 298 return errors.Annotate(err, "failed to set migration status") 299 } 300 mig.statusDoc.StatusMessage = text 301 return nil 302 } 303 304 // Refresh implements ModelMigration. 305 func (mig *modelMigration) Refresh() error { 306 // Only the status document is updated. The modelMigDoc is static 307 // after creation. 308 statusColl, closer := mig.st.getCollection(migrationsStatusC) 309 defer closer() 310 var statusDoc modelMigStatusDoc 311 err := statusColl.FindId(mig.doc.Id).One(&statusDoc) 312 if err == mgo.ErrNotFound { 313 return errors.NotFoundf("migration status") 314 } else if err != nil { 315 return errors.Annotate(err, "migration status lookup failed") 316 } 317 318 mig.statusDoc = statusDoc 319 return nil 320 } 321 322 // ModelMigrationSpec holds the information required to create a 323 // ModelMigration instance. 324 type ModelMigrationSpec struct { 325 InitiatedBy names.UserTag 326 TargetInfo migration.TargetInfo 327 } 328 329 // Validate returns an error if the ModelMigrationSpec contains bad 330 // data. Nil is returned otherwise. 331 func (spec *ModelMigrationSpec) Validate() error { 332 if !names.IsValidUser(spec.InitiatedBy.Id()) { 333 return errors.NotValidf("InitiatedBy") 334 } 335 return spec.TargetInfo.Validate() 336 } 337 338 // CreateModelMigration initialises state that tracks a model 339 // migration. It will return an error if there is already a 340 // model migration in progress. 341 func (st *State) CreateModelMigration(spec ModelMigrationSpec) (ModelMigration, error) { 342 if st.IsController() { 343 return nil, errors.New("controllers can't be migrated") 344 } 345 if err := spec.Validate(); err != nil { 346 return nil, errors.Trace(err) 347 } 348 if err := checkTargetController(st, spec.TargetInfo.ControllerTag); err != nil { 349 return nil, errors.Trace(err) 350 } 351 352 now := GetClock().Now().UnixNano() 353 modelUUID := st.ModelUUID() 354 var doc modelMigDoc 355 var statusDoc modelMigStatusDoc 356 buildTxn := func(int) ([]txn.Op, error) { 357 model, err := st.Model() 358 if err != nil { 359 return nil, errors.Annotate(err, "failed to load model") 360 } 361 if model.Life() != Alive { 362 return nil, errors.New("model is not alive") 363 } 364 365 if isActive, err := st.IsModelMigrationActive(); err != nil { 366 return nil, errors.Trace(err) 367 } else if isActive { 368 return nil, errors.New("already in progress") 369 } 370 371 seq, err := st.sequence("modelmigration") 372 if err != nil { 373 return nil, errors.Trace(err) 374 } 375 376 id := fmt.Sprintf("%s:%d", modelUUID, seq) 377 doc = modelMigDoc{ 378 Id: id, 379 ModelUUID: modelUUID, 380 InitiatedBy: spec.InitiatedBy.Id(), 381 TargetController: spec.TargetInfo.ControllerTag.Id(), 382 TargetAddrs: spec.TargetInfo.Addrs, 383 TargetCACert: spec.TargetInfo.CACert, 384 TargetAuthTag: spec.TargetInfo.AuthTag.String(), 385 TargetPassword: spec.TargetInfo.Password, 386 } 387 statusDoc = modelMigStatusDoc{ 388 Id: id, 389 StartTime: now, 390 Phase: migration.QUIESCE.String(), 391 PhaseChangedTime: now, 392 } 393 return []txn.Op{{ 394 C: migrationsC, 395 Id: doc.Id, 396 Assert: txn.DocMissing, 397 Insert: &doc, 398 }, { 399 C: migrationsStatusC, 400 Id: statusDoc.Id, 401 Assert: txn.DocMissing, 402 Insert: &statusDoc, 403 }, { 404 C: migrationsActiveC, 405 Id: modelUUID, 406 Assert: txn.DocMissing, 407 Insert: bson.M{"id": doc.Id}, 408 }, model.assertActiveOp(), 409 }, nil 410 } 411 if err := st.run(buildTxn); err != nil { 412 return nil, errors.Annotate(err, "failed to create migration") 413 } 414 415 return &modelMigration{ 416 doc: doc, 417 statusDoc: statusDoc, 418 st: st, 419 }, nil 420 } 421 422 func checkTargetController(st *State, targetControllerTag names.ModelTag) error { 423 currentController, err := st.ControllerModel() 424 if err != nil { 425 return errors.Annotate(err, "failed to load existing controller model") 426 } 427 if targetControllerTag == currentController.ModelTag() { 428 return errors.New("model already attached to target controller") 429 } 430 return nil 431 } 432 433 // GetModelMigration returns the most recent ModelMigration for a 434 // model (if any). 435 func (st *State) GetModelMigration() (ModelMigration, error) { 436 migColl, closer := st.getCollection(migrationsC) 437 defer closer() 438 439 query := migColl.Find(bson.M{"model-uuid": st.ModelUUID()}) 440 query = query.Sort("-_id").Limit(1) 441 var doc modelMigDoc 442 err := query.One(&doc) 443 if err == mgo.ErrNotFound { 444 return nil, errors.NotFoundf("migration") 445 } else if err != nil { 446 return nil, errors.Annotate(err, "migration lookup failed") 447 } 448 449 statusColl, closer := st.getCollection(migrationsStatusC) 450 defer closer() 451 var statusDoc modelMigStatusDoc 452 err = statusColl.FindId(doc.Id).One(&statusDoc) 453 if err != nil { 454 return nil, errors.Annotate(err, "failed to find status document") 455 } 456 457 return &modelMigration{ 458 doc: doc, 459 statusDoc: statusDoc, 460 st: st, 461 }, nil 462 } 463 464 // IsModelMigrationActive return true if a migration is in progress for 465 // the model associated with the State. 466 func (st *State) IsModelMigrationActive() (bool, error) { 467 active, closer := st.getCollection(migrationsActiveC) 468 defer closer() 469 n, err := active.FindId(st.ModelUUID()).Count() 470 if err != nil { 471 return false, errors.Trace(err) 472 } 473 return n > 0, nil 474 } 475 476 func unixNanoToTime0(i int64) time.Time { 477 if i == 0 { 478 return time.Time{} 479 } 480 return time.Unix(0, i) 481 }