github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/apiserver/facades/controller/migrationmaster/facade.go (about) 1 // Copyright 2016 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package migrationmaster 5 6 import ( 7 "encoding/json" 8 9 "github.com/juju/collections/set" 10 "github.com/juju/description" 11 "github.com/juju/errors" 12 "github.com/juju/naturalsort" 13 "github.com/juju/version" 14 "gopkg.in/juju/names.v2" 15 16 "github.com/juju/juju/apiserver/common" 17 "github.com/juju/juju/apiserver/facade" 18 "github.com/juju/juju/apiserver/params" 19 coremigration "github.com/juju/juju/core/migration" 20 coremodel "github.com/juju/juju/core/model" 21 "github.com/juju/juju/migration" 22 "github.com/juju/juju/state/watcher" 23 ) 24 25 // API implements the API required for the model migration 26 // master worker. 27 type API struct { 28 backend Backend 29 precheckBackend migration.PrecheckBackend 30 pool migration.Pool 31 authorizer facade.Authorizer 32 resources facade.Resources 33 presence facade.Presence 34 } 35 36 // NewAPI creates a new API server endpoint for the model migration 37 // master worker. 38 func NewAPI( 39 backend Backend, 40 precheckBackend migration.PrecheckBackend, 41 pool migration.Pool, 42 resources facade.Resources, 43 authorizer facade.Authorizer, 44 presence facade.Presence, 45 ) (*API, error) { 46 if !authorizer.AuthController() { 47 return nil, common.ErrPerm 48 } 49 return &API{ 50 backend: backend, 51 precheckBackend: precheckBackend, 52 pool: pool, 53 authorizer: authorizer, 54 resources: resources, 55 presence: presence, 56 }, nil 57 } 58 59 // Watch starts watching for an active migration for the model 60 // associated with the API connection. The returned id should be used 61 // with the NotifyWatcher facade to receive events. 62 func (api *API) Watch() params.NotifyWatchResult { 63 watch := api.backend.WatchForMigration() 64 if _, ok := <-watch.Changes(); ok { 65 return params.NotifyWatchResult{ 66 NotifyWatcherId: api.resources.Register(watch), 67 } 68 } 69 return params.NotifyWatchResult{ 70 Error: common.ServerError(watcher.EnsureErr(watch)), 71 } 72 } 73 74 // MigrationStatus returns the details and progress of the latest 75 // model migration. 76 func (api *API) MigrationStatus() (params.MasterMigrationStatus, error) { 77 empty := params.MasterMigrationStatus{} 78 79 mig, err := api.backend.LatestMigration() 80 if err != nil { 81 return empty, errors.Annotate(err, "retrieving model migration") 82 } 83 target, err := mig.TargetInfo() 84 if err != nil { 85 return empty, errors.Annotate(err, "retrieving target info") 86 } 87 phase, err := mig.Phase() 88 if err != nil { 89 return empty, errors.Annotate(err, "retrieving phase") 90 } 91 macsJSON, err := json.Marshal(target.Macaroons) 92 if err != nil { 93 return empty, errors.Annotate(err, "marshalling macaroons") 94 } 95 return params.MasterMigrationStatus{ 96 Spec: params.MigrationSpec{ 97 ModelTag: names.NewModelTag(mig.ModelUUID()).String(), 98 TargetInfo: params.MigrationTargetInfo{ 99 ControllerTag: target.ControllerTag.String(), 100 Addrs: target.Addrs, 101 CACert: target.CACert, 102 AuthTag: target.AuthTag.String(), 103 Password: target.Password, 104 Macaroons: string(macsJSON), 105 }, 106 }, 107 MigrationId: mig.Id(), 108 Phase: phase.String(), 109 PhaseChangedTime: mig.PhaseChangedTime(), 110 }, nil 111 } 112 113 // ModelInfo returns essential information about the model to be 114 // migrated. 115 func (api *API) ModelInfo() (params.MigrationModelInfo, error) { 116 empty := params.MigrationModelInfo{} 117 118 name, err := api.backend.ModelName() 119 if err != nil { 120 return empty, errors.Annotate(err, "retrieving model name") 121 } 122 123 owner, err := api.backend.ModelOwner() 124 if err != nil { 125 return empty, errors.Annotate(err, "retrieving model owner") 126 } 127 128 vers, err := api.backend.AgentVersion() 129 if err != nil { 130 return empty, errors.Annotate(err, "retrieving agent version") 131 } 132 133 return params.MigrationModelInfo{ 134 UUID: api.backend.ModelUUID(), 135 Name: name, 136 OwnerTag: owner.String(), 137 AgentVersion: vers, 138 }, nil 139 } 140 141 // SetPhase sets the phase of the active model migration. The provided 142 // phase must be a valid phase value, for example QUIESCE" or 143 // "ABORT". See the core/migration package for the complete list. 144 func (api *API) SetPhase(args params.SetMigrationPhaseArgs) error { 145 mig, err := api.backend.LatestMigration() 146 if err != nil { 147 return errors.Annotate(err, "could not get migration") 148 } 149 150 phase, ok := coremigration.ParsePhase(args.Phase) 151 if !ok { 152 return errors.Errorf("invalid phase: %q", args.Phase) 153 } 154 155 err = mig.SetPhase(phase) 156 return errors.Annotate(err, "failed to set phase") 157 } 158 159 // Prechecks performs pre-migration checks on the model and 160 // (source) controller. 161 func (api *API) Prechecks() error { 162 model, err := api.precheckBackend.Model() 163 if err != nil { 164 return errors.Annotate(err, "retrieving model") 165 } 166 backend, err := api.precheckBackend.ControllerBackend() 167 if err != nil { 168 return errors.Trace(err) 169 } 170 controllerModel, err := backend.Model() 171 if err != nil { 172 return errors.Trace(err) 173 } 174 return migration.SourcePrecheck( 175 api.precheckBackend, 176 api.presence.ModelPresence(model.UUID()), 177 api.presence.ModelPresence(controllerModel.UUID()), 178 ) 179 } 180 181 // SetStatusMessage sets a human readable status message containing 182 // information about the migration's progress. This will be shown in 183 // status output shown to the end user. 184 func (api *API) SetStatusMessage(args params.SetMigrationStatusMessageArgs) error { 185 mig, err := api.backend.LatestMigration() 186 if err != nil { 187 return errors.Annotate(err, "could not get migration") 188 } 189 err = mig.SetStatusMessage(args.Message) 190 return errors.Annotate(err, "failed to set status message") 191 } 192 193 // Export serializes the model associated with the API connection. 194 func (api *API) Export() (params.SerializedModel, error) { 195 var serialized params.SerializedModel 196 197 model, err := api.backend.Export() 198 if err != nil { 199 return serialized, err 200 } 201 202 bytes, err := description.Serialize(model) 203 if err != nil { 204 return serialized, err 205 } 206 serialized.Bytes = bytes 207 serialized.Charms = getUsedCharms(model) 208 serialized.Resources = getUsedResources(model) 209 if model.Type() == string(coremodel.IAAS) { 210 serialized.Tools = getUsedTools(model) 211 } 212 return serialized, nil 213 } 214 215 // Reap removes all documents for the model associated with the API 216 // connection. 217 func (api *API) Reap() error { 218 migration, err := api.backend.LatestMigration() 219 if err != nil { 220 return errors.Trace(err) 221 } 222 err = api.backend.RemoveExportingModelDocs() 223 if err != nil { 224 return errors.Trace(err) 225 } 226 // We need to mark the migration as complete here, since removing 227 // the model might kill the worker before it has a chance to set 228 // the phase itself. 229 return errors.Trace(migration.SetPhase(coremigration.DONE)) 230 } 231 232 // WatchMinionReports sets up a watcher which reports when a report 233 // for a migration minion has arrived. 234 func (api *API) WatchMinionReports() params.NotifyWatchResult { 235 mig, err := api.backend.LatestMigration() 236 if err != nil { 237 return params.NotifyWatchResult{Error: common.ServerError(err)} 238 } 239 240 watch, err := mig.WatchMinionReports() 241 if err != nil { 242 return params.NotifyWatchResult{Error: common.ServerError(err)} 243 } 244 245 if _, ok := <-watch.Changes(); ok { 246 return params.NotifyWatchResult{ 247 NotifyWatcherId: api.resources.Register(watch), 248 } 249 } 250 return params.NotifyWatchResult{ 251 Error: common.ServerError(watcher.EnsureErr(watch)), 252 } 253 } 254 255 // MinionReports returns details of the reports made by migration 256 // minions to the controller for the current migration phase. 257 func (api *API) MinionReports() (params.MinionReports, error) { 258 var out params.MinionReports 259 260 mig, err := api.backend.LatestMigration() 261 if err != nil { 262 return out, errors.Trace(err) 263 } 264 265 reports, err := mig.MinionReports() 266 if err != nil { 267 return out, errors.Trace(err) 268 } 269 270 out.MigrationId = mig.Id() 271 phase, err := mig.Phase() 272 if err != nil { 273 return out, errors.Trace(err) 274 } 275 out.Phase = phase.String() 276 277 out.SuccessCount = len(reports.Succeeded) 278 279 out.Failed = make([]string, len(reports.Failed)) 280 for i := 0; i < len(out.Failed); i++ { 281 out.Failed[i] = reports.Failed[i].String() 282 } 283 naturalsort.Sort(out.Failed) 284 285 out.UnknownCount = len(reports.Unknown) 286 287 unknown := make([]string, len(reports.Unknown)) 288 for i := 0; i < len(unknown); i++ { 289 unknown[i] = reports.Unknown[i].String() 290 } 291 naturalsort.Sort(unknown) 292 293 // Limit the number of unknowns reported 294 numSamples := out.UnknownCount 295 if numSamples > 10 { 296 numSamples = 10 297 } 298 out.UnknownSample = unknown[:numSamples] 299 300 return out, nil 301 } 302 303 func getUsedCharms(model description.Model) []string { 304 result := set.NewStrings() 305 for _, application := range model.Applications() { 306 result.Add(application.CharmURL()) 307 } 308 return result.Values() 309 } 310 311 func getUsedTools(model description.Model) []params.SerializedModelTools { 312 // Iterate through the model for all tools, and make a map of them. 313 usedVersions := make(map[version.Binary]bool) 314 // It is most likely that the preconditions will limit the number of 315 // tools versions in use, but that is not relied on here. 316 for _, machine := range model.Machines() { 317 addToolsVersionForMachine(machine, usedVersions) 318 } 319 320 for _, application := range model.Applications() { 321 for _, unit := range application.Units() { 322 tools := unit.Tools() 323 usedVersions[tools.Version()] = true 324 } 325 } 326 327 out := make([]params.SerializedModelTools, 0, len(usedVersions)) 328 for v := range usedVersions { 329 out = append(out, params.SerializedModelTools{ 330 Version: v.String(), 331 URI: common.ToolsURL("", v), 332 }) 333 } 334 return out 335 } 336 337 func addToolsVersionForMachine(machine description.Machine, usedVersions map[version.Binary]bool) { 338 tools := machine.Tools() 339 usedVersions[tools.Version()] = true 340 for _, container := range machine.Containers() { 341 addToolsVersionForMachine(container, usedVersions) 342 } 343 } 344 345 func getUsedResources(model description.Model) []params.SerializedModelResource { 346 var out []params.SerializedModelResource 347 for _, app := range model.Applications() { 348 for _, resource := range app.Resources() { 349 outRes := resourceToSerialized(app.Name(), resource) 350 351 // Hunt through the application's units and look for 352 // revisions of this resource. This is particularly 353 // efficient or clever but will be fine even with 1000's 354 // of units and 10's of resources. 355 outRes.UnitRevisions = make(map[string]params.SerializedModelResourceRevision) 356 for _, unit := range app.Units() { 357 for _, unitResource := range unit.Resources() { 358 if unitResource.Name() == resource.Name() { 359 outRes.UnitRevisions[unit.Name()] = revisionToSerialized(unitResource.Revision()) 360 } 361 } 362 } 363 364 out = append(out, outRes) 365 } 366 367 } 368 return out 369 } 370 371 func resourceToSerialized(app string, desc description.Resource) params.SerializedModelResource { 372 return params.SerializedModelResource{ 373 Application: app, 374 Name: desc.Name(), 375 ApplicationRevision: revisionToSerialized(desc.ApplicationRevision()), 376 CharmStoreRevision: revisionToSerialized(desc.CharmStoreRevision()), 377 } 378 } 379 380 func revisionToSerialized(rr description.ResourceRevision) params.SerializedModelResourceRevision { 381 if rr == nil { 382 return params.SerializedModelResourceRevision{} 383 } 384 return params.SerializedModelResourceRevision{ 385 Revision: rr.Revision(), 386 Type: rr.Type(), 387 Path: rr.Path(), 388 Description: rr.Description(), 389 Origin: rr.Origin(), 390 FingerprintHex: rr.FingerprintHex(), 391 Size: rr.Size(), 392 Timestamp: rr.Timestamp(), 393 Username: rr.Username(), 394 } 395 }