From 5c5e47ac2bef18bd0279ce4e4981bc644ac82e29 Mon Sep 17 00:00:00 2001 From: skudasov Date: Thu, 10 Jul 2025 11:58:25 +0200 Subject: [PATCH 1/5] WIP: cleanup documentation --- book/book.toml | 4 + book/src/SUMMARY.md | 115 +-- book/src/developing.md | 2 +- ...nment_import.md => cl_node_keys_import.md} | 3 +- .../framework/components/chainlink/node.md | 112 --- .../framework/components/chainlink/nodeset.md | 163 ---- book/src/framework/components/mocking.md | 2 +- book/src/framework/components/overview.md | 2 +- book/src/framework/getting_started.md | 4 +- book/src/framework/nodeset_compatibility.md | 64 -- .../observability/observability_stack.md | 7 - book/src/framework/overview.md | 3 - book/src/k8s-test-runner/k8s-test-runner.md | 4 + book/src/legacy.md | 3 + book/src/lib/blockchain.md | 45 -- book/src/lib/client.md | 11 - book/src/lib/client/aws_secrets_manager.md | 33 - book/src/lib/client/github.md | 28 - book/src/lib/client/grafana.md | 80 -- book/src/lib/client/kafka.md | 19 - book/src/lib/client/loki.md | 63 -- book/src/lib/client/mockserver.md | 120 --- book/src/lib/client/postgres.md | 44 -- book/src/lib/client/prometheus.md | 44 -- book/src/lib/concurrency.md | 95 --- book/src/lib/config/config.md | 363 --------- book/src/lib/crib.md | 7 - book/src/lib/docker/blockchain_nodes.md | 202 ----- book/src/lib/docker/chainlink_ecosystem.md | 49 -- book/src/lib/docker/overview.md | 68 -- book/src/lib/docker/test_helpers.md | 10 - book/src/lib/k8s/KUBERNETES.md | 36 - book/src/lib/k8s/REMOTE_RUN.md | 48 -- book/src/lib/k8s/TUTORIAL.md | 743 ------------------ book/src/lib/k8s/labels.md | 67 -- book/src/lib/k8s_new/environments.md | 287 ------- book/src/lib/k8s_new/overview.md | 29 - book/src/lib/k8s_new/remote_runner.md | 250 ------ book/src/lib/k8s_new/test_secrets.md | 36 - book/src/lib/logging.md | 23 - .../src/lib/wasp/how-to/run_included_tests.md | 1 - book/src/libraries.md | 2 +- 42 files changed, 58 insertions(+), 3233 deletions(-) rename book/src/framework/{nodeset_environment_import.md => cl_node_keys_import.md} (91%) delete mode 100644 book/src/framework/components/chainlink/node.md delete mode 100644 book/src/framework/components/chainlink/nodeset.md delete mode 100644 book/src/framework/nodeset_compatibility.md create mode 100644 book/src/legacy.md delete mode 100644 book/src/lib/blockchain.md delete mode 100644 book/src/lib/client.md delete mode 100644 book/src/lib/client/aws_secrets_manager.md delete mode 100644 book/src/lib/client/github.md delete mode 100644 book/src/lib/client/grafana.md delete mode 100644 book/src/lib/client/kafka.md delete mode 100644 book/src/lib/client/loki.md delete mode 100644 book/src/lib/client/mockserver.md delete mode 100644 book/src/lib/client/postgres.md delete mode 100644 book/src/lib/client/prometheus.md delete mode 100644 book/src/lib/concurrency.md delete mode 100644 book/src/lib/config/config.md delete mode 100644 book/src/lib/crib.md delete mode 100644 book/src/lib/docker/blockchain_nodes.md delete mode 100644 book/src/lib/docker/chainlink_ecosystem.md delete mode 100644 book/src/lib/docker/overview.md delete mode 100644 book/src/lib/docker/test_helpers.md delete mode 100644 book/src/lib/k8s/KUBERNETES.md delete mode 100644 book/src/lib/k8s/REMOTE_RUN.md delete mode 100644 book/src/lib/k8s/TUTORIAL.md delete mode 100644 book/src/lib/k8s/labels.md delete mode 100644 book/src/lib/k8s_new/environments.md delete mode 100644 book/src/lib/k8s_new/overview.md delete mode 100644 book/src/lib/k8s_new/remote_runner.md delete mode 100644 book/src/lib/k8s_new/test_secrets.md delete mode 100644 book/src/lib/logging.md delete mode 100644 book/src/lib/wasp/how-to/run_included_tests.md diff --git a/book/book.toml b/book/book.toml index ea17be3c8..c150a410a 100644 --- a/book/book.toml +++ b/book/book.toml @@ -12,6 +12,10 @@ build-dir = "docs" default-theme = "ayu" additional-js = ["mermaid.min.js", "mermaid-init.js"] +[output.html.fold] +enable = true +level = 0 + [preprocessor.alerts] [preprocessor.cmdrun] diff --git a/book/src/SUMMARY.md b/book/src/SUMMARY.md index e94b61e02..7997df380 100644 --- a/book/src/SUMMARY.md +++ b/book/src/SUMMARY.md @@ -2,70 +2,59 @@ - [Overview](./overview.md) - [Framework](./framework/overview.md) +- [Basic Usage](./framework/getting_started.md) - [Getting Started](./framework/getting_started.md) - [Your First Test](./framework/first_test.md) - [NodeSet Environment](./framework/nodeset_environment.md) - - [NodeSet Environment (Import Keys)](./framework/nodeset_environment_import.md) - - [NodeSet with Capabilities](./framework/nodeset_capabilities.md) + - [NodeSet (Capabilities)](./framework/nodeset_capabilities.md) - [NodeSet (Local Docker builds)](./framework/nodeset_docker_rebuild.md) - - [NodeSet Compat Environment](./framework/nodeset_compatibility.md) +- [Advanced Usage](./framework/configuration.md) + - [CLI](./framework/cli.md) + - [Configuration](./framework/configuration.md) + - [Debugging Tests](framework/components/debug.md) - [Creating your own components](./developing/developing_components.md) + - [Exposing Components](framework/components/state.md) - [Asserting Logs](./developing/asserting_logs.md) - [Quick Contracts Deployment](./framework/quick_deployment.md) - [Verifying Contracts](./framework/verify.md) - - [NodeSet with External Blockchain]() - - [CLI](./framework/cli.md) - - [Configuration](./framework/configuration.md) + - [CL Node Keys Import](./framework/cl_node_keys_import.md) - [Test Configuration](./framework/test_configuration_overrides.md) - - [Exposing Components](framework/components/state.md) - - [Debugging Tests](framework/components/debug.md) - [Debugging K8s Chaos Tests](framework/chaos/debug-k8s.md) - [Components Cleanup](framework/components/cleanup.md) - [Components Caching](framework/components/caching.md) - [Components Resources](framework/components/resources.md) - [Containers Network Isolation](framework/components/network_isolation.md) - - [Faking Services](framework/components/mocking.md) + - [Fake Services](framework/components/mocking.md) - [Copying Files](framework/copying_files.md) - [External Environment](framework/components/external.md) - - [Troubleshooting](framework/components/troubleshooting.md) - - [Secrets]() - - [Observability Stack](framework/observability/observability_stack.md) - - [Metrics](framework/observability/metrics.md) - - [Logs](framework/observability/logs.md) - - [Profiling](framework/observability/profiling.md) - - [PostgreSQL](framework/observability/postgresql.md) - - [Traces]() - - [Blockscout](framework/observability/blockscout.md) - - [Components](framework/components/overview.md) - - [Blockchains](framework/components/blockchains/overview.md) - - [EVM](framework/components/blockchains/evm.md) - - [Solana](framework/components/blockchains/solana.md) - - [Aptos](framework/components/blockchains/aptos.md) - - [Sui](framework/components/blockchains/sui.md) - - [TRON](framework/components/blockchains/tron.md) - - [ZKSync](framework/components/blockchains/zksync.md) - - [Ton](framework/components/blockchains/ton.md) - - [Optimism Stack]() - - [Arbitrum Stack]() - - [Chainlink](framework/components/chainlink.md) - - [Node](framework/components/chainlink/node.md) - - [NodeSet](framework/components/chainlink/nodeset.md) - - [Storage](framework/components/storage.md) - - [S3](framework/components/storage/s3.md) - - [Chip Ingress Set](framework/components/chipingresset/chip_ingress.md) - - [Clients]() - - [Chainlink]() - - [RPC]() - - [Loki]() +- [Observability Stack](framework/observability/observability_stack.md) + - [Overview](framework/observability/observability_stack.md) + - [Metrics](framework/observability/metrics.md) + - [Logs](framework/observability/logs.md) + - [Profiling](framework/observability/profiling.md) + - [PostgreSQL](framework/observability/postgresql.md) + - [BlockScout](framework/observability/blockscout.md) +- [Components](framework/components/overview.md) + - [Overview](framework/components/overview.md) + - [Blockchains](framework/components/blockchains/overview.md) + - [EVM](framework/components/blockchains/evm.md) + - [Solana](framework/components/blockchains/solana.md) + - [Aptos](framework/components/blockchains/aptos.md) + - [Sui](framework/components/blockchains/sui.md) + - [TRON](framework/components/blockchains/tron.md) + - [ZKSync](framework/components/blockchains/zksync.md) + - [Ton](framework/components/blockchains/ton.md) + - [Storage](framework/components/storage.md) + - [S3](framework/components/storage/s3.md) + - [Chip Ingress Set](framework/components/chipingresset/chip_ingress.md) +- [Troubleshooting](framework/components/troubleshooting.md) - [Mono Repository Tooling](./monorepo-tools.md) - [Testing Maturity Model](framework/testing.md) - - [Smoke]() - - [Performance]() - - [Chaos](./framework/chaos/chaos.md) + - [Chaos Testing](./framework/chaos/chaos.md) - [Fork Testing](./framework/fork.md) - [Fork Testing (Mutating Storage)](./framework/fork_storage.md) - [Libraries](./libraries.md) - - [Seth](./libs/seth.md) + - [Overview](./libraries.md) - [WASP](./libs/wasp/overview.md) - [Getting started](./libs/wasp/getting_started.md) - [First test (RPS test)](./libs/wasp/first_test.md) @@ -112,45 +101,19 @@ - [Reuse dashboard components](./libs/wasp/how-to/reuse_dashboard_components.md) - [Parallelize load](./libs/wasp/how-to/parallelise_load.md) - [Debug Loki errors](./libs/wasp/how-to/debug_loki_errors.md) - - [Havoc](./libs/havoc.md) - - [K8s Test Runner](k8s-test-runner/k8s-test-runner.md) + - [Havoc](./libs/havoc.md) + - [Seth](./libs/seth.md) + - [AWS Secrets Manager]() - [Sentinel](./libs/sentinel.md) - --- -- [Releasing modules](releasing_modules.md) +- [Legacy]() + - [Overview](./legacy.md) + - [K8s Test Runner](k8s-test-runner/k8s-test-runner.md) --- -- [CTFv1 (Discouraged)](lib.md) - - [Blockchain](lib/blockchain.md) - - [Concurrency](lib/concurrency.md) - - [Client](lib/client.md) - - [Anvil]() - - [AWS Secrets Manager](lib/client/aws_secrets_manager.md) - - [Github](lib/client/github.md) - - [Grafana](lib/client/grafana.md) - - [Kafka](lib/client/kafka.md) - - [Loki](lib/client/loki.md) - - [MockServer](lib/client/mockserver.md) - - [Postgres](lib/client/postgres.md) - - [Prometheus](lib/client/prometheus.md) - - [Kubernetes](lib/k8s_new/overview.md) - - [Creating environments](lib/k8s_new/environments.md) - - [Using remote runner](lib/k8s_new/remote_runner.md) - - [Passing test secrets](lib/k8s_new/test_secrets.md) - - [chain.link labels](lib/k8s/labels.md) - - [Kubernetes (legacy docs)](lib/k8s/KUBERNETES.md) - - [K8s Remote Run](lib/k8s/REMOTE_RUN.md) - - [K8s Tutorial](lib/k8s/TUTORIAL.md) - - [Config](lib/config/config.md) - - [CRIB Connector](lib/crib.md) - - [Docker](lib/docker/overview.md) - - [Blockchain nodes](lib/docker/blockchain_nodes.md) - - [Chainlink ecosystem](lib/docker/chainlink_ecosystem.md) - - [Third party apps]() - - [Test helpers](lib/docker/test_helpers.md) - - [Logging](lib/logging.md) +- [Releasing modules](releasing_modules.md) --- diff --git a/book/src/developing.md b/book/src/developing.md index 629accedf..76cee74da 100644 --- a/book/src/developing.md +++ b/book/src/developing.md @@ -5,4 +5,4 @@ Here we describe good practices for developing components for our framework. Rules for components are simple: - Component should declare some `Input` and an optional `Output` (we use that so we can skip or cache any component results) - Components should be isolated, they should not return anything except basic types like `int`, `string`, `maps` or `structs` -- Component **must** have documentation under [Components](./framework/components/overview.md), here is an [example](./framework/components/chainlink/node.md) \ No newline at end of file +- Component **must** have documentation under [Components](./framework/components/overview.md), here is an [example](./framework/components/chainlink/nodeset.md) \ No newline at end of file diff --git a/book/src/framework/nodeset_environment_import.md b/book/src/framework/cl_node_keys_import.md similarity index 91% rename from book/src/framework/nodeset_environment_import.md rename to book/src/framework/cl_node_keys_import.md index d26e98206..a514b073f 100644 --- a/book/src/framework/nodeset_environment_import.md +++ b/book/src/framework/cl_node_keys_import.md @@ -1,5 +1,6 @@ -# NodeSet Environment (Import Keys) +# CL Node Keys Import If your tests are designed to run not just within a Docker environment but also with external infrastructure, and you want to reuse the test logic across different environments or securely store private keys, refer to the example below. [Import Keys Example](https://github.com/smartcontractkit/chainlink-testing-framework/blob/main/framework/examples/myproject/smoke_import_keys_test.go) + diff --git a/book/src/framework/components/chainlink/node.md b/book/src/framework/components/chainlink/node.md deleted file mode 100644 index eba6e5e92..000000000 --- a/book/src/framework/components/chainlink/node.md +++ /dev/null @@ -1,112 +0,0 @@ -# Node - -Here we provide *full* configuration parameters for `Node` - -
-Here we provide full configuration reference, if you want to copy and run it, please remove all .out fields before! -
- - -## Configuration -```toml -[cl_node] - - [cl_node.db] - # PostgreSQL image version and tag - image = "postgres:12.0" - # Pulls the image every time if set to 'true', used like that in CI. Can be set to 'false' to speed up local runs - pull_image = false - - [cl_node.node] - # custom ports that plugins may need to expose and map to the host machine - custom_ports = [14000, 14001] - # A list of paths to capability binaries - capabilities = ["./capability_1", "./capability_2"] - # Default capabilities directory inside container - capabilities_container_dir = "/usr/local/bin" - # Image to use, you can either provide "image" or "docker_file" + "docker_ctx" fields - image = "public.ecr.aws/chainlink/chainlink:v2.17.0" - # Path to your Chainlink Dockerfile - docker_file = "../../core/chainlink.Dockerfile" - # Path to docker context that should be used to build from - docker_ctx = "../.." - # Optional name for image we build, default is "ctftmp" - docker_image_name = "ctftmp" - # Pulls the image every time if set to 'true', used like that in CI. Can be set to 'false' to speed up local runs - pull_image = false - # Overrides Chainlink node TOML configuration - # can be multiline, see example - user_config_overrides = """ - [Log] - level = 'info' - """ - # Overrides Chainlink node secrets TOML configuration - # you can only add fields, overriding existing fields is prohibited by Chainlink node - user_secrets_overrides = """ - [AnotherSecret] - mySecret = 'a' - """ - - [cl_node.node.env_vars] - MY_FIRST_ENV_VAR = "env var value" - - # Outputs are the results of deploying a component that can be used by another component - [cl_node.out] - # If 'use_cache' equals 'true' we skip component setup when we run the test and return the outputs - use_cache = true - # Describes deployed or external Chainlink node - [cl_node.out.node] - # API user name - api_auth_user = 'notreal@fakeemail.ch' - # API password - api_auth_password = 'fj293fbBnlQ!f9vNs' - # Host Docker URLs the test uses - # in case of using external component you can replace these URLs with another deployment - p2p_url = "http://127.0.0.1:32812" - url = "http://127.0.0.1:32847" - - # Describes deployed or external Chainlink node - [cl_node.out.postgresql] - # PostgreSQL connection string - # in case of using external database can be overriden - url = "postgresql://chainlink:thispasswordislongenough@127.0.0.1:32846/chainlink?sslmode=disable" -``` - -## Usage -```golang -package yourpackage_test - -import ( - "fmt" - "github.com/smartcontractkit/chainlink-testing-framework/framework" - "github.com/smartcontractkit/chainlink-testing-framework/framework/components/blockchain" - "github.com/smartcontractkit/chainlink-testing-framework/framework/components/clnode" - "github.com/stretchr/testify/require" - "testing" -) - -type Step2Cfg struct { - BlockchainA *blockchain.Input `toml:"blockchain_a" validate:"required"` - CLNode *clnode.Input `toml:"cl_node" validate:"required"` -} - -func TestMe(t *testing.T) { - in, err := framework.Load[Step2Cfg](t) - require.NoError(t, err) - - bc, err := blockchain.NewBlockchainNetwork(in.BlockchainA) - require.NoError(t, err) - - networkCfg, err := clnode.NewNetworkCfgOneNetworkAllNodes(bc) - require.NoError(t, err) - in.CLNode.Node.TestConfigOverrides = networkCfg - - output, err := clnode.NewNodeWithDB(in.CLNode) - require.NoError(t, err) - - t.Run("test something", func(t *testing.T) { - fmt.Printf("node url: %s\n", output.Node.ExternalURL) - require.NotEmpty(t, output.Node.ExternalURL) - }) -} -``` \ No newline at end of file diff --git a/book/src/framework/components/chainlink/nodeset.md b/book/src/framework/components/chainlink/nodeset.md deleted file mode 100644 index 6f0fb77b9..000000000 --- a/book/src/framework/components/chainlink/nodeset.md +++ /dev/null @@ -1,163 +0,0 @@ -# NodeSet - -Here we provide *full* configuration parameters for `NodeSet` - -
-Here we provide full configuration reference, if you want to copy and run it, please remove all .out fields before! -
- -## Configuration - -This component requires some Blockchain to be deployed, add this to config -```toml -[blockchain_a] - # Blockchain node type, can be "anvil" or "geth" - type = "anvil" - # Chain ID - chain_id = "1337" - # Anvil command line params, ex.: docker_cmd_params = ['--block-time=1', '...'] - docker_cmd_params = [] - # Docker image and tag - image = "ghcr.io/foundry-rs/foundry:stable" - # External port to expose - port = "8545" - # Pulls the image every time if set to 'true', used like that in CI. Can be set to 'false' to speed up local runs - pull_image = false - - # Outputs are the results of deploying a component that can be used by another component - [blockchain_a.out] - chain_id = "1337" - # If 'use_cache' equals 'true' we skip component setup when we run the test and return the outputs - use_cache = true - - [[blockchain_a.out.nodes]] - # URLs to access the node(s) inside docker network, used by other components - internal_http_url = "http://anvil-14411:8545" - internal_ws_url = "ws://anvil-14411:8545" - # URLs to access the node(s) on your host machine or in CI - http_url = "http://127.0.0.1:33955" - ws_url = "ws://127.0.0.1:33955" -``` - -Then configure NodeSet -```toml -[[nodesets]] - # unique NodeSet name - name = "don" - # amount of Chainlink nodes to spin up - nodes = 5 - # Override mode: can be "all" or "each" - # defines how we override configs, either we apply first node fields to all of them - # or we define each node custom configuration (used in compatibility testing) - override_mode = "all" - # HTTP API port range start, each new node get port incremented (host machine) - http_port_range_start = 10000 - # P2P API port range start, each new node get port incremented (host machine) - p2p_port_range_start = 12000 - - [nodesets.db] - # PostgreSQL image version and tag - image = "postgres:12.0" - # Pulls the image every time if set to 'true', used like that in CI. Can be set to 'false' to speed up local runs - pull_image = false - # PostgreSQL volume name - volume_name = "" - - [[nodesets.node_specs]] - - [nodesets.node_specs.node] - # custom ports that plugins may need to expose and map to the host machine - custom_ports = [14000, 14001] - # A list of paths to capability binaries - capabilities = ["./capability_1", "./capability_2"] - # Default capabilities directory inside container - capabilities_container_dir = "/usr/local/bin" - # Image to use, you can either provide "image" or "docker_file" + "docker_ctx" fields - image = "public.ecr.aws/chainlink/chainlink:v2.17.0" - # Path to your Chainlink Dockerfile - docker_file = "../../core/chainlink.Dockerfile" - # Path to docker context that should be used to build from - docker_ctx = "../.." - # Optional name for image we build, default is "ctftmp" - docker_image_name = "ctftmp" - # Pulls the image every time if set to 'true', used like that in CI. Can be set to 'false' to speed up local runs - pull_image = false - # Overrides Chainlink node TOML configuration - # can be multiline, see example - user_config_overrides = """ - [Log] - level = 'info' - """ - # Overrides Chainlink node secrets TOML configuration - # you can only add fields, overriding existing fields is prohibited by Chainlink node - user_secrets_overrides = """ - [AnotherSecret] - mySecret = 'a' - """ - - # Outputs are the results of deploying a component that can be used by another component - [nodesets.out] - # If 'use_cache' equals 'true' we skip component setup when we run the test and return the outputs - use_cache = true - - # Describes deployed or external Chainlink nodes - [[nodesets.out.cl_nodes]] - use_cache = true - - # Describes deployed or external Chainlink node - [nodesets.out.cl_nodes.node] - # API user name - api_auth_user = 'notreal@fakeemail.ch' - # API password - api_auth_password = 'fj293fbBnlQ!f9vNs' - # Host Docker URLs the test uses - # in case of using external component you can replace these URLs with another deployment - p2p_url = "http://127.0.0.1:32996" - url = "http://127.0.0.1:33096" - # Describes PostgreSQL instance - [nodesets.out.cl_nodes.postgresql] - # PostgreSQL connection string - # in case of using external database can be overriden - url = "postgresql://chainlink:thispasswordislongenough@127.0.0.1:33094/chainlink?sslmode=disable" - - # Can have more than one node, fields are the same, see above ^^ - [[nodesets.out.cl_nodes]] - [nodesets.out.cl_nodes.node] - [nodesets.out.cl_nodes.postgresql] - ... -``` - -## Usage -```golang -package capabilities_test - -import ( - "github.com/smartcontractkit/chainlink-testing-framework/framework" - "github.com/smartcontractkit/chainlink-testing-framework/framework/components/blockchain" - ns "github.com/smartcontractkit/chainlink-testing-framework/framework/components/simple_node_set" - "github.com/stretchr/testify/require" - "testing" -) - -type Config struct { - BlockchainA *blockchain.Input `toml:"blockchain_a" validate:"required"` - NodeSets []*ns.Input `toml:"nodesets" validate:"required"` -} - -func TestMe(t *testing.T) { - in, err := framework.Load[Config](t) - require.NoError(t, err) - - bc, err := blockchain.NewBlockchainNetwork(in.BlockchainA) - require.NoError(t, err) - out, err := ns.NewSharedDBNodeSet(in.NodeSet, bc) - require.NoError(t, err) - - t.Run("test something", func(t *testing.T) { - for _, n := range out.CLNodes { - require.NotEmpty(t, n.Node.ExternalURL) - require.NotEmpty(t, n.Node.HostP2PURL) - } - }) -} -``` \ No newline at end of file diff --git a/book/src/framework/components/mocking.md b/book/src/framework/components/mocking.md index 8664edff3..90c16c89f 100644 --- a/book/src/framework/components/mocking.md +++ b/book/src/framework/components/mocking.md @@ -1,4 +1,4 @@ -# Faking Services +# Fake Services The framework aims to equip you with all the necessary tools to write end-to-end system-level tests, while still allowing the flexibility to fake third-party services that are not critical to your testing scope. diff --git a/book/src/framework/components/overview.md b/book/src/framework/components/overview.md index baf3f64a5..562669b88 100644 --- a/book/src/framework/components/overview.md +++ b/book/src/framework/components/overview.md @@ -3,6 +3,6 @@ CTF contains a lot of useful components, some of them are off-chain services like `Chainlink Node`, `NodeSet` CTF contains three groups of components: -- Off-chain services like `CL Node`, `NodeSet`, `JobDistributor`, etc. +- Off-chain services like `Chainlink NodeSet` - On-chain wrappers for [chainlink-deployments](https://github.com/smartcontractkit/chainlink-deployments) repository - Test components, blockchain simulators, fakes, etc diff --git a/book/src/framework/getting_started.md b/book/src/framework/getting_started.md index a6f245d90..028d42aab 100644 --- a/book/src/framework/getting_started.md +++ b/book/src/framework/getting_started.md @@ -1,13 +1,13 @@ # 🚀 Getting started ## Prerequisites -- `Docker` [OrbStack](https://orbstack.dev/) or [Docker Desktop](https://www.docker.com/products/docker-desktop/), we recommend OrbStack (faster, smaller memory footprint) +- [Docker Desktop](https://www.docker.com/products/docker-desktop/) - [Golang](https://go.dev/doc/install) Tested with ``` +Docker Desktop version 4.36.0 Docker version 27.3.1 -OrbStack Version: 1.8.2 (1080200) ``` ## Test setup diff --git a/book/src/framework/nodeset_compatibility.md b/book/src/framework/nodeset_compatibility.md deleted file mode 100644 index 6c4866842..000000000 --- a/book/src/framework/nodeset_compatibility.md +++ /dev/null @@ -1,64 +0,0 @@ -# Chainlink Node Set Compatibility Testing Environment - -The difference between this and [basic node set configuration](nodeset_environment.md) is that here you can provide any custom configuration for CL nodes. - -Create a configuration file `smoke.toml` -```toml -[blockchain_a] - type = "anvil" - docker_cmd_params = ["-b", "1"] - -[[nodesets]] - name = "don" - nodes = 5 - override_mode = "each" - - [nodesets.db] - image = "postgres:12.0" - - [[nodesets.node_specs]] - - [nodesets.node_specs.node] - image = "public.ecr.aws/chainlink/chainlink:v2.17.0" - user_config_overrides = " [Log]\n level = 'info'\n " - user_secrets_overrides = "" - - [[nodesets.node_specs]] - - [nodesets.node_specs.node] - image = "public.ecr.aws/chainlink/chainlink:v2.17.0" - user_config_overrides = " [Log]\n level = 'info'\n " - user_secrets_overrides = "" - - [[nodesets.node_specs]] - - [nodesets.node_specs.node] - image = "public.ecr.aws/chainlink/chainlink:v2.17.0" - user_config_overrides = " [Log]\n level = 'info'\n " - user_secrets_overrides = "" - - [[nodesets.node_specs]] - - [nodesets.node_specs.node] - image = "public.ecr.aws/chainlink/chainlink:v2.17.0" - user_config_overrides = " [Log]\n level = 'info'\n " - user_secrets_overrides = "" - - [[nodesets.node_specs]] - - [nodesets.node_specs.node] - image = "public.ecr.aws/chainlink/chainlink:v2.17.0" - user_config_overrides = " [Log]\n level = 'info'\n " - user_secrets_overrides = "" -``` - -You can reuse `smoke_test.go` from previous [setup](nodeset_environment.md) - -Run it -```bash -CTF_CONFIGS=smoke.toml go test -v -run TestNodeSet -``` - -Summary: -- We deployed fully-fledged set of Chainlink nodes connected to some blockchain and faked external data provider -- We understood how we can test different versions of Chainlink nodes for compatibility and override configs diff --git a/book/src/framework/observability/observability_stack.md b/book/src/framework/observability/observability_stack.md index c7ba0384e..85438b85b 100644 --- a/book/src/framework/observability/observability_stack.md +++ b/book/src/framework/observability/observability_stack.md @@ -14,13 +14,6 @@ ctf obs down Read more about how to check [logs](logs.md) and [profiles](profiling.md) -## Helpful links for your local setup - -- [Loki](http://localhost:3000/explore?panes=%7B%22qZw%22:%7B%22datasource%22:%22P8E80F9AEF21F6940%22,%22queries%22:%5B%7B%22refId%22:%22A%22,%22expr%22:%22%22,%22queryType%22:%22range%22,%22datasource%22:%7B%22type%22:%22loki%22,%22uid%22:%22P8E80F9AEF21F6940%22%7D%7D%5D,%22range%22:%7B%22from%22:%22now-6h%22,%22to%22:%22now%22%7D%7D%7D&schemaVersion=1&orgId=1) -- [Prometheus](http://localhost:3000/explore?panes=%7B%22qZw%22:%7B%22datasource%22:%22PBFA97CFB590B2093%22,%22queries%22:%5B%7B%22refId%22:%22A%22,%22expr%22:%22%22,%22range%22:true,%22datasource%22:%7B%22type%22:%22prometheus%22,%22uid%22:%22PBFA97CFB590B2093%22%7D%7D%5D,%22range%22:%7B%22from%22:%22now-6h%22,%22to%22:%22now%22%7D%7D%7D&schemaVersion=1&orgId=1) -- [PostgreSQL](http://localhost:3000/d/000000039/postgresql-database?orgId=1&refresh=10s&var-DS_PROMETHEUS=PBFA97CFB590B2093&var-interval=$__auto_interval_interval&var-namespace=&var-release=&var-instance=postgres_exporter_0:9187&var-datname=All&var-mode=All&from=now-5m&to=now) -- [Pyroscope](http://localhost:4040) - ## Developing Change compose files under `framework/cmd/observability` and restart the stack (removing volumes too) diff --git a/book/src/framework/overview.md b/book/src/framework/overview.md index 823011f7e..c41108521 100644 --- a/book/src/framework/overview.md +++ b/book/src/framework/overview.md @@ -17,6 +17,3 @@ It enables tests to run in any environment and serves as a single source of trut - **Caching**: Any component can use cached configs to skip setup for even faster test development. - **Integrated observability stack**: get all the info you need to develop end-to-end tests: metrics, logs, traces, profiles. - -###### * If all the images are cached, you are using [OrbStack](https://orbstack.dev/) with M1/M2/M3 chips and have at least 8CPU dedicated to Docker - diff --git a/book/src/k8s-test-runner/k8s-test-runner.md b/book/src/k8s-test-runner/k8s-test-runner.md index 758597092..f8dce0708 100644 --- a/book/src/k8s-test-runner/k8s-test-runner.md +++ b/book/src/k8s-test-runner/k8s-test-runner.md @@ -1,5 +1,9 @@ ## Preparing to Run Tests on Staging +
+This is legacy documentation and code, it'd be sunset on next release +
+ Ensure you complete the following steps before executing tests on the staging environment: 1. **Connect to the VPN** diff --git a/book/src/legacy.md b/book/src/legacy.md new file mode 100644 index 000000000..cf5ba8e24 --- /dev/null +++ b/book/src/legacy.md @@ -0,0 +1,3 @@ +# Legacy + +This chapter contains legacy documentation or code examples that will be sunset in the next release diff --git a/book/src/lib/blockchain.md b/book/src/lib/blockchain.md deleted file mode 100644 index 5100655fa..000000000 --- a/book/src/lib/blockchain.md +++ /dev/null @@ -1,45 +0,0 @@ -# Blockchain Clients - -
- -This documentation is deprecated, we are using it in [Chainlink Integration Tests](https://github.com/smartcontractkit/chainlink/tree/develop/integration-tests) - -If you want to test our new products use [v2](../framework/overview.md) -
- -This folder contains the bulk of code that handles integrating with different EVM chains. If you're looking to run tests on a new EVM chain, and are having issues with the default implementation, you've come to the right place. - -### Some Terminology - -- [L2 Chain](https://ethereum.org/en/layer-2/): A Layer 2 chain "branching" off Ethereum. -- [EVM](https://ethereum.org/en/developers/docs/evm/): Ethereum Virtual Machine that underpins the Ethereum blockchain. -- [EVM Compatible](https://blog.thirdweb.com/evm-compatible-blockchains-and-ethereum-virtual-machine/#:~:text=What%20does%20'EVM%20compatibility'%20mean,significant%20changes%20to%20their%20code.): A chain that has some large, underlying differences from how base Ethereum works, but can still be interacted with largely the same way as Ethereum. -- [EIP-1559](https://eips.ethereum.org/EIPS/eip-1559): The Ethereum Improvement Proposal that changed how gas fees are calculated and paid on Ethereum -- Legacy Transactions: Transactions that are sent using the old gas fee calculation method, the one used before EIP-1559. -- Dynamic Fee Transaction: Transactions that are sent using the new gas fee calculation method, the one used after EIP-1559. - -## How Client Integrations Work - -In order to test Chainlink nodes, the `chainlink-testing-framework` needs to be able to interact with the chain that the node is running on. This is done through the `blockchain.EVMClient` interface. The `EVMClient` interface is a wrapper around [geth](https://geth.ethereum.org/) to interact with the blockchain. We conduct all our testing blockchain operations through this wrapper, like sending transactions and monitoring on-chain events. The primary implementation of this wrapper is built for [Ethereum](./ethereum.go). Most others, like the [Metis](./metis.go) and [Optimism](./optimism.go) integrations, extend and modify the base Ethereum implementation. - -## Do I Need a New Integration? - -If you're reading this, probably. The default EVM integration is designed to work with mainnet Ethereum, which covers most other EVM chain interactions, but it's not guaranteed to work with all of them. If you're on a new chain and the test framework is throwing errors while doing basic things like send transactions, receive new headers, or deploy contracts, you'll likely need to create a new integration. The most common issue with new chains (especially L2s) is gas estimations and lack of support for dynamic transactions. - -## Creating a New Integration - -Take a look at the [Metis](./metis.go) integration as an example. Metis is an L2, EVM compatible chain. It's largely the same as the base Ethereum integration, so we'll extend from that. - -```go -type MetisMultinodeClient struct { - *EthereumMultinodeClient -} - -type MetisClient struct { - *EthereumClient -} -``` - -Now we need to let other libraries (like our tests in the main Chainlink repo) that this integration exists. So we add the new implementation to the [known_networks.go](https://github.com/smartcontractkit/chainlink-testing-framework/blob/main/lib/blockchain/known_networks.go) file. We can then add that network to our tests' own [known_networks.go](https://github.com/smartcontractkit/chainlink/blob/develop/integration-tests/known_networks.go) file (it's annoying, there are plans to simplify). - -Now our Metis integration is the exact same as our base Ethereum one, which doesn't do us too much good. I'm assuming you came here to make some changes, so first let's find out what we need to change. This is a mix of reading developer documentation on the chain you're testing and trial and error. Mostly the latter in later stages. In the case of Metis, like many L2s, they [have their own spin on gas fees](https://docs.metis.io/dev/protocol-in-detail/transaction-fees-on-the-metis-platform). They also only support Legacy transactions. So we'll need to override any methods that deal with gas estimations, `Fund`, `DeployContract`, and `ReturnFunds`. diff --git a/book/src/lib/client.md b/book/src/lib/client.md deleted file mode 100644 index cc46e7190..000000000 --- a/book/src/lib/client.md +++ /dev/null @@ -1,11 +0,0 @@ -# Client - -We support a variety of clients that ease communication with following applications: -* [Anvil](./client/anvil.md) -* [AWS Secrets Manager](./client/aws_secrets_manager.md) -* [Github](./client/github.md) -* [Kafka](./client/kafka.md) -* [Loki](./client/loki.md) -* [MockServer](./client/mockserver.md) -* [Postgres](./client/postgres.md) -* [Prometheus](./client/prometheus.md) \ No newline at end of file diff --git a/book/src/lib/client/aws_secrets_manager.md b/book/src/lib/client/aws_secrets_manager.md deleted file mode 100644 index 54cf899d8..000000000 --- a/book/src/lib/client/aws_secrets_manager.md +++ /dev/null @@ -1,33 +0,0 @@ -# AWS Secrets Manager - -This simple client makes it even easier to: -* read -* create -* remove secrets from AWS Secrets Manager. - -Creating a new instance is straight-forward. You should either use environment variables or shared configuration and credentials. - -> [!NOTE] -> Environment variables take precedence over shared credentials. - -## Using environment variables -You can pass required configuration as following environment variables: -* `AWS_ACCESS_KEY_ID` -* `AWS_SECRET_ACCESS_KEY` -* `AWS_REGION` - -## Using shared credentials -If you have shared credentials stored in `.aws/credentials` file, then the easiest way to configure the client is by setting -`AWS_PROFILE` environment variable with the profile name. If that environment variable is not set, the SDK will try to use default profile. - -> [!WARNING] -> Remember, that most probably you will need to manually create a new session for that profile before running your application. - - -> [!NOTE] -> You can read more about configuring the AWS SDK [here](https://docs.aws.amazon.com/sdk-for-go/v1/developer-guide/configuring-sdk.html). - -Once you have an instance of AWS Secrets Manager you gain access to following functions: -* `CreateSecret(key string, val string, override bool) error` -* `GetSecret(key string) (AWSSecret, error)` -* `RemoveSecret(key string, noRecovery bool) error` \ No newline at end of file diff --git a/book/src/lib/client/github.md b/book/src/lib/client/github.md deleted file mode 100644 index aab94058f..000000000 --- a/book/src/lib/client/github.md +++ /dev/null @@ -1,28 +0,0 @@ -# Github - -This small client makes it easy to get `N` latest releases or tags from any Github.com repository. To use it, all you need to have -is a properly scoped access token. - -```go -publicRepoClient := NewGithubClient(WITHOUT_TOKEN) - -// "smartcontractkit", "chainlink" -latestCLReleases, err := publicRepoClient.ListLatestCLCoreReleases(10) -if err != nil { - panic(err) -} - -// "smartcontractkit", "chainlink" -latestCLTags, err := publicRepoClient.ListLatestCLCoreTags(10) -if err != nil { - panic(err) -} - -privateRepoClient := NewGithubClient("my-secret-PAT") -myLatestReleases, err := privateRepoClient.ListLatestReleases("my-org", "my-private-repo", 5) -if err != nil { - panic(err) -} -``` - -There's really not much more to it... \ No newline at end of file diff --git a/book/src/lib/client/grafana.md b/book/src/lib/client/grafana.md deleted file mode 100644 index c66f4329c..000000000 --- a/book/src/lib/client/grafana.md +++ /dev/null @@ -1,80 +0,0 @@ -# Grafana - -> [!NOTE] -> Contrary to other clients, you will find Grafana client in a [separate package & go module](https://github.com/smartcontractkit/chainlink-testing-framework/tree/main/lib/grafana). - -Grafana client encapsulate following functionalities: -* Dashboard creation -* Managing dashboard annotations (CRUD) -* Checking alerts - -# New instance -In order to create a new instance you will need: -* URL -* API token - -For example: -```go -url := "http://grafana.io" -apiToken := "such-a-secret-1&11n" -gc := NewGrafanaClient(url, apiToken) -``` - -# Dashboard creation -You can create a new dashboard defined in JSON with: -```go - -//define your dashboard here -dashboardJson := `` - -request := PostDashboardRequest { - Dashboard: dashboardJson, - FolderId: 5 // change to your folder id -} - -dr, rawResponse, err := gc.PostDashboard(request) -if err != nil { - panic(err) -} - -if rawResponse.StatusCode() != 200 { - panic("response code wasn't 200, but " + rawResponse.StatusCode()) -} - -fmt.Println("Dashboard slug is is " + *dr.Slug) -``` - -# Posting annotations -You can post annotations in a following way: -```go -annDetails := PostAnnotation { - DashboardUID: "some-uid", - Time: time.Now(), - TimeEnd: time.Now().Add(1 * time.Second) - Text: "my test annotation" -} - -r, rawResponse, err := gc.PostAnnotation(annDetails) -if rawResponse.StatusCode() != 200 { - panic("response code wasn't 200, but " + rawResponse.StatusCode()) -} - -fmt.Println("Created annotation with id: " + r.Id) - -``` - -# Checking alerts -You can check alerts firing for a dashboard with UID: -```go -alerts, rawResponse, err := gc.AlertRulerClient.GetAlertsForDashboard("some-uid") -if rawResponse.StatusCode() != 200 { - panic("response code wasn't 200, but " + rawResponse.StatusCode()) -} - -for name, value := range alerts { - fmt.Println("Alert named " + name + "was triggered. Details: " + string(value)) -} -``` - -# Troubleshooting -To enable debug mode for the underlaying HTTP client set `RESTY_DEBUG` environment variable to `true`. \ No newline at end of file diff --git a/book/src/lib/client/kafka.md b/book/src/lib/client/kafka.md deleted file mode 100644 index 308e28415..000000000 --- a/book/src/lib/client/kafka.md +++ /dev/null @@ -1,19 +0,0 @@ -# Kafka - -This is a wrapper over HTTP client that can only return a list of topics from Kafka instance. - -```go -client, err := NewKafkaRestClient(&NewKafkaRestClient{URL: "my-kafka-url"}) -if err != nil { - panic(err) -} - -topis, err := client.GetTopics() -if err != nil { - panic(err) -} - -for _, topic := range topics { - fmt.Println("topic: " + topic) -} -``` \ No newline at end of file diff --git a/book/src/lib/client/loki.md b/book/src/lib/client/loki.md deleted file mode 100644 index bf5f8130e..000000000 --- a/book/src/lib/client/loki.md +++ /dev/null @@ -1,63 +0,0 @@ -# Loki - -Loki client simplifies querying of Loki logs with `LogQL`. - -The way it's designed now implies that: -* you need to create a new client instance for each query -* query results are returned as `string` - -## New instance -To create a new instance you to provide the following at the very least: -* Loki URL -* query to execute -* time range - -```go -// scheme is required -lokiUrl := "http://loki-host.io" -// can be empty -tenantId := "promtail" -basicAuth := LokiBasicAuth{ - Login: "admin", - Password: "oh-so-secret", -} -queryParams := LokiQueryParams{ - Query: "quantile_over_time(0.5, {name='my awesome app'} | json| unwrap duration [10s]) by name", - StartTime: time.Now().Add(1 * time.Hour), - EndTime: time.Now(), - Limit: 1000, -} -lokiClient := client.NewLokiClient(lokiUrl, tenantId, basicAuth, queryParams) -``` - -If your instance doesn't have basic auth you should use an empty string: -```go -basicAuth := LokiBasicAuth{} -``` - -## Executing a query -Once you have the client instance created you can execute the query with: -```go -ctx, cancelFn := context.WithTimeout(context.Background, 3 * time.Minute) -defer cancelFn() -results, err := lokiClient.QueryLogs(ctx) -if err != nil { - panic(err) -} - -for _, logEntry := range results { - fmt.Println("At " + logEntry.Timestamp + " found following log: " + logEntry.Log) -} -``` - -## Log entry types -Loki can return various data types in responses to queries. We will try to convert the following ones to `string`: -* `int` -* `float64` - -If it's neither of these types nor a `string` the client will return an error. Same will happen if `nil` is returned. - -# Troubleshooting -If you find yourself in trouble these two environment variables might help you: -* `RESTY_DEBUG` set to `true` will enable debug mode for the underlaying HTTP client -* `LOKI_CLIENT_LOG_LEVEL` controls log level of Loki client (for supported log levels check [logging package](../logging.md) documentation) \ No newline at end of file diff --git a/book/src/lib/client/mockserver.md b/book/src/lib/client/mockserver.md deleted file mode 100644 index 25f2c8a24..000000000 --- a/book/src/lib/client/mockserver.md +++ /dev/null @@ -1,120 +0,0 @@ -# MockServer - -This client is reponsible for simplifying interaction with MockServer during test execution (e.g. to dynamically create or modify mocked responses). - -## Initialising -There are three ways of intializing the client: -* providing the URL -* providing pointer to `Environment` (CTF's representation of a k8s environment) -* providing pointer to raw `MockserverConfig` - -```go -var myk8sEnv *environment.Environment - -// ... create k8s environment - -k8sMockserver := ConnectMockServer(myk8sEnv) - -mockServerUrl := "http://my.mockserver.instance.io" -myMockServer := ConnectMockServerURL(mockServerUrl) - -customConfig := &MockserverConfig{ - LocalURL: mockServerUrl, - ClusterURL: mockServerUrl, - Headers: map[string]string{"x-secret-auth-header": "such-a-necessary-auth-header"}, -} - -myCustomMockServerClient := NewMockserverClient(customConfig) -``` - -In most cases you should initialise it with `*environment.Environment`, unless you need to pass custom headers to make the connection possible. - -## Typical usages -### Arbitrary mock -You can set any desired behaviour by using `PutExpectations(body interface{}) error` funciton, where the body should be a JSON string conforming to -MockServer's format that consists of a request matcher and corresponding action. For example: -```go -var myk8sEnv *environment.Environment - -returnOk := `{ - "httpRequest": { - "method": "GET", - "path": "/status" - }, - "httpResponse": { - "statusCode": 200, - "body": { - "message": "Service is running" - } - } -}` - -ms := ConnectMockServer(myk8sEnv) -err := ms.PutExpectations(returnOk) -if err != nil { - panic(err) -} -``` - -> [!NOTE] -> You can read more about expecations syntax, including OpenAPI v3 or dynamic expecations with JavaScript [here](https://www.mock-server.com/mock_server/creating_expectations.html). - -### Returning a random integer -To return random integer in the response body, together with `200` status code for all requests with a given path, use: -```go -err := ms.SetRandomValuePath("/api/v2/my_endpoint") -if err != nil { - panic(err) -} -``` - -### Returning a static integer -To return a static integer in the response body, together with `200` status code for all requests with a given path, use: -```go -err := ms.SetValuePath("/api/v2/my_endpoint") -if err != nil { - panic(err) -} -``` - -### Returning a static string -To return a static string in the response body, together with `200` status code for all requests with a given path, use: -```go -err := ms.SetStringValuePath("/api/v2/my_endpoint", "oh-my") -if err != nil { - panic(err) -} -``` - -### Returning arbitrary data -To return arbitrary data in the response body, together with `200` status code for all requests with a given path, use: -```go -err := ms.SetAnyValueResponse("/api/v2/my_endpoint", []int{1, 2, 3}) -if err != nil { - panic(err) -} -``` - -### Returning arbitrary response from external adatper -To mock a response for Chainlink's external adapter for all requests with a given path, use: -```go -var mockedResult interface{} -mockedResult = 5 -err := ms.SetAnyValuePath("/api/v2/my_endpoint", mockedResult) -if err != nil { - panic(err) -} -``` - -It will return a response with following structure: -```json -{ - "id": "", - "data": { - "result": 5 - } -} -``` - -# Troubleshooting -Enabling debug mode for the underlaying HTTP client can be achieved by setting `RESTY_DEBUG` environment variable to `true`. \ No newline at end of file diff --git a/book/src/lib/client/postgres.md b/book/src/lib/client/postgres.md deleted file mode 100644 index 5032e1219..000000000 --- a/book/src/lib/client/postgres.md +++ /dev/null @@ -1,44 +0,0 @@ -# Postgres Connector - -Postgres Connect simplifies connecting to a Postgres DB, by either: -* providing it full db config -* providing it a pointer to `enviroment.Environment` - -# Arbitrary DB - -```go -config := &PostgresConfig{ - Host: "db-host.io", - Port: 5432, - User: "admin", - Password: "oh-so-secret", - DBName: "database", - //SSLMode: -} -connector, err := NewPostgresConnector(config) -if err != nil { - panic(err) -} -``` - -If no `SSLMode` is supplied it will default to `sslmode=disable`. - -# K8s -This code assumes it connects to k8s enviroment created with the CTF, where each Chainlink Node -has an underlaying Postgres DB instance using CTF's default configuration: -```go -var myk8sEnv *environment.Environment - -// ... create k8s environment - -node0PgClient, node0err := ConnectDB(0, myk8sEnv) -if node0err != nil { - panic(node0err) -} - -node1PgClient, node1err := ConnectDB(1, myk8sEnv) -if node1err != nil { - panic(node1err) -} -``` - diff --git a/book/src/lib/client/prometheus.md b/book/src/lib/client/prometheus.md deleted file mode 100644 index 32d05bc00..000000000 --- a/book/src/lib/client/prometheus.md +++ /dev/null @@ -1,44 +0,0 @@ -# Prometheus - -This client is basically a wrapper over the official Prometheus client that gas three usesful functions: -* new client creation -* fetching of all firing alerts -* executing an arbitrary query with time range of `(-infinity, now)` - -## New instance -```go -c, err := NewPrometheusClient("prometheus.io") -if err != nil { - panic(err) -} -``` - -## Get all firing alerts -```go -alerts, err := c.GetAlerts() -if err != nil { - panic(err) -} - -for _, alert := range alerts { - fmt.Println("Found alert: " + alert.Value + "in state: " + alert.AlertState) -} -``` - -## Execute a query -```go -queryResult, err := c.GetQuery(`100 - (avg by (instance) (irate(node_cpu_seconds_total{mode="idle"}[2m])) * 100)`) -if err != nil { - panic(err) -} - -if asV, ok := queryResult.(.model.Vector); ok { - for _, v := range asV { - fmt.Println("Metric data: " +v.Metric) - fmt.Println("Value: " + v.Value) - } -} else { - panic(fmt.Sprintf("Result wasn't a model.Vector, but %T", queryResult)) -} - -``` \ No newline at end of file diff --git a/book/src/lib/concurrency.md b/book/src/lib/concurrency.md deleted file mode 100644 index fcf11f48f..000000000 --- a/book/src/lib/concurrency.md +++ /dev/null @@ -1,95 +0,0 @@ -# Concurrency - -It's a small library that simplifies dividing N tasks between X workers with but two options: -* payload -* early exit on first error - -It was created for parallelising chain interaction with [Seth](../libs/seth.md), where strict ordering -and association of a given private key with a specific contract is required. - -> [!NOTE] -> This library is an overkill if all you need to do is to deploy 10 contract, where each deployment -> consists of a single transaction. -> But... if your deployment flow is a multi-stepped one and it's crucial that all operations are executed -> using the same private key (e.g. due to privilleged access) it might be a good pick. Especially, if -> you don't want to extensively test a native `WaitGroup`/`ErrGroup`-based solution. - -## No payload -If the task to be executed requires no payload (or it's the same for each task) using the tool is much simpler. - -First you need to create an instance of the executor: -```go -l := logging.GetTestLogger(nil) - -executor := concurrency.NewConcurrentExecutor[ContractIntstance, contractResult, concurrency.NoTaskType](l) -``` - -Where generic parameters represent (from left to right): -* type of execution result -* type of channel that holds the results -* type of task payload - -In our case, we want the execution to return `ContractInstance`s, that will be stored by this type: -```go -type contractResult struct { - instance ContractIntstance -} -``` - -And which won't use any payload, as indicated by a no-op `concurrency.NoTaskType`. - -Then, we need to define a function that will be executed for each task. For example: -```go -var deployContractFn = func(channel chan contractResult, errorCh chan error, executorNum int) { - keyNum := executorNum + 1 // key 0 is the root key - - instance, err := client.deployContractFromKey(keyNum) - if err != nil { - errorCh <- err - return - } - - channel <- contractResult{instance: instance} -} -``` - -It needs to have the following signature: -```go -type SimpleTaskProcessorFn[ResultChannelType any] func(resultCh chan ResultChannelType, errorCh chan error, executorNum int) -``` -and send results of successful execution to `resultCh` and errors to `errorCh`. - -Once the processing function is defined all that's left is the execution: -```go -results, err := executor.ExecuteSimple(client.getConcurrency(), numberOfContracts, deployContractFn) -``` - -Parameters for `ExecuteSimple` (without payload) are as follows(from left to right): -* concurrency count (number of parallel executors) -* total number of executions -* function to execute - -`results` contain a slice with results of each execution with `ContractInstance` type. -`err` will be non-nil if any of the executions failed. To get all errors you should call `executor.GetErrors()`. - -## With payload -If your tasks need payload, then two things change. - -First, you need to pass task type, when creating the executor instance: -```go -executor := concurrency.NewConcurrentExecutor[ContractIntstance, contractResult, contractConfiguration](l) -``` - -Here, it's set to dummy: -```go -type contractConfiguration struct{} -``` - -Second, the signature of processing function: -```go -type TaskProcessorFn[ResultChannelType, TaskType any] func(resultCh chan ResultChannelType, errorCh chan error, executorNum int, payload TaskType) -``` -Which now includes a forth parameter representing the payload. And that function's implementation (making use of the payload). - -> [!NOTE] -> You can find the full example [here](https://github.com/smartcontractkit/chainlink-testing-framework/blob/main/lib/concurrency/example_test.go). \ No newline at end of file diff --git a/book/src/lib/config/config.md b/book/src/lib/config/config.md deleted file mode 100644 index 38c04672f..000000000 --- a/book/src/lib/config/config.md +++ /dev/null @@ -1,363 +0,0 @@ -# TOML Config - -
- -This documentation is deprecated, we are using it in [Chainlink Integration Tests](https://github.com/smartcontractkit/chainlink/tree/develop/integration-tests) - -If you want to test our new products use [v2](../framework/overview.md) -
- -These basic building blocks can be used to create a TOML config file. For example: - -```golang -import ( - ctf_config "github.com/smartcontractkit/chainlink-testing-framework/config" - ctf_test_env "github.com/smartcontractkit/chainlink-testing-framework/docker/test_env" -) - -type TestConfig struct { - ChainlinkImage *ctf_config.ChainlinkImageConfig `toml:"ChainlinkImage"` - ChainlinkUpgradeImage *ctf_config.ChainlinkImageConfig `toml:"ChainlinkUpgradeImage"` - Logging *ctf_config.LoggingConfig `toml:"Logging"` - Network *ctf_config.NetworkConfig `toml:"Network"` - Pyroscope *ctf_config.PyroscopeConfig `toml:"Pyroscope"` - PrivateEthereumNetwork *ctf_test_env.EthereumNetwork `toml:"PrivateEthereumNetwork"` -} -``` - -It's up to the user to provide a way to read the config from file and unmarshal it into the struct. You can check [testconfig.go](https://github.com/smartcontractkit/chainlink-testing-framework/blob/main/lib/config/examples/testconfig.go) to see one way it could be done. - -`Validate()` should be used to ensure that the config is valid. Some of the building blocks have also a `Default()` method that can be used to get default values. - -Also, you might find `BytesToAnyTomlStruct(logger zerolog.Logger, filename, configurationName string, target any, content []byte) error` utility method useful for unmarshalling TOMLs read from env var or files into a struct - -## Test Secrets - -Test secrets are not stored directly within the `TestConfig` TOML due to security reasons. Instead, they are passed into `TestConfig` via environment variables. Below is a list of all available secrets. Set only the secrets required for your specific tests, like so: `E2E_TEST_CHAINLINK_IMAGE=qa_ecr_image_url`. - -### Default Secret Loading - -By default, secrets are loaded from the `~/.testsecrets` dotenv file. Example of a local `~/.testsecrets` file: - -```bash -E2E_TEST_CHAINLINK_IMAGE=qa_ecr_image_url -E2E_TEST_CHAINLINK_UPGRADE_IMAGE=qa_ecr_image_url -E2E_TEST_ARBITRUM_SEPOLIA_WALLET_KEY=wallet_key -``` - -### All E2E Test Secrets - -| Secret | Env Var | Example | -| ----------------------------- | ------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| Chainlink Image | `E2E_TEST_CHAINLINK_IMAGE` | `E2E_TEST_CHAINLINK_IMAGE=qa_ecr_image_url` | -| Chainlink Upgrade Image | `E2E_TEST_CHAINLINK_UPGRADE_IMAGE` | `E2E_TEST_CHAINLINK_UPGRADE_IMAGE=qa_ecr_image_url` | -| Wallet Key per network | `E2E_TEST_(.+)_WALLET_KEY` or `E2E_TEST_(.+)_WALLET_KEY_(\d+)$` | `E2E_TEST_ARBITRUM_SEPOLIA_WALLET_KEY=wallet_key` or `E2E_TEST_ARBITRUM_SEPOLIA_WALLET_KEY_1=wallet_key_1`, `E2E_TEST_ARBITRUM_SEPOLIA_WALLET_KEY_2=wallet_key_2` for multiple keys per network | -| RPC HTTP URL per network | `E2E_TEST_(.+)_RPC_HTTP_URL` or `E2E_TEST_(.+)_RPC_HTTP_URL_(\d+)$` | `E2E_TEST_ARBITRUM_SEPOLIA_RPC_HTTP_URL=url` or `E2E_TEST_ARBITRUM_SEPOLIA_RPC_HTTP_URL_1=url`, `E2E_TEST_ARBITRUM_SEPOLIA_RPC_HTTP_URL_2=url` for multiple http urls per network | -| RPC WebSocket URL per network | `E2E_TEST_(.+)_RPC_WS_URL` or `E2E_TEST_(.+)_RPC_WS_URL_(\d+)$` | `E2E_TEST_ARBITRUM_RPC_WS_URL=ws_url` or `E2E_TEST_ARBITRUM_RPC_WS_URL_1=ws_url_1`, `E2E_TEST_ARBITRUM_RPC_WS_URL_2=ws_url_2` for multiple ws urls per network | -| Loki Tenant ID | `E2E_TEST_LOKI_TENANT_ID` | `E2E_TEST_LOKI_TENANT_ID=tenant_id` | -| Loki Endpoint | `E2E_TEST_LOKI_ENDPOINT` | `E2E_TEST_LOKI_ENDPOINT=url` | -| Loki Basic Auth | `E2E_TEST_LOKI_BASIC_AUTH` | `E2E_TEST_LOKI_BASIC_AUTH=token` | -| Loki Bearer Token | `E2E_TEST_LOKI_BEARER_TOKEN` | `E2E_TEST_LOKI_BEARER_TOKEN=token` | -| Grafana Bearer Token | `E2E_TEST_GRAFANA_BEARER_TOKEN` | `E2E_TEST_GRAFANA_BEARER_TOKEN=token` | -| Pyroscope Server URL | `E2E_TEST_PYROSCOPE_SERVER_URL` | `E2E_TEST_PYROSCOPE_SERVER_URL=url` | -| Pyroscope Key | `E2E_TEST_PYROSCOPE_KEY` | `E2E_TEST_PYROSCOPE_KEY=key` | - -### Run GitHub Workflow with Your Test Secrets - -By default, GitHub workflows execute with a set of predefined secrets. However, you can use custom secrets by specifying a unique identifier for your secrets when running the `gh workflow` command. - -#### Steps to Use Custom Secrets - -1. **Upload Local Secrets to GitHub Secrets Vault:** - - - **Install `ghsecrets` tool:** - Install the `ghsecrets` tool to manage GitHub Secrets more efficiently. - - ```bash - go install github.com/smartcontractkit/chainlink-testing-framework/tools/ghsecrets@latest - ``` - - If you use `asdf`, run `asdf reshim` - - - **Upload Secrets:** - Run `ghsecrets set` from local core repo to upload the content of your `~/.testsecrets` file to the GitHub Secrets Vault and generate a unique identifier (referred to as `your_ghsecret_id`). - - ```bash - cd path-to-chainlink-core-repo - ``` - - ```bash - ghsecrets set - ``` - - For more details about `ghsecrets`, visit https://github.com/smartcontractkit/chainlink-testing-framework/tree/main/tools/ghsecrets#faq - -2. **Execute the Workflow with Custom Secrets:** - - To use the custom secrets in your GitHub Actions workflow, pass the `-f test_secrets_override_key={your_ghsecret_id}` flag when running the `gh workflow` command. - ```bash - gh workflow run -f test_secrets_override_key={your_ghsecret_id} - ``` - -#### Default Secrets Handling - -If the `test_secrets_override_key` is not provided, the workflow will default to using the secrets preconfigured in the CI environment. - -### Creating New Test Secrets in TestConfig - -When adding a new secret to the `TestConfig`, such as a token or other sensitive information, the method `ReadConfigValuesFromEnvVars()` in `config/testconfig.go` must be extended to include the new secret. Ensure that the new environment variable starts with the `E2E_TEST_` prefix. This prefix is crucial for ensuring that the secret is correctly propagated to Kubernetes tests when using the Remote Runner. - -Here’s a quick checklist for adding a new test secret: - -- Add the secret to ~/.testsecrets with the `E2E_TEST_` prefix to ensure proper handling. -- Extend the `config/testconfig.go:ReadConfigValuesFromEnvVars()` method to load the secret in `TestConfig` -- Add the secrets to [All E2E Test Secrets](#all-e2e-test-secrets) table. - -## Working example - -For a full working example making use of all the building blocks see [testconfig.go](https://github.com/smartcontractkit/chainlink-testing-framework/blob/main/lib/config/examples/testconfig.go). It provides methods for reading TOML, applying overrides and validating non-empty config blocks. It supports 4 levels of overrides, in order of precedence: - -- `BASE64_CONFIG_OVERRIDE` env var -- `overrides.toml` -- `[product_name].toml` -- `default.toml` - -All you need to do now to get the config is execute `func GetConfig(configurationName string, product string) (TestConfig, error)`. It will first look for folder with file `.root_dir` and from there it will look for config files in all subfolders, so that you can place the config files in whatever folder(s) work for you. It assumes that all configuration versions for a single product are kept in `[product_name].toml` under different configuration names (that can represent anything you want: a single test, a test type, a test group, etc). - -Overrides of config files are done in a super-simple way. We try to unmarshall consecutive files into the same struct. Since it's all pointer based only not-nil keys are overwritten. - -## IMPORTANT! - -It is **required** to add `overrides.toml` to `.gitignore` in your project, so that you don't accidentally commit it as it might contain secrets. - -## Network config (and default RPC endpoints) - -Some more explanation is needed for the `NetworkConfig`: - -```golang -type NetworkConfig struct { - // list of networks that should be used for testing - SelectedNetworks []string `toml:"selected_networks"` - // map of network name to EVMNetworks where key is network name and value is a pointer to EVMNetwork - // if not set, it will try to find the network from defined networks in MappedNetworks under known_networks.go - // it doesn't matter if you use `arbitrum_sepolia` or `ARBITRUM_SEPOLIA` or even `arbitrum_SEPOLIA` as key - // as all keys will be uppercased when loading the Default config - EVMNetworks map[string]*blockchain.EVMNetwork `toml:"EVMNetworks,omitempty"` - // map of network name to ForkConfigs where key is network name and value is a pointer to ForkConfig - // only used if network fork is needed, if provided, the network will be forked with the given config - // networkname is fetched first from the EVMNetworks and - // if not defined with EVMNetworks, it will try to find the network from defined networks in MappedNetworks under known_networks.go - ForkConfigs map[string]*ForkConfig `toml:"ForkConfigs,omitempty"` - // map of network name to RPC endpoints where key is network name and value is a list of RPC HTTP endpoints - RpcHttpUrls map[string][]string `toml:"RpcHttpUrls"` - // map of network name to RPC endpoints where key is network name and value is a list of RPC WS endpoints - RpcWsUrls map[string][]string `toml:"RpcWsUrls"` - // map of network name to wallet keys where key is network name and value is a list of private keys (aka funding keys) - WalletKeys map[string][]string `toml:"WalletKeys"` -} - -func (n *NetworkConfig) Default() error { - ... -} -``` - -Sample TOML config: - -```toml -selected_networks = ["arbitrum_goerli", "optimism_goerli", "new_network"] - -[EVMNetworks.new_network] -evm_name = "new_test_network" -evm_chain_id = 100009 -evm_simulated = true -evm_chainlink_transaction_limit = 5000 -evm_minimum_confirmations = 1 -evm_gas_estimation_buffer = 10000 -client_implementation = "Ethereum" -evm_supports_eip1559 = true -evm_default_gas_limit = 6000000 - -[ForkConfigs.new_network] -url = "ws://localhost:8546" -block_number = 100 - -[RpcHttpUrls] -arbitrum_goerli = ["https://devnet-2.mt/ABC/rpc/"] -new_network = ["http://localhost:8545"] - -[RpcWsUrls] -arbitrum_goerli = ["wss://devnet-2.mt/ABC/ws/"] -new_network = ["ws://localhost:8546"] - -[WalletKeys] -arbitrum_goerli = ["1810868fc221b9f50b5b3e0186d8a5f343f892e51ce12a9e818f936ec0b651ed"] -optimism_goerli = ["1810868fc221b9f50b5b3e0186d8a5f343f892e51ce12a9e818f936ec0b651ed"] -new_network = ["ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"] -``` - -Whenever you are adding a new EVMNetwork to the config, you can either - -- provide the rpcs and wallet keys in RpcUrls and WalletKeys. Like in the example above, you can see that `new_network` is added to the `selected_networks` and `EVMNetworks` and then the rpcs and wallet keys are provided in `RpcHttpUrls`, `RpcWsUrls` and `WalletKeys` respectively. -- provide the rpcs and wallet keys in the `EVMNetworks` itself. Like in the example below, you can see that `new_network` is added to the `selected_networks` and `EVMNetworks` and then the rpcs and wallet keys are provided in `EVMNetworks` itself. - -```toml - -selected_networks = ["new_network"] - -[EVMNetworks.new_network] -evm_name = "new_test_network" -evm_chain_id = 100009 -evm_urls = ["ws://localhost:8546"] -evm_http_urls = ["http://localhost:8545"] -evm_keys = ["ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"] -evm_simulated = true -evm_chainlink_transaction_limit = 5000 -evm_minimum_confirmations = 1 -evm_gas_estimation_buffer = 10000 -client_implementation = "Ethereum" -evm_supports_eip1559 = true -evm_default_gas_limit = 6000000 -``` - -If your config struct looks like that: - -```golang - -type TestConfig struct { - Network *ctf_config.NetworkConfig `toml:"Network"` -} -``` - -then your TOML file should look like that: - -```toml -[Network] -selected_networks = ["arbitrum_goerli","new_network"] - -[Network.EVMNetworks.new_network] -evm_name = "new_test_network" -evm_chain_id = 100009 -evm_simulated = true -evm_chainlink_transaction_limit = 5000 -evm_minimum_confirmations = 1 -evm_gas_estimation_buffer = 10000 -client_implementation = "Ethereum" -evm_supports_eip1559 = true -evm_default_gas_limit = 6000000 - -[Network.RpcHttpUrls] -arbitrum_goerli = ["https://devnet-2.mt/ABC/rpc/"] -new_network = ["http://localhost:8545"] - -[Network.RpcWsUrls] -arbitrum_goerli = ["ws://devnet-2.mt/ABC/rpc/"] -new_network = ["ws://localhost:8546"] - -[Network.WalletKeys] -arbitrum_goerli = ["1810868fc221b9f50b5b3e0186d8a5f343f892e51ce12a9e818f936ec0b651ed"] -new_network = ["ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"] -``` - -If in your product config you want to support case-insensitive network names and map keys remember to run `NetworkConfig.UpperCaseNetworkNames()` on your config before using it. - -## Providing custom values in the CI - -Up to this point when we wanted to modify some dynamic tests parameters in the CI we would simply set env vars. That approach won't work anymore. The way to go around it is to build a TOML file, `base64` it, mask it and then set is as `BASE64_CONFIG_OVERRIDE` env var that will be read by tests. Here's an example of a working snippet of how that could look: - -```bash -convert_to_toml_array() { - local IFS=',' - local input_array=($1) - local toml_array_format="[" - - for element in "${input_array[@]}"; do - toml_array_format+="\"$element\"," - done - - toml_array_format="${toml_array_format%,}]" - echo "$toml_array_format" -} - -selected_networks=$(convert_to_toml_array "$SELECTED_NETWORKS") - -if [ -n "$PYROSCOPE_SERVER" ]; then - pyroscope_enabled=true -else - pyroscope_enabled=false -fi - -if [ -n "$ETH2_EL_CLIENT" ]; then - execution_layer="$ETH2_EL_CLIENT" -else - execution_layer="geth" -fi - -cat << EOF > config.toml -[Network] -selected_networks=$selected_networks - -[ChainlinkImage] -image="$CHAINLINK_IMAGE" -version="$CHAINLINK_VERSION" - -[Pyroscope] -enabled=$pyroscope_enabled -server_url="$PYROSCOPE_SERVER" -environment="$PYROSCOPE_ENVIRONMENT" -key_secret="$PYROSCOPE_KEY" - -[Logging.Loki] -tenant_id="$LOKI_TENANT_ID" -url="$LOKI_URL" -basic_auth_secret="$LOKI_BASIC_AUTH" -bearer_token_secret="$LOKI_BEARER_TOKEN" - -[Logging.Grafana] -url="$GRAFANA_URL" -EOF - -BASE64_CONFIG_OVERRIDE=$(cat config.toml | base64 -w 0) -echo ::add-mask::$BASE64_CONFIG_OVERRIDE -echo "BASE64_CONFIG_OVERRIDE=$BASE64_CONFIG_OVERRIDE" >> $GITHUB_ENV -``` - -**These two lines in that very order are super important** - -```bash -BASE64_CONFIG_OVERRIDE=$(cat config.toml | base64 -w 0) -echo ::add-mask::$BASE64_CONFIG_OVERRIDE -``` - -`::add-mask::` has to be called only after env var has been set to it's final value, otherwise it won't be recognized and masked properly and secrets will be exposed in the logs. - -## Providing custom values for local execution - -For local execution it's best to put custom variables in `overrides.toml` file. - -## Providing custom values in k8s - -It's easy. All you need to do is: - -- Create TOML file with these values -- Base64 it: `cat your.toml | base64` -- Set the base64 result as `BASE64_CONFIG_OVERRIDE` environment variable. - -`BASE64_CONFIG_OVERRIDE` will be automatically forwarded to k8s (as long as it is set and available to the test process), when creating the environment programmatically via `environment.New()`. - -Quick example: - -```bash -BASE64_CONFIG_OVERRIDE=$(cat your.toml | base64) go test your-test-that-runs-in-k8s ./file/with/your/test -``` - -# Not moved to TOML - -Not moved to TOML: - -- `SLACK_API_KEY` -- `SLACK_USER` -- `SLACK_CHANNEL` -- `TEST_LOG_LEVEL` -- `CHAINLINK_ENV_USER` -- `DETACH_RUNNER` -- `ENV_JOB_IMAGE` -- most of k8s-specific env variables were left untouched diff --git a/book/src/lib/crib.md b/book/src/lib/crib.md deleted file mode 100644 index 63537d63d..000000000 --- a/book/src/lib/crib.md +++ /dev/null @@ -1,7 +0,0 @@ -### CRIB Connector - -
- -`GAPv1` won't be supported in the future, you can still use [this example](https://github.com/smartcontractkit/chainlink/tree/develop/integration-tests/crib), [CI run](https://github.com/smartcontractkit/chainlink/actions/workflows/crib-integration-test.yml) but expect this to be changed. - -
diff --git a/book/src/lib/docker/blockchain_nodes.md b/book/src/lib/docker/blockchain_nodes.md deleted file mode 100644 index 9f312d406..000000000 --- a/book/src/lib/docker/blockchain_nodes.md +++ /dev/null @@ -1,202 +0,0 @@ -# Blockchain Nodes - -If you plan to experiment with these Docker containers, **use an existing Ethereum environment builder** rather than assembling components manually. This is particularly important for Proof-of-Stake (PoS) networks, where the setup involves multi-stage operations and shared state across multiple containers. - -Each supported client has a default image version defined [here](https://github.com/smartcontractkit/chainlink-testing-framework/blob/main/lib/docker/ethereum/images.go). Clients (except Reth) are available in two flavors: -- **Proof-of-Stake (PoS)**: Ethereum 2.0. -- **Proof-of-Work/Authority (PoW/PoA)**: Ethereum 1.0. - -Reth supports only PoS networks. - -These ephemeral networks use a simplified configuration with a single blockchain node. For PoS, three containers simulate this: -- Execution layer -- Consensus layer -- Validator - -> [!NOTE] -> We use a fork of [Ethereum Genesis Generator](https://github.com/ethpandaops/ethereum-genesis-generator) to create genesis files for PoS networks. - ---- - -## Execution Layer Clients -The following execution layer clients are available: -- Besu -- Erigon -- Geth -- Nethermind -- Reth - -## Consensus Layer Client -- **Prysm** (the only available consensus client). - ---- - -# Quick Start - -The simplest way to start an Ethereum network is by specifying the execution layer only: - -```go -builder := NewEthereumNetworkBuilder() -cfg, err := builder. - WithExecutionLayer(types.ExecutionLayer_Nethermind). - Build() -if err != nil { - panic(err) -} - -net, rpcs, err := cfg.Start() -if err != nil { - panic(err) -} -``` -If no version is specified, **Ethereum 1.0 (pre-Merge)** will be used. - -### Starting Ethereum 2.0 Networks -To start an Ethereum 2.0 network, add the Ethereum version parameter: - -```go -builder := NewEthereumNetworkBuilder() -cfg, err := builder. - WithEthereumVersion(config_types.EthereumVersion_Eth2). - WithExecutionLayer(config_types.ExecutionLayer_Geth). - Build() -if err != nil { - panic(err) -} - -net, rpcs, err := cfg.Start() -if err != nil { - panic(err) -} -``` -> [!NOTE] -> Booting Ethereum 2.0 networks takes longer due to the additional containers. Wait times of up to 1 minute are common. - ---- - -## Custom Docker Images -To use custom Docker images instead of the defaults: - -```go -builder := NewEthereumNetworkBuilder() -cfg, err := builder. - WithCustomDockerImages(map[config.ContainerType]string{ - config.ContainerType_ExecutionLayer: "ethereum/client-go:v1.15.0", - }). - Build() -if err != nil { - panic(err) -} - -net, rpcs, err := cfg.Start() -if err != nil { - panic(err) -} -``` -### Available Container Types -```go -const ( - ContainerType_ExecutionLayer ContainerType = "execution_layer" - ContainerType_ConsensusLayer ContainerType = "consensus_layer" - ContainerType_ConsensusValidator ContainerType = "consensus_validator" - ContainerType_GenesisGenerator ContainerType = "genesis_generator" - ContainerType_ValKeysGenerator ContainerType = "val_keys_generator" -) -``` -> [!NOTE] -> Use the `latest_available` tag for the most recent release, including pre-releases, or the `latest_stable` tag for the latest officially stable release. The `latest_available` may include beta or development versions, whereas `latest_stable` ensures compatibility with production environments. - ---- - -# Advanced Options - -## Connect to Existing Docker Networks -By default, a new random Docker network is created. To use an existing one: - -```go -builder := NewEthereumNetworkBuilder() -cfg, err := builder. - WithExecutionLayer(types.ExecutionLayer_Nethermind). - WithDockerNetworks([]string{"my-existing-network"}). - Build() -``` - -## Chain Customization - -### Ethereum 2.0 Parameters -- **Seconds per slot**: This defines how many seconds validators have to propose and vote on blocks. Lower values accelerate block production and epoch finalization but can cause issues if validators fail to keep up. The minimum allowed value is `3`. -- **Slots per epoch**: Determines the number of slots (voting rounds) per epoch. Lower values mean epochs finalize faster, but fewer voting rounds can impact network stability. The minimum value is `2`. -- **Genesis delay**: The extra delay (in seconds) before the genesis block starts. This ensures all containers (execution, consensus, and validator) are up and running before block production begins. -- **Validator count**: Specifies the number of validators in the network. A higher count increases the robustness of block validation but requires more resources. The minimum allowed value is `4`. - -### General Parameters -- **ChainID**: Integer value for the chain. -- **Addresses to fund**: Pre-fund accounts in the genesis block. - -### Default Configuration -```toml -seconds_per_slot=12 -slots_per_epoch=6 -genesis_delay=15 -validator_count=8 -chain_id=1337 -addresses_to_fund=["0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266"] -``` - ---- - -## Log Level -Customize node log levels (default: `info`): -```go -WithNodeLogLevel("debug") -``` -Supported values: -- `trace`, `debug`, `info`, `warn`, `error` - -## Wait for Finalization (Ethereum 2.0) -Wait until the first epoch finalizes: -```go -WithWaitingForFinalization() -``` - ---- - -# Accessing Containers -The `builder.Start()` function returns: -1. **Network configuration**: Input to test clients. -2. **RPC endpoints**: - - **Public**: Accessible externally. - - **Private**: Internal to the Docker network. - -### Endpoint Usage -- Use **public endpoints** for Ethereum clients deploying contracts, interacting with the chain, etc. -- Use **private endpoints** for Chainlink nodes within the same Docker network. - ---- - -# Ethereum 2.0 Container Creation Sequence -The following sequence explains Ethereum 2.0 container startup: - -```mermaid -sequenceDiagram - participant Host - participant Validator Keys Generator - participant Genesis Generator - participant Execution Layer - participant Beacon Chain (Consensus Layer) - participant Validator - participant After Genesis Helper - participant User - - Validator Keys Generator->>Host: Generate validator keys - Genesis Generator->>Host: Create genesis files - Host->>Execution Layer: Start with genesis - Host->>Beacon Chain (Consensus Layer): Start with genesis - Beacon Chain (Consensus Layer)->>Execution Layer: Connect and sync - Host->>Validator: Start with validator keys - Validator->>Beacon Chain (Consensus Layer): Begin block voting - loop new blocks - After Genesis Helper->>Execution Layer: Monitor new blocks - end - After Genesis Helper->>User: Ready to interact -``` \ No newline at end of file diff --git a/book/src/lib/docker/chainlink_ecosystem.md b/book/src/lib/docker/chainlink_ecosystem.md deleted file mode 100644 index a570bae69..000000000 --- a/book/src/lib/docker/chainlink_ecosystem.md +++ /dev/null @@ -1,49 +0,0 @@ -# Chainlink ecosystem - -Currently, only one application has a wrapper that lives in this framework: the Job Distributor. -Chainlink Node wrapper can be found in the [chainlink repository](https://github.com/smartcontractkit/chainlink/blob/develop/integration-tests/docker/test_env/cl_node.go). - -## Job Distributor -JD is a component for a centralized creation and management of jobs executed by Chainlink Nodes. It's a single point of entry -that frees you from having to setup each job separately on each node from the DON. - -It requires a Postgres DB instance, which can also be started using the CTF: -```go -pg, err := test_env.NewPostgresDb( - []string{network.Name}, - test_env.WithPostgresDbName("jd-db"), - test_env.WithPostgresImageVersion("14.1")) -if err != nil { - panic(err) -} -err = pg.StartContainer() -if err != nil { - panic(err) -} -``` - -Then all you need to do, to start a new instance is: -```go -jd := New([]string{network.Name}, - //replace with actual image - WithImage("localhost:5001/jd"), - WithVersion("latest"), - WithDBURL(pg.InternalURL.String()), -) - -err = jd.StartContainer() -if err != nil { - panic(err) -} -``` - -Once you have JD started you can create a new GRPC connection and start interacting with it -to register DON nodes, create jobs, etc: -```go -conn, cErr := grpc.NewClient(jd.Grpc, []grpc.DialOption{grpc.WithTransportCredentials(insecure.NewCredentials())}...) -if cErr != nil { - panic(cErr) -} - -// use the connection -``` \ No newline at end of file diff --git a/book/src/lib/docker/overview.md b/book/src/lib/docker/overview.md deleted file mode 100644 index 54c1bbadc..000000000 --- a/book/src/lib/docker/overview.md +++ /dev/null @@ -1,68 +0,0 @@ -# Docker - -Docker package represents low-level wrappers for Docker containers built using [testcontainers-go](https://golang.testcontainers.org/) library. -It is strongly advised that you use CTFv2's [Framework](../framework/overview.md) to build your enviroment instead of using it directly. **Consider yourself warned!** - -Supported Docker containers can be divided in a couple of categories: -* [Chainlink ecosystem-related](./chainlink_ecosystem.md) -* [Blockchain nodes](./blockchain_nodes.md) -* Third party apps -* [Test helpers](./test_helpers.md) -* helper containers - -Following Chainlink-related containers are available: -* Job Distributor - -Blockchain nodes: -* Besu -* Erigon -* Geth -* Nethermind -* Reth -* Prysm Beacon client (PoS Ethereum) - -Third party apps: -* Kafka -* Postgres -* Zookeeper -* Schema registry - -Test helpers (mocking solutions): -* Killgrave -* Mockserver - -Helper containers: -* Ethereum genesis generator -* Validator keys generator - -# Basic structure - -All of our Docker container wrappers are composed of some amount of specific elements and an universal part called `EnvComponent` defined as: -```go -type EnvComponent struct { - ContainerName string `json:"containerName"` - ContainerImage string `json:"containerImage"` - ContainerVersion string `json:"containerVersion"` - ContainerEnvs map[string]string `json:"containerEnvs"` - WasRecreated bool `json:"wasRecreated"` - Networks []string `json:"networks"` - Container tc.Container `json:"-"` - PostStartsHooks []tc.ContainerHook `json:"-"` - PostStopsHooks []tc.ContainerHook `json:"-"` - PreTerminatesHooks []tc.ContainerHook `json:"-"` - LogLevel string `json:"-"` - StartupTimeout time.Duration `json:"-"` -} -``` - -It comes with a bunch of functional options that allow you to: -* set container name -* set image name and tag -* set startup timeout -* set log level -* set post start/stop hooks - -And following chaos-testing related functions: -* `ChaosNetworkDelay()` - introducing networking delay -* `ChaosNetworkLoss()` - simulating network package loss -* `ChaosPause()` - pausing the container \ No newline at end of file diff --git a/book/src/lib/docker/test_helpers.md b/book/src/lib/docker/test_helpers.md deleted file mode 100644 index 03f8fa2e4..000000000 --- a/book/src/lib/docker/test_helpers.md +++ /dev/null @@ -1,10 +0,0 @@ -# Test helpers - -There two test helper containers: -* Killgrave -* Mockserver - -Both represent HTTP mocking solutions, with Mockserver used in k8s and Killgrave outside of it. Since both will soon -be replaced by a single, in-house solution, their usage is discouraged. - -That worked is begin done in [this PR](https://github.com/smartcontractkit/chainlink-testing-framework/pull/1246) and tracked in [this ticket](https://smartcontract-it.atlassian.net/browse/TT-1608). \ No newline at end of file diff --git a/book/src/lib/k8s/KUBERNETES.md b/book/src/lib/k8s/KUBERNETES.md deleted file mode 100644 index a80756a00..000000000 --- a/book/src/lib/k8s/KUBERNETES.md +++ /dev/null @@ -1,36 +0,0 @@ -# Kubernetes - - -
- -Managing k8s is challenging, so we've decided to separate `k8s` deployments here - [CRIB](https://github.com/smartcontractkit/crib) - -This documentation is outdated, and we are using it only internally to run our soak tests. For `v2` tests please check [this example](../crib.md) and read [CRIB docs](https://github.com/smartcontractkit/crib) -
- -We run our software in Kubernetes. - -### Local k3d setup - -1. `make install` -2. (Optional) Install `Lens` from [here](https://k8slens.dev/) or use `k9s` as a low resource consumption alternative from [here](https://k9scli.io/topics/install/) - or from source [here](https://github.com/smartcontractkit/helmenv) -3. Setup your docker resources, 6vCPU/10Gb RAM are enough for most CL related tasks -4. `make create_cluster` -5. `make install_monitoring` Note: this will be actively connected to the server, the final log when it is ready is`Forwarding from [::1]:3000 -> 3000` and you can continue with the steps below in another terminal. -6. Check your contexts with `kubectl config get-contexts` -7. Switch context `kubectl config use-context k3d-local` -8. Read [here](README.md) and do some deployments -9. Open Grafana on `localhost:3000` with `admin/sdkfh26!@bHasdZ2` login/password and check the default dashboard -10. `make stop_cluster` -11. `make delete_cluster` - -### Typical problems - -1. Not enough memory/CPU or cluster is slow - Recommended settings for Docker are (Docker -> Preferences -> Resources): - - 6 CPU - - 10Gb MEM - - 50-150Gb Disk -2. `NodeHasDiskPressure` errors, pods get evicted - Use `make docker_prune` to clean up all pods and volumes diff --git a/book/src/lib/k8s/REMOTE_RUN.md b/book/src/lib/k8s/REMOTE_RUN.md deleted file mode 100644 index 6a8fede4f..000000000 --- a/book/src/lib/k8s/REMOTE_RUN.md +++ /dev/null @@ -1,48 +0,0 @@ -## How to run the same environment deployment inside k8s - -
- -Managing k8s is challenging, so we've decided to separate `k8s` deployments here - [CRIB](https://github.com/smartcontractkit/crib) - -This documentation is outdated, and we are using it only internally to run our soak tests. For `v2` tests please check [this example](../crib.md) and read [CRIB docs](https://github.com/smartcontractkit/crib) -
- - -You can build a `Dockerfile` to run exactly the same environment interactions inside k8s in case you need to run long-running tests -Base image is [here](https://github.com/smartcontractkit/chainlink-testing-framework/blob/main/lib/k8s/Dockerfile.base) - -```Dockerfile -FROM .dkr.ecr.us-west-2.amazonaws.com/test-base-image:latest -COPY . . -RUN env GOOS=linux GOARCH=amd64 go build -o test ./examples/remote-test-runner/env.go -RUN chmod +x ./test -ENTRYPOINT ["./test"] -``` - -Build and upload it using the "latest" tag for the test-base-image - -```bash -build_test_image tag=someTag -``` - -or if you want to specify a test-base-image tag - -```bash -build_test_image tag=someTag base_tag=latest -``` - -Then run it - -```bash -# all environment variables with a prefix TEST_ would be provided for k8s job -export TEST_ENV_VAR=myTestVarForAJob -# your image to run as a k8s job -ACCOUNT=$(aws sts get-caller-identity | jq -r .Account) -export ENV_JOB_IMAGE="${ACCOUNT}.dkr.ecr.us-west-2.amazonaws.com/core-integration-tests:v1.1" -export DETACH_RUNNER=true # if you want the test job to run in the background after it has started -export CHAINLINK_ENV_USER=yourUser # user to run the tests -export CHAINLINK_USER_TEAM=yourTeam # team to run the tests for -# your example test file to run inside k8s -# if ENV_JOB_IMAGE is present it will create a job, wait until it finished and get logs -go run examples/remote-test-runner/env.go -``` diff --git a/book/src/lib/k8s/TUTORIAL.md b/book/src/lib/k8s/TUTORIAL.md deleted file mode 100644 index c9286dbf6..000000000 --- a/book/src/lib/k8s/TUTORIAL.md +++ /dev/null @@ -1,743 +0,0 @@ -# How to create environments - -
- -Managing k8s is challenging, so we've decided to separate `k8s` deployments here - [CRIB](https://github.com/smartcontractkit/crib) - -This documentation is outdated, and we are using it only internally to run our soak tests. For `v2` tests please check [this example](../crib.md) and read [CRIB docs](https://github.com/smartcontractkit/crib) -
- - -- [Getting started](#getting-started) -- [Connect to environment](#connect-to-environment) -- [Creating environments](#creating-environments) - - [Debugging a new integration environment](#debugging-a-new-integration-environment) - - [Creating a new deployment part in Helm](#creating-a-new-deployment-part-in-helm) - - [Creating a new deployment part in cdk8s](#creating-a-new-deployment-part-in-cdk8s) - - [Using multi-stage environment](#using-multi-stage-environment) -- [Modifying environments](#modifying-environments) - - [Modifying environment from code](#modifying-environment-from-code) - - [Modifying environment part from code](#modifying-environment-part-from-code) -- [Configuring](#configuring) - - [Environment variables](#environment-variables) - - [Environment config](#environment-config) -- [Utilities](#utilities) - - [Collecting logs](#collecting-logs) - - [Resources summary](#resources-summary) -- [Chaos](#chaos) -- [Coverage](#coverage) -- [Remote run](./REMOTE_RUN.md) - -## Getting started - -Read [here](KUBERNETES.md) about how to spin up a local cluster if you don't have one. - -Following examples will use hardcoded `chain.link` labels for the sake of satisfying validations. When using any of remote clusters you should -provide them with actual and valid values, for example using following convenience functions: -```go -nsLabels, err := GetRequiredChainLinkNamespaceLabels("my-product", "load") -require.NoError(t, err, "Error creating required chain.link labels for namespace") - -workloadPodLabels, err := GetRequiredChainLinkWorkloadAndPodLabels("my-product", "load") -require.NoError(t, err, "Error creating required chain.link labels for workloads and pods") -``` - -And then setting them in the `Environment` config: -```go -envConfig := &environment.Config{ - Labels: nsLabels, - WorkloadLabels: workloadPodLabels - PodLabels: workloadPodLabels - NamespacePrefix: "new-environment", -} -``` - -Now, let's create a simple environment by combining different deployment parts. - -Create `examples/simple/env.go` - -```golang -package main - -import ( - "fmt" - "github.com/smartcontractkit/chainlink-testing-framework/k8s/environment" - "github.com/smartcontractkit/chainlink-testing-framework/k8s/pkg" - "github.com/smartcontractkit/chainlink-testing-framework/k8s/pkg/helm/chainlink" - "github.com/smartcontractkit/chainlink-testing-framework/k8s/pkg/helm/ethereum" - "github.com/smartcontractkit/chainlink-testing-framework/k8s/pkg/helm/mockserver" -) - -func addHardcodedLabelsToEnv(env *environment.Config) { - env.Labels = []string{"chain.link/product=myProduct", "chain.link/team=my-team", "chain.link/cost-center=test-tooling-load-test"} - env.WorkloadLabels = map[string]string{"chain.link/product": "myProduct", "chain.link/team": "my-team", "chain.link/cost-center": "test-tooling-load-test"} - env.PodLabels = map[string]string{"chain.link/product": "myProduct", "chain.link/team": "my-team", "chain.link/cost-center": "test-tooling-load-test"} -} - -func main() { - env := &environment.Config{ - NamespacePrefix: "new-environment", - KeepConnection: false, - RemoveOnInterrupt: false, - } - - addHardcodedLabelsToEnv(env) - err := environment.New(env). - AddHelm(ethereum.New(nil)). - AddHelm(chainlink.New(0, nil)). - Run() - if err != nil { - panic(err) - } -} -``` - -Then run `go run examples/simple/env.go` - -Now you have your environment running, you can [connect](#connect-to-environment) to it later - -> [!NOTE] -> `chain.link/*` labels are used for internal reporting and cost allocation. They are strictly required and validated. You won't be able to create a new environment without them. -> In this tutorial we create almost all of them manually, but there are convenience functions to do it for you. -> You can read more about labels [here](./labels.md) - -## Connect to environment - -We've already created an environment [previously](#getting-started), now we can connect - -If you are planning to use environment locally not in tests and keep connection, modify `KeepConnection` in `environment.Config` we used - -``` - KeepConnection: true, -``` - -Add `ENV_NAMESPACE=${your_env_namespace}` var and run `go run examples/simple/env.go` again - -You can get the namespace name from logs on creation time - -# Creating environments - -## Debugging a new integration environment - -You can spin up environment and block on forwarder if you'd like to run some other code. Let's use convenience functions for creating `chain.link` labels. - -```golang -package main - -import ( - "github.com/smartcontractkit/chainlink-testing-framework/k8s/environment" - "github.com/smartcontractkit/chainlink-testing-framework/k8s/pkg/helm/chainlink" - "github.com/smartcontractkit/chainlink-testing-framework/k8s/pkg/helm/ethereum" -) - -func main() { - env := &environment.Config{ - NamespacePrefix: "new-environment", - KeepConnection: true, - RemoveOnInterrupt: true, - } - - addHardcodedLabelsToEnv(env) - err := environment.New(env). - AddHelm(ethereum.New(nil)). - AddHelm(chainlink.New(0, nil)). - Run() - if err != nil { - panic(err) - } -} -``` - -Send any signal to remove the namespace then, for example `Ctrl+C` `SIGINT` - -## Creating a new deployment part in Helm - -Let's add a new [deployment part](examples/deployment_part/sol.go), it should implement an interface - -```golang -// ConnectedChart interface to interact both with cdk8s apps and helm charts -type ConnectedChart interface { - // IsDeploymentNeeded - // true - we deploy/connect and expose environment data - // false - we are using external environment, but still exposing data - IsDeploymentNeeded() bool - // GetName name of the deployed part - GetName() string - // GetPath get Helm chart path, repo or local path - GetPath() string - // GetProps get code props if it's typed environment - GetProps() any - // GetValues get values.yml props as map, if it's Helm - GetValues() *map[string]any - // ExportData export deployment part data in the env - ExportData(e *Environment) error - // GetLabels get labels for component, it must return `chain.link/component` label - GetLabels() map[string]string -} -``` - -When creating new deployment part, you can use any public Helm chart or a local path in Helm props - -```golang -func New(props *Props) environment.ConnectedChart { - if props == nil { - props = defaultProps() - } - return Chart{ - HelmProps: &HelmProps{ - Name: "sol", - Path: "chainlink-qa/solana-validator", // ./local_path/chartdir will work too - Values: &props.Values, - }, - Props: props, - } -} - -func (m NewDeploymentPart) GetLabels() map[string]string { - return map[string]string{ - "chain.link/component": "new-deployment-part", - } -} -``` - -Now let's tie them together - -```golang -package main - -import ( - "github.com/smartcontractkit/chainlink-testing-framework/k8s/environment" - "github.com/smartcontractkit/chainlink-testing-framework/k8s/examples/deployment_part" - "github.com/smartcontractkit/chainlink-testing-framework/k8s/pkg/helm/chainlink" - "time" -) - -func main() { - env := &environment.Config{ - NamespacePrefix: "adding-new-deployment-part", - TTL: 3 * time.Hour, - KeepConnection: true, - RemoveOnInterrupt: true, - } - - addHardcodedLabelsToEnv(env) - e := environment.New(env). - AddHelm(deployment_part.New(nil)). - AddHelm(chainlink.New(0, map[string]any{ - "replicas": 5, - "env": map[string]any{ - "SOLANA_ENABLED": "true", - "EVM_ENABLED": "false", - "EVM_RPC_ENABLED": "false", - "CHAINLINK_DEV": "false", - "FEATURE_OFFCHAIN_REPORTING2": "true", - "feature_offchain_reporting": "false", - "P2P_NETWORKING_STACK": "V2", - "P2PV2_LISTEN_ADDRESSES": "0.0.0.0:6690", - "P2PV2_DELTA_DIAL": "5s", - "P2PV2_DELTA_RECONCILE": "5s", - "p2p_listen_port": "0", - }, - })) - if err := e.Run(); err != nil { - panic(err) - } -} -``` - -Then run it `examples/deployment_part/cmd/env.go` - -## Creating a new deployment part in cdk8s - -Let's add a new [deployment part](examples/deployment_part/sol.go), it should implement the same interface - -```golang -// ConnectedChart interface to interact both with cdk8s apps and helm charts -type ConnectedChart interface { - // IsDeploymentNeeded - // true - we deploy/connect and expose environment data - // false - we are using external environment, but still exposing data - IsDeploymentNeeded() bool - // GetName name of the deployed part - GetName() string - // GetPath get Helm chart path, repo or local path - GetPath() string - // GetProps get code props if it's typed environment - GetProps() any - // GetValues get values.yml props as map, if it's Helm - GetValues() *map[string]any - // ExportData export deployment part data in the env - ExportData(e *Environment) error - // GetLabels get labels for component, it must return `chain.link/component` label - GetLabels() map[string]string -} -``` - -Now let's tie them together - -```golang -package main - -import ( - "github.com/smartcontractkit/chainlink-testing-framework/k8s/environment" - "github.com/smartcontractkit/chainlink-testing-framework/k8s/examples/deployment_part_cdk8s" - "github.com/smartcontractkit/chainlink-testing-framework/k8s/pkg/helm/chainlink" - "github.com/smartcontractkit/chainlink-testing-framework/k8s/pkg/helm/ethereum" -) - -func main() { - env := &environment.Config{ - NamespacePrefix: "adding-new-deployment-part", - TTL: 3 * time.Hour, - KeepConnection: true, - RemoveOnInterrupt: true, - } - - addHardcodedLabelsToEnv(env) - e := environment.New(env). - AddChart(deployment_part_cdk8s.New(&deployment_part_cdk8s.Props{})). - AddHelm(ethereum.New(nil)). - AddHelm(chainlink.New(0, map[string]any{ - "replicas": 2, - })) - if err := e.Run(); err != nil { - panic(err) - } - e.Shutdown() -} -``` - -Then run it `examples/deployment_part_cdk8s/cmd/env.go` - -## Using multi-stage environment - -You can split [environment](examples/multistage/env.go) deployment in several parts if you need to first copy something into a pod or use connected clients first - -```golang -package main - -import ( - "fmt" - "github.com/smartcontractkit/chainlink-testing-framework/k8s/environment" - "github.com/smartcontractkit/chainlink-testing-framework/k8s/pkg/cdk8s/blockscout" - "github.com/smartcontractkit/chainlink-testing-framework/k8s/pkg/helm/chainlink" - "github.com/smartcontractkit/chainlink-testing-framework/k8s/pkg/helm/ethereum" - "github.com/smartcontractkit/chainlink-testing-framework/k8s/pkg/helm/mockserver" - mockservercfg "github.com/smartcontractkit/chainlink-testing-framework/k8s/pkg/helm/mockserver-cfg" -) - -func main() { - envConfig := &environment.Config{ - Labels: []string{"chain.link/product=myProduct", "chain.link/team=my-team", "chain.link/cost-center=test-tooling-load-test"}, - WorkloadLabels: map[string]string{"chain.link/product": "myProduct", "chain.link/team": "my-team", "chain.link/cost-center": "test-tooling-load-test"}, - PodLabels: map[string]string{"chain.link/product": "myProduct", "chain.link/team": "my-team", "chain.link/cost-center": "test-tooling-load-test"} - } - e := environment.New(envConfig) - err := e. - AddChart(blockscout.New(&blockscout.Props{})). // you can also add cdk8s charts if you like Go code - AddHelm(ethereum.New(nil)). - AddHelm(chainlink.New(0, nil)). - Run() - if err != nil { - panic(err) - } - // do some other stuff with deployed charts - pl, err := e.Client.ListPods(e.Cfg.Namespace, "app=chainlink-0") - if err != nil { - panic(err) - } - dstPath := fmt.Sprintf("%s/%s:/", e.Cfg.Namespace, pl.Items[0].Name) - if _, _, _, err = e.Client.CopyToPod(e.Cfg.Namespace, "./examples/multistage/someData.txt", dstPath, "node"); err != nil { - panic(err) - } - // deploy another part - err = e. - AddHelm(mockservercfg.New(nil)). - AddHelm(mockserver.New(nil)). - Run() - defer func() { - errr := e.Shutdown() - panic(errr) - }() - if err != nil { - panic(err) - } -} -``` - -# Modifying environments - -## Modifying environment from code - -In case you need to [modify](examples/modify_cdk8s/env.go) environment in tests you can always construct manifest again and apply it - -That's working for `cdk8s` components too - -```golang -package main - -import ( - "fmt" - "github.com/smartcontractkit/chainlink-testing-framework/k8s/environment" - "github.com/smartcontractkit/chainlink-testing-framework/k8s/pkg/cdk8s/blockscout" - "github.com/smartcontractkit/chainlink-testing-framework/k8s/pkg/helm/chainlink" - "github.com/smartcontractkit/chainlink-testing-framework/k8s/pkg/helm/ethereum" -) - -func main() { - modifiedEnvConfig := &environment.Config{ - NamespacePrefix: "modified-env", - Labels: []string{"envType=Modified", "chain.link/product=myProduct", "chain.link/team=my-team", "chain.link/cost-center=test-tooling-load-test"}, - WorkloadLabels: map[string]string{"chain.link/product": "myProduct", "chain.link/team": "my-team", "chain.link/cost-center": "test-tooling-load-test"}, - PodLabels: map[string]string{"chain.link/product": "myProduct", "chain.link/team": "my-team", "chain.link/cost-center": "test-tooling-load-test"} - } - e := environment.New(modifiedEnvConfig). - AddChart(blockscout.New(&blockscout.Props{ - WsURL: "ws://geth:8546", - HttpURL: "http://geth:8544", - })). - AddHelm(ethereum.New(nil)). - AddHelm(chainlink.New(0, map[string]any{ - "replicas": 1, - })) - err := e.Run() - if err != nil { - panic(err) - } - e.ClearCharts() - err = e. - AddChart(blockscout.New(&blockscout.Props{ - HttpURL: "http://geth:9000", - })). - AddHelm(ethereum.New(nil)). - AddHelm(chainlink.New(0, map[string]any{ - "replicas": 1, - })). - Run() - if err != nil { - panic(err) - } -} -``` - -## Modifying environment part from code - -We can [modify](examples/modify_helm/env.go) only a part of environment - -```golang -package main - -import ( - "fmt" - "github.com/smartcontractkit/chainlink-testing-framework/k8s/environment" - "github.com/smartcontractkit/chainlink-testing-framework/k8s/pkg/helm/chainlink" - "github.com/smartcontractkit/chainlink-testing-framework/k8s/pkg/helm/ethereum" - "github.com/smartcontractkit/chainlink-testing-framework/k8s/pkg/helm/mockserver" - mockservercfg "github.com/smartcontractkit/chainlink-testing-framework/k8s/pkg/helm/mockserver-cfg" -) - -func main() { - modifiedEnvConfig := &environment.Config{ - NamespacePrefix: "modified-env", - } - - addHardcodedLabelsToEnv(modifiedEnvConfig) - e := environment.New(modifiedEnvConfig). - AddHelm(mockservercfg.New(nil)). - AddHelm(mockserver.New(nil)). - AddHelm(ethereum.New(nil)). - AddHelm(chainlink.New(0, map[string]any{ - "replicas": 1, - })) - err := e.Run() - if err != nil { - panic(err) - } - e.Cfg.KeepConnection = true - e.Cfg.RemoveOnInterrupt = true - e, err = e. - ReplaceHelm("chainlink-0", chainlink.New(0, map[string]any{ - "replicas": 2, - })) - if err != nil { - panic(err) - } - err = e.Run() - if err != nil { - panic(err) - } -} -``` - -# Configuring - -## Environment variables - -List of environment variables available - -```golang -const ( - EnvVarNamespace = "ENV_NAMESPACE" - EnvVarNamespaceDescription = "Namespace name to connect to" - EnvVarNamespaceExample = "chainlink-test-epic" - - // deprecated (for now left for backwards compatibility) - EnvVarCLImage = "CHAINLINK_IMAGE" - EnvVarCLImageDescription = "Chainlink image repository" - EnvVarCLImageExample = "public.ecr.aws/chainlink/chainlink" - - // deprecated (for now left for backwards compatibility) - EnvVarCLTag = "CHAINLINK_VERSION" - EnvVarCLTagDescription = "Chainlink image tag" - EnvVarCLTagExample = "1.5.1-root" - - EnvVarUser = "CHAINLINK_ENV_USER" - EnvVarUserDescription = "Owner of an environment" - EnvVarUserExample = "Satoshi" - - EnvVarTeam = "CHAINLINK_USER_TEAM" - EnvVarTeamDescription = "Team to, which owner of the environment belongs to" - EnvVarTeamExample = "BIX, CCIP, BCM" - - EnvVarCLCommitSha = "CHAINLINK_COMMIT_SHA" - EnvVarCLCommitShaDescription = "The sha of the commit that you're running tests on. Mostly used for CI" - EnvVarCLCommitShaExample = "${{ github.sha }}" - - EnvVarTestTrigger = "TEST_TRIGGERED_BY" - EnvVarTestTriggerDescription = "How the test was triggered, either manual or CI." - EnvVarTestTriggerExample = "CI" - - EnvVarLogLevel = "TEST_LOG_LEVEL" - EnvVarLogLevelDescription = "Environment logging level" - EnvVarLogLevelExample = "info | debug | trace" - - EnvVarSlackKey = "SLACK_API_KEY" - EnvVarSlackKeyDescription = "The OAuth Slack API key to report tests results with" - EnvVarSlackKeyExample = "xoxb-example-key" - - EnvVarSlackChannel = "SLACK_CHANNEL" - EnvVarSlackChannelDescription = "The Slack code for the channel you want to send the notification to" - EnvVarSlackChannelExample = "C000000000" - - EnvVarSlackUser = "SLACK_USER" - EnvVarSlackUserDescription = "The Slack code for the user you want to notify" - EnvVarSlackUserExample = "U000000000" -) -``` - -### Environment config - -```golang -// Config is an environment common configuration, labels, annotations, connection types, readiness check, etc. -type Config struct { - // TTL is time to live for the environment, used with kyverno - TTL time.Duration - // NamespacePrefix is a static namespace prefix - NamespacePrefix string - // Namespace is full namespace name - Namespace string - // Labels is a set of labels applied to the namespace in a format of "key=value" - Labels []string - // PodLabels is a set of labels applied to every pod in the namespace - PodLabels map[string]string - // WorkloadLabels is a set of labels applied to every workload in the namespace - WorkloadLabels map[string]string - // PreventPodEviction if true sets a k8s annotation safe-to-evict=false to prevent pods from being evicted - // Note: This should only be used if your test is completely incapable of handling things like K8s rebalances without failing. - // If that is the case, it's worth the effort to make your test fault-tolerant soon. The alternative is expensive and infuriating. - PreventPodEviction bool - // Allow deployment to nodes with these tolerances - Tolerations []map[string]string - // Restrict deployment to only nodes matching a particular node role - NodeSelector map[string]string - // ReadyCheckData is settings for readiness probes checks for all deployment components - // checking that all pods are ready by default with 8 minutes timeout - // &client.ReadyCheckData{ - // ReadinessProbeCheckSelector: "", - // Timeout: 15 * time.Minute, - // } - ReadyCheckData *client.ReadyCheckData - // DryRun if true, app will just generate a manifest in local dir - DryRun bool - // InsideK8s used for long-running soak tests where you connect to env from the inside - InsideK8s bool - // SkipManifestUpdate will skip updating the manifest upon connecting to the environment. Should be true if you wish to update the manifest (e.g. upgrade pods) - SkipManifestUpdate bool - // KeepConnection keeps connection until interrupted with a signal, useful when prototyping and debugging a new env - KeepConnection bool - // RemoveOnInterrupt automatically removes an environment on interrupt - RemoveOnInterrupt bool - // UpdateWaitInterval an interval to wait for deployment update started - UpdateWaitInterval time.Duration - - // Remote Runner Specific Variables // - // JobImage an image to run environment as a job inside k8s - JobImage string - // Specify only if you want remote-runner to start with a specific name - RunnerName string - // Specify only if you want to mount reports from test run in remote runner - ReportPath string - // JobLogFunction a function that will be run on each log - JobLogFunction func(*Environment, string) - // Test the testing library current Test struct - Test *testing.T - // jobDeployed used to limit us to 1 remote runner deploy - jobDeployed bool - // detachRunner should we detach the remote runner after starting the test - detachRunner bool - // fundReturnFailed the status of a fund return - fundReturnFailed bool -} -``` - -# Utilities - -## Collecting logs - -You can collect the [logs](examples/dump/env.go) while running tests, or if you have created an enrionment [already](#connect-to-environment) - -```golang -package main - -import ( - "github.com/smartcontractkit/chainlink-testing-framework/k8s/environment" - "github.com/smartcontractkit/chainlink-testing-framework/k8s/pkg/helm/chainlink" - "github.com/smartcontractkit/chainlink-testing-framework/k8s/pkg/helm/ethereum" -) - -func main() { - env := &environment.Config{} - - addHardcodedLabelsToEnv(env) - e := environment.New(env). - AddHelm(ethereum.New(nil)). - AddHelm(chainlink.New(0, nil)) - if err := e.Run(); err != nil { - panic(err) - } - if err := e.DumpLogs("logs/mytest"); err != nil { - panic(err) - } -} -``` - -## Resources summary - -It can be useful to get current env [resources](examples/resources/env.go) summary for test reporting - -```golang -package main - -import ( - "fmt" - "github.com/rs/zerolog/log" - "github.com/smartcontractkit/chainlink-testing-framework/k8s/environment" - "github.com/smartcontractkit/chainlink-testing-framework/k8s/pkg" - "github.com/smartcontractkit/chainlink-testing-framework/k8s/pkg/helm/chainlink" - "github.com/smartcontractkit/chainlink-testing-framework/k8s/pkg/helm/ethereum" -) - -func main() { - env := &environment.Config{} - addHardcodedLabelsToEnv(env) - - e := environment.New(env). - AddHelm(ethereum.New(nil)). - AddHelm(chainlink.New(0, nil)) - err := e.Run() - if err != nil { - panic(err) - } - // default k8s selector - summ, err := e.ResourcesSummary("app in (chainlink-0, geth)") - if err != nil { - panic(err) - } - log.Warn().Interface("Resources", summ).Send() - e.Shutdown() -} -``` - -# Chaos - -Check our [tests](https://github.com/smartcontractkit/chainlink/blob/develop/integration-tests/chaos/chaos_test.go) to see how we using Chaosmesh - -# Coverage - -Build your target image with those 2 steps to allow automatic coverage discovery - -```Dockerfile -FROM ... - -# add those 2 steps to instrument the code -RUN curl -s https://api.github.com/repos/qiniu/goc/releases/latest | grep "browser_download_url.*-linux-amd64.tar.gz" | cut -d : -f 2,3 | tr -d \" | xargs -n 1 curl -L | tar -zx && chmod +x goc && mv goc /usr/local/bin -# -o my_service means service will be called "my_service" in goc coverage service -# --center http://goc:7777 means that on deploy, your instrumented service will automatically register to a local goc node inside your deployment (namespace) -RUN goc build -o my_service . --center http://goc:7777 - -CMD ["./my_service"] -``` - -Add `goc` to your deployment, check example with `dummy` service deployment: - -```golang -package main - -import ( - "time" - - "github.com/smartcontractkit/chainlink-testing-framework/k8s/environment" - goc "github.com/smartcontractkit/chainlink-testing-framework/k8s/pkg/cdk8s/goc" - dummy "github.com/smartcontractkit/chainlink-testing-framework/k8s/pkg/cdk8s/http_dummy" -) - -func main() { - envConfig := &environment.Config{} - addHardcodedLabelsToEnv(envConfig) - e := environment.New(envConfig). - AddChart(goc.New()). - AddChart(dummy.New()) - if err := e.Run(); err != nil { - panic(err) - } - // run your test logic here - time.Sleep(1 * time.Minute) - if err := e.SaveCoverage(); err != nil { - panic(err) - } - // clear the coverage, rerun the tests again if needed - if err := e.ClearCoverage(); err != nil { - panic(err) - } -} - -``` - -After tests are finished, coverage is collected for every service, check `cover` directory - -# TOML Config - -Keep in mind that configuring Chainlink image/version & Pyroscope via env vars is deprecated. The latter won't even work anymore. That means that this method should be avoided in new environments. Instead, use the TOML config method described below. - -```golang - AddHelm(chainlink.New(0, nil)) -``` - -It's recommended to use a TOML config file to configure Chainlink and Pyroscope: - -```golang - -// read the config file -config := testconfig.GetConfig("Load", "Automation") - -var overrideFn = func(_ interface{}, target interface{}) { - ctf_config.MustConfigOverrideChainlinkVersion(&config.ChainlinkImage, target) - ctf_config.MightConfigOverridePyroscopeKey(&config.Pyroscope, target) -} - -AddHelm(chainlink.NewWithOverride(0, map[string]interface{}{ - "replicas": 1, -}, &config, overrideFn)) -``` - -Using that will cause the override function to be executed on the default propos thus overriding the default values with the values from the config file. If `config.ChainlinkImage` is `nil` or it's missing either `Image` or `Version` code will panic. If Pyroscope is disabled or key is not set it will be ignored. diff --git a/book/src/lib/k8s/labels.md b/book/src/lib/k8s/labels.md deleted file mode 100644 index 0807bffee..000000000 --- a/book/src/lib/k8s/labels.md +++ /dev/null @@ -1,67 +0,0 @@ -# k8s `chain.link` Labels - -## Purpose -Resource labeling has been introduced to better associate Kubernetes (k8s) costs with products and teams. This document describes the labels used in the k8s cluster. - -## Required Labels -Labels should be applied to all resources in the k8s cluster at three levels: -- **Namespace** -- **Workload** -- **Pod** - -All three levels should include the following labels: -- `chain.link/team` - Name of the team that owns the resource. -- `chain.link/product` - Product that the resource belongs to. -- `chain.link/cost-center` - Product and framework name. - -Additionally, pods should include the following label: -- `chain.link/component` - Name of the component. - -### `chain.link/team` -This label represents the team responsible for the resource, but it might not be the team of the individual who created the resource. It should reflect the team the environment is **created for**. - -For example, if you are a member of the Test Tooling team, but someone from the BIX team requests load tests, the namespace should be labeled as: `chain.link/team: bix`. - -### `chain.link/product` -This label specifies the product the resource belongs to. Internally, some products may have alternative names (e.g., OCR instead of Data Feeds). To standardize data analysis, use the following names: - -``` -automation -bcm -ccip -data-feedsv1.0 -data-feedsv2.0 -data-feedsv3.0 -data-streamsv0.3 -data-streamsv1.0 -deco -functions -proof-of-reserve -scale -staking -vrf -``` - -For example: -- OCR version 1: `data-feedsv1.0` -- OCR version 2: `data-feedsv2.0` - -### `chain.link/cost-center` -This label serves as an umbrella for specific test or environment types and should rarely change. For load or soak tests using solutions provided by the Test Tooling team, use the convention: `test-tooling--test` - -For example: `test-tooling-load-test`. - -This allows easy distinction from load tests run using other tools. - -### `chain.link/component` -This label identifies different components within the same product. Examples include: -- `chainlink` - Chainlink node. -- `geth` - Go-Ethereum blockchain node. -- `test-runner` - Remote test runner. - -## Adding Labels to New Components -Adding a new component to an existing framework is discouraged. The recommended approach is to add the component to CRIB and make these labels part of the deployment templates. - -If you need to add a new component, refer to the following sections in the [k8s Tutorial](./TUTORIAL.md): -- **Creating a new deployment part in Helm** -- **Creating a new deployment part in cdk8s** \ No newline at end of file diff --git a/book/src/lib/k8s_new/environments.md b/book/src/lib/k8s_new/environments.md deleted file mode 100644 index 5ca754d16..000000000 --- a/book/src/lib/k8s_new/environments.md +++ /dev/null @@ -1,287 +0,0 @@ -# Kubernetes - Environments - -As mentioned earlier, `CTFv1` creates `k8s` environments programmatically from existing building blocks. These include: - -- `anvil` -- `blockscout` (cdk8s) -- `chainlink node` -- `geth` -- `goc` (cdk8s) -- `grafana` -- `influxdb` -- `kafka` -- `mock-adapter` -- `mockserver` -- `reorg controller` -- `schema registry` -- `solana validator` -- `starknet validator` -- `wiremock` - -Unless noted otherwise, all components are based on `Helm` charts. - -> [!NOTE] -> The process of creating new environments or modifying existing ones is explained in detail [here](../k8s/TUTORIAL.md). This document focuses on a practical example of creating a new `k8s` test environment with a basic setup. -> -> **It is highly recommended to read that tutorial before proceeding.** - ---- - -## Example: Basic Testing Environment - -We will create a simple testing environment consisting of: -- 6 `Chainlink nodes` -- 1 blockchain node (`go-ethereum`, aka `geth`) - ---- - -### Step 1: Create Chainlink Node TOML Config - -In real-world scenarios, you should dynamically generate or load Chainlink node configurations to suit your needs. For simplicity, we will use a hardcoded configuration: - -```go -func TestSimpleDONWithLinkContract(t *testing.T) { - tomlConfig := `[Feature] -FeedsManager = true -LogPoller = true -UICSAKeys = true - -[Database] -MaxIdleConns = 20 -MaxOpenConns = 40 -MigrateOnStartup = true - -[Log] -Level = "debug" -JSONConsole = true - -[Log.File] -MaxSize = "0b" - -[WebServer] -AllowOrigins = "*" -HTTPWriteTimeout = "3m0s" -HTTPPort = 6688 -SecureCookies = false -SessionTimeout = "999h0m0s" - -[WebServer.RateLimit] -Authenticated = 2000 -Unauthenticated = 1000 - -[WebServer.TLS] -HTTPSPort = 0 - -[OCR] -Enabled = true - -[P2P] - -[P2P.V2] -ListenAddresses = ["0.0.0.0:6690"] - -[[EVM]] -ChainID = "1337" -AutoCreateKey = true -FinalityDepth = 1 -MinContractPayment = "0" - -[EVM.GasEstimator] -PriceMax = "200 gwei" -LimitDefault = 6000000 -FeeCapDefault = "200 gwei" - -[[EVM.Nodes]] -Name = "Simulated Geth-0" -WSURL = "ws://geth:8546" -HTTPURL = "http://geth:8544"` -``` - -This configuration enables the log poller and OCRv2 features while connecting to an EVM chain with `ChainID` `1337`. It uses the following RPC URLs: -- WebSocket: `ws://geth:8546` -- HTTP: `http://geth:8544` - -These URLs correspond to the default ports for `geth` and match the `go-ethereum` service name in the `k8s` cluster. - ---- - -### Step 2: Define the Chainlink Deployment - -To define the Chainlink deployment, we configure the image, version, and other parameters such as replicas and database settings. Here's the detailed implementation: - -```go -chainlinkImageCfg := &ctf_config.ChainlinkImageConfig{ - Image: ptr.Ptr("public.ecr.aws/chainlink/chainlink"), - Version: ptr.Ptr("2.19.0"), -} - -var overrideFn = func(_ interface{}, target interface{}) { - ctf_config.MustConfigOverrideChainlinkVersion(chainlinkImageCfg, target) -} - -cd := chainlink.NewWithOverride(0, map[string]any{ - "replicas": 6, // Number of Chainlink nodes - "toml": tomlConfig, // TOML configuration defined earlier - "db": map[string]any{ - "stateful": true, // Use stateful databases for tests - }, -}, chainlinkImageCfg, overrideFn) -``` - -**Key Details:** -- **Image and Version:** These are hardcoded here for simplicity but should ideally be configurable for different environments. -- **Replicas:** We specify 6 Chainlink nodes to simulate a multi-node setup. -- **Database Configuration:** The database is stateful to allow for persistence during soak tests. -- **Override Function:** This ensures that the specified image and version are applied to all Chainlink node deployments. - ---- - -### Step 3: Label Resources - -To track costs effectively, add required `chain.link` labels to all `k8s` resources: - -```go -productName := "data-feedsv1.0" -nsLabels, err := environment.GetRequiredChainLinkNamespaceLabels(productName, "soak") -if err != nil { - t.Fatal("Error creating namespace labels", err) -} - -workloadPodLabels, err := environment.GetRequiredChainLinkWorkloadAndPodLabels(productName, "soak") -if err != nil { - t.Fatal("Error creating workload and pod labels", err) -} -``` - -Set the following environment variables: -- `CHAINLINK_ENV_USER`: Name of the person running the test. -- `CHAINLINK_USER_TEAM`: Name of the team the test is for. - ---- - -### Step 4: Create Environment Config - -```go -baseEnvironmentConfig := &environment.Config{ - TTL: time.Hour * 2, - NamespacePrefix: "my-namespace-prefix", - Test: t, - PreventPodEviction: true, - Labels: nsLabels, - WorkloadLabels: workloadPodLabels, - PodLabels: workloadPodLabels, -} -``` - -**Key Fields:** -- **`TTL`**: Time-to-live for the namespace (auto-removal after this time). -- **`NamespacePrefix`**: Ensures unique namespace names. -- **`PreventPodEviction`**: Prevents pods from being evicted or restarted. - ---- - -### Step 5: Define Blockchain Network - -To set up the blockchain network, we use predefined properties for a simulated EVM network. Here's the detailed implementation: - -```go -nodeNetwork := blockchain.SimulatedEVMNetwork - -ethProps := ðereum.Props{ - NetworkName: nodeNetwork.Name, // Name of the network - Simulated: nodeNetwork.Simulated, // Indicates that the network is simulated - WsURLs: nodeNetwork.URLs, // WebSocket URLs for the network - HttpURLs: nodeNetwork.HTTPURLs, // HTTP URLs for the network -} -``` - -**Details:** -- **Simulated Network:** Represents a private, ephemeral blockchain used for testing. -- **Dynamic Selection:** In real scenarios, use helper functions to dynamically select networks (public, private, or simulated) based on test requirements. -- **Custom URLs:** The `ethereum` chart requires explicit settings for the network name and URLs. - ---- - -### Step 6: Build the Environment - -```go -testEnv := environment.New(baseEnvironmentConfig). - AddHelm(ethereum.New(ethProps)). // Blockchain node - AddHelm(cd) // Chainlink nodes - -err = testEnv.Run() -if err != nil { - t.Fatal("Error running environment", err) -} -``` - ---- - -### Step 7: Create Blockchain Client - -```go -if !testEnv.Cfg.InsideK8s { - wsURLs := testEnv.URLs[blockchain.SimulatedEVMNetwork.Name] - httpURLs := testEnv.URLs[blockchain.SimulatedEVMNetwork.Name+"_http"] - if len(wsURLs) == 0 || len(httpURLs) == 0 { - t.Fatal("Forwarded Geth URLs should not be empty") - } - nodeNetwork.URLs = wsURLs - nodeNetwork.HTTPURLs = httpURLs -} - -sethClient, err := seth.NewClientBuilder(). - WithRpcUrl(nodeNetwork.URLs[0]). - WithPrivateKeys([]string{nodeNetwork.PrivateKeys[0]}). - Build() -if err != nil { - t.Fatal("Error creating Seth client", err) -} -``` - -**Details:** -- **Local vs. Cluster Environment**: When running tests outside the k8s cluster, the service URLs (`ws://geth:8546`, `http://geth:8544`) are not directly accessible. Port forwarding ensures local access to these services. -- **Automatic Port Forwarding**: The `Environment` object manages forwarding for key services, including Geth in simulated mode, making these forwarded URLs available in the `URLs` map. -- **Dynamic Rewriting**: URLs are dynamically rewritten to switch between in-cluster and local connectivity. - ---- - -### Step 8: Deploy LINK Contract - -```go -linkTokenAbi, err := link_token_interface.LinkTokenMetaData.GetAbi() -if err != nil { - t.Fatal("Error getting LinkToken ABI", err) -} - -linkDeploymentData, err := sethClient.DeployContract(sethClient.NewTXOpts(), "LinkToken", *linkTokenAbi, common.FromHex(link_token_interface.LinkTokenMetaData.Bin)) -if err != nil { - t.Fatal("Error deploying LinkToken contract", err) -} - -linkToken, err := link_token_interface.NewLinkToken(linkDeploymentData.Address, sethClient.Client) -if err != nil { - t.Fatal("Error creating LinkToken contract instance", err) -} - -totalSupply, err := linkToken.TotalSupply(sethClient.NewCallOpts()) -if err != nil { - t.Fatal("Error getting total supply of LinkToken", err) -} -if totalSupply.Cmp(big.NewInt(0)) <= 0 { - t.Fatal("Total supply of LinkToken should be greater than 0") -} -``` - -**Details:** -- **Deploy Contract:** Deploys the LINK token contract to the simulated blockchain. -- **Verify Deployment:** Ensures the total supply is greater than zero as a sanity check. - ---- - -### Next Steps - -Learn how to run long-duration tests using a `remote runner` in the [next chapter](./remote_runner.md). - -> [!NOTE] -> This example can be found [here](https://github.com/smartcontractkit/chainlink-testing-framework/tree/main/lib/k8s/examples/link/link_test.go). \ No newline at end of file diff --git a/book/src/lib/k8s_new/overview.md b/book/src/lib/k8s_new/overview.md deleted file mode 100644 index 2a069d34c..000000000 --- a/book/src/lib/k8s_new/overview.md +++ /dev/null @@ -1,29 +0,0 @@ -# Kubernetes - -> [!WARNING] -> It is highly recommended to use [CRIB](https://github.com/smartcontractkit/crib) for `k8s` deployments. -> Avoid running long tests that are impractical to execute locally or through CI pipelines. -> -> **Proceed at your own risk.** - ---- - -## Overview - -The `CTFv1` tool builds `k8s` environments **programmatically** using either `Helm` or `cdk8s` charts. This approach introduces significant complexity to the deployment process. - -To manage long-running tests, `CTFv1` utilizes a `remote runner`, which is essentially a Docker container containing the test logic. This container is deployed as a `cdk8s`-based chart, creating a `k8s` resource of type `job` that runs the test in a detached manner. This setup requires custom logic to integrate with the test framework. - ---- - -## What We’ll Cover - -1. Creating a simplified `k8s` environment. -2. Adding a basic test that: - - Deploys a smart contract. - - Supports the `remote runner` capability. -3. Building a Docker image for the test and configuring the required environment variables. - ---- - -Are you ready to get started? diff --git a/book/src/lib/k8s_new/remote_runner.md b/book/src/lib/k8s_new/remote_runner.md deleted file mode 100644 index 25cc40fea..000000000 --- a/book/src/lib/k8s_new/remote_runner.md +++ /dev/null @@ -1,250 +0,0 @@ -# Kubernetes - Using Remote Runner - -In this chapter, we explain how to run a test in `k8s` and the changes required in the test logic to support it. - ---- - -## Overview - -The general process of running tests with a `remote runner` involves: - -1. Creating a `k8s` environment from a local machine (either your local machine or a CI runner). - - The environment launches a `remote runner` using the `ENV_JOB_IMAGE` environment variable. -2. The `remote runner` re-executes the same test code from the beginning. - - It detects that the environment is already deployed and skips redeploying it. -3. After the `remote runner` completes its test execution, control returns to the local test execution. - - The local test exits early to prevent duplicate execution. -4. If running in `detached mode`, control returns to the local test as soon as the remote test starts. - - The local test exits immediately. - - The `remote runner` continues running in `k8s` until the test completes. - -Following diagram explains it in a visual way: -```mermaid -sequenceDiagram - actor User as User - participant LocalTest as Local Test - participant K8sEnv as Kubernetes Environment - participant RemoteRunner as Remote Runner - - User->>LocalTest: Start test execution - LocalTest->>K8sEnv: Create environment - K8sEnv-->>LocalTest: Environment created - LocalTest->>K8sEnv: Start remote runner job - K8sEnv-->>RemoteRunner: Deploy remote runner - - alt DETACHED_MODE=true - RemoteRunner-->>K8sEnv: Begin test logic execution - LocalTest-->>User: Exit after remote runner starts - Note right of LocalTest: Detaches after
remote runner starts - else DETACHED_MODE=false - RemoteRunner-->>K8sEnv: Begin test logic execution - RemoteRunner->>K8sEnv: Complete test logic execution - K8sEnv-->>LocalTest: Notify test completion - LocalTest-->>User: Exit after test completion - end - - Note right of RemoteRunner: Executes the test
logic independently -``` - -Although this may seem complex, the necessary changes to the test logic ensure that tests execute correctly without duplications. In the following steps, we explain where and why you should add conditionals and early exits to prevent unwanted re-execution. - -> [!NOTE] -> We are developing a new `k8s` test runner that eliminates the need for `k8s`-specific logic. This will allow tests to run seamlessly both locally and remotely. Documentation for this is available [here](../../k8s-test-runner/k8s-test-runner.md). - ---- - -## Requirements - -The `remote runner` requires a Docker image containing your test code. There are multiple ways to build this image, some of which are automated in our repositories. For this documentation, we will build it from scratch. - -> [!NOTE] -> The CTF repository builds a base testing image for each release using [this action](https://github.com/smartcontractkit/chainlink-testing-framework/actions/workflows/k8s-publish-test-base-image.yaml). This base image includes `kubectl`, `helm`, and other dependencies. Use it as a base image for your final test image. You only need to copy your compiled Go tests and set the entrypoint to execute them. An example from the Chainlink repository can be found [here](https://github.com/smartcontractkit/chainlink/actions/workflows/on-demand-ocr-soak-test.yml). - ---- - -## Step 1: Build a Docker Image with Your Tests - -Define a `Dockerfile`: - -```dockerfile -# Base image for all k8s test runs -FROM golang:1.23-bullseye - -ARG GOARCH -ARG GOOS -ARG BASE_URL -ARG HELM_VERSION -ARG HOME -ARG KUBE_VERSION -ARG NODE_VERSION - -# Compile Go binary targeting linux/amd64, used by k8s runners -ENV GOOS="linux" -ENV GOARCH="amd64" -ENV BASE_URL="https://get.helm.sh" -ENV HELM_VERSION="3.10.3" -ENV KUBE_VERSION="v1.25.5" -ENV NODE_VERSION=18 - -# Install dependencies -RUN apt-get update && apt-get install -y \ - ca-certificates wget curl git gnupg zip && \ - mkdir -p /etc/apt/keyrings && \ - curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg && \ - echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_$NODE_VERSION.x nodistro main" | tee /etc/apt/sources.list.d/nodesource.list && \ - apt-get update && apt-get install -y nodejs && \ - curl -LO https://storage.googleapis.com/kubernetes-release/release/$(curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt)/bin/linux/amd64/kubectl && \ - chmod +x ./kubectl && mv ./kubectl /usr/local/bin && \ - ARCH=$(uname -m | sed 's/x86_64/amd64/;s/aarch64/arm64/') && \ - wget ${BASE_URL}/helm-v${HELM_VERSION}-linux-${ARCH}.tar.gz -O - | tar -xz && \ - mv linux-${ARCH}/helm /usr/bin/helm && chmod +x /usr/bin/helm && rm -rf linux-${ARCH} && \ - npm install -g yarn && apt-get clean && \ - helm repo add chainlink-qa https://raw.githubusercontent.com/smartcontractkit/qa-charts/gh-pages/ && \ - helm repo add bitnami https://charts.bitnami.com/bitnami && helm repo update - -# Install AWS CLI v2 -RUN ARCH=$(uname -m | sed 's/x86_64/x86_64/;s/aarch64/aarch64/') && \ - curl https://awscli.amazonaws.com/awscli-exe-linux-${ARCH}.zip -o "awscliv2.zip" && \ - unzip awscliv2.zip && ./aws/install && rm -rf awscliv2.zip - -COPY lib/ testdir/ - -# Compile Go tests -WORKDIR /go/testdir/k8s/examples/link -RUN go test -c . -o link - -# Entrypoint to run tests -ENTRYPOINT ["./link"] -``` - -**Installed Dependencies:** -- `kubectl` (to interact with `k8s`) -- `nodejs` -- `helm` (for environment setup) -- `AWS CLI v2` (if interacting with AWS `k8s` clusters) - -> [!NOTE] -> If using local Helm charts instead of published ones, set `LOCAL_CHARTS=true`. Local here means onces stored in the Docker image -> in the `lib/charts` folder. - ---- - -## Step 2: Build the Image - -Build the Docker image from the root directory of the CTF repository: - -```bash -docker build -f lib/k8s/examples/link/Dockerfile \ - --platform=linux/amd64 \ - -t link-test:latest . -``` - -> [!NOTE] -> The `--platform=linux/amd64` parameter ensures compatibility with k8s runners. - ---- - -## Step 3: Test the Image - -Before modifying the test logic for `remote runner` compatibility, test the image locally. Ensure you have access to a `k8s` cluster (local or remote). Configure `kubectl` to use the cluster and authenticate if necessary. - -Run the test image: - -```bash -docker run \ - --rm \ - -v ~/.aws:/root/.aws:ro \ - -v ~/.kube/config:/root/.kube/config:ro \ - -e AWS_PROFILE= \ - -e KUBECONFIG=/root/.kube/config \ - link-test:latest -``` - -This mounts the local `.aws` directory and `kubectl` configuration to the container, allowing it to access the cluster. Verify the test completes successfully. - ---- - -## Step 4: Make the Test `Remote Runner`-Compatible - -To adapt the test for `remote runner` execution, you need to divide the test logic into: - -1. **Local Execution Logic**: Responsible for setting up the environment and initiating the `remote runner`. -2. **Remote Execution Logic**: Handles the actual test operations, such as deploying contracts or generating workload. - -### Key Adjustments - -- **Check for Remote Execution**: Use the `testEnv.WillUseRemoteRunner()` function to determine if the test will run in the `remote runner`. If it will, ensure any non-idempotent operations, like test logic execution, are skipped in the local context. -- **Prevent Test Logic Duplication**: Exit the local test execution after initiating the remote runner to avoid running the test logic both locally and remotely. - -### Updated Example - -Here is how you can make these changes: - -```go -err = testEnv.Run() -if err != nil { - t.Fatal("Error running environment: ", err) -} - -if testEnv.WillUseRemoteRunner() { - log.Info().Msg("Stopping local execution as test will continue in the remote runner") - return // Exit early to allow the remote runner to take over -} - -// Additional test logic (if applicable) runs only in the remote runner -``` - -### Why These Changes Are Necessary - -- **Environment Duplication**: Ensures that test logic is executed only once, within the `remote runner`, and not both locally and remotely. - ---- - -## Step 5: Rebuild the image -Rebuild the image to include `remote runner`-related changes. - -## Step 6: Push Image to Registry (Optional) - -If using a remote cluster, push the image to a registry like AWS ECR: - -```bash -aws ecr get-login-password --region | docker login \ - --username AWS --password-stdin .dkr.ecr..amazonaws.com - -docker tag link-test:latest \ - .dkr.ecr..amazonaws.com//link-remote-runner-test:latest - -docker push .dkr.ecr..amazonaws.com//link-remote-runner-test:latest -``` - ---- - -## Step 7: Run with `Remote Runner` - -Run the test in `detached mode` by setting these environment variables: -- `DETACH_RUNNER=true` -- `ENV_JOB_IMAGE=` (one we created in step 5) - -Command: - -```bash -docker run \ - --rm \ - -v ~/.aws:/root/.aws:ro \ - -v ~/.kube/config:/root/.kube/config:ro \ - -e DETACH_RUNNER=true \ - -e ENV_JOB_IMAGE= \ - -e AWS_PROFILE= \ - -e KUBECONFIG=/root/.kube/config \ - -``` - -> [!NOTE] -> You may also need to pass secrets to your test. Refer to the [Test Secrets Documentation](./test_secrets.md) for guidance on securely managing and injecting secrets into your tests. - -The local test detaches after starting the remote runner. Logs from the remote runner can be checked for test progress. - ---- - -You can find the complete example [here](https://github.com/smartcontractkit/chainlink-testing-framework/tree/main/lib/k8s/examples/link). - diff --git a/book/src/lib/k8s_new/test_secrets.md b/book/src/lib/k8s_new/test_secrets.md deleted file mode 100644 index 82ae1b4d3..000000000 --- a/book/src/lib/k8s_new/test_secrets.md +++ /dev/null @@ -1,36 +0,0 @@ -# Kubernetes - Test Secrets - -We all have our secrets, don't we? It's the same case with tests... and since some of our repositories are public, we need to take special precautions to protect them. - -> [!WARNING] -> Before continuing, you should read the [test secrets section of the CTF configuration documentation](../config/config.md). - -## Overview - -In general, your `remote runner` will need access to the same secrets as your local test. Fortunately, these secrets are forwarded automatically and securely as long as their names have the prefix `E2E_TEST_`. - -To make a secret available to the `remote runner`, simply pass it to the `docker run` command: - -```bash -docker run \ - --rm \ - -v ~/.aws:/root/.aws:ro \ - -v ~/.kube/config:/root/.kube/config:ro \ - -e DETACH_RUNNER=true \ - -e E2E_TEST_MY_SECRET=my-secret \ - -e ENV_JOB_NAME="" \ - -e AWS_PROFILE= \ - -e KUBECONFIG=/root/.kube/config \ - -``` - -The secret will then be available to the `remote runner` during its execution. - ---- - -## Important Considerations - -> [!WARNING] -> **Do not use this method of passing secrets in CI environments.** Exposing secrets in this way can compromise their security. -> -> When running `k8s` tests in CI pipelines, use dedicated actions or reusable workflows designed to handle secrets securely. \ No newline at end of file diff --git a/book/src/lib/logging.md b/book/src/lib/logging.md deleted file mode 100644 index 8acf67bbd..000000000 --- a/book/src/lib/logging.md +++ /dev/null @@ -1,23 +0,0 @@ -# Logging - -This small library was created to address two issues: -* mixed up logging for parallel tests, when using vanilla loggers -* conformity with logging interface required by `testcontainers-go` (a Docker container library) - -It uses `"github.com/rs/zerolog"` for the logger. - -## Configuration -There's only one configuration option: the log level. You can set it via `TEST_LOG_LEVEL` environment variable to: -* `trace` -* `debug` -* `info` (default) -* `warn` -* `error` - -## How to use -The main way to get a Logger instance is to call `logging.GetTestLogger(*testing.T)`. `testing.T` instance can be `nil`. - -When using it together with `testcontainers-go`, which is a library we use to interact with Docker containers you should -use `GetTestContainersGoTestLogger(*testing.T)` instead. - -And that's all there is to it :-) \ No newline at end of file diff --git a/book/src/lib/wasp/how-to/run_included_tests.md b/book/src/lib/wasp/how-to/run_included_tests.md deleted file mode 100644 index c8d06ae17..000000000 --- a/book/src/lib/wasp/how-to/run_included_tests.md +++ /dev/null @@ -1 +0,0 @@ -# Try it out quickly diff --git a/book/src/libraries.md b/book/src/libraries.md index 671c2bc8c..03da0d719 100644 --- a/book/src/libraries.md +++ b/book/src/libraries.md @@ -1,4 +1,4 @@ -# Libraries +# Overview CTF monorepository contains a set of libraries: From 1a0d4f9059854e8b908fa4690ddc4190b3db25a3 Mon Sep 17 00:00:00 2001 From: skudasov Date: Thu, 10 Jul 2025 11:58:51 +0200 Subject: [PATCH 2/5] try render --- .github/workflows/docs.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 26f9470ef..e42acbcab 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -3,10 +3,10 @@ name: GH Pages Deploy (Docs, mdbook) on: push: - branches: - - main - tags: - - '*' +# branches: +# - main +# tags: +# - '*' jobs: build-deploy: From f9d5ffaac7d963f3e8792d5b5a64c1884372cfa3 Mon Sep 17 00:00:00 2001 From: skudasov Date: Thu, 10 Jul 2025 12:28:36 +0200 Subject: [PATCH 3/5] move CTFv1 to legacy --- book/src/SUMMARY.md | 32 +- book/src/legacy.md | 2 +- book/src/lib/blockchain.md | 45 ++ book/src/lib/client.md | 11 + book/src/lib/client/aws_secrets_manager.md | 33 + book/src/lib/client/github.md | 28 + book/src/lib/client/grafana.md | 80 ++ book/src/lib/client/kafka.md | 19 + book/src/lib/client/loki.md | 63 ++ book/src/lib/client/mockserver.md | 120 +++ book/src/lib/client/postgres.md | 44 ++ book/src/lib/client/prometheus.md | 44 ++ book/src/lib/concurrency.md | 95 +++ book/src/lib/config/config.md | 363 +++++++++ book/src/lib/crib.md | 7 + book/src/lib/docker/blockchain_nodes.md | 202 +++++ book/src/lib/docker/chainlink_ecosystem.md | 49 ++ book/src/lib/docker/overview.md | 68 ++ book/src/lib/docker/test_helpers.md | 10 + book/src/lib/k8s/KUBERNETES.md | 36 + book/src/lib/k8s/REMOTE_RUN.md | 48 ++ book/src/lib/k8s/TUTORIAL.md | 743 ++++++++++++++++++ book/src/lib/k8s/labels.md | 67 ++ book/src/lib/k8s_new/environments.md | 287 +++++++ book/src/lib/k8s_new/overview.md | 29 + book/src/lib/k8s_new/remote_runner.md | 250 ++++++ book/src/lib/k8s_new/test_secrets.md | 36 + book/src/lib/logging.md | 23 + .../src/lib/wasp/how-to/run_included_tests.md | 1 + 29 files changed, 2832 insertions(+), 3 deletions(-) create mode 100644 book/src/lib/blockchain.md create mode 100644 book/src/lib/client.md create mode 100644 book/src/lib/client/aws_secrets_manager.md create mode 100644 book/src/lib/client/github.md create mode 100644 book/src/lib/client/grafana.md create mode 100644 book/src/lib/client/kafka.md create mode 100644 book/src/lib/client/loki.md create mode 100644 book/src/lib/client/mockserver.md create mode 100644 book/src/lib/client/postgres.md create mode 100644 book/src/lib/client/prometheus.md create mode 100644 book/src/lib/concurrency.md create mode 100644 book/src/lib/config/config.md create mode 100644 book/src/lib/crib.md create mode 100644 book/src/lib/docker/blockchain_nodes.md create mode 100644 book/src/lib/docker/chainlink_ecosystem.md create mode 100644 book/src/lib/docker/overview.md create mode 100644 book/src/lib/docker/test_helpers.md create mode 100644 book/src/lib/k8s/KUBERNETES.md create mode 100644 book/src/lib/k8s/REMOTE_RUN.md create mode 100644 book/src/lib/k8s/TUTORIAL.md create mode 100644 book/src/lib/k8s/labels.md create mode 100644 book/src/lib/k8s_new/environments.md create mode 100644 book/src/lib/k8s_new/overview.md create mode 100644 book/src/lib/k8s_new/remote_runner.md create mode 100644 book/src/lib/k8s_new/test_secrets.md create mode 100644 book/src/lib/logging.md create mode 100644 book/src/lib/wasp/how-to/run_included_tests.md diff --git a/book/src/SUMMARY.md b/book/src/SUMMARY.md index 7997df380..e725cec9e 100644 --- a/book/src/SUMMARY.md +++ b/book/src/SUMMARY.md @@ -103,12 +103,40 @@ - [Debug Loki errors](./libs/wasp/how-to/debug_loki_errors.md) - [Havoc](./libs/havoc.md) - [Seth](./libs/seth.md) - - [AWS Secrets Manager]() - [Sentinel](./libs/sentinel.md) --- -- [Legacy]() +- [Legacy](./legacy.md) - [Overview](./legacy.md) + - [CTFv1](lib.md) + - [Blockchain](lib/blockchain.md) + - [Concurrency](lib/concurrency.md) + - [Client](lib/client.md) + - [Anvil]() + - [AWS Secrets Manager](lib/client/aws_secrets_manager.md) + - [Github](lib/client/github.md) + - [Grafana](lib/client/grafana.md) + - [Kafka](lib/client/kafka.md) + - [Loki](lib/client/loki.md) + - [MockServer](lib/client/mockserver.md) + - [Postgres](lib/client/postgres.md) + - [Prometheus](lib/client/prometheus.md) + - [Kubernetes](lib/k8s_new/overview.md) + - [Creating environments](lib/k8s_new/environments.md) + - [Using remote runner](lib/k8s_new/remote_runner.md) + - [Passing test secrets](lib/k8s_new/test_secrets.md) + - [chain.link labels](lib/k8s/labels.md) + - [Kubernetes (legacy docs)](lib/k8s/KUBERNETES.md) + - [K8s Remote Run](lib/k8s/REMOTE_RUN.md) + - [K8s Tutorial](lib/k8s/TUTORIAL.md) + - [Config](lib/config/config.md) + - [CRIB Connector](lib/crib.md) + - [Docker](lib/docker/overview.md) + - [Blockchain nodes](lib/docker/blockchain_nodes.md) + - [Chainlink ecosystem](lib/docker/chainlink_ecosystem.md) + - [Third party apps]() + - [Test helpers](lib/docker/test_helpers.md) + - [Logging](lib/logging.md) - [K8s Test Runner](k8s-test-runner/k8s-test-runner.md) --- diff --git a/book/src/legacy.md b/book/src/legacy.md index cf5ba8e24..c1dcda801 100644 --- a/book/src/legacy.md +++ b/book/src/legacy.md @@ -1,3 +1,3 @@ # Legacy -This chapter contains legacy documentation or code examples that will be sunset in the next release +This chapter contains legacy documentation or code examples that will be sunset in the next major release diff --git a/book/src/lib/blockchain.md b/book/src/lib/blockchain.md new file mode 100644 index 000000000..5100655fa --- /dev/null +++ b/book/src/lib/blockchain.md @@ -0,0 +1,45 @@ +# Blockchain Clients + +
+ +This documentation is deprecated, we are using it in [Chainlink Integration Tests](https://github.com/smartcontractkit/chainlink/tree/develop/integration-tests) + +If you want to test our new products use [v2](../framework/overview.md) +
+ +This folder contains the bulk of code that handles integrating with different EVM chains. If you're looking to run tests on a new EVM chain, and are having issues with the default implementation, you've come to the right place. + +### Some Terminology + +- [L2 Chain](https://ethereum.org/en/layer-2/): A Layer 2 chain "branching" off Ethereum. +- [EVM](https://ethereum.org/en/developers/docs/evm/): Ethereum Virtual Machine that underpins the Ethereum blockchain. +- [EVM Compatible](https://blog.thirdweb.com/evm-compatible-blockchains-and-ethereum-virtual-machine/#:~:text=What%20does%20'EVM%20compatibility'%20mean,significant%20changes%20to%20their%20code.): A chain that has some large, underlying differences from how base Ethereum works, but can still be interacted with largely the same way as Ethereum. +- [EIP-1559](https://eips.ethereum.org/EIPS/eip-1559): The Ethereum Improvement Proposal that changed how gas fees are calculated and paid on Ethereum +- Legacy Transactions: Transactions that are sent using the old gas fee calculation method, the one used before EIP-1559. +- Dynamic Fee Transaction: Transactions that are sent using the new gas fee calculation method, the one used after EIP-1559. + +## How Client Integrations Work + +In order to test Chainlink nodes, the `chainlink-testing-framework` needs to be able to interact with the chain that the node is running on. This is done through the `blockchain.EVMClient` interface. The `EVMClient` interface is a wrapper around [geth](https://geth.ethereum.org/) to interact with the blockchain. We conduct all our testing blockchain operations through this wrapper, like sending transactions and monitoring on-chain events. The primary implementation of this wrapper is built for [Ethereum](./ethereum.go). Most others, like the [Metis](./metis.go) and [Optimism](./optimism.go) integrations, extend and modify the base Ethereum implementation. + +## Do I Need a New Integration? + +If you're reading this, probably. The default EVM integration is designed to work with mainnet Ethereum, which covers most other EVM chain interactions, but it's not guaranteed to work with all of them. If you're on a new chain and the test framework is throwing errors while doing basic things like send transactions, receive new headers, or deploy contracts, you'll likely need to create a new integration. The most common issue with new chains (especially L2s) is gas estimations and lack of support for dynamic transactions. + +## Creating a New Integration + +Take a look at the [Metis](./metis.go) integration as an example. Metis is an L2, EVM compatible chain. It's largely the same as the base Ethereum integration, so we'll extend from that. + +```go +type MetisMultinodeClient struct { + *EthereumMultinodeClient +} + +type MetisClient struct { + *EthereumClient +} +``` + +Now we need to let other libraries (like our tests in the main Chainlink repo) that this integration exists. So we add the new implementation to the [known_networks.go](https://github.com/smartcontractkit/chainlink-testing-framework/blob/main/lib/blockchain/known_networks.go) file. We can then add that network to our tests' own [known_networks.go](https://github.com/smartcontractkit/chainlink/blob/develop/integration-tests/known_networks.go) file (it's annoying, there are plans to simplify). + +Now our Metis integration is the exact same as our base Ethereum one, which doesn't do us too much good. I'm assuming you came here to make some changes, so first let's find out what we need to change. This is a mix of reading developer documentation on the chain you're testing and trial and error. Mostly the latter in later stages. In the case of Metis, like many L2s, they [have their own spin on gas fees](https://docs.metis.io/dev/protocol-in-detail/transaction-fees-on-the-metis-platform). They also only support Legacy transactions. So we'll need to override any methods that deal with gas estimations, `Fund`, `DeployContract`, and `ReturnFunds`. diff --git a/book/src/lib/client.md b/book/src/lib/client.md new file mode 100644 index 000000000..cc46e7190 --- /dev/null +++ b/book/src/lib/client.md @@ -0,0 +1,11 @@ +# Client + +We support a variety of clients that ease communication with following applications: +* [Anvil](./client/anvil.md) +* [AWS Secrets Manager](./client/aws_secrets_manager.md) +* [Github](./client/github.md) +* [Kafka](./client/kafka.md) +* [Loki](./client/loki.md) +* [MockServer](./client/mockserver.md) +* [Postgres](./client/postgres.md) +* [Prometheus](./client/prometheus.md) \ No newline at end of file diff --git a/book/src/lib/client/aws_secrets_manager.md b/book/src/lib/client/aws_secrets_manager.md new file mode 100644 index 000000000..54cf899d8 --- /dev/null +++ b/book/src/lib/client/aws_secrets_manager.md @@ -0,0 +1,33 @@ +# AWS Secrets Manager + +This simple client makes it even easier to: +* read +* create +* remove secrets from AWS Secrets Manager. + +Creating a new instance is straight-forward. You should either use environment variables or shared configuration and credentials. + +> [!NOTE] +> Environment variables take precedence over shared credentials. + +## Using environment variables +You can pass required configuration as following environment variables: +* `AWS_ACCESS_KEY_ID` +* `AWS_SECRET_ACCESS_KEY` +* `AWS_REGION` + +## Using shared credentials +If you have shared credentials stored in `.aws/credentials` file, then the easiest way to configure the client is by setting +`AWS_PROFILE` environment variable with the profile name. If that environment variable is not set, the SDK will try to use default profile. + +> [!WARNING] +> Remember, that most probably you will need to manually create a new session for that profile before running your application. + + +> [!NOTE] +> You can read more about configuring the AWS SDK [here](https://docs.aws.amazon.com/sdk-for-go/v1/developer-guide/configuring-sdk.html). + +Once you have an instance of AWS Secrets Manager you gain access to following functions: +* `CreateSecret(key string, val string, override bool) error` +* `GetSecret(key string) (AWSSecret, error)` +* `RemoveSecret(key string, noRecovery bool) error` \ No newline at end of file diff --git a/book/src/lib/client/github.md b/book/src/lib/client/github.md new file mode 100644 index 000000000..aab94058f --- /dev/null +++ b/book/src/lib/client/github.md @@ -0,0 +1,28 @@ +# Github + +This small client makes it easy to get `N` latest releases or tags from any Github.com repository. To use it, all you need to have +is a properly scoped access token. + +```go +publicRepoClient := NewGithubClient(WITHOUT_TOKEN) + +// "smartcontractkit", "chainlink" +latestCLReleases, err := publicRepoClient.ListLatestCLCoreReleases(10) +if err != nil { + panic(err) +} + +// "smartcontractkit", "chainlink" +latestCLTags, err := publicRepoClient.ListLatestCLCoreTags(10) +if err != nil { + panic(err) +} + +privateRepoClient := NewGithubClient("my-secret-PAT") +myLatestReleases, err := privateRepoClient.ListLatestReleases("my-org", "my-private-repo", 5) +if err != nil { + panic(err) +} +``` + +There's really not much more to it... \ No newline at end of file diff --git a/book/src/lib/client/grafana.md b/book/src/lib/client/grafana.md new file mode 100644 index 000000000..c66f4329c --- /dev/null +++ b/book/src/lib/client/grafana.md @@ -0,0 +1,80 @@ +# Grafana + +> [!NOTE] +> Contrary to other clients, you will find Grafana client in a [separate package & go module](https://github.com/smartcontractkit/chainlink-testing-framework/tree/main/lib/grafana). + +Grafana client encapsulate following functionalities: +* Dashboard creation +* Managing dashboard annotations (CRUD) +* Checking alerts + +# New instance +In order to create a new instance you will need: +* URL +* API token + +For example: +```go +url := "http://grafana.io" +apiToken := "such-a-secret-1&11n" +gc := NewGrafanaClient(url, apiToken) +``` + +# Dashboard creation +You can create a new dashboard defined in JSON with: +```go + +//define your dashboard here +dashboardJson := `` + +request := PostDashboardRequest { + Dashboard: dashboardJson, + FolderId: 5 // change to your folder id +} + +dr, rawResponse, err := gc.PostDashboard(request) +if err != nil { + panic(err) +} + +if rawResponse.StatusCode() != 200 { + panic("response code wasn't 200, but " + rawResponse.StatusCode()) +} + +fmt.Println("Dashboard slug is is " + *dr.Slug) +``` + +# Posting annotations +You can post annotations in a following way: +```go +annDetails := PostAnnotation { + DashboardUID: "some-uid", + Time: time.Now(), + TimeEnd: time.Now().Add(1 * time.Second) + Text: "my test annotation" +} + +r, rawResponse, err := gc.PostAnnotation(annDetails) +if rawResponse.StatusCode() != 200 { + panic("response code wasn't 200, but " + rawResponse.StatusCode()) +} + +fmt.Println("Created annotation with id: " + r.Id) + +``` + +# Checking alerts +You can check alerts firing for a dashboard with UID: +```go +alerts, rawResponse, err := gc.AlertRulerClient.GetAlertsForDashboard("some-uid") +if rawResponse.StatusCode() != 200 { + panic("response code wasn't 200, but " + rawResponse.StatusCode()) +} + +for name, value := range alerts { + fmt.Println("Alert named " + name + "was triggered. Details: " + string(value)) +} +``` + +# Troubleshooting +To enable debug mode for the underlaying HTTP client set `RESTY_DEBUG` environment variable to `true`. \ No newline at end of file diff --git a/book/src/lib/client/kafka.md b/book/src/lib/client/kafka.md new file mode 100644 index 000000000..308e28415 --- /dev/null +++ b/book/src/lib/client/kafka.md @@ -0,0 +1,19 @@ +# Kafka + +This is a wrapper over HTTP client that can only return a list of topics from Kafka instance. + +```go +client, err := NewKafkaRestClient(&NewKafkaRestClient{URL: "my-kafka-url"}) +if err != nil { + panic(err) +} + +topis, err := client.GetTopics() +if err != nil { + panic(err) +} + +for _, topic := range topics { + fmt.Println("topic: " + topic) +} +``` \ No newline at end of file diff --git a/book/src/lib/client/loki.md b/book/src/lib/client/loki.md new file mode 100644 index 000000000..bf5f8130e --- /dev/null +++ b/book/src/lib/client/loki.md @@ -0,0 +1,63 @@ +# Loki + +Loki client simplifies querying of Loki logs with `LogQL`. + +The way it's designed now implies that: +* you need to create a new client instance for each query +* query results are returned as `string` + +## New instance +To create a new instance you to provide the following at the very least: +* Loki URL +* query to execute +* time range + +```go +// scheme is required +lokiUrl := "http://loki-host.io" +// can be empty +tenantId := "promtail" +basicAuth := LokiBasicAuth{ + Login: "admin", + Password: "oh-so-secret", +} +queryParams := LokiQueryParams{ + Query: "quantile_over_time(0.5, {name='my awesome app'} | json| unwrap duration [10s]) by name", + StartTime: time.Now().Add(1 * time.Hour), + EndTime: time.Now(), + Limit: 1000, +} +lokiClient := client.NewLokiClient(lokiUrl, tenantId, basicAuth, queryParams) +``` + +If your instance doesn't have basic auth you should use an empty string: +```go +basicAuth := LokiBasicAuth{} +``` + +## Executing a query +Once you have the client instance created you can execute the query with: +```go +ctx, cancelFn := context.WithTimeout(context.Background, 3 * time.Minute) +defer cancelFn() +results, err := lokiClient.QueryLogs(ctx) +if err != nil { + panic(err) +} + +for _, logEntry := range results { + fmt.Println("At " + logEntry.Timestamp + " found following log: " + logEntry.Log) +} +``` + +## Log entry types +Loki can return various data types in responses to queries. We will try to convert the following ones to `string`: +* `int` +* `float64` + +If it's neither of these types nor a `string` the client will return an error. Same will happen if `nil` is returned. + +# Troubleshooting +If you find yourself in trouble these two environment variables might help you: +* `RESTY_DEBUG` set to `true` will enable debug mode for the underlaying HTTP client +* `LOKI_CLIENT_LOG_LEVEL` controls log level of Loki client (for supported log levels check [logging package](../logging.md) documentation) \ No newline at end of file diff --git a/book/src/lib/client/mockserver.md b/book/src/lib/client/mockserver.md new file mode 100644 index 000000000..25f2c8a24 --- /dev/null +++ b/book/src/lib/client/mockserver.md @@ -0,0 +1,120 @@ +# MockServer + +This client is reponsible for simplifying interaction with MockServer during test execution (e.g. to dynamically create or modify mocked responses). + +## Initialising +There are three ways of intializing the client: +* providing the URL +* providing pointer to `Environment` (CTF's representation of a k8s environment) +* providing pointer to raw `MockserverConfig` + +```go +var myk8sEnv *environment.Environment + +// ... create k8s environment + +k8sMockserver := ConnectMockServer(myk8sEnv) + +mockServerUrl := "http://my.mockserver.instance.io" +myMockServer := ConnectMockServerURL(mockServerUrl) + +customConfig := &MockserverConfig{ + LocalURL: mockServerUrl, + ClusterURL: mockServerUrl, + Headers: map[string]string{"x-secret-auth-header": "such-a-necessary-auth-header"}, +} + +myCustomMockServerClient := NewMockserverClient(customConfig) +``` + +In most cases you should initialise it with `*environment.Environment`, unless you need to pass custom headers to make the connection possible. + +## Typical usages +### Arbitrary mock +You can set any desired behaviour by using `PutExpectations(body interface{}) error` funciton, where the body should be a JSON string conforming to +MockServer's format that consists of a request matcher and corresponding action. For example: +```go +var myk8sEnv *environment.Environment + +returnOk := `{ + "httpRequest": { + "method": "GET", + "path": "/status" + }, + "httpResponse": { + "statusCode": 200, + "body": { + "message": "Service is running" + } + } +}` + +ms := ConnectMockServer(myk8sEnv) +err := ms.PutExpectations(returnOk) +if err != nil { + panic(err) +} +``` + +> [!NOTE] +> You can read more about expecations syntax, including OpenAPI v3 or dynamic expecations with JavaScript [here](https://www.mock-server.com/mock_server/creating_expectations.html). + +### Returning a random integer +To return random integer in the response body, together with `200` status code for all requests with a given path, use: +```go +err := ms.SetRandomValuePath("/api/v2/my_endpoint") +if err != nil { + panic(err) +} +``` + +### Returning a static integer +To return a static integer in the response body, together with `200` status code for all requests with a given path, use: +```go +err := ms.SetValuePath("/api/v2/my_endpoint") +if err != nil { + panic(err) +} +``` + +### Returning a static string +To return a static string in the response body, together with `200` status code for all requests with a given path, use: +```go +err := ms.SetStringValuePath("/api/v2/my_endpoint", "oh-my") +if err != nil { + panic(err) +} +``` + +### Returning arbitrary data +To return arbitrary data in the response body, together with `200` status code for all requests with a given path, use: +```go +err := ms.SetAnyValueResponse("/api/v2/my_endpoint", []int{1, 2, 3}) +if err != nil { + panic(err) +} +``` + +### Returning arbitrary response from external adatper +To mock a response for Chainlink's external adapter for all requests with a given path, use: +```go +var mockedResult interface{} +mockedResult = 5 +err := ms.SetAnyValuePath("/api/v2/my_endpoint", mockedResult) +if err != nil { + panic(err) +} +``` + +It will return a response with following structure: +```json +{ + "id": "", + "data": { + "result": 5 + } +} +``` + +# Troubleshooting +Enabling debug mode for the underlaying HTTP client can be achieved by setting `RESTY_DEBUG` environment variable to `true`. \ No newline at end of file diff --git a/book/src/lib/client/postgres.md b/book/src/lib/client/postgres.md new file mode 100644 index 000000000..5032e1219 --- /dev/null +++ b/book/src/lib/client/postgres.md @@ -0,0 +1,44 @@ +# Postgres Connector + +Postgres Connect simplifies connecting to a Postgres DB, by either: +* providing it full db config +* providing it a pointer to `enviroment.Environment` + +# Arbitrary DB + +```go +config := &PostgresConfig{ + Host: "db-host.io", + Port: 5432, + User: "admin", + Password: "oh-so-secret", + DBName: "database", + //SSLMode: +} +connector, err := NewPostgresConnector(config) +if err != nil { + panic(err) +} +``` + +If no `SSLMode` is supplied it will default to `sslmode=disable`. + +# K8s +This code assumes it connects to k8s enviroment created with the CTF, where each Chainlink Node +has an underlaying Postgres DB instance using CTF's default configuration: +```go +var myk8sEnv *environment.Environment + +// ... create k8s environment + +node0PgClient, node0err := ConnectDB(0, myk8sEnv) +if node0err != nil { + panic(node0err) +} + +node1PgClient, node1err := ConnectDB(1, myk8sEnv) +if node1err != nil { + panic(node1err) +} +``` + diff --git a/book/src/lib/client/prometheus.md b/book/src/lib/client/prometheus.md new file mode 100644 index 000000000..32d05bc00 --- /dev/null +++ b/book/src/lib/client/prometheus.md @@ -0,0 +1,44 @@ +# Prometheus + +This client is basically a wrapper over the official Prometheus client that gas three usesful functions: +* new client creation +* fetching of all firing alerts +* executing an arbitrary query with time range of `(-infinity, now)` + +## New instance +```go +c, err := NewPrometheusClient("prometheus.io") +if err != nil { + panic(err) +} +``` + +## Get all firing alerts +```go +alerts, err := c.GetAlerts() +if err != nil { + panic(err) +} + +for _, alert := range alerts { + fmt.Println("Found alert: " + alert.Value + "in state: " + alert.AlertState) +} +``` + +## Execute a query +```go +queryResult, err := c.GetQuery(`100 - (avg by (instance) (irate(node_cpu_seconds_total{mode="idle"}[2m])) * 100)`) +if err != nil { + panic(err) +} + +if asV, ok := queryResult.(.model.Vector); ok { + for _, v := range asV { + fmt.Println("Metric data: " +v.Metric) + fmt.Println("Value: " + v.Value) + } +} else { + panic(fmt.Sprintf("Result wasn't a model.Vector, but %T", queryResult)) +} + +``` \ No newline at end of file diff --git a/book/src/lib/concurrency.md b/book/src/lib/concurrency.md new file mode 100644 index 000000000..fcf11f48f --- /dev/null +++ b/book/src/lib/concurrency.md @@ -0,0 +1,95 @@ +# Concurrency + +It's a small library that simplifies dividing N tasks between X workers with but two options: +* payload +* early exit on first error + +It was created for parallelising chain interaction with [Seth](../libs/seth.md), where strict ordering +and association of a given private key with a specific contract is required. + +> [!NOTE] +> This library is an overkill if all you need to do is to deploy 10 contract, where each deployment +> consists of a single transaction. +> But... if your deployment flow is a multi-stepped one and it's crucial that all operations are executed +> using the same private key (e.g. due to privilleged access) it might be a good pick. Especially, if +> you don't want to extensively test a native `WaitGroup`/`ErrGroup`-based solution. + +## No payload +If the task to be executed requires no payload (or it's the same for each task) using the tool is much simpler. + +First you need to create an instance of the executor: +```go +l := logging.GetTestLogger(nil) + +executor := concurrency.NewConcurrentExecutor[ContractIntstance, contractResult, concurrency.NoTaskType](l) +``` + +Where generic parameters represent (from left to right): +* type of execution result +* type of channel that holds the results +* type of task payload + +In our case, we want the execution to return `ContractInstance`s, that will be stored by this type: +```go +type contractResult struct { + instance ContractIntstance +} +``` + +And which won't use any payload, as indicated by a no-op `concurrency.NoTaskType`. + +Then, we need to define a function that will be executed for each task. For example: +```go +var deployContractFn = func(channel chan contractResult, errorCh chan error, executorNum int) { + keyNum := executorNum + 1 // key 0 is the root key + + instance, err := client.deployContractFromKey(keyNum) + if err != nil { + errorCh <- err + return + } + + channel <- contractResult{instance: instance} +} +``` + +It needs to have the following signature: +```go +type SimpleTaskProcessorFn[ResultChannelType any] func(resultCh chan ResultChannelType, errorCh chan error, executorNum int) +``` +and send results of successful execution to `resultCh` and errors to `errorCh`. + +Once the processing function is defined all that's left is the execution: +```go +results, err := executor.ExecuteSimple(client.getConcurrency(), numberOfContracts, deployContractFn) +``` + +Parameters for `ExecuteSimple` (without payload) are as follows(from left to right): +* concurrency count (number of parallel executors) +* total number of executions +* function to execute + +`results` contain a slice with results of each execution with `ContractInstance` type. +`err` will be non-nil if any of the executions failed. To get all errors you should call `executor.GetErrors()`. + +## With payload +If your tasks need payload, then two things change. + +First, you need to pass task type, when creating the executor instance: +```go +executor := concurrency.NewConcurrentExecutor[ContractIntstance, contractResult, contractConfiguration](l) +``` + +Here, it's set to dummy: +```go +type contractConfiguration struct{} +``` + +Second, the signature of processing function: +```go +type TaskProcessorFn[ResultChannelType, TaskType any] func(resultCh chan ResultChannelType, errorCh chan error, executorNum int, payload TaskType) +``` +Which now includes a forth parameter representing the payload. And that function's implementation (making use of the payload). + +> [!NOTE] +> You can find the full example [here](https://github.com/smartcontractkit/chainlink-testing-framework/blob/main/lib/concurrency/example_test.go). \ No newline at end of file diff --git a/book/src/lib/config/config.md b/book/src/lib/config/config.md new file mode 100644 index 000000000..38c04672f --- /dev/null +++ b/book/src/lib/config/config.md @@ -0,0 +1,363 @@ +# TOML Config + +
+ +This documentation is deprecated, we are using it in [Chainlink Integration Tests](https://github.com/smartcontractkit/chainlink/tree/develop/integration-tests) + +If you want to test our new products use [v2](../framework/overview.md) +
+ +These basic building blocks can be used to create a TOML config file. For example: + +```golang +import ( + ctf_config "github.com/smartcontractkit/chainlink-testing-framework/config" + ctf_test_env "github.com/smartcontractkit/chainlink-testing-framework/docker/test_env" +) + +type TestConfig struct { + ChainlinkImage *ctf_config.ChainlinkImageConfig `toml:"ChainlinkImage"` + ChainlinkUpgradeImage *ctf_config.ChainlinkImageConfig `toml:"ChainlinkUpgradeImage"` + Logging *ctf_config.LoggingConfig `toml:"Logging"` + Network *ctf_config.NetworkConfig `toml:"Network"` + Pyroscope *ctf_config.PyroscopeConfig `toml:"Pyroscope"` + PrivateEthereumNetwork *ctf_test_env.EthereumNetwork `toml:"PrivateEthereumNetwork"` +} +``` + +It's up to the user to provide a way to read the config from file and unmarshal it into the struct. You can check [testconfig.go](https://github.com/smartcontractkit/chainlink-testing-framework/blob/main/lib/config/examples/testconfig.go) to see one way it could be done. + +`Validate()` should be used to ensure that the config is valid. Some of the building blocks have also a `Default()` method that can be used to get default values. + +Also, you might find `BytesToAnyTomlStruct(logger zerolog.Logger, filename, configurationName string, target any, content []byte) error` utility method useful for unmarshalling TOMLs read from env var or files into a struct + +## Test Secrets + +Test secrets are not stored directly within the `TestConfig` TOML due to security reasons. Instead, they are passed into `TestConfig` via environment variables. Below is a list of all available secrets. Set only the secrets required for your specific tests, like so: `E2E_TEST_CHAINLINK_IMAGE=qa_ecr_image_url`. + +### Default Secret Loading + +By default, secrets are loaded from the `~/.testsecrets` dotenv file. Example of a local `~/.testsecrets` file: + +```bash +E2E_TEST_CHAINLINK_IMAGE=qa_ecr_image_url +E2E_TEST_CHAINLINK_UPGRADE_IMAGE=qa_ecr_image_url +E2E_TEST_ARBITRUM_SEPOLIA_WALLET_KEY=wallet_key +``` + +### All E2E Test Secrets + +| Secret | Env Var | Example | +| ----------------------------- | ------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Chainlink Image | `E2E_TEST_CHAINLINK_IMAGE` | `E2E_TEST_CHAINLINK_IMAGE=qa_ecr_image_url` | +| Chainlink Upgrade Image | `E2E_TEST_CHAINLINK_UPGRADE_IMAGE` | `E2E_TEST_CHAINLINK_UPGRADE_IMAGE=qa_ecr_image_url` | +| Wallet Key per network | `E2E_TEST_(.+)_WALLET_KEY` or `E2E_TEST_(.+)_WALLET_KEY_(\d+)$` | `E2E_TEST_ARBITRUM_SEPOLIA_WALLET_KEY=wallet_key` or `E2E_TEST_ARBITRUM_SEPOLIA_WALLET_KEY_1=wallet_key_1`, `E2E_TEST_ARBITRUM_SEPOLIA_WALLET_KEY_2=wallet_key_2` for multiple keys per network | +| RPC HTTP URL per network | `E2E_TEST_(.+)_RPC_HTTP_URL` or `E2E_TEST_(.+)_RPC_HTTP_URL_(\d+)$` | `E2E_TEST_ARBITRUM_SEPOLIA_RPC_HTTP_URL=url` or `E2E_TEST_ARBITRUM_SEPOLIA_RPC_HTTP_URL_1=url`, `E2E_TEST_ARBITRUM_SEPOLIA_RPC_HTTP_URL_2=url` for multiple http urls per network | +| RPC WebSocket URL per network | `E2E_TEST_(.+)_RPC_WS_URL` or `E2E_TEST_(.+)_RPC_WS_URL_(\d+)$` | `E2E_TEST_ARBITRUM_RPC_WS_URL=ws_url` or `E2E_TEST_ARBITRUM_RPC_WS_URL_1=ws_url_1`, `E2E_TEST_ARBITRUM_RPC_WS_URL_2=ws_url_2` for multiple ws urls per network | +| Loki Tenant ID | `E2E_TEST_LOKI_TENANT_ID` | `E2E_TEST_LOKI_TENANT_ID=tenant_id` | +| Loki Endpoint | `E2E_TEST_LOKI_ENDPOINT` | `E2E_TEST_LOKI_ENDPOINT=url` | +| Loki Basic Auth | `E2E_TEST_LOKI_BASIC_AUTH` | `E2E_TEST_LOKI_BASIC_AUTH=token` | +| Loki Bearer Token | `E2E_TEST_LOKI_BEARER_TOKEN` | `E2E_TEST_LOKI_BEARER_TOKEN=token` | +| Grafana Bearer Token | `E2E_TEST_GRAFANA_BEARER_TOKEN` | `E2E_TEST_GRAFANA_BEARER_TOKEN=token` | +| Pyroscope Server URL | `E2E_TEST_PYROSCOPE_SERVER_URL` | `E2E_TEST_PYROSCOPE_SERVER_URL=url` | +| Pyroscope Key | `E2E_TEST_PYROSCOPE_KEY` | `E2E_TEST_PYROSCOPE_KEY=key` | + +### Run GitHub Workflow with Your Test Secrets + +By default, GitHub workflows execute with a set of predefined secrets. However, you can use custom secrets by specifying a unique identifier for your secrets when running the `gh workflow` command. + +#### Steps to Use Custom Secrets + +1. **Upload Local Secrets to GitHub Secrets Vault:** + + - **Install `ghsecrets` tool:** + Install the `ghsecrets` tool to manage GitHub Secrets more efficiently. + + ```bash + go install github.com/smartcontractkit/chainlink-testing-framework/tools/ghsecrets@latest + ``` + + If you use `asdf`, run `asdf reshim` + + - **Upload Secrets:** + Run `ghsecrets set` from local core repo to upload the content of your `~/.testsecrets` file to the GitHub Secrets Vault and generate a unique identifier (referred to as `your_ghsecret_id`). + + ```bash + cd path-to-chainlink-core-repo + ``` + + ```bash + ghsecrets set + ``` + + For more details about `ghsecrets`, visit https://github.com/smartcontractkit/chainlink-testing-framework/tree/main/tools/ghsecrets#faq + +2. **Execute the Workflow with Custom Secrets:** + - To use the custom secrets in your GitHub Actions workflow, pass the `-f test_secrets_override_key={your_ghsecret_id}` flag when running the `gh workflow` command. + ```bash + gh workflow run -f test_secrets_override_key={your_ghsecret_id} + ``` + +#### Default Secrets Handling + +If the `test_secrets_override_key` is not provided, the workflow will default to using the secrets preconfigured in the CI environment. + +### Creating New Test Secrets in TestConfig + +When adding a new secret to the `TestConfig`, such as a token or other sensitive information, the method `ReadConfigValuesFromEnvVars()` in `config/testconfig.go` must be extended to include the new secret. Ensure that the new environment variable starts with the `E2E_TEST_` prefix. This prefix is crucial for ensuring that the secret is correctly propagated to Kubernetes tests when using the Remote Runner. + +Here’s a quick checklist for adding a new test secret: + +- Add the secret to ~/.testsecrets with the `E2E_TEST_` prefix to ensure proper handling. +- Extend the `config/testconfig.go:ReadConfigValuesFromEnvVars()` method to load the secret in `TestConfig` +- Add the secrets to [All E2E Test Secrets](#all-e2e-test-secrets) table. + +## Working example + +For a full working example making use of all the building blocks see [testconfig.go](https://github.com/smartcontractkit/chainlink-testing-framework/blob/main/lib/config/examples/testconfig.go). It provides methods for reading TOML, applying overrides and validating non-empty config blocks. It supports 4 levels of overrides, in order of precedence: + +- `BASE64_CONFIG_OVERRIDE` env var +- `overrides.toml` +- `[product_name].toml` +- `default.toml` + +All you need to do now to get the config is execute `func GetConfig(configurationName string, product string) (TestConfig, error)`. It will first look for folder with file `.root_dir` and from there it will look for config files in all subfolders, so that you can place the config files in whatever folder(s) work for you. It assumes that all configuration versions for a single product are kept in `[product_name].toml` under different configuration names (that can represent anything you want: a single test, a test type, a test group, etc). + +Overrides of config files are done in a super-simple way. We try to unmarshall consecutive files into the same struct. Since it's all pointer based only not-nil keys are overwritten. + +## IMPORTANT! + +It is **required** to add `overrides.toml` to `.gitignore` in your project, so that you don't accidentally commit it as it might contain secrets. + +## Network config (and default RPC endpoints) + +Some more explanation is needed for the `NetworkConfig`: + +```golang +type NetworkConfig struct { + // list of networks that should be used for testing + SelectedNetworks []string `toml:"selected_networks"` + // map of network name to EVMNetworks where key is network name and value is a pointer to EVMNetwork + // if not set, it will try to find the network from defined networks in MappedNetworks under known_networks.go + // it doesn't matter if you use `arbitrum_sepolia` or `ARBITRUM_SEPOLIA` or even `arbitrum_SEPOLIA` as key + // as all keys will be uppercased when loading the Default config + EVMNetworks map[string]*blockchain.EVMNetwork `toml:"EVMNetworks,omitempty"` + // map of network name to ForkConfigs where key is network name and value is a pointer to ForkConfig + // only used if network fork is needed, if provided, the network will be forked with the given config + // networkname is fetched first from the EVMNetworks and + // if not defined with EVMNetworks, it will try to find the network from defined networks in MappedNetworks under known_networks.go + ForkConfigs map[string]*ForkConfig `toml:"ForkConfigs,omitempty"` + // map of network name to RPC endpoints where key is network name and value is a list of RPC HTTP endpoints + RpcHttpUrls map[string][]string `toml:"RpcHttpUrls"` + // map of network name to RPC endpoints where key is network name and value is a list of RPC WS endpoints + RpcWsUrls map[string][]string `toml:"RpcWsUrls"` + // map of network name to wallet keys where key is network name and value is a list of private keys (aka funding keys) + WalletKeys map[string][]string `toml:"WalletKeys"` +} + +func (n *NetworkConfig) Default() error { + ... +} +``` + +Sample TOML config: + +```toml +selected_networks = ["arbitrum_goerli", "optimism_goerli", "new_network"] + +[EVMNetworks.new_network] +evm_name = "new_test_network" +evm_chain_id = 100009 +evm_simulated = true +evm_chainlink_transaction_limit = 5000 +evm_minimum_confirmations = 1 +evm_gas_estimation_buffer = 10000 +client_implementation = "Ethereum" +evm_supports_eip1559 = true +evm_default_gas_limit = 6000000 + +[ForkConfigs.new_network] +url = "ws://localhost:8546" +block_number = 100 + +[RpcHttpUrls] +arbitrum_goerli = ["https://devnet-2.mt/ABC/rpc/"] +new_network = ["http://localhost:8545"] + +[RpcWsUrls] +arbitrum_goerli = ["wss://devnet-2.mt/ABC/ws/"] +new_network = ["ws://localhost:8546"] + +[WalletKeys] +arbitrum_goerli = ["1810868fc221b9f50b5b3e0186d8a5f343f892e51ce12a9e818f936ec0b651ed"] +optimism_goerli = ["1810868fc221b9f50b5b3e0186d8a5f343f892e51ce12a9e818f936ec0b651ed"] +new_network = ["ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"] +``` + +Whenever you are adding a new EVMNetwork to the config, you can either + +- provide the rpcs and wallet keys in RpcUrls and WalletKeys. Like in the example above, you can see that `new_network` is added to the `selected_networks` and `EVMNetworks` and then the rpcs and wallet keys are provided in `RpcHttpUrls`, `RpcWsUrls` and `WalletKeys` respectively. +- provide the rpcs and wallet keys in the `EVMNetworks` itself. Like in the example below, you can see that `new_network` is added to the `selected_networks` and `EVMNetworks` and then the rpcs and wallet keys are provided in `EVMNetworks` itself. + +```toml + +selected_networks = ["new_network"] + +[EVMNetworks.new_network] +evm_name = "new_test_network" +evm_chain_id = 100009 +evm_urls = ["ws://localhost:8546"] +evm_http_urls = ["http://localhost:8545"] +evm_keys = ["ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"] +evm_simulated = true +evm_chainlink_transaction_limit = 5000 +evm_minimum_confirmations = 1 +evm_gas_estimation_buffer = 10000 +client_implementation = "Ethereum" +evm_supports_eip1559 = true +evm_default_gas_limit = 6000000 +``` + +If your config struct looks like that: + +```golang + +type TestConfig struct { + Network *ctf_config.NetworkConfig `toml:"Network"` +} +``` + +then your TOML file should look like that: + +```toml +[Network] +selected_networks = ["arbitrum_goerli","new_network"] + +[Network.EVMNetworks.new_network] +evm_name = "new_test_network" +evm_chain_id = 100009 +evm_simulated = true +evm_chainlink_transaction_limit = 5000 +evm_minimum_confirmations = 1 +evm_gas_estimation_buffer = 10000 +client_implementation = "Ethereum" +evm_supports_eip1559 = true +evm_default_gas_limit = 6000000 + +[Network.RpcHttpUrls] +arbitrum_goerli = ["https://devnet-2.mt/ABC/rpc/"] +new_network = ["http://localhost:8545"] + +[Network.RpcWsUrls] +arbitrum_goerli = ["ws://devnet-2.mt/ABC/rpc/"] +new_network = ["ws://localhost:8546"] + +[Network.WalletKeys] +arbitrum_goerli = ["1810868fc221b9f50b5b3e0186d8a5f343f892e51ce12a9e818f936ec0b651ed"] +new_network = ["ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"] +``` + +If in your product config you want to support case-insensitive network names and map keys remember to run `NetworkConfig.UpperCaseNetworkNames()` on your config before using it. + +## Providing custom values in the CI + +Up to this point when we wanted to modify some dynamic tests parameters in the CI we would simply set env vars. That approach won't work anymore. The way to go around it is to build a TOML file, `base64` it, mask it and then set is as `BASE64_CONFIG_OVERRIDE` env var that will be read by tests. Here's an example of a working snippet of how that could look: + +```bash +convert_to_toml_array() { + local IFS=',' + local input_array=($1) + local toml_array_format="[" + + for element in "${input_array[@]}"; do + toml_array_format+="\"$element\"," + done + + toml_array_format="${toml_array_format%,}]" + echo "$toml_array_format" +} + +selected_networks=$(convert_to_toml_array "$SELECTED_NETWORKS") + +if [ -n "$PYROSCOPE_SERVER" ]; then + pyroscope_enabled=true +else + pyroscope_enabled=false +fi + +if [ -n "$ETH2_EL_CLIENT" ]; then + execution_layer="$ETH2_EL_CLIENT" +else + execution_layer="geth" +fi + +cat << EOF > config.toml +[Network] +selected_networks=$selected_networks + +[ChainlinkImage] +image="$CHAINLINK_IMAGE" +version="$CHAINLINK_VERSION" + +[Pyroscope] +enabled=$pyroscope_enabled +server_url="$PYROSCOPE_SERVER" +environment="$PYROSCOPE_ENVIRONMENT" +key_secret="$PYROSCOPE_KEY" + +[Logging.Loki] +tenant_id="$LOKI_TENANT_ID" +url="$LOKI_URL" +basic_auth_secret="$LOKI_BASIC_AUTH" +bearer_token_secret="$LOKI_BEARER_TOKEN" + +[Logging.Grafana] +url="$GRAFANA_URL" +EOF + +BASE64_CONFIG_OVERRIDE=$(cat config.toml | base64 -w 0) +echo ::add-mask::$BASE64_CONFIG_OVERRIDE +echo "BASE64_CONFIG_OVERRIDE=$BASE64_CONFIG_OVERRIDE" >> $GITHUB_ENV +``` + +**These two lines in that very order are super important** + +```bash +BASE64_CONFIG_OVERRIDE=$(cat config.toml | base64 -w 0) +echo ::add-mask::$BASE64_CONFIG_OVERRIDE +``` + +`::add-mask::` has to be called only after env var has been set to it's final value, otherwise it won't be recognized and masked properly and secrets will be exposed in the logs. + +## Providing custom values for local execution + +For local execution it's best to put custom variables in `overrides.toml` file. + +## Providing custom values in k8s + +It's easy. All you need to do is: + +- Create TOML file with these values +- Base64 it: `cat your.toml | base64` +- Set the base64 result as `BASE64_CONFIG_OVERRIDE` environment variable. + +`BASE64_CONFIG_OVERRIDE` will be automatically forwarded to k8s (as long as it is set and available to the test process), when creating the environment programmatically via `environment.New()`. + +Quick example: + +```bash +BASE64_CONFIG_OVERRIDE=$(cat your.toml | base64) go test your-test-that-runs-in-k8s ./file/with/your/test +``` + +# Not moved to TOML + +Not moved to TOML: + +- `SLACK_API_KEY` +- `SLACK_USER` +- `SLACK_CHANNEL` +- `TEST_LOG_LEVEL` +- `CHAINLINK_ENV_USER` +- `DETACH_RUNNER` +- `ENV_JOB_IMAGE` +- most of k8s-specific env variables were left untouched diff --git a/book/src/lib/crib.md b/book/src/lib/crib.md new file mode 100644 index 000000000..63537d63d --- /dev/null +++ b/book/src/lib/crib.md @@ -0,0 +1,7 @@ +### CRIB Connector + +
+ +`GAPv1` won't be supported in the future, you can still use [this example](https://github.com/smartcontractkit/chainlink/tree/develop/integration-tests/crib), [CI run](https://github.com/smartcontractkit/chainlink/actions/workflows/crib-integration-test.yml) but expect this to be changed. + +
diff --git a/book/src/lib/docker/blockchain_nodes.md b/book/src/lib/docker/blockchain_nodes.md new file mode 100644 index 000000000..9f312d406 --- /dev/null +++ b/book/src/lib/docker/blockchain_nodes.md @@ -0,0 +1,202 @@ +# Blockchain Nodes + +If you plan to experiment with these Docker containers, **use an existing Ethereum environment builder** rather than assembling components manually. This is particularly important for Proof-of-Stake (PoS) networks, where the setup involves multi-stage operations and shared state across multiple containers. + +Each supported client has a default image version defined [here](https://github.com/smartcontractkit/chainlink-testing-framework/blob/main/lib/docker/ethereum/images.go). Clients (except Reth) are available in two flavors: +- **Proof-of-Stake (PoS)**: Ethereum 2.0. +- **Proof-of-Work/Authority (PoW/PoA)**: Ethereum 1.0. + +Reth supports only PoS networks. + +These ephemeral networks use a simplified configuration with a single blockchain node. For PoS, three containers simulate this: +- Execution layer +- Consensus layer +- Validator + +> [!NOTE] +> We use a fork of [Ethereum Genesis Generator](https://github.com/ethpandaops/ethereum-genesis-generator) to create genesis files for PoS networks. + +--- + +## Execution Layer Clients +The following execution layer clients are available: +- Besu +- Erigon +- Geth +- Nethermind +- Reth + +## Consensus Layer Client +- **Prysm** (the only available consensus client). + +--- + +# Quick Start + +The simplest way to start an Ethereum network is by specifying the execution layer only: + +```go +builder := NewEthereumNetworkBuilder() +cfg, err := builder. + WithExecutionLayer(types.ExecutionLayer_Nethermind). + Build() +if err != nil { + panic(err) +} + +net, rpcs, err := cfg.Start() +if err != nil { + panic(err) +} +``` +If no version is specified, **Ethereum 1.0 (pre-Merge)** will be used. + +### Starting Ethereum 2.0 Networks +To start an Ethereum 2.0 network, add the Ethereum version parameter: + +```go +builder := NewEthereumNetworkBuilder() +cfg, err := builder. + WithEthereumVersion(config_types.EthereumVersion_Eth2). + WithExecutionLayer(config_types.ExecutionLayer_Geth). + Build() +if err != nil { + panic(err) +} + +net, rpcs, err := cfg.Start() +if err != nil { + panic(err) +} +``` +> [!NOTE] +> Booting Ethereum 2.0 networks takes longer due to the additional containers. Wait times of up to 1 minute are common. + +--- + +## Custom Docker Images +To use custom Docker images instead of the defaults: + +```go +builder := NewEthereumNetworkBuilder() +cfg, err := builder. + WithCustomDockerImages(map[config.ContainerType]string{ + config.ContainerType_ExecutionLayer: "ethereum/client-go:v1.15.0", + }). + Build() +if err != nil { + panic(err) +} + +net, rpcs, err := cfg.Start() +if err != nil { + panic(err) +} +``` +### Available Container Types +```go +const ( + ContainerType_ExecutionLayer ContainerType = "execution_layer" + ContainerType_ConsensusLayer ContainerType = "consensus_layer" + ContainerType_ConsensusValidator ContainerType = "consensus_validator" + ContainerType_GenesisGenerator ContainerType = "genesis_generator" + ContainerType_ValKeysGenerator ContainerType = "val_keys_generator" +) +``` +> [!NOTE] +> Use the `latest_available` tag for the most recent release, including pre-releases, or the `latest_stable` tag for the latest officially stable release. The `latest_available` may include beta or development versions, whereas `latest_stable` ensures compatibility with production environments. + +--- + +# Advanced Options + +## Connect to Existing Docker Networks +By default, a new random Docker network is created. To use an existing one: + +```go +builder := NewEthereumNetworkBuilder() +cfg, err := builder. + WithExecutionLayer(types.ExecutionLayer_Nethermind). + WithDockerNetworks([]string{"my-existing-network"}). + Build() +``` + +## Chain Customization + +### Ethereum 2.0 Parameters +- **Seconds per slot**: This defines how many seconds validators have to propose and vote on blocks. Lower values accelerate block production and epoch finalization but can cause issues if validators fail to keep up. The minimum allowed value is `3`. +- **Slots per epoch**: Determines the number of slots (voting rounds) per epoch. Lower values mean epochs finalize faster, but fewer voting rounds can impact network stability. The minimum value is `2`. +- **Genesis delay**: The extra delay (in seconds) before the genesis block starts. This ensures all containers (execution, consensus, and validator) are up and running before block production begins. +- **Validator count**: Specifies the number of validators in the network. A higher count increases the robustness of block validation but requires more resources. The minimum allowed value is `4`. + +### General Parameters +- **ChainID**: Integer value for the chain. +- **Addresses to fund**: Pre-fund accounts in the genesis block. + +### Default Configuration +```toml +seconds_per_slot=12 +slots_per_epoch=6 +genesis_delay=15 +validator_count=8 +chain_id=1337 +addresses_to_fund=["0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266"] +``` + +--- + +## Log Level +Customize node log levels (default: `info`): +```go +WithNodeLogLevel("debug") +``` +Supported values: +- `trace`, `debug`, `info`, `warn`, `error` + +## Wait for Finalization (Ethereum 2.0) +Wait until the first epoch finalizes: +```go +WithWaitingForFinalization() +``` + +--- + +# Accessing Containers +The `builder.Start()` function returns: +1. **Network configuration**: Input to test clients. +2. **RPC endpoints**: + - **Public**: Accessible externally. + - **Private**: Internal to the Docker network. + +### Endpoint Usage +- Use **public endpoints** for Ethereum clients deploying contracts, interacting with the chain, etc. +- Use **private endpoints** for Chainlink nodes within the same Docker network. + +--- + +# Ethereum 2.0 Container Creation Sequence +The following sequence explains Ethereum 2.0 container startup: + +```mermaid +sequenceDiagram + participant Host + participant Validator Keys Generator + participant Genesis Generator + participant Execution Layer + participant Beacon Chain (Consensus Layer) + participant Validator + participant After Genesis Helper + participant User + + Validator Keys Generator->>Host: Generate validator keys + Genesis Generator->>Host: Create genesis files + Host->>Execution Layer: Start with genesis + Host->>Beacon Chain (Consensus Layer): Start with genesis + Beacon Chain (Consensus Layer)->>Execution Layer: Connect and sync + Host->>Validator: Start with validator keys + Validator->>Beacon Chain (Consensus Layer): Begin block voting + loop new blocks + After Genesis Helper->>Execution Layer: Monitor new blocks + end + After Genesis Helper->>User: Ready to interact +``` \ No newline at end of file diff --git a/book/src/lib/docker/chainlink_ecosystem.md b/book/src/lib/docker/chainlink_ecosystem.md new file mode 100644 index 000000000..a570bae69 --- /dev/null +++ b/book/src/lib/docker/chainlink_ecosystem.md @@ -0,0 +1,49 @@ +# Chainlink ecosystem + +Currently, only one application has a wrapper that lives in this framework: the Job Distributor. +Chainlink Node wrapper can be found in the [chainlink repository](https://github.com/smartcontractkit/chainlink/blob/develop/integration-tests/docker/test_env/cl_node.go). + +## Job Distributor +JD is a component for a centralized creation and management of jobs executed by Chainlink Nodes. It's a single point of entry +that frees you from having to setup each job separately on each node from the DON. + +It requires a Postgres DB instance, which can also be started using the CTF: +```go +pg, err := test_env.NewPostgresDb( + []string{network.Name}, + test_env.WithPostgresDbName("jd-db"), + test_env.WithPostgresImageVersion("14.1")) +if err != nil { + panic(err) +} +err = pg.StartContainer() +if err != nil { + panic(err) +} +``` + +Then all you need to do, to start a new instance is: +```go +jd := New([]string{network.Name}, + //replace with actual image + WithImage("localhost:5001/jd"), + WithVersion("latest"), + WithDBURL(pg.InternalURL.String()), +) + +err = jd.StartContainer() +if err != nil { + panic(err) +} +``` + +Once you have JD started you can create a new GRPC connection and start interacting with it +to register DON nodes, create jobs, etc: +```go +conn, cErr := grpc.NewClient(jd.Grpc, []grpc.DialOption{grpc.WithTransportCredentials(insecure.NewCredentials())}...) +if cErr != nil { + panic(cErr) +} + +// use the connection +``` \ No newline at end of file diff --git a/book/src/lib/docker/overview.md b/book/src/lib/docker/overview.md new file mode 100644 index 000000000..54c1bbadc --- /dev/null +++ b/book/src/lib/docker/overview.md @@ -0,0 +1,68 @@ +# Docker + +Docker package represents low-level wrappers for Docker containers built using [testcontainers-go](https://golang.testcontainers.org/) library. +It is strongly advised that you use CTFv2's [Framework](../framework/overview.md) to build your enviroment instead of using it directly. **Consider yourself warned!** + +Supported Docker containers can be divided in a couple of categories: +* [Chainlink ecosystem-related](./chainlink_ecosystem.md) +* [Blockchain nodes](./blockchain_nodes.md) +* Third party apps +* [Test helpers](./test_helpers.md) +* helper containers + +Following Chainlink-related containers are available: +* Job Distributor + +Blockchain nodes: +* Besu +* Erigon +* Geth +* Nethermind +* Reth +* Prysm Beacon client (PoS Ethereum) + +Third party apps: +* Kafka +* Postgres +* Zookeeper +* Schema registry + +Test helpers (mocking solutions): +* Killgrave +* Mockserver + +Helper containers: +* Ethereum genesis generator +* Validator keys generator + +# Basic structure + +All of our Docker container wrappers are composed of some amount of specific elements and an universal part called `EnvComponent` defined as: +```go +type EnvComponent struct { + ContainerName string `json:"containerName"` + ContainerImage string `json:"containerImage"` + ContainerVersion string `json:"containerVersion"` + ContainerEnvs map[string]string `json:"containerEnvs"` + WasRecreated bool `json:"wasRecreated"` + Networks []string `json:"networks"` + Container tc.Container `json:"-"` + PostStartsHooks []tc.ContainerHook `json:"-"` + PostStopsHooks []tc.ContainerHook `json:"-"` + PreTerminatesHooks []tc.ContainerHook `json:"-"` + LogLevel string `json:"-"` + StartupTimeout time.Duration `json:"-"` +} +``` + +It comes with a bunch of functional options that allow you to: +* set container name +* set image name and tag +* set startup timeout +* set log level +* set post start/stop hooks + +And following chaos-testing related functions: +* `ChaosNetworkDelay()` - introducing networking delay +* `ChaosNetworkLoss()` - simulating network package loss +* `ChaosPause()` - pausing the container \ No newline at end of file diff --git a/book/src/lib/docker/test_helpers.md b/book/src/lib/docker/test_helpers.md new file mode 100644 index 000000000..03f8fa2e4 --- /dev/null +++ b/book/src/lib/docker/test_helpers.md @@ -0,0 +1,10 @@ +# Test helpers + +There two test helper containers: +* Killgrave +* Mockserver + +Both represent HTTP mocking solutions, with Mockserver used in k8s and Killgrave outside of it. Since both will soon +be replaced by a single, in-house solution, their usage is discouraged. + +That worked is begin done in [this PR](https://github.com/smartcontractkit/chainlink-testing-framework/pull/1246) and tracked in [this ticket](https://smartcontract-it.atlassian.net/browse/TT-1608). \ No newline at end of file diff --git a/book/src/lib/k8s/KUBERNETES.md b/book/src/lib/k8s/KUBERNETES.md new file mode 100644 index 000000000..a80756a00 --- /dev/null +++ b/book/src/lib/k8s/KUBERNETES.md @@ -0,0 +1,36 @@ +# Kubernetes + + +
+ +Managing k8s is challenging, so we've decided to separate `k8s` deployments here - [CRIB](https://github.com/smartcontractkit/crib) + +This documentation is outdated, and we are using it only internally to run our soak tests. For `v2` tests please check [this example](../crib.md) and read [CRIB docs](https://github.com/smartcontractkit/crib) +
+ +We run our software in Kubernetes. + +### Local k3d setup + +1. `make install` +2. (Optional) Install `Lens` from [here](https://k8slens.dev/) or use `k9s` as a low resource consumption alternative from [here](https://k9scli.io/topics/install/) + or from source [here](https://github.com/smartcontractkit/helmenv) +3. Setup your docker resources, 6vCPU/10Gb RAM are enough for most CL related tasks +4. `make create_cluster` +5. `make install_monitoring` Note: this will be actively connected to the server, the final log when it is ready is`Forwarding from [::1]:3000 -> 3000` and you can continue with the steps below in another terminal. +6. Check your contexts with `kubectl config get-contexts` +7. Switch context `kubectl config use-context k3d-local` +8. Read [here](README.md) and do some deployments +9. Open Grafana on `localhost:3000` with `admin/sdkfh26!@bHasdZ2` login/password and check the default dashboard +10. `make stop_cluster` +11. `make delete_cluster` + +### Typical problems + +1. Not enough memory/CPU or cluster is slow + Recommended settings for Docker are (Docker -> Preferences -> Resources): + - 6 CPU + - 10Gb MEM + - 50-150Gb Disk +2. `NodeHasDiskPressure` errors, pods get evicted + Use `make docker_prune` to clean up all pods and volumes diff --git a/book/src/lib/k8s/REMOTE_RUN.md b/book/src/lib/k8s/REMOTE_RUN.md new file mode 100644 index 000000000..6a8fede4f --- /dev/null +++ b/book/src/lib/k8s/REMOTE_RUN.md @@ -0,0 +1,48 @@ +## How to run the same environment deployment inside k8s + +
+ +Managing k8s is challenging, so we've decided to separate `k8s` deployments here - [CRIB](https://github.com/smartcontractkit/crib) + +This documentation is outdated, and we are using it only internally to run our soak tests. For `v2` tests please check [this example](../crib.md) and read [CRIB docs](https://github.com/smartcontractkit/crib) +
+ + +You can build a `Dockerfile` to run exactly the same environment interactions inside k8s in case you need to run long-running tests +Base image is [here](https://github.com/smartcontractkit/chainlink-testing-framework/blob/main/lib/k8s/Dockerfile.base) + +```Dockerfile +FROM .dkr.ecr.us-west-2.amazonaws.com/test-base-image:latest +COPY . . +RUN env GOOS=linux GOARCH=amd64 go build -o test ./examples/remote-test-runner/env.go +RUN chmod +x ./test +ENTRYPOINT ["./test"] +``` + +Build and upload it using the "latest" tag for the test-base-image + +```bash +build_test_image tag=someTag +``` + +or if you want to specify a test-base-image tag + +```bash +build_test_image tag=someTag base_tag=latest +``` + +Then run it + +```bash +# all environment variables with a prefix TEST_ would be provided for k8s job +export TEST_ENV_VAR=myTestVarForAJob +# your image to run as a k8s job +ACCOUNT=$(aws sts get-caller-identity | jq -r .Account) +export ENV_JOB_IMAGE="${ACCOUNT}.dkr.ecr.us-west-2.amazonaws.com/core-integration-tests:v1.1" +export DETACH_RUNNER=true # if you want the test job to run in the background after it has started +export CHAINLINK_ENV_USER=yourUser # user to run the tests +export CHAINLINK_USER_TEAM=yourTeam # team to run the tests for +# your example test file to run inside k8s +# if ENV_JOB_IMAGE is present it will create a job, wait until it finished and get logs +go run examples/remote-test-runner/env.go +``` diff --git a/book/src/lib/k8s/TUTORIAL.md b/book/src/lib/k8s/TUTORIAL.md new file mode 100644 index 000000000..c9286dbf6 --- /dev/null +++ b/book/src/lib/k8s/TUTORIAL.md @@ -0,0 +1,743 @@ +# How to create environments + +
+ +Managing k8s is challenging, so we've decided to separate `k8s` deployments here - [CRIB](https://github.com/smartcontractkit/crib) + +This documentation is outdated, and we are using it only internally to run our soak tests. For `v2` tests please check [this example](../crib.md) and read [CRIB docs](https://github.com/smartcontractkit/crib) +
+ + +- [Getting started](#getting-started) +- [Connect to environment](#connect-to-environment) +- [Creating environments](#creating-environments) + - [Debugging a new integration environment](#debugging-a-new-integration-environment) + - [Creating a new deployment part in Helm](#creating-a-new-deployment-part-in-helm) + - [Creating a new deployment part in cdk8s](#creating-a-new-deployment-part-in-cdk8s) + - [Using multi-stage environment](#using-multi-stage-environment) +- [Modifying environments](#modifying-environments) + - [Modifying environment from code](#modifying-environment-from-code) + - [Modifying environment part from code](#modifying-environment-part-from-code) +- [Configuring](#configuring) + - [Environment variables](#environment-variables) + - [Environment config](#environment-config) +- [Utilities](#utilities) + - [Collecting logs](#collecting-logs) + - [Resources summary](#resources-summary) +- [Chaos](#chaos) +- [Coverage](#coverage) +- [Remote run](./REMOTE_RUN.md) + +## Getting started + +Read [here](KUBERNETES.md) about how to spin up a local cluster if you don't have one. + +Following examples will use hardcoded `chain.link` labels for the sake of satisfying validations. When using any of remote clusters you should +provide them with actual and valid values, for example using following convenience functions: +```go +nsLabels, err := GetRequiredChainLinkNamespaceLabels("my-product", "load") +require.NoError(t, err, "Error creating required chain.link labels for namespace") + +workloadPodLabels, err := GetRequiredChainLinkWorkloadAndPodLabels("my-product", "load") +require.NoError(t, err, "Error creating required chain.link labels for workloads and pods") +``` + +And then setting them in the `Environment` config: +```go +envConfig := &environment.Config{ + Labels: nsLabels, + WorkloadLabels: workloadPodLabels + PodLabels: workloadPodLabels + NamespacePrefix: "new-environment", +} +``` + +Now, let's create a simple environment by combining different deployment parts. + +Create `examples/simple/env.go` + +```golang +package main + +import ( + "fmt" + "github.com/smartcontractkit/chainlink-testing-framework/k8s/environment" + "github.com/smartcontractkit/chainlink-testing-framework/k8s/pkg" + "github.com/smartcontractkit/chainlink-testing-framework/k8s/pkg/helm/chainlink" + "github.com/smartcontractkit/chainlink-testing-framework/k8s/pkg/helm/ethereum" + "github.com/smartcontractkit/chainlink-testing-framework/k8s/pkg/helm/mockserver" +) + +func addHardcodedLabelsToEnv(env *environment.Config) { + env.Labels = []string{"chain.link/product=myProduct", "chain.link/team=my-team", "chain.link/cost-center=test-tooling-load-test"} + env.WorkloadLabels = map[string]string{"chain.link/product": "myProduct", "chain.link/team": "my-team", "chain.link/cost-center": "test-tooling-load-test"} + env.PodLabels = map[string]string{"chain.link/product": "myProduct", "chain.link/team": "my-team", "chain.link/cost-center": "test-tooling-load-test"} +} + +func main() { + env := &environment.Config{ + NamespacePrefix: "new-environment", + KeepConnection: false, + RemoveOnInterrupt: false, + } + + addHardcodedLabelsToEnv(env) + err := environment.New(env). + AddHelm(ethereum.New(nil)). + AddHelm(chainlink.New(0, nil)). + Run() + if err != nil { + panic(err) + } +} +``` + +Then run `go run examples/simple/env.go` + +Now you have your environment running, you can [connect](#connect-to-environment) to it later + +> [!NOTE] +> `chain.link/*` labels are used for internal reporting and cost allocation. They are strictly required and validated. You won't be able to create a new environment without them. +> In this tutorial we create almost all of them manually, but there are convenience functions to do it for you. +> You can read more about labels [here](./labels.md) + +## Connect to environment + +We've already created an environment [previously](#getting-started), now we can connect + +If you are planning to use environment locally not in tests and keep connection, modify `KeepConnection` in `environment.Config` we used + +``` + KeepConnection: true, +``` + +Add `ENV_NAMESPACE=${your_env_namespace}` var and run `go run examples/simple/env.go` again + +You can get the namespace name from logs on creation time + +# Creating environments + +## Debugging a new integration environment + +You can spin up environment and block on forwarder if you'd like to run some other code. Let's use convenience functions for creating `chain.link` labels. + +```golang +package main + +import ( + "github.com/smartcontractkit/chainlink-testing-framework/k8s/environment" + "github.com/smartcontractkit/chainlink-testing-framework/k8s/pkg/helm/chainlink" + "github.com/smartcontractkit/chainlink-testing-framework/k8s/pkg/helm/ethereum" +) + +func main() { + env := &environment.Config{ + NamespacePrefix: "new-environment", + KeepConnection: true, + RemoveOnInterrupt: true, + } + + addHardcodedLabelsToEnv(env) + err := environment.New(env). + AddHelm(ethereum.New(nil)). + AddHelm(chainlink.New(0, nil)). + Run() + if err != nil { + panic(err) + } +} +``` + +Send any signal to remove the namespace then, for example `Ctrl+C` `SIGINT` + +## Creating a new deployment part in Helm + +Let's add a new [deployment part](examples/deployment_part/sol.go), it should implement an interface + +```golang +// ConnectedChart interface to interact both with cdk8s apps and helm charts +type ConnectedChart interface { + // IsDeploymentNeeded + // true - we deploy/connect and expose environment data + // false - we are using external environment, but still exposing data + IsDeploymentNeeded() bool + // GetName name of the deployed part + GetName() string + // GetPath get Helm chart path, repo or local path + GetPath() string + // GetProps get code props if it's typed environment + GetProps() any + // GetValues get values.yml props as map, if it's Helm + GetValues() *map[string]any + // ExportData export deployment part data in the env + ExportData(e *Environment) error + // GetLabels get labels for component, it must return `chain.link/component` label + GetLabels() map[string]string +} +``` + +When creating new deployment part, you can use any public Helm chart or a local path in Helm props + +```golang +func New(props *Props) environment.ConnectedChart { + if props == nil { + props = defaultProps() + } + return Chart{ + HelmProps: &HelmProps{ + Name: "sol", + Path: "chainlink-qa/solana-validator", // ./local_path/chartdir will work too + Values: &props.Values, + }, + Props: props, + } +} + +func (m NewDeploymentPart) GetLabels() map[string]string { + return map[string]string{ + "chain.link/component": "new-deployment-part", + } +} +``` + +Now let's tie them together + +```golang +package main + +import ( + "github.com/smartcontractkit/chainlink-testing-framework/k8s/environment" + "github.com/smartcontractkit/chainlink-testing-framework/k8s/examples/deployment_part" + "github.com/smartcontractkit/chainlink-testing-framework/k8s/pkg/helm/chainlink" + "time" +) + +func main() { + env := &environment.Config{ + NamespacePrefix: "adding-new-deployment-part", + TTL: 3 * time.Hour, + KeepConnection: true, + RemoveOnInterrupt: true, + } + + addHardcodedLabelsToEnv(env) + e := environment.New(env). + AddHelm(deployment_part.New(nil)). + AddHelm(chainlink.New(0, map[string]any{ + "replicas": 5, + "env": map[string]any{ + "SOLANA_ENABLED": "true", + "EVM_ENABLED": "false", + "EVM_RPC_ENABLED": "false", + "CHAINLINK_DEV": "false", + "FEATURE_OFFCHAIN_REPORTING2": "true", + "feature_offchain_reporting": "false", + "P2P_NETWORKING_STACK": "V2", + "P2PV2_LISTEN_ADDRESSES": "0.0.0.0:6690", + "P2PV2_DELTA_DIAL": "5s", + "P2PV2_DELTA_RECONCILE": "5s", + "p2p_listen_port": "0", + }, + })) + if err := e.Run(); err != nil { + panic(err) + } +} +``` + +Then run it `examples/deployment_part/cmd/env.go` + +## Creating a new deployment part in cdk8s + +Let's add a new [deployment part](examples/deployment_part/sol.go), it should implement the same interface + +```golang +// ConnectedChart interface to interact both with cdk8s apps and helm charts +type ConnectedChart interface { + // IsDeploymentNeeded + // true - we deploy/connect and expose environment data + // false - we are using external environment, but still exposing data + IsDeploymentNeeded() bool + // GetName name of the deployed part + GetName() string + // GetPath get Helm chart path, repo or local path + GetPath() string + // GetProps get code props if it's typed environment + GetProps() any + // GetValues get values.yml props as map, if it's Helm + GetValues() *map[string]any + // ExportData export deployment part data in the env + ExportData(e *Environment) error + // GetLabels get labels for component, it must return `chain.link/component` label + GetLabels() map[string]string +} +``` + +Now let's tie them together + +```golang +package main + +import ( + "github.com/smartcontractkit/chainlink-testing-framework/k8s/environment" + "github.com/smartcontractkit/chainlink-testing-framework/k8s/examples/deployment_part_cdk8s" + "github.com/smartcontractkit/chainlink-testing-framework/k8s/pkg/helm/chainlink" + "github.com/smartcontractkit/chainlink-testing-framework/k8s/pkg/helm/ethereum" +) + +func main() { + env := &environment.Config{ + NamespacePrefix: "adding-new-deployment-part", + TTL: 3 * time.Hour, + KeepConnection: true, + RemoveOnInterrupt: true, + } + + addHardcodedLabelsToEnv(env) + e := environment.New(env). + AddChart(deployment_part_cdk8s.New(&deployment_part_cdk8s.Props{})). + AddHelm(ethereum.New(nil)). + AddHelm(chainlink.New(0, map[string]any{ + "replicas": 2, + })) + if err := e.Run(); err != nil { + panic(err) + } + e.Shutdown() +} +``` + +Then run it `examples/deployment_part_cdk8s/cmd/env.go` + +## Using multi-stage environment + +You can split [environment](examples/multistage/env.go) deployment in several parts if you need to first copy something into a pod or use connected clients first + +```golang +package main + +import ( + "fmt" + "github.com/smartcontractkit/chainlink-testing-framework/k8s/environment" + "github.com/smartcontractkit/chainlink-testing-framework/k8s/pkg/cdk8s/blockscout" + "github.com/smartcontractkit/chainlink-testing-framework/k8s/pkg/helm/chainlink" + "github.com/smartcontractkit/chainlink-testing-framework/k8s/pkg/helm/ethereum" + "github.com/smartcontractkit/chainlink-testing-framework/k8s/pkg/helm/mockserver" + mockservercfg "github.com/smartcontractkit/chainlink-testing-framework/k8s/pkg/helm/mockserver-cfg" +) + +func main() { + envConfig := &environment.Config{ + Labels: []string{"chain.link/product=myProduct", "chain.link/team=my-team", "chain.link/cost-center=test-tooling-load-test"}, + WorkloadLabels: map[string]string{"chain.link/product": "myProduct", "chain.link/team": "my-team", "chain.link/cost-center": "test-tooling-load-test"}, + PodLabels: map[string]string{"chain.link/product": "myProduct", "chain.link/team": "my-team", "chain.link/cost-center": "test-tooling-load-test"} + } + e := environment.New(envConfig) + err := e. + AddChart(blockscout.New(&blockscout.Props{})). // you can also add cdk8s charts if you like Go code + AddHelm(ethereum.New(nil)). + AddHelm(chainlink.New(0, nil)). + Run() + if err != nil { + panic(err) + } + // do some other stuff with deployed charts + pl, err := e.Client.ListPods(e.Cfg.Namespace, "app=chainlink-0") + if err != nil { + panic(err) + } + dstPath := fmt.Sprintf("%s/%s:/", e.Cfg.Namespace, pl.Items[0].Name) + if _, _, _, err = e.Client.CopyToPod(e.Cfg.Namespace, "./examples/multistage/someData.txt", dstPath, "node"); err != nil { + panic(err) + } + // deploy another part + err = e. + AddHelm(mockservercfg.New(nil)). + AddHelm(mockserver.New(nil)). + Run() + defer func() { + errr := e.Shutdown() + panic(errr) + }() + if err != nil { + panic(err) + } +} +``` + +# Modifying environments + +## Modifying environment from code + +In case you need to [modify](examples/modify_cdk8s/env.go) environment in tests you can always construct manifest again and apply it + +That's working for `cdk8s` components too + +```golang +package main + +import ( + "fmt" + "github.com/smartcontractkit/chainlink-testing-framework/k8s/environment" + "github.com/smartcontractkit/chainlink-testing-framework/k8s/pkg/cdk8s/blockscout" + "github.com/smartcontractkit/chainlink-testing-framework/k8s/pkg/helm/chainlink" + "github.com/smartcontractkit/chainlink-testing-framework/k8s/pkg/helm/ethereum" +) + +func main() { + modifiedEnvConfig := &environment.Config{ + NamespacePrefix: "modified-env", + Labels: []string{"envType=Modified", "chain.link/product=myProduct", "chain.link/team=my-team", "chain.link/cost-center=test-tooling-load-test"}, + WorkloadLabels: map[string]string{"chain.link/product": "myProduct", "chain.link/team": "my-team", "chain.link/cost-center": "test-tooling-load-test"}, + PodLabels: map[string]string{"chain.link/product": "myProduct", "chain.link/team": "my-team", "chain.link/cost-center": "test-tooling-load-test"} + } + e := environment.New(modifiedEnvConfig). + AddChart(blockscout.New(&blockscout.Props{ + WsURL: "ws://geth:8546", + HttpURL: "http://geth:8544", + })). + AddHelm(ethereum.New(nil)). + AddHelm(chainlink.New(0, map[string]any{ + "replicas": 1, + })) + err := e.Run() + if err != nil { + panic(err) + } + e.ClearCharts() + err = e. + AddChart(blockscout.New(&blockscout.Props{ + HttpURL: "http://geth:9000", + })). + AddHelm(ethereum.New(nil)). + AddHelm(chainlink.New(0, map[string]any{ + "replicas": 1, + })). + Run() + if err != nil { + panic(err) + } +} +``` + +## Modifying environment part from code + +We can [modify](examples/modify_helm/env.go) only a part of environment + +```golang +package main + +import ( + "fmt" + "github.com/smartcontractkit/chainlink-testing-framework/k8s/environment" + "github.com/smartcontractkit/chainlink-testing-framework/k8s/pkg/helm/chainlink" + "github.com/smartcontractkit/chainlink-testing-framework/k8s/pkg/helm/ethereum" + "github.com/smartcontractkit/chainlink-testing-framework/k8s/pkg/helm/mockserver" + mockservercfg "github.com/smartcontractkit/chainlink-testing-framework/k8s/pkg/helm/mockserver-cfg" +) + +func main() { + modifiedEnvConfig := &environment.Config{ + NamespacePrefix: "modified-env", + } + + addHardcodedLabelsToEnv(modifiedEnvConfig) + e := environment.New(modifiedEnvConfig). + AddHelm(mockservercfg.New(nil)). + AddHelm(mockserver.New(nil)). + AddHelm(ethereum.New(nil)). + AddHelm(chainlink.New(0, map[string]any{ + "replicas": 1, + })) + err := e.Run() + if err != nil { + panic(err) + } + e.Cfg.KeepConnection = true + e.Cfg.RemoveOnInterrupt = true + e, err = e. + ReplaceHelm("chainlink-0", chainlink.New(0, map[string]any{ + "replicas": 2, + })) + if err != nil { + panic(err) + } + err = e.Run() + if err != nil { + panic(err) + } +} +``` + +# Configuring + +## Environment variables + +List of environment variables available + +```golang +const ( + EnvVarNamespace = "ENV_NAMESPACE" + EnvVarNamespaceDescription = "Namespace name to connect to" + EnvVarNamespaceExample = "chainlink-test-epic" + + // deprecated (for now left for backwards compatibility) + EnvVarCLImage = "CHAINLINK_IMAGE" + EnvVarCLImageDescription = "Chainlink image repository" + EnvVarCLImageExample = "public.ecr.aws/chainlink/chainlink" + + // deprecated (for now left for backwards compatibility) + EnvVarCLTag = "CHAINLINK_VERSION" + EnvVarCLTagDescription = "Chainlink image tag" + EnvVarCLTagExample = "1.5.1-root" + + EnvVarUser = "CHAINLINK_ENV_USER" + EnvVarUserDescription = "Owner of an environment" + EnvVarUserExample = "Satoshi" + + EnvVarTeam = "CHAINLINK_USER_TEAM" + EnvVarTeamDescription = "Team to, which owner of the environment belongs to" + EnvVarTeamExample = "BIX, CCIP, BCM" + + EnvVarCLCommitSha = "CHAINLINK_COMMIT_SHA" + EnvVarCLCommitShaDescription = "The sha of the commit that you're running tests on. Mostly used for CI" + EnvVarCLCommitShaExample = "${{ github.sha }}" + + EnvVarTestTrigger = "TEST_TRIGGERED_BY" + EnvVarTestTriggerDescription = "How the test was triggered, either manual or CI." + EnvVarTestTriggerExample = "CI" + + EnvVarLogLevel = "TEST_LOG_LEVEL" + EnvVarLogLevelDescription = "Environment logging level" + EnvVarLogLevelExample = "info | debug | trace" + + EnvVarSlackKey = "SLACK_API_KEY" + EnvVarSlackKeyDescription = "The OAuth Slack API key to report tests results with" + EnvVarSlackKeyExample = "xoxb-example-key" + + EnvVarSlackChannel = "SLACK_CHANNEL" + EnvVarSlackChannelDescription = "The Slack code for the channel you want to send the notification to" + EnvVarSlackChannelExample = "C000000000" + + EnvVarSlackUser = "SLACK_USER" + EnvVarSlackUserDescription = "The Slack code for the user you want to notify" + EnvVarSlackUserExample = "U000000000" +) +``` + +### Environment config + +```golang +// Config is an environment common configuration, labels, annotations, connection types, readiness check, etc. +type Config struct { + // TTL is time to live for the environment, used with kyverno + TTL time.Duration + // NamespacePrefix is a static namespace prefix + NamespacePrefix string + // Namespace is full namespace name + Namespace string + // Labels is a set of labels applied to the namespace in a format of "key=value" + Labels []string + // PodLabels is a set of labels applied to every pod in the namespace + PodLabels map[string]string + // WorkloadLabels is a set of labels applied to every workload in the namespace + WorkloadLabels map[string]string + // PreventPodEviction if true sets a k8s annotation safe-to-evict=false to prevent pods from being evicted + // Note: This should only be used if your test is completely incapable of handling things like K8s rebalances without failing. + // If that is the case, it's worth the effort to make your test fault-tolerant soon. The alternative is expensive and infuriating. + PreventPodEviction bool + // Allow deployment to nodes with these tolerances + Tolerations []map[string]string + // Restrict deployment to only nodes matching a particular node role + NodeSelector map[string]string + // ReadyCheckData is settings for readiness probes checks for all deployment components + // checking that all pods are ready by default with 8 minutes timeout + // &client.ReadyCheckData{ + // ReadinessProbeCheckSelector: "", + // Timeout: 15 * time.Minute, + // } + ReadyCheckData *client.ReadyCheckData + // DryRun if true, app will just generate a manifest in local dir + DryRun bool + // InsideK8s used for long-running soak tests where you connect to env from the inside + InsideK8s bool + // SkipManifestUpdate will skip updating the manifest upon connecting to the environment. Should be true if you wish to update the manifest (e.g. upgrade pods) + SkipManifestUpdate bool + // KeepConnection keeps connection until interrupted with a signal, useful when prototyping and debugging a new env + KeepConnection bool + // RemoveOnInterrupt automatically removes an environment on interrupt + RemoveOnInterrupt bool + // UpdateWaitInterval an interval to wait for deployment update started + UpdateWaitInterval time.Duration + + // Remote Runner Specific Variables // + // JobImage an image to run environment as a job inside k8s + JobImage string + // Specify only if you want remote-runner to start with a specific name + RunnerName string + // Specify only if you want to mount reports from test run in remote runner + ReportPath string + // JobLogFunction a function that will be run on each log + JobLogFunction func(*Environment, string) + // Test the testing library current Test struct + Test *testing.T + // jobDeployed used to limit us to 1 remote runner deploy + jobDeployed bool + // detachRunner should we detach the remote runner after starting the test + detachRunner bool + // fundReturnFailed the status of a fund return + fundReturnFailed bool +} +``` + +# Utilities + +## Collecting logs + +You can collect the [logs](examples/dump/env.go) while running tests, or if you have created an enrionment [already](#connect-to-environment) + +```golang +package main + +import ( + "github.com/smartcontractkit/chainlink-testing-framework/k8s/environment" + "github.com/smartcontractkit/chainlink-testing-framework/k8s/pkg/helm/chainlink" + "github.com/smartcontractkit/chainlink-testing-framework/k8s/pkg/helm/ethereum" +) + +func main() { + env := &environment.Config{} + + addHardcodedLabelsToEnv(env) + e := environment.New(env). + AddHelm(ethereum.New(nil)). + AddHelm(chainlink.New(0, nil)) + if err := e.Run(); err != nil { + panic(err) + } + if err := e.DumpLogs("logs/mytest"); err != nil { + panic(err) + } +} +``` + +## Resources summary + +It can be useful to get current env [resources](examples/resources/env.go) summary for test reporting + +```golang +package main + +import ( + "fmt" + "github.com/rs/zerolog/log" + "github.com/smartcontractkit/chainlink-testing-framework/k8s/environment" + "github.com/smartcontractkit/chainlink-testing-framework/k8s/pkg" + "github.com/smartcontractkit/chainlink-testing-framework/k8s/pkg/helm/chainlink" + "github.com/smartcontractkit/chainlink-testing-framework/k8s/pkg/helm/ethereum" +) + +func main() { + env := &environment.Config{} + addHardcodedLabelsToEnv(env) + + e := environment.New(env). + AddHelm(ethereum.New(nil)). + AddHelm(chainlink.New(0, nil)) + err := e.Run() + if err != nil { + panic(err) + } + // default k8s selector + summ, err := e.ResourcesSummary("app in (chainlink-0, geth)") + if err != nil { + panic(err) + } + log.Warn().Interface("Resources", summ).Send() + e.Shutdown() +} +``` + +# Chaos + +Check our [tests](https://github.com/smartcontractkit/chainlink/blob/develop/integration-tests/chaos/chaos_test.go) to see how we using Chaosmesh + +# Coverage + +Build your target image with those 2 steps to allow automatic coverage discovery + +```Dockerfile +FROM ... + +# add those 2 steps to instrument the code +RUN curl -s https://api.github.com/repos/qiniu/goc/releases/latest | grep "browser_download_url.*-linux-amd64.tar.gz" | cut -d : -f 2,3 | tr -d \" | xargs -n 1 curl -L | tar -zx && chmod +x goc && mv goc /usr/local/bin +# -o my_service means service will be called "my_service" in goc coverage service +# --center http://goc:7777 means that on deploy, your instrumented service will automatically register to a local goc node inside your deployment (namespace) +RUN goc build -o my_service . --center http://goc:7777 + +CMD ["./my_service"] +``` + +Add `goc` to your deployment, check example with `dummy` service deployment: + +```golang +package main + +import ( + "time" + + "github.com/smartcontractkit/chainlink-testing-framework/k8s/environment" + goc "github.com/smartcontractkit/chainlink-testing-framework/k8s/pkg/cdk8s/goc" + dummy "github.com/smartcontractkit/chainlink-testing-framework/k8s/pkg/cdk8s/http_dummy" +) + +func main() { + envConfig := &environment.Config{} + addHardcodedLabelsToEnv(envConfig) + e := environment.New(envConfig). + AddChart(goc.New()). + AddChart(dummy.New()) + if err := e.Run(); err != nil { + panic(err) + } + // run your test logic here + time.Sleep(1 * time.Minute) + if err := e.SaveCoverage(); err != nil { + panic(err) + } + // clear the coverage, rerun the tests again if needed + if err := e.ClearCoverage(); err != nil { + panic(err) + } +} + +``` + +After tests are finished, coverage is collected for every service, check `cover` directory + +# TOML Config + +Keep in mind that configuring Chainlink image/version & Pyroscope via env vars is deprecated. The latter won't even work anymore. That means that this method should be avoided in new environments. Instead, use the TOML config method described below. + +```golang + AddHelm(chainlink.New(0, nil)) +``` + +It's recommended to use a TOML config file to configure Chainlink and Pyroscope: + +```golang + +// read the config file +config := testconfig.GetConfig("Load", "Automation") + +var overrideFn = func(_ interface{}, target interface{}) { + ctf_config.MustConfigOverrideChainlinkVersion(&config.ChainlinkImage, target) + ctf_config.MightConfigOverridePyroscopeKey(&config.Pyroscope, target) +} + +AddHelm(chainlink.NewWithOverride(0, map[string]interface{}{ + "replicas": 1, +}, &config, overrideFn)) +``` + +Using that will cause the override function to be executed on the default propos thus overriding the default values with the values from the config file. If `config.ChainlinkImage` is `nil` or it's missing either `Image` or `Version` code will panic. If Pyroscope is disabled or key is not set it will be ignored. diff --git a/book/src/lib/k8s/labels.md b/book/src/lib/k8s/labels.md new file mode 100644 index 000000000..0807bffee --- /dev/null +++ b/book/src/lib/k8s/labels.md @@ -0,0 +1,67 @@ +# k8s `chain.link` Labels + +## Purpose +Resource labeling has been introduced to better associate Kubernetes (k8s) costs with products and teams. This document describes the labels used in the k8s cluster. + +## Required Labels +Labels should be applied to all resources in the k8s cluster at three levels: +- **Namespace** +- **Workload** +- **Pod** + +All three levels should include the following labels: +- `chain.link/team` - Name of the team that owns the resource. +- `chain.link/product` - Product that the resource belongs to. +- `chain.link/cost-center` - Product and framework name. + +Additionally, pods should include the following label: +- `chain.link/component` - Name of the component. + +### `chain.link/team` +This label represents the team responsible for the resource, but it might not be the team of the individual who created the resource. It should reflect the team the environment is **created for**. + +For example, if you are a member of the Test Tooling team, but someone from the BIX team requests load tests, the namespace should be labeled as: `chain.link/team: bix`. + +### `chain.link/product` +This label specifies the product the resource belongs to. Internally, some products may have alternative names (e.g., OCR instead of Data Feeds). To standardize data analysis, use the following names: + +``` +automation +bcm +ccip +data-feedsv1.0 +data-feedsv2.0 +data-feedsv3.0 +data-streamsv0.3 +data-streamsv1.0 +deco +functions +proof-of-reserve +scale +staking +vrf +``` + +For example: +- OCR version 1: `data-feedsv1.0` +- OCR version 2: `data-feedsv2.0` + +### `chain.link/cost-center` +This label serves as an umbrella for specific test or environment types and should rarely change. For load or soak tests using solutions provided by the Test Tooling team, use the convention: `test-tooling--test` + +For example: `test-tooling-load-test`. + +This allows easy distinction from load tests run using other tools. + +### `chain.link/component` +This label identifies different components within the same product. Examples include: +- `chainlink` - Chainlink node. +- `geth` - Go-Ethereum blockchain node. +- `test-runner` - Remote test runner. + +## Adding Labels to New Components +Adding a new component to an existing framework is discouraged. The recommended approach is to add the component to CRIB and make these labels part of the deployment templates. + +If you need to add a new component, refer to the following sections in the [k8s Tutorial](./TUTORIAL.md): +- **Creating a new deployment part in Helm** +- **Creating a new deployment part in cdk8s** \ No newline at end of file diff --git a/book/src/lib/k8s_new/environments.md b/book/src/lib/k8s_new/environments.md new file mode 100644 index 000000000..5ca754d16 --- /dev/null +++ b/book/src/lib/k8s_new/environments.md @@ -0,0 +1,287 @@ +# Kubernetes - Environments + +As mentioned earlier, `CTFv1` creates `k8s` environments programmatically from existing building blocks. These include: + +- `anvil` +- `blockscout` (cdk8s) +- `chainlink node` +- `geth` +- `goc` (cdk8s) +- `grafana` +- `influxdb` +- `kafka` +- `mock-adapter` +- `mockserver` +- `reorg controller` +- `schema registry` +- `solana validator` +- `starknet validator` +- `wiremock` + +Unless noted otherwise, all components are based on `Helm` charts. + +> [!NOTE] +> The process of creating new environments or modifying existing ones is explained in detail [here](../k8s/TUTORIAL.md). This document focuses on a practical example of creating a new `k8s` test environment with a basic setup. +> +> **It is highly recommended to read that tutorial before proceeding.** + +--- + +## Example: Basic Testing Environment + +We will create a simple testing environment consisting of: +- 6 `Chainlink nodes` +- 1 blockchain node (`go-ethereum`, aka `geth`) + +--- + +### Step 1: Create Chainlink Node TOML Config + +In real-world scenarios, you should dynamically generate or load Chainlink node configurations to suit your needs. For simplicity, we will use a hardcoded configuration: + +```go +func TestSimpleDONWithLinkContract(t *testing.T) { + tomlConfig := `[Feature] +FeedsManager = true +LogPoller = true +UICSAKeys = true + +[Database] +MaxIdleConns = 20 +MaxOpenConns = 40 +MigrateOnStartup = true + +[Log] +Level = "debug" +JSONConsole = true + +[Log.File] +MaxSize = "0b" + +[WebServer] +AllowOrigins = "*" +HTTPWriteTimeout = "3m0s" +HTTPPort = 6688 +SecureCookies = false +SessionTimeout = "999h0m0s" + +[WebServer.RateLimit] +Authenticated = 2000 +Unauthenticated = 1000 + +[WebServer.TLS] +HTTPSPort = 0 + +[OCR] +Enabled = true + +[P2P] + +[P2P.V2] +ListenAddresses = ["0.0.0.0:6690"] + +[[EVM]] +ChainID = "1337" +AutoCreateKey = true +FinalityDepth = 1 +MinContractPayment = "0" + +[EVM.GasEstimator] +PriceMax = "200 gwei" +LimitDefault = 6000000 +FeeCapDefault = "200 gwei" + +[[EVM.Nodes]] +Name = "Simulated Geth-0" +WSURL = "ws://geth:8546" +HTTPURL = "http://geth:8544"` +``` + +This configuration enables the log poller and OCRv2 features while connecting to an EVM chain with `ChainID` `1337`. It uses the following RPC URLs: +- WebSocket: `ws://geth:8546` +- HTTP: `http://geth:8544` + +These URLs correspond to the default ports for `geth` and match the `go-ethereum` service name in the `k8s` cluster. + +--- + +### Step 2: Define the Chainlink Deployment + +To define the Chainlink deployment, we configure the image, version, and other parameters such as replicas and database settings. Here's the detailed implementation: + +```go +chainlinkImageCfg := &ctf_config.ChainlinkImageConfig{ + Image: ptr.Ptr("public.ecr.aws/chainlink/chainlink"), + Version: ptr.Ptr("2.19.0"), +} + +var overrideFn = func(_ interface{}, target interface{}) { + ctf_config.MustConfigOverrideChainlinkVersion(chainlinkImageCfg, target) +} + +cd := chainlink.NewWithOverride(0, map[string]any{ + "replicas": 6, // Number of Chainlink nodes + "toml": tomlConfig, // TOML configuration defined earlier + "db": map[string]any{ + "stateful": true, // Use stateful databases for tests + }, +}, chainlinkImageCfg, overrideFn) +``` + +**Key Details:** +- **Image and Version:** These are hardcoded here for simplicity but should ideally be configurable for different environments. +- **Replicas:** We specify 6 Chainlink nodes to simulate a multi-node setup. +- **Database Configuration:** The database is stateful to allow for persistence during soak tests. +- **Override Function:** This ensures that the specified image and version are applied to all Chainlink node deployments. + +--- + +### Step 3: Label Resources + +To track costs effectively, add required `chain.link` labels to all `k8s` resources: + +```go +productName := "data-feedsv1.0" +nsLabels, err := environment.GetRequiredChainLinkNamespaceLabels(productName, "soak") +if err != nil { + t.Fatal("Error creating namespace labels", err) +} + +workloadPodLabels, err := environment.GetRequiredChainLinkWorkloadAndPodLabels(productName, "soak") +if err != nil { + t.Fatal("Error creating workload and pod labels", err) +} +``` + +Set the following environment variables: +- `CHAINLINK_ENV_USER`: Name of the person running the test. +- `CHAINLINK_USER_TEAM`: Name of the team the test is for. + +--- + +### Step 4: Create Environment Config + +```go +baseEnvironmentConfig := &environment.Config{ + TTL: time.Hour * 2, + NamespacePrefix: "my-namespace-prefix", + Test: t, + PreventPodEviction: true, + Labels: nsLabels, + WorkloadLabels: workloadPodLabels, + PodLabels: workloadPodLabels, +} +``` + +**Key Fields:** +- **`TTL`**: Time-to-live for the namespace (auto-removal after this time). +- **`NamespacePrefix`**: Ensures unique namespace names. +- **`PreventPodEviction`**: Prevents pods from being evicted or restarted. + +--- + +### Step 5: Define Blockchain Network + +To set up the blockchain network, we use predefined properties for a simulated EVM network. Here's the detailed implementation: + +```go +nodeNetwork := blockchain.SimulatedEVMNetwork + +ethProps := ðereum.Props{ + NetworkName: nodeNetwork.Name, // Name of the network + Simulated: nodeNetwork.Simulated, // Indicates that the network is simulated + WsURLs: nodeNetwork.URLs, // WebSocket URLs for the network + HttpURLs: nodeNetwork.HTTPURLs, // HTTP URLs for the network +} +``` + +**Details:** +- **Simulated Network:** Represents a private, ephemeral blockchain used for testing. +- **Dynamic Selection:** In real scenarios, use helper functions to dynamically select networks (public, private, or simulated) based on test requirements. +- **Custom URLs:** The `ethereum` chart requires explicit settings for the network name and URLs. + +--- + +### Step 6: Build the Environment + +```go +testEnv := environment.New(baseEnvironmentConfig). + AddHelm(ethereum.New(ethProps)). // Blockchain node + AddHelm(cd) // Chainlink nodes + +err = testEnv.Run() +if err != nil { + t.Fatal("Error running environment", err) +} +``` + +--- + +### Step 7: Create Blockchain Client + +```go +if !testEnv.Cfg.InsideK8s { + wsURLs := testEnv.URLs[blockchain.SimulatedEVMNetwork.Name] + httpURLs := testEnv.URLs[blockchain.SimulatedEVMNetwork.Name+"_http"] + if len(wsURLs) == 0 || len(httpURLs) == 0 { + t.Fatal("Forwarded Geth URLs should not be empty") + } + nodeNetwork.URLs = wsURLs + nodeNetwork.HTTPURLs = httpURLs +} + +sethClient, err := seth.NewClientBuilder(). + WithRpcUrl(nodeNetwork.URLs[0]). + WithPrivateKeys([]string{nodeNetwork.PrivateKeys[0]}). + Build() +if err != nil { + t.Fatal("Error creating Seth client", err) +} +``` + +**Details:** +- **Local vs. Cluster Environment**: When running tests outside the k8s cluster, the service URLs (`ws://geth:8546`, `http://geth:8544`) are not directly accessible. Port forwarding ensures local access to these services. +- **Automatic Port Forwarding**: The `Environment` object manages forwarding for key services, including Geth in simulated mode, making these forwarded URLs available in the `URLs` map. +- **Dynamic Rewriting**: URLs are dynamically rewritten to switch between in-cluster and local connectivity. + +--- + +### Step 8: Deploy LINK Contract + +```go +linkTokenAbi, err := link_token_interface.LinkTokenMetaData.GetAbi() +if err != nil { + t.Fatal("Error getting LinkToken ABI", err) +} + +linkDeploymentData, err := sethClient.DeployContract(sethClient.NewTXOpts(), "LinkToken", *linkTokenAbi, common.FromHex(link_token_interface.LinkTokenMetaData.Bin)) +if err != nil { + t.Fatal("Error deploying LinkToken contract", err) +} + +linkToken, err := link_token_interface.NewLinkToken(linkDeploymentData.Address, sethClient.Client) +if err != nil { + t.Fatal("Error creating LinkToken contract instance", err) +} + +totalSupply, err := linkToken.TotalSupply(sethClient.NewCallOpts()) +if err != nil { + t.Fatal("Error getting total supply of LinkToken", err) +} +if totalSupply.Cmp(big.NewInt(0)) <= 0 { + t.Fatal("Total supply of LinkToken should be greater than 0") +} +``` + +**Details:** +- **Deploy Contract:** Deploys the LINK token contract to the simulated blockchain. +- **Verify Deployment:** Ensures the total supply is greater than zero as a sanity check. + +--- + +### Next Steps + +Learn how to run long-duration tests using a `remote runner` in the [next chapter](./remote_runner.md). + +> [!NOTE] +> This example can be found [here](https://github.com/smartcontractkit/chainlink-testing-framework/tree/main/lib/k8s/examples/link/link_test.go). \ No newline at end of file diff --git a/book/src/lib/k8s_new/overview.md b/book/src/lib/k8s_new/overview.md new file mode 100644 index 000000000..2a069d34c --- /dev/null +++ b/book/src/lib/k8s_new/overview.md @@ -0,0 +1,29 @@ +# Kubernetes + +> [!WARNING] +> It is highly recommended to use [CRIB](https://github.com/smartcontractkit/crib) for `k8s` deployments. +> Avoid running long tests that are impractical to execute locally or through CI pipelines. +> +> **Proceed at your own risk.** + +--- + +## Overview + +The `CTFv1` tool builds `k8s` environments **programmatically** using either `Helm` or `cdk8s` charts. This approach introduces significant complexity to the deployment process. + +To manage long-running tests, `CTFv1` utilizes a `remote runner`, which is essentially a Docker container containing the test logic. This container is deployed as a `cdk8s`-based chart, creating a `k8s` resource of type `job` that runs the test in a detached manner. This setup requires custom logic to integrate with the test framework. + +--- + +## What We’ll Cover + +1. Creating a simplified `k8s` environment. +2. Adding a basic test that: + - Deploys a smart contract. + - Supports the `remote runner` capability. +3. Building a Docker image for the test and configuring the required environment variables. + +--- + +Are you ready to get started? diff --git a/book/src/lib/k8s_new/remote_runner.md b/book/src/lib/k8s_new/remote_runner.md new file mode 100644 index 000000000..25cc40fea --- /dev/null +++ b/book/src/lib/k8s_new/remote_runner.md @@ -0,0 +1,250 @@ +# Kubernetes - Using Remote Runner + +In this chapter, we explain how to run a test in `k8s` and the changes required in the test logic to support it. + +--- + +## Overview + +The general process of running tests with a `remote runner` involves: + +1. Creating a `k8s` environment from a local machine (either your local machine or a CI runner). + - The environment launches a `remote runner` using the `ENV_JOB_IMAGE` environment variable. +2. The `remote runner` re-executes the same test code from the beginning. + - It detects that the environment is already deployed and skips redeploying it. +3. After the `remote runner` completes its test execution, control returns to the local test execution. + - The local test exits early to prevent duplicate execution. +4. If running in `detached mode`, control returns to the local test as soon as the remote test starts. + - The local test exits immediately. + - The `remote runner` continues running in `k8s` until the test completes. + +Following diagram explains it in a visual way: +```mermaid +sequenceDiagram + actor User as User + participant LocalTest as Local Test + participant K8sEnv as Kubernetes Environment + participant RemoteRunner as Remote Runner + + User->>LocalTest: Start test execution + LocalTest->>K8sEnv: Create environment + K8sEnv-->>LocalTest: Environment created + LocalTest->>K8sEnv: Start remote runner job + K8sEnv-->>RemoteRunner: Deploy remote runner + + alt DETACHED_MODE=true + RemoteRunner-->>K8sEnv: Begin test logic execution + LocalTest-->>User: Exit after remote runner starts + Note right of LocalTest: Detaches after
remote runner starts + else DETACHED_MODE=false + RemoteRunner-->>K8sEnv: Begin test logic execution + RemoteRunner->>K8sEnv: Complete test logic execution + K8sEnv-->>LocalTest: Notify test completion + LocalTest-->>User: Exit after test completion + end + + Note right of RemoteRunner: Executes the test
logic independently +``` + +Although this may seem complex, the necessary changes to the test logic ensure that tests execute correctly without duplications. In the following steps, we explain where and why you should add conditionals and early exits to prevent unwanted re-execution. + +> [!NOTE] +> We are developing a new `k8s` test runner that eliminates the need for `k8s`-specific logic. This will allow tests to run seamlessly both locally and remotely. Documentation for this is available [here](../../k8s-test-runner/k8s-test-runner.md). + +--- + +## Requirements + +The `remote runner` requires a Docker image containing your test code. There are multiple ways to build this image, some of which are automated in our repositories. For this documentation, we will build it from scratch. + +> [!NOTE] +> The CTF repository builds a base testing image for each release using [this action](https://github.com/smartcontractkit/chainlink-testing-framework/actions/workflows/k8s-publish-test-base-image.yaml). This base image includes `kubectl`, `helm`, and other dependencies. Use it as a base image for your final test image. You only need to copy your compiled Go tests and set the entrypoint to execute them. An example from the Chainlink repository can be found [here](https://github.com/smartcontractkit/chainlink/actions/workflows/on-demand-ocr-soak-test.yml). + +--- + +## Step 1: Build a Docker Image with Your Tests + +Define a `Dockerfile`: + +```dockerfile +# Base image for all k8s test runs +FROM golang:1.23-bullseye + +ARG GOARCH +ARG GOOS +ARG BASE_URL +ARG HELM_VERSION +ARG HOME +ARG KUBE_VERSION +ARG NODE_VERSION + +# Compile Go binary targeting linux/amd64, used by k8s runners +ENV GOOS="linux" +ENV GOARCH="amd64" +ENV BASE_URL="https://get.helm.sh" +ENV HELM_VERSION="3.10.3" +ENV KUBE_VERSION="v1.25.5" +ENV NODE_VERSION=18 + +# Install dependencies +RUN apt-get update && apt-get install -y \ + ca-certificates wget curl git gnupg zip && \ + mkdir -p /etc/apt/keyrings && \ + curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg && \ + echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_$NODE_VERSION.x nodistro main" | tee /etc/apt/sources.list.d/nodesource.list && \ + apt-get update && apt-get install -y nodejs && \ + curl -LO https://storage.googleapis.com/kubernetes-release/release/$(curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt)/bin/linux/amd64/kubectl && \ + chmod +x ./kubectl && mv ./kubectl /usr/local/bin && \ + ARCH=$(uname -m | sed 's/x86_64/amd64/;s/aarch64/arm64/') && \ + wget ${BASE_URL}/helm-v${HELM_VERSION}-linux-${ARCH}.tar.gz -O - | tar -xz && \ + mv linux-${ARCH}/helm /usr/bin/helm && chmod +x /usr/bin/helm && rm -rf linux-${ARCH} && \ + npm install -g yarn && apt-get clean && \ + helm repo add chainlink-qa https://raw.githubusercontent.com/smartcontractkit/qa-charts/gh-pages/ && \ + helm repo add bitnami https://charts.bitnami.com/bitnami && helm repo update + +# Install AWS CLI v2 +RUN ARCH=$(uname -m | sed 's/x86_64/x86_64/;s/aarch64/aarch64/') && \ + curl https://awscli.amazonaws.com/awscli-exe-linux-${ARCH}.zip -o "awscliv2.zip" && \ + unzip awscliv2.zip && ./aws/install && rm -rf awscliv2.zip + +COPY lib/ testdir/ + +# Compile Go tests +WORKDIR /go/testdir/k8s/examples/link +RUN go test -c . -o link + +# Entrypoint to run tests +ENTRYPOINT ["./link"] +``` + +**Installed Dependencies:** +- `kubectl` (to interact with `k8s`) +- `nodejs` +- `helm` (for environment setup) +- `AWS CLI v2` (if interacting with AWS `k8s` clusters) + +> [!NOTE] +> If using local Helm charts instead of published ones, set `LOCAL_CHARTS=true`. Local here means onces stored in the Docker image +> in the `lib/charts` folder. + +--- + +## Step 2: Build the Image + +Build the Docker image from the root directory of the CTF repository: + +```bash +docker build -f lib/k8s/examples/link/Dockerfile \ + --platform=linux/amd64 \ + -t link-test:latest . +``` + +> [!NOTE] +> The `--platform=linux/amd64` parameter ensures compatibility with k8s runners. + +--- + +## Step 3: Test the Image + +Before modifying the test logic for `remote runner` compatibility, test the image locally. Ensure you have access to a `k8s` cluster (local or remote). Configure `kubectl` to use the cluster and authenticate if necessary. + +Run the test image: + +```bash +docker run \ + --rm \ + -v ~/.aws:/root/.aws:ro \ + -v ~/.kube/config:/root/.kube/config:ro \ + -e AWS_PROFILE= \ + -e KUBECONFIG=/root/.kube/config \ + link-test:latest +``` + +This mounts the local `.aws` directory and `kubectl` configuration to the container, allowing it to access the cluster. Verify the test completes successfully. + +--- + +## Step 4: Make the Test `Remote Runner`-Compatible + +To adapt the test for `remote runner` execution, you need to divide the test logic into: + +1. **Local Execution Logic**: Responsible for setting up the environment and initiating the `remote runner`. +2. **Remote Execution Logic**: Handles the actual test operations, such as deploying contracts or generating workload. + +### Key Adjustments + +- **Check for Remote Execution**: Use the `testEnv.WillUseRemoteRunner()` function to determine if the test will run in the `remote runner`. If it will, ensure any non-idempotent operations, like test logic execution, are skipped in the local context. +- **Prevent Test Logic Duplication**: Exit the local test execution after initiating the remote runner to avoid running the test logic both locally and remotely. + +### Updated Example + +Here is how you can make these changes: + +```go +err = testEnv.Run() +if err != nil { + t.Fatal("Error running environment: ", err) +} + +if testEnv.WillUseRemoteRunner() { + log.Info().Msg("Stopping local execution as test will continue in the remote runner") + return // Exit early to allow the remote runner to take over +} + +// Additional test logic (if applicable) runs only in the remote runner +``` + +### Why These Changes Are Necessary + +- **Environment Duplication**: Ensures that test logic is executed only once, within the `remote runner`, and not both locally and remotely. + +--- + +## Step 5: Rebuild the image +Rebuild the image to include `remote runner`-related changes. + +## Step 6: Push Image to Registry (Optional) + +If using a remote cluster, push the image to a registry like AWS ECR: + +```bash +aws ecr get-login-password --region | docker login \ + --username AWS --password-stdin .dkr.ecr..amazonaws.com + +docker tag link-test:latest \ + .dkr.ecr..amazonaws.com//link-remote-runner-test:latest + +docker push .dkr.ecr..amazonaws.com//link-remote-runner-test:latest +``` + +--- + +## Step 7: Run with `Remote Runner` + +Run the test in `detached mode` by setting these environment variables: +- `DETACH_RUNNER=true` +- `ENV_JOB_IMAGE=` (one we created in step 5) + +Command: + +```bash +docker run \ + --rm \ + -v ~/.aws:/root/.aws:ro \ + -v ~/.kube/config:/root/.kube/config:ro \ + -e DETACH_RUNNER=true \ + -e ENV_JOB_IMAGE= \ + -e AWS_PROFILE= \ + -e KUBECONFIG=/root/.kube/config \ + +``` + +> [!NOTE] +> You may also need to pass secrets to your test. Refer to the [Test Secrets Documentation](./test_secrets.md) for guidance on securely managing and injecting secrets into your tests. + +The local test detaches after starting the remote runner. Logs from the remote runner can be checked for test progress. + +--- + +You can find the complete example [here](https://github.com/smartcontractkit/chainlink-testing-framework/tree/main/lib/k8s/examples/link). + diff --git a/book/src/lib/k8s_new/test_secrets.md b/book/src/lib/k8s_new/test_secrets.md new file mode 100644 index 000000000..82ae1b4d3 --- /dev/null +++ b/book/src/lib/k8s_new/test_secrets.md @@ -0,0 +1,36 @@ +# Kubernetes - Test Secrets + +We all have our secrets, don't we? It's the same case with tests... and since some of our repositories are public, we need to take special precautions to protect them. + +> [!WARNING] +> Before continuing, you should read the [test secrets section of the CTF configuration documentation](../config/config.md). + +## Overview + +In general, your `remote runner` will need access to the same secrets as your local test. Fortunately, these secrets are forwarded automatically and securely as long as their names have the prefix `E2E_TEST_`. + +To make a secret available to the `remote runner`, simply pass it to the `docker run` command: + +```bash +docker run \ + --rm \ + -v ~/.aws:/root/.aws:ro \ + -v ~/.kube/config:/root/.kube/config:ro \ + -e DETACH_RUNNER=true \ + -e E2E_TEST_MY_SECRET=my-secret \ + -e ENV_JOB_NAME="" \ + -e AWS_PROFILE= \ + -e KUBECONFIG=/root/.kube/config \ + +``` + +The secret will then be available to the `remote runner` during its execution. + +--- + +## Important Considerations + +> [!WARNING] +> **Do not use this method of passing secrets in CI environments.** Exposing secrets in this way can compromise their security. +> +> When running `k8s` tests in CI pipelines, use dedicated actions or reusable workflows designed to handle secrets securely. \ No newline at end of file diff --git a/book/src/lib/logging.md b/book/src/lib/logging.md new file mode 100644 index 000000000..8acf67bbd --- /dev/null +++ b/book/src/lib/logging.md @@ -0,0 +1,23 @@ +# Logging + +This small library was created to address two issues: +* mixed up logging for parallel tests, when using vanilla loggers +* conformity with logging interface required by `testcontainers-go` (a Docker container library) + +It uses `"github.com/rs/zerolog"` for the logger. + +## Configuration +There's only one configuration option: the log level. You can set it via `TEST_LOG_LEVEL` environment variable to: +* `trace` +* `debug` +* `info` (default) +* `warn` +* `error` + +## How to use +The main way to get a Logger instance is to call `logging.GetTestLogger(*testing.T)`. `testing.T` instance can be `nil`. + +When using it together with `testcontainers-go`, which is a library we use to interact with Docker containers you should +use `GetTestContainersGoTestLogger(*testing.T)` instead. + +And that's all there is to it :-) \ No newline at end of file diff --git a/book/src/lib/wasp/how-to/run_included_tests.md b/book/src/lib/wasp/how-to/run_included_tests.md new file mode 100644 index 000000000..c8d06ae17 --- /dev/null +++ b/book/src/lib/wasp/how-to/run_included_tests.md @@ -0,0 +1 @@ +# Try it out quickly From 20664d2c90feafc54ca82b083cf82dbb9c61c8a6 Mon Sep 17 00:00:00 2001 From: skudasov Date: Thu, 10 Jul 2025 13:41:25 +0200 Subject: [PATCH 4/5] cleanup CTFv1 --- book/src/SUMMARY.md | 1 - book/src/lib.md | 5 +++++ book/src/lib/crib.md | 7 ------- 3 files changed, 5 insertions(+), 8 deletions(-) delete mode 100644 book/src/lib/crib.md diff --git a/book/src/SUMMARY.md b/book/src/SUMMARY.md index e725cec9e..bf62fd336 100644 --- a/book/src/SUMMARY.md +++ b/book/src/SUMMARY.md @@ -130,7 +130,6 @@ - [K8s Remote Run](lib/k8s/REMOTE_RUN.md) - [K8s Tutorial](lib/k8s/TUTORIAL.md) - [Config](lib/config/config.md) - - [CRIB Connector](lib/crib.md) - [Docker](lib/docker/overview.md) - [Blockchain nodes](lib/docker/blockchain_nodes.md) - [Chainlink ecosystem](lib/docker/chainlink_ecosystem.md) diff --git a/book/src/lib.md b/book/src/lib.md index 392ac7cd5..522f2ede0 100644 --- a/book/src/lib.md +++ b/book/src/lib.md @@ -11,8 +11,13 @@ +
+ **DEPRECATED: This is v1 version and it is not actively maintained** +
+ + The purpose of this framework is to: - Interact with different blockchains - Configure CL jobs diff --git a/book/src/lib/crib.md b/book/src/lib/crib.md deleted file mode 100644 index 63537d63d..000000000 --- a/book/src/lib/crib.md +++ /dev/null @@ -1,7 +0,0 @@ -### CRIB Connector - -
- -`GAPv1` won't be supported in the future, you can still use [this example](https://github.com/smartcontractkit/chainlink/tree/develop/integration-tests/crib), [CI run](https://github.com/smartcontractkit/chainlink/actions/workflows/crib-integration-test.yml) but expect this to be changed. - -
From c5bc22d3762c0cf426962ad918f95d7466717483 Mon Sep 17 00:00:00 2001 From: skudasov Date: Thu, 10 Jul 2025 13:48:07 +0200 Subject: [PATCH 5/5] finalize --- .github/workflows/docs.yml | 8 ++++---- book/src/framework/components/overview.md | 9 +++------ 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index e42acbcab..26f9470ef 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -3,10 +3,10 @@ name: GH Pages Deploy (Docs, mdbook) on: push: -# branches: -# - main -# tags: -# - '*' + branches: + - main + tags: + - '*' jobs: build-deploy: diff --git a/book/src/framework/components/overview.md b/book/src/framework/components/overview.md index 562669b88..3db19f12c 100644 --- a/book/src/framework/components/overview.md +++ b/book/src/framework/components/overview.md @@ -1,8 +1,5 @@ # Components -CTF contains a lot of useful components, some of them are off-chain services like `Chainlink Node`, `NodeSet` - -CTF contains three groups of components: -- Off-chain services like `Chainlink NodeSet` -- On-chain wrappers for [chainlink-deployments](https://github.com/smartcontractkit/chainlink-deployments) repository -- Test components, blockchain simulators, fakes, etc +CTF contains of two groups of components: +- Off-chain services like [NodeSet](../../framework/nodeset_environment.md) +- Test components, blockchain simulators, fake server