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