github.com/juju/juju@v0.0.0-20240327075706-a90865de2538/api/state_test.go (about) 1 // Copyright 2013 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package api_test 5 6 import ( 7 "github.com/juju/errors" 8 "github.com/juju/names/v5" 9 jc "github.com/juju/testing/checkers" 10 "github.com/juju/utils/v3" 11 gc "gopkg.in/check.v1" 12 "gopkg.in/macaroon.v2" 13 14 "github.com/juju/juju/api" 15 "github.com/juju/juju/api/client/modelmanager" 16 "github.com/juju/juju/api/client/usermanager" 17 "github.com/juju/juju/core/migration" 18 "github.com/juju/juju/core/network" 19 jujutesting "github.com/juju/juju/juju/testing" 20 proxytest "github.com/juju/juju/proxy/testing" 21 "github.com/juju/juju/rpc/params" 22 "github.com/juju/juju/state" 23 coretesting "github.com/juju/juju/testing" 24 "github.com/juju/juju/testing/factory" 25 ) 26 27 type stateSuite struct { 28 jujutesting.JujuConnSuite 29 } 30 31 var _ = gc.Suite(&stateSuite{}) 32 33 type slideSuite struct { 34 coretesting.BaseSuite 35 } 36 37 var _ = gc.Suite(&slideSuite{}) 38 39 func (s *stateSuite) TestCloseMultipleOk(c *gc.C) { 40 c.Assert(s.APIState.Close(), gc.IsNil) 41 c.Assert(s.APIState.Close(), gc.IsNil) 42 c.Assert(s.APIState.Close(), gc.IsNil) 43 } 44 45 // OpenAPIWithoutLogin connects to the API and returns an api.State without 46 // actually calling st.Login already. The returned strings are the "tag" and 47 // "password" that we would have used to login. 48 func (s *stateSuite) OpenAPIWithoutLogin(c *gc.C) (api.Connection, names.Tag, string) { 49 info := s.APIInfo(c) 50 tag := info.Tag 51 password := info.Password 52 info.Tag = nil 53 info.Password = "" 54 info.Macaroons = nil 55 info.SkipLogin = true 56 apistate, err := api.Open(info, api.DialOpts{}) 57 c.Assert(err, jc.ErrorIsNil) 58 return apistate, tag, password 59 } 60 61 func (s *stateSuite) TestAPIHostPortsAlwaysIncludesTheConnection(c *gc.C) { 62 hostportslist := s.APIState.APIHostPorts() 63 c.Check(hostportslist, gc.HasLen, 1) 64 serverhostports := hostportslist[0] 65 c.Check(serverhostports, gc.HasLen, 1) 66 67 info := s.APIInfo(c) 68 69 // We intentionally set this to invalid values 70 badServers := network.NewSpaceHostPorts(1234, "0.1.2.3") 71 badServers[0].Scope = network.ScopeMachineLocal 72 err := s.State.SetAPIHostPorts([]network.SpaceHostPorts{badServers}) 73 c.Assert(err, jc.ErrorIsNil) 74 75 apistate, err := api.Open(info, api.DialOpts{}) 76 c.Assert(err, jc.ErrorIsNil) 77 defer func() { _ = apistate.Close() }() 78 79 hp, err := network.ParseMachineHostPort(badServers[0].String()) 80 c.Assert(err, jc.ErrorIsNil) 81 hp.Scope = badServers[0].Scope 82 83 hostports := apistate.APIHostPorts() 84 c.Check(hostports, gc.DeepEquals, []network.MachineHostPorts{ 85 serverhostports, 86 {*hp}, 87 }) 88 } 89 90 func (s *stateSuite) TestAPIHostPortsDoesNotIncludeConnectionProxy(c *gc.C) { 91 info := s.APIInfo(c) 92 conn := newRPCConnection() 93 conn.response = ¶ms.LoginResult{ 94 ControllerTag: "controller-" + s.ControllerConfig.ControllerUUID(), 95 ServerVersion: "2.3-rc2", 96 Servers: [][]params.HostPort{ 97 { 98 params.HostPort{ 99 Address: params.Address{ 100 Value: "fe80:abcd::1", 101 CIDR: "128", 102 }, 103 Port: 1234, 104 }, 105 }, 106 }, 107 } 108 109 broken := make(chan struct{}) 110 close(broken) 111 testState := api.NewTestingState(api.TestingStateParams{ 112 RPCConnection: conn, 113 Clock: &fakeClock{}, 114 Address: "localhost:1234", 115 Broken: broken, 116 Closed: make(chan struct{}), 117 Proxier: proxytest.NewMockTunnelProxier(), 118 }) 119 err := testState.Login(info.Tag, info.Password, "", nil) 120 c.Assert(err, jc.ErrorIsNil) 121 122 hostPortList := testState.APIHostPorts() 123 c.Assert(len(hostPortList), gc.Equals, 1) 124 c.Assert(len(hostPortList[0]), gc.Equals, 1) 125 c.Assert(hostPortList[0][0].NetPort, gc.Equals, network.NetPort(1234)) 126 c.Assert(hostPortList[0][0].MachineAddress.Value, gc.Equals, "fe80:abcd::1") 127 } 128 129 func (s *stateSuite) TestTags(c *gc.C) { 130 model, err := s.State.Model() 131 c.Assert(err, jc.ErrorIsNil) 132 apistate, tag, password := s.OpenAPIWithoutLogin(c) 133 defer apistate.Close() 134 // Even though we haven't called Login, the model tag should 135 // still be set. 136 modelTag, ok := apistate.ModelTag() 137 c.Check(ok, jc.IsTrue) 138 c.Check(modelTag, gc.Equals, model.ModelTag()) 139 err = apistate.Login(tag, password, "", nil) 140 c.Assert(err, jc.ErrorIsNil) 141 // Now that we've logged in, ModelTag should still be the same. 142 modelTag, ok = apistate.ModelTag() 143 c.Check(ok, jc.IsTrue) 144 c.Check(modelTag, gc.Equals, model.ModelTag()) 145 controllerTag := apistate.ControllerTag() 146 c.Check(controllerTag, gc.Equals, coretesting.ControllerTag) 147 } 148 149 func (s *stateSuite) TestLoginSetsControllerAccess(c *gc.C) { 150 // The default user has admin access. 151 c.Assert(s.APIState.ControllerAccess(), gc.Equals, "superuser") 152 153 manager := usermanager.NewClient(s.OpenControllerAPI(c)) 154 defer manager.Close() 155 usertag, _, err := manager.AddUser("ro", "ro", "ro-password") 156 c.Assert(err, jc.ErrorIsNil) 157 mmanager := modelmanager.NewClient(s.OpenControllerAPI(c)) 158 defer mmanager.Close() 159 modeltag, ok := s.APIState.ModelTag() 160 c.Assert(ok, jc.IsTrue) 161 err = mmanager.GrantModel(usertag.Id(), "read", modeltag.Id()) 162 c.Assert(err, jc.ErrorIsNil) 163 conn := s.OpenAPIAs(c, usertag, "ro-password") 164 c.Assert(conn.ControllerAccess(), gc.Equals, "login") 165 } 166 167 func (s *stateSuite) TestLoginToMigratedModel(c *gc.C) { 168 modelOwner := s.Factory.MakeUser(c, &factory.UserParams{ 169 Password: "secret", 170 }) 171 modelState := s.Factory.MakeModel(c, &factory.ModelParams{ 172 Owner: modelOwner.UserTag(), 173 }) 174 defer modelState.Close() 175 model, err := modelState.Model() 176 c.Assert(err, jc.ErrorIsNil) 177 178 controllerTag := names.NewControllerTag(utils.MustNewUUID().String()) 179 180 // Migrate the model and delete it from the state 181 mig, err := modelState.CreateMigration(state.MigrationSpec{ 182 InitiatedBy: names.NewUserTag("admin"), 183 TargetInfo: migration.TargetInfo{ 184 ControllerTag: controllerTag, 185 Addrs: []string{"1.2.3.4:5555"}, 186 CACert: coretesting.CACert, 187 AuthTag: names.NewUserTag("user2"), 188 Password: "secret", 189 }, 190 }) 191 c.Assert(err, jc.ErrorIsNil) 192 for _, phase := range migration.SuccessfulMigrationPhases() { 193 c.Assert(mig.SetPhase(phase), jc.ErrorIsNil) 194 } 195 c.Assert(model.Destroy(state.DestroyModelParams{}), jc.ErrorIsNil) 196 c.Assert(modelState.RemoveDyingModel(), jc.ErrorIsNil) 197 198 // Attempt to open an API connection to the migrated model as a user 199 // that had access to the model before it got migrated. 200 info := s.APIInfo(c) 201 info.ModelTag = model.ModelTag() 202 info.Tag = modelOwner.Tag() 203 info.Password = "secret" 204 _, err = api.Open(info, api.DialOpts{}) 205 206 redirErr, ok := errors.Cause(err).(*api.RedirectError) 207 c.Assert(ok, gc.Equals, true) 208 209 nhp := network.NewMachineHostPorts(5555, "1.2.3.4") 210 c.Assert(redirErr.Servers, jc.DeepEquals, []network.MachineHostPorts{nhp}) 211 c.Assert(redirErr.CACert, gc.Equals, coretesting.CACert) 212 c.Assert(redirErr.FollowRedirect, gc.Equals, false) 213 c.Assert(redirErr.ControllerTag, gc.Equals, controllerTag) 214 } 215 216 func (s *stateSuite) TestLoginMacaroonInvalidId(c *gc.C) { 217 apistate, tag, _ := s.OpenAPIWithoutLogin(c) 218 defer apistate.Close() 219 mac, err := macaroon.New([]byte("root-key"), []byte("id"), "juju", macaroon.LatestVersion) 220 c.Assert(err, jc.ErrorIsNil) 221 err = apistate.Login(tag, "", "", []macaroon.Slice{{mac}}) 222 c.Assert(err, gc.ErrorMatches, "interaction required but not possible") 223 } 224 225 func (s *stateSuite) TestBestFacadeVersion(c *gc.C) { 226 c.Check(s.APIState.BestFacadeVersion("Client"), gc.Equals, 7) 227 } 228 229 func (s *stateSuite) TestAPIHostPortsMovesConnectedValueFirst(c *gc.C) { 230 hostPortsList := s.APIState.APIHostPorts() 231 c.Check(hostPortsList, gc.HasLen, 1) 232 serverHostPorts := hostPortsList[0] 233 c.Check(serverHostPorts, gc.HasLen, 1) 234 goodAddress := serverHostPorts[0] 235 236 info := s.APIInfo(c) 237 238 // We intentionally set this to invalid values 239 badValue := network.MachineHostPort{ 240 MachineAddress: network.NewMachineAddress("0.1.2.3", network.WithScope(network.ScopeMachineLocal)), 241 NetPort: 1234, 242 } 243 badServer := []network.MachineHostPort{badValue} 244 245 extraAddress := network.MachineHostPort{ 246 MachineAddress: network.NewMachineAddress("0.1.2.4", network.WithScope(network.ScopeMachineLocal)), 247 NetPort: 5678, 248 } 249 extraAddress2 := network.MachineHostPort{ 250 MachineAddress: network.NewMachineAddress("0.1.2.1", network.WithScope(network.ScopeMachineLocal)), 251 NetPort: 9012, 252 } 253 254 current := []network.SpaceHostPorts{ 255 { 256 network.SpaceHostPort{ 257 SpaceAddress: network.SpaceAddress{MachineAddress: badValue.MachineAddress}, 258 NetPort: badValue.NetPort, 259 }, 260 }, 261 { 262 network.SpaceHostPort{ 263 SpaceAddress: network.SpaceAddress{MachineAddress: extraAddress.MachineAddress}, 264 NetPort: extraAddress.NetPort, 265 }, 266 network.SpaceHostPort{ 267 SpaceAddress: network.SpaceAddress{MachineAddress: goodAddress.MachineAddress}, 268 NetPort: goodAddress.NetPort, 269 }, 270 network.SpaceHostPort{ 271 SpaceAddress: network.SpaceAddress{MachineAddress: extraAddress2.MachineAddress}, 272 NetPort: extraAddress2.NetPort, 273 }, 274 }, 275 } 276 err := s.State.SetAPIHostPorts(current) 277 c.Assert(err, jc.ErrorIsNil) 278 279 apiState, err := api.Open(info, api.DialOpts{}) 280 c.Assert(err, jc.ErrorIsNil) 281 defer func() { _ = apiState.Close() }() 282 283 hostPorts := apiState.APIHostPorts() 284 // We should have rotate the server we connected to as the first item, 285 // and the address of that server as the first address 286 sortedServer := []network.MachineHostPort{ 287 goodAddress, extraAddress, extraAddress2, 288 } 289 expected := []network.MachineHostPorts{sortedServer, badServer} 290 c.Check(hostPorts, gc.DeepEquals, expected) 291 } 292 293 var exampleHostPorts = []network.MachineHostPort{ 294 {MachineAddress: network.NewMachineAddress("0.1.2.3"), NetPort: 1234}, 295 {MachineAddress: network.NewMachineAddress("0.1.2.4"), NetPort: 5678}, 296 {MachineAddress: network.NewMachineAddress("0.1.2.1"), NetPort: 9012}, 297 {MachineAddress: network.NewMachineAddress("0.1.9.1"), NetPort: 8888}, 298 } 299 300 func (s *slideSuite) TestSlideToFrontNoOp(c *gc.C) { 301 servers := []network.MachineHostPorts{ 302 {exampleHostPorts[0]}, 303 {exampleHostPorts[1]}, 304 } 305 // order should not have changed 306 expected := []network.MachineHostPorts{ 307 {exampleHostPorts[0]}, 308 {exampleHostPorts[1]}, 309 } 310 api.SlideAddressToFront(servers, 0, 0) 311 c.Check(servers, gc.DeepEquals, expected) 312 } 313 314 func (s *slideSuite) TestSlideToFrontAddress(c *gc.C) { 315 servers := []network.MachineHostPorts{ 316 {exampleHostPorts[0], exampleHostPorts[1], exampleHostPorts[2]}, 317 {exampleHostPorts[3]}, 318 } 319 // server order should not change, but ports should be switched 320 expected := []network.MachineHostPorts{ 321 {exampleHostPorts[1], exampleHostPorts[0], exampleHostPorts[2]}, 322 {exampleHostPorts[3]}, 323 } 324 api.SlideAddressToFront(servers, 0, 1) 325 c.Check(servers, gc.DeepEquals, expected) 326 } 327 328 func (s *slideSuite) TestSlideToFrontServer(c *gc.C) { 329 servers := []network.MachineHostPorts{ 330 {exampleHostPorts[0], exampleHostPorts[1]}, 331 {exampleHostPorts[2]}, 332 {exampleHostPorts[3]}, 333 } 334 // server 1 should be slid to the front 335 expected := []network.MachineHostPorts{ 336 {exampleHostPorts[2]}, 337 {exampleHostPorts[0], exampleHostPorts[1]}, 338 {exampleHostPorts[3]}, 339 } 340 api.SlideAddressToFront(servers, 1, 0) 341 c.Check(servers, gc.DeepEquals, expected) 342 } 343 344 func (s *slideSuite) TestSlideToFrontBoth(c *gc.C) { 345 servers := []network.MachineHostPorts{ 346 {exampleHostPorts[0]}, 347 {exampleHostPorts[1], exampleHostPorts[2]}, 348 {exampleHostPorts[3]}, 349 } 350 // server 1 should be slid to the front 351 expected := []network.MachineHostPorts{ 352 {exampleHostPorts[2], exampleHostPorts[1]}, 353 {exampleHostPorts[0]}, 354 {exampleHostPorts[3]}, 355 } 356 api.SlideAddressToFront(servers, 1, 1) 357 c.Check(servers, gc.DeepEquals, expected) 358 }