1
0
Fork 0
mirror of https://github.com/eosswedenorg/antelope-api-healthcheck synced 2026-06-18 05:00:03 +02:00

Initial commit

This commit is contained in:
Henrik Hautakoski 2020-02-03 13:24:34 +01:00
commit 33c8439604
9 changed files with 270 additions and 0 deletions

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
build/

31
Makefile Normal file
View file

@ -0,0 +1,31 @@
GO = go
GOCCFLAGS = -v
GOLDFLAGS =
PREFIX = /usr/local
PROGRAM_NAME=eos-api-healthcheck
SOURCES=server.go
DEPENDANCIES= github.com/firstrow/tcp_server \
github.com/liamylian/jsontime/v2 \
github.com/imroc/req
all: build
build: build/$(PROGRAM_NAME)
build/$(PROGRAM_NAME) : $(SOURCES)
$(GO) build -o $@ $(GOCCFLAGS) $(GOLDFLAGS) $<
deps:
$(GO) get $(DEPENDANCIES)
package_deb: build
export PACKAGE_NAME="$(PROGRAM_NAME)" \
export PACKAGE_VERSION="0.1.0" \
export PACKAGE_PREFIX=$(PREFIX:/%=%) \
export PACKAGE_PROGRAM="build/$(PROGRAM_NAME)" \
&& ./scripts/build_deb.sh
clean:
$(GO) clean
$(RM) -rf build/

44
eosapi/functions.go Normal file
View file

@ -0,0 +1,44 @@
package eosapi;
import (
"fmt"
"time"
"io/ioutil"
"github.com/imroc/req"
"github.com/liamylian/jsontime/v2"
)
var json = v2.ConfigWithCustomTimeFormat;
func init() {
// EOS Api does not specify timezone in timestamps (they are always UTC tho).
v2.SetDefaultTimeFormat("2006-01-02T15:04:05", time.UTC);
}
// GetInfo - Fetches get_info from API
// ---------------------------------------------------------
func GetInfo(host string, port int) (Info, error) {
var info Info;
// Format url.
url := fmt.Sprintf("http://%s:%d/v1/chain/get_info", host, port);
// Go's net.http (that `req` uses) sends the port in the host header.
// nodeos api does not like that, so we need to provide our
// own Host header with just the host.
headers := req.Header{
"Host": host,
}
// Send HTTP Get request.
r, err := req.Get(url, headers)
resp := r.Response()
body, _ := ioutil.ReadAll(resp.Body);
// Parse json
err = json.Unmarshal(body, &info);
return info, err;
}

11
eosapi/types.go Normal file
View file

@ -0,0 +1,11 @@
package eosapi;
import "time";
// get_info format (not all fields).
type Info struct {
ServerVersion string `json:"server_version"`
HeadBlockNum int64 `json:"head_block_num"`
HeadBlockTime time.Time `json:"head_block_time"`
}

15
haproxy/types.go Normal file
View file

@ -0,0 +1,15 @@
package haproxy;
// All supported health check values for HAproxy.
// See https://cbonte.github.io/haproxy-dconv/1.7/configuration.html#5.2-agent-check
type HealthCheckStatus string
const (
HealthCheckUp = "up"
HealthCheckDown = "down"
HealthCheckMaint = "maint"
HealthCheckReady = "ready"
HealthCheckDrain = "drain"
HealthCheckFailed = "failed"
HealthCheckStopped = "Stopped"
)

2
scripts/.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
pack/
*.deb

41
scripts/build_deb.sh Executable file
View file

@ -0,0 +1,41 @@
#!/usr/bin/env bash
BASE_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
PACKAGE_DESCRIPTION="HAproxy healthcheck program for EOS API."
PACKAGE_TMPDIR="pack"
# Default to 1 if no release is set.
if [[ -z $RELEASE ]]; then
RELEASE="1"
fi
PACKAGE_FULLNAME="${PACKAGE_NAME}_${PACKAGE_VERSION}-${RELEASE}_amd64"
rm -fr ${BASE_DIR}/${PACKAGE_TMPDIR}
# Create debian files.
mkdir -p ${BASE_DIR}/${PACKAGE_TMPDIR}/DEBIAN
echo "Package: ${PACKAGE_NAME}
Version: ${PACKAGE_VERSION}-${RELEASE}
Section: introspection
Priority: optional
Architecture: amd64
Homepage: https://github.com/eosswedenorg/eos-api-healthcheck
Maintainer: Henrik Hautakoski <henrik@eossweden.org>
Description: ${PACKAGE_DESCRIPTION}" &> ${BASE_DIR}/${PACKAGE_TMPDIR}/DEBIAN/control
cat ${BASE_DIR}/${PACKAGE_TMPDIR}/DEBIAN/control
# Create service file
mkdir -p ${BASE_DIR}/${PACKAGE_TMPDIR}/etc/systemd/system
cat ${BASE_DIR}/template.service \
| sed "s~{{ DESCRIPTION }}~${PACKAGE_DESCRIPTION}~" \
| sed "s~{{ PROGRAM }}~/${PACKAGE_PREFIX}/bin/${PACKAGE_NAME}~" \
> ${BASE_DIR}/${PACKAGE_TMPDIR}/etc/systemd/system/${PACKAGE_NAME}.service
# Copy program
mkdir -p ${BASE_DIR}/${PACKAGE_TMPDIR}/${PACKAGE_PREFIX}/bin
cp ${BASE_DIR}/../${PACKAGE_PROGRAM} ${BASE_DIR}/${PACKAGE_TMPDIR}/${PACKAGE_PREFIX}/bin/${PACKAGE_NAME}
fakeroot dpkg-deb --build ${BASE_DIR}/${PACKAGE_TMPDIR} ${BASE_DIR}/${PACKAGE_FULLNAME}.deb

20
scripts/template.service Normal file
View file

@ -0,0 +1,20 @@
[Unit]
Description={{ DESCRIPTION }}
After=network.target
[Service]
Type=simple
# Another Type: forking
#User=nanodano
#WorkingDirectory=/home/nanodano
ExecStart={{ PROGRAM }}
Restart=on-failure
# Other restart options: always, on-abort, etc
# The install section is needed to use
# `systemctl enable` to start on boot
# For a user service that you want to enable
# and start automatically, use `default.target`
# For system level services, use `multi-user.target`
[Install]
WantedBy=multi-user.target

105
server.go Normal file
View file

@ -0,0 +1,105 @@
package main
import (
"os"
"fmt"
"time"
"strings"
"strconv"
"./haproxy"
"./eosapi"
"github.com/firstrow/tcp_server"
)
// check_api - Validates head block time.
// ---------------------------------------------------------
func check_api(host string, port int) (haproxy.HealthCheckStatus, string) {
info, err := eosapi.GetInfo(host, port)
if err != nil {
msg := fmt.Sprintf("%s", err);
return haproxy.HealthCheckFailed, msg
}
// Validate head block.
now := time.Now().In(time.UTC)
diff := now.Sub(info.HeadBlockTime).Seconds()
if diff > 10.0 {
return haproxy.HealthCheckDown,
fmt.Sprintf("Taking offline because head block is lagging %.0f seconds", diff)
} else if diff < -10.0 {
return haproxy.HealthCheckDown,
fmt.Sprintf("Taking offline because head block is %.0f seconds into the future", diff)
}
return haproxy.HealthCheckUp, "OK"
}
// argv_listen_addr
// Parse listen address from command line.
// ---------------------------------------------------------
func argv_listen_addr() string {
var addr string
argv := os.Args[1:]
if len(argv) > 0 {
addr = argv[0]
} else {
addr = "127.0.0.1"
}
addr += ":"
if len(argv) > 1 {
addr += argv[1]
} else {
addr += "1337"
}
return addr
}
// main
// ---------------------------------------------------------
func main() {
server := tcp_server.New(argv_listen_addr())
// TCP Client connect.
server.OnNewClient(func(c *tcp_server.Client) {
fmt.Println("# Client connected")
});
// TCP Client sends message.
server.OnNewMessage(func(c *tcp_server.Client, message string) {
var host string
var port int = 80
// Parse host + port.
split := strings.Split(strings.TrimSpace(message), ":")
host = split[0]
if len(split) > 1 {
p, err := strconv.ParseInt(split[1], 10, 32)
if err == nil {
port = int(p)
}
}
// Check api.
status, msg := check_api(host, port)
fmt.Printf("API HealthCheck: %s, %s\n", status, msg)
// Report status to HAproxy
c.Send(fmt.Sprintln(status))
c.Close()
});
// TCP Client disconnect.
server.OnClientConnectionClosed(func(c *tcp_server.Client, err error) {
fmt.Println("# Client disconnected")
});
server.Listen()
}