github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/apiserver/facades/controller/migrationtarget/migrationtarget.go (about)

     1  // Copyright 2016 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package migrationtarget
     5  
     6  import (
     7  	"time"
     8  
     9  	"github.com/juju/errors"
    10  	"gopkg.in/juju/names.v2"
    11  
    12  	"github.com/juju/juju/apiserver/common"
    13  	"github.com/juju/juju/apiserver/common/credentialcommon"
    14  	"github.com/juju/juju/apiserver/facade"
    15  	"github.com/juju/juju/apiserver/params"
    16  	"github.com/juju/juju/caas"
    17  	coremigration "github.com/juju/juju/core/migration"
    18  	"github.com/juju/juju/core/status"
    19  	"github.com/juju/juju/environs"
    20  	"github.com/juju/juju/environs/context"
    21  	"github.com/juju/juju/migration"
    22  	"github.com/juju/juju/permission"
    23  	"github.com/juju/juju/state"
    24  	"github.com/juju/juju/state/stateenvirons"
    25  )
    26  
    27  // API implements the API required for the model migration
    28  // master worker when communicating with the target controller.
    29  type API struct {
    30  	state         *state.State
    31  	pool          *state.StatePool
    32  	authorizer    facade.Authorizer
    33  	resources     facade.Resources
    34  	presence      facade.Presence
    35  	getClaimer    migration.ClaimerFunc
    36  	getEnviron    stateenvirons.NewEnvironFunc
    37  	getCAASBroker stateenvirons.NewCAASBrokerFunc
    38  	callContext   context.ProviderCallContext
    39  }
    40  
    41  // NewFacade is used for API registration.
    42  func NewFacade(ctx facade.Context) (*API, error) {
    43  	return NewAPI(
    44  		ctx,
    45  		stateenvirons.GetNewEnvironFunc(environs.New),
    46  		stateenvirons.GetNewCAASBrokerFunc(caas.New),
    47  		state.CallContext(ctx.State()))
    48  }
    49  
    50  // NewAPI returns a new API. Accepts a NewEnvironFunc and context.ProviderCallContext
    51  // for testing purposes.
    52  func NewAPI(ctx facade.Context, getEnviron stateenvirons.NewEnvironFunc, getCAASBroker stateenvirons.NewCAASBrokerFunc, callCtx context.ProviderCallContext) (*API, error) {
    53  	auth := ctx.Auth()
    54  	st := ctx.State()
    55  	if err := checkAuth(auth, st); err != nil {
    56  		return nil, errors.Trace(err)
    57  	}
    58  	return &API{
    59  		state:         st,
    60  		pool:          ctx.StatePool(),
    61  		authorizer:    auth,
    62  		resources:     ctx.Resources(),
    63  		presence:      ctx.Presence(),
    64  		getClaimer:    ctx.LeadershipClaimer,
    65  		getEnviron:    getEnviron,
    66  		getCAASBroker: getCAASBroker,
    67  		callContext:   callCtx,
    68  	}, nil
    69  }
    70  
    71  func checkAuth(authorizer facade.Authorizer, st *state.State) error {
    72  	if !authorizer.AuthClient() {
    73  		return errors.Trace(common.ErrPerm)
    74  	}
    75  
    76  	if isAdmin, err := authorizer.HasPermission(permission.SuperuserAccess, st.ControllerTag()); err != nil {
    77  		return errors.Trace(err)
    78  	} else if !isAdmin {
    79  		// The entire facade is only accessible to controller administrators.
    80  		return errors.Trace(common.ErrPerm)
    81  	}
    82  	return nil
    83  }
    84  
    85  // Prechecks ensure that the target controller is ready to accept a
    86  // model migration.
    87  func (api *API) Prechecks(model params.MigrationModelInfo) error {
    88  	ownerTag, err := names.ParseUserTag(model.OwnerTag)
    89  	if err != nil {
    90  		return errors.Trace(err)
    91  	}
    92  	controllerState := api.pool.SystemState()
    93  	// NOTE (thumper): it isn't clear to me why api.state would be different
    94  	// from the controllerState as I had thought that the Precheck call was
    95  	// on the controller model, in which case it should be the same as the
    96  	// controllerState.
    97  	backend, err := migration.PrecheckShim(api.state, controllerState)
    98  	if err != nil {
    99  		return errors.Annotate(err, "creating backend")
   100  	}
   101  	return migration.TargetPrecheck(
   102  		backend,
   103  		migration.PoolShim(api.pool),
   104  		coremigration.ModelInfo{
   105  			UUID:                   model.UUID,
   106  			Name:                   model.Name,
   107  			Owner:                  ownerTag,
   108  			AgentVersion:           model.AgentVersion,
   109  			ControllerAgentVersion: model.ControllerAgentVersion,
   110  		},
   111  		api.presence.ModelPresence(controllerState.ModelUUID()),
   112  	)
   113  }
   114  
   115  // Import takes a serialized Juju model, deserializes it, and
   116  // recreates it in the receiving controller.
   117  func (api *API) Import(serialized params.SerializedModel) error {
   118  	controller := state.NewController(api.pool)
   119  	_, st, err := migration.ImportModel(controller, api.getClaimer, serialized.Bytes)
   120  	if err != nil {
   121  		return err
   122  	}
   123  	defer st.Close()
   124  	// TODO(mjs) - post import checks
   125  	// NOTE(fwereade) - checks here would be sensible, but we will
   126  	// also need to check after the binaries are imported too.
   127  	return err
   128  }
   129  
   130  func (api *API) getModel(modelTag string) (*state.Model, func(), error) {
   131  	tag, err := names.ParseModelTag(modelTag)
   132  	if err != nil {
   133  		return nil, nil, errors.Trace(err)
   134  	}
   135  	model, ph, err := api.pool.GetModel(tag.Id())
   136  	if err != nil {
   137  		return nil, nil, errors.Trace(err)
   138  	}
   139  	return model, func() { ph.Release() }, nil
   140  }
   141  
   142  func (api *API) getImportingModel(args params.ModelArgs) (*state.Model, func(), error) {
   143  	model, release, err := api.getModel(args.ModelTag)
   144  	if err != nil {
   145  		return nil, nil, errors.Trace(err)
   146  	}
   147  	if model.MigrationMode() != state.MigrationModeImporting {
   148  		release()
   149  		return nil, nil, errors.New("migration mode for the model is not importing")
   150  	}
   151  	return model, release, nil
   152  }
   153  
   154  // Abort removes the specified model from the database. It is an error to
   155  // attempt to Abort a model that has a migration mode other than importing.
   156  func (api *API) Abort(args params.ModelArgs) error {
   157  	model, releaseModel, err := api.getImportingModel(args)
   158  	if err != nil {
   159  		return errors.Trace(err)
   160  	}
   161  	defer releaseModel()
   162  
   163  	st, err := api.pool.Get(model.UUID())
   164  	if err != nil {
   165  		return errors.Trace(err)
   166  	}
   167  	defer st.Release()
   168  	return st.RemoveImportingModelDocs()
   169  }
   170  
   171  // Activate sets the migration mode of the model to "none", meaning it
   172  // is ready for use. It is an error to attempt to Abort a model that
   173  // has a migration mode other than importing.
   174  func (api *API) Activate(args params.ModelArgs) error {
   175  	model, release, err := api.getImportingModel(args)
   176  	if err != nil {
   177  		return errors.Trace(err)
   178  	}
   179  	defer release()
   180  
   181  	if err := model.SetStatus(status.StatusInfo{Status: status.Available}); err != nil {
   182  		return errors.Trace(err)
   183  	}
   184  
   185  	// TODO(fwereade) - need to validate binaries here.
   186  	return model.SetMigrationMode(state.MigrationModeNone)
   187  }
   188  
   189  // LatestLogTime returns the time of the most recent log record
   190  // received by the logtransfer endpoint. This can be used as the start
   191  // point for streaming logs from the source if the transfer was
   192  // interrupted.
   193  //
   194  // For performance reasons, not every time is tracked, so if the
   195  // target controller died during the transfer the latest log time
   196  // might be up to 2 minutes earlier. If the transfer was interrupted
   197  // in some other way (like the source controller going away or a
   198  // network partition) the time will be up-to-date.
   199  //
   200  // Log messages are assumed to be sent in time order (which is how
   201  // debug-log emits them). If that isn't the case then this mechanism
   202  // can't be used to avoid duplicates when logtransfer is restarted.
   203  //
   204  // Returns the zero time if no logs have been transferred.
   205  func (api *API) LatestLogTime(args params.ModelArgs) (time.Time, error) {
   206  	model, release, err := api.getModel(args.ModelTag)
   207  	if err != nil {
   208  		return time.Time{}, errors.Trace(err)
   209  	}
   210  	defer release()
   211  
   212  	tracker := state.NewLastSentLogTracker(api.state, model.UUID(), "migration-logtransfer")
   213  	defer tracker.Close()
   214  	_, timestamp, err := tracker.Get()
   215  	if errors.Cause(err) == state.ErrNeverForwarded {
   216  		return time.Time{}, nil
   217  	}
   218  	if err != nil {
   219  		return time.Time{}, errors.Trace(err)
   220  	}
   221  	return time.Unix(0, timestamp).In(time.UTC), nil
   222  }
   223  
   224  // AdoptResources asks the cloud provider to update the controller
   225  // tags for a model's resources. This prevents the resources from
   226  // being destroyed if the source controller is destroyed after the
   227  // model is migrated away.
   228  func (api *API) AdoptResources(args params.AdoptResourcesArgs) error {
   229  	tag, err := names.ParseModelTag(args.ModelTag)
   230  	if err != nil {
   231  		return errors.Trace(err)
   232  	}
   233  	st, err := api.pool.Get(tag.Id())
   234  	if err != nil {
   235  		return errors.Trace(err)
   236  	}
   237  	defer st.Release()
   238  
   239  	m, err := st.Model()
   240  	if err != nil {
   241  		return errors.Trace(err)
   242  	}
   243  
   244  	var ra environs.ResourceAdopter
   245  	if m.Type() == state.ModelTypeCAAS {
   246  		ra, err = api.getCAASBroker(st.State)
   247  	} else {
   248  		ra, err = api.getEnviron(st.State)
   249  	}
   250  	if err != nil {
   251  		return errors.Trace(err)
   252  	}
   253  
   254  	return errors.Trace(ra.AdoptResources(api.callContext, st.ControllerUUID(), args.SourceControllerVersion))
   255  }
   256  
   257  // CheckMachines compares the machines in state with the ones reported
   258  // by the provider and reports any discrepancies.
   259  func (api *API) CheckMachines(args params.ModelArgs) (params.ErrorResults, error) {
   260  	tag, err := names.ParseModelTag(args.ModelTag)
   261  	if err != nil {
   262  		return params.ErrorResults{}, errors.Trace(err)
   263  	}
   264  	st, err := api.pool.Get(tag.Id())
   265  	if err != nil {
   266  		return params.ErrorResults{}, errors.Trace(err)
   267  	}
   268  	defer st.Release()
   269  
   270  	return credentialcommon.ValidateExistingModelCredential(
   271  		credentialcommon.NewPersistentBackend(st.State),
   272  		api.callContext,
   273  	)
   274  }
   275  
   276  // CACert returns the certificate used to validate the state connection.
   277  func (api *API) CACert() (params.BytesResult, error) {
   278  	cfg, err := api.state.ControllerConfig()
   279  	if err != nil {
   280  		return params.BytesResult{}, errors.Trace(err)
   281  	}
   282  	caCert, _ := cfg.CACert()
   283  	return params.BytesResult{Result: []byte(caCert)}, nil
   284  }