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