github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/cmd/juju/commands/migrate_test.go (about)

     1  // Copyright 2016 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package commands
     5  
     6  import (
     7  	"net/http"
     8  	"net/url"
     9  	"time"
    10  
    11  	"github.com/juju/cmd"
    12  	"github.com/juju/cmd/cmdtesting"
    13  	jc "github.com/juju/testing/checkers"
    14  	gc "gopkg.in/check.v1"
    15  	"gopkg.in/macaroon-bakery.v2-unstable/httpbakery"
    16  	"gopkg.in/macaroon.v2-unstable"
    17  
    18  	"github.com/juju/juju/api"
    19  	"github.com/juju/juju/api/base"
    20  	"github.com/juju/juju/api/controller"
    21  	apitesting "github.com/juju/juju/api/testing"
    22  	"github.com/juju/juju/cmd/modelcmd"
    23  	"github.com/juju/juju/core/model"
    24  	"github.com/juju/juju/jujuclient"
    25  	"github.com/juju/juju/testing"
    26  )
    27  
    28  type MigrateSuite struct {
    29  	testing.FakeJujuXDGDataHomeSuite
    30  	api                 *fakeMigrateAPI
    31  	targetControllerAPI *fakeTargetControllerAPI
    32  	modelAPI            *fakeModelAPI
    33  	store               *jujuclient.MemStore
    34  	password            string
    35  }
    36  
    37  var _ = gc.Suite(&MigrateSuite{})
    38  
    39  const modelUUID = "deadbeef-0bad-400d-8000-4b1d0d06f00d"
    40  const targetControllerUUID = "beefdead-0bad-400d-8000-4b1d0d06f00d"
    41  
    42  func (s *MigrateSuite) SetUpTest(c *gc.C) {
    43  	s.FakeJujuXDGDataHomeSuite.SetUpTest(c)
    44  
    45  	s.store = jujuclient.NewMemStore()
    46  
    47  	// Define the source controller in the config and set it as the default.
    48  	err := s.store.AddController("source", jujuclient.ControllerDetails{
    49  		ControllerUUID: "eeeeeeee-0bad-400d-8000-4b1d0d06f00d",
    50  		CACert:         "somecert",
    51  	})
    52  	c.Assert(err, jc.ErrorIsNil)
    53  	err = s.store.SetCurrentController("source")
    54  	c.Assert(err, jc.ErrorIsNil)
    55  
    56  	// Define an account for the model in the source controller in the config.
    57  	err = s.store.UpdateAccount("source", jujuclient.AccountDetails{
    58  		User: "sourceuser",
    59  	})
    60  	c.Assert(err, jc.ErrorIsNil)
    61  
    62  	// Define the account for the target controller.
    63  	err = s.store.UpdateAccount("target", jujuclient.AccountDetails{
    64  		User:     "targetuser",
    65  		Password: "secret",
    66  	})
    67  	c.Assert(err, jc.ErrorIsNil)
    68  
    69  	// Define the target controller in the config.
    70  	err = s.store.AddController("target", jujuclient.ControllerDetails{
    71  		ControllerUUID: targetControllerUUID,
    72  		APIEndpoints:   []string{"1.2.3.4:5"},
    73  		CACert:         "cert",
    74  	})
    75  	c.Assert(err, jc.ErrorIsNil)
    76  
    77  	s.api = &fakeMigrateAPI{}
    78  	s.modelAPI = &fakeModelAPI{
    79  		models: []base.UserModel{{
    80  			Name:  "model",
    81  			UUID:  modelUUID,
    82  			Type:  model.IAAS,
    83  			Owner: "sourceuser",
    84  		}, {
    85  			Name:  "production",
    86  			UUID:  "prod-1-uuid",
    87  			Type:  model.IAAS,
    88  			Owner: "alpha",
    89  		}, {
    90  			Name:  "production",
    91  			UUID:  "prod-2-uuid",
    92  			Type:  model.IAAS,
    93  			Owner: "sourceuser",
    94  		}},
    95  	}
    96  
    97  	mac0, err := macaroon.New([]byte("secret0"), []byte("id0"), "location0")
    98  	c.Assert(err, jc.ErrorIsNil)
    99  	mac1, err := macaroon.New([]byte("secret1"), []byte("id1"), "location1")
   100  	c.Assert(err, jc.ErrorIsNil)
   101  
   102  	jar, err := s.store.CookieJar("target")
   103  	c.Assert(err, jc.ErrorIsNil)
   104  
   105  	s.targetControllerAPI = &fakeTargetControllerAPI{
   106  		cookieURL: &url.URL{
   107  			Scheme: "https",
   108  			Host:   "testing.invalid",
   109  			Path:   "/",
   110  		},
   111  		macaroons: []macaroon.Slice{{mac0}},
   112  	}
   113  	addCookie(c, jar, mac0, s.targetControllerAPI.cookieURL)
   114  	addCookie(c, jar, mac1, &url.URL{
   115  		Scheme: "https",
   116  		Host:   "tasting.invalid",
   117  		Path:   "/",
   118  	})
   119  
   120  }
   121  
   122  func addCookie(c *gc.C, jar http.CookieJar, mac *macaroon.Macaroon, url *url.URL) {
   123  	cookie, err := httpbakery.NewCookie(macaroon.Slice{mac})
   124  	c.Assert(err, jc.ErrorIsNil)
   125  	cookie.Expires = time.Now().Add(time.Hour) // only persistent cookies are stored
   126  	jar.SetCookies(url, []*http.Cookie{cookie})
   127  }
   128  
   129  func (s *MigrateSuite) TestMissingModel(c *gc.C) {
   130  	_, err := s.makeAndRun(c)
   131  	c.Assert(err, gc.ErrorMatches, "model not specified")
   132  }
   133  
   134  func (s *MigrateSuite) TestMissingTargetController(c *gc.C) {
   135  	_, err := s.makeAndRun(c, "mymodel")
   136  	c.Assert(err, gc.ErrorMatches, "target controller not specified")
   137  }
   138  
   139  func (s *MigrateSuite) TestTooManyArgs(c *gc.C) {
   140  	_, err := s.makeAndRun(c, "one", "too", "many")
   141  	c.Assert(err, gc.ErrorMatches, "too many arguments specified")
   142  }
   143  
   144  func (s *MigrateSuite) TestSuccess(c *gc.C) {
   145  	ctx, err := s.makeAndRun(c, "model", "target")
   146  	c.Assert(err, jc.ErrorIsNil)
   147  
   148  	c.Check(cmdtesting.Stderr(ctx), gc.Matches, "Migration started with ID \"uuid:0\"\n")
   149  	c.Check(s.api.specSeen, jc.DeepEquals, &controller.MigrationSpec{
   150  		ModelUUID:            modelUUID,
   151  		TargetControllerUUID: targetControllerUUID,
   152  		TargetAddrs:          []string{"1.2.3.4:5"},
   153  		TargetCACert:         "cert",
   154  		TargetUser:           "targetuser",
   155  		TargetPassword:       "secret",
   156  	})
   157  }
   158  
   159  func (s *MigrateSuite) TestSuccessMacaroons(c *gc.C) {
   160  	err := s.store.UpdateAccount("target", jujuclient.AccountDetails{
   161  		User:     "targetuser",
   162  		Password: "",
   163  	})
   164  	c.Assert(err, jc.ErrorIsNil)
   165  
   166  	ctx, err := s.makeAndRun(c, "model", "target")
   167  	c.Assert(err, jc.ErrorIsNil)
   168  
   169  	c.Check(cmdtesting.Stderr(ctx), gc.Matches, "Migration started with ID \"uuid:0\"\n")
   170  	// Extract macaroons so we can compare them separately
   171  	// (as they can't be compared using DeepEquals due to 'UnmarshaledAs')
   172  	macs := s.api.specSeen.TargetMacaroons
   173  	s.api.specSeen.TargetMacaroons = nil
   174  	apitesting.MacaroonsEqual(c, macs, s.targetControllerAPI.macaroons)
   175  	c.Check(s.api.specSeen, jc.DeepEquals, &controller.MigrationSpec{
   176  		ModelUUID:            modelUUID,
   177  		TargetControllerUUID: targetControllerUUID,
   178  		TargetAddrs:          []string{"1.2.3.4:5"},
   179  		TargetCACert:         "cert",
   180  		TargetUser:           "targetuser",
   181  	})
   182  }
   183  
   184  func (s *MigrateSuite) TestModelDoesntExist(c *gc.C) {
   185  	cmd := s.makeCommand()
   186  	_, err := cmdtesting.RunCommand(c, cmd, "wat", "target")
   187  	c.Check(err, gc.ErrorMatches, "model .+ not found")
   188  	c.Check(s.api.specSeen, gc.IsNil) // API shouldn't have been called
   189  }
   190  
   191  func (s *MigrateSuite) TestMultipleModelMatch(c *gc.C) {
   192  	cmd := s.makeCommand()
   193  	// Disambiguation is done in the standard way by choosing
   194  	// the current user's model.
   195  	ctx, err := cmdtesting.RunCommand(c, cmd, "production", "target")
   196  	c.Assert(err, jc.ErrorIsNil)
   197  	c.Check(cmdtesting.Stderr(ctx), gc.Matches, "Migration started with ID \"uuid:0\"\n")
   198  	c.Check(s.api.specSeen, jc.DeepEquals, &controller.MigrationSpec{
   199  		ModelUUID:            "prod-2-uuid",
   200  		TargetControllerUUID: targetControllerUUID,
   201  		TargetAddrs:          []string{"1.2.3.4:5"},
   202  		TargetCACert:         "cert",
   203  		TargetUser:           "targetuser",
   204  		TargetPassword:       "secret",
   205  	})
   206  }
   207  
   208  func (s *MigrateSuite) TestSpecifyOwner(c *gc.C) {
   209  	ctx, err := s.makeAndRun(c, "alpha/production", "target")
   210  	c.Assert(err, jc.ErrorIsNil)
   211  
   212  	c.Check(cmdtesting.Stderr(ctx), gc.Matches, "Migration started with ID \"uuid:0\"\n")
   213  	c.Check(s.api.specSeen.ModelUUID, gc.Equals, "prod-1-uuid")
   214  }
   215  
   216  func (s *MigrateSuite) TestControllerDoesntExist(c *gc.C) {
   217  	_, err := s.makeAndRun(c, "model", "wat")
   218  	c.Check(err, gc.ErrorMatches, "controller wat not found")
   219  	c.Check(s.api.specSeen, gc.IsNil) // API shouldn't have been called
   220  }
   221  
   222  func (s *MigrateSuite) makeAndRun(c *gc.C, args ...string) (*cmd.Context, error) {
   223  	return cmdtesting.RunCommand(c, s.makeCommand(), args...)
   224  }
   225  
   226  func (s *MigrateSuite) makeCommand() modelcmd.ModelCommand {
   227  	cmd := newMigrateCommand()
   228  	cmd.SetClientStore(s.store)
   229  	cmd.SetModelAPI(s.modelAPI)
   230  	inner := modelcmd.InnerCommand(cmd).(*migrateCommand)
   231  	inner.api = s.api
   232  	inner.newAPIRoot = func(jujuclient.ClientStore, string, string) (api.Connection, error) {
   233  		return s.targetControllerAPI, nil
   234  	}
   235  	return cmd
   236  }
   237  
   238  type fakeMigrateAPI struct {
   239  	specSeen *controller.MigrationSpec
   240  }
   241  
   242  func (a *fakeMigrateAPI) InitiateMigration(spec controller.MigrationSpec) (string, error) {
   243  	a.specSeen = &spec
   244  	return "uuid:0", nil
   245  }
   246  
   247  type fakeModelAPI struct {
   248  	models []base.UserModel
   249  }
   250  
   251  func (m *fakeModelAPI) ListModels(user string) ([]base.UserModel, error) {
   252  	return m.models, nil
   253  }
   254  
   255  func (m *fakeModelAPI) Close() error {
   256  	return nil
   257  }
   258  
   259  type fakeTargetControllerAPI struct {
   260  	api.Connection
   261  	cookieURL *url.URL
   262  	macaroons []macaroon.Slice
   263  }
   264  
   265  func (a *fakeTargetControllerAPI) CookieURL() *url.URL {
   266  	return a.cookieURL
   267  }
   268  
   269  func (a *fakeTargetControllerAPI) Close() error {
   270  	return nil
   271  }