package tools import ( "encoding/json" "errors" "fmt" "strings" "cloud.o-forge.io/core/oc-lib/config" "cloud.o-forge.io/core/oc-lib/dbs/mongo" beego "github.com/beego/beego/v2/server/web" ) /* * API is the Health Check API * it defines the health check methods */ var UncatchedError = []error{} // Singleton instance of the api 500 error cache type State int // State is an enum that defines the state of the API const ( ALIVE State = iota REDUCED_SERVICE // occurs when some services are down UNPROCESSABLE_ENTITY // occurs when the database is up but the collections are not DB_FALLOUT // occurs when the database is down TEAPOT // well some things boils in here, i'm probably a teapot, occurs when uncatched errors are present (it's fun) DEAD // occurs when the peer is dead ) // EnumIndex returns the index of the enum func (s State) EnumIndex() int { return int(s) } // ToState returns the state from a string func ToState(str string) State { for _, s := range []State{ALIVE, REDUCED_SERVICE, UNPROCESSABLE_ENTITY, DB_FALLOUT, TEAPOT, DEAD} { if s.String() == str { return s } } return DEAD } // String returns the string of the enum func (s State) String() string { return [...]string{"alive", "reduced service", "unprocessable entity", "database fallout", "some things boils in here, i'm probably a teapot", "dead"}[s] } type API struct{} func (a *API) Discovered(infos []*beego.ControllerInfo) { respondToDiscovery := func(m map[string]interface{}) { if len(m) == 0 { a.SubscribeRouter(infos) } } a.ListenRouter(respondToDiscovery) a.SubscribeRouter(infos) } // GetState returns the state of the API func (a *API) GetState() (State, int, error) { // Check if the database is up err := mongo.MONGOService.TestDB(config.GetConfig()) if err != nil { return DB_FALLOUT, 200, err // If the database is not up, return database fallout } err = mongo.MONGOService.TestCollections(config.GetConfig(), []string{}) // Check if the collections are up if err != nil { return UNPROCESSABLE_ENTITY, 200, err // If the collections are not up, return unprocessable entity } if len(UncatchedError) > 0 { // If there are uncatched errors, return teapot errStr := "" for _, e := range UncatchedError { errStr += e.Error() + "\n" } return TEAPOT, 200, errors.New(errStr) } return ALIVE, 200, nil // If everything is up, return alive } func (a *API) ListenRouter(exec func(msg map[string]interface{})) { go NewNATSCaller().ListenNats(DISCOVERY.GenerateKey("api"), exec) } func (a *API) SubscribeRouter(infos []*beego.ControllerInfo) { nats := NewNATSCaller() discovery := map[string][]string{} for _, info := range infos { path := strings.ReplaceAll(info.GetPattern(), "/oc/", "/"+strings.ReplaceAll(config.GetAppName(), "oc-", "")) for k, v := range info.GetMethod() { if discovery[path] == nil { discovery[path] = []string{} } if strings.Contains(strings.ToLower(v), "internal") { discovery[path] = append(discovery[path], "INTERNAL"+k) } else { discovery[path] = append(discovery[path], k) } } } nats.SetNATSPub("api", DISCOVERY, discovery) } // CheckRemotePeer checks the state of a remote peer func (a *API) CheckRemotePeer(url string) (State, map[string]int) { // Check if the database is up caller := NewHTTPCaller(map[DataType]map[METHOD]string{}) // Create a new http caller var resp APIStatusResponse b, err := caller.CallPost(url, "", map[string]interface{}{}) // Call the status endpoint of the peer if err != nil { return DEAD, map[string]int{} // If the peer is not reachable, return dead } json.Unmarshal(b, &resp) if resp.Data == nil { // If the response is empty, return dead return DEAD, map[string]int{} } new := map[string]int{} for k, v := range resp.Data.Services { // Return the services states of the peer new[k] = ToState(v).EnumIndex() } return ToState(resp.Data.State), new // Return the state of the peer & its services states } // CheckRemoteAPIs checks the state of remote APIs from your proper OC func (a *API) CheckRemoteAPIs(apis []DataType) (State, map[string]string, error) { // Check if the database is up new := map[string]string{} caller := NewHTTPCaller(map[DataType]map[METHOD]string{}) // Create a new http caller code := 0 e := "" state := ALIVE reachable := false for _, api := range apis { // Check the state of each remote API in the list var resp APIStatusResponse b, err := caller.CallGet("http://"+api.API()+":8080", "/oc/version/status") // Call the status endpoint of the remote API (standard OC status endpoint) if err != nil { state = REDUCED_SERVICE // If a remote API is not reachable, return reduced service continue } json.Unmarshal(b, &resp) fmt.Println(string(b)) if resp.Data == nil { // state = REDUCED_SERVICE // If the response is empty, return reduced service continue } new[api.String()] = resp.Data.State if resp.Data.Code > code { code = resp.Data.Code e += resp.Error } reachable = true // If the remote API is reachable, set reachable to true cause we are not dead } if !reachable { state = DEAD // If no remote API is reachable, return dead, nobody is alive } if code > 0 { state = REDUCED_SERVICE } return state, new, nil } /* APIStatusResponse is the response of the API status */ type APIStatusResponse struct { Data *APIStatus `json:"data"` Error string `json:"error"` } /* * APIStatus is the status of the API * it defines the state of the API * Code is the status code, where 0 is ALIVE, 1 is REDUCED_SERVICE, 2 is UNPROCESSABLE_ENTITY, 3 is DB_FALLOUT, 4 is TEAPOT, 5 is DEAD */ type APIStatus struct { Code int `json:"code"` // Code is the status code, where 0 is ALIVE, 1 is REDUCED_SERVICE, 2 is UNPROCESSABLE_ENTITY, 3 is DB_FALLOUT, 4 is TEAPOT, 5 is DEAD State string `json:"state"` // State is the state of the API (status shows as a string) (alive, reduced service, unprocessable entity, database fallout, some things boils in here, i'm probably a teapot, dead) Services map[string]string `json:"services"` // Services is the state of the services of the API (status shows as a string) (alive, reduced service, unprocessable entity, database fallout, some things boils in here, i'm probably a teapot, dead) }