Skip to content

Commit

Permalink
Merge pull request juju#6950 from babbageclunk/mm-adopt-resources-api…
Browse files Browse the repository at this point in the history
…server

migrations: Add AdoptResources to the migrationtarget facade

## Description of change
This updates the provider tags for all cloud resources to indicate the
new controller uuid, so that they won't be cleaned up if the old
controller is destroyed. It will be called from the migrationmaster
worker when the migration is complete.

## QA steps

No QA steps yet - the code to call this during migration will be in a 
subsequent PR.

## Bug reference
Part of the fix for https://bugs.launchpad.net/juju/+bug/1648063
  • Loading branch information
jujubot authored Feb 9, 2017
2 parents fed3d0c + a6f226b commit 882082d
Show file tree
Hide file tree
Showing 4 changed files with 113 additions and 23 deletions.
3 changes: 2 additions & 1 deletion apiserver/facade/facadetest/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ type Context struct {
Dispose_ func()
Resources_ facade.Resources
State_ *state.State
StatePool_ *state.StatePool
ID_ string
// Identity is not part of the facade.Context interface, but is instead
// used to make sure that the context objects are the same.
Expand Down Expand Up @@ -48,7 +49,7 @@ func (context Context) State() *state.State {

// StatePool is part of of the facade.Context interface.
func (context Context) StatePool() *state.StatePool {
return state.NewStatePool(context.State_)
return context.StatePool_
}

// ID is part of the facade.Context interface.
Expand Down
60 changes: 46 additions & 14 deletions apiserver/migrationtarget/migrationtarget.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,15 @@ import (
"github.com/juju/juju/apiserver/facade"
"github.com/juju/juju/apiserver/params"
coremigration "github.com/juju/juju/core/migration"
"github.com/juju/juju/environs"
"github.com/juju/juju/migration"
"github.com/juju/juju/permission"
"github.com/juju/juju/state"
"github.com/juju/juju/state/stateenvirons"
)

func init() {
common.RegisterStandardFacade("MigrationTarget", 1, NewAPI)
common.RegisterStandardFacade("MigrationTarget", 1, newAPIWithRealEnviron)
}

// API implements the API required for the model migration
Expand All @@ -28,24 +30,33 @@ type API struct {
state *state.State
authorizer facade.Authorizer
resources facade.Resources
pool *state.StatePool
getEnviron stateenvirons.NewEnvironFunc
}

// NewAPI returns a new API.
func NewAPI(
st *state.State,
resources facade.Resources,
authorizer facade.Authorizer,
) (*API, error) {
if err := checkAuth(authorizer, st); err != nil {
// NewAPI returns a new API. Accepts a NewEnvironFunc for testing
// purposes.
func NewAPI(ctx facade.Context, getEnviron stateenvirons.NewEnvironFunc) (*API, error) {
auth := ctx.Auth()
st := ctx.State()
if err := checkAuth(auth, st); err != nil {
return nil, errors.Trace(err)
}
return &API{
state: st,
authorizer: authorizer,
resources: resources,
authorizer: auth,
resources: ctx.Resources(),
pool: ctx.StatePool(),
getEnviron: getEnviron,
}, nil
}

// newAPIWithRealEnviron creates an API with a real environ factory
// function.
func newAPIWithRealEnviron(ctx facade.Context) (*API, error) {
return NewAPI(ctx, stateenvirons.GetNewEnvironFunc(environs.New))
}

func checkAuth(authorizer facade.Authorizer, st *state.State) error {
if !authorizer.AuthClient() {
return errors.Trace(common.ErrPerm)
Expand Down Expand Up @@ -97,8 +108,8 @@ func (api *API) Import(serialized params.SerializedModel) error {
return err
}

func (api *API) getModel(args params.ModelArgs) (*state.Model, error) {
tag, err := names.ParseModelTag(args.ModelTag)
func (api *API) getModel(modelTag string) (*state.Model, error) {
tag, err := names.ParseModelTag(modelTag)
if err != nil {
return nil, errors.Trace(err)
}
Expand All @@ -110,7 +121,7 @@ func (api *API) getModel(args params.ModelArgs) (*state.Model, error) {
}

func (api *API) getImportingModel(args params.ModelArgs) (*state.Model, error) {
model, err := api.getModel(args)
model, err := api.getModel(args.ModelTag)
if err != nil {
return nil, errors.Trace(err)
}
Expand Down Expand Up @@ -167,7 +178,7 @@ func (api *API) Activate(args params.ModelArgs) error {
//
// Returns the zero time if no logs have been transferred.
func (api *API) LatestLogTime(args params.ModelArgs) (time.Time, error) {
model, err := api.getModel(args)
model, err := api.getModel(args.ModelTag)
if err != nil {
return time.Time{}, errors.Trace(err)
}
Expand All @@ -182,3 +193,24 @@ func (api *API) LatestLogTime(args params.ModelArgs) (time.Time, error) {
}
return time.Unix(0, timestamp).In(time.UTC), nil
}

// AdoptResources asks the cloud provider to update the controller
// tags for a model's resources. This prevents the resources from
// being destroyed if the source controller is destroyed after the
// model is migrated away.
func (api *API) AdoptResources(args params.AdoptResourcesArgs) error {
model, err := api.getModel(args.ModelTag)
if err != nil {
return errors.Trace(err)
}
st, release, err := api.pool.Get(model.UUID())
if err != nil {
return errors.Trace(err)
}
defer release()
env, err := api.getEnviron(st)
if err != nil {
return errors.Trace(err)
}
return errors.Trace(env.AdoptResources(model.ControllerUUID(), args.SourceControllerVersion))
}
58 changes: 50 additions & 8 deletions apiserver/migrationtarget/migrationtarget_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"time"

"github.com/juju/errors"
"github.com/juju/testing"
jc "github.com/juju/testing/checkers"
"github.com/juju/utils"
"github.com/juju/version"
Expand All @@ -19,10 +20,12 @@ import (
"github.com/juju/juju/apiserver/params"
apiservertesting "github.com/juju/juju/apiserver/testing"
"github.com/juju/juju/core/description"
"github.com/juju/juju/environs"
"github.com/juju/juju/provider/dummy"
"github.com/juju/juju/state"
"github.com/juju/juju/state/stateenvirons"
statetesting "github.com/juju/juju/state/testing"
"github.com/juju/juju/testing"
jujutesting "github.com/juju/juju/testing"
)

type Suite struct {
Expand All @@ -36,7 +39,7 @@ var _ = gc.Suite(&Suite{})
func (s *Suite) SetUpTest(c *gc.C) {
// Set up InitialConfig with a dummy provider configuration. This
// is required to allow model import test to work.
s.InitialConfig = testing.CustomModelConfig(c, dummy.SampleConfig())
s.InitialConfig = jujutesting.CustomModelConfig(c, dummy.SampleConfig())

// The call up to StateSuite's SetUpTest uses s.InitialConfig so
// it has to happen here.
Expand All @@ -55,7 +58,7 @@ func (s *Suite) TestFacadeRegistered(c *gc.C) {
factory, err := common.Facades.GetFactory("MigrationTarget", 1)
c.Assert(err, jc.ErrorIsNil)

api, err := factory(facadetest.Context{
api, err := factory(&facadetest.Context{
State_: s.State,
Resources_: s.resources,
Auth_: s.authorizer,
Expand All @@ -66,13 +69,13 @@ func (s *Suite) TestFacadeRegistered(c *gc.C) {

func (s *Suite) TestNotUser(c *gc.C) {
s.authorizer.Tag = names.NewMachineTag("0")
_, err := s.newAPI()
_, _, err := s.newAPI(nil)
c.Assert(errors.Cause(err), gc.Equals, common.ErrPerm)
}

func (s *Suite) TestNotControllerAdmin(c *gc.C) {
s.authorizer.Tag = names.NewUserTag("jrandomuser")
_, err := s.newAPI()
_, _, err := s.newAPI(nil)
c.Assert(errors.Cause(err), gc.Equals, common.ErrPerm)
}

Expand Down Expand Up @@ -224,12 +227,41 @@ func (s *Suite) TestLatestLogTimeNeverSet(c *gc.C) {
c.Assert(latest, gc.Equals, time.Time{})
}

func (s *Suite) newAPI() (*migrationtarget.API, error) {
return migrationtarget.NewAPI(s.State, s.resources, s.authorizer)
func (s *Suite) TestAdoptResources(c *gc.C) {
st := s.Factory.MakeModel(c, nil)
defer st.Close()

env := mockEnviron{Stub: &testing.Stub{}}
api, ctx, err := s.newAPI(func(envSt *state.State) (environs.Environ, error) {
c.Assert(envSt.ModelUUID(), gc.Equals, st.ModelUUID())
return &env, nil
})
c.Assert(err, jc.ErrorIsNil)
defer ctx.StatePool().Close()

err = api.AdoptResources(params.AdoptResourcesArgs{
ModelTag: st.ModelTag().String(),
SourceControllerVersion: version.MustParse("3.2.1"),
})
c.Assert(err, jc.ErrorIsNil)

c.Assert(env.Stub.Calls(), gc.HasLen, 1)
env.Stub.CheckCall(c, 0, "AdoptResources", st.ControllerUUID(), version.MustParse("3.2.1"))
}

func (s *Suite) newAPI(environFunc stateenvirons.NewEnvironFunc) (*migrationtarget.API, *facadetest.Context, error) {
ctx := facadetest.Context{
State_: s.State,
Resources_: s.resources,
Auth_: s.authorizer,
StatePool_: state.NewStatePool(s.State),
}
api, err := migrationtarget.NewAPI(ctx, environFunc)
return api, &ctx, err
}

func (s *Suite) mustNewAPI(c *gc.C) *migrationtarget.API {
api, err := s.newAPI()
api, _, err := s.newAPI(nil)
c.Assert(err, jc.ErrorIsNil)
return api
}
Expand All @@ -256,3 +288,13 @@ func (s *Suite) controllerVersion(c *gc.C) version.Number {
c.Assert(ok, jc.IsTrue)
return vers
}

type mockEnviron struct {
environs.Environ
*testing.Stub
}

func (e *mockEnviron) AdoptResources(controllerUUID string, sourceVersion version.Number) error {
e.MethodCall(e, "AdoptResources", controllerUUID, sourceVersion)
return e.NextErr()
}
15 changes: 15 additions & 0 deletions apiserver/params/migration.go
Original file line number Diff line number Diff line change
Expand Up @@ -209,3 +209,18 @@ type MinionReports struct {
// failed to complete a given migration phase.
Failed []string `json:"failed"`
}

// AdoptResourcesArgs holds the information required to ask the
// provider to update the controller tags for a model's
// resources.
type AdoptResourcesArgs struct {
// ModelTag identifies the model that owns the resources.
ModelTag string `json:"model-tag"`

// SourceControllerVersion indicates the version of the calling
// controller. This is needed in case the way the resources are
// tagged has changed between versions - the provider should
// ensure it looks for the original tags in the correct format for
// that version.
SourceControllerVersion version.Number `json:"source-controller-version"`
}

0 comments on commit 882082d

Please sign in to comment.