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