github.com/mhilton/juju-juju@v0.0.0-20150901100907-a94dd2c73455/upgrades/upgrade.go (about) 1 // Copyright 2014 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package upgrades 5 6 import ( 7 "fmt" 8 9 "github.com/juju/loggo" 10 11 "github.com/juju/juju/version" 12 ) 13 14 var logger = loggo.GetLogger("juju.upgrade") 15 16 // Step defines an idempotent operation that is run to perform 17 // a specific upgrade step. 18 type Step interface { 19 // Description is a human readable description of what the upgrade step does. 20 Description() string 21 22 // Targets returns the target machine types for which the upgrade step is applicable. 23 Targets() []Target 24 25 // Run executes the upgrade business logic. 26 Run(Context) error 27 } 28 29 // Operation defines what steps to perform to upgrade to a target version. 30 type Operation interface { 31 // The Juju version for which this operation is applicable. 32 // Upgrade operations designed for versions of Juju earlier 33 // than we are upgrading from are not run since such steps would 34 // already have been used to get to the version we are running now. 35 TargetVersion() version.Number 36 37 // Steps to perform during an upgrade. 38 Steps() []Step 39 } 40 41 // Target defines the type of machine for which a particular upgrade 42 // step can be run. 43 type Target string 44 45 const ( 46 // AllMachines applies to any machine. 47 AllMachines = Target("allMachines") 48 49 // HostMachine is a machine on which units are deployed. 50 HostMachine = Target("hostMachine") 51 52 // StateServer is a machine participating in a Juju state server cluster. 53 StateServer = Target("stateServer") 54 55 // DatabaseMaster is a StateServer that has the master database, and as such 56 // is the only target that should run database schema upgrade steps. 57 DatabaseMaster = Target("databaseMaster") 58 ) 59 60 // upgradeToVersion encapsulates the steps which need to be run to 61 // upgrade any prior version of Juju to targetVersion. 62 type upgradeToVersion struct { 63 targetVersion version.Number 64 steps []Step 65 } 66 67 // Steps is defined on the Operation interface. 68 func (u upgradeToVersion) Steps() []Step { 69 return u.steps 70 } 71 72 // TargetVersion is defined on the Operation interface. 73 func (u upgradeToVersion) TargetVersion() version.Number { 74 return u.targetVersion 75 } 76 77 // upgradeError records a description of the step being performed and the error. 78 type upgradeError struct { 79 description string 80 err error 81 } 82 83 func (e *upgradeError) Error() string { 84 return fmt.Sprintf("%s: %v", e.description, e.err) 85 } 86 87 // AreUpgradesDefined returns true if there are upgrade operations 88 // defined between the version supplied and the running software 89 // version. 90 func AreUpgradesDefined(from version.Number) bool { 91 return newUpgradeOpsIterator(from).Next() || newStateUpgradeOpsIterator(from).Next() 92 } 93 94 // PerformUpgrade runs the business logic needed to upgrade the current "from" version to this 95 // version of Juju on the "target" type of machine. 96 func PerformUpgrade(from version.Number, targets []Target, context Context) error { 97 if hasStateTarget(targets) { 98 ops := newStateUpgradeOpsIterator(from) 99 if err := runUpgradeSteps(ops, targets, context.StateContext()); err != nil { 100 return err 101 } 102 } 103 104 ops := newUpgradeOpsIterator(from) 105 if err := runUpgradeSteps(ops, targets, context.APIContext()); err != nil { 106 return err 107 } 108 109 logger.Infof("All upgrade steps completed successfully") 110 return nil 111 } 112 113 func hasStateTarget(targets []Target) bool { 114 for _, target := range targets { 115 if target == StateServer || target == DatabaseMaster { 116 return true 117 } 118 } 119 return false 120 } 121 122 // runUpgradeSteps finds all the upgrade operations relevant to 123 // the targets given and runs the associated upgrade steps. 124 // 125 // As soon as any error is encountered, the operation is aborted since 126 // subsequent steps may required successful completion of earlier 127 // ones. The steps must be idempotent so that the entire upgrade 128 // operation can be retried. 129 func runUpgradeSteps(ops *opsIterator, targets []Target, context Context) error { 130 for ops.Next() { 131 for _, step := range ops.Get().Steps() { 132 if targetsMatch(targets, step.Targets()) { 133 logger.Infof("running upgrade step: %v", step.Description()) 134 if err := step.Run(context); err != nil { 135 logger.Errorf("upgrade step %q failed: %v", step.Description(), err) 136 return &upgradeError{ 137 description: step.Description(), 138 err: err, 139 } 140 } 141 } 142 } 143 } 144 return nil 145 } 146 147 // targetsMatch returns true if any machineTargets match any of 148 // stepTargets. 149 func targetsMatch(machineTargets []Target, stepTargets []Target) bool { 150 for _, machineTarget := range machineTargets { 151 for _, stepTarget := range stepTargets { 152 if machineTarget == stepTarget || stepTarget == AllMachines { 153 return true 154 } 155 } 156 } 157 return false 158 } 159 160 // upgradeStep is a default Step implementation. 161 type upgradeStep struct { 162 description string 163 targets []Target 164 run func(Context) error 165 } 166 167 var _ Step = (*upgradeStep)(nil) 168 169 // Description is defined on the Step interface. 170 func (step *upgradeStep) Description() string { 171 return step.description 172 } 173 174 // Targets is defined on the Step interface. 175 func (step *upgradeStep) Targets() []Target { 176 return step.targets 177 } 178 179 // Run is defined on the Step interface. 180 func (step *upgradeStep) Run(context Context) error { 181 return step.run(context) 182 }