-
Notifications
You must be signed in to change notification settings - Fork 375
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
chore(misc/loop): Setup the portal loop infra (#1400)
Portal loop infrastructure code. Co-authored-by: Morgan <git@howl.moe> Co-authored-by: Morgan <morgan@morganbaz.com> Co-authored-by: deelawn <dboltz03@gmail.com> Co-authored-by: Guilhem Fanton <8671905+gfanton@users.noreply.github.com> Co-authored-by: Leon Hudak <33522493+leohhhn@users.noreply.github.com> Co-authored-by: Miloš Živković <milos.zivkovic@tendermint.com> Co-authored-by: Lee ByeongJun <lbj199874@gmail.com> Co-authored-by: Kazaï <149690535+kazai777@users.noreply.github.com> Co-authored-by: Manfred Touron <94029+moul@users.noreply.github.com>
- Loading branch information
1 parent
173d5a2
commit b93032a
Showing
22 changed files
with
1,476 additions
and
94 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
name: portal-loop | ||
|
||
on: | ||
push: | ||
paths: | ||
- misc/loop | ||
- .github/workflows/portal-loop.yml | ||
branches: | ||
- "master" | ||
- "ops/portal-loop" | ||
tags: | ||
- "v*" | ||
|
||
permissions: | ||
contents: read | ||
packages: write | ||
|
||
jobs: | ||
portal-loop: | ||
runs-on: ubuntu-latest | ||
steps: | ||
- name: Checkout | ||
uses: actions/checkout@v3 | ||
|
||
- name: Login to GitHub Container Registry | ||
uses: docker/login-action@v2 | ||
with: | ||
registry: ghcr.io | ||
username: ${{ github.repository_owner }} | ||
password: ${{ secrets.GITHUB_TOKEN }} | ||
|
||
- name: Docker metadata portalloopd | ||
id: meta | ||
uses: docker/metadata-action@v5 | ||
with: | ||
images: ghcr.io/${{ github.repository }}/portalloopd | ||
tags: | | ||
type=raw,value=latest | ||
type=semver,pattern=v{{version}} | ||
- name: Build and push | ||
uses: docker/build-push-action@v4 | ||
with: | ||
context: ./misc/loop | ||
target: portalloopd | ||
push: ${{ github.event_name != 'pull_request' }} | ||
tags: ${{ steps.meta.outputs.tags }} | ||
labels: ${{ steps.meta.outputs.labels }} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
/portalloopd | ||
/backups | ||
/traefik/letsencrypt |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
FROM golang:alpine AS builder | ||
|
||
COPY . /go/src/github.com/gnolang/gno/misc/loop | ||
|
||
WORKDIR /go/src/github.com/gnolang/gno/misc/loop | ||
|
||
RUN --mount=type=cache,target=/root/.cache/go-build \ | ||
--mount=type=cache,target=/root/go/pkg/mod \ | ||
go build -o /build/portalloopd ./cmd | ||
|
||
# Final image for portalloopd | ||
FROM docker AS portalloopd | ||
|
||
RUN apk add bash curl jq | ||
|
||
COPY --from=builder /build/portalloopd /usr/bin/portalloopd | ||
|
||
ENTRYPOINT [ "/usr/bin/portalloopd" ] | ||
CMD [ "serve" ] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,44 +1,22 @@ | ||
# The startup delay (waits until the node is "ready") | ||
DELAY ?= 10 # seconds | ||
# The temporary backup file for transactions | ||
BACKUP_FILE ?= $(abspath ./txs_backup.log) | ||
# The entire txs history across all iterations | ||
HISTORY_OUTPUT := $(abspath ./txs_history.log) | ||
all: docker.start | ||
|
||
# The gnoland binary | ||
gnoland_bin := go run github.com/gnolang/gno/gno.land/cmd/gnoland | ||
# The tx archive binary | ||
tx_bin := go run github.com/gnolang/tx-archive/cmd | ||
docker.start: # Start the portal loop | ||
docker compose up -d | ||
|
||
# The relative gno.land directory | ||
gnoland_dir := $(abspath ../../gno.land) | ||
docker.stop: # Stop the portal loop | ||
docker compose down | ||
docker rm -f $(docker ps -aq --filter "label=the-portal-loop") | ||
|
||
all: loop | ||
docker.build: # (re)Build snapshotter image | ||
docker compose build | ||
|
||
start.gnoland: | ||
cd $(gnoland_dir) && $(gnoland_bin) start -skip-failing-genesis-txs -genesis-txs-file $(HISTORY_OUTPUT) | ||
clean.gnoland: | ||
make -C $(gnoland_dir) fclean | ||
.PHONY: start.gnoland clean.gnoland | ||
docker.pull: # Pull new images to update versions | ||
docker compose pull | ||
|
||
# Starts the backup service | ||
# and backs up transactions into a file | ||
# that is wiped on every loop | ||
tx.backup: | ||
sleep $(DELAY) | ||
$(tx_bin) backup -legacy -watch -overwrite -output-path "$(BACKUP_FILE)" | ||
.PHONY: tx.backup | ||
portalloopd.bash: # Get a bash command inside of the portalloopd container | ||
docker compose exec portalloopd bash | ||
|
||
# Saves the history from previous iterations into | ||
# a temporary transactions log | ||
save.history: | ||
@test -e $(BACKUP_FILE) || (echo "No existing backup file not found: '$(BACKUP_FILE)'"; exit 1) | ||
cat $(BACKUP_FILE) >> $(HISTORY_OUTPUT) | ||
.PHONY: save.history | ||
switch: portalloopd.switch | ||
|
||
loop: clean.gnoland | ||
# backup history, if needed | ||
$(MAKE) save.history || true | ||
# run our dev loop | ||
./run_loop.sh | ||
.PHONY: loop | ||
portalloopd.switch: # Force switch the portal loop with latest image | ||
docker compose exec portalloopd switch |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
# The portal loop :infinity: | ||
|
||
## What is it? | ||
|
||
It's a Gnoland node that aim to run with always the latest version of gno and never loose transactions history. | ||
|
||
For more information, see issue on github [gnolang/gno#1239](https://github.com/gnolang/gno/issues/1239) | ||
|
||
|
||
## How to use | ||
|
||
Start the loop with: | ||
|
||
```sh | ||
$ docker compose up -d | ||
|
||
# or using the Makefile | ||
|
||
$ make | ||
``` | ||
|
||
The [`portalloopd`](./cmd/portalloopd) binary is starting inside of the docker container `portalloopd` | ||
|
||
This script is doing: | ||
|
||
- Setup the current portal-loop in read only mode | ||
- Pull the latest version of [ghcr.io/gnolang/gno]() | ||
- Backup the txs using [gnolang/tx-archive](https://github.com/gnolang/tx-archive) | ||
- Start a new docker container with the backups files | ||
- Changing the proxy (traefik) to redirect to the new portal loop | ||
- Unlock read only mode | ||
- Stop the previous loop | ||
|
||
### Makefile helper | ||
|
||
You can find a [Makefile](./Makefile) to help you interact with the portal loop | ||
|
||
- Force switch of the portal loop with a new version | ||
|
||
```bash | ||
make portalloopd.switch | ||
``` |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
package main | ||
|
||
import ( | ||
"context" | ||
"flag" | ||
"os" | ||
|
||
"github.com/docker/docker/client" | ||
"github.com/gnolang/gno/tm2/pkg/commands" | ||
) | ||
|
||
type backupCfg struct { | ||
rpcAddr string | ||
traefikGnoFile string | ||
backupDir string | ||
hostPWD string | ||
} | ||
|
||
func (c *backupCfg) RegisterFlags(fs *flag.FlagSet) { | ||
if os.Getenv("HOST_PWD") == "" { | ||
os.Setenv("HOST_PWD", os.Getenv("PWD")) | ||
} | ||
|
||
if os.Getenv("BACKUP_DIR") == "" { | ||
os.Setenv("BACKUP_DIR", "./backups") | ||
} | ||
|
||
if os.Getenv("RPC_URL") == "" { | ||
os.Setenv("RPC_URL", "http://rpc.portal.gno.local:26657") | ||
} | ||
|
||
if os.Getenv("PROM_ADDR") == "" { | ||
os.Setenv("PROM_ADDR", ":9090") | ||
} | ||
|
||
if os.Getenv("TRAEFIK_GNO_FILE") == "" { | ||
os.Setenv("TRAEFIK_GNO_FILE", "./traefik/gno.yml") | ||
} | ||
|
||
fs.StringVar(&c.rpcAddr, "rpc", os.Getenv("RPC_URL"), "tendermint rpc url") | ||
fs.StringVar(&c.traefikGnoFile, "traefik-gno-file", os.Getenv("TRAEFIK_GNO_FILE"), "traefik gno file") | ||
fs.StringVar(&c.backupDir, "backup-dir", os.Getenv("BACKUP_DIR"), "backup directory") | ||
fs.StringVar(&c.hostPWD, "pwd", os.Getenv("HOST_PWD"), "host pwd (for docker usage)") | ||
} | ||
|
||
func newBackupCmd(io commands.IO) *commands.Command { | ||
cfg := &backupCfg{} | ||
|
||
return commands.NewCommand( | ||
commands.Metadata{ | ||
Name: "backup", | ||
ShortUsage: "backup [flags]", | ||
}, | ||
cfg, | ||
func(ctx context.Context, _ []string) error { | ||
return execBackup(ctx, cfg) | ||
}, | ||
) | ||
} | ||
|
||
func execBackup(ctx context.Context, cfg *backupCfg) error { | ||
dockerClient, err := client.NewEnvClient() | ||
if err != nil { | ||
return err | ||
} | ||
|
||
portalLoop := &snapshotter{} | ||
|
||
portalLoop, err = NewSnapshotter(dockerClient, config{ | ||
backupDir: cfg.backupDir, | ||
rpcAddr: cfg.rpcAddr, | ||
hostPWD: cfg.hostPWD, | ||
traefikGnoFile: cfg.traefikGnoFile, | ||
}) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
err = StartPortalLoop(ctx, portalLoop, false) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
return portalLoop.backupTXs(ctx, portalLoop.url) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,116 @@ | ||
package main | ||
|
||
import ( | ||
"context" | ||
"flag" | ||
"net/http" | ||
"os" | ||
"time" | ||
|
||
"github.com/docker/docker/client" | ||
"github.com/gnolang/gno/tm2/pkg/commands" | ||
"github.com/prometheus/client_golang/prometheus/promhttp" | ||
"github.com/sirupsen/logrus" | ||
) | ||
|
||
type serveCfg struct { | ||
rpcAddr string | ||
traefikGnoFile string | ||
backupDir string | ||
hostPWD string | ||
} | ||
|
||
type serveService struct { | ||
cfg serveCfg | ||
|
||
// TODO(albttx): put getter on it with RMutex | ||
portalLoop *snapshotter | ||
|
||
portalLoopURL string | ||
} | ||
|
||
func (c *serveCfg) RegisterFlags(fs *flag.FlagSet) { | ||
if os.Getenv("HOST_PWD") == "" { | ||
os.Setenv("HOST_PWD", os.Getenv("PWD")) | ||
} | ||
|
||
if os.Getenv("BACKUP_DIR") == "" { | ||
os.Setenv("BACKUP_DIR", "./backups") | ||
} | ||
|
||
if os.Getenv("RPC_URL") == "" { | ||
os.Setenv("RPC_URL", "http://rpc.portal.gno.local:26657") | ||
} | ||
|
||
if os.Getenv("PROM_ADDR") == "" { | ||
os.Setenv("PROM_ADDR", ":9090") | ||
} | ||
|
||
if os.Getenv("TRAEFIK_GNO_FILE") == "" { | ||
os.Setenv("TRAEFIK_GNO_FILE", "./traefik/gno.yml") | ||
} | ||
|
||
fs.StringVar(&c.rpcAddr, "rpc", os.Getenv("RPC_URL"), "tendermint rpc url") | ||
fs.StringVar(&c.traefikGnoFile, "traefik-gno-file", os.Getenv("TRAEFIK_GNO_FILE"), "traefik gno file") | ||
fs.StringVar(&c.backupDir, "backup-dir", os.Getenv("BACKUP_DIR"), "backup directory") | ||
fs.StringVar(&c.hostPWD, "pwd", os.Getenv("HOST_PWD"), "host pwd (for docker usage)") | ||
} | ||
|
||
func newServeCmd(io commands.IO) *commands.Command { | ||
cfg := &serveCfg{} | ||
|
||
return commands.NewCommand( | ||
commands.Metadata{ | ||
Name: "serve", | ||
ShortUsage: "serve [flags]", | ||
}, | ||
cfg, | ||
func(ctx context.Context, args []string) error { | ||
return execServe(ctx, cfg, args) | ||
}, | ||
) | ||
} | ||
|
||
func execServe(ctx context.Context, cfg *serveCfg, args []string) error { | ||
dockerClient, err := client.NewEnvClient() | ||
if err != nil { | ||
return err | ||
} | ||
|
||
portalLoop := &snapshotter{} | ||
|
||
// Serve monitoring | ||
go func() { | ||
s := &monitoringService{ | ||
portalLoop: portalLoop, | ||
} | ||
|
||
for portalLoop.url == "" { | ||
time.Sleep(time.Second * 1) | ||
} | ||
|
||
go s.recordMetrics() | ||
|
||
http.Handle("/metrics", promhttp.Handler()) | ||
http.ListenAndServe(os.Getenv("PROM_ADDR"), nil) | ||
}() | ||
|
||
// the loop | ||
for { | ||
portalLoop, err = NewSnapshotter(dockerClient, config{ | ||
backupDir: cfg.backupDir, | ||
rpcAddr: cfg.rpcAddr, | ||
hostPWD: cfg.hostPWD, | ||
traefikGnoFile: cfg.traefikGnoFile, | ||
}) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
err = StartPortalLoop(ctx, portalLoop, false) | ||
if err != nil { | ||
logrus.WithError(err).Error() | ||
} | ||
time.Sleep(time.Second * 120) | ||
} | ||
} |
Oops, something went wrong.