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