github.com/mwhudson/juju@v0.0.0-20160512215208-90ff01f3497f/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  
    12  	"github.com/juju/errors"
    13  	"github.com/juju/names"
    14  	jc "github.com/juju/testing/checkers"
    15  	"github.com/juju/utils"
    16  	"github.com/juju/version"
    17  	gc "gopkg.in/check.v1"
    18  	"gopkg.in/juju/charm.v6-unstable"
    19  	"gopkg.in/mgo.v2"
    20  
    21  	"github.com/juju/juju/api"
    22  	"github.com/juju/juju/cmd/modelcmd"
    23  	"github.com/juju/juju/core/description"
    24  	"github.com/juju/juju/environs"
    25  	"github.com/juju/juju/jujuclient/jujuclienttesting"
    26  	"github.com/juju/juju/migration"
    27  	"github.com/juju/juju/provider/dummy"
    28  	_ "github.com/juju/juju/provider/dummy"
    29  	"github.com/juju/juju/state"
    30  	"github.com/juju/juju/state/binarystorage"
    31  	"github.com/juju/juju/state/storage"
    32  	statetesting "github.com/juju/juju/state/testing"
    33  	"github.com/juju/juju/testing"
    34  	"github.com/juju/juju/tools"
    35  )
    36  
    37  type ImportSuite struct {
    38  	statetesting.StateSuite
    39  }
    40  
    41  var _ = gc.Suite(&ImportSuite{})
    42  
    43  func (s *ImportSuite) SetUpTest(c *gc.C) {
    44  	// Specify the config to use for the controller model before calling
    45  	// SetUpTest of the StateSuite, otherwise we get testing.ModelConfig(c).
    46  	// The default provider type specified in the testing.ModelConfig function
    47  	// is one that isn't registered as a valid provider. For our tests here we
    48  	// need a real registered provider, so we use the dummy provider.
    49  	// NOTE: make a better test provider.
    50  	env, err := environs.Prepare(
    51  		modelcmd.BootstrapContext(testing.Context(c)),
    52  		jujuclienttesting.NewMemStore(),
    53  		environs.PrepareParams{
    54  			ControllerName: "dummycontroller",
    55  			BaseConfig:     dummy.SampleConfig(),
    56  			CloudName:      "dummy",
    57  		},
    58  	)
    59  	c.Assert(err, jc.ErrorIsNil)
    60  
    61  	s.InitialConfig = testing.CustomModelConfig(c, env.Config().AllAttrs())
    62  	s.StateSuite.SetUpTest(c)
    63  }
    64  
    65  func (s *ImportSuite) TestBadBytes(c *gc.C) {
    66  	bytes := []byte("not a model")
    67  	model, st, err := migration.ImportModel(s.State, bytes)
    68  	c.Check(st, gc.IsNil)
    69  	c.Check(model, gc.IsNil)
    70  	c.Assert(err, gc.ErrorMatches, "yaml: unmarshal errors:\n.*")
    71  }
    72  
    73  func (s *ImportSuite) TestImportModel(c *gc.C) {
    74  	model, err := s.State.Export()
    75  	c.Check(err, jc.ErrorIsNil)
    76  
    77  	controllerConfig, err := s.State.ModelConfig()
    78  	c.Check(err, jc.ErrorIsNil)
    79  
    80  	// Update the config values in the exported model for different values for
    81  	// "state-port", "api-port", and "ca-cert". Also give the model a new UUID
    82  	// and name so we can import it nicely.
    83  	model.UpdateConfig(map[string]interface{}{
    84  		"name":       "new-model",
    85  		"uuid":       utils.MustNewUUID().String(),
    86  		"state-port": 12345,
    87  		"api-port":   54321,
    88  		"ca-cert":    "not really a cert",
    89  	})
    90  
    91  	bytes, err := description.Serialize(model)
    92  	c.Check(err, jc.ErrorIsNil)
    93  
    94  	dbModel, dbState, err := migration.ImportModel(s.State, bytes)
    95  	c.Check(err, jc.ErrorIsNil)
    96  	defer dbState.Close()
    97  
    98  	dbConfig, err := dbModel.Config()
    99  	c.Assert(err, jc.ErrorIsNil)
   100  	attrs := dbConfig.AllAttrs()
   101  	c.Assert(attrs["state-port"], gc.Equals, controllerConfig.StatePort())
   102  	c.Assert(attrs["api-port"], gc.Equals, controllerConfig.APIPort())
   103  	cacert, ok := controllerConfig.CACert()
   104  	c.Assert(ok, jc.IsTrue)
   105  	c.Assert(attrs["ca-cert"], gc.Equals, cacert)
   106  	c.Assert(attrs["controller-uuid"], gc.Equals, controllerConfig.UUID())
   107  }
   108  
   109  func (s *ImportSuite) TestUploadBinariesTools(c *gc.C) {
   110  	// Create a model that has three different tools versions:
   111  	// one for a machine, one for a container, and one for a unit agent.
   112  	// We don't care about the actual validity of the model (it isn't).
   113  	model := description.NewModel(description.ModelArgs{
   114  		Owner: names.NewUserTag("me"),
   115  	})
   116  	machine := model.AddMachine(description.MachineArgs{
   117  		Id: names.NewMachineTag("0"),
   118  	})
   119  	machine.SetTools(description.AgentToolsArgs{
   120  		Version: version.MustParseBinary("2.0.1-trusty-amd64"),
   121  	})
   122  	container := machine.AddContainer(description.MachineArgs{
   123  		Id: names.NewMachineTag("0/lxc/0"),
   124  	})
   125  	container.SetTools(description.AgentToolsArgs{
   126  		Version: version.MustParseBinary("2.0.5-trusty-amd64"),
   127  	})
   128  	service := model.AddService(description.ServiceArgs{
   129  		Tag:      names.NewServiceTag("magic"),
   130  		CharmURL: "local:trusty/magic",
   131  	})
   132  	unit := service.AddUnit(description.UnitArgs{
   133  		Tag: names.NewUnitTag("magic/0"),
   134  	})
   135  	unit.SetTools(description.AgentToolsArgs{
   136  		Version: version.MustParseBinary("2.0.3-trusty-amd64"),
   137  	})
   138  
   139  	uploader := &fakeUploader{tools: make(map[version.Binary]string)}
   140  	config := migration.UploadBinariesConfig{
   141  		State:            &fakeStateStorage{},
   142  		Model:            model,
   143  		Target:           &fakeAPIConnection{},
   144  		GetCharmUploader: func(api.Connection) migration.CharmUploader { return &noOpUploader{} },
   145  		GetToolsUploader: func(target api.Connection) migration.ToolsUploader {
   146  			return uploader
   147  		},
   148  		GetStateStorage:     func(migration.UploadBackend) storage.Storage { return &fakeCharmsStorage{} },
   149  		GetCharmStoragePath: func(migration.UploadBackend, *charm.URL) (string, error) { return "", nil },
   150  	}
   151  	err := migration.UploadBinaries(config)
   152  	c.Assert(err, jc.ErrorIsNil)
   153  
   154  	c.Assert(uploader.tools, jc.DeepEquals, map[version.Binary]string{
   155  		version.MustParseBinary("2.0.1-trusty-amd64"): "fake tools 2.0.1-trusty-amd64",
   156  		version.MustParseBinary("2.0.3-trusty-amd64"): "fake tools 2.0.3-trusty-amd64",
   157  		version.MustParseBinary("2.0.5-trusty-amd64"): "fake tools 2.0.5-trusty-amd64",
   158  	})
   159  }
   160  
   161  func (s *ImportSuite) TestStreamCharmsTools(c *gc.C) {
   162  	model := description.NewModel(description.ModelArgs{
   163  		Owner: names.NewUserTag("me"),
   164  	})
   165  	model.AddService(description.ServiceArgs{
   166  		Tag:      names.NewServiceTag("magic"),
   167  		CharmURL: "local:trusty/magic",
   168  	})
   169  	model.AddService(description.ServiceArgs{
   170  		Tag:      names.NewServiceTag("magic"),
   171  		CharmURL: "cs:trusty/postgresql-42",
   172  	})
   173  
   174  	uploader := &fakeUploader{charms: make(map[string]string)}
   175  	config := migration.UploadBinariesConfig{
   176  		State:            &fakeStateStorage{},
   177  		Model:            model,
   178  		Target:           &fakeAPIConnection{},
   179  		GetCharmUploader: func(api.Connection) migration.CharmUploader { return uploader },
   180  		GetToolsUploader: func(target api.Connection) migration.ToolsUploader { return &noOpUploader{} },
   181  		GetStateStorage:  func(migration.UploadBackend) storage.Storage { return &fakeCharmsStorage{} },
   182  		GetCharmStoragePath: func(_ migration.UploadBackend, u *charm.URL) (string, error) {
   183  			return "/path/for/" + u.String(), nil
   184  		},
   185  	}
   186  	err := migration.UploadBinaries(config)
   187  	c.Assert(err, jc.ErrorIsNil)
   188  
   189  	c.Assert(uploader.charms, jc.DeepEquals, map[string]string{
   190  		"local:trusty/magic":      "fake file at /path/for/local:trusty/magic",
   191  		"cs:trusty/postgresql-42": "fake file at /path/for/cs:trusty/postgresql-42",
   192  	})
   193  }
   194  
   195  type fakeStateStorage struct {
   196  	tools  fakeToolsStorage
   197  	charms fakeCharmsStorage
   198  }
   199  
   200  type fakeCharmsStorage struct {
   201  	storage.Storage
   202  }
   203  
   204  type fakeAPIConnection struct {
   205  	api.Connection
   206  }
   207  
   208  type fakeToolsStorage struct {
   209  	binarystorage.Storage
   210  	closed bool
   211  }
   212  
   213  func (f *fakeStateStorage) ToolsStorage() (binarystorage.StorageCloser, error) {
   214  	return &f.tools, nil
   215  }
   216  
   217  func (f *fakeStateStorage) ModelUUID() string {
   218  	return testing.ModelTag.Id()
   219  }
   220  
   221  func (f *fakeStateStorage) MongoSession() *mgo.Session {
   222  	return nil
   223  }
   224  
   225  func (f *fakeStateStorage) Charm(*charm.URL) (*state.Charm, error) {
   226  	return nil, nil
   227  }
   228  
   229  func (f *fakeToolsStorage) Open(v string) (binarystorage.Metadata, io.ReadCloser, error) {
   230  	buff := bytes.NewBufferString(fmt.Sprintf("fake tools %s", v))
   231  	return binarystorage.Metadata{}, ioutil.NopCloser(buff), nil
   232  }
   233  
   234  func (f *fakeToolsStorage) Close() error {
   235  	f.closed = true
   236  	return nil
   237  }
   238  
   239  func (f *fakeCharmsStorage) Get(path string) (io.ReadCloser, int64, error) {
   240  	buff := bytes.NewBufferString(fmt.Sprintf("fake file at %s", path))
   241  	return ioutil.NopCloser(buff), int64(buff.Len()), nil
   242  }
   243  
   244  type fakeUploader struct {
   245  	tools  map[version.Binary]string
   246  	charms map[string]string
   247  }
   248  
   249  func (f *fakeUploader) UploadTools(r io.ReadSeeker, v version.Binary, _ ...string) (tools.List, error) {
   250  	data, err := ioutil.ReadAll(r)
   251  	if err != nil {
   252  		return nil, errors.Trace(err)
   253  	}
   254  
   255  	f.tools[v] = string(data)
   256  
   257  	uploaded := &tools.Tools{
   258  		Version: v,
   259  	}
   260  	return tools.List{uploaded}, nil
   261  }
   262  
   263  func (f *fakeUploader) UploadCharm(u *charm.URL, r io.ReadSeeker) (*charm.URL, error) {
   264  	data, err := ioutil.ReadAll(r)
   265  	if err != nil {
   266  		return nil, errors.Trace(err)
   267  	}
   268  
   269  	f.charms[u.String()] = string(data)
   270  	return u, nil
   271  }
   272  
   273  type noOpUploader struct{}
   274  
   275  func (*noOpUploader) UploadCharm(*charm.URL, io.ReadSeeker) (*charm.URL, error) {
   276  	return nil, nil
   277  }
   278  
   279  func (*noOpUploader) UploadTools(io.ReadSeeker, version.Binary, ...string) (tools.List, error) {
   280  	return nil, nil
   281  }
   282  
   283  type ExportSuite struct {
   284  	statetesting.StateSuite
   285  }
   286  
   287  var _ = gc.Suite(&ExportSuite{})
   288  
   289  func (s *ExportSuite) TestExportModel(c *gc.C) {
   290  	bytes, err := migration.ExportModel(s.State)
   291  	c.Assert(err, jc.ErrorIsNil)
   292  	// The bytes must be a valid model.
   293  	_, err = description.Deserialize(bytes)
   294  	c.Assert(err, jc.ErrorIsNil)
   295  }
   296  
   297  type PrecheckSuite struct {
   298  	testing.BaseSuite
   299  }
   300  
   301  var _ = gc.Suite(&PrecheckSuite{})
   302  
   303  // Assert that *state.State implements the PrecheckBackend
   304  var _ migration.PrecheckBackend = (*state.State)(nil)
   305  
   306  func (*PrecheckSuite) TestPrecheckCleanups(c *gc.C) {
   307  	backend := &fakePrecheckBackend{}
   308  	err := migration.Precheck(backend)
   309  	c.Assert(err, jc.ErrorIsNil)
   310  }
   311  
   312  func (*PrecheckSuite) TestPrecheckCleanupsError(c *gc.C) {
   313  	backend := &fakePrecheckBackend{
   314  		cleanupError: errors.New("boom"),
   315  	}
   316  	err := migration.Precheck(backend)
   317  	c.Assert(err, gc.ErrorMatches, "precheck cleanups: boom")
   318  }
   319  
   320  func (*PrecheckSuite) TestPrecheckCleanupsNeeded(c *gc.C) {
   321  	backend := &fakePrecheckBackend{
   322  		cleanupNeeded: true,
   323  	}
   324  	err := migration.Precheck(backend)
   325  	c.Assert(err, gc.ErrorMatches, "precheck failed: cleanup needed")
   326  }
   327  
   328  type fakePrecheckBackend struct {
   329  	cleanupNeeded bool
   330  	cleanupError  error
   331  }
   332  
   333  func (f *fakePrecheckBackend) NeedsCleanup() (bool, error) {
   334  	return f.cleanupNeeded, f.cleanupError
   335  }
   336  
   337  type InternalSuite struct {
   338  	testing.BaseSuite
   339  }
   340  
   341  var _ = gc.Suite(&InternalSuite{})
   342  
   343  func (s *InternalSuite) TestControllerValues(c *gc.C) {
   344  	config := testing.ModelConfig(c)
   345  	fields := migration.ControllerValues(config)
   346  	c.Assert(fields, jc.DeepEquals, map[string]interface{}{
   347  		"controller-uuid": "deadbeef-0bad-400d-8000-4b1d0d06f00d",
   348  		"state-port":      19034,
   349  		"api-port":        17777,
   350  		"ca-cert":         testing.CACert,
   351  	})
   352  }
   353  
   354  func (s *InternalSuite) TestUpdateConfigFromProvider(c *gc.C) {
   355  	controllerConfig := testing.ModelConfig(c)
   356  	configAttrs := testing.FakeConfig()
   357  	configAttrs["type"] = "dummy"
   358  	// Fake the "state-id" so the provider thinks it is prepared already.
   359  	configAttrs["state-id"] = "42"
   360  	// We need to specify a valid provider type, so we use dummy.
   361  	// The dummy provider grabs the UUID from the controller config
   362  	// and returns it in the map with the key "controller-uuid", similar
   363  	// to what the azure provider will need to do.
   364  	model := description.NewModel(description.ModelArgs{
   365  		Owner:  names.NewUserTag("test-admin"),
   366  		Config: configAttrs,
   367  	})
   368  
   369  	err := migration.UpdateConfigFromProvider(model, controllerConfig)
   370  	c.Assert(err, jc.ErrorIsNil)
   371  
   372  	modelConfig := model.Config()
   373  	c.Assert(modelConfig["controller-uuid"], gc.Equals, controllerConfig.UUID())
   374  }
   375  
   376  type CharmInternalSuite struct {
   377  	statetesting.StateSuite
   378  }
   379  
   380  var _ = gc.Suite(&CharmInternalSuite{})
   381  
   382  func (s *CharmInternalSuite) TestCharmStoragePath(c *gc.C) {
   383  	charm := s.Factory.MakeCharm(c, nil)
   384  
   385  	path, err := migration.GetCharmStoragePath(s.State, charm.URL())
   386  	c.Assert(err, jc.ErrorIsNil)
   387  	c.Assert(path, gc.Equals, "fake-storage-path")
   388  }