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