Skip to content
This repository was archived by the owner on Nov 18, 2017. It is now read-only.

Commit 982a5a2

Browse files
author
Joshua Deare
committed
Add API to governor
1 parent 426748f commit 982a5a2

File tree

11 files changed

+283
-15
lines changed

11 files changed

+283
-15
lines changed

api/api.go

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package api
2+
3+
import (
4+
"encoding/json"
5+
"github.com/compose/governor/fsm"
6+
"github.com/compose/governor/ha"
7+
"github.com/compose/governor/service"
8+
"github.com/gorilla/mux"
9+
"github.com/pkg/errors"
10+
"net/http"
11+
)
12+
13+
func Router(singleFSM fsm.SingleLeaderFSM, singleHA *ha.SingleLeaderHA, singleService service.SingleLeaderService) (*mux.Router, error) {
14+
r := mux.NewRouter()
15+
if err := registerFSMRouter(singleFSM, singleService, r.PathPrefix("/fsm").Subrouter()); err != nil {
16+
return nil, errors.Wrap(err, "Error registering FSM subrouter for API")
17+
}
18+
if err := registerServiceRouter(singleService, r.PathPrefix("/service").Subrouter()); err != nil {
19+
return nil, errors.Wrap(err, "Error registering Service subrouter for API")
20+
}
21+
if err := registerHARouter(singleHA, r.PathPrefix("/ha").Subrouter()); err != nil {
22+
return nil, errors.Wrap(err, "Error registering HA subrouter for API")
23+
}
24+
return r, nil
25+
}
26+
27+
type apiSuccessResponse struct {
28+
Data interface{} `json:"data,omitempty"`
29+
}
30+
type apiErrorResponse struct {
31+
Errors []error `json:"errors"`
32+
}
33+
34+
func sendResponse(code int, jsonData interface{}, errorMsgs []error, w http.ResponseWriter) error {
35+
var resp interface{}
36+
if len(errorMsgs) > 0 {
37+
resp = apiErrorResponse{Errors: errorMsgs}
38+
} else if jsonData != nil {
39+
resp = apiSuccessResponse{Data: jsonData}
40+
} else {
41+
return errors.New("Must supply either errorsMsgs or jsonDATA to response")
42+
}
43+
44+
w.Header().Set("Content-Type", "application/json")
45+
w.WriteHeader(code)
46+
47+
if resp == nil {
48+
return nil
49+
}
50+
51+
if err := json.NewEncoder(w).Encode(resp); err != nil {
52+
return errors.Wrap(err, "Error encoding JSON into response body")
53+
}
54+
return nil
55+
}

api/fsm_api.go

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
package api
2+
3+
import (
4+
log "github.com/Sirupsen/logrus"
5+
"github.com/compose/governor/fsm"
6+
"github.com/compose/governor/service"
7+
"github.com/gorilla/mux"
8+
"github.com/pkg/errors"
9+
"net/http"
10+
)
11+
12+
// TODO: Add long listener for updates
13+
func registerFSMRouter(singleFSM fsm.SingleLeaderFSM, singleService service.SingleLeaderService, r *mux.Router) error {
14+
r.HandleFunc("/id", singleFSMIDHandler(singleFSM)).Methods("GET")
15+
r.HandleFunc("/leader", singleFSMLeaderHandler(singleFSM, singleService)).Methods("GET")
16+
r.HandleFunc("/member/{id}", singleFSMMemberHandler(singleFSM, singleService)).Methods("GET")
17+
r.HandleFunc("/members", singleFSMMembersHandler(singleFSM, singleService)).Methods("GET")
18+
return nil
19+
}
20+
21+
func singleFSMIDHandler(singleFSM fsm.SingleLeaderFSM) http.HandlerFunc {
22+
type idAPIResp struct {
23+
ID uint64 `json:"id"`
24+
}
25+
return func(w http.ResponseWriter, req *http.Request) {
26+
id := singleFSM.UniqueID()
27+
if err := sendResponse(200, idAPIResp{ID: id}, []error{}, w); err != nil {
28+
log.Error("Error sending response for ID request")
29+
}
30+
}
31+
}
32+
33+
func singleFSMLeaderHandler(singleFSM fsm.SingleLeaderFSM, singleService service.SingleLeaderService) http.HandlerFunc {
34+
type leaderAPIResp struct {
35+
Leader fsm.Leader `json:"leader"`
36+
Exists bool `json:"exists"`
37+
}
38+
return func(w http.ResponseWriter, req *http.Request) {
39+
leaderData, exists, err := singleFSM.Leader()
40+
if err != nil {
41+
if err := sendResponse(500, nil, []error{err}, w); err != nil {
42+
log.Error("Error sending error response")
43+
}
44+
}
45+
leader, err := singleService.FSMLeaderFromBytes(leaderData)
46+
if err != nil {
47+
if err := sendResponse(500, nil, []error{err}, w); err != nil {
48+
log.Error("Error sending error response")
49+
}
50+
}
51+
if err := sendResponse(200, leaderAPIResp{Leader: leader, Exists: exists}, []error{}, w); err != nil {
52+
log.Error("Error sending leader response")
53+
54+
}
55+
}
56+
}
57+
58+
func singleFSMMemberHandler(singleFSM fsm.SingleLeaderFSM, singleService service.SingleLeaderService) http.HandlerFunc {
59+
type memberAPIResp struct {
60+
Member fsm.Member `json:"member"`
61+
Exists bool `json:"exists"`
62+
}
63+
return func(w http.ResponseWriter, req *http.Request) {
64+
vars := mux.Vars(req)
65+
id, ok := vars["id"]
66+
if !ok {
67+
if err := sendResponse(400, nil, []error{errors.New("ID Not provided in request for member")}, w); err != nil {
68+
log.Error("Error sending error response")
69+
}
70+
}
71+
72+
memberData, exists, err := singleFSM.Member(id)
73+
if err != nil {
74+
if err := sendResponse(500, nil, []error{err}, w); err != nil {
75+
log.Error("Error sending error response")
76+
}
77+
}
78+
member, err := singleService.FSMMemberFromBytes(memberData)
79+
if err != nil {
80+
if err := sendResponse(500, nil, []error{err}, w); err != nil {
81+
log.Error("Error sending error response")
82+
}
83+
}
84+
if err := sendResponse(200, memberAPIResp{Member: member, Exists: exists}, []error{}, w); err != nil {
85+
log.Error("Error sending leader response")
86+
87+
}
88+
}
89+
}
90+
91+
func singleFSMMembersHandler(singleFSM fsm.SingleLeaderFSM, singleService service.SingleLeaderService) http.HandlerFunc {
92+
type membersAPIResp struct {
93+
Members []fsm.Member `json:"members"`
94+
}
95+
return func(w http.ResponseWriter, req *http.Request) {
96+
membersData, err := singleFSM.Members()
97+
members := []fsm.Member{}
98+
for _, memberData := range membersData {
99+
member, err := singleService.FSMMemberFromBytes(memberData)
100+
if err != nil {
101+
if err := sendResponse(500, nil, []error{err}, w); err != nil {
102+
log.Error("Error sending error response")
103+
}
104+
}
105+
members = append(members, member)
106+
}
107+
if err != nil {
108+
if err := sendResponse(500, nil, []error{err}, w); err != nil {
109+
log.Error("Error sending error response")
110+
}
111+
}
112+
if err := sendResponse(200, membersAPIResp{Members: members}, []error{}, w); err != nil {
113+
log.Error("Error sending leader response")
114+
115+
}
116+
}
117+
}

api/ha_api.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package api
2+
3+
import (
4+
log "github.com/Sirupsen/logrus"
5+
"github.com/compose/governor/ha"
6+
"github.com/gorilla/mux"
7+
"net/http"
8+
)
9+
10+
func registerHARouter(singleHA *ha.SingleLeaderHA, r *mux.Router) error {
11+
r.HandleFunc("/is_leader", singleHAIsLeaderHandler(singleHA)).Methods("GET")
12+
return nil
13+
}
14+
15+
func singleHAIsLeaderHandler(singleHA *ha.SingleLeaderHA) http.HandlerFunc {
16+
type isLeaderAPIResp struct {
17+
IsLeader bool `json:"is_leader"`
18+
}
19+
return func(w http.ResponseWriter, req *http.Request) {
20+
isLeader, err := singleHA.IsLeader()
21+
if err != nil {
22+
sendResponse(500, nil, []error{err}, w)
23+
}
24+
if err := sendResponse(200, isLeaderAPIResp{IsLeader: isLeader}, []error{}, w); err != nil {
25+
log.Error("Error sending response for request")
26+
}
27+
}
28+
}

api/service_api.go

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package api
2+
3+
import (
4+
log "github.com/Sirupsen/logrus"
5+
"github.com/compose/governor/service"
6+
"github.com/gorilla/mux"
7+
"net/http"
8+
)
9+
10+
func registerServiceRouter(singleService service.SingleLeaderService, r *mux.Router) error {
11+
r.HandleFunc("/running_as_leader", singleServiceRunningAsLeaderHandler(singleService)).Methods("GET")
12+
r.HandleFunc("/is_running", singleServiceIsRunningHandler(singleService)).Methods("GET")
13+
r.HandleFunc("/is_healthy", singleServiceIsHealthyHandler(singleService)).Methods("GET")
14+
return nil
15+
}
16+
17+
func singleServiceRunningAsLeaderHandler(singleService service.SingleLeaderService) http.HandlerFunc {
18+
type isLeaderAPIResp struct {
19+
RunningAsLeader bool `json:"running_as_leader"`
20+
}
21+
return func(w http.ResponseWriter, req *http.Request) {
22+
runningAsLeader := singleService.RunningAsLeader()
23+
if err := sendResponse(200, isLeaderAPIResp{RunningAsLeader: runningAsLeader}, []error{}, w); err != nil {
24+
log.Error("Error sending response for ID request")
25+
}
26+
}
27+
}
28+
29+
func singleServiceIsRunningHandler(singleService service.SingleLeaderService) http.HandlerFunc {
30+
type isRunningAPIResp struct {
31+
IsRunning bool `json:"is_running"`
32+
}
33+
return func(w http.ResponseWriter, req *http.Request) {
34+
running := singleService.IsRunning()
35+
if err := sendResponse(200, isRunningAPIResp{IsRunning: running}, []error{}, w); err != nil {
36+
log.Error("Error sending response for ID request")
37+
}
38+
}
39+
}
40+
41+
func singleServiceIsHealthyHandler(singleService service.SingleLeaderService) http.HandlerFunc {
42+
type isHealthyAPIResp struct {
43+
IsHealthy bool `json:"is_healthy"`
44+
}
45+
return func(w http.ResponseWriter, req *http.Request) {
46+
healthy := singleService.IsHealthy()
47+
if err := sendResponse(200, isHealthyAPIResp{IsHealthy: healthy}, []error{}, w); err != nil {
48+
log.Error("Error sending response for ID request")
49+
}
50+
}
51+
}

configuration.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ type Configuration struct {
1313
DataDir string `yaml:"data_dir"`
1414
FSM *fsm.Config `yaml:"fsm"`
1515
Postgresql *service.PostgresqlConfig `yaml:"postgresql"`
16-
HAHealth string `yaml:"haproxy_health_endpoint"`
16+
APIPort int `yaml:"api_port"`
1717
}
1818

1919
func LoadConfiguration(path string) (Configuration, error) {

fsm/fsm.go

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -106,12 +106,12 @@ func (f *fsm) RaceForInit(timeout time.Duration) (bool, error) {
106106

107107
// TODO: allow custom logger to be passed in
108108
type Config struct {
109-
RaftPort int `yaml:"raft_port"`
110-
APIPort int `yaml:"api_port"`
111-
BootstrapPeers []string `yaml:"bootstrap_peers"`
112-
BootstrapNode bool `yaml:"is_bootstrap"`
113-
DataDir string `yaml:"data_dir"`
114-
ClusterID uint64 `yaml:"cluster_id"`
109+
RaftPort int `yaml:"raft_port"`
110+
ClusterConfigPort int `yaml:"cluster_config_port"`
111+
BootstrapPeers []string `yaml:"bootstrap_peers"`
112+
BootstrapNode bool `yaml:"is_bootstrap"`
113+
DataDir string `yaml:"data_dir"`
114+
ClusterID uint64 `yaml:"cluster_id"`
115115
// LeaderTTL in milliseconds
116116
LeaderTTL int `yaml:"leader_ttl"`
117117
// MemberTTL in milliseconds
@@ -138,7 +138,7 @@ func NewGovernorFSM(config *Config) (SingleLeaderFSM, error) {
138138
FSM: newFSM,
139139
ClusterID: config.ClusterID,
140140
RaftPort: config.RaftPort,
141-
APIPort: config.APIPort,
141+
APIPort: config.ClusterConfigPort,
142142
BootstrapPeers: config.BootstrapPeers,
143143
BootstrapNode: config.BootstrapNode,
144144
DataDir: config.DataDir,

governor.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,11 @@ import (
55
//"os/exec"
66
"flag"
77
log "github.com/Sirupsen/logrus"
8+
"github.com/compose/governor/api"
89
"github.com/compose/governor/fsm"
910
"github.com/compose/governor/ha"
1011
"github.com/compose/governor/service"
12+
"net/http"
1113
"os"
1214
"os/signal"
1315
"path/filepath"
@@ -102,6 +104,15 @@ func main() {
102104
}).Info("Clean Shutdown Finished")
103105
}
104106
}(singleHA, singleLeaderState, pg)
107+
108+
go func() {
109+
router, err := api.Router(singleLeaderState, singleHA, pg)
110+
if err != nil {
111+
log.Error("Could not start API")
112+
}
113+
http.ListenAndServe(fmt.Sprintf(":%d", configuration.APIPort), router)
114+
}()
115+
105116
if err := singleHA.Run(); err != nil {
106117
log.Fatalf("Error Running HA, %+v", err)
107118
}

ha/ha.go

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -303,7 +303,7 @@ func (ha *SingleLeaderHA) RunCycle() error {
303303
}
304304
}
305305

306-
isLeader, err := ha.isLeader()
306+
isLeader, err := ha.IsLeader()
307307
if err != nil {
308308
return err
309309
}
@@ -469,14 +469,17 @@ func (ha *SingleLeaderHA) leaderIsMe(leader fsm.Leader) (bool, error) {
469469

470470
}
471471

472-
func (ha *SingleLeaderHA) isLeader() (bool, error) {
473-
curLeader := ha.service.FSMLeaderTemplate()
474-
exists, err := ha.fsm.Leader(curLeader)
472+
func (ha *SingleLeaderHA) IsLeader() (bool, error) {
473+
leaderData, exists, err := ha.fsm.Leader()
475474
if err != nil {
476475
return false, err
477476
} else if !exists {
478477
return false, nil
479478
}
479+
curLeader, err := ha.service.FSMLeaderFromBytes(leaderData)
480+
if err != nil {
481+
return false, errors.Wrap(err, "Error getting leader from bytes")
482+
}
480483

481484
meAsLeader, err := ha.service.AsFSMLeader()
482485
if err != nil {

postgres0.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
loop_wait: 1000 #milliseconds
22
data_dir: "data/postgres0" #canoe and pg data will go here
3+
api_port: 5000
34
fsm:
45
raft_port: 1234
5-
api_port: 1244
6+
cluster_config_port: 1244
67
#bootstrap_peers:
78
#- http://localhost:1245
89
#TODO: Alter canoe to allow set-list of bootstrap peers

postgres1.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
loop_wait: 1000 #milliseconds
22
data_dir: "data/postgres1" #canoe and pg data will go here
3+
api_port: 5001
34
fsm:
45
raft_port: 1235
5-
api_port: 1245
6+
cluster_config_port: 1245
67
bootstrap_peers:
78
- http://localhost:1244
89
#TODO: Alter canoe to allow set-list of bootstrap peers

0 commit comments

Comments
 (0)