github.com/iron-io/functions@v0.0.0-20180820112432-d59d7d1c40b2/api/server/server.go (about)

     1  package server
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"fmt"
     7  	"io/ioutil"
     8  	"net"
     9  	"net/http"
    10  	"os"
    11  	"path"
    12  	"sync"
    13  
    14  	"github.com/Sirupsen/logrus"
    15  	"github.com/gin-gonic/gin"
    16  	"github.com/iron-io/functions/api"
    17  	"github.com/iron-io/functions/api/datastore"
    18  	"github.com/iron-io/functions/api/models"
    19  	"github.com/iron-io/functions/api/mqs"
    20  	"github.com/iron-io/functions/api/runner"
    21  	"github.com/iron-io/functions/api/runner/task"
    22  	"github.com/iron-io/functions/api/server/internal/routecache"
    23  	"github.com/iron-io/runner/common"
    24  	"github.com/spf13/viper"
    25  	"github.com/ucirello/supervisor"
    26  )
    27  
    28  const (
    29  	EnvLogLevel = "log_level"
    30  	EnvMQURL    = "mq_url"
    31  	EnvDBURL    = "db_url"
    32  	EnvPort     = "port" // be careful, Gin expects this variable to be "port"
    33  	EnvAPIURL   = "api_url"
    34  )
    35  
    36  type Server struct {
    37  	Datastore models.Datastore
    38  	Runner    *runner.Runner
    39  	Router    *gin.Engine
    40  	MQ        models.MessageQueue
    41  	Enqueue   models.Enqueue
    42  
    43  	apiURL string
    44  
    45  	specialHandlers []SpecialHandler
    46  	appListeners    []AppListener
    47  	middlewares     []Middleware
    48  	runnerListeners []RunnerListener
    49  
    50  	mu           sync.Mutex // protects hotroutes
    51  	hotroutes    *routecache.Cache
    52  	tasks        chan task.Request
    53  	singleflight singleflight // singleflight assists Datastore
    54  }
    55  
    56  const cacheSize = 1024
    57  
    58  // NewFromEnv creates a new IronFunctions server based on env vars.
    59  func NewFromEnv(ctx context.Context) *Server {
    60  	ds, err := datastore.New(viper.GetString(EnvDBURL))
    61  	if err != nil {
    62  		logrus.WithError(err).Fatalln("Error initializing datastore.")
    63  	}
    64  
    65  	mq, err := mqs.New(viper.GetString(EnvMQURL))
    66  	if err != nil {
    67  		logrus.WithError(err).Fatal("Error initializing message queue.")
    68  	}
    69  
    70  	apiURL := viper.GetString(EnvAPIURL)
    71  
    72  	return New(ctx, ds, mq, apiURL)
    73  }
    74  
    75  // New creates a new IronFunctions server with the passed in datastore, message queue and API URL
    76  func New(ctx context.Context, ds models.Datastore, mq models.MessageQueue, apiURL string, opts ...ServerOption) *Server {
    77  	metricLogger := runner.NewMetricLogger()
    78  	funcLogger := runner.NewFuncLogger()
    79  
    80  	rnr, err := runner.New(ctx, funcLogger, metricLogger)
    81  	if err != nil {
    82  		logrus.WithError(err).Fatalln("Failed to create a runner")
    83  		return nil
    84  	}
    85  
    86  	tasks := make(chan task.Request)
    87  	s := &Server{
    88  		Runner:    rnr,
    89  		Router:    gin.New(),
    90  		Datastore: ds,
    91  		MQ:        mq,
    92  		hotroutes: routecache.New(cacheSize),
    93  		tasks:     tasks,
    94  		Enqueue:   DefaultEnqueue,
    95  		apiURL:    apiURL,
    96  	}
    97  
    98  	s.Router.Use(prepareMiddleware(ctx))
    99  	s.bindHandlers(ctx)
   100  	s.setupMiddlewares()
   101  
   102  	for _, opt := range opts {
   103  		opt(s)
   104  	}
   105  	return s
   106  }
   107  
   108  // todo: remove this or change name
   109  func prepareMiddleware(ctx context.Context) gin.HandlerFunc {
   110  	return func(c *gin.Context) {
   111  		ctx, _ := common.LoggerWithFields(ctx, extractFields(c))
   112  
   113  		if appName := c.Param(api.CApp); appName != "" {
   114  			c.Set(api.AppName, appName)
   115  		}
   116  
   117  		if routePath := c.Param(api.CRoute); routePath != "" {
   118  			c.Set(api.Path, routePath)
   119  		}
   120  
   121  		// todo: can probably replace the "ctx" value with the Go 1.7 context on the http.Request
   122  		c.Set("ctx", ctx)
   123  		c.Request = c.Request.WithContext(ctx)
   124  		c.Next()
   125  	}
   126  }
   127  
   128  func DefaultEnqueue(ctx context.Context, mq models.MessageQueue, task *models.Task) (*models.Task, error) {
   129  	ctx, _ = common.LoggerWithFields(ctx, logrus.Fields{"call_id": task.ID})
   130  	return mq.Push(ctx, task)
   131  }
   132  
   133  func (s *Server) cacheget(appname, path string) (*models.Route, bool) {
   134  	s.mu.Lock()
   135  	defer s.mu.Unlock()
   136  	route, ok := s.hotroutes.Get(appname, path)
   137  	if !ok {
   138  		return nil, false
   139  	}
   140  	return route, ok
   141  }
   142  
   143  func (s *Server) cacherefresh(route *models.Route) {
   144  	s.mu.Lock()
   145  	defer s.mu.Unlock()
   146  	s.hotroutes.Refresh(route)
   147  }
   148  
   149  func (s *Server) cachedelete(appname, path string) {
   150  	s.mu.Lock()
   151  	defer s.mu.Unlock()
   152  	s.hotroutes.Delete(appname, path)
   153  }
   154  
   155  func (s *Server) handleRunnerRequest(c *gin.Context) {
   156  	s.handleRequest(c, s.Enqueue)
   157  }
   158  
   159  func (s *Server) handleTaskRequest(c *gin.Context) {
   160  	ctx, _ := common.LoggerWithFields(c, nil)
   161  	switch c.Request.Method {
   162  	case "GET":
   163  		task, err := s.MQ.Reserve(ctx)
   164  		if err != nil {
   165  			logrus.WithError(err).Error()
   166  			c.JSON(http.StatusInternalServerError, simpleError(models.ErrRoutesList))
   167  			return
   168  		}
   169  		c.JSON(http.StatusAccepted, task)
   170  	case "DELETE":
   171  		body, err := ioutil.ReadAll(c.Request.Body)
   172  		if err != nil {
   173  			logrus.WithError(err).Error()
   174  			c.JSON(http.StatusInternalServerError, err)
   175  			return
   176  		}
   177  		var task models.Task
   178  		if err = json.Unmarshal(body, &task); err != nil {
   179  			logrus.WithError(err).Error()
   180  			c.JSON(http.StatusInternalServerError, err)
   181  			return
   182  		}
   183  
   184  		if err := s.MQ.Delete(ctx, &task); err != nil {
   185  			logrus.WithError(err).Error()
   186  			c.JSON(http.StatusInternalServerError, err)
   187  			return
   188  		}
   189  		c.JSON(http.StatusAccepted, task)
   190  	}
   191  }
   192  
   193  func extractFields(c *gin.Context) logrus.Fields {
   194  	fields := logrus.Fields{"action": path.Base(c.HandlerName())}
   195  	for _, param := range c.Params {
   196  		fields[param.Key] = param.Value
   197  	}
   198  	return fields
   199  }
   200  
   201  func (s *Server) Start(ctx context.Context) {
   202  	ctx = contextWithSignal(ctx, os.Interrupt)
   203  	s.startGears(ctx)
   204  	close(s.tasks)
   205  }
   206  
   207  func (s *Server) setupMiddlewares() {
   208  	SetupJwtAuth(s)
   209  }
   210  
   211  func (s *Server) startGears(ctx context.Context) {
   212  	// By default it serves on :8080 unless a
   213  	// PORT environment variable was defined.
   214  	listen := fmt.Sprintf(":%d", viper.GetInt(EnvPort))
   215  	listener, err := net.Listen("tcp", listen)
   216  	if err != nil {
   217  		logrus.WithError(err).Fatalln("Failed to serve functions API.")
   218  	}
   219  	logrus.Infof("Serving Functions API on address `%s`", listen)
   220  
   221  	svr := &supervisor.Supervisor{
   222  		MaxRestarts: supervisor.AlwaysRestart,
   223  		Log: func(msg interface{}) {
   224  			logrus.Debug("supervisor: ", msg)
   225  		},
   226  	}
   227  
   228  	svr.AddFunc(func(ctx context.Context) {
   229  		go func() {
   230  			err := http.Serve(listener, s.Router)
   231  			if err != nil {
   232  				logrus.Fatalf("Error serving API: %v", err)
   233  			}
   234  		}()
   235  		<-ctx.Done()
   236  	})
   237  
   238  	svr.AddFunc(func(ctx context.Context) {
   239  		runner.RunAsyncRunner(ctx, s.apiURL, s.tasks, s.Runner)
   240  	})
   241  
   242  	svr.AddFunc(func(ctx context.Context) {
   243  		runner.StartWorkers(ctx, s.Runner, s.tasks)
   244  	})
   245  
   246  	svr.Serve(ctx)
   247  }
   248  
   249  func (s *Server) bindHandlers(ctx context.Context) {
   250  	engine := s.Router
   251  
   252  	engine.GET("/", handlePing)
   253  	engine.GET("/version", handleVersion)
   254  	engine.GET("/stats", s.handleStats)
   255  
   256  	v1 := engine.Group("/v1")
   257  	v1.Use(s.middlewareWrapperFunc(ctx))
   258  	{
   259  		v1.GET("/apps", s.handleAppList)
   260  		v1.POST("/apps", s.handleAppCreate)
   261  
   262  		v1.GET("/apps/:app", s.handleAppGet)
   263  		v1.PATCH("/apps/:app", s.handleAppUpdate)
   264  		v1.DELETE("/apps/:app", s.handleAppDelete)
   265  
   266  		v1.GET("/routes", s.handleRouteList)
   267  
   268  		apps := v1.Group("/apps/:app")
   269  		{
   270  			apps.GET("/routes", s.handleRouteList)
   271  			apps.POST("/routes", s.handleRouteCreate)
   272  			apps.GET("/routes/*route", s.handleRouteGet)
   273  			apps.PATCH("/routes/*route", s.handleRouteUpdate)
   274  			apps.DELETE("/routes/*route", s.handleRouteDelete)
   275  		}
   276  	}
   277  
   278  	engine.DELETE("/tasks", s.handleTaskRequest)
   279  	engine.GET("/tasks", s.handleTaskRequest)
   280  	engine.Any("/r/:app/*route", s.handleRunnerRequest)
   281  
   282  	// This final route is used for extensions, see Server.Add
   283  	engine.NoRoute(s.handleSpecial)
   284  }
   285  
   286  type appResponse struct {
   287  	Message string      `json:"message"`
   288  	App     *models.App `json:"app"`
   289  }
   290  
   291  type appsResponse struct {
   292  	Message string      `json:"message"`
   293  	Apps    models.Apps `json:"apps"`
   294  }
   295  
   296  type routeResponse struct {
   297  	Message string        `json:"message"`
   298  	Route   *models.Route `json:"route"`
   299  }
   300  
   301  type routesResponse struct {
   302  	Message string        `json:"message"`
   303  	Routes  models.Routes `json:"routes"`
   304  }
   305  
   306  type tasksResponse struct {
   307  	Message string      `json:"message"`
   308  	Task    models.Task `json:"tasksResponse"`
   309  }