github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/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 cookiejar "github.com/juju/persistent-cookiejar" 13 jc "github.com/juju/testing/checkers" 14 gc "gopkg.in/check.v1" 15 "gopkg.in/macaroon-bakery.v1/httpbakery" 16 "gopkg.in/macaroon.v1" 17 18 "github.com/juju/juju/api" 19 "github.com/juju/juju/api/base" 20 "github.com/juju/juju/api/controller" 21 "github.com/juju/juju/cmd/modelcmd" 22 "github.com/juju/juju/feature" 23 "github.com/juju/juju/jujuclient" 24 "github.com/juju/juju/jujuclient/jujuclienttesting" 25 "github.com/juju/juju/testing" 26 ) 27 28 type MigrateSuite struct { 29 testing.FakeJujuXDGDataHomeSuite 30 api *fakeMigrateAPI 31 targetControllerAPI *fakeTargetControllerAPI 32 store *jujuclienttesting.MemStore 33 password string 34 } 35 36 var _ = gc.Suite(&MigrateSuite{}) 37 38 const modelUUID = "deadbeef-0bad-400d-8000-4b1d0d06f00d" 39 const targetControllerUUID = "beefdead-0bad-400d-8000-4b1d0d06f00d" 40 41 func (s *MigrateSuite) SetUpTest(c *gc.C) { 42 s.SetInitialFeatureFlags(feature.Migration) 43 s.FakeJujuXDGDataHomeSuite.SetUpTest(c) 44 45 s.store = jujuclienttesting.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: "source@local", 59 }) 60 c.Assert(err, jc.ErrorIsNil) 61 62 // Define the model to migrate in the config. 63 err = s.store.UpdateModel("source", "source@local/model", jujuclient.ModelDetails{ 64 ModelUUID: modelUUID, 65 }) 66 c.Assert(err, jc.ErrorIsNil) 67 68 // Define the account for the target controller. 69 err = s.store.UpdateAccount("target", jujuclient.AccountDetails{ 70 User: "target@local", 71 Password: "secret", 72 }) 73 c.Assert(err, jc.ErrorIsNil) 74 75 // Define the target controller in the config. 76 err = s.store.AddController("target", jujuclient.ControllerDetails{ 77 ControllerUUID: targetControllerUUID, 78 APIEndpoints: []string{"1.2.3.4:5"}, 79 CACert: "cert", 80 }) 81 c.Assert(err, jc.ErrorIsNil) 82 83 s.api = &fakeMigrateAPI{} 84 85 mac0, err := macaroon.New([]byte("secret0"), "id0", "location0") 86 c.Assert(err, jc.ErrorIsNil) 87 mac1, err := macaroon.New([]byte("secret1"), "id1", "location1") 88 c.Assert(err, jc.ErrorIsNil) 89 90 jar, err := cookiejar.New(&cookiejar.Options{ 91 Filename: cookiejar.DefaultCookieFile(), 92 }) 93 c.Assert(err, jc.ErrorIsNil) 94 95 s.targetControllerAPI = &fakeTargetControllerAPI{ 96 cookieURL: &url.URL{ 97 Scheme: "https", 98 Host: "testing.invalid", 99 Path: "/", 100 }, 101 macaroons: []macaroon.Slice{{mac0}}, 102 } 103 addCookie(c, jar, mac0, s.targetControllerAPI.cookieURL) 104 addCookie(c, jar, mac1, &url.URL{ 105 Scheme: "https", 106 Host: "tasting.invalid", 107 Path: "/", 108 }) 109 110 err = jar.Save() 111 c.Assert(err, jc.ErrorIsNil) 112 } 113 114 func addCookie(c *gc.C, jar *cookiejar.Jar, mac *macaroon.Macaroon, url *url.URL) { 115 cookie, err := httpbakery.NewCookie(macaroon.Slice{mac}) 116 c.Assert(err, jc.ErrorIsNil) 117 cookie.Expires = time.Now().Add(time.Hour) // only persistent cookies are stored 118 jar.SetCookies(url, []*http.Cookie{cookie}) 119 } 120 121 func (s *MigrateSuite) TestMissingModel(c *gc.C) { 122 _, err := s.makeAndRun(c) 123 c.Assert(err, gc.ErrorMatches, "model not specified") 124 } 125 126 func (s *MigrateSuite) TestMissingTargetController(c *gc.C) { 127 _, err := s.makeAndRun(c, "mymodel") 128 c.Assert(err, gc.ErrorMatches, "target controller not specified") 129 } 130 131 func (s *MigrateSuite) TestTooManyArgs(c *gc.C) { 132 _, err := s.makeAndRun(c, "one", "too", "many") 133 c.Assert(err, gc.ErrorMatches, "too many arguments specified") 134 } 135 136 func (s *MigrateSuite) TestSuccess(c *gc.C) { 137 ctx, err := s.makeAndRun(c, "model", "target") 138 c.Assert(err, jc.ErrorIsNil) 139 140 c.Check(testing.Stderr(ctx), gc.Matches, "Migration started with ID \"uuid:0\"\n") 141 c.Check(s.api.specSeen, jc.DeepEquals, &controller.MigrationSpec{ 142 ModelUUID: modelUUID, 143 TargetControllerUUID: targetControllerUUID, 144 TargetAddrs: []string{"1.2.3.4:5"}, 145 TargetCACert: "cert", 146 TargetUser: "target@local", 147 TargetPassword: "secret", 148 }) 149 } 150 151 func (s *MigrateSuite) TestSuccessMacaroons(c *gc.C) { 152 err := s.store.UpdateAccount("target", jujuclient.AccountDetails{ 153 User: "target@local", 154 Password: "", 155 }) 156 c.Assert(err, jc.ErrorIsNil) 157 158 ctx, err := s.makeAndRun(c, "model", "target") 159 c.Assert(err, jc.ErrorIsNil) 160 161 c.Check(testing.Stderr(ctx), gc.Matches, "Migration started with ID \"uuid:0\"\n") 162 c.Check(s.api.specSeen, jc.DeepEquals, &controller.MigrationSpec{ 163 ModelUUID: modelUUID, 164 TargetControllerUUID: targetControllerUUID, 165 TargetAddrs: []string{"1.2.3.4:5"}, 166 TargetCACert: "cert", 167 TargetUser: "target@local", 168 TargetMacaroons: s.targetControllerAPI.macaroons, 169 }) 170 } 171 172 func (s *MigrateSuite) TestModelDoesntExist(c *gc.C) { 173 cmd := s.makeCommand() 174 cmd.SetModelAPI(&fakeModelAPI{}) 175 _, err := s.run(c, cmd, "wat", "target") 176 c.Check(err, gc.ErrorMatches, "model .+ not found") 177 c.Check(s.api.specSeen, gc.IsNil) // API shouldn't have been called 178 } 179 180 func (s *MigrateSuite) TestModelDoesntExistBeforeRefresh(c *gc.C) { 181 cmd := s.makeCommand() 182 cmd.SetModelAPI(&fakeModelAPI{model: "wat"}) // Model is available after refresh 183 _, err := s.run(c, cmd, "wat", "target") 184 c.Check(err, jc.ErrorIsNil) 185 c.Check(s.api.specSeen, gc.NotNil) 186 } 187 188 func (s *MigrateSuite) TestControllerDoesntExist(c *gc.C) { 189 _, err := s.makeAndRun(c, "model", "wat") 190 c.Check(err, gc.ErrorMatches, "controller wat not found") 191 c.Check(s.api.specSeen, gc.IsNil) // API shouldn't have been called 192 } 193 194 func (s *MigrateSuite) makeAndRun(c *gc.C, args ...string) (*cmd.Context, error) { 195 return s.run(c, s.makeCommand(), args...) 196 } 197 198 func (s *MigrateSuite) makeCommand() *migrateCommand { 199 cmd := &migrateCommand{ 200 api: s.api, 201 newAPIRoot: func(jujuclient.ClientStore, string, string) (api.Connection, error) { 202 return s.targetControllerAPI, nil 203 }, 204 } 205 cmd.SetClientStore(s.store) 206 return cmd 207 } 208 209 func (s *MigrateSuite) run(c *gc.C, cmd *migrateCommand, args ...string) (*cmd.Context, error) { 210 return testing.RunCommand(c, modelcmd.WrapController(cmd), args...) 211 } 212 213 type fakeMigrateAPI struct { 214 specSeen *controller.MigrationSpec 215 } 216 217 func (a *fakeMigrateAPI) InitiateMigration(spec controller.MigrationSpec) (string, error) { 218 a.specSeen = &spec 219 return "uuid:0", nil 220 } 221 222 type fakeModelAPI struct { 223 model string 224 } 225 226 func (m *fakeModelAPI) ListModels(user string) ([]base.UserModel, error) { 227 if m.model == "" { 228 return []base.UserModel{}, nil 229 } 230 return []base.UserModel{{ 231 Name: m.model, 232 UUID: modelUUID, 233 Owner: "source@local", 234 }}, nil 235 } 236 237 func (m *fakeModelAPI) Close() error { 238 return nil 239 } 240 241 type fakeTargetControllerAPI struct { 242 api.Connection 243 cookieURL *url.URL 244 macaroons []macaroon.Slice 245 } 246 247 func (a *fakeTargetControllerAPI) CookieURL() *url.URL { 248 return a.cookieURL 249 } 250 251 func (a *fakeTargetControllerAPI) Close() error { 252 return nil 253 }