github.com/mattyw/juju@v0.0.0-20140610034352-732aecd63861/state/apiserver/apiserver.go (about) 1 // Copyright 2013 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package apiserver 5 6 import ( 7 "crypto/tls" 8 "fmt" 9 "net" 10 "net/http" 11 "sync" 12 "sync/atomic" 13 "time" 14 15 "code.google.com/p/go.net/websocket" 16 "github.com/bmizerany/pat" 17 "github.com/juju/loggo" 18 "github.com/juju/utils" 19 "launchpad.net/tomb" 20 21 "github.com/juju/juju/rpc" 22 "github.com/juju/juju/rpc/jsoncodec" 23 "github.com/juju/juju/state" 24 "github.com/juju/juju/state/apiserver/common" 25 ) 26 27 var logger = loggo.GetLogger("juju.state.apiserver") 28 29 // loginRateLimit defines how many concurrent Login requests we will 30 // accept 31 const loginRateLimit = 10 32 33 // Server holds the server side of the API. 34 type Server struct { 35 tomb tomb.Tomb 36 wg sync.WaitGroup 37 state *state.State 38 environUUID string 39 addr net.Addr 40 dataDir string 41 logDir string 42 limiter utils.Limiter 43 } 44 45 // NewServer serves the given state by accepting requests on the given 46 // listener, using the given certificate and key (in PEM format) for 47 // authentication. 48 func NewServer(s *state.State, addr string, cert, key []byte, datadir, logDir string) (*Server, error) { 49 lis, err := net.Listen("tcp", addr) 50 if err != nil { 51 return nil, err 52 } 53 logger.Infof("listening on %q", lis.Addr()) 54 tlsCert, err := tls.X509KeyPair(cert, key) 55 if err != nil { 56 return nil, err 57 } 58 srv := &Server{ 59 state: s, 60 addr: lis.Addr(), 61 dataDir: datadir, 62 logDir: logDir, 63 limiter: utils.NewLimiter(loginRateLimit), 64 } 65 // TODO(rog) check that *srvRoot is a valid type for using 66 // as an RPC server. 67 lis = tls.NewListener(lis, &tls.Config{ 68 Certificates: []tls.Certificate{tlsCert}, 69 }) 70 go srv.run(lis) 71 return srv, nil 72 } 73 74 // Dead returns a channel that signals when the server has exited. 75 func (srv *Server) Dead() <-chan struct{} { 76 return srv.tomb.Dead() 77 } 78 79 // Stop stops the server and returns when all running requests 80 // have completed. 81 func (srv *Server) Stop() error { 82 srv.tomb.Kill(nil) 83 return srv.tomb.Wait() 84 } 85 86 // Kill implements worker.Worker.Kill. 87 func (srv *Server) Kill() { 88 srv.tomb.Kill(nil) 89 } 90 91 // Wait implements worker.Worker.Wait. 92 func (srv *Server) Wait() error { 93 return srv.tomb.Wait() 94 } 95 96 type requestNotifier struct { 97 id int64 98 start time.Time 99 100 mu sync.Mutex 101 tag_ string 102 } 103 104 var globalCounter int64 105 106 func newRequestNotifier() *requestNotifier { 107 return &requestNotifier{ 108 id: atomic.AddInt64(&globalCounter, 1), 109 tag_: "<unknown>", 110 start: time.Now(), 111 } 112 } 113 114 func (n *requestNotifier) login(tag string) { 115 n.mu.Lock() 116 n.tag_ = tag 117 n.mu.Unlock() 118 } 119 120 func (n *requestNotifier) tag() (tag string) { 121 n.mu.Lock() 122 tag = n.tag_ 123 n.mu.Unlock() 124 return 125 } 126 127 func (n *requestNotifier) ServerRequest(hdr *rpc.Header, body interface{}) { 128 if hdr.Request.Type == "Pinger" && hdr.Request.Action == "Ping" { 129 return 130 } 131 // TODO(rog) 2013-10-11 remove secrets from some requests. 132 logger.Debugf("<- [%X] %s %s", n.id, n.tag(), jsoncodec.DumpRequest(hdr, body)) 133 } 134 135 func (n *requestNotifier) ServerReply(req rpc.Request, hdr *rpc.Header, body interface{}, timeSpent time.Duration) { 136 if req.Type == "Pinger" && req.Action == "Ping" { 137 return 138 } 139 logger.Debugf("-> [%X] %s %s %s %s[%q].%s", n.id, n.tag(), timeSpent, jsoncodec.DumpRequest(hdr, body), req.Type, req.Id, req.Action) 140 } 141 142 func (n *requestNotifier) join(req *http.Request) { 143 logger.Infof("[%X] API connection from %s", n.id, req.RemoteAddr) 144 } 145 146 func (n *requestNotifier) leave() { 147 logger.Infof("[%X] %s API connection terminated after %v", n.id, n.tag(), time.Since(n.start)) 148 } 149 150 func (n requestNotifier) ClientRequest(hdr *rpc.Header, body interface{}) { 151 } 152 153 func (n requestNotifier) ClientReply(req rpc.Request, hdr *rpc.Header, body interface{}) { 154 } 155 156 func handleAll(mux *pat.PatternServeMux, pattern string, handler http.Handler) { 157 mux.Get(pattern, handler) 158 mux.Post(pattern, handler) 159 mux.Head(pattern, handler) 160 mux.Put(pattern, handler) 161 mux.Del(pattern, handler) 162 mux.Options(pattern, handler) 163 } 164 165 func (srv *Server) run(lis net.Listener) { 166 defer srv.tomb.Done() 167 defer srv.wg.Wait() // wait for any outstanding requests to complete. 168 srv.wg.Add(1) 169 go func() { 170 <-srv.tomb.Dying() 171 lis.Close() 172 srv.wg.Done() 173 }() 174 srv.wg.Add(1) 175 go func() { 176 err := srv.mongoPinger() 177 srv.tomb.Kill(err) 178 srv.wg.Done() 179 }() 180 // for pat based handlers, they are matched in-order of being 181 // registered, first match wins. So more specific ones have to be 182 // registered first. 183 mux := pat.New() 184 // For backwards compatibility we register all the old paths 185 handleAll(mux, "/environment/:envuuid/log", 186 &debugLogHandler{ 187 httpHandler: httpHandler{state: srv.state}, 188 logDir: srv.logDir}, 189 ) 190 handleAll(mux, "/environment/:envuuid/charms", 191 &charmsHandler{ 192 httpHandler: httpHandler{state: srv.state}, 193 dataDir: srv.dataDir}, 194 ) 195 // TODO: We can switch from handleAll to mux.Post/Get/etc for entries 196 // where we only want to support specific request methods. However, our 197 // tests currently assert that errors come back as application/json and 198 // pat only does "text/plain" responses. 199 handleAll(mux, "/environment/:envuuid/tools", 200 &toolsHandler{httpHandler{state: srv.state}}, 201 ) 202 handleAll(mux, "/environment/:envuuid/api", http.HandlerFunc(srv.apiHandler)) 203 // For backwards compatibility we register all the old paths 204 handleAll(mux, "/log", 205 &debugLogHandler{ 206 httpHandler: httpHandler{state: srv.state}, 207 logDir: srv.logDir}, 208 ) 209 handleAll(mux, "/charms", 210 &charmsHandler{ 211 httpHandler: httpHandler{state: srv.state}, 212 dataDir: srv.dataDir}, 213 ) 214 handleAll(mux, "/tools", 215 &toolsHandler{httpHandler{state: srv.state}}, 216 ) 217 handleAll(mux, "/", http.HandlerFunc(srv.apiHandler)) 218 // The error from http.Serve is not interesting. 219 http.Serve(lis, mux) 220 } 221 222 func (srv *Server) apiHandler(w http.ResponseWriter, req *http.Request) { 223 reqNotifier := newRequestNotifier() 224 reqNotifier.join(req) 225 defer reqNotifier.leave() 226 wsServer := websocket.Server{ 227 Handler: func(conn *websocket.Conn) { 228 srv.wg.Add(1) 229 defer srv.wg.Done() 230 // If we've got to this stage and the tomb is still 231 // alive, we know that any tomb.Kill must occur after we 232 // have called wg.Add, so we avoid the possibility of a 233 // handler goroutine running after Stop has returned. 234 if srv.tomb.Err() != tomb.ErrStillAlive { 235 return 236 } 237 envUUID := req.URL.Query().Get(":envuuid") 238 logger.Tracef("got a request for env %q", envUUID) 239 if err := srv.serveConn(conn, reqNotifier, envUUID); err != nil { 240 logger.Errorf("error serving RPCs: %v", err) 241 } 242 }, 243 } 244 wsServer.ServeHTTP(w, req) 245 } 246 247 // Addr returns the address that the server is listening on. 248 func (srv *Server) Addr() string { 249 return srv.addr.String() 250 } 251 252 func (srv *Server) validateEnvironUUID(envUUID string) error { 253 if envUUID == "" { 254 // We allow the environUUID to be empty for 2 cases 255 // 1) Compatibility with older clients 256 // 2) On first connect. The environment UUID is currently 257 // generated by 'jujud bootstrap-state', and we haven't 258 // threaded that information all the way back to the 'juju 259 // bootstrap' process to be able to cache the value until 260 // after we've connected one time. 261 return nil 262 } 263 if srv.environUUID == "" { 264 env, err := srv.state.Environment() 265 if err != nil { 266 return err 267 } 268 srv.environUUID = env.UUID() 269 } 270 if envUUID != srv.environUUID { 271 return common.UnknownEnvironmentError(envUUID) 272 } 273 return nil 274 } 275 276 func (srv *Server) serveConn(wsConn *websocket.Conn, reqNotifier *requestNotifier, envUUID string) error { 277 codec := jsoncodec.NewWebsocket(wsConn) 278 if loggo.GetLogger("juju.rpc.jsoncodec").EffectiveLogLevel() <= loggo.TRACE { 279 codec.SetLogging(true) 280 } 281 var notifier rpc.RequestNotifier 282 if logger.EffectiveLogLevel() <= loggo.DEBUG { 283 // Incur request monitoring overhead only if we 284 // know we'll need it. 285 notifier = reqNotifier 286 } 287 conn := rpc.NewConn(codec, notifier) 288 err := srv.validateEnvironUUID(envUUID) 289 if err != nil { 290 conn.Serve(&errRoot{err}, serverError) 291 } else { 292 conn.Serve(newStateServer(srv, conn, reqNotifier, srv.limiter), serverError) 293 } 294 conn.Start() 295 select { 296 case <-conn.Dead(): 297 case <-srv.tomb.Dying(): 298 } 299 return conn.Close() 300 } 301 302 func (srv *Server) mongoPinger() error { 303 timer := time.NewTimer(0) 304 session := srv.state.MongoSession() 305 for { 306 select { 307 case <-timer.C: 308 case <-srv.tomb.Dying(): 309 return tomb.ErrDying 310 } 311 if err := session.Ping(); err != nil { 312 logger.Infof("got error pinging mongo: %v", err) 313 return fmt.Errorf("error pinging mongo: %v", err) 314 } 315 timer.Reset(mongoPingInterval) 316 } 317 } 318 319 func serverError(err error) error { 320 if err := common.ServerError(err); err != nil { 321 return err 322 } 323 return nil 324 } 325 326 var logRequests = true