github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/apiserver/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/errors" 10 "github.com/juju/utils" 11 "github.com/juju/utils/set" 12 "github.com/juju/version" 13 "gopkg.in/juju/names.v2" 14 15 "github.com/juju/juju/apiserver/common" 16 "github.com/juju/juju/apiserver/facade" 17 "github.com/juju/juju/apiserver/params" 18 "github.com/juju/juju/core/description" 19 coremigration "github.com/juju/juju/core/migration" 20 "github.com/juju/juju/migration" 21 "github.com/juju/juju/state/watcher" 22 ) 23 24 func init() { 25 common.RegisterStandardFacade("MigrationMaster", 1, newAPIForRegistration) 26 } 27 28 // API implements the API required for the model migration 29 // master worker. 30 type API struct { 31 backend Backend 32 precheckBackend migration.PrecheckBackend 33 authorizer facade.Authorizer 34 resources facade.Resources 35 } 36 37 // NewAPI creates a new API server endpoint for the model migration 38 // master worker. 39 func NewAPI( 40 backend Backend, 41 precheckBackend migration.PrecheckBackend, 42 resources facade.Resources, 43 authorizer facade.Authorizer, 44 ) (*API, error) { 45 if !authorizer.AuthModelManager() { 46 return nil, common.ErrPerm 47 } 48 return &API{ 49 backend: backend, 50 precheckBackend: precheckBackend, 51 authorizer: authorizer, 52 resources: resources, 53 }, nil 54 } 55 56 // Watch starts watching for an active migration for the model 57 // associated with the API connection. The returned id should be used 58 // with the NotifyWatcher facade to receive events. 59 func (api *API) Watch() params.NotifyWatchResult { 60 watch := api.backend.WatchForMigration() 61 if _, ok := <-watch.Changes(); ok { 62 return params.NotifyWatchResult{ 63 NotifyWatcherId: api.resources.Register(watch), 64 } 65 } 66 return params.NotifyWatchResult{ 67 Error: common.ServerError(watcher.EnsureErr(watch)), 68 } 69 } 70 71 // MigrationStatus returns the details and progress of the latest 72 // model migration. 73 func (api *API) MigrationStatus() (params.MasterMigrationStatus, error) { 74 empty := params.MasterMigrationStatus{} 75 76 mig, err := api.backend.LatestMigration() 77 if err != nil { 78 return empty, errors.Annotate(err, "retrieving model migration") 79 } 80 target, err := mig.TargetInfo() 81 if err != nil { 82 return empty, errors.Annotate(err, "retrieving target info") 83 } 84 phase, err := mig.Phase() 85 if err != nil { 86 return empty, errors.Annotate(err, "retrieving phase") 87 } 88 macsJSON, err := json.Marshal(target.Macaroons) 89 if err != nil { 90 return empty, errors.Annotate(err, "marshalling macaroons") 91 } 92 return params.MasterMigrationStatus{ 93 Spec: params.MigrationSpec{ 94 ModelTag: names.NewModelTag(mig.ModelUUID()).String(), 95 TargetInfo: params.MigrationTargetInfo{ 96 ControllerTag: target.ControllerTag.String(), 97 Addrs: target.Addrs, 98 CACert: target.CACert, 99 AuthTag: target.AuthTag.String(), 100 Password: target.Password, 101 Macaroons: string(macsJSON), 102 }, 103 ExternalControl: mig.ExternalControl(), 104 }, 105 MigrationId: mig.Id(), 106 Phase: phase.String(), 107 PhaseChangedTime: mig.PhaseChangedTime(), 108 }, nil 109 } 110 111 // ModelInfo returns essential information about the model to be 112 // migrated. 113 func (api *API) ModelInfo() (params.MigrationModelInfo, error) { 114 empty := params.MigrationModelInfo{} 115 116 name, err := api.backend.ModelName() 117 if err != nil { 118 return empty, errors.Annotate(err, "retrieving model name") 119 } 120 121 owner, err := api.backend.ModelOwner() 122 if err != nil { 123 return empty, errors.Annotate(err, "retrieving model owner") 124 } 125 126 vers, err := api.backend.AgentVersion() 127 if err != nil { 128 return empty, errors.Annotate(err, "retrieving agent version") 129 } 130 131 return params.MigrationModelInfo{ 132 UUID: api.backend.ModelUUID(), 133 Name: name, 134 OwnerTag: owner.String(), 135 AgentVersion: vers, 136 }, nil 137 } 138 139 // SetPhase sets the phase of the active model migration. The provided 140 // phase must be a valid phase value, for example QUIESCE" or 141 // "ABORT". See the core/migration package for the complete list. 142 func (api *API) SetPhase(args params.SetMigrationPhaseArgs) error { 143 mig, err := api.backend.LatestMigration() 144 if err != nil { 145 return errors.Annotate(err, "could not get migration") 146 } 147 148 phase, ok := coremigration.ParsePhase(args.Phase) 149 if !ok { 150 return errors.Errorf("invalid phase: %q", args.Phase) 151 } 152 153 err = mig.SetPhase(phase) 154 return errors.Annotate(err, "failed to set phase") 155 } 156 157 // Prechecks performs pre-migration checks on the model and 158 // (source) controller. 159 func (api *API) Prechecks() error { 160 return migration.SourcePrecheck(api.precheckBackend) 161 } 162 163 // SetStatusMessage sets a human readable status message containing 164 // information about the migration's progress. This will be shown in 165 // status output shown to the end user. 166 func (api *API) SetStatusMessage(args params.SetMigrationStatusMessageArgs) error { 167 mig, err := api.backend.LatestMigration() 168 if err != nil { 169 return errors.Annotate(err, "could not get migration") 170 } 171 err = mig.SetStatusMessage(args.Message) 172 return errors.Annotate(err, "failed to set status message") 173 } 174 175 // Export serializes the model associated with the API connection. 176 func (api *API) Export() (params.SerializedModel, error) { 177 var serialized params.SerializedModel 178 179 model, err := api.backend.Export() 180 if err != nil { 181 return serialized, err 182 } 183 184 bytes, err := description.Serialize(model) 185 if err != nil { 186 return serialized, err 187 } 188 serialized.Bytes = bytes 189 serialized.Charms = getUsedCharms(model) 190 serialized.Tools = getUsedTools(model) 191 return serialized, nil 192 } 193 194 // Reap removes all documents for the model associated with the API 195 // connection. 196 func (api *API) Reap() error { 197 return api.backend.RemoveExportingModelDocs() 198 } 199 200 // WatchMinionReports sets up a watcher which reports when a report 201 // for a migration minion has arrived. 202 func (api *API) WatchMinionReports() params.NotifyWatchResult { 203 mig, err := api.backend.LatestMigration() 204 if err != nil { 205 return params.NotifyWatchResult{Error: common.ServerError(err)} 206 } 207 208 watch, err := mig.WatchMinionReports() 209 if err != nil { 210 return params.NotifyWatchResult{Error: common.ServerError(err)} 211 } 212 213 if _, ok := <-watch.Changes(); ok { 214 return params.NotifyWatchResult{ 215 NotifyWatcherId: api.resources.Register(watch), 216 } 217 } 218 return params.NotifyWatchResult{ 219 Error: common.ServerError(watcher.EnsureErr(watch)), 220 } 221 } 222 223 // MinionReports returns details of the reports made by migration 224 // minions to the controller for the current migration phase. 225 func (api *API) MinionReports() (params.MinionReports, error) { 226 var out params.MinionReports 227 228 mig, err := api.backend.LatestMigration() 229 if err != nil { 230 return out, errors.Trace(err) 231 } 232 233 reports, err := mig.MinionReports() 234 if err != nil { 235 return out, errors.Trace(err) 236 } 237 238 out.MigrationId = mig.Id() 239 phase, err := mig.Phase() 240 if err != nil { 241 return out, errors.Trace(err) 242 } 243 out.Phase = phase.String() 244 245 out.SuccessCount = len(reports.Succeeded) 246 247 out.Failed = make([]string, len(reports.Failed)) 248 for i := 0; i < len(out.Failed); i++ { 249 out.Failed[i] = reports.Failed[i].String() 250 } 251 utils.SortStringsNaturally(out.Failed) 252 253 out.UnknownCount = len(reports.Unknown) 254 255 unknown := make([]string, len(reports.Unknown)) 256 for i := 0; i < len(unknown); i++ { 257 unknown[i] = reports.Unknown[i].String() 258 } 259 utils.SortStringsNaturally(unknown) 260 261 // Limit the number of unknowns reported 262 numSamples := out.UnknownCount 263 if numSamples > 10 { 264 numSamples = 10 265 } 266 out.UnknownSample = unknown[:numSamples] 267 268 return out, nil 269 } 270 271 func getUsedCharms(model description.Model) []string { 272 result := set.NewStrings() 273 for _, application := range model.Applications() { 274 result.Add(application.CharmURL()) 275 } 276 return result.Values() 277 } 278 279 func getUsedTools(model description.Model) []params.SerializedModelTools { 280 // Iterate through the model for all tools, and make a map of them. 281 usedVersions := make(map[version.Binary]bool) 282 // It is most likely that the preconditions will limit the number of 283 // tools versions in use, but that is not relied on here. 284 for _, machine := range model.Machines() { 285 addToolsVersionForMachine(machine, usedVersions) 286 } 287 288 for _, application := range model.Applications() { 289 for _, unit := range application.Units() { 290 tools := unit.Tools() 291 usedVersions[tools.Version()] = true 292 } 293 } 294 295 out := make([]params.SerializedModelTools, 0, len(usedVersions)) 296 for v := range usedVersions { 297 out = append(out, params.SerializedModelTools{ 298 Version: v.String(), 299 URI: common.ToolsURL("", v), 300 }) 301 } 302 return out 303 } 304 305 func addToolsVersionForMachine(machine description.Machine, usedVersions map[version.Binary]bool) { 306 tools := machine.Tools() 307 usedVersions[tools.Version()] = true 308 for _, container := range machine.Containers() { 309 addToolsVersionForMachine(container, usedVersions) 310 } 311 }