diff --git a/scripts/integration/DIRS b/scripts/integration/DIRS deleted file mode 100644 index 5e495a497f806..0000000000000 --- a/scripts/integration/DIRS +++ /dev/null @@ -1,28 +0,0 @@ -amqp -apex -aws -axiom -azure -chronicle -clickhouse -datadog-agent -datadog-traces -elasticsearch -eventstoredb -gcp -http-client -humio -influxdb -kafka -logstash -loki -mongodb -nats -nginx -opentelemetry -postgres -prometheus -pulsar -redis -shutdown -splunk diff --git a/scripts/integration/README.md b/scripts/integration/README.md new file mode 100644 index 0000000000000..116d37553c47b --- /dev/null +++ b/scripts/integration/README.md @@ -0,0 +1,31 @@ +This directory contains a set of integration test frameworks for vector which are executed by the +`vdev` tool. + +Each directory contains two files: + +1. A `compose.yaml` file containing the instructions to `docker-compose` or `podman-compose` for how + to set up the containers in which to run the integrations, and +2. A `test.yaml` file that describes how to run the integration tests, including a matrix of + software versions or other parameters over which the tests will be run. + +You can list these tests with `cargo vdev integration show`[1], which provides a list of all the +integration test names followed by the extrapolated matrix of environments. + +Each test can be run using one of the following: + +1. Run a single test environment from the above list with `cargo vdev integration test NAME ENV` +2. Run all the environments for one test with `cargo vdev integration test NAME` +3. Run all the steps individually using the `start`, `test`, and then `stop` subcommands with the + same parameters as above (see below). This allows developers to start the environment once and + then repeat the testing step while working on a component. + +```shell +cargo vdev integration start NAME ENVIRONMENT +cargo vdev integration test NAME [ENVIRONMENT] +cargo vdev integration stop NAME [ENVIRONMENT] +``` + +If no environment is named for the `test` and `stop` subcommands, all active environments are used. + +[1] Note that the `vdev` tool accepts abbreviated subcommand names, so this can also be run as +`cargo vdev int show` for brevity. diff --git a/scripts/integration/amqp/compose.yaml b/scripts/integration/amqp/compose.yaml new file mode 100644 index 0000000000000..91cf0313cd0a7 --- /dev/null +++ b/scripts/integration/amqp/compose.yaml @@ -0,0 +1,12 @@ +version: '3' + +services: + rabbitmq: + image: docker.io/rabbitmq:${AMQP_VERSION} + ports: + - 5672:5672 + +networks: + default: + name: ${VECTOR_NETWORK} + external: true diff --git a/scripts/integration/amqp/test.yaml b/scripts/integration/amqp/test.yaml new file mode 100644 index 0000000000000..e42b401f5ca51 --- /dev/null +++ b/scripts/integration/amqp/test.yaml @@ -0,0 +1,8 @@ +args: +- --features +- amqp-integration-tests +- --lib +- '::amqp::' + +matrix: +- version: ['3.8'] diff --git a/scripts/integration/apex/compose.yaml b/scripts/integration/apex/compose.yaml new file mode 100644 index 0000000000000..89d2f58b2ca99 --- /dev/null +++ b/scripts/integration/apex/compose.yaml @@ -0,0 +1,14 @@ +version: '3' + +services: + mock-apex: + image: mcasper/mock-apex:${APEX_VERSION} + environment: + - MOCK_API_TOKEN=token + ports: + - '4567' + +networks: + default: + name: ${VECTOR_NETWORK} + external: true diff --git a/scripts/integration/apex/test.yaml b/scripts/integration/apex/test.yaml new file mode 100644 index 0000000000000..a86d1263f19da --- /dev/null +++ b/scripts/integration/apex/test.yaml @@ -0,0 +1,11 @@ +args: +- --features +- apex-integration-tests +- --lib +- '::apex::' + +env: + MOCK_APEX_ADDRESS: http://mock-apex:4567 + +matrix: +- version: [latest] diff --git a/scripts/integration/aws/compose.yaml b/scripts/integration/aws/compose.yaml new file mode 100644 index 0000000000000..7441cccbe77f6 --- /dev/null +++ b/scripts/integration/aws/compose.yaml @@ -0,0 +1,21 @@ +version: '3' + +services: + mock-ec2-metadata: + image: public.ecr.aws/aws-ec2/amazon-ec2-metadata-mock:v1.11.2 + mock-localstack: + image: docker.io/localstack/localstack-full:0.11.6 + environment: + - SERVICES=kinesis,s3,cloudwatch,elasticsearch,es,firehose,sqs + mock-watchlogs: + image: docker.io/luciofranco/mockwatchlogs:latest + mock-ecs: + image: docker.io/amazon/amazon-ecs-local-container-endpoints:latest + volumes: + - /var/run/docker.sock:/var/run/docker.sock + - $HOME/.aws/:/home/.aws/ + +networks: + default: + name: ${VECTOR_NETWORK} + external: true diff --git a/scripts/integration/aws/test.yaml b/scripts/integration/aws/test.yaml new file mode 100644 index 0000000000000..b70e76c65b916 --- /dev/null +++ b/scripts/integration/aws/test.yaml @@ -0,0 +1,20 @@ +args: +- --features +- aws-integration-tests +- --lib +- ::aws_ + +env: + AWS_ACCESS_KEY_ID: dummy + AWS_SECRET_ACCESS_KEY: dummy + CLOUDWATCH_ADDRESS: http://mock-localstack:4566 + EC2_METADATA_ADDRESS: http://mock-ec2-metadata:8111 + ECS_ADDRESS: http://mock-ecs + ELASTICSEARCH_ADDRESS: http://mock-localstack:4571 + KINESIS_ADDRESS: http://mock-localstack:4566 + S3_ADDRESS: http://mock-localstack:4566 + SQS_ADDRESS: http://mock-localstack:4566 + WATCHLOGS_ADDRESS: http://mock-watchlogs:6000 + +matrix: +- version: [latest] diff --git a/scripts/integration/axiom/compose.yaml b/scripts/integration/axiom/compose.yaml new file mode 100644 index 0000000000000..8ef2e0699fc84 --- /dev/null +++ b/scripts/integration/axiom/compose.yaml @@ -0,0 +1,34 @@ +version: '3' + +services: + postgres: + image: postgres:${AXIOM_VERSION} + environment: + POSTGRES_USER: axiom + POSTGRES_PASSWORD: axiom + volumes: + - postgres_data:/var/lib/postgresql/data + axiom-db: + image: axiomhq/axiom-db:latest + restart: unless-stopped + environment: + AXIOM_STORAGE: file:///data + AXIOM_POSTGRES_URL: postgres://axiom:axiom@postgres?sslmode=disable&connect_timeout=5 + depends_on: + - postgres + axiom-core: + image: axiomhq/axiom-core:latest + restart: unless-stopped + environment: + AXIOM_POSTGRES_URL: postgres://axiom:axiom@postgres?sslmode=disable&connect_timeout=5 + AXIOM_DB_URL: http://axiom-db:8080 + AXIOM_HTTP_PORT: '80' + depends_on: + - axiom-db + ports: + - 8081:80 + +networks: + default: + name: ${VECTOR_NETWORK} + external: true diff --git a/scripts/integration/axiom/test.yaml b/scripts/integration/axiom/test.yaml new file mode 100644 index 0000000000000..c60b15336b19c --- /dev/null +++ b/scripts/integration/axiom/test.yaml @@ -0,0 +1,11 @@ +args: +- --features +- axiom-integration-tests +- --lib +- 'sinks::axiom::' + +env: + AXIOM_URL: http://axiom-core + +matrix: +- version: [13-alpine] diff --git a/scripts/integration/azure/compose.yaml b/scripts/integration/azure/compose.yaml new file mode 100644 index 0000000000000..7db655dd9c642 --- /dev/null +++ b/scripts/integration/azure/compose.yaml @@ -0,0 +1,13 @@ +version: '3' + +services: + local-azure-blob: + image: mcr.microsoft.com/azure-storage/azurite:${AZURE_VERSION} + command: azurite --blobHost 0.0.0.0 --loose + volumes: + - /var/run:/var/run + +networks: + default: + name: ${VECTOR_NETWORK} + external: true diff --git a/scripts/integration/azure/test.yaml b/scripts/integration/azure/test.yaml new file mode 100644 index 0000000000000..ef434644bfd30 --- /dev/null +++ b/scripts/integration/azure/test.yaml @@ -0,0 +1,13 @@ +args: +- --features +- azure-integration-tests +- --lib +- ::azure_ + +env: + AZURE_ADDRESS: local-azure-blob + HEARTBEAT_ADDRESS: 0.0.0.0:8080 + LOGSTASH_ADDRESS: 0.0.0.0:8081 + +matrix: +- version: [3.14.0] diff --git a/scripts/integration/chronicle/compose.yaml b/scripts/integration/chronicle/compose.yaml new file mode 100644 index 0000000000000..c8d6eadb82ad1 --- /dev/null +++ b/scripts/integration/chronicle/compose.yaml @@ -0,0 +1,17 @@ +version: '3' + +services: + chronicle-emulator: + image: docker.io/plork/chronicle-emulator:${CHRONICLE_VERSION} + ports: + - 3000:3000 + volumes: + - ../chroniclepub.pem:/public.pem + command: + - -p + - /public.pem + +networks: + default: + name: ${VECTOR_NETWORK} + external: true diff --git a/scripts/integration/chronicle/test.yaml b/scripts/integration/chronicle/test.yaml new file mode 100644 index 0000000000000..1d5c733cede85 --- /dev/null +++ b/scripts/integration/chronicle/test.yaml @@ -0,0 +1,11 @@ +args: +- --features +- chronicle-integration-tests +- --lib +- '::chronicle_unstructured::' + +env: + CHRONICLE_ADDRESS: http://chronicle-emulator:3000 + +matrix: +- version: [latest] diff --git a/scripts/integration/clickhouse/compose.yaml b/scripts/integration/clickhouse/compose.yaml new file mode 100644 index 0000000000000..1da88d0d2f821 --- /dev/null +++ b/scripts/integration/clickhouse/compose.yaml @@ -0,0 +1,10 @@ +version: '3' + +services: + clickhouse: + image: docker.io/yandex/clickhouse-server:${CLICKHOUSE_VERSION} + +networks: + default: + name: ${VECTOR_NETWORK} + external: true diff --git a/scripts/integration/clickhouse/test.yaml b/scripts/integration/clickhouse/test.yaml new file mode 100644 index 0000000000000..cb7b61daa287d --- /dev/null +++ b/scripts/integration/clickhouse/test.yaml @@ -0,0 +1,11 @@ +args: +- --features +- clickhouse-integration-tests +- --lib +- '::clickhouse::' + +env: + CLICKHOUSE_ADDRESS: http://clickhouse:8123 + +matrix: +- version: ['19'] diff --git a/scripts/integration/datadog-agent/compose.yaml b/scripts/integration/datadog-agent/compose.yaml new file mode 100644 index 0000000000000..4f060dcb4e72a --- /dev/null +++ b/scripts/integration/datadog-agent/compose.yaml @@ -0,0 +1,33 @@ +version: '3' + +services: + datadog-agent: + image: docker.io/datadog/agent:${DATADOG_AGENT_VERSION} + environment: + - DD_API_KEY=${TEST_DATADOG_API_KEY:?TEST_DATADOG_API_KEY required} + - DD_APM_ENABLED=false + - DD_LOGS_ENABLED=true + - DD_LOGS_CONFIG_LOGS_DD_URL=runner:8080 + - DD_LOGS_CONFIG_LOGS_NO_SSL=true + - DD_LOGS_CONFIG_USE_HTTP=true + - DD_HEALTH_PORT=8182 + - DD_CMD_PORT=5001 + - DD_USE_DOGSTATSD=false + - DD_HOSTNAME=datadog-agent + volumes: + - ../../../tests/data/datadog-agent/conf.yaml:/etc/datadog-agent/conf.d/test.d/conf.yaml + datadog-trace-agent: + image: docker.io/datadog/agent:7.31.0 + environment: + - DD_API_KEY=${TEST_DATADOG_API_KEY:?TEST_DATADOG_API_KEY required} + - DD_APM_ENABLED=true + - DD_APM_DD_URL=http://runner:8081 + - DD_HEALTH_PORT=8183 + - DD_CMD_PORT=5002 + - DD_USE_DOGSTATSD=false + - DD_HOSTNAME=datadog-trace-agent + +networks: + default: + name: ${VECTOR_NETWORK} + external: true diff --git a/scripts/integration/datadog-agent/test.yaml b/scripts/integration/datadog-agent/test.yaml new file mode 100644 index 0000000000000..3ade6e59bc12f --- /dev/null +++ b/scripts/integration/datadog-agent/test.yaml @@ -0,0 +1,14 @@ +args: +- --features +- datadog-agent-integration-tests +- --lib +- 'sources::datadog_agent::integration_tests::' + +env: + AGENT_ADDRESS: datadog-agent:8181 + AGENT_HEALTH_ADDRESS: http://datadog-agent:8182 + TRACE_AGENT_HEALTH_ADDRESS: http://datadog-trace-agent:8183 + TRACE_AGENT_URL: http://datadog-trace-agent:8126/v0.4/traces + +matrix: +- version: ['7'] diff --git a/scripts/integration/datadog-traces/compose.yaml b/scripts/integration/datadog-traces/compose.yaml new file mode 100644 index 0000000000000..44ecf647482f2 --- /dev/null +++ b/scripts/integration/datadog-traces/compose.yaml @@ -0,0 +1,36 @@ +version: '3' + +services: + datadog-trace-agent: + image: docker.io/datadog/agent:${DATADOG_TRACES_VERSION} + environment: + - DD_API_KEY=${TEST_DATADOG_API_KEY:?TEST_DATADOG_API_KEY required} + - DD_APM_ENABLED=true + - DD_APM_DD_URL=http://runner:8082 + - DD_HEALTH_PORT=8183 + - DD_CMD_PORT=5002 + - DD_USE_DOGSTATSD=false + - DD_APM_MAX_TPS=999999 + - DD_APM_ERROR_TPS=999999 + - DD_APM_MAX_MEMORY=0 + - DD_APM_MAX_CPU_PERCENT=0 + - DD_HOSTNAME=datadog-trace-agent + datadog-trace-agent-to-vector: + image: docker.io/datadog/agent:${DATADOG_TRACES_VERSION} + environment: + - DD_API_KEY=${TEST_DATADOG_API_KEY:?TEST_DATADOG_API_KEY required} + - DD_APM_ENABLED=true + - DD_APM_DD_URL=http://runner:8081 + - DD_HEALTH_PORT=8183 + - DD_CMD_PORT=5002 + - DD_USE_DOGSTATSD=false + - DD_APM_MAX_TPS=999999 + - DD_APM_ERROR_TPS=999999 + - DD_APM_MAX_MEMORY=0 + - DD_APM_MAX_CPU_PERCENT=0 + - DD_HOSTNAME=datadog-trace-agent-to-vector + +networks: + default: + name: ${VECTOR_NETWORK} + external: true diff --git a/scripts/integration/datadog-traces/test.yaml b/scripts/integration/datadog-traces/test.yaml new file mode 100644 index 0000000000000..16e377a809b73 --- /dev/null +++ b/scripts/integration/datadog-traces/test.yaml @@ -0,0 +1,17 @@ +args: +- --no-default-features +- --features +- datadog-traces-integration-tests +- --lib +- '::datadog::traces::' + +env: + AGENT_PORT: '8082' + TEST_DATADOG_API_KEY: ${TEST_DATADOG_API_KEY:?TEST_DATADOG_API_KEY required} + TEST_LOG: ${TEST_LOG} + TRACE_AGENT_TO_VECTOR_URL: http://datadog-trace-agent-to-vector:8126/v0.3/traces + TRACE_AGENT_URL: http://datadog-trace-agent:8126/v0.3/traces + VECTOR_PORT: '8081' + +matrix: +- version: [latest] diff --git a/scripts/integration/elasticsearch/compose.yaml b/scripts/integration/elasticsearch/compose.yaml new file mode 100644 index 0000000000000..3bcfd1461b503 --- /dev/null +++ b/scripts/integration/elasticsearch/compose.yaml @@ -0,0 +1,32 @@ +version: '3' + +services: + localstack: + image: docker.io/localstack/localstack@sha256:f21f1fc770ee4bfd5012afdc902154c56b7fb18c14cf672de151b65569c8251e + environment: + - SERVICES=elasticsearch:4571 + elasticsearch: + image: docker.io/elasticsearch:${ELASTICSEARCH_VERSION} + environment: + - discovery.type=single-node + - ES_JAVA_OPTS=-Xms400m -Xmx400m + elasticsearch-secure: + image: docker.io/elasticsearch:${ELASTICSEARCH_VERSION} + environment: + - discovery.type=single-node + - xpack.security.enabled=true + - xpack.security.http.ssl.enabled=true + - xpack.security.http.ssl.certificate=certs/intermediate_server/certs/elasticsearch-secure-chain.cert.pem + - xpack.security.http.ssl.key=certs/intermediate_server/private/elasticsearch-secure.key.pem + - xpack.security.transport.ssl.enabled=true + - xpack.security.transport.ssl.certificate=certs/intermediate_server/certs/elasticsearch-secure-chain.cert.pem + - xpack.security.transport.ssl.key=certs/intermediate_server/private/elasticsearch-secure.key.pem + - ELASTIC_PASSWORD=vector + - ES_JAVA_OPTS=-Xms400m -Xmx400m + volumes: + - ../../../tests/data/ca:/usr/share/elasticsearch/config/certs:ro + +networks: + default: + name: ${VECTOR_NETWORK} + external: true diff --git a/scripts/integration/elasticsearch/test.yaml b/scripts/integration/elasticsearch/test.yaml new file mode 100644 index 0000000000000..183ee1a2d2818 --- /dev/null +++ b/scripts/integration/elasticsearch/test.yaml @@ -0,0 +1,15 @@ +args: +- --features +- es-integration-tests +- --lib +- '::elasticsearch::integration_tests::' + +env: + AWS_ACCESS_KEY_ID: dummy + AWS_SECRET_ACCESS_KEY: dummy + ELASTICSEARCH_AWS_ADDRESS: http://localstack:4571 + ELASTICSEARCH_HTTPS_ADDRESS: https://elasticsearch-secure:9200 + ELASTICSEARCH_HTTP_ADDRESS: http://elasticsearch:9200 + +matrix: +- version: [7.13.1] diff --git a/scripts/integration/eventstoredb/compose.yaml b/scripts/integration/eventstoredb/compose.yaml new file mode 100644 index 0000000000000..d1b99b8871d2c --- /dev/null +++ b/scripts/integration/eventstoredb/compose.yaml @@ -0,0 +1,13 @@ +version: '3' + +services: + eventstoredb: + image: docker.io/eventstore/eventstore:${EVENTSTOREDB_VERSION} + command: --insecure --stats-period-sec=1 + volumes: + - ../../../tests/data:/etc/vector:ro + +networks: + default: + name: ${VECTOR_NETWORK} + external: true diff --git a/scripts/integration/eventstoredb/test.yaml b/scripts/integration/eventstoredb/test.yaml new file mode 100644 index 0000000000000..7407653999ddb --- /dev/null +++ b/scripts/integration/eventstoredb/test.yaml @@ -0,0 +1,9 @@ +args: +- --no-default-features +- --features +- eventstoredb_metrics-integration-tests +- --lib +- '::eventstoredb_metrics::' + +matrix: +- version: [latest] diff --git a/scripts/integration/gcp/compose.yaml b/scripts/integration/gcp/compose.yaml new file mode 100644 index 0000000000000..b0c637e4ef125 --- /dev/null +++ b/scripts/integration/gcp/compose.yaml @@ -0,0 +1,13 @@ +version: '3' + +services: + gcloud-pubsub: + image: docker.io/messagebird/gcloud-pubsub-emulator:${GCP_VERSION} + environment: + - PUBSUB_PROJECT1=testproject,topic1:subscription1 + - PUBSUB_PROJECT2=sourceproject,topic2:subscription2 + +networks: + default: + name: ${VECTOR_NETWORK} + external: true diff --git a/scripts/integration/gcp/test.yaml b/scripts/integration/gcp/test.yaml new file mode 100644 index 0000000000000..b48835a94fe9b --- /dev/null +++ b/scripts/integration/gcp/test.yaml @@ -0,0 +1,11 @@ +args: +- --features +- gcp-integration-tests +- --lib +- '::gcp::' + +env: + EMULATOR_ADDRESS: http://gcloud-pubsub:8681 + +matrix: +- version: [latest] diff --git a/scripts/integration/http-client/compose.yaml b/scripts/integration/http-client/compose.yaml new file mode 100644 index 0000000000000..5a42c3a7dbe2a --- /dev/null +++ b/scripts/integration/http-client/compose.yaml @@ -0,0 +1,36 @@ +version: '3' + +services: + dufs: + image: docker.io/sigoden/dufs:${HTTP_CLIENT_VERSION} + command: + - /data + volumes: + - ../../../tests/data/http-client/serve:/data + dufs-auth: + image: docker.io/sigoden/dufs:${HTTP_CLIENT_VERSION} + command: + - -a + - /@user:pass + - --auth-method + - basic + - /data + volumes: + - ../../../tests/data/http-client/serve:/data + dufs-https: + image: docker.io/sigoden/dufs:${HTTP_CLIENT_VERSION} + command: + - --tls-cert + - /certs/ca.cert.pem + - --tls-key + - /certs/ca.key.pem + - /data + volumes: + - ../../../tests/data/http-client/serve:/data + - ../../../tests/data/ca/intermediate_server/certs/dufs-https-chain.cert.pem:/certs/ca.cert.pem + - ../../../tests/data/ca/intermediate_server/private/dufs-https.key.pem:/certs/ca.key.pem + +networks: + default: + name: ${VECTOR_NETWORK} + external: true diff --git a/scripts/integration/http-client/test.yaml b/scripts/integration/http-client/test.yaml new file mode 100644 index 0000000000000..ee8f7adb390c2 --- /dev/null +++ b/scripts/integration/http-client/test.yaml @@ -0,0 +1,13 @@ +args: +- --features +- http-client-integration-tests +- --lib +- 'sources::http_client::' + +env: + DUFS_ADDRESS: http://dufs:5000 + DUFS_AUTH_ADDRESS: http://dufs-auth:5000 + DUFS_HTTPS_ADDRESS: https://dufs-https:5000 + +matrix: +- version: [latest] diff --git a/scripts/integration/humio/compose.yaml b/scripts/integration/humio/compose.yaml new file mode 100644 index 0000000000000..ce1b4991a5e2f --- /dev/null +++ b/scripts/integration/humio/compose.yaml @@ -0,0 +1,10 @@ +version: '3' + +services: + humio: + image: docker.io/humio/humio:${HUMIO_VERSION} + +networks: + default: + name: ${VECTOR_NETWORK} + external: true diff --git a/scripts/integration/humio/test.yaml b/scripts/integration/humio/test.yaml new file mode 100644 index 0000000000000..9e23ae78b88a3 --- /dev/null +++ b/scripts/integration/humio/test.yaml @@ -0,0 +1,11 @@ +args: +- --features +- humio-integration-tests +- --lib +- 'sinks::humio::' + +env: + HUMIO_ADDRESS: http://localhost:8080 + +matrix: +- version: [1.13.1] diff --git a/scripts/integration/influxdb/compose.yaml b/scripts/integration/influxdb/compose.yaml new file mode 100644 index 0000000000000..af1b348c56b8a --- /dev/null +++ b/scripts/integration/influxdb/compose.yaml @@ -0,0 +1,26 @@ +version: '3' + +services: + influxdb-v1: + image: docker.io/influxdb:${INFLUXDB_VERSION} + environment: + - INFLUXDB_REPORTING_DISABLED=true + influxdb-v1-tls: + image: docker.io/influxdb:${INFLUXDB_VERSION} + environment: + - INFLUXDB_REPORTING_DISABLED=true + - INFLUXDB_HTTP_HTTPS_ENABLED=true + - INFLUXDB_HTTP_HTTPS_CERTIFICATE=/etc/ssl/intermediate_server/certs/influxdb-v1-tls-chain.cert.pem + - INFLUXDB_HTTP_HTTPS_PRIVATE_KEY=/etc/ssl/intermediate_server/private/influxdb-v1-tls.key.pem + volumes: + - ../../../tests/data/ca:/etc/ssl:ro + influxdb-v2: + image: docker.io/influxdb:2.0 + command: influxd --reporting-disabled + environment: + - INFLUXDB_REPORTING_DISABLED=true + +networks: + default: + name: ${VECTOR_NETWORK} + external: true diff --git a/scripts/integration/influxdb/test.yaml b/scripts/integration/influxdb/test.yaml new file mode 100644 index 0000000000000..7b89ce43c6be3 --- /dev/null +++ b/scripts/integration/influxdb/test.yaml @@ -0,0 +1,13 @@ +args: +- --features +- influxdb-integration-tests +- --lib +- '::influxdb::' + +env: + INFLUXDB_V1_HTTPS_ADDRESS: https://influxdb-v1-tls:8086 + INFLUXDB_V1_HTTP_ADDRESS: http://influxdb-v1:8086 + INFLUXDB_V2_ADDRESS: http://influxdb-v2:8086 + +matrix: +- version: ['1.8'] diff --git a/scripts/integration/kafka/compose.yaml b/scripts/integration/kafka/compose.yaml new file mode 100644 index 0000000000000..881c0fa3bb387 --- /dev/null +++ b/scripts/integration/kafka/compose.yaml @@ -0,0 +1,40 @@ +version: '3' + +services: + zookeeper: + image: docker.io/wurstmeister/zookeeper:${KAFKA_VERSION} + ports: + - 2181:2181 + kafka: + image: docker.io/wurstmeister/kafka:2.13-2.6.0 + depends_on: + - zookeeper + environment: + - KAFKA_BROKER_ID=1 + - KAFKA_ZOOKEEPER_CONNECT=zookeeper:2181 + - KAFKA_LISTENERS=PLAINTEXT://:9091,SSL://:9092,SASL_PLAINTEXT://:9093 + - KAFKA_ADVERTISED_LISTENERS=PLAINTEXT://kafka:9091,SSL://kafka:9092,SASL_PLAINTEXT://kafka:9093 + - KAFKA_SSL_KEYSTORE_TYPE=PKCS12 + - KAFKA_SSL_KEYSTORE_LOCATION=/certs/kafka.p12 + - KAFKA_SSL_KEYSTORE_PASSWORD=NOPASS + - KAFKA_SSL_TRUSTSTORE_TYPE=PKCS12 + - KAFKA_SSL_TRUSTSTORE_LOCATION=/certs/kafka.p12 + - KAFKA_SSL_TRUSTSTORE_PASSWORD=NOPASS + - KAFKA_SSL_KEY_PASSWORD=NOPASS + - KAFKA_SSL_ENDPOINT_IDENTIFICATION_ALGORITHM=none + - KAFKA_OPTS=-Djava.security.auth.login.config=/etc/kafka/kafka_server_jaas.conf + - KAFKA_INTER_BROKER_LISTENER_NAME=SASL_PLAINTEXT + - KAFKA_SASL_ENABLED_MECHANISMS=PLAIN + - KAFKA_SASL_MECHANISM_INTER_BROKER_PROTOCOL=PLAIN + ports: + - 9091:9091 + - 9092:9092 + - 9093:9093 + volumes: + - ../../../tests/data/ca/intermediate_server/private/kafka.p12:/certs/kafka.p12:ro + - ../../../tests/data/kafka_server_jaas.conf:/etc/kafka/kafka_server_jaas.conf + +networks: + default: + name: ${VECTOR_NETWORK} + external: true diff --git a/scripts/integration/kafka/test.yaml b/scripts/integration/kafka/test.yaml new file mode 100644 index 0000000000000..74d07e1333fa7 --- /dev/null +++ b/scripts/integration/kafka/test.yaml @@ -0,0 +1,11 @@ +args: +- --features +- kafka-integration-tests +- --lib +- '::kafka::' + +env: + KAFKA_HOST: kafka + +matrix: +- version: [latest] diff --git a/scripts/integration/logstash/compose.yaml b/scripts/integration/logstash/compose.yaml new file mode 100644 index 0000000000000..8558790c047ca --- /dev/null +++ b/scripts/integration/logstash/compose.yaml @@ -0,0 +1,19 @@ +version: '3' + +services: + beats-heartbeat: + image: docker.elastic.co/beats/heartbeat:${LOGSTASH_VERSION} + command: -environment=container -strict.perms=false + volumes: + - ../../../tests/data/logstash/heartbeat.yml:/usr/share/heartbeat/heartbeat.yml:ro + logstash: + image: docker.elastic.co/logstash/logstash:7.13.1 + volumes: + - /dev/null:/usr/share/logstash/pipeline/logstash.yml + - ../../../tests/data/host.docker.internal.crt:/tmp/logstash.crt + - ../../../tests/data/logstash/logstash.conf:/usr/share/logstash/pipeline/logstash.conf + +networks: + default: + name: ${VECTOR_NETWORK} + external: true diff --git a/scripts/integration/logstash/test.yaml b/scripts/integration/logstash/test.yaml new file mode 100644 index 0000000000000..adc5a5677aab9 --- /dev/null +++ b/scripts/integration/logstash/test.yaml @@ -0,0 +1,12 @@ +args: +- --features +- logstash-integration-tests +- --lib +- '::logstash::integration_tests::' + +env: + HEARTBEAT_ADDRESS: 0.0.0.0:8080 + LOGSTASH_ADDRESS: 0.0.0.0:8081 + +matrix: +- version: [7.12.1] diff --git a/scripts/integration/loki/compose.yaml b/scripts/integration/loki/compose.yaml new file mode 100644 index 0000000000000..4af16b4fc6567 --- /dev/null +++ b/scripts/integration/loki/compose.yaml @@ -0,0 +1,11 @@ +version: '3' + +services: + loki: + image: docker.io/grafana/loki:${LOKI_VERSION} + command: -config.file=/etc/loki/local-config.yaml -auth.enabled=true + +networks: + default: + name: ${VECTOR_NETWORK} + external: true diff --git a/scripts/integration/loki/test.yaml b/scripts/integration/loki/test.yaml new file mode 100644 index 0000000000000..ff270dae7f934 --- /dev/null +++ b/scripts/integration/loki/test.yaml @@ -0,0 +1,11 @@ +args: +- --features +- loki-integration-tests +- --lib +- '::loki::' + +env: + LOKI_ADDRESS: http://loki:3100 + +matrix: +- version: [2.4.1] diff --git a/scripts/integration/mongodb/compose.yaml b/scripts/integration/mongodb/compose.yaml new file mode 100644 index 0000000000000..95fb10a1fb4d5 --- /dev/null +++ b/scripts/integration/mongodb/compose.yaml @@ -0,0 +1,37 @@ +version: '3' + +services: + mongodb-primary: + image: docker.io/bitnami/mongodb:${MONGODB_VERSION} + environment: + - MONGODB_ADVERTISED_HOSTNAME=mongodb-primary + - MONGODB_REPLICA_SET_MODE=primary + - MONGODB_ROOT_PASSWORD=toor + - MONGODB_REPLICA_SET_KEY=vector + mongodb-secondary: + image: docker.io/bitnami/mongodb:${MONGODB_VERSION} + depends_on: + - mongodb-primary + environment: + - MONGODB_ADVERTISED_HOSTNAME=mongodb-secondary + - MONGODB_REPLICA_SET_MODE=secondary + - MONGODB_INITIAL_PRIMARY_HOST=mongodb-primary + - MONGODB_INITIAL_PRIMARY_PORT_NUMBER=27017 + - MONGODB_INITIAL_PRIMARY_ROOT_PASSWORD=toor + - MONGODB_REPLICA_SET_KEY=vector + mongodb-arbiter: + image: docker.io/bitnami/mongodb:${MONGODB_VERSION} + depends_on: + - mongodb-primary + environment: + - MONGODB_ADVERTISED_HOSTNAME=mongodb-arbiter + - MONGODB_REPLICA_SET_MODE=arbiter + - MONGODB_INITIAL_PRIMARY_HOST=mongodb-primary + - MONGODB_INITIAL_PRIMARY_PORT_NUMBER=27017 + - MONGODB_INITIAL_PRIMARY_ROOT_PASSWORD=toor + - MONGODB_REPLICA_SET_KEY=vector + +networks: + default: + name: ${VECTOR_NETWORK} + external: true diff --git a/scripts/integration/mongodb/test.yaml b/scripts/integration/mongodb/test.yaml new file mode 100644 index 0000000000000..c1c6678ce5b99 --- /dev/null +++ b/scripts/integration/mongodb/test.yaml @@ -0,0 +1,12 @@ +args: +- --features +- mongodb_metrics-integration-tests +- --lib +- '::mongodb_metrics::' + +env: + PRIMARY_MONGODB_ADDRESS: mongodb://root:toor@mongodb-primary + SECONDARY_MONGODB_ADDRESS: mongodb://root:toor@mongodb-secondary + +matrix: +- version: [4.2.10] diff --git a/scripts/integration/nats/compose.yaml b/scripts/integration/nats/compose.yaml new file mode 100644 index 0000000000000..5c7bbc0649897 --- /dev/null +++ b/scripts/integration/nats/compose.yaml @@ -0,0 +1,50 @@ +version: '3' + +services: + nats: + image: docker.io/library/nats:${NATS_VERSION} + nats-userpass: + image: docker.io/library/nats:${NATS_VERSION} + command: + - --user + - natsuser + - --pass + - natspass + nats-token: + image: docker.io/library/nats:${NATS_VERSION} + command: + - --auth + - secret + nats-nkey: + image: docker.io/library/nats:${NATS_VERSION} + command: + - --config + - /usr/share/nats/config/nats-nkey.conf + volumes: + - ../../../tests/data/nats:/usr/share/nats/config + nats-tls: + image: docker.io/library/nats:${NATS_VERSION} + command: + - --config + - /usr/share/nats/config/nats-tls.conf + volumes: + - ../../../tests/data/nats:/usr/share/nats/config + nats-tls-client-cert: + image: docker.io/library/nats:${NATS_VERSION} + command: + - --config + - /usr/share/nats/config/nats-tls-client-cert.conf + volumes: + - ../../../tests/data/nats:/usr/share/nats/config + nats-jwt: + image: docker.io/library/nats:${NATS_VERSION} + command: + - --config + - /usr/share/nats/config/nats-jwt.conf + volumes: + - ../../../tests/data/nats:/usr/share/nats/config + +networks: + default: + name: ${VECTOR_NETWORK} + external: true diff --git a/scripts/integration/nats/test.yaml b/scripts/integration/nats/test.yaml new file mode 100644 index 0000000000000..71354c4c11404 --- /dev/null +++ b/scripts/integration/nats/test.yaml @@ -0,0 +1,17 @@ +args: +- --features +- nats-integration-tests +- --lib +- '::nats::' + +env: + NATS_ADDRESS: nats://nats:4222 + NATS_JWT_ADDRESS: nats://nats-jwt:4222 + NATS_NKEY_ADDRESS: nats://nats-nkey:4222 + NATS_TLS_ADDRESS: nats://nats-tls:4222 + NATS_TLS_CLIENT_CERT_ADDRESS: nats://nats-tls-client-cert:4222 + NATS_TOKEN_ADDRESS: nats://nats-token:4222 + NATS_USERPASS_ADDRESS: nats://nats-userpass:4222 + +matrix: +- version: [latest] diff --git a/scripts/integration/nginx/compose.yaml b/scripts/integration/nginx/compose.yaml new file mode 100644 index 0000000000000..9b37d825958ed --- /dev/null +++ b/scripts/integration/nginx/compose.yaml @@ -0,0 +1,20 @@ +version: '3' + +services: + squid: + image: docker.io/babim/squid + depends_on: + - nginx-proxy + nginx: + image: docker.io/nginx:${NGINX_VERSION} + volumes: + - ../../../tests/data/nginx/:/etc/nginx:ro + nginx-proxy: + image: docker.io/nginx:${NGINX_VERSION} + volumes: + - ../../../tests/data/nginx/:/etc/nginx:ro + +networks: + default: + name: ${VECTOR_NETWORK} + external: true diff --git a/scripts/integration/nginx/test.yaml b/scripts/integration/nginx/test.yaml new file mode 100644 index 0000000000000..61cde026255f5 --- /dev/null +++ b/scripts/integration/nginx/test.yaml @@ -0,0 +1,13 @@ +args: +- --features +- nginx-integration-tests +- --lib +- '::nginx_metrics::' + +env: + NGINX_ADDRESS: http://nginx:8000 + NGINX_PROXY_ADDRESS: http://nginx-proxy:8000 + SQUID_ADDRESS: http://squid:3128 + +matrix: +- version: [1.19.4] diff --git a/scripts/integration/opentelemetry/compose.yaml b/scripts/integration/opentelemetry/compose.yaml new file mode 100644 index 0000000000000..bfd0ac937c412 --- /dev/null +++ b/scripts/integration/opentelemetry/compose.yaml @@ -0,0 +1,12 @@ +version: '3' + +services: + opentelemetry-collector: + image: docker.io/otel/opentelemetry-collector-contrib:${OPENTELEMETRY_VERSION} + volumes: + - ../../../tests/data/opentelemetry/config.yaml:/etc/otelcol-contrib/config.yaml + +networks: + default: + name: ${VECTOR_NETWORK} + external: true diff --git a/scripts/integration/opentelemetry/test.yaml b/scripts/integration/opentelemetry/test.yaml new file mode 100644 index 0000000000000..23874e1e25905 --- /dev/null +++ b/scripts/integration/opentelemetry/test.yaml @@ -0,0 +1,12 @@ +args: +- --features +- opentelemetry-integration-tests +- --lib +- 'sources::opentelemetry::integration_tests::' + +env: + OTEL_HEALTH_URL: http://opentelemetry-collector:13133 + OTEL_OTLPHTTP_URL: http://opentelemetry-collector:9876 + +matrix: +- version: [0.56.0] diff --git a/scripts/integration/postgres/compose.yaml b/scripts/integration/postgres/compose.yaml new file mode 100644 index 0000000000000..cbd63a8105a48 --- /dev/null +++ b/scripts/integration/postgres/compose.yaml @@ -0,0 +1,18 @@ +version: '3' + +services: + postgres: + image: docker.io/postgres:${POSTGRES_VERSION} + command: /postgres-init.sh + environment: + - POSTGRES_USER=vector + - POSTGRES_PASSWORD=vector + volumes: + - socket:/var/run/postgresql + - ../../../tests/data/postgres-init.sh:/postgres-init.sh:ro + - ../../../tests/data/ca:/certs:ro + +networks: + default: + name: ${VECTOR_NETWORK} + external: true diff --git a/scripts/integration/postgres/test.yaml b/scripts/integration/postgres/test.yaml new file mode 100644 index 0000000000000..ec082cc53186a --- /dev/null +++ b/scripts/integration/postgres/test.yaml @@ -0,0 +1,12 @@ +args: +- --features +- postgresql_metrics-integration-tests +- --lib +- ::postgres + +env: + PG_HOST: postgres + PG_SOCKET: /socket + +matrix: +- version: ['13.1'] diff --git a/scripts/integration/prometheus/compose.yaml b/scripts/integration/prometheus/compose.yaml new file mode 100644 index 0000000000000..1b45434224763 --- /dev/null +++ b/scripts/integration/prometheus/compose.yaml @@ -0,0 +1,28 @@ +version: '3' + +services: + influxdb: + image: docker.io/influxdb:${PROMETHEUS_VERSION} + environment: + - INFLUXDB_REPORTING_DISABLED=true + influxdb-tls: + image: docker.io/influxdb:${PROMETHEUS_VERSION} + environment: + - INFLUXDB_REPORTING_DISABLED=true + - INFLUXDB_HTTP_HTTPS_ENABLED=true + - INFLUXDB_HTTP_BIND_ADDRESS=:8087 + - INFLUXDB_BIND_ADDRESS=:8089 + - INFLUXDB_HTTP_HTTPS_CERTIFICATE=/etc/ssl/intermediate_server/certs/localhost-chain.cert.pem + - INFLUXDB_HTTP_HTTPS_PRIVATE_KEY=/etc/ssl/intermediate_server/private/localhost.key.pem + volumes: + - ../../../tests/data/ca:/etc/ssl:ro + prometheus: + image: docker.io/prom/prometheus:${PROMETHEUS_VERSION:-v2.33.4} + command: --config.file=/etc/vector/prometheus.yaml + volumes: + - ../../../tests/data:/etc/vector:ro + +networks: + default: + name: ${VECTOR_NETWORK} + external: true diff --git a/scripts/integration/prometheus/test.yaml b/scripts/integration/prometheus/test.yaml new file mode 100644 index 0000000000000..7b582dfabacd0 --- /dev/null +++ b/scripts/integration/prometheus/test.yaml @@ -0,0 +1,8 @@ +args: +- --features +- prometheus-integration-tests +- --lib +- '::prometheus::' + +matrix: +- version: ['1.8'] diff --git a/scripts/integration/pulsar/compose.yaml b/scripts/integration/pulsar/compose.yaml new file mode 100644 index 0000000000000..d17d04af671b3 --- /dev/null +++ b/scripts/integration/pulsar/compose.yaml @@ -0,0 +1,13 @@ +version: '3' + +services: + pulsar: + image: docker.io/apachepulsar/pulsar:${PULSAR_VERSION} + command: bin/pulsar standalone + ports: + - 6650:6650 + +networks: + default: + name: ${VECTOR_NETWORK} + external: true diff --git a/scripts/integration/pulsar/test.yaml b/scripts/integration/pulsar/test.yaml new file mode 100644 index 0000000000000..c9bb32778804b --- /dev/null +++ b/scripts/integration/pulsar/test.yaml @@ -0,0 +1,11 @@ +args: +- --features +- pulsar-integration-tests +- --lib +- '::pulsar::' + +env: + PULSAR_ADDRESS: pulsar://pulsar:6650 + +matrix: +- version: [latest] diff --git a/scripts/integration/redis/compose.yaml b/scripts/integration/redis/compose.yaml new file mode 100644 index 0000000000000..8b074fd4e0796 --- /dev/null +++ b/scripts/integration/redis/compose.yaml @@ -0,0 +1,10 @@ +version: '3' + +services: + redis: + image: docker.io/redis:${REDIS_VERSION} + +networks: + default: + name: ${VECTOR_NETWORK} + external: true diff --git a/scripts/integration/redis/test.yaml b/scripts/integration/redis/test.yaml new file mode 100644 index 0000000000000..6a71a6c3b557e --- /dev/null +++ b/scripts/integration/redis/test.yaml @@ -0,0 +1,11 @@ +args: +- --features +- redis-integration-tests +- --lib +- '::redis::' + +env: + REDIS_URL: redis://redis:6379/0 + +matrix: +- version: [6-alpine] diff --git a/scripts/integration/shutdown/compose.yaml b/scripts/integration/shutdown/compose.yaml new file mode 100644 index 0000000000000..68ed90c7fd7be --- /dev/null +++ b/scripts/integration/shutdown/compose.yaml @@ -0,0 +1,38 @@ +version: '3' + +services: + zookeeper: + image: docker.io/wurstmeister/zookeeper:${SHUTDOWN_VERSION} + ports: + - 2181:2181 + kafka: + image: docker.io/wurstmeister/kafka:2.13-2.6.0 + depends_on: + - zookeeper + environment: + - KAFKA_BROKER_ID=1 + - KAFKA_ZOOKEEPER_CONNECT=zookeeper:2181 + - KAFKA_LISTENERS=PLAINTEXT://:9091,SSL://:9092,SASL_PLAINTEXT://:9093 + - KAFKA_ADVERTISED_LISTENERS=PLAINTEXT://kafka:9091,SSL://kafka:9092,SASL_PLAINTEXT://kafka:9093 + - KAFKA_SSL_KEYSTORE_LOCATION=/certs/kafka.p12 + - KAFKA_SSL_KEYSTORE_PASSWORD=NOPASS + - KAFKA_SSL_TRUSTSTORE_LOCATION=/certs/kafka.p12 + - KAFKA_SSL_TRUSTSTORE_PASSWORD=NOPASS + - KAFKA_SSL_KEY_PASSWORD=NOPASS + - KAFKA_SSL_ENDPOINT_IDENTIFICATION_ALGORITHM=none + - KAFKA_OPTS=-Djava.security.auth.login.config=/etc/kafka/kafka_server_jaas.conf + - KAFKA_INTER_BROKER_LISTENER_NAME=SASL_PLAINTEXT + - KAFKA_SASL_ENABLED_MECHANISMS=PLAIN + - KAFKA_SASL_MECHANISM_INTER_BROKER_PROTOCOL=PLAIN + ports: + - 9091:9091 + - 9092:9092 + - 9093:9093 + volumes: + - ../../../tests/data/kafka.p12:/certs/kafka.p12:ro + - ../../../tests/data/kafka_server_jaas.conf:/etc/kafka/kafka_server_jaas.conf + +networks: + default: + name: ${VECTOR_NETWORK} + external: true diff --git a/scripts/integration/shutdown/test.yaml b/scripts/integration/shutdown/test.yaml new file mode 100644 index 0000000000000..192a9be6d3bb3 --- /dev/null +++ b/scripts/integration/shutdown/test.yaml @@ -0,0 +1,13 @@ +args: +- --features +- shutdown-tests +- --test +- shutdown +- --test-threads +- '4' + +env: + KAFKA_HOST: kafka + +matrix: +- version: [latest] diff --git a/scripts/integration/splunk/compose.yaml b/scripts/integration/splunk/compose.yaml new file mode 100644 index 0000000000000..29a4926b9b626 --- /dev/null +++ b/scripts/integration/splunk/compose.yaml @@ -0,0 +1,20 @@ +version: '3' + +services: + splunk-hec: + image: docker.io/splunk/splunk:${SPLUNK_VERSION} + environment: + - SPLUNK_START_ARGS=--accept-license + - SPLUNK_PASSWORD=password + - SPLUNK_HEC_TOKEN=abcd1234 + volumes: + - ../../../tests/data/splunk/default.yml:/tmp/defaults/default.yml + ports: + - 8000:8000 + - 8088:8088 + - 8089:8089 + +networks: + default: + name: ${VECTOR_NETWORK} + external: true diff --git a/scripts/integration/splunk/test.yaml b/scripts/integration/splunk/test.yaml new file mode 100644 index 0000000000000..60047a836abc9 --- /dev/null +++ b/scripts/integration/splunk/test.yaml @@ -0,0 +1,12 @@ +args: +- --features +- splunk-integration-tests +- --lib +- '::splunk_hec::' + +env: + SPLUNK_API_ADDRESS: https://splunk-hec:8089 + SPLUNK_HEC_ADDRESS: http://splunk-hec:8088 + +matrix: +- version: [8.2.4] diff --git a/vdev/src/commands/build.rs b/vdev/src/commands/build.rs index 6fe28386c8917..4039fbe8c2799 100644 --- a/vdev/src/commands/build.rs +++ b/vdev/src/commands/build.rs @@ -33,12 +33,10 @@ impl Cli { } command.arg("--features"); - if !self.feature.is_empty() { - command.args([self.feature.join(",")]); - } else if cfg!(windows) { - command.arg("default-msvc"); + if self.feature.is_empty() { + command.arg(platform::default_features()); } else { - command.arg("default"); + command.arg(self.feature.join(",")); } let target = self.target.unwrap_or_else(platform::default_target); diff --git a/vdev/src/commands/integration/mod.rs b/vdev/src/commands/integration/mod.rs index e0058803b722d..f09a99c468d22 100644 --- a/vdev/src/commands/integration/mod.rs +++ b/vdev/src/commands/integration/mod.rs @@ -1,5 +1,10 @@ crate::cli_subcommands! { - "Manage integration test environments" + r#"Manage integration test environments + +These test setups are organized into a set of integrations, located in subdirectories +`scripts/integration`. For each integration, there is a matrix of environments, described in the +`matrix` setting in the `test.yaml` file contained therein."# + mod show, mod start, mod stop, diff --git a/vdev/src/commands/integration/show.rs b/vdev/src/commands/integration/show.rs index 6aad2b81a1af1..6132ae51eb0b6 100644 --- a/vdev/src/commands/integration/show.rs +++ b/vdev/src/commands/integration/show.rs @@ -1,8 +1,8 @@ -use anyhow::{Context, Result}; +use std::collections::HashSet; + +use anyhow::Result; use clap::Args; -use std::path::PathBuf; -use crate::app; use crate::testing::{config::IntegrationTestConfig, state}; /// Show information about integrations @@ -17,27 +17,28 @@ impl Cli { pub fn exec(self) -> Result<()> { match self.integration { None => { - let mut entries = vec![]; - let root_dir: PathBuf = [app::path(), "scripts", "integration"].iter().collect(); - for entry in root_dir - .read_dir() - .with_context(|| format!("failed to read directory {}", root_dir.display()))? - { - let entry = entry?; - if entry.path().is_dir() { - entries.push(entry.file_name().into_string().unwrap()); - } - } - entries.sort(); - - for integration in &entries { - println!("{integration}"); + let entries = IntegrationTestConfig::collect_all()?; + let width = entries + .keys() + .fold(16, |width, entry| width.max(entry.len())); + println!("{:width$} Environment Name(s)", "Integration Name"); + println!("{:width$} -------------------", "----------------"); + for (integration, config) in entries { + let envs_dir = state::EnvsDir::new(&integration); + let active_envs = envs_dir.list_active()?; + let environments = config + .environments() + .keys() + .map(|environment| format(&active_envs, environment)) + .collect::>() + .join(" "); + println!("{integration:width$} {environments}"); } } Some(integration) => { let (_test_dir, config) = IntegrationTestConfig::load(&integration)?; - let envs_dir = state::envs_dir(&integration); - let active_envs = state::active_envs(&envs_dir)?; + let envs_dir = state::EnvsDir::new(&integration); + let active_envs = envs_dir.list_active()?; println!("Test args: {}", config.args.join(" ")); @@ -55,3 +56,11 @@ impl Cli { Ok(()) } } + +fn format(active_envs: &HashSet, environment: &str) -> String { + if active_envs.contains(environment) { + format!("{environment} (active)") + } else { + environment.into() + } +} diff --git a/vdev/src/commands/integration/start.rs b/vdev/src/commands/integration/start.rs index f413439cccfd3..b2ef6ab18c19a 100644 --- a/vdev/src/commands/integration/start.rs +++ b/vdev/src/commands/integration/start.rs @@ -1,10 +1,7 @@ -use std::process::Command; - -use anyhow::{bail, Result}; +use anyhow::Result; use clap::Args; -use crate::app::CommandExt as _; -use crate::testing::{config::IntegrationTestConfig, runner::*, state}; +use crate::testing::integration::IntegrationTest; /// Start an environment #[derive(Args, Debug)] @@ -19,36 +16,6 @@ pub struct Cli { impl Cli { pub fn exec(self) -> Result<()> { - let (test_dir, config) = IntegrationTestConfig::load(&self.integration)?; - - let envs_dir = state::envs_dir(&self.integration); - let runner = IntegrationTestRunner::new(self.integration)?; - runner.ensure_network()?; - - let mut command = Command::new("cargo"); - command.current_dir(&test_dir); - command.env(NETWORK_ENV_VAR, runner.network_name()); - command.args(["run", "--quiet", "--", "start"]); - - let environments = config.environments(); - let json = match environments.get(&self.environment) { - Some(config) => serde_json::to_string(config)?, - None => bail!("unknown environment: {}", self.environment), - }; - command.arg(&json); - - if state::env_exists(&envs_dir, &self.environment) { - bail!("environment is already up"); - } - - if let Some(env_vars) = config.env { - command.envs(env_vars); - } - - waiting!("Starting environment {}", &self.environment); - command.check_run()?; - - state::save_env(&envs_dir, &self.environment, &json)?; - Ok(()) + IntegrationTest::new(self.integration, self.environment)?.start() } } diff --git a/vdev/src/commands/integration/stop.rs b/vdev/src/commands/integration/stop.rs index fb07df072c1f3..e0e6effb9b379 100644 --- a/vdev/src/commands/integration/stop.rs +++ b/vdev/src/commands/integration/stop.rs @@ -1,9 +1,7 @@ -use anyhow::{bail, Result}; +use anyhow::Result; use clap::Args; -use std::process::Command; -use crate::app::CommandExt as _; -use crate::testing::{config::IntegrationTestConfig, runner::*, state}; +use crate::testing::{integration::IntegrationTest, state::EnvsDir}; /// Stop an environment #[derive(Args, Debug)] @@ -12,8 +10,8 @@ pub struct Cli { /// The desired integration integration: String, - /// The desired environment - environment: String, + /// The desired environment. If not present, all running environments are stopped. + environment: Option, /// Use the currently defined configuration if the environment is not up #[arg(short, long)] @@ -22,40 +20,19 @@ pub struct Cli { impl Cli { pub fn exec(self) -> Result<()> { - let (test_dir, config) = IntegrationTestConfig::load(&self.integration)?; - let envs_dir = state::envs_dir(&self.integration); - let runner = IntegrationTestRunner::new(self.integration.clone())?; - - let mut command = Command::new("cargo"); - command.current_dir(&test_dir); - command.env(NETWORK_ENV_VAR, runner.network_name()); - command.args(["run", "--quiet", "--", "stop"]); - - if state::env_exists(&envs_dir, &self.environment) { - command.arg(state::read_env_config(&envs_dir, &self.environment)?); - } else if self.force { - let environments = config.environments(); - if let Some(config) = environments.get(&self.environment) { - command.arg(serde_json::to_string(config)?); + if let Some(environment) = self.environment { + IntegrationTest::new(self.integration, environment)?.stop(self.force) + } else { + let envs = EnvsDir::new(&self.integration).list_active()?; + if envs.is_empty() { + println!("No environments for {:?} are active.", self.integration); } else { - bail!("unknown environment: {}", self.environment); + for environment in envs { + IntegrationTest::new(self.integration.clone(), environment)? + .stop(self.force)?; + } } - } else { - bail!("environment is not up"); - } - - if let Some(env_vars) = config.env { - command.envs(env_vars); + Ok(()) } - - waiting!("Stopping environment {}", &self.environment); - command.check_run()?; - - state::remove_env(&envs_dir, &self.environment)?; - if state::active_envs(&envs_dir)?.is_empty() { - runner.stop()?; - } - - Ok(()) } } diff --git a/vdev/src/commands/integration/test.rs b/vdev/src/commands/integration/test.rs index 45d3122990999..b59b9f7227e62 100644 --- a/vdev/src/commands/integration/test.rs +++ b/vdev/src/commands/integration/test.rs @@ -1,107 +1,60 @@ -use anyhow::{bail, Result}; +use anyhow::Result; use clap::Args; -use std::collections::BTreeMap; -use std::process::Command; -use crate::app::CommandExt as _; -use crate::testing::{config::IntegrationTestConfig, runner::*, state}; - -/// Execute tests +use crate::testing::runner::{ContainerTestRunnerBase, IntegrationTestRunner}; +use crate::testing::{config::IntegrationTestConfig, integration::IntegrationTest, state::EnvsDir}; + +/// Execute integration tests +/// +/// If an environment is named, a single test is run. If the environment was not previously started, +/// it is started before the test is run and stopped afterwards. +/// +/// If no environment is named, but some have been started already, only those environments are run. +/// +/// Otherwise, all environments are started, the test run, and then stopped. #[derive(Args, Debug)] #[command()] pub struct Cli { /// The desired integration integration: String, - /// The desired environment + /// The desired environment (optional) environment: Option, /// Extra test command arguments - args: Option>, + args: Vec, } impl Cli { pub fn exec(self) -> Result<()> { - let (test_dir, config) = IntegrationTestConfig::load(&self.integration)?; - let runner = IntegrationTestRunner::new(self.integration.clone())?; - let envs_dir = state::envs_dir(&self.integration); + let (_test_dir, config) = IntegrationTestConfig::load(&self.integration)?; let envs = config.environments(); - let env_vars: BTreeMap<_, _> = config - .env - .clone() - .map_or(BTreeMap::default(), |map| map.into_iter().collect()); + let env_vars = config.env.unwrap_or_default(); - let mut args: Vec<_> = config.args.into_iter().collect(); - if let Some(configured_args) = self.args { - args.extend(configured_args); - } + let mut args = config.args; + args.extend(self.args); if let Some(environment) = &self.environment { - if !state::env_exists(&envs_dir, environment) { - bail!("environment {environment} is not up"); - } - - return runner.test(&env_vars, &args); - } - - runner.ensure_network()?; - - let active_envs = state::active_envs(&envs_dir)?; - for (env_name, env_config) in envs { - if !(active_envs.is_empty() || active_envs.contains(&env_name)) { - continue; - } - - let env_active = state::env_exists(&envs_dir, &env_name); - if !env_active { - let mut command = Command::new("cargo"); - command.current_dir(&test_dir); - command.env(NETWORK_ENV_VAR, runner.network_name()); - command.args(["run", "--quiet", "--", "start"]); - - let json = serde_json::to_string(&env_config)?; - command.arg(&json); - - if let Some(env_vars) = &config.env { - command.envs(env_vars); + IntegrationTest::new(&self.integration, environment)?.test(&env_vars, &args) + } else { + let runner = IntegrationTestRunner::new(self.integration.clone())?; + runner.ensure_network()?; + + let active_envs = EnvsDir::new(&self.integration).list_active()?; + for env_name in envs.keys() { + if !(active_envs.is_empty() || active_envs.contains(env_name)) { + continue; } - waiting!("Starting environment {}", env_name); - command.check_run()?; - - state::save_env(&envs_dir, &env_name, &json)?; + IntegrationTest::new(&self.integration, env_name)?.test(&env_vars, &args)?; } - runner.test(&env_vars, &args)?; - - if !env_active { - let mut command = Command::new("cargo"); - command.current_dir(&test_dir); - command.env(NETWORK_ENV_VAR, runner.network_name()); - command.args([ - "run", - "--quiet", - "--", - "stop", - &state::read_env_config(&envs_dir, &env_name)?, - ]); - - if let Some(env_vars) = &config.env { - command.envs(env_vars); - } - - waiting!("Stopping environment {}", env_name); - command.check_run()?; - - state::remove_env(&envs_dir, &env_name)?; + if active_envs.is_empty() { + runner.stop()?; } - } - if active_envs.is_empty() { - runner.stop()?; + Ok(()) } - - Ok(()) } } diff --git a/vdev/src/commands/test.rs b/vdev/src/commands/test.rs index 92871811efc70..f847a857869ee 100644 --- a/vdev/src/commands/test.rs +++ b/vdev/src/commands/test.rs @@ -2,6 +2,7 @@ use anyhow::Result; use clap::Args; use std::collections::BTreeMap; +use crate::platform; use crate::testing::{config::RustToolchainConfig, runner::get_agent_test_runner}; /// Execute tests @@ -20,35 +21,33 @@ pub struct Cli { env: Option>, } +fn parse_env(env: Vec) -> BTreeMap { + env.into_iter() + .map(|entry| { + let split = entry.split_once('='); + #[allow(clippy::map_unwrap_or)] + split + .map(|(k, v)| (k.to_owned(), v.to_owned())) + .unwrap_or_else(|| (entry, String::new())) + }) + .collect() +} + impl Cli { pub fn exec(self) -> Result<()> { let toolchain_config = RustToolchainConfig::parse()?; let runner = get_agent_test_runner(self.container, toolchain_config.channel); - let mut env_vars = BTreeMap::new(); - if let Some(extra_env_vars) = &self.env { - for entry in extra_env_vars { - if let Some((key, value)) = entry.split_once('=') { - env_vars.insert(key.to_string(), value.to_string()); - } else { - env_vars.insert(entry.to_string(), String::new()); - } - } - } - let mut args = vec!["--workspace".to_string()]; if let Some(extra_args) = &self.args { args.extend(extra_args.clone()); if !(self.container || extra_args.contains(&"--features".to_string())) { - if cfg!(windows) { - args.extend(["--features".to_string(), "default-msvc".to_string()]); - } else { - args.extend(["--features".to_string(), "default".to_string()]); - } + let features = platform::default_features(); + args.extend(["--features".to_string(), features.to_string()]); } } - runner.test(&env_vars, &args) + runner.test(&parse_env(self.env.unwrap_or_default()), &args) } } diff --git a/vdev/src/main.rs b/vdev/src/main.rs index d0a945714347b..e4237be03dee0 100644 --- a/vdev/src/main.rs +++ b/vdev/src/main.rs @@ -3,8 +3,7 @@ clippy::module_name_repetitions, clippy::print_stdout, clippy::unused_self, - clippy::unnecessary_wraps, - clippy::wildcard_imports + clippy::unnecessary_wraps )] #[macro_use] diff --git a/vdev/src/platform.rs b/vdev/src/platform.rs index 1e9c1754f0766..ec9e22a2c53fd 100644 --- a/vdev/src/platform.rs +++ b/vdev/src/platform.rs @@ -28,6 +28,14 @@ pub fn default_target() -> String { } } +pub fn default_features() -> &'static str { + if cfg!(windows) { + "default-msvc" + } else { + "default" + } +} + #[once] fn _project_dirs() -> ProjectDirs { ProjectDirs::from("", "vector", "vdev").expect("Could not determine the project directory") diff --git a/vdev/src/testing/config.rs b/vdev/src/testing/config.rs index 548bfc50c4406..ff11638b546fc 100644 --- a/vdev/src/testing/config.rs +++ b/vdev/src/testing/config.rs @@ -1,13 +1,13 @@ +use std::collections::{BTreeMap, HashMap}; +use std::fs; +use std::path::{Path, PathBuf}; + use anyhow::{bail, Context, Result}; use hashlink::LinkedHashMap; use itertools::{self, Itertools}; use serde::Deserialize; -use std::collections::BTreeMap; -use std::fs; -use std::path::{Path, PathBuf}; - -use crate::app; +use crate::{app, util}; const FILE_NAME: &str = "test.yaml"; @@ -41,6 +41,8 @@ pub struct IntegrationTestConfig { matrix: Vec>>, } +pub type Environment = HashMap; + impl IntegrationTestConfig { fn parse_file(config_file: &Path) -> Result { let contents = fs::read_to_string(config_file) @@ -55,16 +57,16 @@ impl IntegrationTestConfig { Ok(config) } - pub fn environments(&self) -> LinkedHashMap> { + pub fn environments(&self) -> LinkedHashMap { let mut environments = LinkedHashMap::new(); for matrix in &self.matrix { for product in matrix.values().multi_cartesian_product() { - let mut config = LinkedHashMap::new(); - for (variable, &value) in matrix.keys().zip(product.iter()) { - config.insert(variable.clone(), value.clone()); - } - + let config: Environment = matrix + .keys() + .zip(product.iter()) + .map(|(variable, &value)| (variable.clone(), value.clone())) + .collect(); environments.insert(product.iter().join("-"), config); } } @@ -84,19 +86,19 @@ impl IntegrationTestConfig { Ok((test_dir, config)) } - #[allow(dead_code)] - pub fn collect_all(root: &str) -> Result> { + pub fn collect_all() -> Result> { let mut configs = BTreeMap::new(); - let tests_dir: PathBuf = [root, "scripts", "integration"].iter().collect(); + let tests_dir: PathBuf = [app::path(), "scripts", "integration"].iter().collect(); for entry in tests_dir.read_dir()? { let entry = entry?; - if !entry.path().is_dir() { - continue; + if entry.path().is_dir() { + let config_file: PathBuf = + [entry.path().to_str().unwrap(), FILE_NAME].iter().collect(); + if util::exists(&config_file)? { + let config = Self::parse_file(&config_file)?; + configs.insert(entry.file_name().into_string().unwrap(), config); + } } - - let config_file: PathBuf = [entry.path().to_str().unwrap(), FILE_NAME].iter().collect(); - let config = Self::parse_file(&config_file)?; - configs.insert(entry.file_name().into_string().unwrap(), config); } Ok(configs) diff --git a/vdev/src/testing/integration.rs b/vdev/src/testing/integration.rs new file mode 100644 index 0000000000000..6f0042f3c8444 --- /dev/null +++ b/vdev/src/testing/integration.rs @@ -0,0 +1,128 @@ +use std::{collections::BTreeMap, path::Path, path::PathBuf, process::Command}; + +use anyhow::{bail, Context, Result}; + +use super::runner::{ + ContainerTestRunnerBase as _, IntegrationTestRunner, TestRunner as _, CONTAINER_TOOL, + NETWORK_ENV_VAR, +}; +use super::{config::Environment, config::IntegrationTestConfig, state::EnvsDir}; +use crate::app::CommandExt as _; + +pub struct IntegrationTest { + integration: String, + environment: String, + test_dir: PathBuf, + config: IntegrationTestConfig, + envs_dir: EnvsDir, + runner: IntegrationTestRunner, +} + +impl IntegrationTest { + pub fn new(integration: impl Into, environment: impl Into) -> Result { + let integration = integration.into(); + let environment = environment.into(); + let (test_dir, config) = IntegrationTestConfig::load(&integration)?; + let envs_dir = EnvsDir::new(&integration); + let runner = IntegrationTestRunner::new(integration.clone())?; + + Ok(Self { + integration, + environment, + test_dir, + config, + envs_dir, + runner, + }) + } + + pub fn env_exists(&self) -> bool { + self.envs_dir.exists(&self.environment) + } + + pub fn test(&self, env_vars: &BTreeMap, args: &[String]) -> Result<()> { + let active = self.env_exists(); + if !active { + self.start()?; + } + + self.runner.test(env_vars, args)?; + if !active { + self.stop(false)?; + } + Ok(()) + } + + pub fn start(&self) -> Result<()> { + self.runner.ensure_network()?; + + let environments = self.config.environments(); + let cmd_config = match environments.get(&self.environment) { + Some(config) => config, + None => bail!("unknown environment: {}", self.environment), + }; + + if self.envs_dir.exists(&self.environment) { + bail!("environment is already up"); + } + + self.run_compose("Starting", &["up", "--detach"], cmd_config)?; + + self.envs_dir.save(&self.environment, cmd_config) + } + + pub fn stop(&self, force: bool) -> Result<()> { + let cmd_config: Environment = if self.envs_dir.exists(&self.environment) { + self.envs_dir.read_config(&self.environment)? + } else if force { + let environments = self.config.environments(); + if let Some(config) = environments.get(&self.environment) { + config.clone() + } else { + bail!("unknown environment: {}", self.environment); + } + } else { + bail!("environment is not up"); + }; + + self.run_compose("Stopping", &["down", "--timeout", "0"], &cmd_config)?; + + self.envs_dir.remove(&self.environment)?; + if self.envs_dir.list_active()?.is_empty() { + self.runner.stop()?; + } + + Ok(()) + } + + fn run_compose(&self, action: &str, args: &[&'static str], config: &Environment) -> Result<()> { + let compose_path: PathBuf = [&self.test_dir, Path::new("compose.yaml")].iter().collect(); + let compose_file = dunce::canonicalize(compose_path) + .context("Could not canonicalize docker compose path")? + .display() + .to_string(); + + let mut command = CONTAINER_TOOL.clone(); + command.push("-compose"); + let mut command = Command::new(command); + command.args(["--file", &compose_file]); + command.args(args); + + command.current_dir(&self.test_dir); + + command.env(NETWORK_ENV_VAR, self.runner.network_name()); + if let Some(env_vars) = &self.config.env { + command.envs(env_vars); + } + if let Some(version) = config.get("version") { + let version_env = format!( + "{}_VERSION", + self.integration.replace('-', "_").to_uppercase() + ); + command.env(version_env, version); + } + + waiting!("{action} environment {}", self.environment); + command.check_run() + } +} diff --git a/vdev/src/testing/mod.rs b/vdev/src/testing/mod.rs index fa895888889d4..1bec37be971f7 100644 --- a/vdev/src/testing/mod.rs +++ b/vdev/src/testing/mod.rs @@ -1,3 +1,4 @@ pub mod config; +pub mod integration; pub mod runner; pub mod state; diff --git a/vdev/src/testing/runner.rs b/vdev/src/testing/runner.rs index 6121022a54053..4973c2216260d 100644 --- a/vdev/src/testing/runner.rs +++ b/vdev/src/testing/runner.rs @@ -94,18 +94,25 @@ trait ContainerTestRunner: ContainerTestRunnerBase { fn state(&self) -> Result { let mut command = dockercmd(["ps", "-a", "--format", "{{.Names}} {{.State}}"]); + let container_name = self.container_name(); for line in command.capture_output()?.lines() { if let Some((name, state)) = line.split_once(' ') { - if name == self.container_name() { - return Ok(match state { - "created" => RunnerState::Created, - "dead" => RunnerState::Dead, - "exited" => RunnerState::Exited, - "paused" => RunnerState::Paused, - "restarting" => RunnerState::Restarting, - "running" => RunnerState::Running, - _ => RunnerState::Unknown, + if name == container_name { + return Ok(if state == "created" { + RunnerState::Created + } else if state == "dead" { + RunnerState::Dead + } else if state == "exited" || state.starts_with("Exited ") { + RunnerState::Exited + } else if state == "paused" { + RunnerState::Paused + } else if state == "restarting" { + RunnerState::Restarting + } else if state == "running" || state.starts_with("Up ") { + RunnerState::Running + } else { + RunnerState::Unknown }); } } diff --git a/vdev/src/testing/state.rs b/vdev/src/testing/state.rs index 29ecfb260ad00..6d0e6cfa32ecc 100644 --- a/vdev/src/testing/state.rs +++ b/vdev/src/testing/state.rs @@ -4,72 +4,81 @@ use std::path::{Path, PathBuf}; use anyhow::{Context, Result}; +use super::config::Environment; use crate::platform; const CONFIG_FILE: &str = "config.json"; -pub fn envs_dir(integration: &str) -> PathBuf { - [ - &platform::data_dir(), - Path::new("integration/envs"), - Path::new(integration), - ] - .iter() - .collect() +pub struct EnvsDir { + path: PathBuf, } -pub fn env_exists(envs_dir: &Path, environment: &str) -> bool { - let dir: PathBuf = [envs_dir, Path::new(environment)].iter().collect(); - dir.is_dir() -} +impl EnvsDir { + pub fn new(integration: &str) -> Self { + let path = [ + platform::data_dir().as_path(), + Path::new("integration"), + Path::new("envs"), + Path::new(integration), + ] + .iter() + .collect(); + Self { path } + } -pub fn active_envs(envs_dir: &Path) -> Result> { - let mut environments = HashSet::new(); - if !envs_dir.is_dir() { - return Ok(environments); + pub fn exists(&self, environment: &str) -> bool { + self.path.join(environment).is_dir() } - for entry in envs_dir - .read_dir() - .with_context(|| format!("failed to read directory {}", envs_dir.display()))? - { - let entry = entry - .with_context(|| format!("failed to read directory entry {}", envs_dir.display()))?; - if entry.path().is_dir() { - environments.insert(entry.file_name().into_string().unwrap()); + pub fn list_active(&self) -> Result> { + let mut environments = HashSet::new(); + if self.path.is_dir() { + for entry in self + .path + .read_dir() + .with_context(|| format!("failed to read directory {:?}", self.path))? + { + let entry = entry.with_context(|| { + format!("failed to read directory entry in {:?}", self.path) + })?; + if entry.path().is_dir() { + environments.insert(entry.file_name().into_string().unwrap_or_else(|entry| { + panic!("Invalid directory entry in {:?}: {entry:?}", self.path) + })); + } + } } + Ok(environments) } - Ok(environments) -} + pub fn save(&self, environment: &str, config: &Environment) -> Result<()> { + let mut path = self.path.join(environment); + if !path.is_dir() { + fs::create_dir_all(&path) + .with_context(|| format!("failed to create directory {path:?}"))?; + } -pub fn save_env(envs_dir: &Path, environment: &str, config: &str) -> Result<()> { - let mut path = envs_dir.join(environment); - if !path.is_dir() { - fs::create_dir_all(&path) - .with_context(|| format!("failed to create directory {}", path.display()))?; + let config = serde_json::to_string(&config)?; + path.push(CONFIG_FILE); + fs::write(&path, config).with_context(|| format!("failed to write file {path:?}")) } - path.push(CONFIG_FILE); - fs::write(&path, config).with_context(|| format!("failed to write file {}", path.display()))?; - - Ok(()) -} + pub fn read_config(&self, environment: &str) -> Result { + let mut config_file = self.path.join(environment); + config_file.push(CONFIG_FILE); -pub fn read_env_config(envs_dir: &Path, environment: &str) -> Result { - let mut config_file = envs_dir.join(environment); - config_file.push(CONFIG_FILE); + let json = fs::read_to_string(&config_file) + .with_context(|| format!("failed to write file {config_file:?}"))?; + serde_json::from_str(&json).with_context(|| format!("invalid contents in {config_file:?}")) + } - fs::read_to_string(&config_file) - .with_context(|| format!("failed to write file {}", config_file.display())) -} + pub fn remove(&self, environment: &str) -> Result<()> { + let env_path = self.path.join(environment); + if env_path.is_dir() { + fs::remove_dir_all(&env_path) + .with_context(|| format!("failed to remove directory {env_path:?}"))?; + } -pub fn remove_env(envs_dir: &Path, environment: &str) -> Result<()> { - let env_path = envs_dir.join(environment); - if env_path.is_dir() { - fs::remove_dir_all(&env_path) - .with_context(|| format!("failed to remove directory {env_path:?}"))?; + Ok(()) } - - Ok(()) } diff --git a/vdev/src/util.rs b/vdev/src/util.rs index 87ff15ae9b0c3..16f978153bd96 100644 --- a/vdev/src/util.rs +++ b/vdev/src/util.rs @@ -1,5 +1,5 @@ use std::process::{Command, Output}; -use std::{collections::BTreeMap, fs}; +use std::{collections::BTreeMap, fmt::Debug, fs, io::ErrorKind, path::Path}; use anyhow::{Context as _, Result}; use serde::Deserialize; @@ -46,3 +46,11 @@ pub fn release_channel() -> Result<&'static str> { } }) } + +pub fn exists(path: impl AsRef + Debug) -> Result { + match fs::metadata(path.as_ref()) { + Ok(_) => Ok(true), + Err(error) if error.kind() == ErrorKind::NotFound => Ok(false), + Err(error) => Err(error).context(format!("Could not stat {path:?}")), + } +}