github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/state/upgrades.go (about) 1 // Copyright 2014 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package state 5 6 import ( 7 "github.com/juju/errors" 8 "github.com/juju/loggo" 9 "gopkg.in/juju/names.v2" 10 "gopkg.in/mgo.v2/bson" 11 "gopkg.in/mgo.v2/txn" 12 13 "github.com/juju/juju/status" 14 ) 15 16 var upgradesLogger = loggo.GetLogger("juju.state.upgrade") 17 18 func AddPreferredAddressesToMachines(st *State) error { 19 machines, err := st.AllMachines() 20 if err != nil { 21 return errors.Trace(err) 22 } 23 24 for _, machine := range machines { 25 if machine.Life() == Dead { 26 continue 27 } 28 // Setting the addresses is enough to trigger setting the preferred 29 // addresses. 30 err = machine.SetMachineAddresses(machine.MachineAddresses()...) 31 if err != nil { 32 return errors.Trace(err) 33 } 34 err := machine.SetProviderAddresses(machine.ProviderAddresses()...) 35 if err != nil { 36 return errors.Trace(err) 37 } 38 } 39 return nil 40 } 41 42 // runForAllEnvStates will run runner function for every env passing a state 43 // for that env. 44 func runForAllEnvStates(st *State, runner func(st *State) error) error { 45 environments, closer := st.getCollection(modelsC) 46 defer closer() 47 48 var envDocs []bson.M 49 err := environments.Find(nil).Select(bson.M{"_id": 1}).All(&envDocs) 50 if err != nil { 51 return errors.Annotate(err, "failed to read models") 52 } 53 54 for _, envDoc := range envDocs { 55 modelUUID := envDoc["_id"].(string) 56 envSt, err := st.ForModel(names.NewModelTag(modelUUID)) 57 if err != nil { 58 return errors.Annotatef(err, "failed to open model %q", modelUUID) 59 } 60 defer envSt.Close() 61 if err := runner(envSt); err != nil { 62 return errors.Annotatef(err, "model UUID %q", modelUUID) 63 } 64 } 65 return nil 66 } 67 68 // AddFilesystemStatus ensures each filesystem has a status doc. 69 func AddFilesystemStatus(st *State) error { 70 return runForAllEnvStates(st, func(st *State) error { 71 filesystems, err := st.AllFilesystems() 72 if err != nil { 73 return errors.Trace(err) 74 } 75 var ops []txn.Op 76 for _, filesystem := range filesystems { 77 _, err := filesystem.Status() 78 if err == nil { 79 continue 80 } 81 if !errors.IsNotFound(err) { 82 return errors.Annotate(err, "getting status") 83 } 84 status, err := upgradingFilesystemStatus(st, filesystem) 85 if err != nil { 86 return errors.Annotate(err, "deciding filesystem status") 87 } 88 ops = append(ops, createStatusOp(st, filesystem.globalKey(), statusDoc{ 89 Status: status, 90 Updated: st.clock.Now().UnixNano(), 91 })) 92 } 93 if len(ops) > 0 { 94 return errors.Trace(st.runTransaction(ops)) 95 } 96 return nil 97 }) 98 } 99 100 // If the filesystem has not been provisioned, then it should be Pending; 101 // if it has been provisioned, but there is an unprovisioned attachment, then 102 // it should be Attaching; otherwise it is Attached. 103 func upgradingFilesystemStatus(st *State, filesystem Filesystem) (status.Status, error) { 104 if _, err := filesystem.Info(); errors.IsNotProvisioned(err) { 105 return status.Pending, nil 106 } 107 attachments, err := st.FilesystemAttachments(filesystem.FilesystemTag()) 108 if err != nil { 109 return "", errors.Trace(err) 110 } 111 for _, attachment := range attachments { 112 _, err := attachment.Info() 113 if errors.IsNotProvisioned(err) { 114 return status.Attaching, nil 115 } 116 } 117 return status.Attached, nil 118 } 119 120 // MigrateSettingsSchema migrates the schema of the settings collection, 121 // moving non-reserved keys at the top-level into a subdoc, and introducing 122 // a top-level "version" field with the initial value matching txn-revno. 123 // 124 // This migration takes place both before and after model-uuid migration, 125 // to get the correct txn-revno value. 126 func MigrateSettingsSchema(st *State) error { 127 coll, closer := st.getRawCollection(settingsC) 128 defer closer() 129 130 upgradesLogger.Debugf("migrating schema of the %s collection", settingsC) 131 iter := coll.Find(nil).Iter() 132 defer iter.Close() 133 134 var ops []txn.Op 135 var doc bson.M 136 for iter.Next(&doc) { 137 if !settingsDocNeedsMigration(doc) { 138 continue 139 } 140 141 id := doc["_id"] 142 txnRevno := doc["txn-revno"].(int64) 143 144 // Remove reserved attributes; we'll move the remaining 145 // ones to the "settings" subdoc. 146 delete(doc, "model-uuid") 147 delete(doc, "_id") 148 delete(doc, "txn-revno") 149 delete(doc, "txn-queue") 150 151 // If there exists a setting by the name "settings", 152 // we must remove it first, or it will collide with 153 // the dotted-notation $sets. 154 if _, ok := doc["settings"]; ok { 155 ops = append(ops, txn.Op{ 156 C: settingsC, 157 Id: id, 158 Assert: txn.DocExists, 159 Update: bson.D{{"$unset", bson.D{{"settings", 1}}}}, 160 }) 161 } 162 163 var update bson.D 164 for key, value := range doc { 165 if key != "settings" && key != "version" { 166 // Don't try to unset these fields, 167 // as we've unset "settings" above 168 // already, and we'll overwrite 169 // "version" below. 170 update = append(update, bson.DocElem{ 171 "$unset", bson.D{{key, 1}}, 172 }) 173 } 174 update = append(update, bson.DocElem{ 175 "$set", bson.D{{"settings." + key, value}}, 176 }) 177 } 178 if len(update) == 0 { 179 // If there are no settings, then we need 180 // to add an empty "settings" map so we 181 // can tell for next time that migration 182 // is complete, and don't move the "version" 183 // field we add. 184 update = bson.D{{ 185 "$set", bson.D{{"settings", bson.M{}}}, 186 }} 187 } 188 update = append(update, bson.DocElem{ 189 "$set", bson.D{{"version", txnRevno}}, 190 }) 191 192 ops = append(ops, txn.Op{ 193 C: settingsC, 194 Id: id, 195 Assert: txn.DocExists, 196 Update: update, 197 }) 198 } 199 if err := iter.Err(); err != nil { 200 return errors.Trace(err) 201 } 202 return st.runRawTransaction(ops) 203 } 204 205 func settingsDocNeedsMigration(doc bson.M) bool { 206 // It is not possible for there to exist a settings value 207 // with type bson.M, so we know that it is the new settings 208 // field and not just a setting with the name "settings". 209 if _, ok := doc["settings"].(bson.M); ok { 210 return false 211 } 212 return true 213 } 214 215 func addDefaultBindingsToServices(st *State) error { 216 applications, err := st.AllApplications() 217 if err != nil { 218 return errors.Trace(err) 219 } 220 221 upgradesLogger.Debugf("adding default endpoint bindings to applications (where missing)") 222 ops := make([]txn.Op, 0, len(applications)) 223 for _, application := range applications { 224 ch, _, err := application.Charm() 225 if err != nil { 226 return errors.Annotatef(err, "cannot get charm for application %q", application.Name()) 227 } 228 if _, err := application.EndpointBindings(); err == nil { 229 upgradesLogger.Debugf("application %q already has bindings (skipping)", application.Name()) 230 continue 231 } else if !errors.IsNotFound(err) { 232 return errors.Annotatef(err, "checking application %q for existing bindings", application.Name()) 233 } 234 // Passing nil for the bindings map will use the defaults. 235 createOp, err := createEndpointBindingsOp(st, application.globalKey(), nil, ch.Meta()) 236 if err != nil { 237 return errors.Annotatef(err, "setting default endpoint bindings for application %q", application.Name()) 238 } 239 ops = append(ops, createOp) 240 } 241 return st.runTransaction(ops) 242 } 243 244 // AddDefaultEndpointBindingsToServices adds default endpoint bindings for each 245 // service. As long as the service has a charm URL set, each charm endpoint will 246 // be bound to the default space. 247 func AddDefaultEndpointBindingsToServices(st *State) error { 248 return runForAllEnvStates(st, addDefaultBindingsToServices) 249 }