Skip to content

Commit e34941d

Browse files
authored
JD, custom n:m ports mapping syntax (#1430)
add jd and n:m port mapping
1 parent 9e40f27 commit e34941d

File tree

20 files changed

+1022
-299
lines changed

20 files changed

+1022
-299
lines changed

.github/workflows/framework-golden-tests.yml

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,6 @@ jobs:
1010
env:
1111
LOKI_TENANT_ID: promtail
1212
LOKI_URL: http://localhost:3030/loki/api/v1/push
13-
# this is not the best practice, and it must be fixed, run your tests WITHOUT IT!
14-
# however, on current latest image we must use this flag
15-
CTF_IGNORE_CRITICAL_LOGS: true
1613
runs-on: ubuntu-latest
1714
permissions:
1815
id-token: write

.github/workflows/framework.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ jobs:
99
defaults:
1010
run:
1111
working-directory: framework
12+
env:
13+
CTF_JD_IMAGE: ${{ secrets.CTF_JD_IMAGE }}
1214
runs-on: ubuntu-latest
1315
permissions:
1416
id-token: write

book/src/framework/components/state.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ One node set is enough for any kind of testing, if you need more nodes consider
4040

4141
## Custom ports
4242

43-
You can also define a custom set of ports for any node
43+
You can also define a custom set of ports for any node.
4444
```toml
4545
[nodeset]
4646
nodes = 5
@@ -53,6 +53,7 @@ You can also define a custom set of ports for any node
5353

5454
[nodeset.node_specs.node]
5555
# here we defined 2 new ports to listen and mapped them to our host machine
56-
custom_ports = [14000, 14001]
56+
# syntax is "host:docker", if you provide only host port then we map 1-to-1
57+
custom_ports = ["14000:15000", "20000"]
5758
image = "public.ecr.aws/chainlink/chainlink:v2.16.0"
5859
```

framework/.changeset/v0.3.2.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
- Allow exposing CL node ports in host:docker format
2+
- Expose default test private keys as framework constants
3+
- Rename CHAINLINK_IMAGE to CTF_CHAINLINK_IMAGE to avoid CI collisions
4+
- Add CTF_JD_IMAGE env var
5+
- Add JobDistributor component

framework/Makefile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,5 +29,5 @@ tidy:
2929
go_mod:
3030
go mod download
3131

32-
test_unit: go_mod
33-
go test -timeout 5m -count 1 -run TestDocker
32+
test_component: go_mod
33+
go test -timeout 5m -count 1 -run TestComponentDocker ./...

framework/components/clnode/clnode.go

Lines changed: 57 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"fmt"
88
"os"
99
"path/filepath"
10+
"strings"
1011
"sync"
1112
"text/template"
1213
"time"
@@ -20,9 +21,10 @@ import (
2021
)
2122

2223
const (
23-
DefaultHTTPPort = "6688"
24-
DefaultP2PPort = "6690"
25-
TmpImageName = "chainlink-tmp:latest"
24+
DefaultHTTPPort = "6688"
25+
DefaultP2PPort = "6690"
26+
TmpImageName = "chainlink-tmp:latest"
27+
CustomPortSeparator = ":"
2628
)
2729

2830
var (
@@ -51,7 +53,7 @@ type NodeInput struct {
5153
UserSecretsOverrides string `toml:"user_secrets_overrides"`
5254
HTTPPort int `toml:"port"`
5355
P2PPort int `toml:"p2p_port"`
54-
CustomPorts []int `toml:"custom_ports"`
56+
CustomPorts []string `toml:"custom_ports"`
5557
}
5658

5759
// Output represents Chainlink node output, nodes and databases connection URLs
@@ -111,6 +113,54 @@ func NewNode(in *Input, pgOut *postgres.Output) (*Output, error) {
111113
return out, nil
112114
}
113115

116+
// generatePortBindings generates exposed ports and port bindings
117+
// exposes default CL node port
118+
// exposes custom_ports in format "host:docker" or map 1-to-1 if only "host" port is provided
119+
func generatePortBindings(in *Input) ([]string, nat.PortMap, error) {
120+
httpPort := fmt.Sprintf("%s/tcp", DefaultHTTPPort)
121+
portBindings := nat.PortMap{
122+
nat.Port(httpPort): []nat.PortBinding{
123+
{
124+
HostIP: "0.0.0.0",
125+
HostPort: fmt.Sprintf("%d/tcp", in.Node.HTTPPort),
126+
},
127+
},
128+
}
129+
customPorts := make([]string, 0)
130+
for _, p := range in.Node.CustomPorts {
131+
if strings.Contains(p, CustomPortSeparator) {
132+
pp := strings.Split(p, CustomPortSeparator)
133+
if len(pp) != 2 {
134+
return nil, nil, errors.New("custom_ports has ':' but you must provide both ports")
135+
}
136+
customPorts = append(customPorts, fmt.Sprintf("%s/tcp", pp[1]))
137+
138+
dockerPort := nat.Port(fmt.Sprintf("%s/tcp", pp[1]))
139+
hostPort := fmt.Sprintf("%s/tcp", pp[0])
140+
portBindings[dockerPort] = []nat.PortBinding{
141+
{
142+
HostIP: "0.0.0.0",
143+
HostPort: hostPort,
144+
},
145+
}
146+
} else {
147+
customPorts = append(customPorts, fmt.Sprintf("%s/tcp", p))
148+
149+
dockerPort := nat.Port(fmt.Sprintf("%s/tcp", p))
150+
hostPort := fmt.Sprintf("%s/tcp", p)
151+
portBindings[dockerPort] = []nat.PortBinding{
152+
{
153+
HostIP: "0.0.0.0",
154+
HostPort: hostPort,
155+
},
156+
}
157+
}
158+
}
159+
exposedPorts := []string{httpPort}
160+
exposedPorts = append(exposedPorts, customPorts...)
161+
return exposedPorts, portBindings, nil
162+
}
163+
114164
func newNode(in *Input, pgOut *postgres.Output) (*NodeOut, error) {
115165
ctx := context.Background()
116166

@@ -147,37 +197,17 @@ func newNode(in *Input, pgOut *postgres.Output) (*NodeOut, error) {
147197
return nil, err
148198
}
149199

150-
httpPort := fmt.Sprintf("%s/tcp", DefaultHTTPPort)
151200
var containerName string
152201
if in.Node.Name != "" {
153202
containerName = in.Node.Name
154203
} else {
155204
containerName = framework.DefaultTCName("node")
156205
}
157-
customPorts := make([]string, 0)
158-
for _, p := range in.Node.CustomPorts {
159-
customPorts = append(customPorts, fmt.Sprintf("%d/tcp", p))
160-
}
161-
exposedPorts := []string{httpPort}
162-
exposedPorts = append(exposedPorts, customPorts...)
163206

164-
portBindings := nat.PortMap{
165-
nat.Port(httpPort): []nat.PortBinding{
166-
{
167-
HostIP: "0.0.0.0",
168-
HostPort: fmt.Sprintf("%d/tcp", in.Node.HTTPPort),
169-
},
170-
},
171-
}
172-
for _, p := range customPorts {
173-
portBindings[nat.Port(p)] = []nat.PortBinding{
174-
{
175-
HostIP: "0.0.0.0",
176-
HostPort: p,
177-
},
178-
}
207+
exposedPorts, portBindings, err := generatePortBindings(in)
208+
if err != nil {
209+
return nil, err
179210
}
180-
181211
req := tc.ContainerRequest{
182212
AlwaysPullImage: in.Node.PullImage,
183213
Image: in.Node.Image,

framework/components/jd/jd.go

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
package jd
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"github.com/docker/docker/api/types/container"
7+
"github.com/docker/go-connections/nat"
8+
"github.com/smartcontractkit/chainlink-testing-framework/framework"
9+
tc "github.com/testcontainers/testcontainers-go"
10+
tcwait "github.com/testcontainers/testcontainers-go/wait"
11+
"os"
12+
)
13+
14+
const (
15+
TmpImageName = "jd-local"
16+
GRPCPort string = "42242"
17+
CSAEncryptionKey string = "!PASsword000!"
18+
WSRPCPort string = "8080"
19+
)
20+
21+
type Input struct {
22+
Image string `toml:"image"`
23+
GRPCPort string `toml:"grpc_port"`
24+
WSRPCPort string `toml:"wsrpc_port"`
25+
DBURL string `toml:"db_url"`
26+
CSAEncryptionKey string `toml:"csa_encryption_key"`
27+
DockerFilePath string `toml:"docker_file"`
28+
DockerContext string `toml:"docker_ctx"`
29+
Out *Output `toml:"out"`
30+
}
31+
32+
type Output struct {
33+
UseCache bool `toml:"use_cache"`
34+
HostGRPCUrl string `toml:"grpc_url"`
35+
DockerGRPCUrl string `toml:"docker_internal_grpc_url"`
36+
HostWSRPCUrl string `toml:"wsrpc_url"`
37+
DockerWSRPCUrl string `toml:"docker_internal_wsrpc_url"`
38+
}
39+
40+
func defaults(in *Input) {
41+
if in.GRPCPort == "" {
42+
in.GRPCPort = GRPCPort
43+
}
44+
if in.WSRPCPort == "" {
45+
in.WSRPCPort = WSRPCPort
46+
}
47+
if in.CSAEncryptionKey == "" {
48+
in.CSAEncryptionKey = CSAEncryptionKey
49+
}
50+
}
51+
52+
func NewJD(in *Input) (*Output, error) {
53+
if in.Out != nil && in.Out.UseCache {
54+
return in.Out, nil
55+
}
56+
ctx := context.Background()
57+
defaults(in)
58+
jdImg := os.Getenv("CTF_JD_IMAGE")
59+
if jdImg != "" {
60+
in.Image = jdImg
61+
}
62+
containerName := framework.DefaultTCName("jd")
63+
bindPort := fmt.Sprintf("%s/tcp", in.GRPCPort)
64+
req := tc.ContainerRequest{
65+
Name: containerName,
66+
Image: in.Image,
67+
Labels: framework.DefaultTCLabels(),
68+
Networks: []string{framework.DefaultNetworkName},
69+
NetworkAliases: map[string][]string{
70+
framework.DefaultNetworkName: {containerName},
71+
},
72+
ExposedPorts: []string{bindPort},
73+
HostConfigModifier: func(h *container.HostConfig) {
74+
h.PortBindings = framework.MapTheSamePort(bindPort)
75+
},
76+
Env: map[string]string{
77+
"DATABASE_URL": in.DBURL,
78+
"PORT": in.GRPCPort,
79+
"NODE_RPC_PORT": in.WSRPCPort,
80+
"CSA_KEY_ENCRYPTION_SECRET": in.CSAEncryptionKey,
81+
},
82+
WaitingFor: tcwait.ForAll(
83+
tcwait.ForListeningPort(nat.Port(fmt.Sprintf("%s/tcp", in.GRPCPort))),
84+
),
85+
}
86+
if req.Image == "" {
87+
req.Image = TmpImageName
88+
if err := framework.BuildImage(in.DockerContext, in.DockerFilePath, req.Image); err != nil {
89+
return nil, err
90+
}
91+
req.KeepImage = false
92+
}
93+
c, err := tc.GenericContainer(ctx, tc.GenericContainerRequest{
94+
ContainerRequest: req,
95+
Started: true,
96+
})
97+
if err != nil {
98+
return nil, err
99+
}
100+
host, err := framework.GetHost(c)
101+
if err != nil {
102+
return nil, err
103+
}
104+
out := &Output{
105+
UseCache: true,
106+
HostGRPCUrl: fmt.Sprintf("http://%s:%s", host, in.GRPCPort),
107+
DockerGRPCUrl: fmt.Sprintf("http://%s:%s", containerName, in.GRPCPort),
108+
HostWSRPCUrl: fmt.Sprintf("ws://%s:%s", host, in.WSRPCPort),
109+
DockerWSRPCUrl: fmt.Sprintf("ws://%s:%s", containerName, in.WSRPCPort),
110+
}
111+
in.Out = out
112+
return out, nil
113+
}

framework/components/jd/jd_test.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package jd_test
2+
3+
import (
4+
"github.com/smartcontractkit/chainlink-testing-framework/framework"
5+
"github.com/smartcontractkit/chainlink-testing-framework/framework/components/jd"
6+
"github.com/smartcontractkit/chainlink-testing-framework/framework/components/postgres"
7+
"github.com/stretchr/testify/require"
8+
"os"
9+
"sync"
10+
"testing"
11+
)
12+
13+
// here we only test that we can boot up JD
14+
// client examples are under "examples" dir
15+
// since JD is private this env var should be set locally and in CI
16+
// TODO: add ComponentDocker prefix to turn this on when we'll have access to ECRs
17+
func TestJD(t *testing.T) {
18+
err := framework.DefaultNetwork(&sync.Once{})
19+
require.NoError(t, err)
20+
pgOut, err := postgres.NewPostgreSQL(&postgres.Input{
21+
Image: "postgres:12.0",
22+
Port: 14402,
23+
VolumeName: "c",
24+
})
25+
require.NoError(t, err)
26+
_, err = jd.NewJD(&jd.Input{
27+
DBURL: pgOut.JDDockerInternalURL,
28+
Image: os.Getenv("CTF_JD_IMAGE"),
29+
})
30+
require.NoError(t, err)
31+
}

framework/components/postgres/postgres.go

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,11 @@ type Input struct {
3232
}
3333

3434
type Output struct {
35-
Url string `toml:"url"`
36-
ContainerName string `toml:"container_name"`
37-
DockerInternalURL string `toml:"docker_internal_url"`
35+
Url string `toml:"url"`
36+
ContainerName string `toml:"container_name"`
37+
DockerInternalURL string `toml:"docker_internal_url"`
38+
JDUrl string `toml:"jd_url"`
39+
JDDockerInternalURL string `toml:"jd_docker_internal_url"`
3840
}
3941

4042
func NewPostgreSQL(in *Input) (*Output, error) {
@@ -47,6 +49,7 @@ func NewPostgreSQL(in *Input) (*Output, error) {
4749
for i := 0; i <= in.Databases; i++ {
4850
sqlCommands = append(sqlCommands, fmt.Sprintf("CREATE DATABASE db_%d;", i))
4951
}
52+
sqlCommands = append(sqlCommands, "CREATE DATABASE jd;")
5053
sqlCommands = append(sqlCommands, "ALTER USER chainlink WITH SUPERUSER;")
5154
initSQL := strings.Join(sqlCommands, "\n")
5255
initFile, err := os.CreateTemp("", "init-*.sql")
@@ -144,5 +147,21 @@ func NewPostgreSQL(in *Input) (*Output, error) {
144147
portToExpose,
145148
Database,
146149
),
150+
JDDockerInternalURL: fmt.Sprintf(
151+
"postgresql://%s:%s@%s:%s/%s?sslmode=disable",
152+
User,
153+
Password,
154+
containerName,
155+
Port,
156+
"jd",
157+
),
158+
JDUrl: fmt.Sprintf(
159+
"postgresql://%s:%s@%s:%d/%s?sslmode=disable",
160+
User,
161+
Password,
162+
host,
163+
portToExpose,
164+
"jd",
165+
),
147166
}, nil
148167
}

framework/components/simple_node_set/node_set.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ type Input struct {
3232
// Output is a node set configuration output, used for caching or external components
3333
type Output struct {
3434
UseCache bool `toml:"use_cache"`
35+
DBOut *postgres.Output `toml:"db_out"`
3536
CLNodes []*clnode.Output `toml:"cl_nodes"`
3637
}
3738

@@ -170,6 +171,7 @@ func sharedDBSetup(in *Input, bcOut *blockchain.Output) (*Output, error) {
170171
sortNodeOutsByHostPort(nodeOuts)
171172
return &Output{
172173
UseCache: true,
174+
DBOut: dbOut,
173175
CLNodes: nodeOuts,
174176
}, nil
175177
}

0 commit comments

Comments
 (0)