Skip to content

Commit

Permalink
🎉 Testing destination: multiple logging modes (#8824)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
tuliren authored Dec 19, 2021
1 parent ec305a5 commit 0de30f5
Show file tree
Hide file tree
Showing 32 changed files with 1,028 additions and 69 deletions.
3 changes: 3 additions & 0 deletions airbyte-config/init/src/main/resources/icons/airbyte.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,12 @@
dockerImageTag: 0.1.0
documentationUrl: https://docs.airbyte.io/integrations/destinations/dynamodb
icon: dynamodb.svg
- name: E2E Testing
destinationDefinitionId: 2eb65e87-983a-4fd7-b3e3-9d9dc6eb8537
dockerRepository: airbyte/destination-e2e-test
dockerImageTag: 0.2.0
documentationUrl: https://docs.airbyte.io/integrations/destinations/e2e-test
icon: airbyte.svg
- destinationDefinitionId: 68f351a7-2745-4bef-ad7f-996b8e51bb8c
name: ElasticSearch
dockerRepository: airbyte/destination-elasticsearch
Expand Down
159 changes: 159 additions & 0 deletions airbyte-config/init/src/main/resources/seed/destination_specs.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -769,6 +769,165 @@
supported_destination_sync_modes:
- "overwrite"
- "append"
- dockerImage: "airbyte/destination-e2e-test:0.2.0"
spec:
documentationUrl: "https://example.com"
connectionSpecification:
$schema: "http://json-schema.org/draft-07/schema#"
title: "E2E Test Destination Spec"
type: "object"
oneOf:
- title: "Logging"
required:
- "type"
- "logging_config"
properties:
type:
type: "string"
const: "LOGGING"
default: "LOGGING"
logging_config:
title: "Logging Configuration"
type: "object"
description: "Configurate how the messages are logged."
oneOf:
- title: "First N Entries"
description: "Log first N entries per stream."
type: "object"
required:
- "logging_type"
- "max_entry_count"
properties:
logging_type:
type: "string"
enum:
- "FirstN"
default: "FirstN"
max_entry_count:
title: "N"
description: "Number of entries to log. This destination is for\
\ testing only. So it won't make sense to log infinitely. The\
\ maximum is 1,000 entries."
type: "number"
default: 100
examples:
- 100
minimum: 1
maximum: 1000
- title: "Every N-th Entry"
description: "For each stream, log every N-th entry with a maximum cap."
type: "object"
required:
- "logging_type"
- "nth_entry_to_log"
- "max_entry_count"
properties:
logging_type:
type: "string"
enum:
- "EveryNth"
default: "EveryNth"
nth_entry_to_log:
title: "N"
description: "The N-th entry to log for each stream. N starts from\
\ 1. For example, when N = 1, every entry is logged; when N =\
\ 2, every other entry is logged; when N = 3, one out of three\
\ entries is logged."
type: "number"
example:
- 3
minimum: 1
maximum: 1000
max_entry_count:
title: "Max Log Entries"
description: "Max number of entries to log. This destination is\
\ for testing only. So it won't make sense to log infinitely.\
\ The maximum is 1,000 entries."
type: "number"
default: 100
examples:
- 100
minimum: 1
maximum: 1000
- title: "Random Sampling"
description: "For each stream, randomly log a percentage of the entries\
\ with a maximum cap."
type: "object"
required:
- "logging_type"
- "sampling_ratio"
- "max_entry_count"
properties:
logging_type:
type: "string"
enum:
- "RandomSampling"
default: "RandomSampling"
sampling_ratio:
title: "Sampling Ratio"
description: "A positive floating number smaller than 1."
type: "number"
default: 0.001
examples:
- 0.001
minimum: 0
maximum: 1
seed:
title: "Random Number Generator Seed"
description: "When the seed is unspecified, the current time millis\
\ will be used as the seed."
type: "number"
examples:
- 1900
max_entry_count:
title: "Max Log Entries"
description: "Max number of entries to log. This destination is\
\ for testing only. So it won't make sense to log infinitely.\
\ The maximum is 1,000 entries."
type: "number"
default: 100
examples:
- 100
minimum: 1
maximum: 1000
- title: "Silent"
required:
- "type"
properties:
type:
type: "string"
const: "SILENT"
default: "SILENT"
- title: "Throttled"
required:
- "type"
- "millis_per_record"
properties:
type:
type: "string"
const: "THROTTLED"
default: "THROTTLED"
millis_per_record:
description: "Number of milli-second to pause in between records."
type: "integer"
- title: "Failing"
required:
- "type"
- "num_messages"
properties:
type:
type: "string"
const: "FAILING"
default: "FAILING"
num_messages:
description: "Number of messages after which to fail."
type: "integer"
supportsIncremental: true
supportsNormalization: false
supportsDBT: false
supported_destination_sync_modes:
- "overwrite"
- "append"
- dockerImage: "airbyte/destination-elasticsearch:0.1.0"
spec:
documentationUrl: "https://docs.airbyte.io/integrations/destinations/elasticsearch"
Expand Down
2 changes: 2 additions & 0 deletions airbyte-integrations/builds.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,9 @@
| 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) |
| 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) |
| Databricks | (Temporarily Not Available) |
| 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) |
| Elasticsearch | (Temporarily Not Available) |
| 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) |
| 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) |
| 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) |
| 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) |
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
*
!Dockerfile
!build
10 changes: 10 additions & 0 deletions airbyte-integrations/connectors/destination-dev-null/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
FROM airbyte/integration-base-java:dev

WORKDIR /airbyte
ENV APPLICATION destination-dev-null

COPY build/distributions/${APPLICATION}*.tar ${APPLICATION}.tar
RUN tar xf ${APPLICATION}.tar --strip-components=1

LABEL io.airbyte.version=0.1.0
LABEL io.airbyte.name=airbyte/destination-dev-null
51 changes: 51 additions & 0 deletions airbyte-integrations/connectors/destination-dev-null/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# Destination Dev Null

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.

## Local development

#### Building via Gradle
From the Airbyte repository root, run:
```
./gradlew :airbyte-integrations:connectors:destination-dev-null:build
```

### Locally running the connector docker image

#### Build
Build the connector image via Gradle:
```
./gradlew :airbyte-integrations:connectors:destination-dev-null:airbyteDocker
```
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
the Dockerfile.

#### Run
Then run any of the connector commands as follows:
```
docker run --rm airbyte/destination-dev-null:dev spec
docker run --rm -v $(pwd)/secrets:/secrets airbyte/destination-dev-null:dev check --config /secrets/config.json
docker run --rm -v $(pwd)/secrets:/secrets airbyte/destination-dev-null:dev discover --config /secrets/config.json
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
```

### Using gradle to run tests
All commands should be run from airbyte project root.
To run unit tests:
```
./gradlew :airbyte-integrations:connectors:destination-dev-null:unitTest
```
To run acceptance and custom integration tests:
```
./gradlew :airbyte-integrations:connectors:destination-dev-null:integrationTest
```

## Dependency Management

### Publishing a new version of the connector
You've checked out the repo, implemented a million dollar feature, and you're ready to share your changes with the world. Now what?
1. Make sure your changes are passing unit and integration tests.
1. Bump the connector version in `Dockerfile` -- just increment the value of the `LABEL io.airbyte.version` appropriately (we use [SemVer](https://semver.org/)).
1. Create a Pull Request.
1. Pat yourself on the back for being an awesome contributor.
1. Someone from Airbyte will take a look at your PR and iterate with you to merge it into master.
20 changes: 20 additions & 0 deletions airbyte-integrations/connectors/destination-dev-null/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
plugins {
id 'application'
id 'airbyte-docker'
id 'airbyte-integration-test-java'
}

application {
mainClass = 'io.airbyte.integrations.destination.dev_null.DevNullDestination'
}

dependencies {
implementation project(':airbyte-config:models')
implementation project(':airbyte-protocol:models')
implementation project(':airbyte-integrations:bases:base-java')
implementation project(':airbyte-integrations:connectors:destination-e2e-test')
implementation files(project(':airbyte-integrations:bases:base-java').airbyteDocker.outputs)

integrationTestJavaImplementation project(':airbyte-integrations:bases:standard-destination-test')
integrationTestJavaImplementation project(':airbyte-integrations:connectors:destination-dev-null')
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/*
* Copyright (c) 2021 Airbyte, Inc., all rights reserved.
*/

package io.airbyte.integrations.destination.dev_null;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import io.airbyte.commons.json.Jsons;
import io.airbyte.integrations.base.Destination;
import io.airbyte.integrations.base.IntegrationRunner;
import io.airbyte.integrations.base.spec_modification.SpecModifyingDestination;
import io.airbyte.integrations.destination.e2e_test.TestingDestinations;
import io.airbyte.protocol.models.ConnectorSpecification;
import java.util.Iterator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class DevNullDestination extends SpecModifyingDestination implements Destination {

private static final Logger LOGGER = LoggerFactory.getLogger(DevNullDestination.class);
private static final String DEV_NULL_DESTINATION_TITLE = "Dev Null Destination Spec";

public DevNullDestination() {
super(new TestingDestinations());
}

public static void main(final String[] args) throws Exception {
LOGGER.info("Starting destination: {}", DevNullDestination.class);
new IntegrationRunner(new DevNullDestination()).run(args);
LOGGER.info("Completed destination: {}", DevNullDestination.class);
}

/**
* 1. Update the title. 2. Only keep the "silent" mode.
*/
@Override
public ConnectorSpecification modifySpec(final ConnectorSpecification originalSpec) {
final ConnectorSpecification spec = Jsons.clone(originalSpec);

((ObjectNode) spec.getConnectionSpecification()).put("title", DEV_NULL_DESTINATION_TITLE);

final ArrayNode types = (ArrayNode) spec.getConnectionSpecification().get("oneOf");
final Iterator<JsonNode> typesIterator = types.elements();
while (typesIterator.hasNext()) {
final JsonNode typeNode = typesIterator.next();
if (!typeNode.get("properties").get("type").get("const").asText().equalsIgnoreCase("silent")) {
typesIterator.remove();
}
}
return spec;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/*
* Copyright (c) 2021 Airbyte, Inc., all rights reserved.
*/

package io.airbyte.integrations.destination.dev_null;

import static org.junit.jupiter.api.Assertions.assertEquals;

import com.fasterxml.jackson.databind.JsonNode;
import io.airbyte.commons.json.Jsons;
import io.airbyte.integrations.standardtest.destination.DestinationAcceptanceTest;
import io.airbyte.protocol.models.AirbyteMessage;
import io.airbyte.protocol.models.AirbyteRecordMessage;
import java.util.Collections;
import java.util.List;

public class DevNullDestinationAcceptanceTest extends DestinationAcceptanceTest {

@Override
protected String getImageName() {
return "airbyte/destination-dev-null:dev";
}

@Override
protected JsonNode getConfig() {
return Jsons.jsonNode(Collections.singletonMap("type", "SILENT"));
}

@Override
protected JsonNode getFailCheckConfig() {
return Jsons.jsonNode(Collections.singletonMap("type", "invalid"));
}

@Override
protected List<JsonNode> retrieveRecords(final TestDestinationEnv testEnv,
final String streamName,
final String namespace,
final JsonNode streamSchema) {
return Collections.emptyList();
}

@Override
protected void setup(final TestDestinationEnv testEnv) {
// do nothing
}

@Override
protected void tearDown(final TestDestinationEnv testEnv) {
// do nothing
}

@Override
protected void assertSameMessages(final List<AirbyteMessage> expected,
final List<AirbyteRecordMessage> actual,
final boolean pruneAirbyteInternalFields) {
assertEquals(0, actual.size());
}

}
Loading

0 comments on commit 0de30f5

Please sign in to comment.