github.com/altoros/juju-vmware@v0.0.0-20150312064031-f19ae857ccca/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  	"strings"
    17  
    18  	"code.google.com/p/go.net/websocket"
    19  	"github.com/juju/errors"
    20  	"github.com/juju/loggo"
    21  	"github.com/juju/names"
    22  	jc "github.com/juju/testing/checkers"
    23  	gc "gopkg.in/check.v1"
    24  	"gopkg.in/juju/charm.v4"
    25  
    26  	"github.com/juju/juju/api"
    27  	"github.com/juju/juju/apiserver/params"
    28  	jujutesting "github.com/juju/juju/juju/testing"
    29  	"github.com/juju/juju/state"
    30  	"github.com/juju/juju/testcharms"
    31  	"github.com/juju/juju/testing/factory"
    32  	"github.com/juju/juju/version"
    33  )
    34  
    35  type clientSuite struct {
    36  	jujutesting.JujuConnSuite
    37  }
    38  
    39  var _ = gc.Suite(&clientSuite{})
    40  
    41  // TODO(jam) 2013-08-27 http://pad.lv/1217282
    42  // Right now most of the direct tests for api.Client behavior are in
    43  // apiserver/client/*_test.go
    44  
    45  func (s *clientSuite) TestCloseMultipleOk(c *gc.C) {
    46  	client := s.APIState.Client()
    47  	c.Assert(client.Close(), gc.IsNil)
    48  	c.Assert(client.Close(), gc.IsNil)
    49  	c.Assert(client.Close(), gc.IsNil)
    50  }
    51  
    52  func (s *clientSuite) TestAddLocalCharm(c *gc.C) {
    53  	charmArchive := testcharms.Repo.CharmArchive(c.MkDir(), "dummy")
    54  	curl := charm.MustParseURL(
    55  		fmt.Sprintf("local:quantal/%s-%d", charmArchive.Meta().Name, charmArchive.Revision()),
    56  	)
    57  	client := s.APIState.Client()
    58  
    59  	// Test the sanity checks first.
    60  	_, err := client.AddLocalCharm(charm.MustParseURL("cs:quantal/wordpress-1"), nil)
    61  	c.Assert(err, gc.ErrorMatches, `expected charm URL with local: schema, got "cs:quantal/wordpress-1"`)
    62  
    63  	// Upload an archive with its original revision.
    64  	savedURL, err := client.AddLocalCharm(curl, charmArchive)
    65  	c.Assert(err, jc.ErrorIsNil)
    66  	c.Assert(savedURL.String(), gc.Equals, curl.String())
    67  
    68  	// Upload a charm directory with changed revision.
    69  	charmDir := testcharms.Repo.ClonedDir(c.MkDir(), "dummy")
    70  	charmDir.SetDiskRevision(42)
    71  	savedURL, err = client.AddLocalCharm(curl, charmDir)
    72  	c.Assert(err, jc.ErrorIsNil)
    73  	c.Assert(savedURL.Revision, gc.Equals, 42)
    74  
    75  	// Upload a charm directory again, revision should be bumped.
    76  	savedURL, err = client.AddLocalCharm(curl, charmDir)
    77  	c.Assert(err, jc.ErrorIsNil)
    78  	c.Assert(savedURL.String(), gc.Equals, curl.WithRevision(43).String())
    79  }
    80  
    81  func (s *clientSuite) TestAddLocalCharmError(c *gc.C) {
    82  	lis, err := net.Listen("tcp", "127.0.0.1:0")
    83  	c.Assert(err, jc.ErrorIsNil)
    84  	defer lis.Close()
    85  	url := fmt.Sprintf("http://%v", lis.Addr())
    86  	http.HandleFunc("/charms", func(w http.ResponseWriter, r *http.Request) {
    87  		if r.Method == "POST" {
    88  			http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed)
    89  		}
    90  	})
    91  	go func() {
    92  		http.Serve(lis, nil)
    93  	}()
    94  
    95  	client := s.APIState.Client()
    96  	api.SetServerRoot(client, url)
    97  
    98  	charmArchive := testcharms.Repo.CharmArchive(c.MkDir(), "dummy")
    99  	curl := charm.MustParseURL(
   100  		fmt.Sprintf("local:quantal/%s-%d", charmArchive.Meta().Name, charmArchive.Revision()),
   101  	)
   102  	_, err = client.AddLocalCharm(curl, charmArchive)
   103  	c.Assert(err, gc.ErrorMatches, "charm upload failed: 405 \\(Method Not Allowed\\)")
   104  }
   105  
   106  func (s *clientSuite) TestClientEnvironmentUUID(c *gc.C) {
   107  	environ, err := s.State.Environment()
   108  	c.Assert(err, jc.ErrorIsNil)
   109  
   110  	client := s.APIState.Client()
   111  	c.Assert(client.EnvironmentUUID(), gc.Equals, environ.Tag().Id())
   112  }
   113  
   114  func (s *clientSuite) TestShareEnvironmentExistingUser(c *gc.C) {
   115  	client := s.APIState.Client()
   116  	user := s.Factory.MakeEnvUser(c, nil)
   117  	cleanup := api.PatchClientFacadeCall(client,
   118  		func(request string, paramsIn interface{}, response interface{}) error {
   119  			if users, ok := paramsIn.(params.ModifyEnvironUsers); ok {
   120  				c.Assert(users.Changes, gc.HasLen, 1)
   121  				c.Logf(string(users.Changes[0].Action), gc.Equals, string(params.AddEnvUser))
   122  				c.Logf(users.Changes[0].UserTag, gc.Equals, user.UserTag().String())
   123  			} else {
   124  				c.Fatalf("wrong input structure")
   125  			}
   126  			if result, ok := response.(*params.ErrorResults); ok {
   127  				err := &params.Error{Message: "failed to create environment user: env user already exists"}
   128  				*result = params.ErrorResults{Results: []params.ErrorResult{{Error: err}}}
   129  			} else {
   130  				c.Fatalf("wrong input structure")
   131  			}
   132  			return nil
   133  		},
   134  	)
   135  	defer cleanup()
   136  
   137  	err := client.ShareEnvironment([]names.UserTag{user.UserTag()})
   138  	c.Assert(err, gc.ErrorMatches, "failed to create environment user: env user already exists")
   139  }
   140  
   141  func (s *clientSuite) TestShareEnvironmentThreeUsers(c *gc.C) {
   142  	client := s.APIState.Client()
   143  	existingUser := s.Factory.MakeEnvUser(c, nil)
   144  	localUser := s.Factory.MakeUser(c, nil)
   145  	newUserTag := names.NewUserTag("foo@bar")
   146  	cleanup := api.PatchClientFacadeCall(client,
   147  		func(request string, paramsIn interface{}, response interface{}) error {
   148  			if users, ok := paramsIn.(params.ModifyEnvironUsers); ok {
   149  				c.Assert(users.Changes, gc.HasLen, 3)
   150  				c.Logf(string(users.Changes[0].Action), gc.Equals, string(params.AddEnvUser))
   151  				c.Logf(users.Changes[0].UserTag, gc.Equals, existingUser.UserTag().String())
   152  				c.Logf(string(users.Changes[1].Action), gc.Equals, string(params.AddEnvUser))
   153  				c.Logf(users.Changes[1].UserTag, gc.Equals, localUser.UserTag().String())
   154  				c.Logf(string(users.Changes[1].Action), gc.Equals, string(params.AddEnvUser))
   155  				c.Logf(users.Changes[1].UserTag, gc.Equals, newUserTag.String())
   156  			} else {
   157  				c.Log("wrong input structure")
   158  				c.Fail()
   159  			}
   160  			if result, ok := response.(*params.ErrorResults); ok {
   161  				err := &params.Error{Message: "existing user"}
   162  				*result = params.ErrorResults{Results: []params.ErrorResult{{Error: err}, {Error: nil}, {Error: nil}}}
   163  			} else {
   164  				c.Log("wrong output structure")
   165  				c.Fail()
   166  			}
   167  			return nil
   168  		},
   169  	)
   170  	defer cleanup()
   171  
   172  	err := client.ShareEnvironment([]names.UserTag{existingUser.UserTag(), localUser.UserTag(), newUserTag})
   173  	c.Assert(err, gc.ErrorMatches, `existing user`)
   174  }
   175  
   176  func (s *clientSuite) TestShareEnvironmentRealAPIServer(c *gc.C) {
   177  	client := s.APIState.Client()
   178  	user := names.NewUserTag("foo@ubuntuone")
   179  	err := client.ShareEnvironment([]names.UserTag{user})
   180  	c.Assert(err, jc.ErrorIsNil)
   181  
   182  	envUser, err := s.State.EnvironmentUser(user)
   183  	c.Assert(err, jc.ErrorIsNil)
   184  	c.Assert(envUser.UserName(), gc.Equals, user.Username())
   185  	c.Assert(envUser.CreatedBy(), gc.Equals, s.AdminUserTag(c).Username())
   186  	c.Assert(envUser.LastConnection(), gc.IsNil)
   187  }
   188  
   189  func (s *clientSuite) TestUnshareEnvironmentRealAPIServer(c *gc.C) {
   190  	client := s.APIState.Client()
   191  	user := names.NewUserTag("foo@ubuntuone")
   192  	err := client.ShareEnvironment([]names.UserTag{user})
   193  	c.Assert(err, jc.ErrorIsNil)
   194  
   195  	envUser, err := s.State.EnvironmentUser(user)
   196  	c.Assert(err, jc.ErrorIsNil)
   197  	c.Assert(envUser.UserName(), gc.Equals, user.Username())
   198  
   199  	err = client.UnshareEnvironment([]names.UserTag{user})
   200  	c.Assert(err, jc.ErrorIsNil)
   201  
   202  	_, err = s.State.EnvironmentUser(user)
   203  	c.Assert(errors.IsNotFound(err), jc.IsTrue)
   204  }
   205  
   206  func (s *clientSuite) TestWatchDebugLogConnected(c *gc.C) {
   207  	// Shows both the unmarshalling of a real error, and
   208  	// that the api server is connected.
   209  	client := s.APIState.Client()
   210  	reader, err := client.WatchDebugLog(api.DebugLogParams{})
   211  	c.Assert(err, gc.ErrorMatches, "cannot open log file: .*")
   212  	c.Assert(reader, gc.IsNil)
   213  }
   214  
   215  func (s *clientSuite) TestConnectionErrorBadConnection(c *gc.C) {
   216  	s.PatchValue(api.WebsocketDialConfig, func(_ *websocket.Config) (io.ReadCloser, error) {
   217  		return nil, fmt.Errorf("bad connection")
   218  	})
   219  	client := s.APIState.Client()
   220  	reader, err := client.WatchDebugLog(api.DebugLogParams{})
   221  	c.Assert(err, gc.ErrorMatches, "bad connection")
   222  	c.Assert(reader, gc.IsNil)
   223  }
   224  
   225  func (s *clientSuite) TestConnectionErrorNoData(c *gc.C) {
   226  	s.PatchValue(api.WebsocketDialConfig, func(_ *websocket.Config) (io.ReadCloser, error) {
   227  		return ioutil.NopCloser(&bytes.Buffer{}), nil
   228  	})
   229  	client := s.APIState.Client()
   230  	reader, err := client.WatchDebugLog(api.DebugLogParams{})
   231  	c.Assert(err, gc.ErrorMatches, "unable to read initial response: EOF")
   232  	c.Assert(reader, gc.IsNil)
   233  }
   234  
   235  func (s *clientSuite) TestConnectionErrorBadData(c *gc.C) {
   236  	s.PatchValue(api.WebsocketDialConfig, func(_ *websocket.Config) (io.ReadCloser, error) {
   237  		junk := strings.NewReader("junk\n")
   238  		return ioutil.NopCloser(junk), nil
   239  	})
   240  	client := s.APIState.Client()
   241  	reader, err := client.WatchDebugLog(api.DebugLogParams{})
   242  	c.Assert(err, gc.ErrorMatches, "unable to unmarshal initial response: .*")
   243  	c.Assert(reader, gc.IsNil)
   244  }
   245  
   246  func (s *clientSuite) TestConnectionErrorReadError(c *gc.C) {
   247  	s.PatchValue(api.WebsocketDialConfig, func(_ *websocket.Config) (io.ReadCloser, error) {
   248  		err := fmt.Errorf("bad read")
   249  		return ioutil.NopCloser(&badReader{err}), nil
   250  	})
   251  	client := s.APIState.Client()
   252  	reader, err := client.WatchDebugLog(api.DebugLogParams{})
   253  	c.Assert(err, gc.ErrorMatches, "unable to read initial response: bad read")
   254  	c.Assert(reader, gc.IsNil)
   255  }
   256  
   257  func (s *clientSuite) TestParamsEncoded(c *gc.C) {
   258  	s.PatchValue(api.WebsocketDialConfig, echoURL(c))
   259  
   260  	params := api.DebugLogParams{
   261  		IncludeEntity: []string{"a", "b"},
   262  		IncludeModule: []string{"c", "d"},
   263  		ExcludeEntity: []string{"e", "f"},
   264  		ExcludeModule: []string{"g", "h"},
   265  		Limit:         100,
   266  		Backlog:       200,
   267  		Level:         loggo.ERROR,
   268  		Replay:        true,
   269  	}
   270  
   271  	client := s.APIState.Client()
   272  	reader, err := client.WatchDebugLog(params)
   273  	c.Assert(err, jc.ErrorIsNil)
   274  
   275  	connectURL := connectURLFromReader(c, reader)
   276  
   277  	c.Assert(connectURL.Path, gc.Matches, "/log")
   278  	values := connectURL.Query()
   279  	c.Assert(values, jc.DeepEquals, url.Values{
   280  		"includeEntity": params.IncludeEntity,
   281  		"includeModule": params.IncludeModule,
   282  		"excludeEntity": params.ExcludeEntity,
   283  		"excludeModule": params.ExcludeModule,
   284  		"maxLines":      {"100"},
   285  		"backlog":       {"200"},
   286  		"level":         {"ERROR"},
   287  		"replay":        {"true"},
   288  	})
   289  }
   290  
   291  func (s *clientSuite) TestDebugLogRootPath(c *gc.C) {
   292  	s.PatchValue(api.WebsocketDialConfig, echoURL(c))
   293  
   294  	// If the server is old, we log at "/log"
   295  	info := s.APIInfo(c)
   296  	info.EnvironTag = names.NewEnvironTag("")
   297  	apistate, err := api.Open(info, api.DialOpts{})
   298  	c.Assert(err, jc.ErrorIsNil)
   299  	defer apistate.Close()
   300  	reader, err := apistate.Client().WatchDebugLog(api.DebugLogParams{})
   301  	c.Assert(err, jc.ErrorIsNil)
   302  	connectURL := connectURLFromReader(c, reader)
   303  	c.Assert(connectURL.Path, gc.Matches, "/log")
   304  }
   305  
   306  func (s *clientSuite) TestDebugLogAtUUIDLogPath(c *gc.C) {
   307  	s.PatchValue(api.WebsocketDialConfig, echoURL(c))
   308  	// If the server supports it, we should log at "/environment/UUID/log"
   309  	environ, err := s.State.Environment()
   310  	c.Assert(err, jc.ErrorIsNil)
   311  	info := s.APIInfo(c)
   312  	info.EnvironTag = environ.EnvironTag()
   313  	apistate, err := api.Open(info, api.DialOpts{})
   314  	c.Assert(err, jc.ErrorIsNil)
   315  	defer apistate.Close()
   316  	reader, err := apistate.Client().WatchDebugLog(api.DebugLogParams{})
   317  	c.Assert(err, jc.ErrorIsNil)
   318  	connectURL := connectURLFromReader(c, reader)
   319  	c.ExpectFailure("debug log always goes to /log for compatibility http://pad.lv/1326799")
   320  	c.Assert(connectURL.Path, gc.Matches, fmt.Sprintf("/%s/log", environ.UUID()))
   321  }
   322  
   323  func (s *clientSuite) TestOpenUsesEnvironUUIDPaths(c *gc.C) {
   324  	info := s.APIInfo(c)
   325  	// Backwards compatibility, passing EnvironTag = "" should just work
   326  	info.EnvironTag = names.NewEnvironTag("")
   327  	apistate, err := api.Open(info, api.DialOpts{})
   328  	c.Assert(err, jc.ErrorIsNil)
   329  	apistate.Close()
   330  
   331  	// Passing in the correct environment UUID should also work
   332  	environ, err := s.State.Environment()
   333  	c.Assert(err, jc.ErrorIsNil)
   334  	info.EnvironTag = environ.EnvironTag()
   335  	apistate, err = api.Open(info, api.DialOpts{})
   336  	c.Assert(err, jc.ErrorIsNil)
   337  	apistate.Close()
   338  
   339  	// Passing in a bad environment UUID should fail with a known error
   340  	info.EnvironTag = names.NewEnvironTag("dead-beef-123456")
   341  	apistate, err = api.Open(info, api.DialOpts{})
   342  	c.Check(err, gc.ErrorMatches, `unknown environment: "dead-beef-123456"`)
   343  	c.Check(err, jc.Satisfies, params.IsCodeNotFound)
   344  	c.Assert(apistate, gc.IsNil)
   345  }
   346  
   347  func (s *clientSuite) TestSetEnvironAgentVersionDuringUpgrade(c *gc.C) {
   348  	// This is an integration test which ensure that a test with the
   349  	// correct error code is seen by the client from the
   350  	// SetEnvironAgentVersion call when an upgrade is in progress.
   351  	envConfig, err := s.State.EnvironConfig()
   352  	c.Assert(err, jc.ErrorIsNil)
   353  	agentVersion, ok := envConfig.AgentVersion()
   354  	c.Assert(ok, jc.IsTrue)
   355  	machine := s.Factory.MakeMachine(c, &factory.MachineParams{
   356  		Jobs: []state.MachineJob{state.JobManageEnviron},
   357  	})
   358  	err = machine.SetAgentVersion(version.MustParseBinary(agentVersion.String() + "-quantal-amd64"))
   359  	c.Assert(err, jc.ErrorIsNil)
   360  	nextVersion := version.MustParse("9.8.7")
   361  	_, err = s.State.EnsureUpgradeInfo(machine.Id(), agentVersion, nextVersion)
   362  	c.Assert(err, jc.ErrorIsNil)
   363  
   364  	err = s.APIState.Client().SetEnvironAgentVersion(nextVersion)
   365  
   366  	// Expect an error with a error code that indicates this specific
   367  	// situation. The client needs to be able to reliably identify
   368  	// this error and handle it differently to other errors.
   369  	c.Assert(params.IsCodeUpgradeInProgress(err), jc.IsTrue)
   370  }
   371  
   372  func (s *clientSuite) TestAbortCurrentUpgrade(c *gc.C) {
   373  	client := s.APIState.Client()
   374  	someErr := errors.New("random")
   375  	cleanup := api.PatchClientFacadeCall(client,
   376  		func(request string, args interface{}, response interface{}) error {
   377  			c.Assert(request, gc.Equals, "AbortCurrentUpgrade")
   378  			c.Assert(args, gc.IsNil)
   379  			c.Assert(response, gc.IsNil)
   380  			return someErr
   381  		},
   382  	)
   383  	defer cleanup()
   384  
   385  	err := client.AbortCurrentUpgrade()
   386  	c.Assert(err, gc.Equals, someErr) // Confirms that the correct facade was called
   387  }
   388  
   389  func (s *clientSuite) TestEnvironmentGet(c *gc.C) {
   390  	client := s.APIState.Client()
   391  	env, err := client.EnvironmentGet()
   392  	c.Assert(err, jc.ErrorIsNil)
   393  	// Check a known value, just checking that there is something there.
   394  	c.Assert(env["type"], gc.Equals, "dummy")
   395  }
   396  
   397  func (s *clientSuite) TestEnvironmentSet(c *gc.C) {
   398  	client := s.APIState.Client()
   399  	err := client.EnvironmentSet(map[string]interface{}{
   400  		"some-name":  "value",
   401  		"other-name": true,
   402  	})
   403  	c.Assert(err, jc.ErrorIsNil)
   404  	// Check them using EnvironmentGet.
   405  	env, err := client.EnvironmentGet()
   406  	c.Assert(err, jc.ErrorIsNil)
   407  	c.Assert(env["some-name"], gc.Equals, "value")
   408  	c.Assert(env["other-name"], gc.Equals, true)
   409  }
   410  
   411  func (s *clientSuite) TestEnvironmentUnset(c *gc.C) {
   412  	client := s.APIState.Client()
   413  	err := client.EnvironmentSet(map[string]interface{}{
   414  		"some-name": "value",
   415  	})
   416  	c.Assert(err, jc.ErrorIsNil)
   417  
   418  	// Now unset it and make sure it isn't there.
   419  	err = client.EnvironmentUnset("some-name")
   420  	c.Assert(err, jc.ErrorIsNil)
   421  
   422  	env, err := client.EnvironmentGet()
   423  	c.Assert(err, jc.ErrorIsNil)
   424  	_, found := env["some-name"]
   425  	c.Assert(found, jc.IsFalse)
   426  }
   427  
   428  // badReader raises err when Read is called.
   429  type badReader struct {
   430  	err error
   431  }
   432  
   433  func (r *badReader) Read(p []byte) (n int, err error) {
   434  	return 0, r.err
   435  }
   436  
   437  func echoURL(c *gc.C) func(*websocket.Config) (io.ReadCloser, error) {
   438  	response := &params.ErrorResult{}
   439  	message, err := json.Marshal(response)
   440  	c.Assert(err, jc.ErrorIsNil)
   441  	return func(config *websocket.Config) (io.ReadCloser, error) {
   442  		pr, pw := io.Pipe()
   443  		go func() {
   444  			fmt.Fprintf(pw, "%s\n", message)
   445  			fmt.Fprintf(pw, "%s\n", config.Location)
   446  		}()
   447  		return pr, nil
   448  	}
   449  }
   450  
   451  func connectURLFromReader(c *gc.C, rc io.ReadCloser) *url.URL {
   452  	bufReader := bufio.NewReader(rc)
   453  	location, err := bufReader.ReadString('\n')
   454  	c.Assert(err, jc.ErrorIsNil)
   455  	connectURL, err := url.Parse(strings.TrimSpace(location))
   456  	c.Assert(err, jc.ErrorIsNil)
   457  	rc.Close()
   458  	return connectURL
   459  }