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  }