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  }