Skip to content

Commit

Permalink
cmr saas migration: add step for finding the remote model if it was on
Browse files Browse the repository at this point in the history
the same controller.

This pr enables us to update the externalcontroller collection if a
model of the same controller has been moved.
  • Loading branch information
nam committed Dec 3, 2019
1 parent f0871db commit cc3679e
Show file tree
Hide file tree
Showing 12 changed files with 128 additions and 69 deletions.
11 changes: 1 addition & 10 deletions api/state_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -170,16 +170,7 @@ func (s *stateSuite) TestLoginToMigratedModel(c *gc.C) {
},
})
c.Assert(err, jc.ErrorIsNil)
phases := []migration.Phase{
migration.IMPORT,
migration.PROCESSRELATIONS,
migration.VALIDATION,
migration.SUCCESS,
migration.LOGTRANSFER,
migration.REAP,
migration.DONE,
}
for _, phase := range phases {
for _, phase := range migration.SuccessfulMigrationPhases() {
c.Assert(mig.SetPhase(phase), jc.ErrorIsNil)
}
c.Assert(model.Destroy(state.DestroyModelParams{}), jc.ErrorIsNil)
Expand Down
2 changes: 1 addition & 1 deletion apiserver/admin.go
Original file line number Diff line number Diff line change
Expand Up @@ -349,7 +349,7 @@ func (a *admin) maybeEmitRedirectError(modelUUID string, authTag names.Tag) erro
// Check if the model was not found because it was migrated to another
// controller. If that is the case and the user was able to access it
// before the migration return back a RedirectError.
mig, err := st.LatestRemovedModelMigration()
mig, err := st.CompletedMigrationForModel()
if err != nil && !errors.IsNotFound(err) {
return errors.Trace(err)
}
Expand Down
11 changes: 1 addition & 10 deletions apiserver/admin_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -770,16 +770,7 @@ func (s *loginSuite) TestMigratedModelLogin(c *gc.C) {
},
})
c.Assert(err, jc.ErrorIsNil)
phases := []migration.Phase{
migration.IMPORT,
migration.PROCESSRELATIONS,
migration.VALIDATION,
migration.SUCCESS,
migration.LOGTRANSFER,
migration.REAP,
migration.DONE,
}
for _, phase := range phases {
for _, phase := range migration.SuccessfulMigrationPhases() {
c.Assert(mig.SetPhase(phase), jc.ErrorIsNil)
}
c.Assert(model.Destroy(state.DestroyModelParams{}), jc.ErrorIsNil)
Expand Down
32 changes: 31 additions & 1 deletion apiserver/common/controllerconfig.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"gopkg.in/juju/names.v3"

"github.com/juju/juju/apiserver/params"
"github.com/juju/juju/core/crossmodel"
"github.com/juju/juju/state"
)

Expand Down Expand Up @@ -76,9 +77,38 @@ func (s *controllerStateShim) ControllerInfo(modelUUID string) (addrs []string,
return StateControllerInfo(s.State)
}

// Now check any external controllers.
ec := state.NewExternalControllers(s.State)
info, err := ec.ControllerForModel(modelUUID)

// the model on this controller may have been migrated, if that is the case we try to get the information.
// If that is true we try to save the migration controller as an external controller.
// This does not take cmr across controller into consideration controller a (consumer) --> b (offer).
// If a cross controller model has been migrated b (offer moving) --> c (new offer).
// There will be no migration information on this collection on controller a (consumer)
// about the migrated offer from b to c
if errors.IsNotFound(err) {
migration, err := s.State.CompletedMigration(modelUUID)
if err != nil {
return nil, "", errors.Trace(err)
}
target, err := migration.TargetInfo()
if err != nil {
return nil, "", errors.Trace(err)

}
logger.Debugf("found migrated model on another controller, saving the information")
_, err = ec.Save(crossmodel.ControllerInfo{
ControllerTag: target.ControllerTag,
Alias: target.ControllerAlias,
Addrs: target.Addrs,
CACert: target.CACert,
}, modelUUID)
if err != nil {
return nil, "", errors.Trace(err)
}
return target.Addrs, target.CACert, nil
}

if err != nil {
return nil, "", errors.Trace(err)
}
Expand Down
41 changes: 40 additions & 1 deletion apiserver/common/controllerconfig_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,23 @@ package common_test
import (
"fmt"

"github.com/juju/errors"
jc "github.com/juju/testing/checkers"
"github.com/juju/utils"
gc "gopkg.in/check.v1"
"gopkg.in/juju/names.v3"

"github.com/juju/errors"
"github.com/juju/juju/apiserver/common"
"github.com/juju/juju/apiserver/params"
"github.com/juju/juju/controller"
"github.com/juju/juju/core/crossmodel"
"github.com/juju/juju/core/migration"
"github.com/juju/juju/environs/config"
jujutesting "github.com/juju/juju/juju/testing"
"github.com/juju/juju/provider/dummy"
"github.com/juju/juju/state"
"github.com/juju/juju/testing"
"github.com/juju/juju/testing/factory"
)

type controllerConfigSuite struct {
Expand Down Expand Up @@ -146,3 +148,40 @@ func (s *controllerInfoSuite) TestControllerInfoExternalModel(c *gc.C) {
c.Assert(results.Results[0].Addresses, gc.DeepEquals, info.Addrs)
c.Assert(results.Results[0].CACert, gc.Equals, info.CACert)
}

func (s *controllerInfoSuite) TestControllerInfoMigratedController(c *gc.C) {
cc := common.NewStateControllerConfig(s.State)
modelState := s.Factory.MakeModel(c, &factory.ModelParams{})
model, err := modelState.Model()
c.Assert(err, jc.ErrorIsNil)

targetControllerTag := names.NewControllerTag(utils.MustNewUUID().String())
defer modelState.Close()

// Migrate the model and delete it from the state
controllerIP := "1.2.3.4:5555"
mig, err := modelState.CreateMigration(state.MigrationSpec{
InitiatedBy: names.NewUserTag("admin"),
TargetInfo: migration.TargetInfo{
ControllerTag: targetControllerTag,
ControllerAlias: "target",
Addrs: []string{controllerIP},
CACert: "",
AuthTag: names.NewUserTag("user2"),
Password: "secret",
},
})
c.Assert(err, jc.ErrorIsNil)
for _, phase := range migration.SuccessfulMigrationPhases() {
c.Assert(mig.SetPhase(phase), jc.ErrorIsNil)
}

c.Assert(model.Destroy(state.DestroyModelParams{}), jc.ErrorIsNil)
c.Assert(modelState.RemoveDyingModel(), jc.ErrorIsNil)

externalControllerInfo, err := cc.ControllerAPIInfoForModels(params.Entities{
Entities: []params.Entity{{Tag: names.NewModelTag(model.UUID()).String()}}})
c.Assert(err, jc.ErrorIsNil)
c.Assert(len(externalControllerInfo.Results), gc.Equals, 1)
c.Assert(externalControllerInfo.Results[0].Addresses[0], gc.Equals, controllerIP)
}
2 changes: 1 addition & 1 deletion apiserver/common/modelmanagerinterface.go
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ func (st modelManagerStateShim) GetBackend(modelUUID string) (ModelManagerBacken

// Check if this model has been migrated and this user had
// access to it before its migration.
mig, mErr := otherState.LatestRemovedModelMigration()
mig, mErr := otherState.CompletedMigrationForModel()
if mErr != nil && !errors.IsNotFound(mErr) {
return nil, nil, errors.Trace(mErr)
}
Expand Down
12 changes: 2 additions & 10 deletions apiserver/facades/client/modelmanager/modelmanager_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1639,16 +1639,8 @@ func (s *modelManagerStateSuite) TestModelInfoForMigratedModel(c *gc.C) {
},
})
c.Assert(err, jc.ErrorIsNil)
phases := []migration.Phase{
migration.IMPORT,
migration.PROCESSRELATIONS,
migration.VALIDATION,
migration.SUCCESS,
migration.LOGTRANSFER,
migration.REAP,
migration.DONE,
}
for _, phase := range phases {

for _, phase := range migration.SuccessfulMigrationPhases() {
c.Assert(mig.SetPhase(phase), jc.ErrorIsNil)
}
c.Assert(model.Destroy(state.DestroyModelParams{}), jc.ErrorIsNil)
Expand Down
2 changes: 1 addition & 1 deletion apiserver/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ func newAPIHandler(srv *Server, st *state.State, rpcConn *rpc.Conn, modelUUID st
// migrated allow clients to connect and wait for a login
// request to decide whether the users should be redirected to
// the new controller for this model or not.
if _, migErr := st.LatestRemovedModelMigration(); migErr != nil {
if _, migErr := st.CompletedMigrationForModel(); migErr != nil {
return nil, errors.Trace(err) // return original NotFound error
}
}
Expand Down
4 changes: 2 additions & 2 deletions core/doc.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ Package core exists to hold concepts and pure logic pertaining to juju's domain.
We'd call it "model code" if we weren't planning to rename "environ" to "model";
but that'd be quite needlessly confusing, so "core" it is.
This is a necessarily broad brush; if anything, it's mmost important to be aware
This is a necessarily broad brush; if anything, it's most important to be aware
what should *not* go here. In particular:
* if it makes any reference to MongoDB, it should not be in here.
Expand Down Expand Up @@ -48,7 +48,7 @@ because:
...but plenty of other bits will *not* be easy: in particular, all the business
rules that concern consistency are really tricky, and somewhat dangerous, to
extract, because (while those rules and relationshipps *are* business logic) we
extract, because (while those rules and relationships *are* business logic) we
need to be able to *render* them into a mgo/txn representation to ensure DB
consistency. If we just depend on implementing the state bits to match, rather
than *use*, the core logic, we're basically completely screwed.
Expand Down
13 changes: 13 additions & 0 deletions core/migration/phase.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,19 @@ var phaseNames = []string{
"ABORTDONE",
}

// Those phases are only used to get a complete successful round for testing purposes.
func SuccessfulMigrationPhases() []Phase {
return []Phase{
IMPORT,
PROCESSRELATIONS,
VALIDATION,
SUCCESS,
LOGTRANSFER,
REAP,
DONE,
}
}

// String returns the name of an model migration phase constant.
func (p Phase) String() string {
i := int(p)
Expand Down
39 changes: 30 additions & 9 deletions state/modelmigration.go
Original file line number Diff line number Diff line change
Expand Up @@ -814,10 +814,10 @@ func checkTargetController(st *State, targetControllerTag names.ControllerTag) e
// LatestMigration returns the most recent ModelMigration (if any) for a model
// that has not been removed from the state. Callers interested in
// ModelMigrations for models that have been removed after a successful
// migration to another controller should use LatestRemovedModelMigration
// migration to another controller should use CompletedMigrationForModel
// instead.
func (st *State) LatestMigration() (ModelMigration, error) {
mig, phase, err := st.latestMigration()
mig, phase, err := st.latestMigration(st.ModelUUID())
if err != nil {
return nil, err
}
Expand All @@ -837,11 +837,11 @@ func (st *State) LatestMigration() (ModelMigration, error) {
return mig, nil
}

// LatestRemovedModelMigration returns the most recent ModelMigration (if any)
// for a model that has been removed from the state after a successful
// migration to another controller.
func (st *State) LatestRemovedModelMigration() (ModelMigration, error) {
mig, phase, err := st.latestMigration()
// CompletedMigrationForModel returns the most recent ModelMigration (if any)
// for a model that has been removed from the state after
// it reached the DONE phase and caused the model to be relocated.
func (st *State) CompletedMigrationForModel() (ModelMigration, error) {
mig, phase, err := st.latestMigration(st.ModelUUID())
if err != nil {
return nil, err
}
Expand All @@ -856,12 +856,33 @@ func (st *State) LatestRemovedModelMigration() (ModelMigration, error) {
return mig, nil
}

// CompletedMigration returns the most recent ModelMigration (if any)
// for input model UUID that has been removed from the state after
// it reached the DONE phase and caused the model to be relocated.
func (st *State) CompletedMigration(modelUUID string) (ModelMigration, error) {
mig, phase, err := st.latestMigration(modelUUID)
if err != nil {
return nil, err
}
// Return NotFound if the model still modelExists or the migration is not
// flagged as completed.
modelExists, err := st.ModelExists(modelUUID)
if err != nil {
return nil, err
}
if phase != migration.DONE || modelExists {
return nil, errors.NotFoundf("migration")
}

return mig, nil
}

// latestMigration returns the most recent ModelMigration for a model
// (if any).
func (st *State) latestMigration() (ModelMigration, migration.Phase, error) {
func (st *State) latestMigration(modelUUID string) (ModelMigration, migration.Phase, error) {
migColl, closer := st.db().GetCollection(migrationsC)
defer closer()
query := migColl.Find(bson.M{"model-uuid": st.ModelUUID()})
query := migColl.Find(bson.M{"model-uuid": modelUUID})
query = query.Sort("-attempt").Limit(1)
mig, err := st.migrationFromQuery(query)
if err != nil {
Expand Down
28 changes: 5 additions & 23 deletions state/modelmigration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -331,29 +331,19 @@ func (s *MigrationSuite) TestLatestRemovedModelMigration(c *gc.C) {
mig1, err := s.State2.CreateMigration(s.stdSpec)
c.Assert(err, jc.ErrorIsNil)

// Cycle through the phases and complete the migration
phases := []migration.Phase{
migration.IMPORT,
migration.PROCESSRELATIONS,
migration.VALIDATION,
migration.SUCCESS,
migration.LOGTRANSFER,
migration.REAP,
migration.DONE,
}
for _, phase := range phases {
for _, phase := range migration.SuccessfulMigrationPhases() {
c.Assert(mig1.SetPhase(phase), jc.ErrorIsNil)
}

// LatestRemovedModelMigration should fail as the model docs are still there
_, err = s.State2.LatestRemovedModelMigration()
// CompletedMigrationForModel should fail as the model docs are still there
_, err = s.State2.CompletedMigrationForModel()
c.Assert(errors.IsNotFound(err), gc.Equals, true)

// Delete the model and check that we get back the MigrationModel
c.Assert(model.Destroy(state.DestroyModelParams{}), jc.ErrorIsNil)
c.Assert(s.State2.RemoveDyingModel(), jc.ErrorIsNil)

mig2, err := s.State2.LatestRemovedModelMigration()
mig2, err := s.State2.CompletedMigrationForModel()
c.Assert(err, jc.ErrorIsNil)
c.Assert(mig2, jc.DeepEquals, mig1)
}
Expand Down Expand Up @@ -400,15 +390,7 @@ func (s *MigrationSuite) TestSuccessfulPhaseTransitions(c *gc.C) {
mig2, err := st.LatestMigration()
c.Assert(err, jc.ErrorIsNil)

phases := []migration.Phase{
migration.IMPORT,
migration.PROCESSRELATIONS,
migration.VALIDATION,
migration.SUCCESS,
migration.LOGTRANSFER,
migration.REAP,
migration.DONE,
}
phases := migration.SuccessfulMigrationPhases()

var successTime time.Time
for _, phase := range phases[:len(phases)-1] {
Expand Down

0 comments on commit cc3679e

Please sign in to comment.