github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/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-unstable" 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/state" 17 "github.com/juju/juju/status" 18 "github.com/juju/juju/tools" 19 ) 20 21 // PrecheckBackend defines the interface to query Juju's state 22 // for migration prechecks. 23 type PrecheckBackend interface { 24 AgentVersion() (version.Number, error) 25 NeedsCleanup() (bool, error) 26 Model() (PrecheckModel, error) 27 AllModels() ([]PrecheckModel, error) 28 IsUpgrading() (bool, error) 29 IsMigrationActive(string) (bool, error) 30 AllMachines() ([]PrecheckMachine, error) 31 AllApplications() ([]PrecheckApplication, error) 32 ControllerBackend() (PrecheckBackend, error) 33 } 34 35 // PrecheckModel describes the state interface a model as needed by 36 // the migration prechecks. 37 type PrecheckModel interface { 38 UUID() string 39 Name() string 40 Owner() names.UserTag 41 Life() state.Life 42 MigrationMode() state.MigrationMode 43 } 44 45 // PrecheckMachine describes the state interface for a machine needed 46 // by migration prechecks. 47 type PrecheckMachine interface { 48 Id() string 49 AgentTools() (*tools.Tools, error) 50 Life() state.Life 51 Status() (status.StatusInfo, error) 52 AgentPresence() (bool, error) 53 InstanceStatus() (status.StatusInfo, error) 54 ShouldRebootOrShutdown() (state.RebootAction, error) 55 } 56 57 // PrecheckApplication describes the state interface for an 58 // application needed by migration prechecks. 59 type PrecheckApplication interface { 60 Name() string 61 Life() state.Life 62 CharmURL() (*charm.URL, bool) 63 AllUnits() ([]PrecheckUnit, error) 64 MinUnits() int 65 } 66 67 // PrecheckUnit describes state interface for a unit needed by 68 // migration prechecks. 69 type PrecheckUnit interface { 70 Name() string 71 AgentTools() (*tools.Tools, error) 72 Life() state.Life 73 CharmURL() (*charm.URL, bool) 74 AgentStatus() (status.StatusInfo, error) 75 Status() (status.StatusInfo, error) 76 AgentPresence() (bool, error) 77 } 78 79 // SourcePrecheck checks the state of the source controller to make 80 // sure that the preconditions for model migration are met. The 81 // backend provided must be for the model to be migrated. 82 func SourcePrecheck(backend PrecheckBackend) error { 83 if err := checkModel(backend); err != nil { 84 return errors.Trace(err) 85 } 86 87 if err := checkMachines(backend); err != nil { 88 return errors.Trace(err) 89 } 90 91 if err := checkApplications(backend); err != nil { 92 return errors.Trace(err) 93 } 94 95 if cleanupNeeded, err := backend.NeedsCleanup(); err != nil { 96 return errors.Annotate(err, "checking cleanups") 97 } else if cleanupNeeded { 98 return errors.New("cleanup needed") 99 } 100 101 // Check the source controller. 102 controllerBackend, err := backend.ControllerBackend() 103 if err != nil { 104 return errors.Trace(err) 105 } 106 if err := checkController(controllerBackend); err != nil { 107 return errors.Annotate(err, "controller") 108 } 109 return nil 110 } 111 112 func checkModel(backend PrecheckBackend) error { 113 model, err := backend.Model() 114 if err != nil { 115 return errors.Annotate(err, "retrieving model") 116 } 117 if model.Life() != state.Alive { 118 return errors.Errorf("model is %s", model.Life()) 119 } 120 if model.MigrationMode() == state.MigrationModeImporting { 121 return errors.New("model is being imported as part of another migration") 122 } 123 return nil 124 } 125 126 // TargetPrecheck checks the state of the target controller to make 127 // sure that the preconditions for model migration are met. The 128 // backend provided must be for the target controller. 129 func TargetPrecheck(backend PrecheckBackend, modelInfo coremigration.ModelInfo) error { 130 if err := modelInfo.Validate(); err != nil { 131 return errors.Trace(err) 132 } 133 134 // This check is necessary because there is a window between the 135 // REAP phase and then end of the DONE phase where a model's 136 // documents have been deleted but the migration isn't quite done 137 // yet. Migrating a model back into the controller during this 138 // window can upset the migrationmaster worker. 139 // 140 // See also https://lpad.tv/1611391 141 if migrating, err := backend.IsMigrationActive(modelInfo.UUID); err != nil { 142 return errors.Annotate(err, "checking for active migration") 143 } else if migrating { 144 return errors.New("model is being migrated out of target controller") 145 } 146 147 controllerVersion, err := backend.AgentVersion() 148 if err != nil { 149 return errors.Annotate(err, "retrieving model version") 150 } 151 152 if controllerVersion.Compare(modelInfo.AgentVersion) < 0 { 153 return errors.Errorf("model has higher version than target controller (%s > %s)", 154 modelInfo.AgentVersion, controllerVersion) 155 } 156 157 if err := checkController(backend); err != nil { 158 return errors.Trace(err) 159 } 160 161 // Check for conflicts with existing models 162 models, err := backend.AllModels() 163 if err != nil { 164 return errors.Annotate(err, "retrieving models") 165 } 166 canonicalOwner := modelInfo.Owner.Canonical() 167 for _, model := range models { 168 // If the model is importing then it's probably left behind 169 // from a previous migration attempt. It will be removed 170 // before the next import. 171 if model.UUID() == modelInfo.UUID && model.MigrationMode() != state.MigrationModeImporting { 172 return errors.Errorf("model with same UUID already exists (%s)", modelInfo.UUID) 173 } 174 if model.Name() == modelInfo.Name && model.Owner().Canonical() == canonicalOwner { 175 return errors.Errorf("model named %q already exists", model.Name()) 176 } 177 } 178 179 return nil 180 } 181 182 func checkController(backend PrecheckBackend) error { 183 model, err := backend.Model() 184 if err != nil { 185 return errors.Annotate(err, "retrieving model") 186 } 187 if model.Life() != state.Alive { 188 return errors.Errorf("model is %s", model.Life()) 189 } 190 191 if upgrading, err := backend.IsUpgrading(); err != nil { 192 return errors.Annotate(err, "checking for upgrades") 193 } else if upgrading { 194 return errors.New("upgrade in progress") 195 } 196 197 err = checkMachines(backend) 198 return errors.Trace(err) 199 } 200 201 func checkMachines(backend PrecheckBackend) error { 202 modelVersion, err := backend.AgentVersion() 203 if err != nil { 204 return errors.Annotate(err, "retrieving model version") 205 } 206 207 machines, err := backend.AllMachines() 208 if err != nil { 209 return errors.Annotate(err, "retrieving machines") 210 } 211 for _, machine := range machines { 212 if machine.Life() != state.Alive { 213 return errors.Errorf("machine %s is %s", machine.Id(), machine.Life()) 214 } 215 216 if statusInfo, err := machine.InstanceStatus(); err != nil { 217 return errors.Annotatef(err, "retrieving machine %s instance status", machine.Id()) 218 } else if statusInfo.Status != status.Running { 219 return newStatusError("machine %s not running", machine.Id(), statusInfo.Status) 220 } 221 222 if statusInfo, err := common.MachineStatus(machine); err != nil { 223 return errors.Annotatef(err, "retrieving machine %s status", machine.Id()) 224 } else if statusInfo.Status != status.Started { 225 return newStatusError("machine %s agent not functioning at this time", 226 machine.Id(), statusInfo.Status) 227 } 228 229 if rebootAction, err := machine.ShouldRebootOrShutdown(); err != nil { 230 return errors.Annotatef(err, "retrieving machine %s reboot status", machine.Id()) 231 } else if rebootAction != state.ShouldDoNothing { 232 return errors.Errorf("machine %s is scheduled to %s", machine.Id(), rebootAction) 233 } 234 235 if err := checkAgentTools(modelVersion, machine, "machine "+machine.Id()); err != nil { 236 return errors.Trace(err) 237 } 238 } 239 return nil 240 } 241 242 func checkApplications(backend PrecheckBackend) error { 243 modelVersion, err := backend.AgentVersion() 244 if err != nil { 245 return errors.Annotate(err, "retrieving model version") 246 } 247 apps, err := backend.AllApplications() 248 if err != nil { 249 return errors.Annotate(err, "retrieving applications") 250 } 251 for _, app := range apps { 252 if app.Life() != state.Alive { 253 return errors.Errorf("application %s is %s", app.Name(), app.Life()) 254 } 255 err := checkUnits(app, modelVersion) 256 if err != nil { 257 return errors.Trace(err) 258 } 259 } 260 return nil 261 } 262 263 func checkUnits(app PrecheckApplication, modelVersion version.Number) error { 264 units, err := app.AllUnits() 265 if err != nil { 266 return errors.Annotatef(err, "retrieving units for %s", app.Name()) 267 } 268 if len(units) < app.MinUnits() { 269 return errors.Errorf("application %s is below its minimum units threshold", app.Name()) 270 } 271 272 appCharmURL, _ := app.CharmURL() 273 274 for _, unit := range units { 275 if unit.Life() != state.Alive { 276 return errors.Errorf("unit %s is %s", unit.Name(), unit.Life()) 277 } 278 279 if err := checkUnitAgentStatus(unit); err != nil { 280 return errors.Trace(err) 281 } 282 283 if err := checkAgentTools(modelVersion, unit, "unit "+unit.Name()); err != nil { 284 return errors.Trace(err) 285 } 286 287 unitCharmURL, _ := unit.CharmURL() 288 if appCharmURL.String() != unitCharmURL.String() { 289 return errors.Errorf("unit %s is upgrading", unit.Name()) 290 } 291 } 292 return nil 293 } 294 295 func checkUnitAgentStatus(unit PrecheckUnit) error { 296 statusData, _ := common.UnitStatus(unit) 297 if statusData.Err != nil { 298 return errors.Annotatef(statusData.Err, "retrieving unit %s status", unit.Name()) 299 } 300 agentStatus := statusData.Status.Status 301 if agentStatus != status.Idle { 302 return newStatusError("unit %s not idle", unit.Name(), agentStatus) 303 } 304 return nil 305 } 306 307 func checkAgentTools(modelVersion version.Number, agent agentToolsGetter, agentLabel string) error { 308 tools, err := agent.AgentTools() 309 if err != nil { 310 return errors.Annotatef(err, "retrieving tools for %s", agentLabel) 311 } 312 agentVersion := tools.Version.Number 313 if agentVersion != modelVersion { 314 return errors.Errorf("%s tools don't match model (%s != %s)", 315 agentLabel, agentVersion, modelVersion) 316 } 317 return nil 318 } 319 320 type agentToolsGetter interface { 321 AgentTools() (*tools.Tools, error) 322 } 323 324 func newStatusError(format, id string, s status.Status) error { 325 msg := fmt.Sprintf(format, id) 326 if s != status.Empty { 327 msg += fmt.Sprintf(" (%s)", s) 328 } 329 return errors.New(msg) 330 }