Skip to content
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

Define resources for common components: NodeSet, Blockchain, PostgreSQL #1590

Merged
merged 6 commits into from
Jan 21, 2025
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
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
6 changes: 5 additions & 1 deletion .github/workflows/framework-golden-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ jobs:
config: smoke.toml
count: 1
timeout: 10m
- name: TestSmoke
config: smoke_limited_resources.toml
count: 1
timeout: 10m
- name: TestSuiSmoke
config: smoke_sui.toml
count: 1
Expand Down Expand Up @@ -54,7 +58,7 @@ jobs:
config: scalability.toml
count: 1
timeout: 10m
name: ${{ matrix.test.name }}
name: ${{ matrix.test.name }} - ${{ matrix.test.config }}
steps:
- name: Checkout repo
uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4
Expand Down
1 change: 1 addition & 0 deletions book/src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
- [Debugging Tests](framework/components/debug.md)
- [Components Cleanup](framework/components/cleanup.md)
- [Components Caching](framework/components/caching.md)
- [Components Resources](framework/components/resources.md)
- [Mocking Services](framework/components/mocking.md)
- [Copying Files](framework/copying_files.md)
- [External Environment](framework/components/external.md)
Expand Down
24 changes: 24 additions & 0 deletions book/src/framework/components/resources.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Components Resources

You can use `resources` to limit containers CPU/Memory for `NodeSet`, `Blockchain` and `PostgreSQL` components.
```toml
[blockchain_a.resources]
cpus = 0.5
memory_mb = 1048

[nodeset.db.resources]
cpus = 2
memory_mb = 2048

[nodeset.node_specs.node.resources]
cpus = 1
memory_mb = 1048
```

Read more about resource constraints [here](https://docs.docker.com/engine/containers/resource_constraints/).

We are using `cpu-period` and `cpu-quota` for simplicity, and because it's working with an arbitrary amount of containers, it is absolute.
skudasov marked this conversation as resolved.
Show resolved Hide resolved

Memory swapping is off if you specify `resources` key.
skudasov marked this conversation as resolved.
Show resolved Hide resolved

Full configuration [example]()
1 change: 1 addition & 0 deletions framework/.changeset/v0.4.7.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- Set cgroups resource for containers
1 change: 1 addition & 0 deletions framework/components/blockchain/anvil.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ func newAnvil(in *Input) (*Output, error) {
ExposedPorts: []string{bindPort},
HostConfigModifier: func(h *container.HostConfig) {
h.PortBindings = framework.MapTheSamePort(bindPort)
framework.ResourceLimitsFunc(h, in.ContainerResources)
},
Networks: []string{framework.DefaultNetworkName},
NetworkAliases: map[string][]string{
Expand Down
1 change: 1 addition & 0 deletions framework/components/blockchain/aptos.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ func newAptos(in *Input) (*Output, error) {
},
HostConfigModifier: func(h *container.HostConfig) {
h.PortBindings = framework.MapTheSamePort(bindPort)
framework.ResourceLimitsFunc(h, in.ContainerResources)
},
ImagePlatform: "linux/amd64",
Cmd: []string{
Expand Down
1 change: 1 addition & 0 deletions framework/components/blockchain/besu.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ func newBesu(in *Input) (*Output, error) {
Labels: framework.DefaultTCLabels(),
HostConfigModifier: func(h *container.HostConfig) {
h.PortBindings = framework.MapTheSamePort(bindPortWs, bindPort)
framework.ResourceLimitsFunc(h, in.ContainerResources)
},
WaitingFor: wait.ForListeningPort(nat.Port(in.Port)).WithStartupTimeout(15 * time.Second).WithPollInterval(200 * time.Millisecond),
Cmd: entryPoint,
Expand Down
4 changes: 3 additions & 1 deletion framework/components/blockchain/blockchain.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package blockchain

import (
"fmt"
"github.com/smartcontractkit/chainlink-testing-framework/framework"
"github.com/testcontainers/testcontainers-go"
)

Expand All @@ -25,7 +26,8 @@ type Input struct {
// programs to deploy on solana-test-validator start
// a map of program name to program id
// there needs to be a matching .so file in contracts_dir
SolanaPrograms map[string]string `toml:"solana_programs"`
SolanaPrograms map[string]string `toml:"solana_programs"`
ContainerResources *framework.ContainerResources `toml:"resources"`
}

// Output is a blockchain network output, ChainID and one or more nodes that forms the network
Expand Down
1 change: 1 addition & 0 deletions framework/components/blockchain/geth.go
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,7 @@ func newGeth(in *Input) (*Output, error) {
ExposedPorts: []string{bindPort},
HostConfigModifier: func(h *container.HostConfig) {
h.PortBindings = framework.MapTheSamePort(bindPort)
framework.ResourceLimitsFunc(h, in.ContainerResources)
h.Mounts = append(h.Mounts, mount.Mount{
Type: mount.TypeBind,
Source: keystoreDir,
Expand Down
1 change: 1 addition & 0 deletions framework/components/blockchain/solana.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ func newSolana(in *Input) (*Output, error) {
WithPollInterval(100 * time.Millisecond),
HostConfigModifier: func(h *container.HostConfig) {
h.PortBindings = framework.MapTheSamePort(bindPort, wsBindPort)
framework.ResourceLimitsFunc(h, in.ContainerResources)
h.Mounts = append(h.Mounts, mount.Mount{
Type: mount.TypeBind,
Source: contractsDir,
Expand Down
1 change: 1 addition & 0 deletions framework/components/blockchain/sui.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ func newSui(in *Input) (*Output, error) {
},
HostConfigModifier: func(h *container.HostConfig) {
h.PortBindings = framework.MapTheSamePort(bindPort, DefaultFaucetPort)
framework.ResourceLimitsFunc(h, in.ContainerResources)
},
ImagePlatform: "linux/amd64",
Env: map[string]string{
Expand Down
1 change: 1 addition & 0 deletions framework/components/blockchain/tron.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ func newTron(in *Input) (*Output, error) {
Labels: framework.DefaultTCLabels(),
HostConfigModifier: func(h *container.HostConfig) {
h.PortBindings = framework.MapTheSamePort(bindPort)
framework.ResourceLimitsFunc(h, in.ContainerResources)
},
WaitingFor: wait.ForLog("Mnemonic").WithPollInterval(200 * time.Millisecond).WithStartupTimeout(1 * time.Minute),
Files: []testcontainers.ContainerFile{
Expand Down
32 changes: 17 additions & 15 deletions framework/components/clnode/clnode.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,21 +42,22 @@ type Input struct {

// NodeInput is CL nod container inputs
type NodeInput struct {
Image string `toml:"image" validate:"required"`
Name string `toml:"name"`
DockerFilePath string `toml:"docker_file"`
DockerContext string `toml:"docker_ctx"`
PullImage bool `toml:"pull_image"`
CapabilitiesBinaryPaths []string `toml:"capabilities"`
CapabilityContainerDir string `toml:"capabilities_container_dir"`
TestConfigOverrides string `toml:"test_config_overrides"`
UserConfigOverrides string `toml:"user_config_overrides"`
TestSecretsOverrides string `toml:"test_secrets_overrides"`
UserSecretsOverrides string `toml:"user_secrets_overrides"`
HTTPPort int `toml:"port"`
P2PPort int `toml:"p2p_port"`
CustomPorts []string `toml:"custom_ports"`
DebuggerPort int `toml:"debugger_port"`
Image string `toml:"image" validate:"required"`
Name string `toml:"name"`
DockerFilePath string `toml:"docker_file"`
DockerContext string `toml:"docker_ctx"`
PullImage bool `toml:"pull_image"`
CapabilitiesBinaryPaths []string `toml:"capabilities"`
CapabilityContainerDir string `toml:"capabilities_container_dir"`
TestConfigOverrides string `toml:"test_config_overrides"`
UserConfigOverrides string `toml:"user_config_overrides"`
TestSecretsOverrides string `toml:"test_secrets_overrides"`
UserSecretsOverrides string `toml:"user_secrets_overrides"`
HTTPPort int `toml:"port"`
P2PPort int `toml:"p2p_port"`
CustomPorts []string `toml:"custom_ports"`
DebuggerPort int `toml:"debugger_port"`
ContainerResources *framework.ContainerResources `toml:"resources"`
}

// Output represents Chainlink node output, nodes and databases connection URLs
Expand Down Expand Up @@ -247,6 +248,7 @@ func newNode(in *Input, pgOut *postgres.Output) (*NodeOut, error) {
if in.Node.HTTPPort != 0 && in.Node.P2PPort != 0 {
req.HostConfigModifier = func(h *container.HostConfig) {
h.PortBindings = portBindings
framework.ResourceLimitsFunc(h, in.Node.ContainerResources)
}
}
files := []tc.ContainerFile{
Expand Down
18 changes: 10 additions & 8 deletions framework/components/postgres/postgres.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,15 @@ const (
)

type Input struct {
Image string `toml:"image" validate:"required"`
Port int `toml:"port"`
Name string `toml:"name"`
VolumeName string `toml:"volume_name"`
Databases int `toml:"databases"`
JDDatabase bool `toml:"jd_database"`
PullImage bool `toml:"pull_image"`
Out *Output `toml:"out"`
Image string `toml:"image" validate:"required"`
Port int `toml:"port"`
Name string `toml:"name"`
VolumeName string `toml:"volume_name"`
Databases int `toml:"databases"`
JDDatabase bool `toml:"jd_database"`
PullImage bool `toml:"pull_image"`
ContainerResources *framework.ContainerResources `toml:"resources"`
Out *Output `toml:"out"`
}

type Output struct {
Expand Down Expand Up @@ -133,6 +134,7 @@ func NewPostgreSQL(in *Input) (*Output, error) {
},
},
}
framework.ResourceLimitsFunc(h, in.ContainerResources)
}
c, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{
ContainerRequest: req,
Expand Down
1 change: 1 addition & 0 deletions framework/components/simple_node_set/node_set.go
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ func sharedDBSetup(in *Input, bcOut *blockchain.Output) (*Output, error) {
UserConfigOverrides: in.NodeSpecs[overrideIdx].Node.UserConfigOverrides,
TestSecretsOverrides: in.NodeSpecs[overrideIdx].Node.TestSecretsOverrides,
UserSecretsOverrides: in.NodeSpecs[overrideIdx].Node.UserSecretsOverrides,
ContainerResources: in.NodeSpecs[overrideIdx].Node.ContainerResources,
},
}

Expand Down
25 changes: 25 additions & 0 deletions framework/docker.go
Original file line number Diff line number Diff line change
Expand Up @@ -320,3 +320,28 @@ func ExecContainer(containerName string, command []string) (string, error) {
L.Info().Str("Output", string(output)).Msg("Command output")
return string(output), nil
}

type ContainerResources struct {
CPUs float64 `toml:"cpus"`
MemoryMb int `toml:"memory_mb"`
skudasov marked this conversation as resolved.
Show resolved Hide resolved
}

// ResourceLimitsFunc returns a function to configure container resources based on the human-readable CPUs and memory in Mb
func ResourceLimitsFunc(h *container.HostConfig, resources *ContainerResources) {
if resources == nil {
return
}
if resources.MemoryMb != 0 {
h.Memory = int64(resources.MemoryMb) * 1024 * 1024 // Memory in Mb
h.MemoryReservation = int64(resources.MemoryMb) * 1024 * 1024 // Total memory that can be reserved (soft) in Mb
// https://docs.docker.com/engine/containers/resource_constraints/ if both values are equal swap is off, read the docs
h.MemorySwap = h.Memory
}
if resources.CPUs != 0 {
skudasov marked this conversation as resolved.
Show resolved Hide resolved
// Set CPU limits using CPUQuota and CPUPeriod
// we don't use runtime.NumCPU or docker API to get CPUs because h.CPUShares is relative to amount of containers you run
// CPUPeriod and CPUQuota are absolute and easier to control
h.CPUPeriod = 100000 // Default period (100ms)
h.CPUQuota = int64(resources.CPUs * 100000) // Quota in microseconds (e.g., 0.5 CPUs = 50000)
}
}
6 changes: 2 additions & 4 deletions framework/examples/myproject/smoke.toml
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@

[blockchain_a]
# choose "anvil", "geth" or "besu"
# use docker_cmd_params = ["-b", "1"] for "anvil"
type = "anvil"
docker_cmd_params = ["-b", "1"]
type = "anvil"

[data_provider]
port = 9111
Expand All @@ -18,4 +16,4 @@
[[nodeset.node_specs]]

[nodeset.node_specs.node]
image = "public.ecr.aws/chainlink/chainlink:v2.17.0"
image = "public.ecr.aws/chainlink/chainlink:v2.17.0"
31 changes: 31 additions & 0 deletions framework/examples/myproject/smoke_limited_resources.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@

[blockchain_a]
docker_cmd_params = ["-b", "1"]
type = "anvil"

[blockchain_a.resources]
cpus = 1
memory_mb = 1048

[data_provider]
port = 9111

[nodeset]
nodes = 5
override_mode = "all"

[nodeset.db]
image = "postgres:12.0"

[nodeset.db.resources]
cpus = 1
memory_mb = 1048

[[nodeset.node_specs]]

[nodeset.node_specs.node]
image = "public.ecr.aws/chainlink/chainlink:v2.17.0"

[nodeset.node_specs.node.resources]
cpus = 1
memory_mb = 1048
Loading