From ed2778e2cc68ac72c160c4aed30ae773bd7603b1 Mon Sep 17 00:00:00 2001 From: Howl Date: Thu, 7 Apr 2016 19:32:48 +0200 Subject: [PATCH] Graceful restart! --- app/start.go | 14 +++++++--- app/v1/meta.go | 22 +++++++++++++++ app/v1/privileges.go | 2 ++ common/privileges.go | 10 ++++++- main.go | 64 +++++++++++++++++++++++++++++++++++++++++++- 5 files changed, 107 insertions(+), 5 deletions(-) create mode 100644 app/v1/meta.go diff --git a/app/start.go b/app/start.go index 496c2ba..e071471 100644 --- a/app/start.go +++ b/app/start.go @@ -10,7 +10,7 @@ import ( ) // Start begins taking HTTP connections. -func Start(conf common.Conf, db *sql.DB) { +func Start(conf common.Conf, db *sql.DB) *gin.Engine { r := gin.Default() r.Use(gzip.Gzip(gzip.DefaultCompression), ErrorHandler()) @@ -38,12 +38,20 @@ func Start(conf common.Conf, db *sql.DB) { // ReadConfidential privilege required gv1.GET("/friends", Method(v1.FriendsGET, db, common.PrivilegeReadConfidential)) gv1.GET("/friends/with/:id", Method(v1.FriendsWithGET, db, common.PrivilegeReadConfidential)) + + // M E T A + // E T "wow thats so meta" + // T E -- the one who said "wow that's so meta" + // A T E M + gv1.GET("/meta/restart", Method(v1.MetaRestartGET, db, common.PrivilegeAPIMeta)) } } r.NoRoute(v1.Handle404) - if conf.Unix { + + return r + /*if conf.Unix { panic(r.RunUnix(conf.ListenTo)) } - panic(r.Run(conf.ListenTo)) + panic(r.Run(conf.ListenTo))*/ } diff --git a/app/v1/meta.go b/app/v1/meta.go new file mode 100644 index 0000000..42d41cc --- /dev/null +++ b/app/v1/meta.go @@ -0,0 +1,22 @@ +package v1 + +import ( + "os" + "syscall" + + "github.com/osuripple/api/common" +) + +// MetaRestartGET restarts the API with Zero Downtime™. +func MetaRestartGET(md common.MethodData) (r common.Response) { + proc, err := os.FindProcess(syscall.Getpid()) + if err != nil { + r.Code = 500 + r.Message = "couldn't find process. what the fuck?" + return + } + r.Code = 200 + r.Message = "brb" + go proc.Signal(syscall.SIGUSR2) + return +} diff --git a/app/v1/privileges.go b/app/v1/privileges.go index b4fb534..f1a241b 100644 --- a/app/v1/privileges.go +++ b/app/v1/privileges.go @@ -16,6 +16,7 @@ type privilegesData struct { PrivilegeManageRoles bool `json:"manage_roles"` PrivilegeManageAPIKeys bool `json:"manage_api_keys"` PrivilegeBlog bool `json:"blog"` + PrivilegeAPIMeta bool `json:"api_meta"` } // PrivilegesGET returns an explaination for the privileges, telling the client what they can do with this token. @@ -34,6 +35,7 @@ func PrivilegesGET(md common.MethodData) (r common.Response) { PrivilegeManageRoles: md.User.Privileges.HasPrivilegeManageRoles(), PrivilegeManageAPIKeys: md.User.Privileges.HasPrivilegeManageAPIKeys(), PrivilegeBlog: md.User.Privileges.HasPrivilegeBlog(), + PrivilegeAPIMeta: md.User.Privileges.HasPrivilegeAPIMeta(), } return } diff --git a/common/privileges.go b/common/privileges.go index 47e19fc..614f0d0 100644 --- a/common/privileges.go +++ b/common/privileges.go @@ -15,10 +15,11 @@ const ( PrivilegeManageRoles // translates as admin, as they can basically assign roles to anyone, even themselves PrivilegeManageAPIKeys // admin permission to manage user permission, not only self permissions. Only ever do this if you completely trust the application, because this essentially means to put the entire ripple database in the hands of a (potentially evil?) application. PrivilegeBlog // can do pretty much anything to the blog, and the documentation. + PrivilegeAPIMeta // can do /meta API calls. basically means they can restart the API server. ) // Privileges is a bitwise enum of the privileges of an user's API key. -type Privileges int +type Privileges uint64 // HasPrivilegeRead returns whether the Read privilege is included in the privileges. func (p Privileges) HasPrivilegeRead() bool { @@ -75,6 +76,11 @@ func (p Privileges) HasPrivilegeBlog() bool { return p&PrivilegeBlog != 0 } +// HasPrivilegeAPIMeta returns whether the Blog privilege is included in the privileges. +func (p Privileges) HasPrivilegeAPIMeta() bool { + return p&PrivilegeAPIMeta != 0 +} + var privilegeString = [...]string{ "Read", "ReadConfidential", @@ -87,6 +93,7 @@ var privilegeString = [...]string{ "ManageRoles", "ManageAPIKeys", "Blog", + "APIMeta", } func (p Privileges) String() string { @@ -111,6 +118,7 @@ var privilegeMustBe = [...]int{ 4, 4, 3, + 4, } // CanOnly removes any privilege that the user has requested to have, but cannot have due to their rank. diff --git a/main.go b/main.go index ae7d939..4a0e375 100644 --- a/main.go +++ b/main.go @@ -2,14 +2,26 @@ package main import ( "database/sql" + "fmt" "log" + "net" + "net/http" + "syscall" + "time" // Golint pls dont break balls _ "github.com/go-sql-driver/mysql" "github.com/osuripple/api/app" "github.com/osuripple/api/common" + + "github.com/rcrowley/goagain" ) +func init() { + log.SetFlags(log.Ltime) + log.SetPrefix(fmt.Sprintf("%d|", syscall.Getpid())) +} + func main() { conf, halt := common.Load() if halt { @@ -19,5 +31,55 @@ func main() { if err != nil { log.Fatal(err) } - app.Start(conf, db) + engine := app.Start(conf, db) + + // Inherit a net.Listener from our parent process or listen anew. + l, err := goagain.Listener() + if nil != err { + + // Listen on a TCP or a UNIX domain socket (TCP here). + if conf.Unix { + l, err = net.Listen("unix", conf.ListenTo) + } else { + l, err = net.Listen("tcp", conf.ListenTo) + } + if nil != err { + log.Fatalln(err) + } + + log.Println("LISTENINGU STARTUATO ON", l.Addr()) + + // Accept connections in a new goroutine. + go http.Serve(l, engine) + + } else { + + // Resume accepting connections in a new goroutine. + log.Println("LISTENINGU RESUMINGU ON", l.Addr()) + go http.Serve(l, engine) + + // Kill the parent, now that the child has started successfully. + if err := goagain.Kill(); nil != err { + log.Fatalln(err) + } + + } + + // Block the main goroutine awaiting signals. + if _, err := goagain.Wait(l); nil != err { + log.Fatalln(err) + } + + // 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) + } + if err := db.Close(); err != nil { + log.Fatalln(err) + } + time.Sleep(time.Second * 1) + }