Skip to content

Commit

Permalink
Merge pull request #145 from jllucas/restore
Browse files Browse the repository at this point in the history
Restore from backup & Delete backup
  • Loading branch information
gdiazlo committed Jul 16, 2019
2 parents 2fd75b1 + 4bb3c08 commit 25e7c9a
Show file tree
Hide file tree
Showing 32 changed files with 1,025 additions and 166 deletions.
154 changes: 77 additions & 77 deletions api/apihttp/apihttp.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,45 @@ type HealthCheckResponse struct {
Status string `json:"status"`
}

// NewApiHttp returns a new *http.ServeMux containing the current API handlers.
// /health-check -> Qed server healthcheck
// /events -> Add event operation
// /events/bulk -> Add event bulk operation
// /proofs/membership -> Membership query using event
// /proofs/digest-membership -> Membership query using event digest
// /proofs/incremental -> Incremental query
// /info/shards -> Qed cluster information
func NewApiHttp(balloon raftwal.RaftBalloonApi) *http.ServeMux {

api := http.NewServeMux()
api.HandleFunc("/healthcheck", AuthHandlerMiddleware(HealthCheckHandler))
api.HandleFunc("/events", AuthHandlerMiddleware(Add(balloon)))
api.HandleFunc("/events/bulk", AuthHandlerMiddleware(AddBulk(balloon)))
api.HandleFunc("/proofs/membership", AuthHandlerMiddleware(Membership(balloon)))
api.HandleFunc("/proofs/digest-membership", AuthHandlerMiddleware(DigestMembership(balloon)))
api.HandleFunc("/proofs/incremental", AuthHandlerMiddleware(Incremental(balloon)))
api.HandleFunc("/info/shards", AuthHandlerMiddleware(InfoShardsHandler(balloon)))

return api
}

// AuthHandlerMiddleware function is an HTTP handler wrapper that performs
// simple authorization tasks. Currently only checks that Api-Key is present.
//
// If Api-Key is not present, it will raise a `http.StatusUnauthorized` errror.
func AuthHandlerMiddleware(handler http.HandlerFunc) http.HandlerFunc {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {

// Check if Api-Key header is empty
if r.Header.Get("Api-Key") == "" {
http.Error(w, "Missing Api-Key header", http.StatusUnauthorized)
return
}

handler.ServeHTTP(w, r)
})
}

// HealthCheckHandler checks the system status and returns it accordinly.
// The http call it answer is:
// HEAD /
Expand Down Expand Up @@ -341,83 +380,6 @@ func Incremental(api raftwal.RaftBalloonApi) http.HandlerFunc {
}
}

// AuthHandlerMiddleware function is an HTTP handler wrapper that performs
// simple authorization tasks. Currently only checks that Api-Key is present.
//
// If Api-Key is not present, it will raise a `http.StatusUnauthorized` errror.
func AuthHandlerMiddleware(handler http.HandlerFunc) http.HandlerFunc {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {

// Check if Api-Key header is empty
if r.Header.Get("Api-Key") == "" {
http.Error(w, "Missing Api-Key header", http.StatusUnauthorized)
return
}

handler.ServeHTTP(w, r)
})
}

// NewApiHttp returns a new *http.ServeMux containing the current API handlers.
// /events -> Add event operation
// /events/bulk -> Add event bulk operation
// /health-check -> Qed server healthcheck
// /info/shards -> Qed cluster information
// /proofs/membership -> Membership query using event
// /proofs/digest-membership -> Membership query using event digest
// /proofs/incremental -> Incremental query
func NewApiHttp(balloon raftwal.RaftBalloonApi) *http.ServeMux {

api := http.NewServeMux()
api.HandleFunc("/healthcheck", AuthHandlerMiddleware(HealthCheckHandler))
api.HandleFunc("/events", AuthHandlerMiddleware(Add(balloon)))
api.HandleFunc("/events/bulk", AuthHandlerMiddleware(AddBulk(balloon)))
api.HandleFunc("/proofs/membership", AuthHandlerMiddleware(Membership(balloon)))
api.HandleFunc("/proofs/digest-membership", AuthHandlerMiddleware(DigestMembership(balloon)))
api.HandleFunc("/proofs/incremental", AuthHandlerMiddleware(Incremental(balloon)))
api.HandleFunc("/info/shards", AuthHandlerMiddleware(InfoShardsHandler(balloon)))

return api
}

type statusWriter struct {
http.ResponseWriter
status int
length int
}

func (w *statusWriter) WriteHeader(status int) {
w.status = status
w.ResponseWriter.WriteHeader(status)
}

func (w *statusWriter) Write(b []byte) (int, error) {
if w.status == 0 {
w.status = 200
}
w.length = len(b)
return w.ResponseWriter.Write(b)
}

// LogHandler Logs the Http Status for a request into fileHandler and returns a
// httphandler function which is a wrapper to log the requests.
func LogHandler(handle http.Handler) http.HandlerFunc {
return func(w http.ResponseWriter, request *http.Request) {
start := time.Now()
writer := statusWriter{w, 0, 0}
handle.ServeHTTP(&writer, request)
latency := time.Now().Sub(start)

log.Debugf("Request: lat %d %+v", latency, request)
if writer.status >= 400 && writer.status < 500 {
log.Infof("Bad Request: %d %+v", latency, request)
}
if writer.status >= 500 {
log.Infof("Server error: %d %+v", latency, request)
}
}
}

// InfoShardsHandler returns information about QED shards.
// The http post url is:
// GET /info/shards
Expand Down Expand Up @@ -483,6 +445,44 @@ func InfoShardsHandler(balloon raftwal.RaftBalloonApi) http.HandlerFunc {
}
}

// LogHandler Logs the Http Status for a request into fileHandler and returns a
// httphandler function which is a wrapper to log the requests.
func LogHandler(handle http.Handler) http.HandlerFunc {
return func(w http.ResponseWriter, request *http.Request) {
start := time.Now()
writer := statusWriter{w, 0, 0}
handle.ServeHTTP(&writer, request)
latency := time.Now().Sub(start)

log.Debugf("Request: lat %d %+v", latency, request)
if writer.status >= 400 && writer.status < 500 {
log.Infof("Bad Request: %d %+v", latency, request)
}
if writer.status >= 500 {
log.Infof("Server error: %d %+v", latency, request)
}
}
}

type statusWriter struct {
http.ResponseWriter
status int
length int
}

func (w *statusWriter) WriteHeader(status int) {
w.status = status
w.ResponseWriter.WriteHeader(status)
}

func (w *statusWriter) Write(b []byte) (int, error) {
if w.status == 0 {
w.status = 200
}
w.length = len(b)
return w.ResponseWriter.Write(b)
}

// PostReqSanitizer function checks that certain request info exists and it is correct.
func PostReqSanitizer(w http.ResponseWriter, r *http.Request) (http.ResponseWriter, *http.Request, error) {
if r.Method != "POST" {
Expand Down
5 changes: 5 additions & 0 deletions api/apihttp/apihttp_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ func (b fakeRaftBalloon) QueryDigestMembershipConsistency(keyDigest hashing.Dige
Hasher: hashing.NewFakeXorHasher(),
}, nil
}

func (b fakeRaftBalloon) QueryDigestMembership(keyDigest hashing.Digest) (*balloon.MembershipProof, error) {
return &balloon.MembershipProof{
Exists: true,
Expand Down Expand Up @@ -151,6 +152,10 @@ func (b fakeRaftBalloon) ListBackups() []*storage.BackupInfo {
return nil
}

func (b fakeRaftBalloon) DeleteBackup(backupID uint32) error {
return nil
}

func TestHealthCheckHandler(t *testing.T) {
// Create a request to pass to our handler. We don't have any query parameters for now, so we'll
// pass 'nil' as the third parameter.
Expand Down
102 changes: 87 additions & 15 deletions api/mgmthttp/mgmthttp.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,18 +20,19 @@ package mgmthttp
import (
"encoding/json"
"net/http"
"strconv"

"github.com/bbva/qed/api/apihttp"
"github.com/bbva/qed/raftwal"
)

// NewMgmtHttp will return a mux server with the endpoint required to
// join the raft cluster.
// NewMgmtHttp will return a mux server with endpoints to manage different
// QED log service features: DDBB backups, Raft membership,...
func NewMgmtHttp(balloon raftwal.RaftBalloonApi) *http.ServeMux {
mux := http.NewServeMux()
mux.HandleFunc("/join", joinHandle(balloon))
mux.HandleFunc("/backup", backupHandle(balloon))
mux.HandleFunc("/backups", listBackupsHandle(balloon))
mux.HandleFunc("/backup", apihttp.AuthHandlerMiddleware(ManageBackup(balloon)))
mux.HandleFunc("/backups", apihttp.AuthHandlerMiddleware(ListBackups(balloon)))
return mux
}

Expand Down Expand Up @@ -88,25 +89,67 @@ func joinHandle(api raftwal.RaftBalloonApi) http.HandlerFunc {
}
}

func backupHandle(api raftwal.RaftBalloonApi) http.HandlerFunc {
func ManageBackup(api raftwal.RaftBalloonApi) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var err error
// Make sure we can only be called with an HTTP POST request.
w, _, err = apihttp.PostReqSanitizer(w, r)
if err != nil {
switch r.Method {
case "DELETE":
DeleteBackup(api, w, r)
case "POST":
CreateBackup(api, w, r)
default:
w.Header().Set("Allow", "POST, DELETE")
w.WriteHeader(http.StatusMethodNotAllowed)
return
}
}
}

if err := api.Backup(); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
// CreateBackup creates a backup of the RocksDB data up to now:
// The http post url is:
// POST /backup
//
// The following statuses are expected:
// If everything is alright, the HTTP status is 200 with an empty body.
func CreateBackup(api raftwal.RaftBalloonApi, w http.ResponseWriter, r *http.Request) {
// Make sure we can only be called with an HTTP POST request.
if r.Method != "POST" {
w.Header().Set("Allow", "POST")
w.WriteHeader(http.StatusMethodNotAllowed)
return
}

w.WriteHeader(http.StatusOK)
if err := api.Backup(); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}

w.WriteHeader(http.StatusOK)
}

func listBackupsHandle(api raftwal.RaftBalloonApi) http.HandlerFunc {
// ListBackups returns a list of backups along with each backup information.
// The http post url is:
// GET /backups
//
// The following statuses are expected:
// If everything is alright, the HTTP status is 204 and the body contains:
// [
// {
// "ID": 1,
// "Timestamp": 1523653256854,
// "Size": 16786",
// "NumFiles": 4,
// "Metadata": "foo"
// },
// {
// "ID": 2,
// "Timestamp": 1523653256999,
// "Size": 21786,
// "NumFiles": 4,
// "Metadata": "bar"
// },
// ...
// ]
func ListBackups(api raftwal.RaftBalloonApi) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var err error
// Make sure we can only be called with an HTTP GET request.
Expand All @@ -132,3 +175,32 @@ func listBackupsHandle(api raftwal.RaftBalloonApi) http.HandlerFunc {
_, _ = w.Write(out)
}
}

// DeleteBackup deletes a certain backup (given its ID) from the system:
// The http post url is:
// DELETE /backup?backupID=<id>
//
// The following statuses are expected:
// If everything is alright, the HTTP status is 204 with an empty body.
func DeleteBackup(api raftwal.RaftBalloonApi, w http.ResponseWriter, r *http.Request) {

if r.Method != "DELETE" {
w.Header().Set("Allow", "DELETE")
w.WriteHeader(http.StatusMethodNotAllowed)
return
}

b := r.URL.Query()["backupID"]
backupID, err := strconv.ParseUint(b[0], 10, 32)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
return
}

if err := api.DeleteBackup(uint32(backupID)); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}

w.WriteHeader(http.StatusNoContent)
}

0 comments on commit 25e7c9a

Please sign in to comment.