Graceful restart!

This commit is contained in:
Howl 2016-04-07 19:32:48 +02:00
parent 8d99ff1070
commit ed2778e2cc
5 changed files with 107 additions and 5 deletions

View File

@ -10,7 +10,7 @@ import (
) )
// Start begins taking HTTP connections. // 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 := gin.Default()
r.Use(gzip.Gzip(gzip.DefaultCompression), ErrorHandler()) r.Use(gzip.Gzip(gzip.DefaultCompression), ErrorHandler())
@ -38,12 +38,20 @@ func Start(conf common.Conf, db *sql.DB) {
// ReadConfidential privilege required // ReadConfidential privilege required
gv1.GET("/friends", Method(v1.FriendsGET, db, common.PrivilegeReadConfidential)) gv1.GET("/friends", Method(v1.FriendsGET, db, common.PrivilegeReadConfidential))
gv1.GET("/friends/with/:id", Method(v1.FriendsWithGET, 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) r.NoRoute(v1.Handle404)
if conf.Unix {
return r
/*if conf.Unix {
panic(r.RunUnix(conf.ListenTo)) panic(r.RunUnix(conf.ListenTo))
} }
panic(r.Run(conf.ListenTo)) panic(r.Run(conf.ListenTo))*/
} }

22
app/v1/meta.go Normal file
View File

@ -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
}

View File

@ -16,6 +16,7 @@ type privilegesData struct {
PrivilegeManageRoles bool `json:"manage_roles"` PrivilegeManageRoles bool `json:"manage_roles"`
PrivilegeManageAPIKeys bool `json:"manage_api_keys"` PrivilegeManageAPIKeys bool `json:"manage_api_keys"`
PrivilegeBlog bool `json:"blog"` 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. // 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(), PrivilegeManageRoles: md.User.Privileges.HasPrivilegeManageRoles(),
PrivilegeManageAPIKeys: md.User.Privileges.HasPrivilegeManageAPIKeys(), PrivilegeManageAPIKeys: md.User.Privileges.HasPrivilegeManageAPIKeys(),
PrivilegeBlog: md.User.Privileges.HasPrivilegeBlog(), PrivilegeBlog: md.User.Privileges.HasPrivilegeBlog(),
PrivilegeAPIMeta: md.User.Privileges.HasPrivilegeAPIMeta(),
} }
return return
} }

View File

@ -15,10 +15,11 @@ const (
PrivilegeManageRoles // translates as admin, as they can basically assign roles to anyone, even themselves 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. 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. 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. // 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. // HasPrivilegeRead returns whether the Read privilege is included in the privileges.
func (p Privileges) HasPrivilegeRead() bool { func (p Privileges) HasPrivilegeRead() bool {
@ -75,6 +76,11 @@ func (p Privileges) HasPrivilegeBlog() bool {
return p&PrivilegeBlog != 0 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{ var privilegeString = [...]string{
"Read", "Read",
"ReadConfidential", "ReadConfidential",
@ -87,6 +93,7 @@ var privilegeString = [...]string{
"ManageRoles", "ManageRoles",
"ManageAPIKeys", "ManageAPIKeys",
"Blog", "Blog",
"APIMeta",
} }
func (p Privileges) String() string { func (p Privileges) String() string {
@ -111,6 +118,7 @@ var privilegeMustBe = [...]int{
4, 4,
4, 4,
3, 3,
4,
} }
// CanOnly removes any privilege that the user has requested to have, but cannot have due to their rank. // CanOnly removes any privilege that the user has requested to have, but cannot have due to their rank.

64
main.go
View File

@ -2,14 +2,26 @@ package main
import ( import (
"database/sql" "database/sql"
"fmt"
"log" "log"
"net"
"net/http"
"syscall"
"time"
// Golint pls dont break balls // Golint pls dont break balls
_ "github.com/go-sql-driver/mysql" _ "github.com/go-sql-driver/mysql"
"github.com/osuripple/api/app" "github.com/osuripple/api/app"
"github.com/osuripple/api/common" "github.com/osuripple/api/common"
"github.com/rcrowley/goagain"
) )
func init() {
log.SetFlags(log.Ltime)
log.SetPrefix(fmt.Sprintf("%d|", syscall.Getpid()))
}
func main() { func main() {
conf, halt := common.Load() conf, halt := common.Load()
if halt { if halt {
@ -19,5 +31,55 @@ func main() {
if err != nil { if err != nil {
log.Fatal(err) 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)
} }