Example #1
0
func main() {

	// Get the listener and ppid from the environment.  If this is successful,
	// this process is a child that's inheriting and open listener from ppid.
	l, ppid, err := goagain.GetEnvs()

	if nil != err {

		// Listen on a TCP socket and accept connections in a new goroutine.
		laddr, err := net.ResolveTCPAddr("tcp", "127.0.0.1:48879")
		if nil != err {
			log.Println(err)
			os.Exit(1)
		}
		log.Printf("listening on %v", laddr)
		l, err = net.ListenTCP("tcp", laddr)
		if nil != err {
			log.Println(err)
			os.Exit(1)
		}
		go serve(l)

	} else {

		// Resume listening and accepting connections in a new goroutine.
		log.Printf("resuming listening on %v", l.Addr())
		go serve(l)

		// Kill the parent, now that the child has started successfully.
		if err := goagain.KillParent(ppid); nil != err {
			log.Println(err)
			os.Exit(1)
		}

	}

	// Block the main goroutine awaiting signals.
	if err := goagain.AwaitSignals(l); nil != err {
		log.Println(err)
		os.Exit(1)
	}

	// Do whatever's necessary to ensure a graceful exit like waiting for
	// goroutines to terminate or a channel to become closed.

	// In this case, we'll simply stop listening and wait one second.
	if err := l.Close(); nil != err {
		log.Fatalln(err)
	}
	time.Sleep(1e9)

}
Example #2
0
func main() {
	xlog.SetOutput(os.Stdout)

	options := struct {
		Addr                string `goptions:"-L, --listen, description='Listen address'"`
		HashKey             string `goptions:"--hashkey, description='Hash key for cookie store and XSRF', obligatory"`
		BlockKey            string `goptions:"--blockkey, description='Crypto key for cookie store and XSRF', obligatory"`
		GplusClientID       string `goptions:"--gplusclientid, description='Google+ Client ID', obligatory"`
		GplusClientSecret   string `goptions:"--gplusclientsecret, description='Google+ Client Secret', obligatory"`
		GPlusAuthURL        string `goptions:"--gplusauthurl, description='Google+ Authentication URL', obligatory"`
		TwitterClientKey    string `goptions:"--twitterclientkey, description='Twitter Client Key', obligatory"`
		TwitterClientSecret string `goptions:"--twitterclientsecret, description='Twitter Client Secret', obligatory"`
		TwitterAuthURL      string `goptions:"--twitterauthurl, description='Twitter Authentication URL', obligatory"`
		DSN                 string `goptions:"--dsn, description='MySQL DSN string', obligatory"`
		HtdocsDir           string `goptions:"--htdocs, description='htdocs directory', obligatory"`
		UploadDir           string `goptions:"--uploaddir, description='Upload directory', obligatory"`
		TmpDir              string `goptions:"--tmpdir, description='directory for temporary files', obligatory"`
		RedisAddr           string `goptions:"--redis, description='redis address', obligatory"`
		AccessLog           bool   `goptions:"--accesslog, description='log HTTP requests'"`
		StatHat             string `goptions:"--stathat, description='Enable StatHat tracking and set user key'"`
		Topic               string `goptions:"--topic, description='Topic to which uploads shall be published for conversions'"`
		NSQAddr             string `goptions:"--nsqd, description='address:port of nsqd to publish messages to'"`
		PersonaAudience     string `goptions:"--persona-audience, description='Persona audience, e.g. http://localhost:8080'"`
	}{
		Addr:      "[::]:8080",
		RedisAddr: ":6379",
	}
	goptions.ParseAndFail(&options)

	if options.StatHat != "" {
		enableStatHat = true
		stathatUserKey = options.StatHat
	}

	xlog.Debug("Creating cookie store...")
	sessionStore := sessions.NewCookieStore([]byte(options.HashKey), []byte(options.BlockKey))
	secureCookie := securecookie.New([]byte(options.HashKey), []byte(options.BlockKey))

	auth.Config.CookieSecret = []byte(options.HashKey)
	auth.Config.LoginSuccessRedirect = "/api/connect"
	auth.Config.CookieSecure = false

	xlog.Debugf("Connecting to database %s...", options.DSN)

	var dbStore *Store
	if sqldb, err := sql.Open("mysql", options.DSN); err != nil {
		xlog.Fatalf("sql.Open failed: %v", err)
	} else {
		dbStore = NewStore(sqldb)
	}

	fileStore := &FileUploadStore{UploadDir: options.UploadDir, TmpDir: options.TmpDir, Topic: options.Topic, NSQ: nsq.NewWriter(options.NSQAddr)}

	xlog.Debugf("Creating upload directory %s...", options.UploadDir)
	os.Mkdir(options.UploadDir, 0755)

	os.Mkdir(options.TmpDir, 0755)

	xlog.Debugf("Setting up HTTP server...")
	mux := http.NewServeMux()

	// auth calls
	mux.Handle("/auth/gplus", auth.Google(options.GplusClientID, options.GplusClientSecret, options.GPlusAuthURL))
	mux.Handle("/auth/twitter", auth.Twitter(options.TwitterClientKey, options.TwitterClientSecret, options.TwitterAuthURL))
	mux.Handle("/auth/persona", &PersonaAuthHandler{Audience: options.PersonaAudience, SessionStore: sessionStore, DBStore: dbStore, SecureCookie: secureCookie})

	// API calls.
	apiRouter := pat.New()
	apiRouter.Get("/api/loggedin", &LoggedInHandler{SessionStore: sessionStore})
	apiRouter.Get("/api/connect", http.HandlerFunc(auth.SecureUser(func(w http.ResponseWriter, r *http.Request, u auth.User) {
		Connect(w, r, u, sessionStore, secureCookie, dbStore)
	})))
	apiRouter.Get("/api/connected", &ConnectedHandler{SessionStore: sessionStore, DBStore: dbStore})
	apiRouter.Post("/api/disconnect", &DisconnectHandler{SessionStore: sessionStore, SecureCookie: secureCookie})
	apiRouter.Post("/api/upload", &UploadHandler{SessionStore: sessionStore, DBStore: dbStore, UploadStore: fileStore, SecureCookie: secureCookie})
	apiRouter.Get("/api/getuploads", &GetUploadsHandler{SessionStore: sessionStore, DBStore: dbStore})
	apiRouter.Post("/api/renameupload", &RenameUploadHandler{SessionStore: sessionStore, DBStore: dbStore, SecureCookie: secureCookie})
	apiRouter.Post("/api/delupload", &DeleteUploadHandler{SessionStore: sessionStore, DBStore: dbStore, SecureCookie: secureCookie, UploadStore: fileStore})
	apiRouter.Post("/api/startsession", &StartSessionHandler{SessionStore: sessionStore, DBStore: dbStore, SecureCookie: secureCookie})
	apiRouter.Post("/api/stopsession", &StopSessionHandler{SessionStore: sessionStore, DBStore: dbStore, SecureCookie: secureCookie, RedisAddr: options.RedisAddr})
	apiRouter.Post("/api/delsession", &DeleteSessionHandler{SessionStore: sessionStore, DBStore: dbStore, SecureCookie: secureCookie})
	apiRouter.Get("/api/getsessions", &GetSessionsHandler{SessionStore: sessionStore, DBStore: dbStore})
	apiRouter.Get("/api/sessioninfo/:id", &GetSessionInfoHandler{SessionStore: sessionStore, DBStore: dbStore})
	mux.Handle("/api/ws", websocket.Handler(func(c *websocket.Conn) {
		WebsocketHandler(c, dbStore, sessionStore, options.RedisAddr)
	}))
	// let all API things go through autogzip.
	mux.Handle("/api/", autogzip.Handle(apiRouter))

	// deliver index.html through autogzip.
	deliverIndex := autogzip.HandleFunc(func(w http.ResponseWriter, r *http.Request) {
		http.ServeFile(w, r, path.Join(options.HtdocsDir, "index.html"))
	})

	// deliver index.html for AngularJS routes.
	mux.HandleFunc("/v/", deliverIndex)
	mux.HandleFunc("/s/", deliverIndex)

	// XXX make sure that files from /userdata/ don't go through autogzip. That messes up
	// the load progress of pdf.js.
	mux.Handle("/userdata/", http.StripPrefix("/userdata/", fileStore))

	mux.HandleFunc("/contact", deliverIndex)
	mux.HandleFunc("/tos", deliverIndex)
	mux.HandleFunc("/settings", deliverIndex)

	// deliver static files from htdocs, autogzip'd.
	mux.Handle("/", autogzip.Handle(http.FileServer(http.Dir(options.HtdocsDir))))

	handler := http.Handler(mux)
	if options.AccessLog {
		handler = Logger(handler)
	}

	l, ppid, err := goagain.GetEnvs()
	if err != nil {
		StatCount("satsuma start", 1)
		xlog.Debugf("Starting HTTP server on %s", options.Addr)
		laddr, err := net.ResolveTCPAddr("tcp", options.Addr)
		if err != nil {
			xlog.Fatalf("net.ResolveTCPAddr failed: %v", err)
		}
		l, err = net.ListenTCP("tcp", laddr)
		if err != nil {
			xlog.Fatalf("net.ListenTCP failed: %v", err)
		}
		go http.Serve(l, handler)
	} else {
		StatCount("satsuma reload", 1)
		go http.Serve(l, handler)

		if err := goagain.KillParent(ppid); err != nil {
			xlog.Fatalf("goagain.KillParent failed: %v", err)
		}
	}

	if err := goagain.AwaitSignals(l); nil != err {
		xlog.Fatalf("goagain.AwaitSignals failed: %v", err)
	}

	if err := l.Close(); err != nil {
		xlog.Fatalf("Closing listening socket failed: %v", err)
	}

	// TODO: make sure all requests are finished before exiting, e.g. through a common waitgroup.

	time.Sleep(1 * time.Second)
}
Example #3
0
func (a *App) goAgainListenAndServe(listenNet, listenAddr string) {
	l, ppid, err := goagain.GetEnvs()

	if err != nil {
		a.Info("No parent - starting listener on %s:%s", listenNet, listenAddr)
		// No parent, start our own listener
		l, err = net.Listen(listenNet, listenAddr)
		a.Debug("Listener is %v err is %v", l, err)
		if err != nil {
			a.Fatalln(err)
		}
	} else {
		// We have a parent, and we're now listening. Tell them to shut down.
		a.Info("Child taking over from graceful parent. Killing ppid %d\n", ppid)
		if err := goagain.KillParent(ppid); nil != err {
			a.Fatalln(err)
		}
	}
	go func() {
		a.Serve(l)
	}()

	// Block the main goroutine awaiting signals.
	if err := goagain.AwaitSignals(l); nil != err {
		a.Fatalln(err)
	}

	a.Error("Signal received - starting exit or restart")

	// We're the parent. Our child has taken over the listening duties. We can close
	// off our listener and drain pending requests.
	l.Close()
	waitSecs, _ := a.Cfg.GetInt("gop", "graceful_wait_secs", 60)
	timeoutChan := time.After(time.Second * time.Duration(waitSecs))

	tickMillis, _ := a.Cfg.GetInt("gop", "graceful_poll_msecs", 500)
	tickChan := time.Tick(time.Millisecond * time.Duration(tickMillis))

	waiting := true
	for waiting {
		select {
		case <-timeoutChan:
			{
				a.Errorf("Graceful restart timed out after %d seconds - being less graceful and exiting", waitSecs)
				waiting = false
			}
		case <-tickChan:
			{
				appStats := a.GetStats()
				if appStats.currentReqs-appStats.currentWSReqs >= 0 {
					a.Error("Graceful restart - no pending non-ws requests - time to die")
					waiting = false
				} else {
					a.Info("Graceful restart - tick still have %d pending reqs", appStats.currentReqs)
				}
			}
		}
	}

	appStats := a.GetStats()
	a.Info("Graceful restart/exit - with %d pending reqs", appStats.currentReqs)
}
Example #4
0
func main() {

	flag.Usage = usage
	flag.Parse()
	runtime.SetBlockProfileRate(*blockProfileRate)
	runtime.MemProfileRate = *memProfileRate

	// Default to strict validation
	config.Legacy_metric_validation.Level = m20.Strict

	config_file = "/etc/carbon-relay-ng.ini"
	if 1 == flag.NArg() {
		config_file = flag.Arg(0)
	}

	if _, err := toml.DecodeFile(config_file, &config); err != nil {
		log.Error("Cannot use config file '%s':\n", config_file)
		log.Error(err.Error())
		usage()
		return
	}
	//runtime.SetBlockProfileRate(1) // to enable block profiling. in my experience, adds 35% overhead.

	levels := map[string]logging.Level{
		"critical": logging.CRITICAL,
		"error":    logging.ERROR,
		"warning":  logging.WARNING,
		"notice":   logging.NOTICE,
		"info":     logging.INFO,
		"debug":    logging.DEBUG,
	}
	level, ok := levels[config.Log_level]
	if !ok {
		log.Error("unrecognized log level '%s'\n", config.Log_level)
		return
	}
	logging.SetLevel(level, "carbon-relay-ng")
	if *cpuprofile != "" {
		f, err := os.Create(*cpuprofile)
		if err != nil {
			log.Fatal(err)
		}
		pprof.StartCPUProfile(f)
		defer pprof.StopCPUProfile()
	}

	if len(config.Instance) == 0 {
		log.Error("instance identifier cannot be empty")
		os.Exit(1)
	}

	runtime.GOMAXPROCS(config.max_procs)

	instance = config.Instance
	expvar.NewString("instance").Set(instance)
	expvar.NewString("service").Set(service)

	log.Notice("===== carbon-relay-ng instance '%s' starting. =====\n", instance)

	numIn = Counter("unit=Metric.direction=in")
	numInvalid = Counter("unit=Err.type=invalid")
	if config.Instrumentation.Graphite_addr != "" {
		addr, err := net.ResolveTCPAddr("tcp", config.Instrumentation.Graphite_addr)
		if err != nil {
			log.Fatal(err)
		}
		go metrics.Graphite(metrics.DefaultRegistry, time.Duration(config.Instrumentation.Graphite_interval)*time.Millisecond, "", addr)
	}

	log.Notice("creating routing table...")
	maxAge, err := time.ParseDuration(config.Bad_metrics_max_age)
	if err != nil {
		log.Error("could not parse badMetrics max age")
		log.Error(err.Error())
		os.Exit(1)
	}
	badMetrics = badmetrics.New(maxAge)
	table = NewTable(config.Spool_dir)
	log.Notice("initializing routing table...")
	for i, cmd := range config.Init {
		log.Notice("applying: %s", cmd)
		err = applyCommand(table, cmd)
		if err != nil {
			log.Error("could not apply init cmd #%d", i+1)
			log.Error(err.Error())
			os.Exit(1)
		}
	}
	tablePrinted := table.Print()
	log.Notice("===========================")
	log.Notice("========== TABLE ==========")
	log.Notice("===========================")
	for _, line := range strings.Split(tablePrinted, "\n") {
		log.Notice(line)
	}

	// Follow the goagain protocol, <https://github.com/rcrowley/goagain>.
	l, ppid, err := goagain.GetEnvs()
	if nil != err {
		laddr, err := net.ResolveTCPAddr("tcp", config.Listen_addr)
		if nil != err {
			log.Error(err.Error())
			os.Exit(1)
		}
		l, err = net.ListenTCP("tcp", laddr)
		if nil != err {
			log.Error(err.Error())

			os.Exit(1)
		}
		log.Notice("listening on %v/tcp", laddr)
		go accept(l.(*net.TCPListener), config)
	} else {
		log.Notice("resuming listening on %v/tcp", l.Addr())
		go accept(l.(*net.TCPListener), config)
		if err := goagain.KillParent(ppid); nil != err {
			log.Error(err.Error())
			os.Exit(1)
		}
		for {
			err := syscall.Kill(ppid, 0)
			if err != nil {
				break
			}
			time.Sleep(10 * time.Millisecond)
		}
	}

	udp_addr, err := net.ResolveUDPAddr("udp", config.Listen_addr)
	if nil != err {
		log.Error(err.Error())
		os.Exit(1)
	}
	udp_conn, err := net.ListenUDP("udp", udp_addr)
	if nil != err {
		log.Error(err.Error())
		os.Exit(1)
	}
	log.Notice("listening on %v/udp", udp_addr)
	go handle(udp_conn, config)

	if config.Pid_file != "" {
		f, err := os.Create(config.Pid_file)
		if err != nil {
			fmt.Println("error creating pidfile:", err.Error())
			os.Exit(1)
		}
		_, err = f.Write([]byte(strconv.Itoa(os.Getpid())))
		if err != nil {
			fmt.Println("error writing to pidfile:", err.Error())
			os.Exit(1)
		}
		f.Close()
	}

	if config.Admin_addr != "" {
		go func() {
			err := adminListener(config.Admin_addr)
			if err != nil {
				fmt.Println("Error listening:", err.Error())
				os.Exit(1)
			}
		}()
	}

	if config.Http_addr != "" {
		go HttpListener(config.Http_addr, table)
	}

	if err := goagain.AwaitSignals(l); nil != err {
		log.Error(err.Error())
		os.Exit(1)
	}
}