github.com/mattyw/juju@v0.0.0-20140610034352-732aecd63861/state/apiserver/server_test.go (about) 1 // Copyright 2012, 2013 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package apiserver_test 5 6 import ( 7 "crypto/tls" 8 "crypto/x509" 9 "fmt" 10 "io" 11 "net" 12 stdtesting "testing" 13 "time" 14 15 "code.google.com/p/go.net/websocket" 16 jc "github.com/juju/testing/checkers" 17 "github.com/juju/utils" 18 gc "launchpad.net/gocheck" 19 20 "github.com/juju/juju/cert" 21 jujutesting "github.com/juju/juju/juju/testing" 22 "github.com/juju/juju/rpc" 23 "github.com/juju/juju/state" 24 "github.com/juju/juju/state/api" 25 "github.com/juju/juju/state/api/params" 26 "github.com/juju/juju/state/apiserver" 27 coretesting "github.com/juju/juju/testing" 28 ) 29 30 func TestAll(t *stdtesting.T) { 31 coretesting.MgoTestPackage(t) 32 } 33 34 var fastDialOpts = api.DialOpts{} 35 36 type serverSuite struct { 37 jujutesting.JujuConnSuite 38 } 39 40 var _ = gc.Suite(&serverSuite{}) 41 42 func (s *serverSuite) TestStop(c *gc.C) { 43 // Start our own instance of the server so we have 44 // a handle on it to stop it. 45 srv, err := apiserver.NewServer( 46 s.State, "localhost:0", 47 []byte(coretesting.ServerCert), []byte(coretesting.ServerKey), 48 "", "") 49 c.Assert(err, gc.IsNil) 50 defer srv.Stop() 51 52 stm, err := s.State.AddMachine("quantal", state.JobHostUnits) 53 c.Assert(err, gc.IsNil) 54 err = stm.SetProvisioned("foo", "fake_nonce", nil) 55 c.Assert(err, gc.IsNil) 56 password, err := utils.RandomPassword() 57 c.Assert(err, gc.IsNil) 58 err = stm.SetPassword(password) 59 c.Assert(err, gc.IsNil) 60 61 // Note we can't use openAs because we're not connecting to 62 // s.APIConn. 63 apiInfo := &api.Info{ 64 Tag: stm.Tag(), 65 Password: password, 66 Nonce: "fake_nonce", 67 Addrs: []string{srv.Addr()}, 68 CACert: coretesting.CACert, 69 } 70 st, err := api.Open(apiInfo, fastDialOpts) 71 c.Assert(err, gc.IsNil) 72 defer st.Close() 73 74 _, err = st.Machiner().Machine(stm.Tag()) 75 c.Assert(err, gc.IsNil) 76 77 err = srv.Stop() 78 c.Assert(err, gc.IsNil) 79 80 _, err = st.Machiner().Machine(stm.Tag()) 81 // The client has not necessarily seen the server shutdown yet, 82 // so there are two possible errors. 83 if err != rpc.ErrShutdown && err != io.ErrUnexpectedEOF { 84 c.Fatalf("unexpected error from request: %v", err) 85 } 86 87 // Check it can be stopped twice. 88 err = srv.Stop() 89 c.Assert(err, gc.IsNil) 90 } 91 92 func (s *serverSuite) TestOpenAsMachineErrors(c *gc.C) { 93 assertNotProvisioned := func(err error) { 94 c.Assert(err, gc.NotNil) 95 c.Assert(err, jc.Satisfies, params.IsCodeNotProvisioned) 96 c.Assert(err, gc.ErrorMatches, `machine \d+ is not provisioned`) 97 } 98 stm, err := s.State.AddMachine("quantal", state.JobHostUnits) 99 c.Assert(err, gc.IsNil) 100 err = stm.SetProvisioned("foo", "fake_nonce", nil) 101 c.Assert(err, gc.IsNil) 102 password, err := utils.RandomPassword() 103 c.Assert(err, gc.IsNil) 104 err = stm.SetPassword(password) 105 c.Assert(err, gc.IsNil) 106 107 // This does almost exactly the same as OpenAPIAsMachine but checks 108 // for failures instead. 109 _, info, err := s.APIConn.Environ.StateInfo() 110 info.Tag = stm.Tag() 111 info.Password = password 112 info.Nonce = "invalid-nonce" 113 st, err := api.Open(info, fastDialOpts) 114 assertNotProvisioned(err) 115 c.Assert(st, gc.IsNil) 116 117 // Try with empty nonce as well. 118 info.Nonce = "" 119 st, err = api.Open(info, fastDialOpts) 120 assertNotProvisioned(err) 121 c.Assert(st, gc.IsNil) 122 123 // Finally, with the correct one succeeds. 124 info.Nonce = "fake_nonce" 125 st, err = api.Open(info, fastDialOpts) 126 c.Assert(err, gc.IsNil) 127 c.Assert(st, gc.NotNil) 128 st.Close() 129 130 // Now add another machine, intentionally unprovisioned. 131 stm1, err := s.State.AddMachine("quantal", state.JobHostUnits) 132 c.Assert(err, gc.IsNil) 133 err = stm1.SetPassword(password) 134 c.Assert(err, gc.IsNil) 135 136 // Try connecting, it will fail. 137 info.Tag = stm1.Tag() 138 info.Nonce = "" 139 st, err = api.Open(info, fastDialOpts) 140 assertNotProvisioned(err) 141 c.Assert(st, gc.IsNil) 142 } 143 144 func (s *serverSuite) TestMachineLoginStartsPinger(c *gc.C) { 145 // This is the same steps as OpenAPIAsNewMachine but we need to assert 146 // the agent is not alive before we actually open the API. 147 // Create a new machine to verify "agent alive" behavior. 148 machine, err := s.State.AddMachine("quantal", state.JobHostUnits) 149 c.Assert(err, gc.IsNil) 150 err = machine.SetProvisioned("foo", "fake_nonce", nil) 151 c.Assert(err, gc.IsNil) 152 password, err := utils.RandomPassword() 153 c.Assert(err, gc.IsNil) 154 err = machine.SetPassword(password) 155 c.Assert(err, gc.IsNil) 156 157 // Not alive yet. 158 s.assertAlive(c, machine, false) 159 160 // Login as the machine agent of the created machine. 161 st := s.OpenAPIAsMachine(c, machine.Tag(), password, "fake_nonce") 162 163 // Make sure the pinger has started. 164 s.assertAlive(c, machine, true) 165 166 // Now make sure it stops when connection is closed. 167 c.Assert(st.Close(), gc.IsNil) 168 169 // Sync, then wait for a bit to make sure the state is updated. 170 s.State.StartSync() 171 <-time.After(coretesting.ShortWait) 172 s.State.StartSync() 173 174 s.assertAlive(c, machine, false) 175 } 176 177 func (s *serverSuite) TestUnitLoginStartsPinger(c *gc.C) { 178 // Create a new service and unit to verify "agent alive" behavior. 179 service := s.AddTestingService(c, "wordpress", s.AddTestingCharm(c, "wordpress")) 180 unit, err := service.AddUnit() 181 c.Assert(err, gc.IsNil) 182 password, err := utils.RandomPassword() 183 c.Assert(err, gc.IsNil) 184 err = unit.SetPassword(password) 185 c.Assert(err, gc.IsNil) 186 187 // Not alive yet. 188 s.assertAlive(c, unit, false) 189 190 // Login as the unit agent of the created unit. 191 st := s.OpenAPIAs(c, unit.Tag(), password) 192 193 // Make sure the pinger has started. 194 s.assertAlive(c, unit, true) 195 196 // Now make sure it stops when connection is closed. 197 c.Assert(st.Close(), gc.IsNil) 198 199 // Sync, then wait for a bit to make sure the state is updated. 200 s.State.StartSync() 201 <-time.After(coretesting.ShortWait) 202 s.State.StartSync() 203 204 s.assertAlive(c, unit, false) 205 } 206 207 type agentAliver interface { 208 AgentAlive() (bool, error) 209 } 210 211 func (s *serverSuite) assertAlive(c *gc.C, entity agentAliver, isAlive bool) { 212 s.State.StartSync() 213 alive, err := entity.AgentAlive() 214 c.Assert(err, gc.IsNil) 215 c.Assert(alive, gc.Equals, isAlive) 216 } 217 218 func dialWebsocket(c *gc.C, addr, path string) (*websocket.Conn, error) { 219 origin := "http://localhost/" 220 url := fmt.Sprintf("wss://%s%s", addr, path) 221 config, err := websocket.NewConfig(url, origin) 222 c.Assert(err, gc.IsNil) 223 pool := x509.NewCertPool() 224 xcert, err := cert.ParseCert(coretesting.CACert) 225 c.Assert(err, gc.IsNil) 226 pool.AddCert(xcert) 227 config.TlsConfig = &tls.Config{RootCAs: pool} 228 return websocket.DialConfig(config) 229 } 230 231 func (s *serverSuite) TestNonCompatiblePathsAre404(c *gc.C) { 232 // we expose the API at '/' for compatibility, and at '/ENVUUID/api' 233 // for the correct location, but other Paths should fail. 234 srv, err := apiserver.NewServer( 235 s.State, "localhost:0", 236 []byte(coretesting.ServerCert), []byte(coretesting.ServerKey), 237 "", "") 238 c.Assert(err, gc.IsNil) 239 defer srv.Stop() 240 // We have to use 'localhost' because that is what the TLS cert says. 241 // So find just the Port for the server 242 _, portString, err := net.SplitHostPort(srv.Addr()) 243 c.Assert(err, gc.IsNil) 244 addr := "localhost:" + portString 245 // '/' should be fine 246 conn, err := dialWebsocket(c, addr, "/") 247 c.Assert(err, gc.IsNil) 248 conn.Close() 249 // '/environment/ENVIRONUUID/api' should be fine 250 conn, err = dialWebsocket(c, addr, "/environment/dead-beef-123456/api") 251 c.Assert(err, gc.IsNil) 252 conn.Close() 253 254 // '/randompath' is not ok 255 conn, err = dialWebsocket(c, addr, "/randompath") 256 // Unfortunately go.net/websocket just returns Bad Status, it doesn't 257 // give us any information (whether this was a 404 Not Found, Internal 258 // Server Error, 200 OK, etc.) 259 c.Assert(err, gc.ErrorMatches, `websocket.Dial wss://localhost:\d+/randompath: bad status`) 260 c.Assert(conn, gc.IsNil) 261 }