Skip to content

libhive: client build args and alternative dockerfiles #767

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 32 commits into from
Jun 2, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
cf2ebac
libhive,libdocker: allow json/yaml client build config
marioevz Apr 27, 2023
9ea85e3
clients/go-ethereum: multiple dockerfiles
marioevz Apr 27, 2023
0d2a5fb
libdocker: log printing
marioevz Apr 27, 2023
a36aede
main: typo
marioevz Apr 27, 2023
7c5fca5
libhive: refactor to use `build_args`
marioevz May 15, 2023
90d07bb
libhive: improve test
marioevz May 16, 2023
9e42607
internal/libhive: rename DockerFile -> Dockerfile
fjl May 30, 2023
feec09c
internal/libhive: remove JSON parsing of client build args
fjl May 30, 2023
d2e610e
internal/libhive: update documentation
fjl May 30, 2023
8ed90bd
internal/libhive: remove named slice type ClientsBuildInfo
fjl May 30, 2023
8c75032
internal/libhive: make client names deterministic
fjl May 30, 2023
218292b
internal/libhive: rename ClientBuildInfo to ClientDesignator
fjl May 30, 2023
e95fc58
hive: add -client-file flag for YAML file
fjl May 30, 2023
b386a8a
internal/libhive: fix client dockerfile
fjl May 30, 2023
3741d94
clients/go-ethereum: remove makecache invocations in new Dockerfiles
fjl May 30, 2023
7321a65
clients/go-ethereum: remove local build instructions
fjl May 30, 2023
6c4b416
internal/libdocker: rename BuildEnv -> BuildArgs to match docker terms
fjl May 30, 2023
6df0d71
internal/libhive: rename tests
fjl May 30, 2023
029c158
docs: update
fjl May 30, 2023
4c0e768
docs: update
fjl May 30, 2023
27f89ae
internal/libhive: rename buildInfoNames
fjl May 30, 2023
2dd76c3
internal/libdocker: remove varargs
fjl May 30, 2023
7e8439c
hive: fix flag check for older Go
fjl May 31, 2023
6ebdc61
internal/libhive: enable KnownFields on YAML decoder
fjl May 31, 2023
e1ca627
internal/libhive: warn for unknown build args
fjl May 31, 2023
838fd36
internal/libhive: validate more client info at parse time
fjl Jun 1, 2023
2c2bd49
internal/libhive: use maps.Keys
fjl Jun 1, 2023
4c80749
internal/libhive: validate CLI client list
fjl Jun 1, 2023
78e110d
internal/libhive: client name assignment
fjl Jun 1, 2023
632b85f
internal/libhive: rename build args
fjl Jun 2, 2023
560536d
clients/go-ethereum: update build arg names
fjl Jun 2, 2023
1e565f6
docs: update for new build arg names
fjl Jun 2, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 3 additions & 43 deletions clients/go-ethereum/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,47 +1,7 @@
## By default, the geth is pulled from Docker Hub.
ARG baseimage=ethereum/client-go
ARG tag=latest

ARG branch=latest
FROM ethereum/client-go:$branch as builder

## ----
## Uncomment the steps below (and comment out the steps above!) to build go-ethereum
## from local sources in the ./go-ethereum directory.

# FROM golang:1-alpine as builder
# ARG branch=master
# ADD go-ethereum /go-ethereum
# WORKDIR /go-ethereum
# RUN apk add --update bash curl jq git make
# RUN make geth
# RUN mv ./build/bin/geth /usr/local/bin/geth

## ----

## ----
## Uncomment the steps below (and comment out the steps above!) to build
## go-ethereum from a git branch.

# FROM alpine:latest as builder
# ARG user=ethereum
# ARG repo=go-ethereum
# ARG branch=master
# RUN \
# apk add --update bash curl jq go git make gcc musl-dev \
# ca-certificates linux-headers && \
# git clone --depth 1 --branch $branch \
# https://github.com/$user/$repo && \
# (cd go-ethereum && make geth) && \
# (cd go-ethereum && \
# echo "{}" \
# | jq ".+ {\"repo\":\"$(git config --get remote.origin.url)\"}" \
# | jq ".+ {\"branch\":\"$(git rev-parse --abbrev-ref HEAD)\"}" \
# | jq ".+ {\"commit\":\"$(git rev-parse HEAD)\"}" \
# > /version.json) && \
# cp go-ethereum/build/bin/geth /usr/local/bin/geth && \
# apk del go git make gcc musl-dev linux-headers && \
# rm -rf /go-ethereum && rm -rf /var/cache/apk/*

## ----
FROM $baseimage:$tag as builder

FROM alpine:latest
RUN apk add --update bash curl jq
Expand Down
40 changes: 40 additions & 0 deletions clients/go-ethereum/Dockerfile.git
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
## Pulls geth from a git repository and builds it from source.

FROM alpine:latest as builder
ARG github=ethereum/go-ethereum
ARG tag=master

RUN \
apk add --update bash curl jq go git make gcc musl-dev \
ca-certificates linux-headers && \
git clone --depth 1 --branch $tag https://github.com/$github && \
cd go-ethereum && \
make geth && \
cp go-ethereum/build/bin/geth /usr/local/bin/geth && \
apk del go git make gcc musl-dev linux-headers && \
rm -rf /go-ethereum && rm -rf /var/cache/apk/*

FROM alpine:latest
RUN apk add --update bash curl jq
COPY --from=builder /usr/local/bin/geth /usr/local/bin/geth

# Generate the version.txt file.
RUN /usr/local/bin/geth console --exec 'console.log(admin.nodeInfo.name)' --maxpeers=0 --nodiscover --dev 2>/dev/null | head -1 > /version.txt

# Inject the startup script.
ADD geth.sh /geth.sh
ADD mapper.jq /mapper.jq
RUN chmod +x /geth.sh

# Inject the enode id retriever script.
RUN mkdir /hive-bin
ADD enode.sh /hive-bin/enode.sh
RUN chmod +x /hive-bin/enode.sh

# Add a default genesis file.
ADD genesis.json /genesis.json

# Export the usual networking ports to allow outside access to the node
EXPOSE 8545 8546 8547 8551 30303 30303/udp

ENTRYPOINT ["/geth.sh"]
33 changes: 33 additions & 0 deletions clients/go-ethereum/Dockerfile.local
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
## Build geth from source from a local directory called go-ethereum.

FROM golang:1-alpine as builder
ADD go-ethereum /go-ethereum
WORKDIR /go-ethereum
RUN apk add --update bash curl jq git make
RUN make geth
RUN mv ./build/bin/geth /usr/local/bin/geth

FROM alpine:latest
RUN apk add --update bash curl jq
COPY --from=builder /usr/local/bin/geth /usr/local/bin/geth

# Generate the version.txt file.
RUN /usr/local/bin/geth console --exec 'console.log(admin.nodeInfo.name)' --maxpeers=0 --nodiscover --dev 2>/dev/null | head -1 > /version.txt

# Inject the startup script.
ADD geth.sh /geth.sh
ADD mapper.jq /mapper.jq
RUN chmod +x /geth.sh

# Inject the enode id retriever script.
RUN mkdir /hive-bin
ADD enode.sh /hive-bin/enode.sh
RUN chmod +x /hive-bin/enode.sh

# Add a default genesis file.
ADD genesis.json /genesis.json

# Export the usual networking ports to allow outside access to the node
EXPOSE 8545 8546 8547 8551 30303 30303/udp

ENTRYPOINT ["/geth.sh"]
22 changes: 16 additions & 6 deletions docs/clients.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,29 @@ Clients are docker images which can be instantiated by a simulation. A client de
consists of a Dockerfile and associated resources. Client definitions live in
subdirectories of `clients/` in the hive repository.

See the [go-ethereum client definition][geth-docker] for an example of a client
Dockerfile.

When hive runs a simulation, it first builds all client docker images using their
Dockerfile, i.e. it basically runs `docker build .` in the client directory. Since most
client definitions wrap an existing Ethereum client, and building the client from source
may take a long time, it is usually best to base the hive client wrapper on a pre-built
docker image from Docker Hub.

Client Dockerfiles should support an optional argument named `branch`, which specifies the
requested client version. This argument can be set by users by appending it to the client
name like:
The client Dockerfile should support an optional argument named `branch`, which specifies
the requested client version. This argument can be set by users by appending it to the
client name like:

./hive --sim my-simulation --client go-ethereum_v1.9.23,go_ethereum_v1.9.22

See the [go-ethereum client definition][geth-docker] for an example of a client
Dockerfile.
Other build arguments can also be set using a YAML file, see the [hive command
documentation][hive-client-yaml] for more information.

### Alternative Dockerfiles

There can be other Dockerfiles besides the main one. Typically, a client should also
provide a `Dockerfile.git` that builds the client from source code. Alternative
Dockerfiles can be selected through hive's `-client-file` YAML configuration.

### hive.yaml

Expand All @@ -35,7 +44,7 @@ list:

The role list is available to simulators and can be used to differentiate between clients
based on features. Declaring a client role also signals that the client supports certain
role-specific environment variables and files. If `hive.yml` is missing or doesn't declare
role-specific environment variables and files. If `hive.yaml` is missing or doesn't declare
roles, the `eth1` role is assumed.

### /version.txt
Expand Down Expand Up @@ -155,6 +164,7 @@ For the server role, the following additional variables should be supported:

[LES]: https://github.com/ethereum/devp2p/blob/master/caps/les.md
[geth-docker]: ../clients/go-ethereum/Dockerfile
[hive-client-yaml]: ./commandline.md#client-build-parameters
[oe-genesis-jq]: ../clients/openethereum/mapper.jq
[EIP-155]: https://eips.ethereum.org/EIPS/eip-155
[EIP-606]: https://eips.ethereum.org/EIPS/eip-606
Expand Down
68 changes: 52 additions & 16 deletions docs/commandline.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,42 @@ version by appending it to the client name with `_`, for example:

./hive --sim devp2p --client go-ethereum_v1.9.22,go-ethereum_v1.9.23

Simulation runs can be customized in many ways. Here's an overview of the available
command-line options.
### Client Build Parameters

`--client.checktimelimit <timeout>`: The timeout of waiting for clients to open up TCP
port 8545. If a very long chain is imported, this timeout may need to be quite long. A
lower value means that hive won't wait as long in case the node crashes and never opens
the RPC port. Defaults to 3 minutes.
The client list for a run can also be given in a YAML file. This also allows further
customization of the build arguments of the client. Specify the `--client-file` option to
use a client list file.

./hive --sim my-simulation --client-file clients.yaml

Here is an example clients.yaml file:

- client: go-ethereum
dockerfile: git
- client: nethermind
build_args:
baseimage: nethermindeth/hive
tag: latest

For each client in the list, the following options can be given:

- `client`: Name of the client. This must refer to a known client in the clients/ directory.
- `dockerfile`: The Dockerfile extension to use. For example, specifying `git` here will
build the client using `Dockerfile.git` instead of the default `Dockerfile`.
- `nametag`: this can be used to assign a more descriptive name to the client. If unset,
a unique nametag will be chosen based on the version tag and/or build arguments.
- `build_args`: Build arguments passed to the Dockerfile, see below.

Supported build arguments depend on the client and the docker image being used. Common build
arguments are:

- `tag`: The git commit/tag/branch or docker tag name to use.
- `baseimage`: For clients pulled from DockerHub, this can be used to override the organization
and image name. Example `ethereum/client-go`.
- `github`: For client Dockerfiles building from git, this setting can be used to change
the source code repository (fork) on GitHub. Example: `ethereum/go-ethereum`.

### Docker Options

`--docker.pull`: Setting this option makes hive re-pull the base images of all built
docker containers.
Expand All @@ -56,16 +85,7 @@ docker containers.
rebuild. You can use this option during simulator development to ensure a new image is
built even when there are no changes to the simulator code.

`--sim.timelimit <timeout>`: Simulation timeout. Hive aborts the simulator if it exceeds
this time. There is no default timeout.

`--sim.loglevel <level>`: Selects log level of client instances. Supports values 0-5,
defaults to 3. Note that this value may be overridden by simulators for specific clients.
This sets the default value of `HIVE_LOGLEVEL` in client containers.

`--sim.parallelism <number>`: Sets max number of parallel clients/containers. This is
interpreted by simulators. It sets the `HIVE_PARALLELISM` environment variable. Defaults
to 1.
### Simulation Options

`--sim.limit <pattern>`: Specifies a regular expression to selectively enable suites and
test cases. This is interpreted by simulators. It sets the `HIVE_TEST_PATTERN` environment
Expand All @@ -86,6 +106,22 @@ directory (note the first `/`, matching any suite name):

./hive --sim ethereum/consensus --sim.limit /stBugs/

`--sim.timelimit <timeout>`: Simulation timeout. Hive aborts the simulator if it exceeds
this time. There is no default timeout.

`--client.checktimelimit <timeout>`: The timeout of waiting for clients to open up TCP
port 8545. If a very long chain is imported, this timeout may need to be quite long. A
lower value means that hive won't wait as long in case the node crashes and never opens
the RPC port. Defaults to 3 minutes.

`--sim.loglevel <level>`: Selects log level of client instances. Supports values 0-5,
defaults to 3. Note that this value may be overridden by simulators for specific clients.
This sets the default value of `HIVE_LOGLEVEL` in client containers.

`--sim.parallelism <number>`: Sets max number of parallel clients/containers. This is
interpreted by simulators. It sets the `HIVE_PARALLELISM` environment variable. Defaults
to 1.

## Viewing simulation results (hiveview)

The results of hive simulation runs are stored in JSON files containing test results, and
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ require (
github.com/yusufpapurcu/wmi v1.2.2 // indirect
go.opencensus.io v0.23.0 // indirect
golang.org/x/crypto v0.4.0 // indirect
golang.org/x/exp v0.0.0-20230206171751-46f607a40771 // indirect
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 // indirect
golang.org/x/sys v0.5.0 // indirect
golang.org/x/text v0.7.0 // indirect
google.golang.org/protobuf v1.28.1 // indirect
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -910,6 +910,8 @@ golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EH
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
golang.org/x/exp v0.0.0-20230206171751-46f607a40771 h1:xP7rWLUr1e1n2xkK5YB4LI0hPEy3LJC6Wk+D4pGlOJg=
golang.org/x/exp v0.0.0-20230206171751-46f607a40771/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 h1:k/i9J1pBpvlfR+9QsetwPyERsqu1GIbi967PQMq3Ivc=
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
Expand Down
60 changes: 53 additions & 7 deletions hive.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,14 @@ func main() {
simDevModeAPIEndpoint = flag.String("dev.addr", "127.0.0.1:3000", "Endpoint that the simulator API listens on")
useCredHelper = flag.Bool("docker.cred-helper", false, "configure docker authentication using locally-configured credential helper")

clientsFile = flag.String("client-file", "", `YAML `+"`file`"+` containing client configurations.`)

clients = flag.String("client", "go-ethereum", "Comma separated `list` of clients to use. Client names in the list may be given as\n"+
"just the client name, or a client_branch specifier. If a branch name is supplied,\n"+
"the client image will use the given git branch or docker tag. Multiple instances of\n"+
"a single client type may be requested with different branches.\n"+
"Example: \"besu_latest,besu_20.10.2\"")
"Example: \"besu_latest,besu_20.10.2\"\n")

clientTimeout = flag.Duration("client.checktimelimit", 3*time.Minute, "The `timeout` of waiting for clients to open up the RPC port.\n"+
"If a very long chain is imported, this timeout may need to be quite large.\n"+
"A lower value means that hive won't wait as long in case the node crashes and\n"+
Expand Down Expand Up @@ -111,8 +114,24 @@ func main() {
ClientStartTimeout: *clientTimeout,
}
runner := libhive.NewRunner(inv, builder, cb)
clientList := splitAndTrim(*clients, ",")

// Parse the client list.
// It can be supplied as a comma-separated list, or as a YAML file.
checkFlagsExclusive("client", "client-file")
var clientList []libhive.ClientDesignator
if *clientsFile != "" {
clientList, err = parseClientsFile(&inv, *clientsFile)
if err != nil {
fatal("-client-file:", err)
}
} else {
clientList, err = libhive.ParseClientList(&inv, *clients)
if err != nil {
fatal("-client:", err)
}
}

// Build clients and simulators.
if err := runner.Build(ctx, clientList, simList); err != nil {
fatal(err)
}
Expand All @@ -122,6 +141,7 @@ func main() {
return
}

// Run simulators.
var failCount int
for _, sim := range simList {
result, err := runner.Run(ctx, sim, env)
Expand All @@ -146,10 +166,36 @@ func fatal(args ...interface{}) {
os.Exit(1)
}

func splitAndTrim(input, sep string) []string {
list := strings.Split(input, sep)
for i := range list {
list[i] = strings.TrimSpace(list[i])
func parseClientsFile(inv *libhive.Inventory, file string) ([]libhive.ClientDesignator, error) {
f, err := os.Open(file)
if err != nil {
return nil, err
}
defer f.Close()
return libhive.ParseClientListYAML(inv, f)
}

func checkFlagsExclusive(flagNames ...string) {
set := make(map[string]bool)
flag.Visit(func(f *flag.Flag) {
set[f.Name] = true
})
var found bool
for _, name := range flagNames {
if set[name] {
if found {
fatal("flags", flagListString(flagNames), "cannot be used together")
}
found = true
set[name] = false
}
}
}

func flagListString(names []string) string {
flags := make([]string, len(names))
for i := range flags {
flags[i] = "-" + names[i]
}
return list
return strings.Join(flags, ", ")
}
Loading