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

     1  // Copyright 2016 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package migrationmaster_test
     5  
     6  import (
     7  	"fmt"
     8  	"time"
     9  
    10  	"github.com/juju/description"
    11  	"github.com/juju/errors"
    12  	"github.com/juju/testing"
    13  	jc "github.com/juju/testing/checkers"
    14  	"github.com/juju/utils"
    15  	"github.com/juju/version"
    16  	gc "gopkg.in/check.v1"
    17  	"gopkg.in/juju/names.v2"
    18  	"gopkg.in/macaroon.v2-unstable"
    19  
    20  	"github.com/juju/juju/apiserver/common"
    21  	"github.com/juju/juju/apiserver/facade"
    22  	"github.com/juju/juju/apiserver/facades/controller/migrationmaster"
    23  	"github.com/juju/juju/apiserver/params"
    24  	apiservertesting "github.com/juju/juju/apiserver/testing"
    25  	coremigration "github.com/juju/juju/core/migration"
    26  	"github.com/juju/juju/core/presence"
    27  	"github.com/juju/juju/migration"
    28  	"github.com/juju/juju/state"
    29  	coretesting "github.com/juju/juju/testing"
    30  	jujuversion "github.com/juju/juju/version"
    31  )
    32  
    33  type Suite struct {
    34  	coretesting.BaseSuite
    35  
    36  	model      description.Model
    37  	stub       *testing.Stub
    38  	backend    *stubBackend
    39  	resources  *common.Resources
    40  	authorizer apiservertesting.FakeAuthorizer
    41  }
    42  
    43  var _ = gc.Suite(&Suite{})
    44  
    45  func (s *Suite) SetUpTest(c *gc.C) {
    46  	s.BaseSuite.SetUpTest(c)
    47  
    48  	s.model = description.NewModel(description.ModelArgs{
    49  		Type:               "iaas",
    50  		Config:             map[string]interface{}{"uuid": modelUUID},
    51  		Owner:              names.NewUserTag("admin"),
    52  		LatestToolsVersion: jujuversion.Current,
    53  	})
    54  	s.stub = new(testing.Stub)
    55  	s.backend = &stubBackend{
    56  		migration: &stubMigration{stub: s.stub},
    57  		stub:      s.stub,
    58  		model:     s.model,
    59  	}
    60  
    61  	s.resources = common.NewResources()
    62  	s.AddCleanup(func(*gc.C) { s.resources.StopAll() })
    63  
    64  	s.authorizer = apiservertesting.FakeAuthorizer{
    65  		Controller: true,
    66  	}
    67  }
    68  
    69  func (s *Suite) TestNotController(c *gc.C) {
    70  	s.authorizer.Controller = false
    71  
    72  	api, err := s.makeAPI()
    73  	c.Assert(api, gc.IsNil)
    74  	c.Assert(err, gc.Equals, common.ErrPerm)
    75  }
    76  
    77  func (s *Suite) TestWatch(c *gc.C) {
    78  	api := s.mustMakeAPI(c)
    79  
    80  	result := api.Watch()
    81  	c.Assert(result.Error, gc.IsNil)
    82  
    83  	resource := s.resources.Get(result.NotifyWatcherId)
    84  	watcher, _ := resource.(state.NotifyWatcher)
    85  	c.Assert(watcher, gc.NotNil)
    86  
    87  	select {
    88  	case <-watcher.Changes():
    89  		c.Fatalf("initial event not consumed")
    90  	case <-time.After(coretesting.ShortWait):
    91  	}
    92  }
    93  
    94  func (s *Suite) TestMigrationStatus(c *gc.C) {
    95  	var expectedMacaroons = `
    96  [[{"caveats":[],"location":"location","identifier":"id","signature":"a9802bf274262733d6283a69c62805b5668dbf475bcd7edc25a962833f7c2cba"}]]`[1:]
    97  
    98  	api := s.mustMakeAPI(c)
    99  	status, err := api.MigrationStatus()
   100  	c.Assert(err, jc.ErrorIsNil)
   101  
   102  	c.Check(status, gc.DeepEquals, params.MasterMigrationStatus{
   103  		Spec: params.MigrationSpec{
   104  			ModelTag: names.NewModelTag(modelUUID).String(),
   105  			TargetInfo: params.MigrationTargetInfo{
   106  				ControllerTag: names.NewControllerTag(controllerUUID).String(),
   107  				Addrs:         []string{"1.1.1.1:1", "2.2.2.2:2"},
   108  				CACert:        "trust me",
   109  				AuthTag:       names.NewUserTag("admin").String(),
   110  				Password:      "secret",
   111  				Macaroons:     expectedMacaroons,
   112  			},
   113  		},
   114  		MigrationId:      "id",
   115  		Phase:            "IMPORT",
   116  		PhaseChangedTime: s.backend.migration.PhaseChangedTime(),
   117  	})
   118  }
   119  
   120  func (s *Suite) TestModelInfo(c *gc.C) {
   121  	api := s.mustMakeAPI(c)
   122  	model, err := api.ModelInfo()
   123  	c.Assert(err, jc.ErrorIsNil)
   124  	c.Assert(model.UUID, gc.Equals, "model-uuid")
   125  	c.Assert(model.Name, gc.Equals, "model-name")
   126  	c.Assert(model.OwnerTag, gc.Equals, names.NewUserTag("owner").String())
   127  	c.Assert(model.AgentVersion, gc.Equals, version.MustParse("1.2.3"))
   128  }
   129  
   130  func (s *Suite) TestSetPhase(c *gc.C) {
   131  	api := s.mustMakeAPI(c)
   132  
   133  	err := api.SetPhase(params.SetMigrationPhaseArgs{Phase: "ABORT"})
   134  	c.Assert(err, jc.ErrorIsNil)
   135  
   136  	c.Assert(s.backend.migration.phaseSet, gc.Equals, coremigration.ABORT)
   137  }
   138  
   139  func (s *Suite) TestSetPhaseNoMigration(c *gc.C) {
   140  	s.backend.getErr = errors.New("boom")
   141  	api := s.mustMakeAPI(c)
   142  
   143  	err := api.SetPhase(params.SetMigrationPhaseArgs{Phase: "ABORT"})
   144  	c.Assert(err, gc.ErrorMatches, "could not get migration: boom")
   145  }
   146  
   147  func (s *Suite) TestSetPhaseBadPhase(c *gc.C) {
   148  	api := s.mustMakeAPI(c)
   149  
   150  	err := api.SetPhase(params.SetMigrationPhaseArgs{Phase: "wat"})
   151  	c.Assert(err, gc.ErrorMatches, `invalid phase: "wat"`)
   152  }
   153  
   154  func (s *Suite) TestSetPhaseError(c *gc.C) {
   155  	s.backend.migration.setPhaseErr = errors.New("blam")
   156  	api := s.mustMakeAPI(c)
   157  
   158  	err := api.SetPhase(params.SetMigrationPhaseArgs{Phase: "ABORT"})
   159  	c.Assert(err, gc.ErrorMatches, "failed to set phase: blam")
   160  }
   161  
   162  func (s *Suite) TestSetStatusMessage(c *gc.C) {
   163  	api := s.mustMakeAPI(c)
   164  
   165  	err := api.SetStatusMessage(params.SetMigrationStatusMessageArgs{Message: "foo"})
   166  	c.Assert(err, jc.ErrorIsNil)
   167  	c.Check(s.backend.migration.messageSet, gc.Equals, "foo")
   168  }
   169  
   170  func (s *Suite) TestSetStatusMessageNoMigration(c *gc.C) {
   171  	s.backend.getErr = errors.New("boom")
   172  	api := s.mustMakeAPI(c)
   173  
   174  	err := api.SetStatusMessage(params.SetMigrationStatusMessageArgs{Message: "foo"})
   175  	c.Check(err, gc.ErrorMatches, "could not get migration: boom")
   176  }
   177  
   178  func (s *Suite) TestSetStatusMessageError(c *gc.C) {
   179  	s.backend.migration.setMessageErr = errors.New("blam")
   180  	api := s.mustMakeAPI(c)
   181  
   182  	err := api.SetStatusMessage(params.SetMigrationStatusMessageArgs{Message: "foo"})
   183  	c.Assert(err, gc.ErrorMatches, "failed to set status message: blam")
   184  }
   185  
   186  func (s *Suite) TestPrechecks(c *gc.C) {
   187  	api := s.mustMakeAPI(c)
   188  	err := api.Prechecks()
   189  	c.Assert(err, gc.ErrorMatches, "retrieving model: boom")
   190  }
   191  
   192  func (s *Suite) TestExportIAAS(c *gc.C) {
   193  	s.assertExport(c, "iaas")
   194  }
   195  
   196  func (s *Suite) TestExportCAAS(c *gc.C) {
   197  	s.model = description.NewModel(description.ModelArgs{
   198  		Type:               "caas",
   199  		Config:             map[string]interface{}{"uuid": modelUUID},
   200  		Owner:              names.NewUserTag("admin"),
   201  		LatestToolsVersion: jujuversion.Current,
   202  	})
   203  	s.backend.model = s.model
   204  	s.assertExport(c, "caas")
   205  }
   206  
   207  func (s *Suite) assertExport(c *gc.C, modelType string) {
   208  	app := s.model.AddApplication(description.ApplicationArgs{
   209  		Tag:      names.NewApplicationTag("foo"),
   210  		CharmURL: "cs:foo-0",
   211  	})
   212  
   213  	const tools0 = "2.0.0-xenial-amd64"
   214  	const tools1 = "2.0.1-xenial-amd64"
   215  	m := s.model.AddMachine(description.MachineArgs{Id: names.NewMachineTag("9")})
   216  	m.SetTools(description.AgentToolsArgs{
   217  		Version: version.MustParseBinary(tools1),
   218  	})
   219  
   220  	res := app.AddResource(description.ResourceArgs{"bin"})
   221  	appRev := res.SetApplicationRevision(description.ResourceRevisionArgs{
   222  		Revision:       2,
   223  		Type:           "file",
   224  		Path:           "bin.tar.gz",
   225  		Description:    "who knows",
   226  		Origin:         "upload",
   227  		FingerprintHex: "abcd",
   228  		Size:           123,
   229  		Timestamp:      time.Now(),
   230  		Username:       "bob",
   231  	})
   232  	csRev := res.SetCharmStoreRevision(description.ResourceRevisionArgs{
   233  		Revision:       3,
   234  		Type:           "file",
   235  		Path:           "fink.tar.gz",
   236  		Description:    "knows who",
   237  		Origin:         "store",
   238  		FingerprintHex: "deaf",
   239  		Size:           321,
   240  		Timestamp:      time.Now(),
   241  		Username:       "xena",
   242  	})
   243  
   244  	unit := app.AddUnit(description.UnitArgs{
   245  		Tag: names.NewUnitTag("foo/0"),
   246  	})
   247  	unit.SetTools(description.AgentToolsArgs{
   248  		Version: version.MustParseBinary(tools0),
   249  	})
   250  	unitRes := unit.AddResource(description.UnitResourceArgs{
   251  		Name: "bin",
   252  		RevisionArgs: description.ResourceRevisionArgs{
   253  			Revision:       1,
   254  			Type:           "file",
   255  			Path:           "bin.tar.gz",
   256  			Description:    "nose knows",
   257  			Origin:         "upload",
   258  			FingerprintHex: "beef",
   259  			Size:           222,
   260  			Timestamp:      time.Now(),
   261  			Username:       "bambam",
   262  		},
   263  	})
   264  	unitRev := unitRes.Revision()
   265  
   266  	api := s.mustMakeAPI(c)
   267  	serialized, err := api.Export()
   268  	c.Assert(err, jc.ErrorIsNil)
   269  
   270  	// We don't want to tie this test the serialisation output (that's
   271  	// tested elsewhere). Just check that at least one thing we expect
   272  	// is in the serialised output.
   273  	c.Check(string(serialized.Bytes), jc.Contains, jujuversion.Current.String())
   274  
   275  	c.Check(serialized.Charms, gc.DeepEquals, []string{"cs:foo-0"})
   276  	if modelType == "caas" {
   277  		c.Check(serialized.Tools, gc.HasLen, 0)
   278  	} else {
   279  		c.Check(serialized.Tools, jc.SameContents, []params.SerializedModelTools{
   280  			{tools0, "/tools/" + tools0},
   281  			{tools1, "/tools/" + tools1},
   282  		})
   283  	}
   284  	c.Check(serialized.Resources, gc.DeepEquals, []params.SerializedModelResource{{
   285  		Application: "foo",
   286  		Name:        "bin",
   287  		ApplicationRevision: params.SerializedModelResourceRevision{
   288  			Revision:       appRev.Revision(),
   289  			Type:           appRev.Type(),
   290  			Path:           appRev.Path(),
   291  			Description:    appRev.Description(),
   292  			Origin:         appRev.Origin(),
   293  			FingerprintHex: appRev.FingerprintHex(),
   294  			Size:           appRev.Size(),
   295  			Timestamp:      appRev.Timestamp(),
   296  			Username:       appRev.Username(),
   297  		},
   298  		CharmStoreRevision: params.SerializedModelResourceRevision{
   299  			Revision:       csRev.Revision(),
   300  			Type:           csRev.Type(),
   301  			Path:           csRev.Path(),
   302  			Description:    csRev.Description(),
   303  			Origin:         csRev.Origin(),
   304  			FingerprintHex: csRev.FingerprintHex(),
   305  			Size:           csRev.Size(),
   306  			Timestamp:      csRev.Timestamp(),
   307  			Username:       csRev.Username(),
   308  		},
   309  		UnitRevisions: map[string]params.SerializedModelResourceRevision{
   310  			"foo/0": {
   311  				Revision:       unitRev.Revision(),
   312  				Type:           unitRev.Type(),
   313  				Path:           unitRev.Path(),
   314  				Description:    unitRev.Description(),
   315  				Origin:         unitRev.Origin(),
   316  				FingerprintHex: unitRev.FingerprintHex(),
   317  				Size:           unitRev.Size(),
   318  				Timestamp:      unitRev.Timestamp(),
   319  				Username:       unitRev.Username(),
   320  			},
   321  		},
   322  	}})
   323  
   324  }
   325  
   326  func (s *Suite) TestReap(c *gc.C) {
   327  	api := s.mustMakeAPI(c)
   328  	s.backend.migration = &stubMigration{}
   329  
   330  	err := api.Reap()
   331  	c.Check(err, jc.ErrorIsNil)
   332  	// Reaping should set the migration phase to DONE - otherwise
   333  	// there's a race between the migrationmaster worker updating the
   334  	// phase and being stopped because the model's gone. This leaves
   335  	// the migration as active in the source controller, which will
   336  	// prevent the model from being migrated back.
   337  	s.backend.stub.CheckCalls(c, []testing.StubCall{
   338  		{"LatestMigration", []interface{}{}},
   339  		{"RemoveExportingModelDocs", []interface{}{}},
   340  	})
   341  	c.Assert(s.backend.migration.phaseSet, gc.Equals, coremigration.DONE)
   342  }
   343  
   344  func (s *Suite) TestReapError(c *gc.C) {
   345  	s.backend.removeErr = errors.New("boom")
   346  	api := s.mustMakeAPI(c)
   347  
   348  	err := api.Reap()
   349  	c.Check(err, gc.ErrorMatches, "boom")
   350  }
   351  
   352  func (s *Suite) TestWatchMinionReports(c *gc.C) {
   353  	api := s.mustMakeAPI(c)
   354  
   355  	result := api.WatchMinionReports()
   356  	c.Assert(result.Error, gc.IsNil)
   357  
   358  	s.stub.CheckCallNames(c,
   359  		"LatestMigration",
   360  		"ModelMigration.WatchMinionReports",
   361  	)
   362  
   363  	resource := s.resources.Get(result.NotifyWatcherId)
   364  	watcher, _ := resource.(state.NotifyWatcher)
   365  	c.Assert(watcher, gc.NotNil)
   366  
   367  	select {
   368  	case <-watcher.Changes():
   369  		c.Fatalf("initial event not consumed")
   370  	case <-time.After(coretesting.ShortWait):
   371  	}
   372  }
   373  
   374  func (s *Suite) TestMinionReports(c *gc.C) {
   375  	// Report 16 unknowns. These are in reverse order in order to test
   376  	// sorting.
   377  	unknown := make([]names.Tag, 0, 16)
   378  	for i := cap(unknown) - 1; i >= 0; i-- {
   379  		unknown = append(unknown, names.NewMachineTag(fmt.Sprintf("%d", i)))
   380  	}
   381  	m50c0 := names.NewMachineTag("50/lxd/0")
   382  	m50c1 := names.NewMachineTag("50/lxd/1")
   383  	m50 := names.NewMachineTag("50")
   384  	m51 := names.NewMachineTag("51")
   385  	m52 := names.NewMachineTag("52")
   386  	u0 := names.NewUnitTag("foo/0")
   387  	u1 := names.NewUnitTag("foo/1")
   388  	s.backend.migration.minionReports = &state.MinionReports{
   389  		Succeeded: []names.Tag{m50, m51, u0},
   390  		Failed:    []names.Tag{u1, m52, m50c1, m50c0},
   391  		Unknown:   unknown,
   392  	}
   393  
   394  	api := s.mustMakeAPI(c)
   395  	reports, err := api.MinionReports()
   396  	c.Assert(err, jc.ErrorIsNil)
   397  
   398  	// Expect the sample of unknowns to be in order and be limited to
   399  	// the first 10.
   400  	expectedSample := make([]string, 0, 10)
   401  	for i := 0; i < cap(expectedSample); i++ {
   402  		expectedSample = append(expectedSample, names.NewMachineTag(fmt.Sprintf("%d", i)).String())
   403  	}
   404  	c.Assert(reports, gc.DeepEquals, params.MinionReports{
   405  		MigrationId:   "id",
   406  		Phase:         "IMPORT",
   407  		SuccessCount:  3,
   408  		UnknownCount:  len(unknown),
   409  		UnknownSample: expectedSample,
   410  		Failed: []string{
   411  			// Note sorting
   412  			m50c0.String(),
   413  			m50c1.String(),
   414  			m52.String(),
   415  			u1.String(),
   416  		},
   417  	})
   418  }
   419  
   420  func (s *Suite) makeAPI() (*migrationmaster.API, error) {
   421  	return migrationmaster.NewAPI(
   422  		s.backend,
   423  		new(failingPrecheckBackend),
   424  		nil, // pool
   425  		s.resources,
   426  		s.authorizer,
   427  		&fakePresence{},
   428  	)
   429  }
   430  
   431  func (s *Suite) mustMakeAPI(c *gc.C) *migrationmaster.API {
   432  	api, err := s.makeAPI()
   433  	c.Assert(err, jc.ErrorIsNil)
   434  	return api
   435  }
   436  
   437  type stubBackend struct {
   438  	migrationmaster.Backend
   439  
   440  	stub      *testing.Stub
   441  	getErr    error
   442  	removeErr error
   443  	migration *stubMigration
   444  	model     description.Model
   445  }
   446  
   447  func (b *stubBackend) WatchForMigration() state.NotifyWatcher {
   448  	b.stub.AddCall("WatchForMigration")
   449  	return apiservertesting.NewFakeNotifyWatcher()
   450  }
   451  
   452  func (b *stubBackend) LatestMigration() (state.ModelMigration, error) {
   453  	b.stub.AddCall("LatestMigration")
   454  	if b.getErr != nil {
   455  		return nil, b.getErr
   456  	}
   457  	return b.migration, nil
   458  }
   459  
   460  func (b *stubBackend) ModelUUID() string {
   461  	return "model-uuid"
   462  }
   463  
   464  func (b *stubBackend) ModelName() (string, error) {
   465  	return "model-name", nil
   466  }
   467  
   468  func (b *stubBackend) ModelOwner() (names.UserTag, error) {
   469  	return names.NewUserTag("owner"), nil
   470  }
   471  
   472  func (b *stubBackend) AgentVersion() (version.Number, error) {
   473  	return version.MustParse("1.2.3"), nil
   474  }
   475  
   476  func (b *stubBackend) RemoveExportingModelDocs() error {
   477  	b.stub.AddCall("RemoveExportingModelDocs")
   478  	return b.removeErr
   479  }
   480  
   481  func (b *stubBackend) Export() (description.Model, error) {
   482  	b.stub.AddCall("Export")
   483  	return b.model, nil
   484  }
   485  
   486  type stubMigration struct {
   487  	state.ModelMigration
   488  
   489  	stub            *testing.Stub
   490  	setPhaseErr     error
   491  	phaseSet        coremigration.Phase
   492  	setMessageErr   error
   493  	messageSet      string
   494  	minionReports   *state.MinionReports
   495  	externalControl bool
   496  }
   497  
   498  func (m *stubMigration) Id() string {
   499  	return "id"
   500  }
   501  
   502  func (m *stubMigration) Phase() (coremigration.Phase, error) {
   503  	return coremigration.IMPORT, nil
   504  }
   505  
   506  func (m *stubMigration) PhaseChangedTime() time.Time {
   507  	return time.Date(2016, 6, 22, 16, 38, 0, 0, time.UTC)
   508  }
   509  
   510  func (m *stubMigration) ModelUUID() string {
   511  	return modelUUID
   512  }
   513  
   514  func (m *stubMigration) TargetInfo() (*coremigration.TargetInfo, error) {
   515  	mac, err := macaroon.New([]byte("secret"), []byte("id"), "location")
   516  	if err != nil {
   517  		panic(err)
   518  	}
   519  	return &coremigration.TargetInfo{
   520  		ControllerTag: names.NewControllerTag(controllerUUID),
   521  		Addrs:         []string{"1.1.1.1:1", "2.2.2.2:2"},
   522  		CACert:        "trust me",
   523  		AuthTag:       names.NewUserTag("admin"),
   524  		Password:      "secret",
   525  		Macaroons:     []macaroon.Slice{{mac}},
   526  	}, nil
   527  }
   528  
   529  func (m *stubMigration) SetPhase(phase coremigration.Phase) error {
   530  	if m.setPhaseErr != nil {
   531  		return m.setPhaseErr
   532  	}
   533  	m.phaseSet = phase
   534  	return nil
   535  }
   536  
   537  func (m *stubMigration) SetStatusMessage(message string) error {
   538  	if m.setMessageErr != nil {
   539  		return m.setMessageErr
   540  	}
   541  	m.messageSet = message
   542  	return nil
   543  }
   544  
   545  func (m *stubMigration) WatchMinionReports() (state.NotifyWatcher, error) {
   546  	m.stub.AddCall("ModelMigration.WatchMinionReports")
   547  	return apiservertesting.NewFakeNotifyWatcher(), nil
   548  }
   549  
   550  func (m *stubMigration) MinionReports() (*state.MinionReports, error) {
   551  	return m.minionReports, nil
   552  }
   553  
   554  var modelUUID string
   555  var controllerUUID string
   556  
   557  func init() {
   558  	modelUUID = utils.MustNewUUID().String()
   559  	controllerUUID = utils.MustNewUUID().String()
   560  }
   561  
   562  type failingPrecheckBackend struct {
   563  	migration.PrecheckBackend
   564  }
   565  
   566  func (b *failingPrecheckBackend) Model() (migration.PrecheckModel, error) {
   567  	return nil, errors.New("boom")
   568  }
   569  
   570  type fakePresence struct {
   571  }
   572  
   573  func (f *fakePresence) ModelPresence(modelUUID string) facade.ModelPresence {
   574  	return f
   575  }
   576  
   577  func (f *fakePresence) AgentStatus(agent string) (presence.Status, error) {
   578  	return presence.Alive, nil
   579  }