github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/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 "net" 8 "sync/atomic" 9 10 "github.com/juju/errors" 11 "github.com/juju/names" 12 "github.com/juju/testing" 13 jc "github.com/juju/testing/checkers" 14 "github.com/juju/utils/parallel" 15 "golang.org/x/net/websocket" 16 gc "gopkg.in/check.v1" 17 18 "github.com/juju/juju/api" 19 "github.com/juju/juju/apiserver/params" 20 jujutesting "github.com/juju/juju/juju/testing" 21 "github.com/juju/juju/rpc" 22 jujuversion "github.com/juju/juju/version" 23 ) 24 25 type apiclientSuite struct { 26 jujutesting.JujuConnSuite 27 } 28 29 var _ = gc.Suite(&apiclientSuite{}) 30 31 func (s *apiclientSuite) TestConnectWebsocketToEnv(c *gc.C) { 32 info := s.APIInfo(c) 33 conn, _, err := api.ConnectWebsocket(info, api.DialOpts{}) 34 c.Assert(err, jc.ErrorIsNil) 35 defer conn.Close() 36 assertConnAddrForEnv(c, conn, info.Addrs[0], s.State.ModelUUID(), "/api") 37 } 38 39 func (s *apiclientSuite) TestConnectWebsocketToRoot(c *gc.C) { 40 info := s.APIInfo(c) 41 info.ModelTag = names.NewModelTag("") 42 conn, _, err := api.ConnectWebsocket(info, api.DialOpts{}) 43 c.Assert(err, jc.ErrorIsNil) 44 defer conn.Close() 45 assertConnAddrForRoot(c, conn, info.Addrs[0]) 46 } 47 48 func (s *apiclientSuite) TestConnectWebsocketMultiple(c *gc.C) { 49 // Create a socket that proxies to the API server. 50 info := s.APIInfo(c) 51 serverAddr := info.Addrs[0] 52 proxy := testing.NewTCPProxy(c, serverAddr) 53 defer proxy.Close() 54 55 // Check that we can use the proxy to connect. 56 info.Addrs = []string{proxy.Addr()} 57 conn, _, err := api.ConnectWebsocket(info, api.DialOpts{}) 58 c.Assert(err, jc.ErrorIsNil) 59 conn.Close() 60 assertConnAddrForEnv(c, conn, proxy.Addr(), s.State.ModelUUID(), "/api") 61 62 // Now break Addrs[0], and ensure that Addrs[1] 63 // is successfully connected to. 64 proxy.Close() 65 info.Addrs = []string{proxy.Addr(), serverAddr} 66 conn, _, err = api.ConnectWebsocket(info, api.DialOpts{}) 67 c.Assert(err, jc.ErrorIsNil) 68 conn.Close() 69 assertConnAddrForEnv(c, conn, serverAddr, s.State.ModelUUID(), "/api") 70 } 71 72 func (s *apiclientSuite) TestConnectWebsocketMultipleError(c *gc.C) { 73 listener, err := net.Listen("tcp", "127.0.0.1:0") 74 c.Assert(err, jc.ErrorIsNil) 75 defer listener.Close() 76 // count holds the number of times we've accepted a connection. 77 var count int32 78 go func() { 79 for { 80 client, err := listener.Accept() 81 if err != nil { 82 return 83 } 84 atomic.AddInt32(&count, 1) 85 client.Close() 86 } 87 }() 88 info := s.APIInfo(c) 89 addr := listener.Addr().String() 90 info.Addrs = []string{addr, addr, addr} 91 _, _, err = api.ConnectWebsocket(info, api.DialOpts{}) 92 c.Assert(err, gc.ErrorMatches, `unable to connect to API: websocket.Dial wss://.*/model/[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}/api: .*`) 93 c.Assert(atomic.LoadInt32(&count), gc.Equals, int32(3)) 94 } 95 96 func (s *apiclientSuite) TestOpen(c *gc.C) { 97 info := s.APIInfo(c) 98 st, err := api.Open(info, api.DialOpts{}) 99 c.Assert(err, jc.ErrorIsNil) 100 defer st.Close() 101 102 c.Assert(st.Addr(), gc.Equals, info.Addrs[0]) 103 modelTag, err := st.ModelTag() 104 c.Assert(err, jc.ErrorIsNil) 105 c.Assert(modelTag, gc.Equals, s.State.ModelTag()) 106 107 remoteVersion, versionSet := st.ServerVersion() 108 c.Assert(versionSet, jc.IsTrue) 109 c.Assert(remoteVersion, gc.Equals, jujuversion.Current) 110 } 111 112 func (s *apiclientSuite) TestOpenHonorsModelTag(c *gc.C) { 113 info := s.APIInfo(c) 114 115 // TODO(jam): 2014-06-05 http://pad.lv/1326802 116 // we want to test this eventually, but for now s.APIInfo uses 117 // conn.StateInfo() which doesn't know about ModelTag. 118 // c.Check(info.ModelTag, gc.Equals, env.Tag()) 119 // c.Assert(info.ModelTag, gc.Not(gc.Equals), "") 120 121 // We start by ensuring we have an invalid tag, and Open should fail. 122 info.ModelTag = names.NewModelTag("bad-tag") 123 _, err := api.Open(info, api.DialOpts{}) 124 c.Assert(errors.Cause(err), gc.DeepEquals, &rpc.RequestError{ 125 Message: `unknown model: "bad-tag"`, 126 Code: "not found", 127 }) 128 c.Check(params.ErrCode(err), gc.Equals, params.CodeNotFound) 129 130 // Now set it to the right tag, and we should succeed. 131 info.ModelTag = s.State.ModelTag() 132 st, err := api.Open(info, api.DialOpts{}) 133 c.Assert(err, jc.ErrorIsNil) 134 st.Close() 135 136 // Backwards compatibility, we should succeed if we do not set an 137 // model tag 138 info.ModelTag = names.NewModelTag("") 139 st, err = api.Open(info, api.DialOpts{}) 140 c.Assert(err, jc.ErrorIsNil) 141 st.Close() 142 } 143 144 func (s *apiclientSuite) TestServerRoot(c *gc.C) { 145 url := api.ServerRoot(s.APIState.Client()) 146 c.Assert(url, gc.Matches, "https://localhost:[0-9]+") 147 } 148 149 func (s *apiclientSuite) TestDialWebsocketStopped(c *gc.C) { 150 stopped := make(chan struct{}) 151 f := api.NewWebsocketDialer(nil, api.DialOpts{}) 152 close(stopped) 153 result, err := f(stopped) 154 c.Assert(err, gc.Equals, parallel.ErrStopped) 155 c.Assert(result, gc.IsNil) 156 } 157 158 func assertConnAddrForEnv(c *gc.C, conn *websocket.Conn, addr, modelUUID, tail string) { 159 c.Assert(conn.RemoteAddr(), gc.Matches, "^wss://"+addr+"/model/"+modelUUID+tail+"$") 160 } 161 162 func assertConnAddrForRoot(c *gc.C, conn *websocket.Conn, addr string) { 163 c.Assert(conn.RemoteAddr(), gc.Matches, "^wss://"+addr+"/$") 164 }