Skip to content

Commit

Permalink
v0.4.8: Logs API, chaos examples, debugger port fix (#1609)
Browse files Browse the repository at this point in the history
v0.4.8
  • Loading branch information
skudasov authored Feb 3, 2025
1 parent 99204e1 commit 508c148
Show file tree
Hide file tree
Showing 16 changed files with 584 additions and 34 deletions.
9 changes: 5 additions & 4 deletions .github/workflows/framework-golden-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,11 @@ jobs:
config: smoke_limited_resources.toml
count: 1
timeout: 10m
- name: TestSuiSmoke
config: smoke_sui.toml
count: 1
timeout: 10m
# Sui client starts to flake and do not respond to healthcheck properly, need to be fixed
# - name: TestSuiSmoke
# config: smoke_sui.toml
# count: 1
# timeout: 10m
- name: TestAptosSmoke
config: smoke_aptos.toml
count: 1
Expand Down
3 changes: 2 additions & 1 deletion .nancy-ignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,5 @@ CVE-2023-42319 # CWE-noinfo: lol... go-ethereum v1.13.8 again
CVE-2024-10086 # Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')
CVE-2024-51744 # CWE-755: Improper Handling of Exceptional Conditions
CVE-2024-45338 # CWE-770: Allocation of Resources Without Limits or Throttling
CVE-2024-45337 # CWE-863: Incorrect Authorization in golang.org/x/[email protected]
CVE-2024-45337 # CWE-863: Incorrect Authorization in golang.org/x/[email protected]
CVE-2025-24883 # CWE-248: Uncaught Exception
1 change: 1 addition & 0 deletions book/src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
- [NodeSet (Local Docker builds)](./framework/nodeset_docker_rebuild.md)
- [NodeSet Compat Environment](./framework/nodeset_compatibility.md)
- [Creating your own components](./developing/developing_components.md)
- [Asserting Logs](./developing/asserting_logs.md)
- [Fork Testing](./framework/fork.md)
- [Quick Contracts Deployment](./framework/quick_deployment.md)
- [Verifying Contracts](./framework/verify.md)
Expand Down
35 changes: 35 additions & 0 deletions book/src/developing/asserting_logs.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Asserting Container Logs

You can either assert that CL nodes have no errors like that, we check `(CRIT|PANIC|FATAL)` levels by default for all the nodes

```golang
in, err := framework.Load[Cfg](t)
require.NoError(t, err)
t.Cleanup(func() {
err := framework.SaveAndCheckLogs(t)
require.NoError(t, err)
})
```

or customize file assertions

```golang
in, err := framework.Load[Cfg](t)
require.NoError(t, err)
t.Cleanup(func() {
// save all the logs to default directory "logs/docker-$test_name"
logs, err := framework.SaveContainerLogs(fmt.Sprintf("%s-%s", framework.DefaultCTFLogsDir, t.Name()))
require.NoError(t, err)
// check that CL nodes has no errors (CRIT|PANIC|FATAL) levels
err = framework.CheckCLNodeContainerErrors()
require.NoError(t, err)
// do custom assertions
for _, l := range logs {
matches, err := framework.SearchLogFile(l, " name=HeadReporter version=\\d")
require.NoError(t, err)
_ = matches
}
})
```

Full [example](https://github.com/smartcontractkit/chainlink-testing-framework/blob/main/framework/examples/myproject/smoke_logs_test.go)
2 changes: 2 additions & 0 deletions framework/.changeset/v0.4.8.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
- Do not expose 40k ports by default, only with CTF_CLNODE_DLV flag
- Remove default log checking API, make it simpler to customize, add docs
17 changes: 9 additions & 8 deletions framework/components/clnode/clnode.go
Original file line number Diff line number Diff line change
Expand Up @@ -135,20 +135,22 @@ func generateEntryPoint() []string {
// exposes custom_ports in format "host:docker" or map 1-to-1 if only "host" port is provided
func generatePortBindings(in *Input) ([]string, nat.PortMap, error) {
httpPort := fmt.Sprintf("%s/tcp", DefaultHTTPPort)
innerDebuggerPort := fmt.Sprintf("%d/tcp", DefaultDebuggerPort)
exposedPorts := []string{httpPort}
portBindings := nat.PortMap{
nat.Port(httpPort): []nat.PortBinding{
{
HostIP: "0.0.0.0",
HostPort: strconv.Itoa(in.Node.HTTPPort),
},
},
nat.Port(innerDebuggerPort): []nat.PortBinding{
{
HostIP: "0.0.0.0",
HostPort: strconv.Itoa(in.Node.DebuggerPort),
},
},
}
if os.Getenv("CTF_CLNODE_DLV") == "true" {
innerDebuggerPort := fmt.Sprintf("%d/tcp", DefaultDebuggerPort)
portBindings[nat.Port(innerDebuggerPort)] = append(portBindings[nat.Port(innerDebuggerPort)], nat.PortBinding{
HostIP: "0.0.0.0",
HostPort: strconv.Itoa(in.Node.DebuggerPort),
})
exposedPorts = append(exposedPorts, strconv.Itoa(DefaultDebuggerPort))
}
customPorts := make([]string, 0)
for _, p := range in.Node.CustomPorts {
Expand Down Expand Up @@ -180,7 +182,6 @@ func generatePortBindings(in *Input) ([]string, nat.PortMap, error) {
}
}
}
exposedPorts := []string{httpPort, strconv.Itoa(DefaultDebuggerPort)}
exposedPorts = append(exposedPorts, customPorts...)
return exposedPorts, portBindings, nil
}
Expand Down
2 changes: 1 addition & 1 deletion framework/components/simple_node_set/reload.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import (
// this API is discouraged, however, you can use it if nodes require restart or configuration updates, temporarily!
func UpgradeNodeSet(t *testing.T, in *Input, bc *blockchain.Output, wait time.Duration) (*Output, error) {
uniq := fmt.Sprintf("%s-%s-%s", framework.DefaultCTFLogsDir, t.Name(), uuid.NewString()[0:4])
if err := framework.WriteAllContainersLogs(uniq); err != nil {
if _, err := framework.SaveContainerLogs(uniq); err != nil {
return nil, err
}
_, err := chaos.ExecPumba("rm --volumes=false re2:^node.*|ns-postgresql.*", wait)
Expand Down
4 changes: 0 additions & 4 deletions framework/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -130,10 +130,6 @@ func Load[X any](t *testing.T) (*X, error) {
t.Cleanup(func() {
err := Store[X](input)
require.NoError(t, err)
err = WriteAllContainersLogs(fmt.Sprintf("%s-%s", DefaultCTFLogsDir, t.Name()))
require.NoError(t, err)
err = checkAllNodeLogErrors()
require.NoError(t, err)
})
// TODO: not all the people have AWS access, sadly enough, uncomment when granted
//if os.Getenv(EnvVarAWSSecretsManager) == "true" {
Expand Down
63 changes: 55 additions & 8 deletions framework/docker.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@ package framework

import (
"archive/tar"
"bufio"
"bytes"
"context"
"encoding/binary"
"fmt"
"github.com/docker/docker/api/types/container"
filters2 "github.com/docker/docker/api/types/filters"
dfilter "github.com/docker/docker/api/types/filters"
"github.com/docker/docker/client"
"github.com/docker/go-connections/nat"
"github.com/google/uuid"
Expand All @@ -17,8 +18,10 @@ import (
"os"
"os/exec"
"path/filepath"
"regexp"
"strings"
"sync"
"testing"
)

const (
Expand Down Expand Up @@ -176,30 +179,70 @@ func (dc *DockerClient) copyToContainer(containerID, sourceFile, targetPath stri
return nil
}

// WriteAllContainersLogs writes all Docker container logs to the default logs directory
func WriteAllContainersLogs(dir string) error {
// SearchLogFile searches logfile using regex and return matches or error
func SearchLogFile(fp string, regex string) ([]string, error) {
file, err := os.Open(fp)
if err != nil {
return nil, err
}
defer file.Close()
scanner := bufio.NewScanner(file)
re, err := regexp.Compile(regex)
if err != nil {
return nil, err
}
matches := make([]string, 0)
for scanner.Scan() {
line := scanner.Text()
if re.MatchString(line) {
L.Info().Str("Regex", regex).Msg("Log match found")
matches = append(matches, line)
}
}

if err := scanner.Err(); err != nil {
return matches, err
}
return matches, nil
}

func SaveAndCheckLogs(t *testing.T) error {
_, err := SaveContainerLogs(fmt.Sprintf("%s-%s", DefaultCTFLogsDir, t.Name()))
if err != nil {
return err
}
err = CheckCLNodeContainerErrors()
if err != nil {
return err
}
return nil
}

// SaveContainerLogs writes all Docker container logs to some directory
func SaveContainerLogs(dir string) ([]string, error) {
L.Info().Msg("Writing Docker containers logs")
if _, err := os.Stat(dir); os.IsNotExist(err) {
if err := os.MkdirAll(dir, 0755); err != nil {
return fmt.Errorf("failed to create directory %s: %w", dir, err)
return nil, fmt.Errorf("failed to create directory %s: %w", dir, err)
}
}
provider, err := tc.NewDockerProvider()
if err != nil {
return fmt.Errorf("failed to create Docker provider: %w", err)
return nil, fmt.Errorf("failed to create Docker provider: %w", err)
}
containers, err := provider.Client().ContainerList(context.Background(), container.ListOptions{
All: true,
Filters: filters2.NewArgs(filters2.KeyValuePair{
Filters: dfilter.NewArgs(dfilter.KeyValuePair{
Key: "label",
Value: "framework=ctf",
}),
})
if err != nil {
return fmt.Errorf("failed to list Docker containers: %w", err)
return nil, fmt.Errorf("failed to list Docker containers: %w", err)
}

eg := &errgroup.Group{}
logFilePaths := make([]string, 0)

for _, containerInfo := range containers {
eg.Go(func() error {
Expand All @@ -217,6 +260,7 @@ func WriteAllContainersLogs(dir string) error {
L.Error().Err(err).Str("Container", containerName).Msg("failed to create container log file")
return err
}
logFilePaths = append(logFilePaths, logFilePath)
// Parse and write logs
header := make([]byte, 8) // Docker stream header is 8 bytes
for {
Expand Down Expand Up @@ -249,7 +293,10 @@ func WriteAllContainersLogs(dir string) error {
return nil
})
}
return eg.Wait()
if err := eg.Wait(); err != nil {
return nil, err
}
return logFilePaths, nil
}

func BuildImageOnce(once *sync.Once, dctx, dfile, nameAndTag string) error {
Expand Down
59 changes: 59 additions & 0 deletions framework/examples/myproject/chaos_blockchain_evm_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package examples

import (
"github.com/smartcontractkit/chainlink-testing-framework/framework/rpc"
"github.com/stretchr/testify/require"
"os"
"testing"
"time"
)

func TestBlockchainChaos(t *testing.T) {
srcChain := os.Getenv("CTF_CHAOS_SRC_CHAIN_RPC_HTTP_URL")
require.NotEmpty(t, srcChain, "source chain RPC must be set")
dstChain := os.Getenv("CTF_CHAOS_DST_CHAIN_RPC_HTTP_URL")
require.NotEmpty(t, dstChain, "destination chain RPC must be set")

recoveryIntervalDuration := 120 * time.Second

testCases := []struct {
name string
chainURL string
reorgDepth int
}{
{
name: "Reorg src with depth: 1",
chainURL: srcChain,
reorgDepth: 1,
},
{
name: "Reorg dst with depth: 1",
chainURL: dstChain,
reorgDepth: 1,
},
{
name: "Reorg src with depth: 5",
chainURL: srcChain,
reorgDepth: 5,
},
{
name: "Reorg dst with depth: 5",
chainURL: dstChain,
reorgDepth: 5,
},
}

// Run test cases
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
t.Log(tc.name)
r := rpc.New(tc.chainURL, nil)
err := r.GethSetHead(tc.reorgDepth)
require.NoError(t, err)
t.Logf("Awaiting chaos recovery: %s", tc.name)
time.Sleep(recoveryIntervalDuration)

// Validate Chainlink product here, use load test assertions
})
}
}
Loading

0 comments on commit 508c148

Please sign in to comment.