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 }