github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/migration/migration_test.go (about)

     1  // Copyright 2016 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package migration_test
     5  
     6  import (
     7  	"bytes"
     8  	"fmt"
     9  	"io"
    10  	"io/ioutil"
    11  	"net/url"
    12  	"time"
    13  
    14  	"github.com/juju/description"
    15  	"github.com/juju/errors"
    16  	"github.com/juju/loggo"
    17  	"github.com/juju/testing"
    18  	jc "github.com/juju/testing/checkers"
    19  	"github.com/juju/utils"
    20  	"github.com/juju/version"
    21  	gc "gopkg.in/check.v1"
    22  	"gopkg.in/juju/charm.v6"
    23  
    24  	"github.com/juju/juju/component/all"
    25  	"github.com/juju/juju/core/leadership"
    26  	"github.com/juju/juju/core/lease"
    27  	coremigration "github.com/juju/juju/core/migration"
    28  	"github.com/juju/juju/feature"
    29  	"github.com/juju/juju/migration"
    30  	"github.com/juju/juju/provider/dummy"
    31  	_ "github.com/juju/juju/provider/dummy"
    32  	"github.com/juju/juju/resource"
    33  	"github.com/juju/juju/resource/resourcetesting"
    34  	"github.com/juju/juju/state"
    35  	statetesting "github.com/juju/juju/state/testing"
    36  	coretesting "github.com/juju/juju/testing"
    37  	"github.com/juju/juju/testing/factory"
    38  	"github.com/juju/juju/tools"
    39  )
    40  
    41  func init() {
    42  	// Required for resources.
    43  	if err := all.RegisterForServer(); err != nil {
    44  		panic(err)
    45  	}
    46  }
    47  
    48  type ImportSuite struct {
    49  	statetesting.StateSuite
    50  }
    51  
    52  var _ = gc.Suite(&ImportSuite{})
    53  
    54  func (s *ImportSuite) SetUpTest(c *gc.C) {
    55  	// Specify the config to use for the controller model before
    56  	// calling SetUpTest of the StateSuite, otherwise we get
    57  	// coretesting.ModelConfig(c). The default provider type
    58  	// specified in the coretesting.ModelConfig function is one that
    59  	// isn't registered as a valid provider. For our tests here we
    60  	// need a real registered provider, so we use the dummy provider.
    61  	// NOTE: make a better test provider.
    62  	s.InitialConfig = coretesting.CustomModelConfig(c, dummy.SampleConfig())
    63  	s.StateSuite.SetUpTest(c)
    64  }
    65  
    66  func (s *ImportSuite) TestBadBytes(c *gc.C) {
    67  	bytes := []byte("not a model")
    68  	controller := state.NewController(s.StatePool)
    69  	model, st, err := migration.ImportModel(controller, fakeGetClaimer, bytes)
    70  	c.Check(st, gc.IsNil)
    71  	c.Check(model, gc.IsNil)
    72  	c.Assert(err, gc.ErrorMatches, "yaml: unmarshal errors:\n.*")
    73  }
    74  
    75  func (s *ImportSuite) exportImport(c *gc.C, getClaimer migration.ClaimerFunc) *state.State {
    76  	model, err := s.State.Export()
    77  	c.Assert(err, jc.ErrorIsNil)
    78  
    79  	// Update the config values in the exported model for different values for
    80  	// "state-port", "api-port", and "ca-cert". Also give the model a new UUID
    81  	// and name so we can import it nicely.
    82  	uuid := utils.MustNewUUID().String()
    83  	model.UpdateConfig(map[string]interface{}{
    84  		"name": "new-model",
    85  		"uuid": uuid,
    86  	})
    87  
    88  	bytes, err := description.Serialize(model)
    89  	c.Check(err, jc.ErrorIsNil)
    90  
    91  	controller := state.NewController(s.StatePool)
    92  	dbModel, dbState, err := migration.ImportModel(controller, getClaimer, bytes)
    93  	c.Assert(err, jc.ErrorIsNil)
    94  	s.AddCleanup(func(*gc.C) { dbState.Close() })
    95  
    96  	dbConfig, err := dbModel.Config()
    97  	c.Assert(err, jc.ErrorIsNil)
    98  	c.Assert(dbConfig.UUID(), gc.Equals, uuid)
    99  	c.Assert(dbConfig.Name(), gc.Equals, "new-model")
   100  	return dbState
   101  }
   102  
   103  func (s *ImportSuite) TestImportModel(c *gc.C) {
   104  	s.exportImport(c, fakeGetClaimer)
   105  }
   106  
   107  func (s *ImportSuite) TestImportsLeadership(c *gc.C) {
   108  	s.makeApplicationWithUnits(c, "wordpress", 3)
   109  	s.makeUnitApplicationLeader(c, "wordpress/1", "wordpress")
   110  	s.makeApplicationWithUnits(c, "mysql", 2)
   111  
   112  	var (
   113  		claimer   fakeClaimer
   114  		modelUUID string
   115  	)
   116  	dbState := s.exportImport(c, func(uuid string) (leadership.Claimer, error) {
   117  		modelUUID = uuid
   118  		return &claimer, nil
   119  	})
   120  	c.Assert(modelUUID, gc.Equals, dbState.ModelUUID())
   121  	c.Assert(claimer.stub.Calls(), gc.HasLen, 1)
   122  	claimer.stub.CheckCall(c, 0, "ClaimLeadership", "wordpress", "wordpress/1", time.Minute)
   123  }
   124  
   125  func (s *ImportSuite) TestImportsLeadershipLegacy(c *gc.C) {
   126  	err := s.State.UpdateControllerConfig(map[string]interface{}{
   127  		"features": []interface{}{feature.LegacyLeases},
   128  	}, nil)
   129  	c.Assert(err, jc.ErrorIsNil)
   130  	s.makeApplicationWithUnits(c, "wordpress", 3)
   131  	s.makeUnitApplicationLeaderLegacy(c, "wordpress/1", "wordpress")
   132  	s.makeApplicationWithUnits(c, "mysql", 2)
   133  
   134  	dbState := s.exportImport(c, nil)
   135  
   136  	leaders, err := dbState.ApplicationLeaders()
   137  	c.Assert(err, jc.ErrorIsNil)
   138  	c.Assert(leaders, gc.DeepEquals, map[string]string{"wordpress": "wordpress/1"})
   139  }
   140  
   141  func (s *ImportSuite) makeApplicationWithUnits(c *gc.C, applicationname string, count int) {
   142  	units := make([]*state.Unit, count)
   143  	application := s.Factory.MakeApplication(c, &factory.ApplicationParams{
   144  		Name: applicationname,
   145  		Charm: s.Factory.MakeCharm(c, &factory.CharmParams{
   146  			Name: applicationname,
   147  		}),
   148  	})
   149  	for i := 0; i < count; i++ {
   150  		units[i] = s.Factory.MakeUnit(c, &factory.UnitParams{
   151  			Application: application,
   152  		})
   153  	}
   154  }
   155  
   156  func (s *ImportSuite) makeUnitApplicationLeader(c *gc.C, unitName, applicationName string) {
   157  	target := s.State.LeaseNotifyTarget(
   158  		ioutil.Discard,
   159  		loggo.GetLogger("migration_import_test"),
   160  	)
   161  	target.Claimed(
   162  		lease.Key{"application-leadership", s.State.ModelUUID(), applicationName},
   163  		unitName,
   164  	)
   165  }
   166  
   167  func (s *ImportSuite) makeUnitApplicationLeaderLegacy(c *gc.C, unitName, applicationName string) {
   168  	err := s.State.LeadershipClaimer().ClaimLeadership(
   169  		applicationName,
   170  		unitName,
   171  		time.Minute)
   172  	c.Assert(err, jc.ErrorIsNil)
   173  }
   174  
   175  func (s *ImportSuite) TestUploadBinariesConfigValidate(c *gc.C) {
   176  	type T migration.UploadBinariesConfig // alias for brevity
   177  
   178  	check := func(modify func(*T), missing string) {
   179  		config := T{
   180  			CharmDownloader:    struct{ migration.CharmDownloader }{},
   181  			CharmUploader:      struct{ migration.CharmUploader }{},
   182  			ToolsDownloader:    struct{ migration.ToolsDownloader }{},
   183  			ToolsUploader:      struct{ migration.ToolsUploader }{},
   184  			ResourceDownloader: struct{ migration.ResourceDownloader }{},
   185  			ResourceUploader:   struct{ migration.ResourceUploader }{},
   186  		}
   187  		modify(&config)
   188  		realConfig := migration.UploadBinariesConfig(config)
   189  		c.Check(realConfig.Validate(), gc.ErrorMatches, fmt.Sprintf("missing %s not valid", missing))
   190  	}
   191  
   192  	check(func(c *T) { c.CharmDownloader = nil }, "CharmDownloader")
   193  	check(func(c *T) { c.CharmUploader = nil }, "CharmUploader")
   194  	check(func(c *T) { c.ToolsDownloader = nil }, "ToolsDownloader")
   195  	check(func(c *T) { c.ToolsUploader = nil }, "ToolsUploader")
   196  	check(func(c *T) { c.ResourceDownloader = nil }, "ResourceDownloader")
   197  	check(func(c *T) { c.ResourceUploader = nil }, "ResourceUploader")
   198  }
   199  
   200  func (s *ImportSuite) TestBinariesMigration(c *gc.C) {
   201  	downloader := &fakeDownloader{}
   202  	uploader := &fakeUploader{
   203  		tools:     make(map[version.Binary]string),
   204  		resources: make(map[string]string),
   205  	}
   206  
   207  	toolsMap := map[version.Binary]string{
   208  		version.MustParseBinary("2.1.0-trusty-amd64"): "/tools/0",
   209  		version.MustParseBinary("2.0.0-xenial-amd64"): "/tools/1",
   210  	}
   211  
   212  	app0Res := resourcetesting.NewResource(c, nil, "blob0", "app0", "blob0").Resource
   213  	app1Res := resourcetesting.NewResource(c, nil, "blob1", "app1", "blob1").Resource
   214  	app1UnitRes := app1Res
   215  	app1UnitRes.Revision = 1
   216  	app2Res := resourcetesting.NewPlaceholderResource(c, "blob2", "app2")
   217  	resources := []coremigration.SerializedModelResource{
   218  		{ApplicationRevision: app0Res},
   219  		{
   220  			ApplicationRevision: app1Res,
   221  			UnitRevisions:       map[string]resource.Resource{"app1/99": app1UnitRes},
   222  		},
   223  		{ApplicationRevision: app2Res},
   224  	}
   225  
   226  	config := migration.UploadBinariesConfig{
   227  		Charms: []string{
   228  			// These 2 are out of order. Rev 2 must be uploaded first.
   229  			"local:trusty/magic-10",
   230  			"local:trusty/magic-2",
   231  			"cs:trusty/postgresql-42",
   232  		},
   233  		CharmDownloader:    downloader,
   234  		CharmUploader:      uploader,
   235  		Tools:              toolsMap,
   236  		ToolsDownloader:    downloader,
   237  		ToolsUploader:      uploader,
   238  		Resources:          resources,
   239  		ResourceDownloader: downloader,
   240  		ResourceUploader:   uploader,
   241  	}
   242  	err := migration.UploadBinaries(config)
   243  	c.Assert(err, jc.ErrorIsNil)
   244  
   245  	expectedCharms := []string{
   246  		// Note ordering.
   247  		"cs:trusty/postgresql-42",
   248  		"local:trusty/magic-2",
   249  		"local:trusty/magic-10",
   250  	}
   251  	c.Assert(downloader.charms, jc.DeepEquals, expectedCharms)
   252  	c.Assert(uploader.charms, jc.DeepEquals, expectedCharms)
   253  
   254  	c.Assert(downloader.uris, jc.SameContents, []string{
   255  		"/tools/0",
   256  		"/tools/1",
   257  	})
   258  	c.Assert(uploader.tools, jc.DeepEquals, toolsMap)
   259  
   260  	c.Assert(downloader.resources, jc.SameContents, []string{
   261  		"app0/blob0",
   262  		"app1/blob1",
   263  	})
   264  	c.Assert(uploader.resources, jc.DeepEquals, map[string]string{
   265  		"app0/blob0": "blob0",
   266  		"app1/blob1": "blob1",
   267  	})
   268  	c.Assert(uploader.unitResources, jc.SameContents, []string{"app1/99-blob1"})
   269  }
   270  
   271  func (s *ImportSuite) TestWrongCharmURLAssigned(c *gc.C) {
   272  	downloader := &fakeDownloader{}
   273  	uploader := &fakeUploader{
   274  		reassignCharmURL: true,
   275  	}
   276  
   277  	config := migration.UploadBinariesConfig{
   278  		Charms:             []string{"local:foo/bar-2"},
   279  		CharmDownloader:    downloader,
   280  		CharmUploader:      uploader,
   281  		ToolsDownloader:    downloader,
   282  		ToolsUploader:      uploader,
   283  		ResourceDownloader: downloader,
   284  		ResourceUploader:   uploader,
   285  	}
   286  	err := migration.UploadBinaries(config)
   287  	c.Assert(err, gc.ErrorMatches,
   288  		"charm local:foo/bar-2 unexpectedly assigned local:foo/bar-1")
   289  }
   290  
   291  type fakeDownloader struct {
   292  	charms    []string
   293  	uris      []string
   294  	resources []string
   295  }
   296  
   297  func (d *fakeDownloader) OpenCharm(curl *charm.URL) (io.ReadCloser, error) {
   298  	urlStr := curl.String()
   299  	d.charms = append(d.charms, urlStr)
   300  	// Return the charm URL string as the fake charm content
   301  	return ioutil.NopCloser(bytes.NewReader([]byte(urlStr + " content"))), nil
   302  }
   303  
   304  func (d *fakeDownloader) OpenURI(uri string, query url.Values) (io.ReadCloser, error) {
   305  	if query != nil {
   306  		panic("query should be empty")
   307  	}
   308  	d.uris = append(d.uris, uri)
   309  	// Return the URI string as fake content
   310  	return ioutil.NopCloser(bytes.NewReader([]byte(uri))), nil
   311  }
   312  
   313  func (d *fakeDownloader) OpenResource(app, name string) (io.ReadCloser, error) {
   314  	d.resources = append(d.resources, app+"/"+name)
   315  	// Use the resource name as the content.
   316  	return ioutil.NopCloser(bytes.NewReader([]byte(name))), nil
   317  }
   318  
   319  type fakeUploader struct {
   320  	tools            map[version.Binary]string
   321  	charms           []string
   322  	resources        map[string]string
   323  	unitResources    []string
   324  	reassignCharmURL bool
   325  }
   326  
   327  func (f *fakeUploader) UploadTools(r io.ReadSeeker, v version.Binary, _ ...string) (tools.List, error) {
   328  	data, err := ioutil.ReadAll(r)
   329  	if err != nil {
   330  		return nil, errors.Trace(err)
   331  	}
   332  	f.tools[v] = string(data)
   333  	return tools.List{&tools.Tools{Version: v}}, nil
   334  }
   335  
   336  func (f *fakeUploader) UploadCharm(u *charm.URL, r io.ReadSeeker) (*charm.URL, error) {
   337  	data, err := ioutil.ReadAll(r)
   338  	if err != nil {
   339  		return nil, errors.Trace(err)
   340  	}
   341  	if string(data) != u.String()+" content" {
   342  		panic(fmt.Sprintf("unexpected charm body for %s: %s", u.String(), data))
   343  	}
   344  	f.charms = append(f.charms, u.String())
   345  
   346  	outU := *u
   347  	if f.reassignCharmURL {
   348  		outU.Revision--
   349  	}
   350  	return &outU, nil
   351  }
   352  
   353  func (f *fakeUploader) UploadResource(res resource.Resource, r io.ReadSeeker) error {
   354  	body, err := ioutil.ReadAll(r)
   355  	if err != nil {
   356  		return errors.Trace(err)
   357  	}
   358  	f.resources[res.ApplicationID+"/"+res.Name] = string(body)
   359  	return nil
   360  }
   361  
   362  func (f *fakeUploader) SetPlaceholderResource(res resource.Resource) error {
   363  	f.resources[res.ApplicationID+"/"+res.Name] = "<placeholder>"
   364  	return nil
   365  }
   366  
   367  func (f *fakeUploader) SetUnitResource(unit string, res resource.Resource) error {
   368  	f.unitResources = append(f.unitResources, unit+"-"+res.Name)
   369  	return nil
   370  }
   371  
   372  type ExportSuite struct {
   373  	statetesting.StateSuite
   374  }
   375  
   376  var _ = gc.Suite(&ExportSuite{})
   377  
   378  func (s *ExportSuite) TestExportModel(c *gc.C) {
   379  	bytes, err := migration.ExportModel(s.State)
   380  	c.Assert(err, jc.ErrorIsNil)
   381  	// The bytes must be a valid model.
   382  	modelDesc, err := description.Deserialize(bytes)
   383  	c.Assert(err, jc.ErrorIsNil)
   384  	c.Assert(modelDesc.Validate(), jc.ErrorIsNil)
   385  }
   386  
   387  func fakeGetClaimer(string) (leadership.Claimer, error) {
   388  	return &fakeClaimer{}, nil
   389  }
   390  
   391  type fakeClaimer struct {
   392  	leadership.Claimer
   393  	stub testing.Stub
   394  }
   395  
   396  func (c *fakeClaimer) ClaimLeadership(application, unit string, duration time.Duration) error {
   397  	c.stub.AddCall("ClaimLeadership", application, unit, duration)
   398  	return c.stub.NextErr()
   399  }