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  }