func (s *apiclientSuite) TestOpenPassesEnvironTag(c *gc.C) { info := s.APIInfo(c) env, err := s.State.Environment() c.Assert(err, gc.IsNil) // TODO(jam): 2014-06-05 http://pad.lv/1326802 // we want to test this eventually, but for now s.APIInfo uses // conn.StateInfo() which doesn't know about EnvironTag. // c.Check(info.EnvironTag, gc.Equals, env.Tag()) // c.Assert(info.EnvironTag, gc.Not(gc.Equals), "") // We start by ensuring we have an invalid tag, and Open should fail. info.EnvironTag = names.NewEnvironTag("bad-tag") _, err = api.Open(info, api.DialOpts{}) c.Check(err, gc.ErrorMatches, `unknown environment: "bad-tag"`) c.Check(params.ErrCode(err), gc.Equals, params.CodeNotFound) // Now set it to the right tag, and we should succeed. info.EnvironTag = env.Tag() st, err := api.Open(info, api.DialOpts{}) c.Assert(err, gc.IsNil) st.Close() // Backwards compatibility, we should succeed if we do not set an // environ tag info.EnvironTag = nil st, err = api.Open(info, api.DialOpts{}) c.Assert(err, gc.IsNil) st.Close() }
func (s *loginSuite) TestLoginRateLimited(c *gc.C) { info, cleanup := s.setupMachineAndServer(c) defer cleanup() delayChan, cleanup := apiserver.DelayLogins() defer cleanup() // Start enough concurrent Login requests so that we max out our // LoginRateLimit errResults, wg := startNLogins(c, apiserver.LoginRateLimit, info) // All of them should have started, none of them should have succeeded // (or failed) yet select { case err := <-errResults: c.Fatalf("we should not have gotten any logins yet: %v", err) case <-time.After(coretesting.ShortWait): } // We now have a bunch of pending Login requests, the next login // request should be immediately bounced _, err := api.Open(info, fastDialOpts) c.Check(err, gc.ErrorMatches, "try again") c.Check(err, jc.Satisfies, params.IsCodeTryAgain) // Let one request through, we should see that it succeeds without // error, and then be able to start a new request, but it will block delayChan <- struct{}{} select { case err := <-errResults: c.Check(err, gc.IsNil) case <-time.After(coretesting.LongWait): c.Fatalf("timed out expecting one login to succeed") } chOne := make(chan error, 1) wg.Add(1) go func() { st, err := api.Open(info, fastDialOpts) chOne <- err if err == nil { st.Close() } wg.Done() }() select { case err := <-chOne: c.Fatalf("the open request should not have completed: %v", err) case <-time.After(coretesting.ShortWait): } // Let all the logins finish. We started with LoginRateLimit, let one // proceed, but the issued another one, so there should be // LoginRateLimit logins pending. for i := 0; i < apiserver.LoginRateLimit; i++ { delayChan <- struct{}{} } wg.Wait() close(errResults) for err := range errResults { c.Check(err, gc.IsNil) } }
func (s *serverSuite) TestOpenAsMachineErrors(c *gc.C) { assertNotProvisioned := func(err error) { c.Assert(err, gc.NotNil) c.Assert(err, jc.Satisfies, params.IsCodeNotProvisioned) c.Assert(err, gc.ErrorMatches, `machine \d+ is not provisioned`) } stm, err := s.State.AddMachine("quantal", state.JobHostUnits) c.Assert(err, gc.IsNil) err = stm.SetProvisioned("foo", "fake_nonce", nil) c.Assert(err, gc.IsNil) password, err := utils.RandomPassword() c.Assert(err, gc.IsNil) err = stm.SetPassword(password) c.Assert(err, gc.IsNil) // This does almost exactly the same as OpenAPIAsMachine but checks // for failures instead. info := s.APIInfo(c) info.Tag = stm.Tag() info.Password = password info.Nonce = "invalid-nonce" st, err := api.Open(info, fastDialOpts) assertNotProvisioned(err) c.Assert(st, gc.IsNil) // Try with empty nonce as well. info.Nonce = "" st, err = api.Open(info, fastDialOpts) assertNotProvisioned(err) c.Assert(st, gc.IsNil) // Finally, with the correct one succeeds. info.Nonce = "fake_nonce" st, err = api.Open(info, fastDialOpts) c.Assert(err, gc.IsNil) c.Assert(st, gc.NotNil) st.Close() // Now add another machine, intentionally unprovisioned. stm1, err := s.State.AddMachine("quantal", state.JobHostUnits) c.Assert(err, gc.IsNil) err = stm1.SetPassword(password) c.Assert(err, gc.IsNil) // Try connecting, it will fail. info.Tag = stm1.Tag() info.Nonce = "" st, err = api.Open(info, fastDialOpts) assertNotProvisioned(err) c.Assert(st, gc.IsNil) }
func (s *loginSuite) TestBadLogin(c *gc.C) { // Start our own server so we can control when the first login // happens. Otherwise in JujuConnSuite.SetUpTest api.Open is // called with user-admin permissions automatically. info, cleanup := s.setupServer(c) defer cleanup() for i, t := range badLoginTests { c.Logf("test %d; entity %q; password %q", i, t.tag, t.password) // Note that Open does not log in if the tag and password // are empty. This allows us to test operations on the connection // before calling Login, which we could not do if Open // always logged in. info.Tag = "" info.Password = "" func() { st, err := api.Open(info, fastDialOpts) c.Assert(err, gc.IsNil) defer st.Close() _, err = st.Machiner().Machine(names.NewMachineTag("0")) c.Assert(err, gc.ErrorMatches, `unknown object type "Machiner"`) // Since these are user login tests, the nonce is empty. err = st.Login(t.tag, t.password, "") c.Assert(err, gc.ErrorMatches, t.err) c.Assert(params.ErrCode(err), gc.Equals, t.code) _, err = st.Machiner().Machine(names.NewMachineTag("0")) c.Assert(err, gc.ErrorMatches, `unknown object type "Machiner"`) }() } }
func ping0(apiInfo *api.Info, logf func(string, ...interface{})) (pingTimes, error) { var times = pingTimes{ addr: apiInfo.Addrs[0], open: -1, login: -1, } t0 := time.Now() tag, password := apiInfo.Tag, apiInfo.Password apiInfo.Tag = "" apiInfo.Password = "" logf("start") st, err := api.Open(apiInfo, dialOpts) if err != nil { return times, err } times.open = time.Since(t0) defer st.Close() logf("opened API") if err := st.Login(tag, password, ""); err != nil { return times, fmt.Errorf("cannot log in: %v", err) } times.login = time.Since(t0) - times.open logf("logged in") return times, nil }
func startNLogins(c *gc.C, n int, info *api.Info) (chan error, *sync.WaitGroup) { errResults := make(chan error, 100) var doneWG sync.WaitGroup var startedWG sync.WaitGroup c.Logf("starting %d concurrent logins to %v", n, info.Addrs) for i := 0; i < n; i++ { i := i c.Logf("starting login request %d", i) startedWG.Add(1) doneWG.Add(1) go func() { c.Logf("started login %d", i) startedWG.Done() st, err := api.Open(info, fastDialOpts) errResults <- err if err == nil { st.Close() } doneWG.Done() c.Logf("finished login %d: %v", i, err) }() } startedWG.Wait() return errResults, &doneWG }
func (s *UpgradeSuite) canLoginToAPI(info *api.Info) (out bool) { apiState, err := api.Open(info, upgradeTestDialOpts) if apiState != nil && err == nil { apiState.Close() return true } return false }
func (s *stateSuite) TestClientNoNeedToPing(c *gc.C) { s.PatchValue(apiserver.MaxClientPingInterval, time.Duration(0)) st, err := api.Open(s.APIInfo(c), api.DefaultDialOpts()) c.Assert(err, gc.IsNil) time.Sleep(coretesting.ShortWait) err = st.Ping() c.Assert(err, gc.IsNil) }
func (s *MachineSuite) TestManageEnvironServesAPI(c *gc.C) { s.assertJobWithState(c, state.JobManageEnviron, func(conf agent.Config, agentState *state.State) { st, err := api.Open(conf.APIInfo(), fastDialOpts) c.Assert(err, gc.IsNil) defer st.Close() m, err := st.Machiner().Machine(conf.Tag().(names.MachineTag)) c.Assert(err, gc.IsNil) c.Assert(m.Life(), gc.Equals, params.Alive) }) }
// OpenAPIWithoutLogin connects to the API and returns an api.State without // actually calling st.Login already. The returned strings are the "tag" and // "password" that we would have used to login. func (s *stateSuite) OpenAPIWithoutLogin(c *gc.C) (*api.State, string, string) { info := s.APIInfo(c) tag := info.Tag password := info.Password info.Tag = nil info.Password = "" apistate, err := api.Open(info, api.DialOpts{}) c.Assert(err, gc.IsNil) return apistate, tag.String(), password }
// openAPIAs opens the API and ensures that the *api.State returned will be // closed during the test teardown by using a cleanup function. func (s *JujuConnSuite) openAPIAs(c *gc.C, tag names.Tag, password, nonce string) *api.State { apiInfo := s.APIInfo(c) apiInfo.Tag = tag apiInfo.Password = password apiInfo.Nonce = nonce apiState, err := api.Open(apiInfo, api.DialOpts{}) c.Assert(err, gc.IsNil) c.Assert(apiState, gc.NotNil) s.apiStates = append(s.apiStates, apiState) return apiState }
// openAPIAs opens the API and ensures that the *api.State returned will be // closed during the test teardown by using a cleanup function. func (s *JujuConnSuite) openAPIAs(c *gc.C, tag, password, nonce string) *api.State { _, info, err := s.APIConn.Environ.StateInfo() c.Assert(err, gc.IsNil) info.Tag = tag info.Password = password info.Nonce = nonce apiState, err := api.Open(info, api.DialOpts{}) c.Assert(err, gc.IsNil) c.Assert(apiState, gc.NotNil) s.apiStates = append(s.apiStates, apiState) return apiState }
// NewAPIState creates an api.State object from an Environ func NewAPIState(environ environs.Environ, dialOpts api.DialOpts) (*api.State, error) { info, err := environAPIInfo(environ) if err != nil { return nil, err } st, err := api.Open(info, dialOpts) if err != nil { return nil, err } return st, nil }
func (s *UpgradeSuite) canLoginToAPIAsMachine(c *gc.C, config agent.Config) bool { // Ensure logins are always to the API server (machine-0) info := config.APIInfo() info.Addrs = s.machine0Config.APIInfo().Addrs apiState, err := api.Open(info, upgradeTestDialOpts) if apiState != nil { apiState.Close() } if apiState != nil && err == nil { return true } return false }
func (s *apiclientSuite) TestOpenMultiple(c *gc.C) { // Create a socket that proxies to the API server. info := s.APIInfo(c) serverAddr := info.Addrs[0] server, err := net.Dial("tcp", serverAddr) c.Assert(err, gc.IsNil) defer server.Close() listener, err := net.Listen("tcp", "127.0.0.1:0") c.Assert(err, gc.IsNil) defer listener.Close() go func() { for { client, err := listener.Accept() if err != nil { return } go io.Copy(client, server) go io.Copy(server, client) } }() // Check that we can use the proxy to connect. proxyAddr := listener.Addr().String() info.Addrs = []string{proxyAddr} st, err := api.Open(info, api.DialOpts{}) c.Assert(err, gc.IsNil) defer st.Close() c.Assert(st.Addr(), gc.Equals, proxyAddr) // Now break Addrs[0], and ensure that Addrs[1] // is successfully connected to. info.Addrs = []string{proxyAddr, serverAddr} listener.Close() st, err = api.Open(info, api.DialOpts{}) c.Assert(err, gc.IsNil) defer st.Close() c.Assert(st.Addr(), gc.Equals, serverAddr) }
func (s *serverSuite) TestStop(c *gc.C) { // Start our own instance of the server so we have // a handle on it to stop it. listener, err := net.Listen("tcp", ":0") c.Assert(err, gc.IsNil) srv, err := apiserver.NewServer(s.State, listener, apiserver.ServerConfig{ Cert: []byte(coretesting.ServerCert), Key: []byte(coretesting.ServerKey), }) c.Assert(err, gc.IsNil) defer srv.Stop() stm, err := s.State.AddMachine("quantal", state.JobHostUnits) c.Assert(err, gc.IsNil) err = stm.SetProvisioned("foo", "fake_nonce", nil) c.Assert(err, gc.IsNil) password, err := utils.RandomPassword() c.Assert(err, gc.IsNil) err = stm.SetPassword(password) c.Assert(err, gc.IsNil) // Note we can't use openAs because we're not connecting to apiInfo := &api.Info{ Tag: stm.Tag(), Password: password, Nonce: "fake_nonce", Addrs: []string{srv.Addr()}, CACert: coretesting.CACert, } st, err := api.Open(apiInfo, fastDialOpts) c.Assert(err, gc.IsNil) defer st.Close() _, err = st.Machiner().Machine(stm.Tag().(names.MachineTag)) c.Assert(err, gc.IsNil) err = srv.Stop() c.Assert(err, gc.IsNil) _, err = st.Machiner().Machine(stm.Tag().(names.MachineTag)) // The client has not necessarily seen the server shutdown yet, // so there are two possible errors. if err != rpc.ErrShutdown && err != io.ErrUnexpectedEOF { c.Fatalf("unexpected error from request: %v", err) } // Check it can be stopped twice. err = srv.Stop() c.Assert(err, gc.IsNil) }
func (s *clientSuite) TestOpenUsesEnvironUUIDPaths(c *gc.C) { info := s.APIInfo(c) // Backwards compatibility, passing EnvironTag = "" should just work info.EnvironTag = nil apistate, err := api.Open(info, api.DialOpts{}) c.Assert(err, gc.IsNil) apistate.Close() // Passing in the correct environment UUID should also work environ, err := s.State.Environment() c.Assert(err, gc.IsNil) info.EnvironTag = environ.Tag() apistate, err = api.Open(info, api.DialOpts{}) c.Assert(err, gc.IsNil) apistate.Close() // Passing in a bad environment UUID should fail with a known error info.EnvironTag = names.NewEnvironTag("dead-beef-123456") apistate, err = api.Open(info, api.DialOpts{}) c.Check(err, gc.ErrorMatches, `unknown environment: "dead-beef-123456"`) c.Check(err, jc.Satisfies, params.IsCodeNotFound) c.Assert(apistate, gc.IsNil) }
// openAs connects to the API state as the given entity // with the default password for that entity. func (s *baseSuite) openAs(c *gc.C, tag names.Tag) *api.State { info := s.APIInfo(c) info.Tag = tag // Must match defaultPassword() info.Password = fmt.Sprintf("%s password-1234567890", tag) // Set this always, so that the login attempts as a machine will // not fail with ErrNotProvisioned; it's not used otherwise. info.Nonce = "fake_nonce" c.Logf("opening state; entity %q; password %q", info.Tag, info.Password) st, err := api.Open(info, api.DialOpts{}) c.Assert(err, gc.IsNil) c.Assert(st, gc.NotNil) return st }
func (s *clientSuite) TestDebugLogRootPath(c *gc.C) { s.PatchValue(api.WebsocketDialConfig, echoURL(c)) // If the server is old, we log at "/log" info := s.APIInfo(c) info.EnvironTag = nil apistate, err := api.Open(info, api.DialOpts{}) c.Assert(err, gc.IsNil) defer apistate.Close() reader, err := apistate.Client().WatchDebugLog(api.DebugLogParams{}) c.Assert(err, gc.IsNil) connectURL := connectURLFromReader(c, reader) c.Assert(connectURL.Path, gc.Matches, "/log") }
func SimpleClientFactory(info *api.Info) (*Client, error) { dialOpts := api.DialOpts{} state, err := api.Open(info, dialOpts) if err != nil { return nil, err } client := state.Client() wrapper := &Client{} wrapper.client = client wrapper.apiState = state //defer apiclient.Close() return wrapper, err }
func (s *clientSuite) TestDebugLogAtUUIDLogPath(c *gc.C) { s.PatchValue(api.WebsocketDialConfig, echoURL(c)) // If the server supports it, we should log at "/environment/UUID/log" environ, err := s.State.Environment() c.Assert(err, gc.IsNil) info := s.APIInfo(c) info.EnvironTag = environ.Tag() apistate, err := api.Open(info, api.DialOpts{}) c.Assert(err, gc.IsNil) defer apistate.Close() reader, err := apistate.Client().WatchDebugLog(api.DebugLogParams{}) c.Assert(err, gc.IsNil) connectURL := connectURLFromReader(c, reader) c.ExpectFailure("debug log always goes to /log for compatibility http://pad.lv/1326799") c.Assert(connectURL.Path, gc.Matches, fmt.Sprintf("/%s/log", environ.UUID())) }
// NewAPIConn returns a new Conn that uses the // given environment. The environment must have already // been bootstrapped. func NewAPIConn(environ environs.Environ, dialOpts api.DialOpts) (*APIConn, error) { info, err := environAPIInfo(environ) if err != nil { return nil, err } st, err := api.Open(info, dialOpts) // TODO(rog): handle errUnauthorized when the API handles passwords. if err != nil { return nil, err } return &APIConn{ Environ: environ, State: st, }, nil }
func (s *stateSuite) TestLoginSetsEnvironTag(c *gc.C) { env, err := s.State.Environment() c.Assert(err, gc.IsNil) info := s.APIInfo(c) tag := info.Tag password := info.Password info.Tag = "" info.Password = "" apistate, err := api.Open(info, api.DialOpts{}) c.Assert(err, gc.IsNil) defer apistate.Close() // We haven't called Login yet, so the EnvironTag shouldn't be set. c.Check(apistate.EnvironTag(), gc.Equals, "") err = apistate.Login(tag, password, "") c.Assert(err, gc.IsNil) // Now that we've logged in, EnvironTag should be updated correctly. c.Check(apistate.EnvironTag(), gc.Equals, env.Tag()) }
func (s *loginSuite) runLoginWithValidator(c *gc.C, validator apiserver.LoginValidator) error { info, cleanup := s.setupServerWithValidator(c, validator) defer cleanup() info.Tag = "" info.Password = "" st, err := api.Open(info, fastDialOpts) c.Assert(err, gc.IsNil) defer st.Close() // Ensure not already logged in. _, err = st.Machiner().Machine(names.NewMachineTag("0")) c.Assert(err, gc.ErrorMatches, `unknown object type "Machiner"`) // Since these are user login tests, the nonce is empty. return st.Login("user-admin", "dummy-secret", "") }
func (s *apiclientSuite) TestOpenMultipleError(c *gc.C) { listener, err := net.Listen("tcp", "127.0.0.1:0") c.Assert(err, gc.IsNil) defer listener.Close() go func() { for { client, err := listener.Accept() if err != nil { return } client.Close() } }() info := s.APIInfo(c) addr := listener.Addr().String() info.Addrs = []string{addr, addr, addr} _, err = api.Open(info, api.DialOpts{}) c.Assert(err, gc.ErrorMatches, `unable to connect to "wss://.*/environment/[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}/api"`) }
func (s *loginSuite) TestLoginSetsLogIdentifier(c *gc.C) { info, cleanup := s.setupServer(c) defer cleanup() machineInState, err := s.State.AddMachine("quantal", state.JobHostUnits) c.Assert(err, gc.IsNil) err = machineInState.SetProvisioned("foo", "fake_nonce", nil) c.Assert(err, gc.IsNil) password, err := utils.RandomPassword() c.Assert(err, gc.IsNil) err = machineInState.SetPassword(password) c.Assert(err, gc.IsNil) c.Assert(machineInState.Tag(), gc.Equals, names.NewMachineTag("0")) var tw loggo.TestWriter c.Assert(loggo.RegisterWriter("login-tester", &tw, loggo.DEBUG), gc.IsNil) defer loggo.RemoveWriter("login-tester") // TODO(dfc) this should be a Tag info.Tag = machineInState.Tag().String() info.Password = password info.Nonce = "fake_nonce" apiConn, err := api.Open(info, fastDialOpts) c.Assert(err, gc.IsNil) defer apiConn.Close() // TODO(dfc) why does this return a string apiMachine, err := apiConn.Machiner().Machine(machineInState.Tag().(names.MachineTag)) c.Assert(err, gc.IsNil) c.Assert(apiMachine.Tag(), gc.Equals, machineInState.Tag().String()) c.Assert(tw.Log(), jc.LogMatches, []string{ `<- \[[0-9A-F]+\] <unknown> {"RequestId":1,"Type":"Admin","Request":"Login",` + `"Params":{"AuthTag":"machine-0","Password":"******"]*","Nonce":"fake_nonce"}` + `}`, // Now that we are logged in, we see the entity's tag // [0-9.umns] is to handle timestamps that are ns, us, ms, or s // long, though we expect it to be in the 'ms' range. `-> \[[0-9A-F]+\] machine-0 [0-9.]+[umn]?s {"RequestId":1,"Response":.*} Admin\[""\].Login`, `<- \[[0-9A-F]+\] machine-0 {"RequestId":2,"Type":"Machiner","Request":"Life","Params":{"Entities":\[{"Tag":"machine-0"}\]}}`, `-> \[[0-9A-F]+\] machine-0 [0-9.umns]+ {"RequestId":2,"Response":{"Results":\[{"Life":"alive","Error":null}\]}} Machiner\[""\]\.Life`, }) }
func (s *stateSuite) TestAPIHostPortsMovesConnectedValueFirst(c *gc.C) { hostportslist := s.APIState.APIHostPorts() c.Check(hostportslist, gc.HasLen, 1) serverhostports := hostportslist[0] c.Check(serverhostports, gc.HasLen, 1) goodAddress := serverhostports[0] // the other addresses, but always see this one as well. info := s.APIInfo(c) // We intentionally set this to invalid values badValue := network.HostPort{network.Address{ Value: "0.1.2.3", Type: network.IPv4Address, NetworkName: "", Scope: network.ScopeMachineLocal, }, 1234} badServer := []network.HostPort{badValue} extraAddress := network.HostPort{network.Address{ Value: "0.1.2.4", Type: network.IPv4Address, NetworkName: "", Scope: network.ScopeMachineLocal, }, 5678} extraAddress2 := network.HostPort{network.Address{ Value: "0.1.2.1", Type: network.IPv4Address, NetworkName: "", Scope: network.ScopeMachineLocal, }, 9012} serverExtra := []network.HostPort{extraAddress, goodAddress, extraAddress2} current := [][]network.HostPort{badServer, serverExtra} s.State.SetAPIHostPorts(current) apistate, err := api.Open(info, api.DialOpts{}) c.Assert(err, gc.IsNil) defer apistate.Close() hostports := apistate.APIHostPorts() // We should have rotate the server we connected to as the first item, // and the address of that server as the first address sortedServer := []network.HostPort{goodAddress, extraAddress, extraAddress2} expected := [][]network.HostPort{sortedServer, badServer} c.Check(hostports, gc.DeepEquals, expected) }
func (s *loginSuite) TestLoginReportsEnvironTag(c *gc.C) { info, cleanup := s.setupServer(c) defer cleanup() // If we call api.Open without giving a username and password, then it // won't call Login, so we can call it ourselves. // We Login without passing an EnvironTag, to show that it still lets // us in, and that we can find out the real EnvironTag from the // response. st, err := api.Open(info, fastDialOpts) c.Assert(err, gc.IsNil) var result params.LoginResult creds := ¶ms.Creds{ AuthTag: "user-admin", Password: "******", } err = st.Call("Admin", "", "Login", creds, &result) c.Assert(err, gc.IsNil) env, err := s.State.Environment() c.Assert(err, gc.IsNil) c.Assert(result.EnvironTag, gc.Equals, env.Tag()) }
func (s *stateSuite) TestAPIHostPortsAlwaysIncludesTheConnection(c *gc.C) { hostportslist := s.APIState.APIHostPorts() c.Check(hostportslist, gc.HasLen, 1) serverhostports := hostportslist[0] c.Check(serverhostports, gc.HasLen, 1) // the other addresses, but always see this one as well. info := s.APIInfo(c) // We intentionally set this to invalid values badValue := network.HostPort{network.Address{ Value: "0.1.2.3", Type: network.IPv4Address, NetworkName: "", Scope: network.ScopeMachineLocal, }, 1234} badServer := []network.HostPort{badValue} s.State.SetAPIHostPorts([][]network.HostPort{badServer}) apistate, err := api.Open(info, api.DialOpts{}) c.Assert(err, gc.IsNil) defer apistate.Close() hostports := apistate.APIHostPorts() c.Check(hostports, gc.DeepEquals, [][]network.HostPort{serverhostports, badServer}) }
func (s *UpgradeSuite) checkLoginToAPIAsUser(c *gc.C, expectFullApi exposedAPI) { info := s.machine0Config.APIInfo() info.Tag = names.NewUserTag("admin") info.Password = "******" info.Nonce = "" apiState, err := api.Open(info, upgradeTestDialOpts) c.Assert(err, gc.IsNil) defer apiState.Close() // this call should always work var result api.Status err = apiState.Call("Client", "", "FullStatus", nil, &result) c.Assert(err, gc.IsNil) // this call should only work if API is not restricted err = apiState.Call("Client", "", "DestroyEnvironment", nil, nil) if expectFullApi { c.Assert(err, gc.IsNil) } else { c.Assert(err, gc.ErrorMatches, "upgrade in progress .+") } }