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 }