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 = &params.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  }