github.com/mhilton/juju-juju@v0.0.0-20150901100907-a94dd2c73455/api/client_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  	"bufio"
     8  	"bytes"
     9  	"encoding/json"
    10  	"fmt"
    11  	"io"
    12  	"io/ioutil"
    13  	"net"
    14  	"net/http"
    15  	"net/url"
    16  	"path"
    17  	"strings"
    18  
    19  	"github.com/juju/errors"
    20  	"github.com/juju/loggo"
    21  	"github.com/juju/names"
    22  	jc "github.com/juju/testing/checkers"
    23  	"golang.org/x/net/websocket"
    24  	gc "gopkg.in/check.v1"
    25  	"gopkg.in/juju/charm.v5"
    26  
    27  	"github.com/juju/juju/api"
    28  	"github.com/juju/juju/apiserver/params"
    29  	jujunames "github.com/juju/juju/juju/names"
    30  	jujutesting "github.com/juju/juju/juju/testing"
    31  	"github.com/juju/juju/state"
    32  	"github.com/juju/juju/testcharms"
    33  	coretesting "github.com/juju/juju/testing"
    34  	"github.com/juju/juju/testing/factory"
    35  	"github.com/juju/juju/version"
    36  )
    37  
    38  type clientSuite struct {
    39  	jujutesting.JujuConnSuite
    40  }
    41  
    42  var _ = gc.Suite(&clientSuite{})
    43  
    44  // TODO(jam) 2013-08-27 http://pad.lv/1217282
    45  // Right now most of the direct tests for api.Client behavior are in
    46  // apiserver/client/*_test.go
    47  
    48  func (s *clientSuite) TestCloseMultipleOk(c *gc.C) {
    49  	client := s.APIState.Client()
    50  	c.Assert(client.Close(), gc.IsNil)
    51  	c.Assert(client.Close(), gc.IsNil)
    52  	c.Assert(client.Close(), gc.IsNil)
    53  }
    54  
    55  func (s *clientSuite) TestUploadToolsOtherEnvironment(c *gc.C) {
    56  	otherSt, otherAPISt := s.otherEnviron(c)
    57  	defer otherSt.Close()
    58  	defer otherAPISt.Close()
    59  	client := otherAPISt.Client()
    60  	newVersion := version.MustParseBinary("5.4.3-quantal-amd64")
    61  	var called bool
    62  
    63  	// build fake tools
    64  	expectedTools, _ := coretesting.TarGz(
    65  		coretesting.NewTarFile(jujunames.Jujud, 0777, "jujud contents "+newVersion.String()))
    66  
    67  	// UploadTools does not use the facades, so instead of patching the
    68  	// facade call, we set up a fake endpoint to test.
    69  	defer fakeAPIEndpoint(c, client, envEndpoint(c, otherAPISt, "tools"), "POST",
    70  		func(w http.ResponseWriter, r *http.Request) {
    71  			called = true
    72  
    73  			c.Assert(r.URL.Query(), gc.DeepEquals, url.Values{
    74  				"binaryVersion": []string{"5.4.3-quantal-amd64"},
    75  				"series":        []string{""},
    76  			})
    77  			defer r.Body.Close()
    78  			obtainedTools, err := ioutil.ReadAll(r.Body)
    79  			c.Assert(err, jc.ErrorIsNil)
    80  			c.Assert(obtainedTools, gc.DeepEquals, expectedTools)
    81  		},
    82  	).Close()
    83  
    84  	// We don't test the error or tools results as we only wish to assert that
    85  	// the API client POSTs the tools archive to the correct endpoint.
    86  	client.UploadTools(bytes.NewReader(expectedTools), newVersion)
    87  	c.Assert(called, jc.IsTrue)
    88  }
    89  
    90  func (s *clientSuite) TestAddLocalCharm(c *gc.C) {
    91  	charmArchive := testcharms.Repo.CharmArchive(c.MkDir(), "dummy")
    92  	curl := charm.MustParseURL(
    93  		fmt.Sprintf("local:quantal/%s-%d", charmArchive.Meta().Name, charmArchive.Revision()),
    94  	)
    95  	client := s.APIState.Client()
    96  
    97  	// Test the sanity checks first.
    98  	_, err := client.AddLocalCharm(charm.MustParseURL("cs:quantal/wordpress-1"), nil)
    99  	c.Assert(err, gc.ErrorMatches, `expected charm URL with local: schema, got "cs:quantal/wordpress-1"`)
   100  
   101  	// Upload an archive with its original revision.
   102  	savedURL, err := client.AddLocalCharm(curl, charmArchive)
   103  	c.Assert(err, jc.ErrorIsNil)
   104  	c.Assert(savedURL.String(), gc.Equals, curl.String())
   105  
   106  	// Upload a charm directory with changed revision.
   107  	charmDir := testcharms.Repo.ClonedDir(c.MkDir(), "dummy")
   108  	charmDir.SetDiskRevision(42)
   109  	savedURL, err = client.AddLocalCharm(curl, charmDir)
   110  	c.Assert(err, jc.ErrorIsNil)
   111  	c.Assert(savedURL.Revision, gc.Equals, 42)
   112  
   113  	// Upload a charm directory again, revision should be bumped.
   114  	savedURL, err = client.AddLocalCharm(curl, charmDir)
   115  	c.Assert(err, jc.ErrorIsNil)
   116  	c.Assert(savedURL.String(), gc.Equals, curl.WithRevision(43).String())
   117  }
   118  
   119  func (s *clientSuite) TestAddLocalCharmOtherEnvironment(c *gc.C) {
   120  	charmArchive := testcharms.Repo.CharmArchive(c.MkDir(), "dummy")
   121  	curl := charm.MustParseURL(
   122  		fmt.Sprintf("local:quantal/%s-%d", charmArchive.Meta().Name, charmArchive.Revision()),
   123  	)
   124  
   125  	otherSt, otherAPISt := s.otherEnviron(c)
   126  	defer otherSt.Close()
   127  	defer otherAPISt.Close()
   128  	client := otherAPISt.Client()
   129  
   130  	// Upload an archive
   131  	savedURL, err := client.AddLocalCharm(curl, charmArchive)
   132  	c.Assert(err, jc.ErrorIsNil)
   133  	c.Assert(savedURL.String(), gc.Equals, curl.String())
   134  
   135  	charm, err := otherSt.Charm(curl)
   136  	c.Assert(err, jc.ErrorIsNil)
   137  	c.Assert(charm.String(), gc.Equals, curl.String())
   138  }
   139  
   140  func (s *clientSuite) otherEnviron(c *gc.C) (*state.State, api.Connection) {
   141  	otherSt := s.Factory.MakeEnvironment(c, nil)
   142  	info := s.APIInfo(c)
   143  	info.EnvironTag = otherSt.EnvironTag()
   144  	apiState, err := api.Open(info, api.DefaultDialOpts())
   145  	c.Assert(err, jc.ErrorIsNil)
   146  	return otherSt, apiState
   147  }
   148  
   149  func (s *clientSuite) TestAddLocalCharmError(c *gc.C) {
   150  	client := s.APIState.Client()
   151  
   152  	// AddLocalCharm does not use the facades, so instead of patching the
   153  	// facade call, we set up a fake endpoint to test.
   154  	defer fakeAPIEndpoint(c, client, envEndpoint(c, s.APIState, "charms"), "POST",
   155  		func(w http.ResponseWriter, r *http.Request) {
   156  			http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed)
   157  		},
   158  	).Close()
   159  
   160  	charmArchive := testcharms.Repo.CharmArchive(c.MkDir(), "dummy")
   161  	curl := charm.MustParseURL(
   162  		fmt.Sprintf("local:quantal/%s-%d", charmArchive.Meta().Name, charmArchive.Revision()),
   163  	)
   164  
   165  	_, err := client.AddLocalCharm(curl, charmArchive)
   166  	c.Assert(err, gc.ErrorMatches, "charm upload failed: 405 \\(Method Not Allowed\\)")
   167  }
   168  
   169  func fakeAPIEndpoint(c *gc.C, client *api.Client, address, method string, handle func(http.ResponseWriter, *http.Request)) net.Listener {
   170  	lis, err := net.Listen("tcp", "127.0.0.1:0")
   171  	c.Assert(err, jc.ErrorIsNil)
   172  
   173  	http.HandleFunc(address, func(w http.ResponseWriter, r *http.Request) {
   174  		if r.Method == method {
   175  			handle(w, r)
   176  		}
   177  	})
   178  	go func() {
   179  		http.Serve(lis, nil)
   180  	}()
   181  	api.SetServerAddress(client, "http", lis.Addr().String())
   182  	return lis
   183  }
   184  
   185  // envEndpoint returns "/environment/<env-uuid>/<destination>"
   186  func envEndpoint(c *gc.C, apiState api.Connection, destination string) string {
   187  	envTag, err := apiState.EnvironTag()
   188  	c.Assert(err, jc.ErrorIsNil)
   189  	return path.Join("/environment", envTag.Id(), destination)
   190  }
   191  
   192  func (s *clientSuite) TestClientEnvironmentUUID(c *gc.C) {
   193  	environ, err := s.State.Environment()
   194  	c.Assert(err, jc.ErrorIsNil)
   195  
   196  	client := s.APIState.Client()
   197  	c.Assert(client.EnvironmentUUID(), gc.Equals, environ.Tag().Id())
   198  }
   199  
   200  func (s *clientSuite) TestClientEnvironmentUsers(c *gc.C) {
   201  	client := s.APIState.Client()
   202  	cleanup := api.PatchClientFacadeCall(client,
   203  		func(request string, paramsIn interface{}, response interface{}) error {
   204  			c.Assert(paramsIn, gc.IsNil)
   205  			if response, ok := response.(*params.EnvUserInfoResults); ok {
   206  				response.Results = []params.EnvUserInfoResult{
   207  					{Result: &params.EnvUserInfo{UserName: "one"}},
   208  					{Result: &params.EnvUserInfo{UserName: "two"}},
   209  					{Result: &params.EnvUserInfo{UserName: "three"}},
   210  				}
   211  			} else {
   212  				c.Log("wrong output structure")
   213  				c.Fail()
   214  			}
   215  			return nil
   216  		},
   217  	)
   218  	defer cleanup()
   219  
   220  	obtained, err := client.EnvironmentUserInfo()
   221  	c.Assert(err, jc.ErrorIsNil)
   222  
   223  	c.Assert(obtained, jc.DeepEquals, []params.EnvUserInfo{
   224  		{UserName: "one"},
   225  		{UserName: "two"},
   226  		{UserName: "three"},
   227  	})
   228  }
   229  
   230  func (s *clientSuite) TestShareEnvironmentExistingUser(c *gc.C) {
   231  	client := s.APIState.Client()
   232  	user := s.Factory.MakeEnvUser(c, nil)
   233  	cleanup := api.PatchClientFacadeCall(client,
   234  		func(request string, paramsIn interface{}, response interface{}) error {
   235  			if users, ok := paramsIn.(params.ModifyEnvironUsers); ok {
   236  				c.Assert(users.Changes, gc.HasLen, 1)
   237  				c.Logf(string(users.Changes[0].Action), gc.Equals, string(params.AddEnvUser))
   238  				c.Logf(users.Changes[0].UserTag, gc.Equals, user.UserTag().String())
   239  			} else {
   240  				c.Fatalf("wrong input structure")
   241  			}
   242  			if result, ok := response.(*params.ErrorResults); ok {
   243  				err := &params.Error{
   244  					Message: "error message",
   245  					Code:    params.CodeAlreadyExists,
   246  				}
   247  				*result = params.ErrorResults{Results: []params.ErrorResult{{Error: err}}}
   248  			} else {
   249  				c.Fatalf("wrong input structure")
   250  			}
   251  			return nil
   252  		},
   253  	)
   254  	defer cleanup()
   255  
   256  	err := client.ShareEnvironment(user.UserTag())
   257  	c.Assert(err, jc.ErrorIsNil)
   258  	logMsg := fmt.Sprintf("WARNING juju.api environment is already shared with %s", user.UserName())
   259  	c.Assert(c.GetTestLog(), jc.Contains, logMsg)
   260  }
   261  
   262  func (s *clientSuite) TestDestroyEnvironment(c *gc.C) {
   263  	client := s.APIState.Client()
   264  	var called bool
   265  	cleanup := api.PatchClientFacadeCall(client,
   266  		func(req string, args interface{}, resp interface{}) error {
   267  			c.Assert(req, gc.Equals, "DestroyEnvironment")
   268  			called = true
   269  			return nil
   270  		})
   271  	defer cleanup()
   272  
   273  	err := client.DestroyEnvironment()
   274  	c.Assert(err, jc.ErrorIsNil)
   275  	c.Assert(called, jc.IsTrue)
   276  }
   277  
   278  func (s *clientSuite) TestShareEnvironmentThreeUsers(c *gc.C) {
   279  	client := s.APIState.Client()
   280  	existingUser := s.Factory.MakeEnvUser(c, nil)
   281  	localUser := s.Factory.MakeUser(c, nil)
   282  	newUserTag := names.NewUserTag("foo@bar")
   283  	cleanup := api.PatchClientFacadeCall(client,
   284  		func(request string, paramsIn interface{}, response interface{}) error {
   285  			if users, ok := paramsIn.(params.ModifyEnvironUsers); ok {
   286  				c.Assert(users.Changes, gc.HasLen, 3)
   287  				c.Assert(string(users.Changes[0].Action), gc.Equals, string(params.AddEnvUser))
   288  				c.Assert(users.Changes[0].UserTag, gc.Equals, existingUser.UserTag().String())
   289  				c.Assert(string(users.Changes[1].Action), gc.Equals, string(params.AddEnvUser))
   290  				c.Assert(users.Changes[1].UserTag, gc.Equals, localUser.UserTag().String())
   291  				c.Assert(string(users.Changes[2].Action), gc.Equals, string(params.AddEnvUser))
   292  				c.Assert(users.Changes[2].UserTag, gc.Equals, newUserTag.String())
   293  			} else {
   294  				c.Log("wrong input structure")
   295  				c.Fail()
   296  			}
   297  			if result, ok := response.(*params.ErrorResults); ok {
   298  				err := &params.Error{Message: "existing user"}
   299  				*result = params.ErrorResults{Results: []params.ErrorResult{{Error: err}, {Error: nil}, {Error: nil}}}
   300  			} else {
   301  				c.Log("wrong output structure")
   302  				c.Fail()
   303  			}
   304  			return nil
   305  		},
   306  	)
   307  	defer cleanup()
   308  
   309  	err := client.ShareEnvironment(existingUser.UserTag(), localUser.UserTag(), newUserTag)
   310  	c.Assert(err, gc.ErrorMatches, `existing user`)
   311  }
   312  
   313  func (s *clientSuite) TestUnshareEnvironmentThreeUsers(c *gc.C) {
   314  	client := s.APIState.Client()
   315  	missingUser := s.Factory.MakeEnvUser(c, nil)
   316  	localUser := s.Factory.MakeUser(c, nil)
   317  	newUserTag := names.NewUserTag("foo@bar")
   318  	cleanup := api.PatchClientFacadeCall(client,
   319  		func(request string, paramsIn interface{}, response interface{}) error {
   320  			if users, ok := paramsIn.(params.ModifyEnvironUsers); ok {
   321  				c.Assert(users.Changes, gc.HasLen, 3)
   322  				c.Assert(string(users.Changes[0].Action), gc.Equals, string(params.RemoveEnvUser))
   323  				c.Assert(users.Changes[0].UserTag, gc.Equals, missingUser.UserTag().String())
   324  				c.Assert(string(users.Changes[1].Action), gc.Equals, string(params.RemoveEnvUser))
   325  				c.Assert(users.Changes[1].UserTag, gc.Equals, localUser.UserTag().String())
   326  				c.Assert(string(users.Changes[2].Action), gc.Equals, string(params.RemoveEnvUser))
   327  				c.Assert(users.Changes[2].UserTag, gc.Equals, newUserTag.String())
   328  			} else {
   329  				c.Log("wrong input structure")
   330  				c.Fail()
   331  			}
   332  			if result, ok := response.(*params.ErrorResults); ok {
   333  				err := &params.Error{Message: "error unsharing user"}
   334  				*result = params.ErrorResults{Results: []params.ErrorResult{{Error: err}, {Error: nil}, {Error: nil}}}
   335  			} else {
   336  				c.Log("wrong output structure")
   337  				c.Fail()
   338  			}
   339  			return nil
   340  		},
   341  	)
   342  	defer cleanup()
   343  
   344  	err := client.UnshareEnvironment(missingUser.UserTag(), localUser.UserTag(), newUserTag)
   345  	c.Assert(err, gc.ErrorMatches, "error unsharing user")
   346  }
   347  
   348  func (s *clientSuite) TestUnshareEnvironmentMissingUser(c *gc.C) {
   349  	client := s.APIState.Client()
   350  	user := names.NewUserTag("bob@local")
   351  	cleanup := api.PatchClientFacadeCall(client,
   352  		func(request string, paramsIn interface{}, response interface{}) error {
   353  			if users, ok := paramsIn.(params.ModifyEnvironUsers); ok {
   354  				c.Assert(users.Changes, gc.HasLen, 1)
   355  				c.Logf(string(users.Changes[0].Action), gc.Equals, string(params.RemoveEnvUser))
   356  				c.Logf(users.Changes[0].UserTag, gc.Equals, user.String())
   357  			} else {
   358  				c.Fatalf("wrong input structure")
   359  			}
   360  			if result, ok := response.(*params.ErrorResults); ok {
   361  				err := &params.Error{
   362  					Message: "error message",
   363  					Code:    params.CodeNotFound,
   364  				}
   365  				*result = params.ErrorResults{Results: []params.ErrorResult{{Error: err}}}
   366  			} else {
   367  				c.Fatalf("wrong input structure")
   368  			}
   369  			return nil
   370  		},
   371  	)
   372  	defer cleanup()
   373  
   374  	err := client.UnshareEnvironment(user)
   375  	c.Assert(err, jc.ErrorIsNil)
   376  	logMsg := fmt.Sprintf("WARNING juju.api environment was not previously shared with user %s", user.Username())
   377  	c.Assert(c.GetTestLog(), jc.Contains, logMsg)
   378  }
   379  
   380  func (s *clientSuite) TestWatchDebugLogConnected(c *gc.C) {
   381  	// Shows both the unmarshalling of a real error, and
   382  	// that the api server is connected.
   383  	client := s.APIState.Client()
   384  	reader, err := client.WatchDebugLog(api.DebugLogParams{})
   385  	c.Assert(err, gc.ErrorMatches, "cannot open log file: .*")
   386  	c.Assert(reader, gc.IsNil)
   387  }
   388  
   389  func (s *clientSuite) TestConnectionErrorBadConnection(c *gc.C) {
   390  	s.PatchValue(api.WebsocketDialConfig, func(_ *websocket.Config) (io.ReadCloser, error) {
   391  		return nil, fmt.Errorf("bad connection")
   392  	})
   393  	client := s.APIState.Client()
   394  	reader, err := client.WatchDebugLog(api.DebugLogParams{})
   395  	c.Assert(err, gc.ErrorMatches, "bad connection")
   396  	c.Assert(reader, gc.IsNil)
   397  }
   398  
   399  func (s *clientSuite) TestConnectionErrorNoData(c *gc.C) {
   400  	s.PatchValue(api.WebsocketDialConfig, func(_ *websocket.Config) (io.ReadCloser, error) {
   401  		return ioutil.NopCloser(&bytes.Buffer{}), nil
   402  	})
   403  	client := s.APIState.Client()
   404  	reader, err := client.WatchDebugLog(api.DebugLogParams{})
   405  	c.Assert(err, gc.ErrorMatches, "unable to read initial response: EOF")
   406  	c.Assert(reader, gc.IsNil)
   407  }
   408  
   409  func (s *clientSuite) TestConnectionErrorBadData(c *gc.C) {
   410  	s.PatchValue(api.WebsocketDialConfig, func(_ *websocket.Config) (io.ReadCloser, error) {
   411  		junk := strings.NewReader("junk\n")
   412  		return ioutil.NopCloser(junk), nil
   413  	})
   414  	client := s.APIState.Client()
   415  	reader, err := client.WatchDebugLog(api.DebugLogParams{})
   416  	c.Assert(err, gc.ErrorMatches, "unable to unmarshal initial response: .*")
   417  	c.Assert(reader, gc.IsNil)
   418  }
   419  
   420  func (s *clientSuite) TestConnectionErrorReadError(c *gc.C) {
   421  	s.PatchValue(api.WebsocketDialConfig, func(_ *websocket.Config) (io.ReadCloser, error) {
   422  		err := fmt.Errorf("bad read")
   423  		return ioutil.NopCloser(&badReader{err}), nil
   424  	})
   425  	client := s.APIState.Client()
   426  	reader, err := client.WatchDebugLog(api.DebugLogParams{})
   427  	c.Assert(err, gc.ErrorMatches, "unable to read initial response: bad read")
   428  	c.Assert(reader, gc.IsNil)
   429  }
   430  
   431  func (s *clientSuite) TestParamsEncoded(c *gc.C) {
   432  	s.PatchValue(api.WebsocketDialConfig, echoURL(c))
   433  
   434  	params := api.DebugLogParams{
   435  		IncludeEntity: []string{"a", "b"},
   436  		IncludeModule: []string{"c", "d"},
   437  		ExcludeEntity: []string{"e", "f"},
   438  		ExcludeModule: []string{"g", "h"},
   439  		Limit:         100,
   440  		Backlog:       200,
   441  		Level:         loggo.ERROR,
   442  		Replay:        true,
   443  	}
   444  
   445  	client := s.APIState.Client()
   446  	reader, err := client.WatchDebugLog(params)
   447  	c.Assert(err, jc.ErrorIsNil)
   448  
   449  	connectURL := connectURLFromReader(c, reader)
   450  	values := connectURL.Query()
   451  	c.Assert(values, jc.DeepEquals, url.Values{
   452  		"includeEntity": params.IncludeEntity,
   453  		"includeModule": params.IncludeModule,
   454  		"excludeEntity": params.ExcludeEntity,
   455  		"excludeModule": params.ExcludeModule,
   456  		"maxLines":      {"100"},
   457  		"backlog":       {"200"},
   458  		"level":         {"ERROR"},
   459  		"replay":        {"true"},
   460  	})
   461  }
   462  
   463  func (s *clientSuite) TestDebugLogRootPath(c *gc.C) {
   464  	s.PatchValue(api.WebsocketDialConfig, echoURL(c))
   465  
   466  	// If the server is old, we log at "/log"
   467  	info := s.APIInfo(c)
   468  	info.EnvironTag = names.NewEnvironTag("")
   469  	apistate, err := api.OpenWithVersion(info, api.DialOpts{}, 1)
   470  	c.Assert(err, jc.ErrorIsNil)
   471  	defer apistate.Close()
   472  	reader, err := apistate.Client().WatchDebugLog(api.DebugLogParams{})
   473  	c.Assert(err, jc.ErrorIsNil)
   474  	connectURL := connectURLFromReader(c, reader)
   475  	c.Assert(connectURL.Path, gc.Matches, "/log")
   476  }
   477  
   478  func (s *clientSuite) TestDebugLogAtUUIDLogPath(c *gc.C) {
   479  	s.PatchValue(api.WebsocketDialConfig, echoURL(c))
   480  	// If the server supports it, we should log at "/environment/UUID/log"
   481  	environ, err := s.State.Environment()
   482  	c.Assert(err, jc.ErrorIsNil)
   483  	info := s.APIInfo(c)
   484  	info.EnvironTag = environ.EnvironTag()
   485  	apistate, err := api.Open(info, api.DialOpts{})
   486  	c.Assert(err, jc.ErrorIsNil)
   487  	defer apistate.Close()
   488  	reader, err := apistate.Client().WatchDebugLog(api.DebugLogParams{})
   489  	c.Assert(err, jc.ErrorIsNil)
   490  	connectURL := connectURLFromReader(c, reader)
   491  	c.Assert(connectURL.Path, gc.Matches, fmt.Sprintf("/environment/%s/log", environ.UUID()))
   492  }
   493  
   494  func (s *clientSuite) TestOpenUsesEnvironUUIDPaths(c *gc.C) {
   495  	info := s.APIInfo(c)
   496  	// Backwards compatibility, passing EnvironTag = "" should just work
   497  	info.EnvironTag = names.NewEnvironTag("")
   498  	apistate, err := api.Open(info, api.DialOpts{})
   499  	c.Assert(err, jc.ErrorIsNil)
   500  	apistate.Close()
   501  
   502  	// Passing in the correct environment UUID should also work
   503  	environ, err := s.State.Environment()
   504  	c.Assert(err, jc.ErrorIsNil)
   505  	info.EnvironTag = environ.EnvironTag()
   506  	apistate, err = api.Open(info, api.DialOpts{})
   507  	c.Assert(err, jc.ErrorIsNil)
   508  	apistate.Close()
   509  
   510  	// Passing in a bad environment UUID should fail with a known error
   511  	info.EnvironTag = names.NewEnvironTag("dead-beef-123456")
   512  	apistate, err = api.Open(info, api.DialOpts{})
   513  	c.Check(err, gc.ErrorMatches, `unknown environment: "dead-beef-123456"`)
   514  	c.Check(err, jc.Satisfies, params.IsCodeNotFound)
   515  	c.Assert(apistate, gc.IsNil)
   516  }
   517  
   518  func (s *clientSuite) TestSetEnvironAgentVersionDuringUpgrade(c *gc.C) {
   519  	// This is an integration test which ensure that a test with the
   520  	// correct error code is seen by the client from the
   521  	// SetEnvironAgentVersion call when an upgrade is in progress.
   522  	envConfig, err := s.State.EnvironConfig()
   523  	c.Assert(err, jc.ErrorIsNil)
   524  	agentVersion, ok := envConfig.AgentVersion()
   525  	c.Assert(ok, jc.IsTrue)
   526  	machine := s.Factory.MakeMachine(c, &factory.MachineParams{
   527  		Jobs: []state.MachineJob{state.JobManageEnviron},
   528  	})
   529  	err = machine.SetAgentVersion(version.MustParseBinary(agentVersion.String() + "-quantal-amd64"))
   530  	c.Assert(err, jc.ErrorIsNil)
   531  	nextVersion := version.MustParse("9.8.7")
   532  	_, err = s.State.EnsureUpgradeInfo(machine.Id(), agentVersion, nextVersion)
   533  	c.Assert(err, jc.ErrorIsNil)
   534  
   535  	err = s.APIState.Client().SetEnvironAgentVersion(nextVersion)
   536  
   537  	// Expect an error with a error code that indicates this specific
   538  	// situation. The client needs to be able to reliably identify
   539  	// this error and handle it differently to other errors.
   540  	c.Assert(params.IsCodeUpgradeInProgress(err), jc.IsTrue)
   541  }
   542  
   543  func (s *clientSuite) TestAbortCurrentUpgrade(c *gc.C) {
   544  	client := s.APIState.Client()
   545  	someErr := errors.New("random")
   546  	cleanup := api.PatchClientFacadeCall(client,
   547  		func(request string, args interface{}, response interface{}) error {
   548  			c.Assert(request, gc.Equals, "AbortCurrentUpgrade")
   549  			c.Assert(args, gc.IsNil)
   550  			c.Assert(response, gc.IsNil)
   551  			return someErr
   552  		},
   553  	)
   554  	defer cleanup()
   555  
   556  	err := client.AbortCurrentUpgrade()
   557  	c.Assert(err, gc.Equals, someErr) // Confirms that the correct facade was called
   558  }
   559  
   560  func (s *clientSuite) TestEnvironmentGet(c *gc.C) {
   561  	client := s.APIState.Client()
   562  	env, err := client.EnvironmentGet()
   563  	c.Assert(err, jc.ErrorIsNil)
   564  	// Check a known value, just checking that there is something there.
   565  	c.Assert(env["type"], gc.Equals, "dummy")
   566  }
   567  
   568  func (s *clientSuite) TestEnvironmentSet(c *gc.C) {
   569  	client := s.APIState.Client()
   570  	err := client.EnvironmentSet(map[string]interface{}{
   571  		"some-name":  "value",
   572  		"other-name": true,
   573  	})
   574  	c.Assert(err, jc.ErrorIsNil)
   575  	// Check them using EnvironmentGet.
   576  	env, err := client.EnvironmentGet()
   577  	c.Assert(err, jc.ErrorIsNil)
   578  	c.Assert(env["some-name"], gc.Equals, "value")
   579  	c.Assert(env["other-name"], gc.Equals, true)
   580  }
   581  
   582  func (s *clientSuite) TestEnvironmentUnset(c *gc.C) {
   583  	client := s.APIState.Client()
   584  	err := client.EnvironmentSet(map[string]interface{}{
   585  		"some-name": "value",
   586  	})
   587  	c.Assert(err, jc.ErrorIsNil)
   588  
   589  	// Now unset it and make sure it isn't there.
   590  	err = client.EnvironmentUnset("some-name")
   591  	c.Assert(err, jc.ErrorIsNil)
   592  
   593  	env, err := client.EnvironmentGet()
   594  	c.Assert(err, jc.ErrorIsNil)
   595  	_, found := env["some-name"]
   596  	c.Assert(found, jc.IsFalse)
   597  }
   598  
   599  // badReader raises err when Read is called.
   600  type badReader struct {
   601  	err error
   602  }
   603  
   604  func (r *badReader) Read(p []byte) (n int, err error) {
   605  	return 0, r.err
   606  }
   607  
   608  func echoURL(c *gc.C) func(*websocket.Config) (io.ReadCloser, error) {
   609  	response := &params.ErrorResult{}
   610  	message, err := json.Marshal(response)
   611  	c.Assert(err, jc.ErrorIsNil)
   612  	return func(config *websocket.Config) (io.ReadCloser, error) {
   613  		pr, pw := io.Pipe()
   614  		go func() {
   615  			fmt.Fprintf(pw, "%s\n", message)
   616  			fmt.Fprintf(pw, "%s\n", config.Location)
   617  		}()
   618  		return pr, nil
   619  	}
   620  }
   621  
   622  func connectURLFromReader(c *gc.C, rc io.ReadCloser) *url.URL {
   623  	bufReader := bufio.NewReader(rc)
   624  	location, err := bufReader.ReadString('\n')
   625  	c.Assert(err, jc.ErrorIsNil)
   626  	connectURL, err := url.Parse(strings.TrimSpace(location))
   627  	c.Assert(err, jc.ErrorIsNil)
   628  	rc.Close()
   629  	return connectURL
   630  }