github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/migration/precheck.go (about) 1 // Copyright 2016 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package migration 5 6 import ( 7 "fmt" 8 9 "github.com/juju/errors" 10 "github.com/juju/version" 11 "gopkg.in/juju/charm.v6" 12 "gopkg.in/juju/names.v2" 13 14 "github.com/juju/juju/apiserver/common" 15 coremigration "github.com/juju/juju/core/migration" 16 "github.com/juju/juju/core/presence" 17 "github.com/juju/juju/core/status" 18 "github.com/juju/juju/resource" 19 "github.com/juju/juju/state" 20 "github.com/juju/juju/tools" 21 ) 22 23 // PrecheckBackend defines the interface to query Juju's state 24 // for migration prechecks. 25 type PrecheckBackend interface { 26 AgentVersion() (version.Number, error) 27 NeedsCleanup() (bool, error) 28 Model() (PrecheckModel, error) 29 AllModelUUIDs() ([]string, error) 30 IsUpgrading() (bool, error) 31 IsMigrationActive(string) (bool, error) 32 AllMachines() ([]PrecheckMachine, error) 33 AllApplications() ([]PrecheckApplication, error) 34 AllRelations() ([]PrecheckRelation, error) 35 ControllerBackend() (PrecheckBackend, error) 36 CloudCredential(tag names.CloudCredentialTag) (state.Credential, error) 37 ListPendingResources(string) ([]resource.Resource, error) 38 } 39 40 // Pool defines the interface to a StatePool used by the migration 41 // prechecks. 42 type Pool interface { 43 GetModel(string) (PrecheckModel, func(), error) 44 } 45 46 // PrecheckModel describes the state interface a model as needed by 47 // the migration prechecks. 48 type PrecheckModel interface { 49 UUID() string 50 Name() string 51 Type() state.ModelType 52 Owner() names.UserTag 53 Life() state.Life 54 MigrationMode() state.MigrationMode 55 CloudCredential() (names.CloudCredentialTag, bool) 56 } 57 58 // PrecheckMachine describes the state interface for a machine needed 59 // by migration prechecks. 60 type PrecheckMachine interface { 61 Id() string 62 AgentTools() (*tools.Tools, error) 63 Life() state.Life 64 Status() (status.StatusInfo, error) 65 AgentPresence() (bool, error) 66 InstanceStatus() (status.StatusInfo, error) 67 ShouldRebootOrShutdown() (state.RebootAction, error) 68 } 69 70 // PrecheckApplication describes the state interface for an 71 // application needed by migration prechecks. 72 type PrecheckApplication interface { 73 Name() string 74 Life() state.Life 75 CharmURL() (*charm.URL, bool) 76 AllUnits() ([]PrecheckUnit, error) 77 MinUnits() int 78 } 79 80 // PrecheckUnit describes state interface for a unit needed by 81 // migration prechecks. 82 type PrecheckUnit interface { 83 Name() string 84 AgentTools() (*tools.Tools, error) 85 Life() state.Life 86 CharmURL() (*charm.URL, bool) 87 AgentStatus() (status.StatusInfo, error) 88 Status() (status.StatusInfo, error) 89 AgentPresence() (bool, error) 90 ShouldBeAssigned() bool 91 } 92 93 // PrecheckRelation describes the state interface for relations needed 94 // for prechecks. 95 type PrecheckRelation interface { 96 String() string 97 IsCrossModel() (bool, error) 98 Endpoints() []state.Endpoint 99 Unit(PrecheckUnit) (PrecheckRelationUnit, error) 100 } 101 102 // PrecheckRelationUnit describes the interface for relation units 103 // needed for migration prechecks. 104 type PrecheckRelationUnit interface { 105 Valid() (bool, error) 106 InScope() (bool, error) 107 } 108 109 // ModelPresence represents the API server connections for a model. 110 type ModelPresence interface { 111 // For a given non controller agent, return the Status for that agent. 112 AgentStatus(agent string) (presence.Status, error) 113 } 114 115 // SourcePrecheck checks the state of the source controller to make 116 // sure that the preconditions for model migration are met. The 117 // backend provided must be for the model to be migrated. 118 func SourcePrecheck( 119 backend PrecheckBackend, 120 modelPresence ModelPresence, 121 controllerPresence ModelPresence, 122 ) error { 123 ctx := precheckContext{backend, modelPresence} 124 if err := ctx.checkModel(); err != nil { 125 return errors.Trace(err) 126 } 127 128 if err := ctx.checkMachines(); err != nil { 129 return errors.Trace(err) 130 } 131 132 appUnits, err := ctx.checkApplications() 133 if err != nil { 134 return errors.Trace(err) 135 } 136 137 if err := ctx.checkRelations(appUnits); err != nil { 138 return errors.Trace(err) 139 } 140 141 if cleanupNeeded, err := backend.NeedsCleanup(); err != nil { 142 return errors.Annotate(err, "checking cleanups") 143 } else if cleanupNeeded { 144 return errors.New("cleanup needed") 145 } 146 147 // Check the source controller. 148 controllerBackend, err := backend.ControllerBackend() 149 if err != nil { 150 return errors.Trace(err) 151 } 152 controllerCtx := precheckContext{controllerBackend, controllerPresence} 153 if err := controllerCtx.checkController(); err != nil { 154 return errors.Annotate(err, "controller") 155 } 156 return nil 157 } 158 159 type precheckContext struct { 160 backend PrecheckBackend 161 presence ModelPresence 162 } 163 164 func (ctx *precheckContext) checkModel() error { 165 model, err := ctx.backend.Model() 166 if err != nil { 167 return errors.Annotate(err, "retrieving model") 168 } 169 if model.Life() != state.Alive { 170 return errors.Errorf("model is %s", model.Life()) 171 } 172 if model.MigrationMode() == state.MigrationModeImporting { 173 return errors.New("model is being imported as part of another migration") 174 } 175 if credTag, found := model.CloudCredential(); found { 176 creds, err := ctx.backend.CloudCredential(credTag) 177 if err != nil { 178 return errors.Trace(err) 179 } 180 if creds.Revoked { 181 return errors.New("model has revoked credentials") 182 } 183 } 184 return nil 185 } 186 187 // TargetPrecheck checks the state of the target controller to make 188 // sure that the preconditions for model migration are met. The 189 // backend provided must be for the target controller. 190 func TargetPrecheck(backend PrecheckBackend, pool Pool, modelInfo coremigration.ModelInfo, presence ModelPresence) error { 191 if err := modelInfo.Validate(); err != nil { 192 return errors.Trace(err) 193 } 194 195 // This check is necessary because there is a window between the 196 // REAP phase and then end of the DONE phase where a model's 197 // documents have been deleted but the migration isn't quite done 198 // yet. Migrating a model back into the controller during this 199 // window can upset the migrationmaster worker. 200 // 201 // See also https://lpad.tv/1611391 202 if migrating, err := backend.IsMigrationActive(modelInfo.UUID); err != nil { 203 return errors.Annotate(err, "checking for active migration") 204 } else if migrating { 205 return errors.New("model is being migrated out of target controller") 206 } 207 208 controllerVersion, err := backend.AgentVersion() 209 if err != nil { 210 return errors.Annotate(err, "retrieving model version") 211 } 212 213 if controllerVersion.Compare(modelInfo.AgentVersion) < 0 { 214 return errors.Errorf("model has higher version than target controller (%s > %s)", 215 modelInfo.AgentVersion, controllerVersion) 216 } 217 218 if !controllerVersionCompatible(modelInfo.ControllerAgentVersion, controllerVersion) { 219 return errors.Errorf("source controller has higher version than target controller (%s > %s)", 220 modelInfo.ControllerAgentVersion, controllerVersion) 221 } 222 223 controllerCtx := precheckContext{backend, presence} 224 if err := controllerCtx.checkController(); err != nil { 225 return errors.Trace(err) 226 } 227 228 // Check for conflicts with existing models 229 modelUUIDs, err := backend.AllModelUUIDs() 230 if err != nil { 231 return errors.Annotate(err, "retrieving models") 232 } 233 for _, modelUUID := range modelUUIDs { 234 model, release, err := pool.GetModel(modelUUID) 235 if err != nil { 236 return errors.Trace(err) 237 } 238 defer release() 239 240 // If the model is importing then it's probably left behind 241 // from a previous migration attempt. It will be removed 242 // before the next import. 243 if model.UUID() == modelInfo.UUID && model.MigrationMode() != state.MigrationModeImporting { 244 return errors.Errorf("model with same UUID already exists (%s)", modelInfo.UUID) 245 } 246 if model.Name() == modelInfo.Name && model.Owner() == modelInfo.Owner { 247 return errors.Errorf("model named %q already exists", model.Name()) 248 } 249 } 250 251 return nil 252 } 253 254 func controllerVersionCompatible(sourceVersion, targetVersion version.Number) bool { 255 // Compare source controller version to target controller version, only 256 // considering major and minor version numbers. Downgrades between 257 // patch, build releases for a given major.minor release are 258 // ok. Tag differences are ok too. 259 sourceVersion = versionToMajMin(sourceVersion) 260 targetVersion = versionToMajMin(targetVersion) 261 return sourceVersion.Compare(targetVersion) <= 0 262 } 263 264 func versionToMajMin(ver version.Number) version.Number { 265 ver.Patch = 0 266 ver.Build = 0 267 ver.Tag = "" 268 return ver 269 } 270 271 func (ctx *precheckContext) checkController() error { 272 model, err := ctx.backend.Model() 273 if err != nil { 274 return errors.Annotate(err, "retrieving model") 275 } 276 if model.Life() != state.Alive { 277 return errors.Errorf("model is %s", model.Life()) 278 } 279 280 if upgrading, err := ctx.backend.IsUpgrading(); err != nil { 281 return errors.Annotate(err, "checking for upgrades") 282 } else if upgrading { 283 return errors.New("upgrade in progress") 284 } 285 286 return errors.Trace(ctx.checkMachines()) 287 } 288 289 func (ctx *precheckContext) checkMachines() error { 290 modelVersion, err := ctx.backend.AgentVersion() 291 if err != nil { 292 return errors.Annotate(err, "retrieving model version") 293 } 294 295 machines, err := ctx.backend.AllMachines() 296 if err != nil { 297 return errors.Annotate(err, "retrieving machines") 298 } 299 modelPresenceContext := common.ModelPresenceContext{ctx.presence} 300 for _, machine := range machines { 301 if machine.Life() != state.Alive { 302 return errors.Errorf("machine %s is %s", machine.Id(), machine.Life()) 303 } 304 305 if statusInfo, err := machine.InstanceStatus(); err != nil { 306 return errors.Annotatef(err, "retrieving machine %s instance status", machine.Id()) 307 } else if statusInfo.Status != status.Running { 308 return newStatusError("machine %s not running", machine.Id(), statusInfo.Status) 309 } 310 311 if statusInfo, err := modelPresenceContext.MachineStatus(machine); err != nil { 312 return errors.Annotatef(err, "retrieving machine %s status", machine.Id()) 313 } else if statusInfo.Status != status.Started { 314 return newStatusError("machine %s agent not functioning at this time", 315 machine.Id(), statusInfo.Status) 316 } 317 318 if rebootAction, err := machine.ShouldRebootOrShutdown(); err != nil { 319 return errors.Annotatef(err, "retrieving machine %s reboot status", machine.Id()) 320 } else if rebootAction != state.ShouldDoNothing { 321 return errors.Errorf("machine %s is scheduled to %s", machine.Id(), rebootAction) 322 } 323 324 if err := checkAgentTools(modelVersion, machine, "machine "+machine.Id()); err != nil { 325 return errors.Trace(err) 326 } 327 } 328 return nil 329 } 330 331 func (ctx *precheckContext) checkApplications() (map[string][]PrecheckUnit, error) { 332 modelVersion, err := ctx.backend.AgentVersion() 333 if err != nil { 334 return nil, errors.Annotate(err, "retrieving model version") 335 } 336 apps, err := ctx.backend.AllApplications() 337 if err != nil { 338 return nil, errors.Annotate(err, "retrieving applications") 339 } 340 341 model, err := ctx.backend.Model() 342 if err != nil { 343 return nil, errors.Annotate(err, "retrieving model") 344 } 345 appUnits := make(map[string][]PrecheckUnit, len(apps)) 346 for _, app := range apps { 347 if app.Life() != state.Alive { 348 return nil, errors.Errorf("application %s is %s", app.Name(), app.Life()) 349 } 350 units, err := app.AllUnits() 351 if err != nil { 352 return nil, errors.Annotatef(err, "retrieving units for %s", app.Name()) 353 } 354 err = ctx.checkUnits(app, units, modelVersion, model.Type()) 355 if err != nil { 356 return nil, errors.Trace(err) 357 } 358 appUnits[app.Name()] = units 359 } 360 return appUnits, nil 361 } 362 363 func (ctx *precheckContext) checkUnits(app PrecheckApplication, units []PrecheckUnit, modelVersion version.Number, modelType state.ModelType) error { 364 if len(units) < app.MinUnits() { 365 return errors.Errorf("application %s is below its minimum units threshold", app.Name()) 366 } 367 368 appCharmURL, _ := app.CharmURL() 369 370 for _, unit := range units { 371 if unit.Life() != state.Alive { 372 return errors.Errorf("unit %s is %s", unit.Name(), unit.Life()) 373 } 374 375 if err := ctx.checkUnitAgentStatus(unit); err != nil { 376 return errors.Trace(err) 377 } 378 379 if modelType == state.ModelTypeIAAS { 380 if err := checkAgentTools(modelVersion, unit, "unit "+unit.Name()); err != nil { 381 return errors.Trace(err) 382 } 383 } 384 385 unitCharmURL, _ := unit.CharmURL() 386 if appCharmURL.String() != unitCharmURL.String() { 387 return errors.Errorf("unit %s is upgrading", unit.Name()) 388 } 389 } 390 return nil 391 } 392 393 func (ctx *precheckContext) checkUnitAgentStatus(unit PrecheckUnit) error { 394 modelPresenceContext := common.ModelPresenceContext{ctx.presence} 395 statusData, _ := modelPresenceContext.UnitStatus(unit) 396 if statusData.Err != nil { 397 return errors.Annotatef(statusData.Err, "retrieving unit %s status", unit.Name()) 398 } 399 agentStatus := statusData.Status.Status 400 switch agentStatus { 401 case status.Idle, status.Executing: 402 // These two are fine. 403 default: 404 return newStatusError("unit %s not idle or executing", unit.Name(), agentStatus) 405 } 406 return nil 407 } 408 409 func checkAgentTools(modelVersion version.Number, agent agentToolsGetter, agentLabel string) error { 410 tools, err := agent.AgentTools() 411 if err != nil { 412 return errors.Annotatef(err, "retrieving agent binaries for %s", agentLabel) 413 } 414 agentVersion := tools.Version.Number 415 if agentVersion != modelVersion { 416 return errors.Errorf("%s agent binaries don't match model (%s != %s)", 417 agentLabel, agentVersion, modelVersion) 418 } 419 return nil 420 } 421 422 type agentToolsGetter interface { 423 AgentTools() (*tools.Tools, error) 424 } 425 426 func newStatusError(format, id string, s status.Status) error { 427 msg := fmt.Sprintf(format, id) 428 if s != status.Empty { 429 msg += fmt.Sprintf(" (%s)", s) 430 } 431 return errors.New(msg) 432 } 433 434 func (ctx *precheckContext) checkRelations(appUnits map[string][]PrecheckUnit) error { 435 relations, err := ctx.backend.AllRelations() 436 if err != nil { 437 return errors.Annotate(err, "retrieving model relations") 438 } 439 for _, rel := range relations { 440 // We expect a relationScope and settings for each of the 441 // units of the specified application, unless it is a 442 // remote application. 443 crossModel, err := rel.IsCrossModel() 444 if err != nil { 445 return errors.Annotatef(err, "checking whether relation %s is cross-model", rel) 446 } 447 if crossModel { 448 continue 449 } 450 for _, ep := range rel.Endpoints() { 451 for _, unit := range appUnits[ep.ApplicationName] { 452 ru, err := rel.Unit(unit) 453 if err != nil { 454 return errors.Trace(err) 455 } 456 valid, err := ru.Valid() 457 if err != nil { 458 return errors.Trace(err) 459 } 460 if !valid { 461 continue 462 } 463 inScope, err := ru.InScope() 464 if err != nil { 465 return errors.Trace(err) 466 } 467 if !inScope { 468 return errors.Errorf("unit %s hasn't joined relation %s yet", unit.Name(), rel) 469 } 470 } 471 } 472 } 473 return nil 474 }