github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/apiserver/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/errors"
    11  	"github.com/juju/testing"
    12  	jc "github.com/juju/testing/checkers"
    13  	"github.com/juju/utils"
    14  	"github.com/juju/version"
    15  	gc "gopkg.in/check.v1"
    16  	"gopkg.in/juju/names.v2"
    17  	"gopkg.in/macaroon.v1"
    18  
    19  	"github.com/juju/juju/apiserver/common"
    20  	"github.com/juju/juju/apiserver/migrationmaster"
    21  	"github.com/juju/juju/apiserver/params"
    22  	apiservertesting "github.com/juju/juju/apiserver/testing"
    23  	"github.com/juju/juju/core/description"
    24  	coremigration "github.com/juju/juju/core/migration"
    25  	"github.com/juju/juju/migration"
    26  	"github.com/juju/juju/state"
    27  	coretesting "github.com/juju/juju/testing"
    28  	jujuversion "github.com/juju/juju/version"
    29  )
    30  
    31  type Suite struct {
    32  	coretesting.BaseSuite
    33  
    34  	model      description.Model
    35  	stub       *testing.Stub
    36  	backend    *stubBackend
    37  	resources  *common.Resources
    38  	authorizer apiservertesting.FakeAuthorizer
    39  }
    40  
    41  var _ = gc.Suite(&Suite{})
    42  
    43  func (s *Suite) SetUpTest(c *gc.C) {
    44  	s.BaseSuite.SetUpTest(c)
    45  
    46  	s.model = description.NewModel(description.ModelArgs{
    47  		Config:             map[string]interface{}{"uuid": modelUUID},
    48  		Owner:              names.NewUserTag("admin"),
    49  		LatestToolsVersion: jujuversion.Current,
    50  	})
    51  	s.stub = new(testing.Stub)
    52  	s.backend = &stubBackend{
    53  		migration: &stubMigration{stub: s.stub},
    54  		stub:      s.stub,
    55  		model:     s.model,
    56  	}
    57  
    58  	s.resources = common.NewResources()
    59  	s.AddCleanup(func(*gc.C) { s.resources.StopAll() })
    60  
    61  	s.authorizer = apiservertesting.FakeAuthorizer{
    62  		EnvironManager: true,
    63  	}
    64  }
    65  
    66  func (s *Suite) TestNotEnvironManager(c *gc.C) {
    67  	s.authorizer.EnvironManager = false
    68  
    69  	api, err := s.makeAPI()
    70  	c.Assert(api, gc.IsNil)
    71  	c.Assert(err, gc.Equals, common.ErrPerm)
    72  }
    73  
    74  func (s *Suite) TestWatch(c *gc.C) {
    75  	api := s.mustMakeAPI(c)
    76  
    77  	result := api.Watch()
    78  	c.Assert(result.Error, gc.IsNil)
    79  
    80  	resource := s.resources.Get(result.NotifyWatcherId)
    81  	watcher, _ := resource.(state.NotifyWatcher)
    82  	c.Assert(watcher, gc.NotNil)
    83  
    84  	select {
    85  	case <-watcher.Changes():
    86  		c.Fatalf("initial event not consumed")
    87  	case <-time.After(coretesting.ShortWait):
    88  	}
    89  }
    90  
    91  func (s *Suite) TestMigrationStatus(c *gc.C) {
    92  	var expectedMacaroons = `
    93  [[{"caveats":[],"location":"location","identifier":"id","signature":"a9802bf274262733d6283a69c62805b5668dbf475bcd7edc25a962833f7c2cba"}]]`[1:]
    94  
    95  	api := s.mustMakeAPI(c)
    96  	status, err := api.MigrationStatus()
    97  	c.Assert(err, jc.ErrorIsNil)
    98  
    99  	c.Check(status, gc.DeepEquals, params.MasterMigrationStatus{
   100  		Spec: params.MigrationSpec{
   101  			ModelTag: names.NewModelTag(modelUUID).String(),
   102  			TargetInfo: params.MigrationTargetInfo{
   103  				ControllerTag: names.NewControllerTag(controllerUUID).String(),
   104  				Addrs:         []string{"1.1.1.1:1", "2.2.2.2:2"},
   105  				CACert:        "trust me",
   106  				AuthTag:       names.NewUserTag("admin").String(),
   107  				Password:      "secret",
   108  				Macaroons:     expectedMacaroons,
   109  			},
   110  		},
   111  		MigrationId:      "id",
   112  		Phase:            "IMPORT",
   113  		PhaseChangedTime: s.backend.migration.PhaseChangedTime(),
   114  	})
   115  }
   116  
   117  func (s *Suite) TestMigrationStatusExternalControl(c *gc.C) {
   118  	s.backend.migration.externalControl = true
   119  	status, err := s.mustMakeAPI(c).MigrationStatus()
   120  	c.Assert(err, jc.ErrorIsNil)
   121  	c.Check(status.Spec.ExternalControl, jc.IsTrue)
   122  }
   123  
   124  func (s *Suite) TestModelInfo(c *gc.C) {
   125  	api := s.mustMakeAPI(c)
   126  	model, err := api.ModelInfo()
   127  	c.Assert(err, jc.ErrorIsNil)
   128  	c.Assert(model.UUID, gc.Equals, "model-uuid")
   129  	c.Assert(model.Name, gc.Equals, "model-name")
   130  	c.Assert(model.OwnerTag, gc.Equals, names.NewUserTag("owner").String())
   131  	c.Assert(model.AgentVersion, gc.Equals, version.MustParse("1.2.3"))
   132  }
   133  
   134  func (s *Suite) TestSetPhase(c *gc.C) {
   135  	api := s.mustMakeAPI(c)
   136  
   137  	err := api.SetPhase(params.SetMigrationPhaseArgs{Phase: "ABORT"})
   138  	c.Assert(err, jc.ErrorIsNil)
   139  
   140  	c.Assert(s.backend.migration.phaseSet, gc.Equals, coremigration.ABORT)
   141  }
   142  
   143  func (s *Suite) TestSetPhaseNoMigration(c *gc.C) {
   144  	s.backend.getErr = errors.New("boom")
   145  	api := s.mustMakeAPI(c)
   146  
   147  	err := api.SetPhase(params.SetMigrationPhaseArgs{Phase: "ABORT"})
   148  	c.Assert(err, gc.ErrorMatches, "could not get migration: boom")
   149  }
   150  
   151  func (s *Suite) TestSetPhaseBadPhase(c *gc.C) {
   152  	api := s.mustMakeAPI(c)
   153  
   154  	err := api.SetPhase(params.SetMigrationPhaseArgs{Phase: "wat"})
   155  	c.Assert(err, gc.ErrorMatches, `invalid phase: "wat"`)
   156  }
   157  
   158  func (s *Suite) TestSetPhaseError(c *gc.C) {
   159  	s.backend.migration.setPhaseErr = errors.New("blam")
   160  	api := s.mustMakeAPI(c)
   161  
   162  	err := api.SetPhase(params.SetMigrationPhaseArgs{Phase: "ABORT"})
   163  	c.Assert(err, gc.ErrorMatches, "failed to set phase: blam")
   164  }
   165  
   166  func (s *Suite) TestSetStatusMessage(c *gc.C) {
   167  	api := s.mustMakeAPI(c)
   168  
   169  	err := api.SetStatusMessage(params.SetMigrationStatusMessageArgs{Message: "foo"})
   170  	c.Assert(err, jc.ErrorIsNil)
   171  	c.Check(s.backend.migration.messageSet, gc.Equals, "foo")
   172  }
   173  
   174  func (s *Suite) TestSetStatusMessageNoMigration(c *gc.C) {
   175  	s.backend.getErr = errors.New("boom")
   176  	api := s.mustMakeAPI(c)
   177  
   178  	err := api.SetStatusMessage(params.SetMigrationStatusMessageArgs{Message: "foo"})
   179  	c.Check(err, gc.ErrorMatches, "could not get migration: boom")
   180  }
   181  
   182  func (s *Suite) TestSetStatusMessageError(c *gc.C) {
   183  	s.backend.migration.setMessageErr = errors.New("blam")
   184  	api := s.mustMakeAPI(c)
   185  
   186  	err := api.SetStatusMessage(params.SetMigrationStatusMessageArgs{Message: "foo"})
   187  	c.Assert(err, gc.ErrorMatches, "failed to set status message: blam")
   188  }
   189  
   190  func (s *Suite) TestPrechecks(c *gc.C) {
   191  	api := s.mustMakeAPI(c)
   192  	err := api.Prechecks()
   193  	c.Assert(err, gc.ErrorMatches, "retrieving model: boom")
   194  }
   195  
   196  func (s *Suite) TestExport(c *gc.C) {
   197  	s.model.AddApplication(description.ApplicationArgs{
   198  		Tag:      names.NewApplicationTag("foo"),
   199  		CharmURL: "cs:foo-0",
   200  	})
   201  	const tools = "2.0.0-xenial-amd64"
   202  	m := s.model.AddMachine(description.MachineArgs{Id: names.NewMachineTag("9")})
   203  	m.SetTools(description.AgentToolsArgs{
   204  		Version: version.MustParseBinary(tools),
   205  	})
   206  	api := s.mustMakeAPI(c)
   207  
   208  	serialized, err := api.Export()
   209  
   210  	c.Assert(err, jc.ErrorIsNil)
   211  	// We don't want to tie this test the serialisation output (that's
   212  	// tested elsewhere). Just check that at least one thing we expect
   213  	// is in the serialised output.
   214  	c.Assert(string(serialized.Bytes), jc.Contains, jujuversion.Current.String())
   215  	c.Assert(serialized.Charms, gc.DeepEquals, []string{"cs:foo-0"})
   216  	c.Assert(serialized.Tools, gc.DeepEquals, []params.SerializedModelTools{
   217  		{tools, "/tools/" + tools},
   218  	})
   219  }
   220  
   221  func (s *Suite) TestReap(c *gc.C) {
   222  	api := s.mustMakeAPI(c)
   223  
   224  	err := api.Reap()
   225  	c.Check(err, jc.ErrorIsNil)
   226  	s.backend.stub.CheckCalls(c, []testing.StubCall{
   227  		{"RemoveExportingModelDocs", []interface{}{}},
   228  	})
   229  }
   230  
   231  func (s *Suite) TestReapError(c *gc.C) {
   232  	s.backend.removeErr = errors.New("boom")
   233  	api := s.mustMakeAPI(c)
   234  
   235  	err := api.Reap()
   236  	c.Check(err, gc.ErrorMatches, "boom")
   237  }
   238  
   239  func (s *Suite) TestWatchMinionReports(c *gc.C) {
   240  	api := s.mustMakeAPI(c)
   241  
   242  	result := api.WatchMinionReports()
   243  	c.Assert(result.Error, gc.IsNil)
   244  
   245  	s.stub.CheckCallNames(c,
   246  		"LatestMigration",
   247  		"ModelMigration.WatchMinionReports",
   248  	)
   249  
   250  	resource := s.resources.Get(result.NotifyWatcherId)
   251  	watcher, _ := resource.(state.NotifyWatcher)
   252  	c.Assert(watcher, gc.NotNil)
   253  
   254  	select {
   255  	case <-watcher.Changes():
   256  		c.Fatalf("initial event not consumed")
   257  	case <-time.After(coretesting.ShortWait):
   258  	}
   259  }
   260  
   261  func (s *Suite) TestMinionReports(c *gc.C) {
   262  	// Report 16 unknowns. These are in reverse order in order to test
   263  	// sorting.
   264  	unknown := make([]names.Tag, 0, 16)
   265  	for i := cap(unknown) - 1; i >= 0; i-- {
   266  		unknown = append(unknown, names.NewMachineTag(fmt.Sprintf("%d", i)))
   267  	}
   268  	m50c0 := names.NewMachineTag("50/lxd/0")
   269  	m50c1 := names.NewMachineTag("50/lxd/1")
   270  	m50 := names.NewMachineTag("50")
   271  	m51 := names.NewMachineTag("51")
   272  	m52 := names.NewMachineTag("52")
   273  	u0 := names.NewUnitTag("foo/0")
   274  	u1 := names.NewUnitTag("foo/1")
   275  	s.backend.migration.minionReports = &state.MinionReports{
   276  		Succeeded: []names.Tag{m50, m51, u0},
   277  		Failed:    []names.Tag{u1, m52, m50c1, m50c0},
   278  		Unknown:   unknown,
   279  	}
   280  
   281  	api := s.mustMakeAPI(c)
   282  	reports, err := api.MinionReports()
   283  	c.Assert(err, jc.ErrorIsNil)
   284  
   285  	// Expect the sample of unknowns to be in order and be limited to
   286  	// the first 10.
   287  	expectedSample := make([]string, 0, 10)
   288  	for i := 0; i < cap(expectedSample); i++ {
   289  		expectedSample = append(expectedSample, names.NewMachineTag(fmt.Sprintf("%d", i)).String())
   290  	}
   291  	c.Assert(reports, gc.DeepEquals, params.MinionReports{
   292  		MigrationId:   "id",
   293  		Phase:         "IMPORT",
   294  		SuccessCount:  3,
   295  		UnknownCount:  len(unknown),
   296  		UnknownSample: expectedSample,
   297  		Failed: []string{
   298  			// Note sorting
   299  			m50c0.String(),
   300  			m50c1.String(),
   301  			m52.String(),
   302  			u1.String(),
   303  		},
   304  	})
   305  }
   306  
   307  func (s *Suite) makeAPI() (*migrationmaster.API, error) {
   308  	return migrationmaster.NewAPI(s.backend, new(failingPrecheckBackend),
   309  		s.resources, s.authorizer)
   310  }
   311  
   312  func (s *Suite) mustMakeAPI(c *gc.C) *migrationmaster.API {
   313  	api, err := s.makeAPI()
   314  	c.Assert(err, jc.ErrorIsNil)
   315  	return api
   316  }
   317  
   318  type stubBackend struct {
   319  	migrationmaster.Backend
   320  
   321  	stub      *testing.Stub
   322  	getErr    error
   323  	removeErr error
   324  	migration *stubMigration
   325  	model     description.Model
   326  }
   327  
   328  func (b *stubBackend) WatchForMigration() state.NotifyWatcher {
   329  	b.stub.AddCall("WatchForMigration")
   330  	return apiservertesting.NewFakeNotifyWatcher()
   331  }
   332  
   333  func (b *stubBackend) LatestMigration() (state.ModelMigration, error) {
   334  	b.stub.AddCall("LatestMigration")
   335  	if b.getErr != nil {
   336  		return nil, b.getErr
   337  	}
   338  	return b.migration, nil
   339  }
   340  
   341  func (b *stubBackend) ModelUUID() string {
   342  	return "model-uuid"
   343  }
   344  
   345  func (b *stubBackend) ModelName() (string, error) {
   346  	return "model-name", nil
   347  }
   348  
   349  func (b *stubBackend) ModelOwner() (names.UserTag, error) {
   350  	return names.NewUserTag("owner"), nil
   351  }
   352  
   353  func (b *stubBackend) AgentVersion() (version.Number, error) {
   354  	return version.MustParse("1.2.3"), nil
   355  }
   356  
   357  func (b *stubBackend) RemoveExportingModelDocs() error {
   358  	b.stub.AddCall("RemoveExportingModelDocs")
   359  	return b.removeErr
   360  }
   361  
   362  func (b *stubBackend) Export() (description.Model, error) {
   363  	b.stub.AddCall("Export")
   364  	return b.model, nil
   365  }
   366  
   367  type stubMigration struct {
   368  	state.ModelMigration
   369  
   370  	stub            *testing.Stub
   371  	setPhaseErr     error
   372  	phaseSet        coremigration.Phase
   373  	setMessageErr   error
   374  	messageSet      string
   375  	minionReports   *state.MinionReports
   376  	externalControl bool
   377  }
   378  
   379  func (m *stubMigration) Id() string {
   380  	return "id"
   381  }
   382  
   383  func (m *stubMigration) Phase() (coremigration.Phase, error) {
   384  	return coremigration.IMPORT, nil
   385  }
   386  
   387  func (m *stubMigration) PhaseChangedTime() time.Time {
   388  	return time.Date(2016, 6, 22, 16, 38, 0, 0, time.UTC)
   389  }
   390  
   391  func (m *stubMigration) Attempt() (int, error) {
   392  	return 1, nil
   393  }
   394  
   395  func (m *stubMigration) ModelUUID() string {
   396  	return modelUUID
   397  }
   398  
   399  func (m *stubMigration) ExternalControl() bool {
   400  	return m.externalControl
   401  }
   402  
   403  func (m *stubMigration) TargetInfo() (*coremigration.TargetInfo, error) {
   404  	mac, err := macaroon.New([]byte("secret"), "id", "location")
   405  	if err != nil {
   406  		panic(err)
   407  	}
   408  	return &coremigration.TargetInfo{
   409  		ControllerTag: names.NewControllerTag(controllerUUID),
   410  		Addrs:         []string{"1.1.1.1:1", "2.2.2.2:2"},
   411  		CACert:        "trust me",
   412  		AuthTag:       names.NewUserTag("admin"),
   413  		Password:      "secret",
   414  		Macaroons:     []macaroon.Slice{{mac}},
   415  	}, nil
   416  }
   417  
   418  func (m *stubMigration) SetPhase(phase coremigration.Phase) error {
   419  	if m.setPhaseErr != nil {
   420  		return m.setPhaseErr
   421  	}
   422  	m.phaseSet = phase
   423  	return nil
   424  }
   425  
   426  func (m *stubMigration) SetStatusMessage(message string) error {
   427  	if m.setMessageErr != nil {
   428  		return m.setMessageErr
   429  	}
   430  	m.messageSet = message
   431  	return nil
   432  }
   433  
   434  func (m *stubMigration) WatchMinionReports() (state.NotifyWatcher, error) {
   435  	m.stub.AddCall("ModelMigration.WatchMinionReports")
   436  	return apiservertesting.NewFakeNotifyWatcher(), nil
   437  }
   438  
   439  func (m *stubMigration) MinionReports() (*state.MinionReports, error) {
   440  	return m.minionReports, nil
   441  }
   442  
   443  var modelUUID string
   444  var controllerUUID string
   445  
   446  func init() {
   447  	modelUUID = utils.MustNewUUID().String()
   448  	controllerUUID = utils.MustNewUUID().String()
   449  }
   450  
   451  type failingPrecheckBackend struct {
   452  	migration.PrecheckBackend
   453  }
   454  
   455  func (b *failingPrecheckBackend) Model() (migration.PrecheckModel, error) {
   456  	return nil, errors.New("boom")
   457  }