Skip to content

Commit e5e3ed4

Browse files
tulirenschlattk
authored andcommitted
🎉 Testing destination: multiple logging modes (airbytehq#8824)
* Implement destination null * Update existing testing destinations * Merge in logging consumer * Remove old destination null * Add documentation * Add destination to build and summary * Fix test * Update acceptance test * Log state message * Remove unused variable * Remove extra statement * Remove old null doc * Add dev null destination * Update doc to include changelog for dev null * Format code * Fix doc * Register e2e test destination in seed
1 parent f087f9f commit e5e3ed4

File tree

32 files changed

+1028
-69
lines changed

32 files changed

+1028
-69
lines changed
Loading

airbyte-config/init/src/main/resources/seed/destination_definitions.yaml

+6
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,12 @@
4545
dockerImageTag: 0.1.0
4646
documentationUrl: https://docs.airbyte.io/integrations/destinations/dynamodb
4747
icon: dynamodb.svg
48+
- name: E2E Testing
49+
destinationDefinitionId: 2eb65e87-983a-4fd7-b3e3-9d9dc6eb8537
50+
dockerRepository: airbyte/destination-e2e-test
51+
dockerImageTag: 0.2.0
52+
documentationUrl: https://docs.airbyte.io/integrations/destinations/e2e-test
53+
icon: airbyte.svg
4854
- destinationDefinitionId: 68f351a7-2745-4bef-ad7f-996b8e51bb8c
4955
name: ElasticSearch
5056
dockerRepository: airbyte/destination-elasticsearch

airbyte-config/init/src/main/resources/seed/destination_specs.yaml

+159
Original file line numberDiff line numberDiff line change
@@ -769,6 +769,165 @@
769769
supported_destination_sync_modes:
770770
- "overwrite"
771771
- "append"
772+
- dockerImage: "airbyte/destination-e2e-test:0.2.0"
773+
spec:
774+
documentationUrl: "https://example.com"
775+
connectionSpecification:
776+
$schema: "http://json-schema.org/draft-07/schema#"
777+
title: "E2E Test Destination Spec"
778+
type: "object"
779+
oneOf:
780+
- title: "Logging"
781+
required:
782+
- "type"
783+
- "logging_config"
784+
properties:
785+
type:
786+
type: "string"
787+
const: "LOGGING"
788+
default: "LOGGING"
789+
logging_config:
790+
title: "Logging Configuration"
791+
type: "object"
792+
description: "Configurate how the messages are logged."
793+
oneOf:
794+
- title: "First N Entries"
795+
description: "Log first N entries per stream."
796+
type: "object"
797+
required:
798+
- "logging_type"
799+
- "max_entry_count"
800+
properties:
801+
logging_type:
802+
type: "string"
803+
enum:
804+
- "FirstN"
805+
default: "FirstN"
806+
max_entry_count:
807+
title: "N"
808+
description: "Number of entries to log. This destination is for\
809+
\ testing only. So it won't make sense to log infinitely. The\
810+
\ maximum is 1,000 entries."
811+
type: "number"
812+
default: 100
813+
examples:
814+
- 100
815+
minimum: 1
816+
maximum: 1000
817+
- title: "Every N-th Entry"
818+
description: "For each stream, log every N-th entry with a maximum cap."
819+
type: "object"
820+
required:
821+
- "logging_type"
822+
- "nth_entry_to_log"
823+
- "max_entry_count"
824+
properties:
825+
logging_type:
826+
type: "string"
827+
enum:
828+
- "EveryNth"
829+
default: "EveryNth"
830+
nth_entry_to_log:
831+
title: "N"
832+
description: "The N-th entry to log for each stream. N starts from\
833+
\ 1. For example, when N = 1, every entry is logged; when N =\
834+
\ 2, every other entry is logged; when N = 3, one out of three\
835+
\ entries is logged."
836+
type: "number"
837+
example:
838+
- 3
839+
minimum: 1
840+
maximum: 1000
841+
max_entry_count:
842+
title: "Max Log Entries"
843+
description: "Max number of entries to log. This destination is\
844+
\ for testing only. So it won't make sense to log infinitely.\
845+
\ The maximum is 1,000 entries."
846+
type: "number"
847+
default: 100
848+
examples:
849+
- 100
850+
minimum: 1
851+
maximum: 1000
852+
- title: "Random Sampling"
853+
description: "For each stream, randomly log a percentage of the entries\
854+
\ with a maximum cap."
855+
type: "object"
856+
required:
857+
- "logging_type"
858+
- "sampling_ratio"
859+
- "max_entry_count"
860+
properties:
861+
logging_type:
862+
type: "string"
863+
enum:
864+
- "RandomSampling"
865+
default: "RandomSampling"
866+
sampling_ratio:
867+
title: "Sampling Ratio"
868+
description: "A positive floating number smaller than 1."
869+
type: "number"
870+
default: 0.001
871+
examples:
872+
- 0.001
873+
minimum: 0
874+
maximum: 1
875+
seed:
876+
title: "Random Number Generator Seed"
877+
description: "When the seed is unspecified, the current time millis\
878+
\ will be used as the seed."
879+
type: "number"
880+
examples:
881+
- 1900
882+
max_entry_count:
883+
title: "Max Log Entries"
884+
description: "Max number of entries to log. This destination is\
885+
\ for testing only. So it won't make sense to log infinitely.\
886+
\ The maximum is 1,000 entries."
887+
type: "number"
888+
default: 100
889+
examples:
890+
- 100
891+
minimum: 1
892+
maximum: 1000
893+
- title: "Silent"
894+
required:
895+
- "type"
896+
properties:
897+
type:
898+
type: "string"
899+
const: "SILENT"
900+
default: "SILENT"
901+
- title: "Throttled"
902+
required:
903+
- "type"
904+
- "millis_per_record"
905+
properties:
906+
type:
907+
type: "string"
908+
const: "THROTTLED"
909+
default: "THROTTLED"
910+
millis_per_record:
911+
description: "Number of milli-second to pause in between records."
912+
type: "integer"
913+
- title: "Failing"
914+
required:
915+
- "type"
916+
- "num_messages"
917+
properties:
918+
type:
919+
type: "string"
920+
const: "FAILING"
921+
default: "FAILING"
922+
num_messages:
923+
description: "Number of messages after which to fail."
924+
type: "integer"
925+
supportsIncremental: true
926+
supportsNormalization: false
927+
supportsDBT: false
928+
supported_destination_sync_modes:
929+
- "overwrite"
930+
- "append"
772931
- dockerImage: "airbyte/destination-elasticsearch:0.1.0"
773932
spec:
774933
documentationUrl: "https://docs.airbyte.io/integrations/destinations/elasticsearch"

airbyte-integrations/builds.md

+2
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,9 @@
112112
| ClickHouse | [![destination-clickhouse](https://img.shields.io/endpoint?url=https%3A%2F%2Fdnsgjos7lj2fu.cloudfront.net%2Ftests%2Fsummary%2Fdestination-clickhouse%2Fbadge.json)](https://dnsgjos7lj2fu.cloudfront.net/tests/summary/destination-clickhouse) |
113113
| Cassandra | [![destination-cassandra](https://img.shields.io/endpoint?url=https%3A%2F%2Fdnsgjos7lj2fu.cloudfront.net%2Ftests%2Fsummary%2Fdestination-cassandra%2Fbadge.json)](https://dnsgjos7lj2fu.cloudfront.net/tests/summary/destination-cassandra) |
114114
| Databricks | (Temporarily Not Available) |
115+
| Dev Null | [![destination-dev-null](https://img.shields.io/endpoint?url=https%3A%2F%2Fdnsgjos7lj2fu.cloudfront.net%2Ftests%2Fsummary%2Fdestination-dev-null%2Fbadge.json)](https://dnsgjos7lj2fu.cloudfront.net/tests/summary/destination-dev-null) |
115116
| Elasticsearch | (Temporarily Not Available) |
117+
| End-to-End Testing | [![destination-e2e-test](https://img.shields.io/endpoint?url=https%3A%2F%2Fdnsgjos7lj2fu.cloudfront.net%2Ftests%2Fsummary%2Fdestination-e2e-test%2Fbadge.json)](https://dnsgjos7lj2fu.cloudfront.net/tests/summary/destination-e2e-test) |
116118
| Google Cloud Storage (GCS) | [![destination-gcs](https://img.shields.io/endpoint?url=https%3A%2F%2Fdnsgjos7lj2fu.cloudfront.net%2Ftests%2Fsummary%2Fdestination-gcs%2Fbadge.json)](https://dnsgjos7lj2fu.cloudfront.net/tests/summary/destination-gcs) |
117119
| Google Firestore | [![destination-firestore](https://img.shields.io/endpoint?url=https%3A%2F%2Fdnsgjos7lj2fu.cloudfront.net%2Ftests%2Fsummary%2Fdestination-firestore%2Fbadge.json)](https://dnsgjos7lj2fu.cloudfront.net/tests/summary/destination-firestore) |
118120
| Google PubSub | [![destination-pubsub](https://img.shields.io/endpoint?url=https%3A%2F%2Fdnsgjos7lj2fu.cloudfront.net%2Ftests%2Fsummary%2Fdestination-pubsub%2Fbadge.json)](https://dnsgjos7lj2fu.cloudfront.net/tests/summary/destination-pubsub) |
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
*
2+
!Dockerfile
3+
!build
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
FROM airbyte/integration-base-java:dev
2+
3+
WORKDIR /airbyte
4+
ENV APPLICATION destination-dev-null
5+
6+
COPY build/distributions/${APPLICATION}*.tar ${APPLICATION}.tar
7+
RUN tar xf ${APPLICATION}.tar --strip-components=1
8+
9+
LABEL io.airbyte.version=0.1.0
10+
LABEL io.airbyte.name=airbyte/destination-dev-null
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
# Destination Dev Null
2+
3+
This destination is a "safe" version of the [E2E Test destination](https://docs.airbyte.io/integrations/destinations/e2e-test). It only allows the "silent" mode.
4+
5+
## Local development
6+
7+
#### Building via Gradle
8+
From the Airbyte repository root, run:
9+
```
10+
./gradlew :airbyte-integrations:connectors:destination-dev-null:build
11+
```
12+
13+
### Locally running the connector docker image
14+
15+
#### Build
16+
Build the connector image via Gradle:
17+
```
18+
./gradlew :airbyte-integrations:connectors:destination-dev-null:airbyteDocker
19+
```
20+
When building via Gradle, the docker image name and tag, respectively, are the values of the `io.airbyte.name` and `io.airbyte.version` `LABEL`s in
21+
the Dockerfile.
22+
23+
#### Run
24+
Then run any of the connector commands as follows:
25+
```
26+
docker run --rm airbyte/destination-dev-null:dev spec
27+
docker run --rm -v $(pwd)/secrets:/secrets airbyte/destination-dev-null:dev check --config /secrets/config.json
28+
docker run --rm -v $(pwd)/secrets:/secrets airbyte/destination-dev-null:dev discover --config /secrets/config.json
29+
docker run --rm -v $(pwd)/secrets:/secrets -v $(pwd)/integration_tests:/integration_tests airbyte/destination-dev-null:dev read --config /secrets/config.json --catalog /integration_tests/configured_catalog.json
30+
```
31+
32+
### Using gradle to run tests
33+
All commands should be run from airbyte project root.
34+
To run unit tests:
35+
```
36+
./gradlew :airbyte-integrations:connectors:destination-dev-null:unitTest
37+
```
38+
To run acceptance and custom integration tests:
39+
```
40+
./gradlew :airbyte-integrations:connectors:destination-dev-null:integrationTest
41+
```
42+
43+
## Dependency Management
44+
45+
### Publishing a new version of the connector
46+
You've checked out the repo, implemented a million dollar feature, and you're ready to share your changes with the world. Now what?
47+
1. Make sure your changes are passing unit and integration tests.
48+
1. Bump the connector version in `Dockerfile` -- just increment the value of the `LABEL io.airbyte.version` appropriately (we use [SemVer](https://semver.org/)).
49+
1. Create a Pull Request.
50+
1. Pat yourself on the back for being an awesome contributor.
51+
1. Someone from Airbyte will take a look at your PR and iterate with you to merge it into master.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
plugins {
2+
id 'application'
3+
id 'airbyte-docker'
4+
id 'airbyte-integration-test-java'
5+
}
6+
7+
application {
8+
mainClass = 'io.airbyte.integrations.destination.dev_null.DevNullDestination'
9+
}
10+
11+
dependencies {
12+
implementation project(':airbyte-config:models')
13+
implementation project(':airbyte-protocol:models')
14+
implementation project(':airbyte-integrations:bases:base-java')
15+
implementation project(':airbyte-integrations:connectors:destination-e2e-test')
16+
implementation files(project(':airbyte-integrations:bases:base-java').airbyteDocker.outputs)
17+
18+
integrationTestJavaImplementation project(':airbyte-integrations:bases:standard-destination-test')
19+
integrationTestJavaImplementation project(':airbyte-integrations:connectors:destination-dev-null')
20+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
/*
2+
* Copyright (c) 2021 Airbyte, Inc., all rights reserved.
3+
*/
4+
5+
package io.airbyte.integrations.destination.dev_null;
6+
7+
import com.fasterxml.jackson.databind.JsonNode;
8+
import com.fasterxml.jackson.databind.node.ArrayNode;
9+
import com.fasterxml.jackson.databind.node.ObjectNode;
10+
import io.airbyte.commons.json.Jsons;
11+
import io.airbyte.integrations.base.Destination;
12+
import io.airbyte.integrations.base.IntegrationRunner;
13+
import io.airbyte.integrations.base.spec_modification.SpecModifyingDestination;
14+
import io.airbyte.integrations.destination.e2e_test.TestingDestinations;
15+
import io.airbyte.protocol.models.ConnectorSpecification;
16+
import java.util.Iterator;
17+
import org.slf4j.Logger;
18+
import org.slf4j.LoggerFactory;
19+
20+
public class DevNullDestination extends SpecModifyingDestination implements Destination {
21+
22+
private static final Logger LOGGER = LoggerFactory.getLogger(DevNullDestination.class);
23+
private static final String DEV_NULL_DESTINATION_TITLE = "Dev Null Destination Spec";
24+
25+
public DevNullDestination() {
26+
super(new TestingDestinations());
27+
}
28+
29+
public static void main(final String[] args) throws Exception {
30+
LOGGER.info("Starting destination: {}", DevNullDestination.class);
31+
new IntegrationRunner(new DevNullDestination()).run(args);
32+
LOGGER.info("Completed destination: {}", DevNullDestination.class);
33+
}
34+
35+
/**
36+
* 1. Update the title. 2. Only keep the "silent" mode.
37+
*/
38+
@Override
39+
public ConnectorSpecification modifySpec(final ConnectorSpecification originalSpec) {
40+
final ConnectorSpecification spec = Jsons.clone(originalSpec);
41+
42+
((ObjectNode) spec.getConnectionSpecification()).put("title", DEV_NULL_DESTINATION_TITLE);
43+
44+
final ArrayNode types = (ArrayNode) spec.getConnectionSpecification().get("oneOf");
45+
final Iterator<JsonNode> typesIterator = types.elements();
46+
while (typesIterator.hasNext()) {
47+
final JsonNode typeNode = typesIterator.next();
48+
if (!typeNode.get("properties").get("type").get("const").asText().equalsIgnoreCase("silent")) {
49+
typesIterator.remove();
50+
}
51+
}
52+
return spec;
53+
}
54+
55+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
/*
2+
* Copyright (c) 2021 Airbyte, Inc., all rights reserved.
3+
*/
4+
5+
package io.airbyte.integrations.destination.dev_null;
6+
7+
import static org.junit.jupiter.api.Assertions.assertEquals;
8+
9+
import com.fasterxml.jackson.databind.JsonNode;
10+
import io.airbyte.commons.json.Jsons;
11+
import io.airbyte.integrations.standardtest.destination.DestinationAcceptanceTest;
12+
import io.airbyte.protocol.models.AirbyteMessage;
13+
import io.airbyte.protocol.models.AirbyteRecordMessage;
14+
import java.util.Collections;
15+
import java.util.List;
16+
17+
public class DevNullDestinationAcceptanceTest extends DestinationAcceptanceTest {
18+
19+
@Override
20+
protected String getImageName() {
21+
return "airbyte/destination-dev-null:dev";
22+
}
23+
24+
@Override
25+
protected JsonNode getConfig() {
26+
return Jsons.jsonNode(Collections.singletonMap("type", "SILENT"));
27+
}
28+
29+
@Override
30+
protected JsonNode getFailCheckConfig() {
31+
return Jsons.jsonNode(Collections.singletonMap("type", "invalid"));
32+
}
33+
34+
@Override
35+
protected List<JsonNode> retrieveRecords(final TestDestinationEnv testEnv,
36+
final String streamName,
37+
final String namespace,
38+
final JsonNode streamSchema) {
39+
return Collections.emptyList();
40+
}
41+
42+
@Override
43+
protected void setup(final TestDestinationEnv testEnv) {
44+
// do nothing
45+
}
46+
47+
@Override
48+
protected void tearDown(final TestDestinationEnv testEnv) {
49+
// do nothing
50+
}
51+
52+
@Override
53+
protected void assertSameMessages(final List<AirbyteMessage> expected,
54+
final List<AirbyteRecordMessage> actual,
55+
final boolean pruneAirbyteInternalFields) {
56+
assertEquals(0, actual.size());
57+
}
58+
59+
}

0 commit comments

Comments
 (0)