github.com/cloudbase/juju-core@v0.0.0-20140504232958-a7271ac7912f/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 "net" 9 "net/http" 10 "sync" 11 "sync/atomic" 12 "time" 13 14 "code.google.com/p/go.net/websocket" 15 "github.com/juju/loggo" 16 "launchpad.net/tomb" 17 18 "launchpad.net/juju-core/rpc" 19 "launchpad.net/juju-core/rpc/jsoncodec" 20 "launchpad.net/juju-core/state" 21 "launchpad.net/juju-core/state/apiserver/common" 22 ) 23 24 var logger = loggo.GetLogger("juju.state.apiserver") 25 26 // Server holds the server side of the API. 27 type Server struct { 28 tomb tomb.Tomb 29 wg sync.WaitGroup 30 state *state.State 31 addr net.Addr 32 dataDir string 33 } 34 35 // Serve serves the given state by accepting requests on the given 36 // listener, using the given certificate and key (in PEM format) for 37 // authentication. 38 func NewServer(s *state.State, addr string, cert, key []byte, datadir string) (*Server, error) { 39 lis, err := net.Listen("tcp", addr) 40 if err != nil { 41 return nil, err 42 } 43 logger.Infof("listening on %q", lis.Addr()) 44 tlsCert, err := tls.X509KeyPair(cert, key) 45 if err != nil { 46 return nil, err 47 } 48 srv := &Server{ 49 state: s, 50 addr: lis.Addr(), 51 dataDir: datadir, 52 } 53 // TODO(rog) check that *srvRoot is a valid type for using 54 // as an RPC server. 55 lis = tls.NewListener(lis, &tls.Config{ 56 Certificates: []tls.Certificate{tlsCert}, 57 }) 58 go srv.run(lis) 59 return srv, nil 60 } 61 62 // Dead returns a channel that signals when the server has exited. 63 func (srv *Server) Dead() <-chan struct{} { 64 return srv.tomb.Dead() 65 } 66 67 // Stop stops the server and returns when all running requests 68 // have completed. 69 func (srv *Server) Stop() error { 70 srv.tomb.Kill(nil) 71 return srv.tomb.Wait() 72 } 73 74 // Kill implements worker.Worker.Kill. 75 func (srv *Server) Kill() { 76 srv.tomb.Kill(nil) 77 } 78 79 // Wait implements worker.Worker.Wait. 80 func (srv *Server) Wait() error { 81 return srv.tomb.Wait() 82 } 83 84 type requestNotifier struct { 85 id int64 86 start time.Time 87 88 mu sync.Mutex 89 tag_ string 90 } 91 92 var globalCounter int64 93 94 func newRequestNotifier() *requestNotifier { 95 return &requestNotifier{ 96 id: atomic.AddInt64(&globalCounter, 1), 97 tag_: "<unknown>", 98 start: time.Now(), 99 } 100 } 101 102 func (n *requestNotifier) login(tag string) { 103 n.mu.Lock() 104 n.tag_ = tag 105 n.mu.Unlock() 106 } 107 108 func (n *requestNotifier) tag() (tag string) { 109 n.mu.Lock() 110 tag = n.tag_ 111 n.mu.Unlock() 112 return 113 } 114 115 func (n *requestNotifier) ServerRequest(hdr *rpc.Header, body interface{}) { 116 if hdr.Request.Type == "Pinger" && hdr.Request.Action == "Ping" { 117 return 118 } 119 // TODO(rog) 2013-10-11 remove secrets from some requests. 120 logger.Debugf("<- [%X] %s %s", n.id, n.tag(), jsoncodec.DumpRequest(hdr, body)) 121 } 122 123 func (n *requestNotifier) ServerReply(req rpc.Request, hdr *rpc.Header, body interface{}, timeSpent time.Duration) { 124 if req.Type == "Pinger" && req.Action == "Ping" { 125 return 126 } 127 logger.Debugf("-> [%X] %s %s %s %s[%q].%s", n.id, n.tag(), timeSpent, jsoncodec.DumpRequest(hdr, body), req.Type, req.Id, req.Action) 128 } 129 130 func (n *requestNotifier) join(req *http.Request) { 131 logger.Infof("[%X] API connection from %s", n.id, req.RemoteAddr) 132 } 133 134 func (n *requestNotifier) leave() { 135 logger.Infof("[%X] %s API connection terminated after %v", n.id, n.tag(), time.Since(n.start)) 136 } 137 138 func (n requestNotifier) ClientRequest(hdr *rpc.Header, body interface{}) { 139 } 140 141 func (n requestNotifier) ClientReply(req rpc.Request, hdr *rpc.Header, body interface{}) { 142 } 143 144 func (srv *Server) run(lis net.Listener) { 145 defer srv.tomb.Done() 146 defer srv.wg.Wait() // wait for any outstanding requests to complete. 147 srv.wg.Add(1) 148 go func() { 149 <-srv.tomb.Dying() 150 lis.Close() 151 srv.wg.Done() 152 }() 153 mux := http.NewServeMux() 154 mux.HandleFunc("/", srv.apiHandler) 155 mux.Handle("/charms", &charmsHandler{state: srv.state, dataDir: srv.dataDir}) 156 // The error from http.Serve is not interesting. 157 http.Serve(lis, mux) 158 } 159 160 func (srv *Server) apiHandler(w http.ResponseWriter, req *http.Request) { 161 reqNotifier := newRequestNotifier() 162 reqNotifier.join(req) 163 defer reqNotifier.leave() 164 wsServer := websocket.Server{ 165 Handler: func(conn *websocket.Conn) { 166 srv.wg.Add(1) 167 defer srv.wg.Done() 168 // If we've got to this stage and the tomb is still 169 // alive, we know that any tomb.Kill must occur after we 170 // have called wg.Add, so we avoid the possibility of a 171 // handler goroutine running after Stop has returned. 172 if srv.tomb.Err() != tomb.ErrStillAlive { 173 return 174 } 175 if err := srv.serveConn(conn, reqNotifier); err != nil { 176 logger.Errorf("error serving RPCs: %v", err) 177 } 178 }, 179 } 180 wsServer.ServeHTTP(w, req) 181 } 182 183 // Addr returns the address that the server is listening on. 184 func (srv *Server) Addr() string { 185 return srv.addr.String() 186 } 187 188 func (srv *Server) serveConn(wsConn *websocket.Conn, reqNotifier *requestNotifier) error { 189 codec := jsoncodec.NewWebsocket(wsConn) 190 if loggo.GetLogger("juju.rpc.jsoncodec").EffectiveLogLevel() <= loggo.TRACE { 191 codec.SetLogging(true) 192 } 193 var notifier rpc.RequestNotifier 194 if logger.EffectiveLogLevel() <= loggo.DEBUG { 195 // Incur request monitoring overhead only if we 196 // know we'll need it. 197 notifier = reqNotifier 198 } 199 conn := rpc.NewConn(codec, notifier) 200 conn.Serve(newStateServer(srv, conn, reqNotifier), serverError) 201 conn.Start() 202 select { 203 case <-conn.Dead(): 204 case <-srv.tomb.Dying(): 205 } 206 return conn.Close() 207 } 208 209 func serverError(err error) error { 210 if err := common.ServerError(err); err != nil { 211 return err 212 } 213 return nil 214 } 215 216 var logRequests = true