From 0d27205c7c042edfe08a7f970f9cdeeaab2ed36f Mon Sep 17 00:00:00 2001 From: Henrik Hautakoski Date: Fri, 13 Aug 2021 12:59:22 +0200 Subject: [PATCH] Inital Commit --- .gitignore | 1 + LICENSE | 21 ++++++++++ Makefile | 25 +++++++++++ compile.sh | 79 ++++++++++++++++++++++++++++++++++ go.mod | 17 ++++++++ go.sum | 16 +++++++ scripts/pkg.sh | 36 ++++++++++++++++ scripts/pkg_deb.sh | 46 ++++++++++++++++++++ src/cmd/main.go | 91 ++++++++++++++++++++++++++++++++++++++++ src/haproxy/go.mod | 3 ++ src/haproxy/types.go | 15 +++++++ src/tcp_server/client.go | 44 +++++++++++++++++++ src/tcp_server/go.mod | 3 ++ src/tcp_server/server.go | 56 +++++++++++++++++++++++++ src/tcp_server/types.go | 19 +++++++++ 15 files changed, 472 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 Makefile create mode 100755 compile.sh create mode 100644 go.mod create mode 100644 go.sum create mode 100755 scripts/pkg.sh create mode 100755 scripts/pkg_deb.sh create mode 100644 src/cmd/main.go create mode 100644 src/haproxy/go.mod create mode 100644 src/haproxy/types.go create mode 100644 src/tcp_server/client.go create mode 100644 src/tcp_server/go.mod create mode 100644 src/tcp_server/server.go create mode 100644 src/tcp_server/types.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..567609b --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +build/ diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..9d2afaa --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 EOS Sw/eden + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..a6b6c56 --- /dev/null +++ b/Makefile @@ -0,0 +1,25 @@ +GO = go +GOCCFLAGS = -v +GOLDFLAGS = -ldflags="-s -w" +PREFIX = /usr/local + +PROGRAM_NAME=eth-healthcheck +SOURCES=src/cmd/main.go + +all: build +build: build/$(PROGRAM_NAME) + +build/$(PROGRAM_NAME) : $(SOURCES) + $(GO) build -o $@ $(GOCCFLAGS) $(GOLDFLAGS) $^ + +pkg_info : + echo PACKAGE_NAME=\"$(PROGRAM_NAME)\" "\n"\ + PACKAGE_DESCRIPTION=\"HAproxy healthcheck program for Etherium nodes.\" "\n"\ + PACKAGE_VERSION=\"0.1.0\" "\n"\ + PACKAGE_PREFIX=\"$(PREFIX:/%=%)\" "\n"\ + PACKAGE_PROGRAM=\"$(PROGRAM_NAME)\" > build/pkg_info + +package : build pkg_info + +package_deb: package + ./scripts/pkg.sh deb $(realpath build) diff --git a/compile.sh b/compile.sh new file mode 100755 index 0000000..993b8f9 --- /dev/null +++ b/compile.sh @@ -0,0 +1,79 @@ +#!/bin/bash + +SYSTEMS=( windows linux freebsd ) + +ARCHS=( 386 amd64 amd64p32 arm arm64 ppc ppc64 ) + +function usage() { + echo "Usage: ${0##*/} [ -h|--help ] [ --target ] [ -a|--arch ] [ -p|--package ]" + echo "" + echo " Valid systems:" + for i in "${SYSTEMS[@]}"; do + echo " * ${i}" + done + echo "" + echo " Valid architectures:" + for i in "${ARCHS[@]}"; do + echo " * ${i}" + done + echo "" + exit 1 +} + +options=$(getopt -n "${0##*/}" -o "ht:a:p" -l "help,target:,arch:,package" -- "$@") + +[ $? -eq 0 ] || usage + +eval set -- "$options" + +MAKE_TARGET="all" + +while true; do + + case $1 in + -p|--package) + MAKE_TARGET="package_deb" + ;; + -t|--target) + shift + REGEX=$(echo "${SYSTEMS[@]}" | sed 's/[[:space:]]/|/g') + [[ ! "$1" =~ ^($REGEX)$ ]] && { + echo "Incorrect system '$1' provided" + usage + } + export GOOS=$1 + ;; + -a|--arch) + shift + REGEX=$(echo "${ARCHS[@]}" | sed 's/[[:space:]]/|/g') + [[ ! "$1" =~ ^($REGEX)$ ]] && { + echo "Incorrect architecture '$1' provided" + usage + } + export GOARCH=$1 + ;; + -h|--help) usage ;; + --) shift + break + ;; + esac + shift +done + +MESSAGE="" +if [ ! -z "${GOOS}" ]; then + # Hack to select the right package :) + if [ "${MAKE_TARGET}" == "package_deb" ] && [ "${GOOS}" == "freebsd" ]; then + MAKE_TARGET="package_freebsd" + fi + MESSAGE="[\e[34m::\e[0m] Crosscompiling for: ${GOOS}" +fi + +if [ ! -z "${GOARCH}" ]; then + MESSAGE="${MESSAGE} (${GOARCH})" +fi + + +[ ! -z "${MESSAGE}" ] && echo -e "" $MESSAGE + +make -B ${MAKE_TARGET} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..9d74589 --- /dev/null +++ b/go.mod @@ -0,0 +1,17 @@ +module eosswedenorg/eth-healthcheck + +go 1.14 + +require ( + github.com/go-stack/stack v1.8.0 + github.com/inconshreveable/log15 v0.0.0-20201112154412-8562bdadbbac + github.com/mattn/go-colorable v0.1.8 + github.com/mattn/go-isatty v0.0.13 + github.com/onrik/ethrpc v1.0.0 + github.com/pborman/getopt/v2 v2.1.0 + internal/haproxy v1.0.0 + internal/tcp_server v1.0.0 +) + +replace internal/haproxy => ./src/haproxy +replace internal/tcp_server => ./src/tcp_server diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..c35083b --- /dev/null +++ b/go.sum @@ -0,0 +1,16 @@ +github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/inconshreveable/log15 v0.0.0-20201112154412-8562bdadbbac h1:n1DqxAo4oWPMvH1+v+DLYlMCecgumhhgnxAPdqDIFHI= +github.com/inconshreveable/log15 v0.0.0-20201112154412-8562bdadbbac/go.mod h1:cOaXtrgN4ScfRrD9Bre7U1thNq5RtJ8ZoP4iXVGRj6o= +github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8= +github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.13 h1:qdl+GuBjcsKKDco5BsxPJlId98mSWNKqYA+Co0SC1yA= +github.com/mattn/go-isatty v0.0.13/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/onrik/ethrpc v1.0.0 h1:caG0U/Y1op32mntqmDnwvzue4Tg37cSA2EU8PMIMSjM= +github.com/onrik/ethrpc v1.0.0/go.mod h1:RoqOlDiBBs1qYamkcYhxMgkPijxu5R8t55mgUiy4le8= +github.com/pborman/getopt/v2 v2.1.0 h1:eNfR+r+dWLdWmV8g5OlpyrTYHkhVNxHBdN2cCrJmOEA= +github.com/pborman/getopt/v2 v2.1.0/go.mod h1:4NtW75ny4eBw9fO1bhtNdYTlZKYX5/tBLtsOpwKIKd0= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae h1:/WDfKMnPU+m5M4xB+6x4kaepxRw6jWvR5iDRdvjHgy8= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= diff --git a/scripts/pkg.sh b/scripts/pkg.sh new file mode 100755 index 0000000..7a14972 --- /dev/null +++ b/scripts/pkg.sh @@ -0,0 +1,36 @@ +#!/bin/bash + +############################ +# Exported variables. # +############################ + +export BASE_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" + +if [ $# -lt 2 ]; then + echo "$0 " + exit 1 +fi + +PKG_TYPE=$1 +BUILD_DIR=$2 + +PKG_SCRIPT="${BASE_DIR}/pkg_${PKG_TYPE}.sh" + +# Check and call script +if [ ! -x $PKG_SCRIPT ]; then + echo "$PKG_SCRIPT not found" + exit 1 +fi + +# Info +set -o allexport +source ${BUILD_DIR}/pkg_info +set +o allexport + +# Directories. +export PACKAGE_BINDIR=${PACKAGE_PREFIX}/bin +export PACKAGE_SHAREDIR=${PACKAGE_PREFIX}/share/${PACKAGE_NAME} +export PACKAGE_TMPDIR="${BUILD_DIR}/pkg_${PKG_TYPE}" +export BUILD_DIR + +$PKG_SCRIPT diff --git a/scripts/pkg_deb.sh b/scripts/pkg_deb.sh new file mode 100755 index 0000000..249bb30 --- /dev/null +++ b/scripts/pkg_deb.sh @@ -0,0 +1,46 @@ +#!/usr/bin/env bash + +PACKAGE_ARCH=amd64 + +if [[ -f /etc/upstream-release/lsb-release ]]; then + source /etc/upstream-release/lsb-release +elif [[ -f /etc/lsb-release ]]; then + source /etc/lsb-release +else + echo "ERROR: could not determine debian release." + exit 1 +fi + +DISTRIB_ID=$(echo $DISTRIB_ID | tr '[:upper:]' '[:lower:]') + +# Default to 1 if no release is set. +if [[ -z $RELEASE ]]; then + RELEASE="1" +fi + +PACKAGE_FULLNAME="${PACKAGE_NAME}_${PACKAGE_VERSION}-${RELEASE}-${DISTRIB_ID}-${DISTRIB_RELEASE}_${PACKAGE_ARCH}" + +rm -fr ${PACKAGE_TMPDIR} + +# Create debian files. +mkdir -p ${PACKAGE_TMPDIR}/DEBIAN +echo "Package: ${PACKAGE_NAME} +Version: ${PACKAGE_VERSION}-${RELEASE} +Section: introspection +Priority: optional +Architecture: ${PACKAGE_ARCH} +Homepage: https://github.com/eosswedenorg/eth-healthcheck +Maintainer: Henrik Hautakoski +Description: ${PACKAGE_DESCRIPTION}" &> ${PACKAGE_TMPDIR}/DEBIAN/control + +cat ${PACKAGE_TMPDIR}/DEBIAN/control + +# Copy program +mkdir -p ${PACKAGE_TMPDIR}/${PACKAGE_BINDIR} +cp ${BUILD_DIR}/${PACKAGE_PROGRAM} ${PACKAGE_TMPDIR}/${PACKAGE_BINDIR}/${PACKAGE_NAME} + +# Copy files. +mkdir -p ${PACKAGE_TMPDIR}/${PACKAGE_SHAREDIR} +cp ${BASE_DIR}/../LICENSE ${PACKAGE_TMPDIR}/${PACKAGE_SHAREDIR} + +fakeroot dpkg-deb --build ${PACKAGE_TMPDIR} ${BUILD_DIR}/${PACKAGE_FULLNAME}.deb diff --git a/src/cmd/main.go b/src/cmd/main.go new file mode 100644 index 0000000..5b9e0b3 --- /dev/null +++ b/src/cmd/main.go @@ -0,0 +1,91 @@ + +package main + +import ( + "fmt" + "strings" + log "github.com/inconshreveable/log15" + "github.com/pborman/getopt/v2" + "github.com/onrik/ethrpc" + "internal/haproxy" + "internal/tcp_server" +) + +var logger log.Logger + +func onConnect(c *tcp_server.Client) { + logger.Info("Client connected", "addr", c.Addr.String()) +} + +func onTcpMessage(c *tcp_server.Client, message string) { + + status := haproxy.HealthCheckDown + client := ethrpc.New(strings.TrimSpace(message)) + + resp, err := client.EthSyncing() + if err == nil { + if resp.IsSyncing == false { + status = haproxy.HealthCheckUp + } + } else { + logger.Warn(err.Error()) + } + + logger.Info("Node status", "status", status) + + // Report status to HAproxy + c.WriteString(fmt.Sprintln(status)) + c.Close() +} + +func onDisconnect(c *tcp_server.Client, err error) { + if err == nil { + logger.Info("Client disconnected", "addr", c.Addr.String()) + } else { + logger.Warn("Client disconnected", "addr", c.Addr.String(), "err", err) + } +} + +func argv_listen_addr() string { + + var addr string + + argv := getopt.Args() + if len(argv) > 0 { + addr = argv[0] + } else { + addr = "127.0.0.1" + } + + addr += ":" + if len(argv) > 1 { + addr += argv[1] + } else { + addr += "1301" + } + + return addr +} + +func main() { + + logger = log.New() + + getopt.Parse() + + addr := argv_listen_addr() + server := tcp_server.New(argv_listen_addr()) + + logger.Info(fmt.Sprintf("Listening on: %s", addr)) + + // TCP Client sends message. + server.OnConnect(onConnect) + server.OnMessage(onTcpMessage) + server.OnDisconnect(onDisconnect) + + err := server.Listen() + + if err != nil { + logger.Error(err.Error()) + } +} diff --git a/src/haproxy/go.mod b/src/haproxy/go.mod new file mode 100644 index 0000000..6786f2b --- /dev/null +++ b/src/haproxy/go.mod @@ -0,0 +1,3 @@ +module internal/haproxy + +go 1.14 diff --git a/src/haproxy/types.go b/src/haproxy/types.go new file mode 100644 index 0000000..50a5e43 --- /dev/null +++ b/src/haproxy/types.go @@ -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" +) diff --git a/src/tcp_server/client.go b/src/tcp_server/client.go new file mode 100644 index 0000000..a0139ee --- /dev/null +++ b/src/tcp_server/client.go @@ -0,0 +1,44 @@ + +package tcp_server + +import ( + "bufio" + "net" +) + +// Read client messages. +func (c *Client) read() { + reader := bufio.NewReader(c.conn) + for { + message, err := reader.ReadString('\n') + if err != nil { + c.conn.Close() + c.Server.onDisconnect(c, err) + return + } + c.Server.onMessage(c, message) + } +} + +// Write string to client. +func (c *Client) WriteString(message string) error { + return c.Write([]byte(message)) +} + +// Write bytes to client +func (c *Client) Write(b []byte) error { + _, err := c.conn.Write(b) + if err != nil { + c.conn.Close() + c.Server.onDisconnect(c, err) + } + return err +} + +func (c *Client) Conn() net.Conn { + return c.conn +} + +func (c *Client) Close() error { + return c.conn.Close() +} diff --git a/src/tcp_server/go.mod b/src/tcp_server/go.mod new file mode 100644 index 0000000..15f8ed7 --- /dev/null +++ b/src/tcp_server/go.mod @@ -0,0 +1,3 @@ +module internal/tcp_server + +go 1.14 diff --git a/src/tcp_server/server.go b/src/tcp_server/server.go new file mode 100644 index 0000000..328ce16 --- /dev/null +++ b/src/tcp_server/server.go @@ -0,0 +1,56 @@ + +package tcp_server + +import ( + "net" +) + +func New(address string) *server { + + server := &server{ + address: address, + } + + server.OnConnect(func(c *Client) {}) + server.OnMessage(func(c *Client, message string) {}) + server.OnDisconnect(func(c *Client, err error) {}) + + return server +} + +// Called when a client connects +func (s *server) OnConnect(callback func(c *Client)) { + s.onConnect = callback +} + +// Called the server gets a message from a client. +func (s *server) OnMessage(callback func(c *Client, message string)) { + s.onMessage = callback +} + +// Called when a connection is closed. +func (s *server) OnDisconnect(callback func(c *Client, err error)) { + s.onDisconnect = callback +} + +func (s *server) Listen() error { + + sock, err := net.Listen("tcp", s.address) + if err != nil { + return err + } + defer sock.Close() + + for { + conn, _ := sock.Accept() + c := &Client{ + conn: conn, + Server: s, + Addr: conn.RemoteAddr(), + } + s.onConnect(c) + go c.read() + } + + return nil +} diff --git a/src/tcp_server/types.go b/src/tcp_server/types.go new file mode 100644 index 0000000..dcdfffa --- /dev/null +++ b/src/tcp_server/types.go @@ -0,0 +1,19 @@ + +package tcp_server + +import ( + "net" +) + +type Client struct { + conn net.Conn + Addr net.Addr + Server *server +} + +type server struct { + address string + onConnect func(c *Client) + onDisconnect func(c *Client, err error) + onMessage func(c *Client, message string) +}