diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..a62bc1a --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1 @@ +@vechainfoundation/node-hosting-code-owners \ No newline at end of file diff --git a/CODE_OF_CONDUCT.md b/.github/CODE_OF_CONDUCT.md similarity index 67% rename from CODE_OF_CONDUCT.md rename to .github/CODE_OF_CONDUCT.md index ec43f55..95e441c 100644 --- a/CODE_OF_CONDUCT.md +++ b/.github/CODE_OF_CONDUCT.md @@ -1,10 +1,10 @@ -# [Project Name] Code of Conduct +Node Healthcheck Code of Conduct ## 1. Introduction -The [Project Name] community is dedicated to providing a welcoming, inclusive, and harassment-free environment for everyone, regardless of age, gender, gender identity and expression, sexual orientation, disability, physical appearance, body size, race, ethnicity, nationality, religion, or technical experience. This Code of Conduct outlines our expectations for all members of the [Project Name] community, as well as the consequences for unacceptable behavior. +The Node Healthcheck community is dedicated to providing a welcoming, inclusive, and harassment-free environment for everyone, regardless of age, gender, gender identity and expression, sexual orientation, disability, physical appearance, body size, race, ethnicity, nationality, religion, or technical experience. This Code of Conduct outlines our expectations for all members of the Node Healthcheck community, as well as the consequences for unacceptable behavior. -We invite all those who participate in [Project Name] to help us create a safe and positive environment for everyone. +We invite all those who participate in Node Healthcheck to help us create a safe and positive environment for everyone. ## 2. Expected Behavior @@ -39,13 +39,11 @@ If a community member engages in unacceptable behavior, the community organizers ## 5. Reporting Guidelines -If you are subject to or witness unacceptable behavior, or have any other concerns, please notify a community organizer as soon as possible. You can contact the project maintainer at [email@example.com]. - -Additionally, community organizers are available to help community members engage with local law enforcement or to otherwise help those experiencing unacceptable behavior feel safe. In the context of in-person events, organizers will also provide escorts as desired by the person experiencing distress. +If you are subject to or witness unacceptable behavior, or have any other concerns, please [contact us](#8-contact-information) as soon as possible. ## 6. Addressing Grievances -If you feel you have been falsely or unfairly accused of violating this Code of Conduct, you should notify the project maintainer with a concise description of your grievance. Your grievance will be handled in accordance with our existing governing policies. +If you feel you have been falsely or unfairly accused of violating this Code of Conduct, you should [contact us](#8-contact-information) with a concise description of your grievance. Your grievance will be handled in accordance with our existing governing policies. ## 7. Scope @@ -53,7 +51,11 @@ We expect all community participants (contributors, paid or otherwise; sponsors; ## 8. Contact Information -You can contact the project maintainer at [email@example.com]. +* Project maintainer kostas.apostolopoulos@vechain.org +* Project maintainer rishi.pal@vechain.org +* Main contributor fabio.rigamonti@vechain.org +* Discord https://discord.com/invite/vechain #support +* Support https://support.vechain.org ## 9. License and Attribution diff --git a/CONTRIBUTING.md b/.github/CONTRIBUTING.md similarity index 66% rename from CONTRIBUTING.md rename to .github/CONTRIBUTING.md index be68228..e1b269a 100644 --- a/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -1,17 +1,17 @@ -# CONTRIBUTING to [Your Project Name] +# Contributing to Node Healthcheck -First of all, thank you for considering to contribute to [Your Project Name]! Your help is invaluable to improve the project and make it more useful for the community. This document will guide you through the contribution process and make it easier for you to get started. +First of all, thank you for considering to contribute to Node Healthcheck! Your help is invaluable to improve the project and make it more useful for the community. This document will guide you through the contribution process and make it easier for you to get started. ## Table of Contents -- [CONTRIBUTING to \[Your Project Name\]](#contributing-to-your-project-name) +- [Contributing to Node Healthcheck](#contributing-to-node-healthcheck) - [Table of Contents](#table-of-contents) - [Code of Conduct](#code-of-conduct) - - [Getting Started](#getting-started) - [How to Contribute](#how-to-contribute) - [Reporting Bugs](#reporting-bugs) - [Suggesting Enhancements](#suggesting-enhancements) - [Pull Requests](#pull-requests) + - [How to submit a pull request](#how-to-submit-a-pull-request) - [Style Guide](#style-guide) - [Additional Resources](#additional-resources) @@ -19,22 +19,11 @@ First of all, thank you for considering to contribute to [Your Project Name]! Yo By participating in this project, you agree to abide by our [Code of Conduct](CODE_OF_CONDUCT.md). Please read it to ensure a welcoming and inclusive environment for all contributors. -## Getting Started - -1. Fork the repository on GitHub. -2. Clone your fork to your local machine. -3. Create a new branch for your feature or bugfix. - 1. Use `git checkout -b feature/your-feature-name` for features. - 2. Use `git checkout -b bugfix/your-bugfix-name`) for bugfixes. -4. Make your changes and commit them to your new branch. -5. Push your changes to your fork. -6. Open a pull request against the main branch of the original repository. - ## How to Contribute ### Reporting Bugs -If you find a bug, please create a new issue in the [issue tracker](https://github.com/vechainfoundation/your_project_name/issues). When submitting a bug report, please include: +If you find a bug, please create a new issue in the [issue tracker](https://github.com/vechainfoundation/node-healthcheck/issues). When submitting a bug report, please include: - A clear and descriptive title. - A detailed description of the issue, including the steps to reproduce the bug. @@ -43,7 +32,7 @@ If you find a bug, please create a new issue in the [issue tracker](https://gith ### Suggesting Enhancements -If you have an idea for a new feature or improvement, please create a new issue in the [issue tracker](https://github.com/vechainfoundation/your_project_name/issues). When suggesting an enhancement, please include: +If you have an idea for a new feature or improvement, please create a new issue in the [issue tracker](https://github.com/vechainfoundation/node-healthcheck/issues). When suggesting an enhancement, please include: - A clear and descriptive title. - A detailed description of the proposed feature, including examples of how it should work. @@ -60,11 +49,25 @@ If you would like to contribute code, documentation, or other assets to the proj - Make sure your code passes any tests and linters that the project uses. - Update any relevant documentation or comments. +#### How to submit a pull request + +1. Fork the repository on GitHub. +2. Clone your fork to your local machine. +3. Create a new branch for your feature or bugfix based on the `main` branch. + 1. Use `git checkout -b feature/your-feature-name` for features. + 2. Use `git checkout -b bugfix/your-bugfix-name` for bugfixes. +4. Make your changes and commit them to your new branch. +5. Push your changes to your fork. +6. Open a pull request against the main branch of the original repository. + +> :information_source: Tip: +> Write your commit messages following [the git guidelines](https://git-scm.com/book/en/v2/Distributed-Git-Contributing-to-a-Project). Use present tense and imperative verbs, e.g. "Fix bug" and not "Fixed bug" or "Fixes bug.". Your commit message should describe what the commit, when applied, does to the code – not what you did to the code. + ## Style Guide Please adhere to the project's coding style and conventions when contributing. This may include: -- Code formatting rules (e.g., indentation, line length, etc.). +- Code formatting (e.g., indentation, line length, etc.). - Naming conventions for variables, functions, classes, etc. - Commenting guidelines, including when and how to write comments. - Test-writing guidelines, including test coverage requirements. @@ -77,6 +80,6 @@ If the project uses a specific code formatter or linter, please ensure your cont - [GitHub Help: Creating a pull request from a fork](https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/creating-a-pull-request-from-a-fork) - [Git Branching and Merging](https://git-scm.com/book/en/v2/Git-Branching-Basic-Branching-and-Merging) -Once again, thank you for your interest in contributing to [Your Project Name]! Your support and collaboration are crucial to the success and growth of this project. If you have any questions or need additional guidance, please don't hesitate to reach out to the maintainers. +Once again, thank you for your interest in contributing to Node Healthcheck! Your support and collaboration are crucial to the success and growth of this project. If you have any questions or need additional guidance, please don't hesitate to reach out to the maintainers. Happy coding! \ No newline at end of file diff --git a/LICENSE.md b/.github/LICENSE.md similarity index 100% rename from LICENSE.md rename to .github/LICENSE.md diff --git a/.github/README.md b/.github/README.md new file mode 100644 index 0000000..f14c30b --- /dev/null +++ b/.github/README.md @@ -0,0 +1,129 @@ +# Node Healthcheck + +![Node Hosting Project Logo](images/node-hosting.png) + +## Introduction + +This is a simple API that checks the health of a vechain node by comparing the timestamp of its latest block with the current time. If the timestamp is within an acceptable tolerance setting, it is considered healthy. + +The main purpose of this tool is to be used in load balancers and availability monitors, to determine whether a node is not only online, but also fully synchronized with the blockchain. Moreover, it provides a `metrics` endpoint that is compatible with [prometheus](https://github.com/prometheus/prometheus) and exposes node health information to it. + +The API contains two endpoints: + +- `/healthcheck` + + used by ALB to determine whether the node is online + +- `/metrics` + + used by Prometheus to collect health metrics, such as the last block timestamp, number of seconds since last block and node health status. + +![Node Hosting Design Diagram - Healthcheck](images/architecture-diagram-healthcheck.webp) + +## Table of Contents + +- [Node Healthcheck](#node-healthcheck) + - [Introduction](#introduction) + - [Table of Contents](#table-of-contents) + - [Getting Started](#getting-started) + - [Configuration](#configuration) + - [NODE\_URL](#node_url) + - [NODE\_HEALTHCHECK\_PORT](#node_healthcheck_port) + - [NODE\_HEALTHCHECK\_TOLERANCE\_IN\_SECONDS](#node_healthcheck_tolerance_in_seconds) + - [Using Node](#using-node) + - [Using Docker](#using-docker) + - [Release new docker image](#release-new-docker-image) + - [Contributing](#contributing) + - [Roadmap](#roadmap) + - [Changelog](#changelog) + - [License](#license) + - [Credits](#credits) + +## Getting Started + +You can run the application with node, or in a container using docker. + +### Configuration + +You may override the default configuration by modifying the `.env` file. + +#### NODE_URL + +The URL of the node to be monitored by the healthcheck. Defaults to `https://node-test.vechain.org` + +#### NODE_HEALTHCHECK_PORT + +The port of the healthcheck API. Defaults to `11012` + +#### NODE_HEALTHCHECK_TOLERANCE_IN_SECONDS + +The amount of seconds before the healthcheck classifies the node as `unhealthy`. Defaults to `15` + +### Using Node + +To install and run the application with [node version 16.20.2](https://nodejs.org/dist/v16.20.2/), run the following commands in the root directory of the project: + +```bash +cd src +npm ci +npm start +``` + +### Using Docker + +To build and run the image with docker, run the following commands in the root directory of the project: + +```bash +docker build . -t node-healthcheck:dev +docker run -d \ + --name node-hc \ + -p 11012:11012 \ + -e NODE_URL=https://mainnet.vechain.org \ + node-healthcheck:dev +``` + +To download and run the image with docker: + +```bash +docker run -d \ + --name node-hc \ + -p 11012:11012 \ + -e NODE_URL=https://mainnet.vechain.org \ + public.ecr.aws/vechainfoundation/node-healthcheck:latest +``` + +For more image tags, refer to our [Container Registry](https://gallery.ecr.aws/vechainfoundation/node-healthcheck). + +#### Release new docker image + +To release a new version of the exporter, follow these steps: +1. If you haven't done so already, enable multi-architecture builds on your system: + 1. Enable Docker BuildKit for multi-architecture builds by setting the environment variable in your shell profile: `export DOCKER_BUILDKIT=1` + 2. Create Docker BuildKit builder on your system: `docker buildx create --use` +2. You will also need to have the AWS CLI installed and configured with credentials for ECR through profile `prod-node-devops`. +3. Run `./release.sh ` to build and push the image to ECR, where `` is the tag for the new image. +4. Verify that the new release was correctly pushed to [the docker repository](https://gallery.ecr.aws/vechainfoundation/node-healthcheck). + +### Contributing + +If you want to contribute to this project and make it better, your help is very welcome. Contributing is also a great way to learn more about social coding on Github, new technologies and and their ecosystems and how to make constructive, helpful bug reports, feature requests and the noblest of all contributions: a good, clean pull request. + +For more details and guidelines on how to contribute, refer to [CONTRIBUTING](CONTRIBUTING.md). + +### Roadmap + +We are planning to add more features to this application going forward. More details to follow and suggestions are always welcome in the form of [GitHub issues](https://docs.github.com/en/issues/tracking-your-work-with-issues/creating-an-issue). + +### Changelog + +- v1 [31-Mar-2023] Add prometheus metrics for node health +- v0 [08-Feb-2023] Implement a basic healthcheck API for nodes, based on block timestamps + +### License + +This project is licensed under [the MIT license](LICENSE.md). + +### Credits + +Special recognition to the main contributors: +- @fabiorigam diff --git a/.github/images/architecture-diagram-healthcheck.png b/.github/images/architecture-diagram-healthcheck.png new file mode 100644 index 0000000..4f15679 Binary files /dev/null and b/.github/images/architecture-diagram-healthcheck.png differ diff --git a/.github/images/node-hosting.png b/.github/images/node-hosting.png new file mode 100644 index 0000000..84b06ec Binary files /dev/null and b/.github/images/node-hosting.png differ diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..01c8431 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,56 @@ +name: Build +on: + push: + branches: + - main + pull_request: + types: [opened, synchronize, reopened] +jobs: + gitleaks: + name: GitLeaks + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + with: + fetch-depth: 0 + - uses: gitleaks/gitleaks-action@v2 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITLEAKS_LICENSE: ${{ secrets.GITLEAKS_LICENSE }} + + sonarcloud: + name: SonarCloud + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + with: + fetch-depth: 0 # Shallow clones should be disabled for a better relevancy + - name: SonarCloud Scan + uses: SonarSource/sonarcloud-github-action@master + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + + build: + name: Build + runs-on: ubuntu-latest + defaults: + run: + working-directory: ./scripts + steps: + - name: Checkout + uses: actions/checkout@v3 + with: + fetch-depth: 0 + - name: Set up QEMU dependency + uses: docker/setup-qemu-action@v3 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + - name: Build docker image + run: ./build.sh + - name: Run docker image + run: ./run.sh + - name: Smoke test + run: ./test.sh diff --git a/.github/workflows/gitleaks.yml b/.github/workflows/gitleaks.yml deleted file mode 100644 index 8e0416b..0000000 --- a/.github/workflows/gitleaks.yml +++ /dev/null @@ -1,18 +0,0 @@ -name: gitleaks -on: - pull_request: - push: - branches: - - 'main' -jobs: - scan: - name: gitleaks - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - with: - fetch-depth: 0 - - uses: gitleaks/gitleaks-action@v2 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - GITLEAKS_LICENSE: ${{ secrets.GITLEAKS_LICENSE}} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..abcb366 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +**/node_modules/* \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..5225eb7 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,33 @@ +# Build stage for amd64 architecture +FROM --platform=linux/amd64 node:gallium-alpine3.18 AS builder-amd64 + +WORKDIR /app +# Copy the package.json and package-lock.json to the container +COPY ./src/package*.json . +RUN npm ci --ignore-scripts +# Copy the rest of the application code to the container +COPY ./src . + +# Build stage for arm64/v8 architecture +FROM --platform=linux/arm64/v8 node:gallium-alpine3.18 AS builder-arm64 + +WORKDIR /app +# Copy the package.json and package-lock.json to the container +COPY ./src/package*.json . +RUN npm ci --ignore-scripts +# Copy the rest of the application code to the container +COPY ./src . + +# Production stage +FROM node:gallium-alpine3.18 AS runtime + +RUN addgroup -S nonroot && adduser -S nonroot -G nonroot +WORKDIR /app + +# Copy the files from the appropriate builder stage based on the target architecture +COPY --from=builder-amd64 /app /app +COPY --from=builder-arm64 /app /app +USER nonroot + +# Set the default command to run when starting the container +CMD ["npm", "start"] \ No newline at end of file diff --git a/README.md b/README.md deleted file mode 100644 index b8c496f..0000000 --- a/README.md +++ /dev/null @@ -1,94 +0,0 @@ -# Project Title - -![Your Project Logo](link-to-logo-image) - -## Introduction - -A brief description of your project, its purpose, and main features. - -This is a template repository, that allows you to quickly create new repos with the following templates: -1. [README.md](/README.md) -2. [CONTRIBUTING.md](/CONTRIBUTING.md) -3. [CODE_OF_CONDUCT.md](/CODE_OF_CONDUCT.md) -4. [LICENSE.md](/LICENSE.md) - -Consider turning on branch protection for `main` as follows: -1. Require a pull request before merging. - 1. Require 1 approval. - 2. Dismiss stale pull request approvals when new commits are pushed. - 3. Require review from Code Owners. - 4. Require approval of the most recent reviewable push. -2. Require status checks to pass before merging. -3. Require branches to be up to date before merging. -4. Require conversation resolution before merging. -5. Require deployments to succeed before merging. - -## Table of Contents - -- [Project Title](#project-title) - - [Introduction](#introduction) - - [Table of Contents](#table-of-contents) - - [Getting Started](#getting-started) - - [Prerequisites](#prerequisites) - - [Installation](#installation) - - [Configuration](#configuration) - - [Usage](#usage) - - [Documentation](#documentation) - - [Contributing](#contributing) - - [Roadmap](#roadmap) - - [Changelog](#changelog) - - [License](#license) - - [Credits](#credits) - -## Getting Started - -### Prerequisites - -List the required software, libraries, or tools needed to use or contribute to the project. - -### Installation - -Provide step-by-step instructions for installing the project, including any required dependencies. - -```bash -# Example installation commands -``` - -### Configuration - -Explain how to configure the project, if necessary. - -### Usage - -Include code examples or usage instructions to help users get started quickly. - -### Documentation - -Link to any additional documentation or tutorials, either within your repository or hosted externally. - -### Contributing - -Explain how others can contribute to the project. Include information on: - - How to submit bug reports or feature requests. - The process for submitting pull requests. - Any specific coding standards or guidelines. - The best way to get in touch with the maintainers, if needed. - -You may use [a separate `CONTRIBUTING` file](/CONTRIBUTING.md) to keep your `README.md` short. - -### Roadmap - -Share the project's development roadmap, if available, including planned features and improvements. - -### Changelog - -Keep a log of all notable changes and updates in the project. - -### License - -This project is licensed under the LICENSE-NAME - see [the LICENSE file](/LICENSE.md) for details. - -### Credits - -Recognize any significant contributors, sponsors, or organizations that have supported the project. diff --git a/cspell.json b/cspell.json new file mode 100644 index 0000000..26a67e9 --- /dev/null +++ b/cspell.json @@ -0,0 +1,16 @@ +{ + "version": "0.2", + "ignorePaths": [], + "dictionaryDefinitions": [], + "dictionaries": [], + "words": [ + "BUILDKIT", + "buildx", + "gitleaks", + "healtchcheck", + "sonarcloud", + "vechainfoundation" + ], + "ignoreWords": [], + "import": [] +} diff --git a/scripts/all.sh b/scripts/all.sh new file mode 100755 index 0000000..37c01a5 --- /dev/null +++ b/scripts/all.sh @@ -0,0 +1,10 @@ +#!/bin/bash +set -e + +scripts_path="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +cd $scripts_path +./clean.sh +./build.sh +./run.sh +sleep 1 +./test.sh \ No newline at end of file diff --git a/scripts/build.sh b/scripts/build.sh new file mode 100755 index 0000000..26addb3 --- /dev/null +++ b/scripts/build.sh @@ -0,0 +1,7 @@ +#!/bin/bash +set -e + +scripts_path="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +cd $scripts_path +docker buildx build --platform linux/amd64,linux/arm64/v8 -t node-healthcheck:dev .. +docker buildx build --load -t node-healthcheck:dev .. diff --git a/scripts/clean.sh b/scripts/clean.sh new file mode 100755 index 0000000..0ad1477 --- /dev/null +++ b/scripts/clean.sh @@ -0,0 +1,5 @@ +#!/bin/bash +set -e + +docker rm node-healthcheck -f 2> /dev/null +docker rmi node-healthcheck:dev -f 2> /dev/null diff --git a/scripts/release.sh b/scripts/release.sh new file mode 100755 index 0000000..56277ef --- /dev/null +++ b/scripts/release.sh @@ -0,0 +1,12 @@ +#!/bin/bash +set -e + +if [ -z "$1" ]; then + echo "Please provide the tag for the new build." + exit 1 +fi +tag=$1 + +aws ecr-public get-login-password --region us-east-1 --profile prod-node-devops | docker login --username AWS --password-stdin public.ecr.aws/vechainfoundation +docker buildx build --platform linux/amd64,linux/arm64/v8 -t public.ecr.aws/vechainfoundation/node-healthcheck:$tag --push . +docker buildx build --platform linux/amd64,linux/arm64/v8 -t public.ecr.aws/vechainfoundation/node-healthcheck:latest --push . diff --git a/scripts/run.sh b/scripts/run.sh new file mode 100755 index 0000000..7aaaabc --- /dev/null +++ b/scripts/run.sh @@ -0,0 +1,8 @@ +#!/bin/bash +set -e + +docker run -d \ + --name node-healthcheck \ + -p 11012:11012 \ + -e NODE_URL="https://sync-testnet.vechain.org" \ + node-healthcheck:dev diff --git a/scripts/test.sh b/scripts/test.sh new file mode 100755 index 0000000..43474a1 --- /dev/null +++ b/scripts/test.sh @@ -0,0 +1,30 @@ +#!/bin/bash +set -e + +retry_command() { + local max_attempts=5 + local delay=3 + local attempt=0 + + until [ $attempt -ge $max_attempts ]; do + "$@" + local exit_code=$? + if [ $exit_code -eq 0 ]; then + return 0 + fi + + attempt=$((attempt + 1)) + echo "Command failed (Attempt $attempt/$max_attempts). Retrying in $delay seconds..." >&2 + sleep "$delay" + done + + echo "Command failed $max_attempts times. Giving up." >&2 +} + +response=$(retry_command curl -s http://localhost:11012/healthcheck) +is_healthy=$(echo $response | jq ".isHealthy") +if [ "$is_healthy" == "true" ]; then + echo "Smoke test successful." +else + echo "Smoke test failed." +fi diff --git a/sonar-project.properties b/sonar-project.properties new file mode 100644 index 0000000..9952e91 --- /dev/null +++ b/sonar-project.properties @@ -0,0 +1,12 @@ +sonar.projectKey=vechainfoundation_node-healthcheck +sonar.organization=vechainfoundation + +# This is the name and version displayed in the SonarCloud UI. +#sonar.projectName=node-healthcheck +#sonar.projectVersion=1.0 + +# Path is relative to the sonar-project.properties file. Replace "\" by "/" on Windows. +#sonar.sources=. + +# Encoding of the source code. Default is default system encoding +#sonar.sourceEncoding=UTF-8 \ No newline at end of file diff --git a/src/.dockerignore b/src/.dockerignore new file mode 100644 index 0000000..b512c09 --- /dev/null +++ b/src/.dockerignore @@ -0,0 +1 @@ +node_modules \ No newline at end of file diff --git a/src/.env b/src/.env new file mode 100644 index 0000000..de15ea3 --- /dev/null +++ b/src/.env @@ -0,0 +1,3 @@ +NODE_URL = https://mainnet.vechain.org +NODE_HEALTHCHECK_PORT = 11012 +NODE_HEALTHCHECK_TOLERANCE_IN_SECONDS = 15 \ No newline at end of file diff --git a/src/index.js b/src/index.js new file mode 100644 index 0000000..5fb1048 --- /dev/null +++ b/src/index.js @@ -0,0 +1,103 @@ +const polka = require('polka'); +const axios = require('axios'); +const prometheus = require('prom-client'); +require('dotenv').config(); + +const nodeLastBlockUri = new URL('/blocks/best', process.env.NODE_URL).href +const nodeHealthcheckPort = process.env.NODE_HEALTHCHECK_PORT +const nodeHealthcheckToleranceInSeconds = process.env.NODE_HEALTHCHECK_TOLERANCE_IN_SECONDS + +const register = new prometheus.Registry(); + +const lastBlockTimestampGauge = new prometheus.Gauge({ + name: 'node_last_block_timestamp', + help: 'Timestamp of the last block' +}); + +const secondsSinceLastBlockGauge = new prometheus.Gauge({ + name: 'node_seconds_since_last_block', + help: 'Seconds since the last block' +}); + +const healthcheckStatusGauge = new prometheus.Gauge({ + name: 'node_healthcheck_status', + help: 'Healthcheck status of the node' +}); + +register.registerMetric(lastBlockTimestampGauge); +register.registerMetric(secondsSinceLastBlockGauge); +register.registerMetric(healthcheckStatusGauge); + +function writeLog(message) { + console.log(`[${new Date().toISOString()}] ${message}`); +} + +async function requestLogger(req, res, next) { + writeLog(`Request - ${req.method}: ${req.url}`); + next(); +} + +async function responseLogger(req, res, next) { + writeLog(`Respond - ${res.statusCode}: ${JSON.stringify(req.node)}`) + next(); +} + +async function healthChecker(req, res, next) { + try { + req.timestamp = await axios.get(nodeLastBlockUri) + .then(response => { + const lastBlockTimestamp = response.data.timestamp; + const secondsSinceLastBlock = Math.floor(Date.now() / 1000) - lastBlockTimestamp; + const isHealthy = Math.abs(secondsSinceLastBlock) < nodeHealthcheckToleranceInSeconds + req.node = { + lastBlockTimestamp: lastBlockTimestamp, + secondsSinceLastBlock: secondsSinceLastBlock, + isHealthy: isHealthy + }; + lastBlockTimestampGauge.set(lastBlockTimestamp); + secondsSinceLastBlockGauge.set(secondsSinceLastBlock); + healthcheckStatusGauge.set(isHealthy ? 1 : 0); + }); + next(); + } catch (error) { + writeLog(`Error - ${error}`); + res.statusCode = 500; + res.end(`Error: ${error}`); + } +} + +async function writeMetrics(req, res) { + const metrics = [ + `node_last_block_timestamp ${req.node.lastBlockTimestamp.toString()}`, + `node_seconds_since_last_block ${req.node.secondsSinceLastBlock.toString()}`, + `node_healthcheck_status ${req.node.isHealthy ? '1' : '0'}` + ].join('\n'); + + res.setHeader('Content-Type', register.contentType); + res.end(metrics); +} + +polka() + .use(requestLogger, healthChecker) + .get('/healthcheck', (req, res) => { + if (!req.node.isHealthy) { + res.statusCode = 500; + } + res.end(JSON.stringify(req.node)); + }) + .get('/metrics', async (req, res) => { + try { + await writeMetrics(req, res); + } catch (error) { + res.statusCode = 500; + res.end(`Error: ${error}`); + } + }) + .use(responseLogger) + .listen(nodeHealthcheckPort, () => { + writeLog(`Environment configuration:`) + writeLog(` NODE_LAST_BLOCK_URI: ${nodeLastBlockUri}`); + writeLog(` NODE_HEALTHCHECK_PORT: ${nodeHealthcheckPort}`); + writeLog(` NODE_HEALTHCHECK_TOLERANCE_IN_SECONDS: ${nodeHealthcheckToleranceInSeconds}`); + writeLog(`Running - http://localhost:${nodeHealthcheckPort}`); + }); diff --git a/src/package-lock.json b/src/package-lock.json new file mode 100644 index 0000000..fbd68e6 --- /dev/null +++ b/src/package-lock.json @@ -0,0 +1,305 @@ +{ + "name": "src", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "dependencies": { + "axios": "^1.3.1", + "dotenv": "^16.0.3", + "polka": "^0.5.2", + "prom-client": "^14.2.0" + } + }, + "node_modules/@arr/every": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@arr/every/-/every-1.0.1.tgz", + "integrity": "sha512-UQFQ6SgyJ6LX42W8rHCs8KVc0JS0tzVL9ct4XYedJukskYVWTo49tNiMEK9C2HTyarbNiT/RVIRSY82vH+6sTg==", + "engines": { + "node": ">=4" + } + }, + "node_modules/@polka/url": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@polka/url/-/url-0.5.0.tgz", + "integrity": "sha512-oZLYFEAzUKyi3SKnXvj32ZCEGH6RDnao7COuCVhDydMS9NrCSVXhM79VaKyP5+Zc33m0QXEd2DN3UkU7OsHcfw==" + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, + "node_modules/axios": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.3.1.tgz", + "integrity": "sha512-78pWJsQTceInlyaeBQeYZ/QgZeWS8hGeKiIJiDKQe3hEyBb7sEMq0K4gjx+Va6WHTYO4zI/RRl8qGRzn0YMadA==", + "dependencies": { + "follow-redirects": "^1.15.0", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/bintrees": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bintrees/-/bintrees-1.0.2.tgz", + "integrity": "sha512-VOMgTMwjAaUG580SXn3LacVgjurrbMme7ZZNYGSSV7mmtY6QQRh0Eg3pwIcntQ77DErK1L0NxkbetjcoXzVwKw==" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/dotenv": { + "version": "16.0.3", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.3.tgz", + "integrity": "sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ==", + "engines": { + "node": ">=12" + } + }, + "node_modules/follow-redirects": { + "version": "1.15.2", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", + "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/matchit": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/matchit/-/matchit-1.1.0.tgz", + "integrity": "sha512-+nGYoOlfHmxe5BW5tE0EMJppXEwdSf8uBA1GTZC7Q77kbT35+VKLYJMzVNWCHSsga1ps1tPYFtFyvxvKzWVmMA==", + "dependencies": { + "@arr/every": "^1.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/polka": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/polka/-/polka-0.5.2.tgz", + "integrity": "sha512-FVg3vDmCqP80tOrs+OeNlgXYmFppTXdjD5E7I4ET1NjvtNmQrb1/mJibybKkb/d4NA7YWAr1ojxuhpL3FHqdlw==", + "dependencies": { + "@polka/url": "^0.5.0", + "trouter": "^2.0.1" + } + }, + "node_modules/prom-client": { + "version": "14.2.0", + "resolved": "https://registry.npmjs.org/prom-client/-/prom-client-14.2.0.tgz", + "integrity": "sha512-sF308EhTenb/pDRPakm+WgiN+VdM/T1RaHj1x+MvAuT8UiQP8JmOEbxVqtkbfR4LrvOg5n7ic01kRBDGXjYikA==", + "dependencies": { + "tdigest": "^0.1.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, + "node_modules/tdigest": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/tdigest/-/tdigest-0.1.2.tgz", + "integrity": "sha512-+G0LLgjjo9BZX2MfdvPfH+MKLCrxlXSYec5DaPYP1fe6Iyhf0/fSmJ0bFiZ1F8BT6cGXl2LpltQptzjXKWEkKA==", + "dependencies": { + "bintrees": "1.0.2" + } + }, + "node_modules/trouter": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/trouter/-/trouter-2.0.1.tgz", + "integrity": "sha512-kr8SKKw94OI+xTGOkfsvwZQ8mWoikZDd2n8XZHjJVZUARZT+4/VV6cacRS6CLsH9bNm+HFIPU1Zx4CnNnb4qlQ==", + "dependencies": { + "matchit": "^1.0.0" + }, + "engines": { + "node": ">=6" + } + } + }, + "dependencies": { + "@arr/every": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@arr/every/-/every-1.0.1.tgz", + "integrity": "sha512-UQFQ6SgyJ6LX42W8rHCs8KVc0JS0tzVL9ct4XYedJukskYVWTo49tNiMEK9C2HTyarbNiT/RVIRSY82vH+6sTg==" + }, + "@polka/url": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@polka/url/-/url-0.5.0.tgz", + "integrity": "sha512-oZLYFEAzUKyi3SKnXvj32ZCEGH6RDnao7COuCVhDydMS9NrCSVXhM79VaKyP5+Zc33m0QXEd2DN3UkU7OsHcfw==" + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, + "axios": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.3.1.tgz", + "integrity": "sha512-78pWJsQTceInlyaeBQeYZ/QgZeWS8hGeKiIJiDKQe3hEyBb7sEMq0K4gjx+Va6WHTYO4zI/RRl8qGRzn0YMadA==", + "requires": { + "follow-redirects": "^1.15.0", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "bintrees": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bintrees/-/bintrees-1.0.2.tgz", + "integrity": "sha512-VOMgTMwjAaUG580SXn3LacVgjurrbMme7ZZNYGSSV7mmtY6QQRh0Eg3pwIcntQ77DErK1L0NxkbetjcoXzVwKw==" + }, + "combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "requires": { + "delayed-stream": "~1.0.0" + } + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==" + }, + "dotenv": { + "version": "16.0.3", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.3.tgz", + "integrity": "sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ==" + }, + "follow-redirects": { + "version": "1.15.2", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", + "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==" + }, + "form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + } + }, + "matchit": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/matchit/-/matchit-1.1.0.tgz", + "integrity": "sha512-+nGYoOlfHmxe5BW5tE0EMJppXEwdSf8uBA1GTZC7Q77kbT35+VKLYJMzVNWCHSsga1ps1tPYFtFyvxvKzWVmMA==", + "requires": { + "@arr/every": "^1.0.0" + } + }, + "mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==" + }, + "mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "requires": { + "mime-db": "1.52.0" + } + }, + "polka": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/polka/-/polka-0.5.2.tgz", + "integrity": "sha512-FVg3vDmCqP80tOrs+OeNlgXYmFppTXdjD5E7I4ET1NjvtNmQrb1/mJibybKkb/d4NA7YWAr1ojxuhpL3FHqdlw==", + "requires": { + "@polka/url": "^0.5.0", + "trouter": "^2.0.1" + } + }, + "prom-client": { + "version": "14.2.0", + "resolved": "https://registry.npmjs.org/prom-client/-/prom-client-14.2.0.tgz", + "integrity": "sha512-sF308EhTenb/pDRPakm+WgiN+VdM/T1RaHj1x+MvAuT8UiQP8JmOEbxVqtkbfR4LrvOg5n7ic01kRBDGXjYikA==", + "requires": { + "tdigest": "^0.1.1" + } + }, + "proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, + "tdigest": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/tdigest/-/tdigest-0.1.2.tgz", + "integrity": "sha512-+G0LLgjjo9BZX2MfdvPfH+MKLCrxlXSYec5DaPYP1fe6Iyhf0/fSmJ0bFiZ1F8BT6cGXl2LpltQptzjXKWEkKA==", + "requires": { + "bintrees": "1.0.2" + } + }, + "trouter": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/trouter/-/trouter-2.0.1.tgz", + "integrity": "sha512-kr8SKKw94OI+xTGOkfsvwZQ8mWoikZDd2n8XZHjJVZUARZT+4/VV6cacRS6CLsH9bNm+HFIPU1Zx4CnNnb4qlQ==", + "requires": { + "matchit": "^1.0.0" + } + } + } +} diff --git a/src/package.json b/src/package.json new file mode 100644 index 0000000..d9eb573 --- /dev/null +++ b/src/package.json @@ -0,0 +1,11 @@ +{ + "dependencies": { + "axios": "^1.3.1", + "dotenv": "^16.0.3", + "polka": "^0.5.2", + "prom-client": "^14.2.0" + }, + "scripts": { + "start": "node index.js" + } +} \ No newline at end of file