github.com/Pankov404/juju@v0.0.0-20150703034450-be266991dceb/api/apiclient_test.go (about)

     1  // Copyright 2014 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package api_test
     5  
     6  import (
     7  	"errors"
     8  	"fmt"
     9  	"io"
    10  	"net"
    11  	"strconv"
    12  
    13  	"golang.org/x/net/websocket"
    14  
    15  	"github.com/juju/names"
    16  	jc "github.com/juju/testing/checkers"
    17  	"github.com/juju/utils"
    18  	"github.com/juju/utils/parallel"
    19  	gc "gopkg.in/check.v1"
    20  
    21  	"github.com/juju/juju/api"
    22  	"github.com/juju/juju/apiserver/params"
    23  	jujutesting "github.com/juju/juju/juju/testing"
    24  	"github.com/juju/juju/version"
    25  )
    26  
    27  type apiclientSuite struct {
    28  	jujutesting.JujuConnSuite
    29  }
    30  
    31  var _ = gc.Suite(&apiclientSuite{})
    32  
    33  func (s *apiclientSuite) TestConnectToEnv(c *gc.C) {
    34  	info := s.APIInfo(c)
    35  	conn, err := api.Connect(info, "", nil, api.DialOpts{})
    36  	c.Assert(err, jc.ErrorIsNil)
    37  	defer conn.Close()
    38  	assertConnAddrForEnv(c, conn, info.Addrs[0], s.State.EnvironUUID(), "/api")
    39  }
    40  
    41  func (s *apiclientSuite) TestConnectToEnvWithPathTail(c *gc.C) {
    42  	info := s.APIInfo(c)
    43  	conn, err := api.Connect(info, "/log", nil, api.DialOpts{})
    44  	c.Assert(err, jc.ErrorIsNil)
    45  	defer conn.Close()
    46  	assertConnAddrForEnv(c, conn, info.Addrs[0], s.State.EnvironUUID(), "/log")
    47  }
    48  
    49  func (s *apiclientSuite) TestConnectToRoot(c *gc.C) {
    50  	info := s.APIInfo(c)
    51  	info.EnvironTag = names.NewEnvironTag("")
    52  	conn, err := api.Connect(info, "", nil, api.DialOpts{})
    53  	c.Assert(err, jc.ErrorIsNil)
    54  	defer conn.Close()
    55  	assertConnAddrForRoot(c, conn, info.Addrs[0])
    56  }
    57  
    58  func (s *apiclientSuite) TestConnectWithHeader(c *gc.C) {
    59  	var seenCfg *websocket.Config
    60  	fakeNewDialer := func(cfg *websocket.Config, _ api.DialOpts) func(<-chan struct{}) (io.Closer, error) {
    61  		seenCfg = cfg
    62  		return func(<-chan struct{}) (io.Closer, error) {
    63  			return nil, errors.New("fake")
    64  		}
    65  	}
    66  	s.PatchValue(api.NewWebsocketDialerPtr, fakeNewDialer)
    67  
    68  	header := utils.BasicAuthHeader("foo", "bar")
    69  	api.Connect(s.APIInfo(c), "", header, api.DialOpts{}) // Return values not important here
    70  	c.Assert(seenCfg, gc.NotNil)
    71  	c.Assert(seenCfg.Header, gc.DeepEquals, header)
    72  }
    73  
    74  func (s *apiclientSuite) TestConnectRequiresTailStartsWithSlash(c *gc.C) {
    75  	_, err := api.Connect(s.APIInfo(c), "foo", nil, api.DialOpts{})
    76  	c.Assert(err, gc.ErrorMatches, `path tail must start with "/"`)
    77  }
    78  
    79  func (s *apiclientSuite) TestConnectPrefersLocalhostIfPresent(c *gc.C) {
    80  	// Create a socket that proxies to the API server though our localhost address.
    81  	info := s.APIInfo(c)
    82  	serverAddr := info.Addrs[0]
    83  	server, err := net.Dial("tcp", serverAddr)
    84  	c.Assert(err, jc.ErrorIsNil)
    85  	defer server.Close()
    86  	listener, err := net.Listen("tcp", "localhost:0")
    87  	c.Assert(err, jc.ErrorIsNil)
    88  	defer listener.Close()
    89  	go func() {
    90  		for {
    91  			client, err := listener.Accept()
    92  			if err != nil {
    93  				return
    94  			}
    95  			go io.Copy(client, server)
    96  			go io.Copy(server, client)
    97  		}
    98  	}()
    99  
   100  	// Check that we are using our working address to connect
   101  	listenerAddress := listener.Addr().String()
   102  	// listenAddress contains the actual IP address, but APIHostPorts
   103  	// is going to report localhost, so just find the port
   104  	_, port, err := net.SplitHostPort(listenerAddress)
   105  	c.Check(err, jc.ErrorIsNil)
   106  	portNum, err := strconv.Atoi(port)
   107  	c.Check(err, jc.ErrorIsNil)
   108  	expectedHostPort := fmt.Sprintf("localhost:%d", portNum)
   109  	info.Addrs = []string{"fakeAddress:1", "fakeAddress:1", expectedHostPort}
   110  	conn, err := api.Connect(info, "/api", nil, api.DialOpts{})
   111  	c.Assert(err, jc.ErrorIsNil)
   112  	defer conn.Close()
   113  	assertConnAddrForEnv(c, conn, expectedHostPort, s.State.EnvironUUID(), "/api")
   114  }
   115  
   116  func (s *apiclientSuite) TestConnectMultiple(c *gc.C) {
   117  	// Create a socket that proxies to the API server.
   118  	info := s.APIInfo(c)
   119  	serverAddr := info.Addrs[0]
   120  	server, err := net.Dial("tcp", serverAddr)
   121  	c.Assert(err, jc.ErrorIsNil)
   122  	defer server.Close()
   123  	listener, err := net.Listen("tcp", "127.0.0.1:0")
   124  	c.Assert(err, jc.ErrorIsNil)
   125  	defer listener.Close()
   126  	go func() {
   127  		for {
   128  			client, err := listener.Accept()
   129  			if err != nil {
   130  				return
   131  			}
   132  			go io.Copy(client, server)
   133  			go io.Copy(server, client)
   134  		}
   135  	}()
   136  
   137  	// Check that we can use the proxy to connect.
   138  	proxyAddr := listener.Addr().String()
   139  	info.Addrs = []string{proxyAddr}
   140  	conn, err := api.Connect(info, "/api", nil, api.DialOpts{})
   141  	c.Assert(err, jc.ErrorIsNil)
   142  	conn.Close()
   143  	assertConnAddrForEnv(c, conn, proxyAddr, s.State.EnvironUUID(), "/api")
   144  
   145  	// Now break Addrs[0], and ensure that Addrs[1]
   146  	// is successfully connected to.
   147  	info.Addrs = []string{proxyAddr, serverAddr}
   148  	listener.Close()
   149  	conn, err = api.Connect(info, "/api", nil, api.DialOpts{})
   150  	c.Assert(err, jc.ErrorIsNil)
   151  	conn.Close()
   152  	assertConnAddrForEnv(c, conn, serverAddr, s.State.EnvironUUID(), "/api")
   153  }
   154  
   155  func (s *apiclientSuite) TestConnectMultipleError(c *gc.C) {
   156  	listener, err := net.Listen("tcp", "127.0.0.1:0")
   157  	c.Assert(err, jc.ErrorIsNil)
   158  	defer listener.Close()
   159  	go func() {
   160  		for {
   161  			client, err := listener.Accept()
   162  			if err != nil {
   163  				return
   164  			}
   165  			client.Close()
   166  		}
   167  	}()
   168  	info := s.APIInfo(c)
   169  	addr := listener.Addr().String()
   170  	info.Addrs = []string{addr, addr, addr}
   171  	_, err = api.Connect(info, "/api", nil, api.DialOpts{})
   172  	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"`)
   173  }
   174  
   175  func (s *apiclientSuite) TestOpen(c *gc.C) {
   176  	info := s.APIInfo(c)
   177  	st, err := api.Open(info, api.DialOpts{})
   178  	c.Assert(err, jc.ErrorIsNil)
   179  	defer st.Close()
   180  
   181  	c.Assert(st.Addr(), gc.Equals, info.Addrs[0])
   182  	envTag, err := st.EnvironTag()
   183  	c.Assert(err, jc.ErrorIsNil)
   184  	c.Assert(envTag, gc.Equals, s.State.EnvironTag())
   185  
   186  	remoteVersion, versionSet := st.ServerVersion()
   187  	c.Assert(versionSet, jc.IsTrue)
   188  	c.Assert(remoteVersion, gc.Equals, version.Current.Number)
   189  }
   190  
   191  func (s *apiclientSuite) TestOpenHonorsEnvironTag(c *gc.C) {
   192  	info := s.APIInfo(c)
   193  
   194  	// TODO(jam): 2014-06-05 http://pad.lv/1326802
   195  	// we want to test this eventually, but for now s.APIInfo uses
   196  	// conn.StateInfo() which doesn't know about EnvironTag.
   197  	// c.Check(info.EnvironTag, gc.Equals, env.Tag())
   198  	// c.Assert(info.EnvironTag, gc.Not(gc.Equals), "")
   199  
   200  	// We start by ensuring we have an invalid tag, and Open should fail.
   201  	info.EnvironTag = names.NewEnvironTag("bad-tag")
   202  	_, err := api.Open(info, api.DialOpts{})
   203  	c.Check(err, gc.ErrorMatches, `unknown environment: "bad-tag"`)
   204  	c.Check(params.ErrCode(err), gc.Equals, params.CodeNotFound)
   205  
   206  	// Now set it to the right tag, and we should succeed.
   207  	info.EnvironTag = s.State.EnvironTag()
   208  	st, err := api.Open(info, api.DialOpts{})
   209  	c.Assert(err, jc.ErrorIsNil)
   210  	st.Close()
   211  
   212  	// Backwards compatibility, we should succeed if we do not set an
   213  	// environ tag
   214  	info.EnvironTag = names.NewEnvironTag("")
   215  	st, err = api.Open(info, api.DialOpts{})
   216  	c.Assert(err, jc.ErrorIsNil)
   217  	st.Close()
   218  }
   219  
   220  func (s *apiclientSuite) TestServerRoot(c *gc.C) {
   221  	url := api.ServerRoot(s.APIState.Client())
   222  	c.Assert(url, gc.Matches, "https://localhost:[0-9]+")
   223  }
   224  
   225  func (s *apiclientSuite) TestDialWebsocketStopped(c *gc.C) {
   226  	stopped := make(chan struct{})
   227  	f := api.NewWebsocketDialer(nil, api.DialOpts{})
   228  	close(stopped)
   229  	result, err := f(stopped)
   230  	c.Assert(err, gc.Equals, parallel.ErrStopped)
   231  	c.Assert(result, gc.IsNil)
   232  }
   233  
   234  func assertConnAddrForEnv(c *gc.C, conn *websocket.Conn, addr, envUUID, tail string) {
   235  	c.Assert(conn.RemoteAddr(), gc.Matches, "^wss://"+addr+"/environment/"+envUUID+tail+"$")
   236  }
   237  
   238  func assertConnAddrForRoot(c *gc.C, conn *websocket.Conn, addr string) {
   239  	c.Assert(conn.RemoteAddr(), gc.Matches, "^wss://"+addr+"/$")
   240  }