Skip to content

Commit

Permalink
Merge pull request #233 from RADAR-base/release-0.5.11
Browse files Browse the repository at this point in the history
Release 0.5.11
  • Loading branch information
nivemaham authored Jul 15, 2020
2 parents 07c1352 + 2a11128 commit 157002c
Show file tree
Hide file tree
Showing 13 changed files with 460 additions and 168 deletions.
5 changes: 4 additions & 1 deletion Dockerfile.kafkaInit
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ ENV KAFKA_NUM_PARTITIONS=3
ENV KAFKA_NUM_REPLICATION=3
ENV KAFKA_NUM_BROKERS=3
ENV KAFKA_ZOOKEEPER_CONNECT=zookeeper-1:2181
ENV CC_CONFIG_FILE_PATH=/etc/confluent/java-config.properties

RUN apt-get update && apt-get install -y --no-install-recommends \
curl \
Expand All @@ -47,8 +48,10 @@ COPY --from=builder /code/java-sdk/radar-schemas-tools/build/distributions/radar
COPY --from=builder /code ./original

VOLUME /schema/conf
VOLUME /etc/confluent/
# Copy bash file
COPY ./docker/topic_init.sh ./docker/init.sh ./docker/list_aggregated.sh ./docker/list_raw.sh /usr/bin/
COPY ./docker/specifications.exclude /etc/radar-schemas/specifications.exclude
COPY ./docker/topic_init.sh ./docker/init.sh ./docker/cc_topic_init.sh ./docker/list_aggregated.sh ./docker/list_raw.sh /usr/bin/
RUN chmod +x /usr/bin/*.sh

ENTRYPOINT ["init.sh"]
Expand Down
34 changes: 34 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,14 @@
- The `specifications` directory contains specifications of what data types are collected through which devices.
- Java SDKs for each of the components are provided in the `java-sdk` folder, see installation instructions there. They are automatically generated from the Avro schemas using the Avro 1.8.2 specification.

## Usage

This project can be used in RADAR-base by using the `radarbase/kafka-init` Docker image. The schemas and specifications can be extended by locally creating a directory structure that includes a `commons` and `specifications` directory and mounting it to the image, to the `/schema/conf/commons` and `/schema/conf/specifications` directories, respectively. Existing specifications can be excluded from your deployment by mounting a file at `/etc/radar-schemas/specifications.exclude`, with on each line a file pattern that can be excluded. The pattern should start from the `specifications` directory as parent directory. Example file contents:
```
active/*
passive/biovotion*
```

## Contributing

The Avro schemas should follow the [Google JSON style guide](https://google.github.io/styleguide/jsoncstyleguide.xml).
Expand Down Expand Up @@ -64,3 +72,29 @@ docker-compose run --rm tools radar-schemas-tools schema-topic --backup -f schem
# ensure the validity of the _schemas topic
docker-compose run --rm tools radar-schemas-tools schema-topic --ensure -f schema.json -b 1 -r 1 zookeeper-1:2181
```

### Using radar-schema-tools with Confluent Cloud

1. Create topics on Confluent Cloud

1.1. Create a `java-config.properties` file. A Confluent Cloud config for Java application based on this [template](https://github.com/confluentinc/configuration-templates/blob/master/clients/cloud/java-sr.config).

```properties
# Kafka
bootstrap.servers={{ BROKER_ENDPOINT }}
security.protocol=SASL_SSL
sasl.jaas.config=org.apache.kafka.common.security.plain.PlainLoginModule required username="{{ CLUSTER_API_KEY }}" password="{{ CLUSTER_API_SECRET }}";
ssl.endpoint.identification.algorithm=https
sasl.mechanism=PLAIN
```
1.2. Run `cc-topic-create` command

```
docker run --rm -v "$PWD/java-config.properties:/schema/conf/java.properties" radarbase/kafka-init radar-schemas-tools cc-topic-create -c java-config.properties
```

2. Register schemas on Confluent Cloud schema registry

```
docker run --rm radarbase/kafka-init radar-schemas-tools register SR_ENDPOINT -u SR_API_KEY -p SR_API_SECRET
```
29 changes: 29 additions & 0 deletions docker/cc_topic_init.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
#!/bin/bash

# Create topics
echo "Creating RADAR-base topics on Confluent Cloud..."

if ! radar-schemas-tools cc-topic-create -c $CC_CONFIG_FILE_PATH -p $KAFKA_NUM_PARTITIONS -r $KAFKA_NUM_REPLICATION merged; then
echo "FAILED TO CREATE TOPICS ... Retrying again"
if ! radar-schemas-tools cc-topic-create -c $CC_CONFIG_FILE_PATH -p $KAFKA_NUM_PARTITIONS -r $KAFKA_NUM_REPLICATION merged; then
echo "FAILED TO CREATE TOPICS"
exit 1
else
echo "Created topics at second attempt"
fi
else
echo "Topics created."
fi

echo "Registering RADAR-base schemas..."

if ! radar-schemas-tools register --force -u $CC_API_KEY -p $CC_API_SECRET "${KAFKA_SCHEMA_REGISTRY}" merged; then
echo "FAILED TO REGISTER SCHEMAS"
exit 1
fi

echo "Schemas registered."

echo "*******************************************"
echo "** RADAR-base topics and schemas ready **"
echo "*******************************************"
13 changes: 11 additions & 2 deletions docker/init.sh
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,13 @@ AVRO_TOOLS=/usr/share/java/avro-tools.jar
rsync -a /schema/original/commons /schema/original/specifications /schema/merged
rsync -a /schema/conf/ /schema/merged

EXCLUDE_FILE=${EXCLUDE_FILE:-/etc/radar-schemas/specifications.exclude}
if [ -f "$EXCLUDE_FILE" ]; then
while read -r exclude; do
rm /schema/merged/specifications/$exclude
done < "$EXCLUDE_FILE"
fi

# Compiling updated schemas
echo "Compiling schemas..." >&2

Expand All @@ -25,8 +32,10 @@ printf "===> Independent schemas:\n${INDEPENDENT}\n"
printf "===> Dependent schemas:\n${DEPENDENT}\n"

java -jar "${AVRO_TOOLS}" compile -string schema ${INDEPENDENT} ${DEPENDENT} java/src 2>/dev/null
find java/src -name "*.java" -print0 | xargs -0 javac -cp /usr/lib/*:java/classes -d java/classes -sourcepath java/src
find java/src -name "*.java" -print0 | xargs -0 javac -cp /usr/lib/*:java/classes -d java/classes -sourcepath java/src
# Update the radar schemas so the tools find the new classes in classpath
jar uf /usr/lib/radar-schemas-commons-*.jar -C java/classes .

exec "$@"
if [ $# != 0 ]; then
exec "$@"
fi
Empty file added docker/specifications.exclude
Empty file.
2 changes: 1 addition & 1 deletion java-sdk/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ subprojects {
apply plugin: 'idea'

// Configuration
version = '0.5.10.1'
version = '0.5.11'
group = 'org.radarcns'
ext.githubRepoName = 'RADAR-base/RADAR-Schemas'

Expand Down
2 changes: 1 addition & 1 deletion java-sdk/radar-schemas-tools/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ ext {
confluentVersion = '5.5.0'
kafkaVersion = '2.5.0'
okHttpVersion = '4.7.2'
radarCommonsVersion = '0.12.3'
radarCommonsVersion = '0.13.0'
slf4jVersion = '1.7.30'
javaxValidationVersion = '2.0.1.Final'
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import net.sourceforge.argparse4j.inf.ArgumentParserException;
import net.sourceforge.argparse4j.inf.Namespace;
import net.sourceforge.argparse4j.inf.Subparsers;
import org.radarcns.schema.registration.ConfluentCloudTopics;
import org.radarcns.schema.registration.KafkaTopics;
import org.radarcns.schema.registration.SchemaTopicManager;
import org.radarcns.schema.registration.SchemaRegistry;
Expand Down Expand Up @@ -64,6 +65,7 @@ public class CommandLineApp {
public CommandLineApp(Path root) throws IOException {
this.root = root;
this.catalogue = SourceCatalogue.load(root);
logger.info("radar-schema-tools is initialized with root directory {}", this.root);
}

/**
Expand Down Expand Up @@ -134,6 +136,7 @@ public Stream<String> getTopicsVerbose(boolean prettyPrint, String source) {
public static void main(String... args) {
SortedMap<String, SubCommand> subCommands = commandsToMap(
KafkaTopics.command(),
ConfluentCloudTopics.command(),
SchemaRegistry.command(),
listCommand(),
SchemaValidator.command(),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
package org.radarcns.schema.registration;

import static org.radarcns.schema.CommandLineApp.matchTopic;

import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.validation.constraints.NotNull;

import org.apache.kafka.clients.admin.AdminClient;
import org.apache.kafka.clients.admin.ListTopicsOptions;
import org.apache.kafka.clients.admin.NewTopic;
import org.radarcns.schema.specification.SourceCatalogue;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public abstract class AbstractTopicRegistrar implements TopicRegistrar {
static final int MAX_SLEEP = 32;
private static final Logger logger = LoggerFactory.getLogger(AbstractTopicRegistrar.class);
private Set<String> topics;

@Override
public int createTopics(@NotNull SourceCatalogue catalogue, int partitions, short replication,
String topic, String match) {
Pattern pattern = matchTopic(topic, match);

if (pattern == null) {
return createTopics(catalogue, partitions, replication) ? 0 : 1;
} else {
List<String> topicNames =
catalogue.getTopicNames().filter(s -> pattern.matcher(s).find())
.collect(Collectors.toList());

if (topicNames.isEmpty()) {
logger.error("Topic {} does not match a known topic."
+ " Find the list of acceptable topics"
+ " with the `radar-schemas-tools list` command. Aborting.", pattern);
return 1;
}
return createTopics(topicNames.stream(), partitions, replication) ? 0 : 1;
}
}


/**
* Create all topics in a catalogue.
*
* @param catalogue source catalogue to extract topic names from
* @param partitions number of partitions per topic
* @param replication number of replicas for a topic
* @return whether the whole catalogue was registered
*/
private boolean createTopics(@NotNull SourceCatalogue catalogue, int partitions,
short replication) {
ensureInitialized();
return createTopics(catalogue.getTopicNames(), partitions, replication);
}


@Override
public boolean createTopics(Stream<String> topicsToCreate, int partitions, short replication) {
ensureInitialized();
try {
refreshTopics();
logger.info("Creating topics. Topics marked with [*] already exist.");

List<NewTopic> newTopics = topicsToCreate.sorted().distinct().filter(t -> {
if (this.topics != null && this.topics.contains(t)) {
logger.info("[*] {}", t);
return false;
} else {
logger.info("[ ] {}", t);
return true;
}
}).map(t -> new NewTopic(t, partitions, replication)).collect(Collectors.toList());

if (!newTopics.isEmpty()) {
getKafkaClient().createTopics(newTopics).all().get();
logger.info("Created {} topics. Requesting to refresh topics", newTopics.size());
refreshTopics();
} else {
logger.info("All of the topics are already created.");
}
return true;
} catch (Exception ex) {
logger.error("Failed to create topics {}", ex.toString());
return false;
}
}

@Override
public boolean refreshTopics() throws InterruptedException {
ensureInitialized();
logger.info("Waiting for topics to become available.");
int sleep = 10;
int numTries = 10;

topics = null;
ListTopicsOptions opts = new ListTopicsOptions().listInternal(true);
for (int tries = 0; tries < numTries; tries++) {
try {
topics = getKafkaClient().listTopics(opts).names().get(sleep, TimeUnit.SECONDS);
} catch (ExecutionException e) {
logger.error("Failed to list topics from brokers: {}."
+ " Trying again after {} seconds.", e.toString(), sleep);
Thread.sleep(sleep * 1000L);
sleep = Math.min(MAX_SLEEP, sleep * 2);
continue;
} catch (TimeoutException e) {
// do nothing
}
if (topics != null && !topics.isEmpty()) {
break;
}
if (tries < numTries - 1) {
logger.warn("Topics not listed yet after {} seconds", sleep);
} else {
logger.error("Topics have not become available. Failed to wait on Kafka.");
}
sleep = Math.min(MAX_SLEEP, sleep * 2);
}

if (topics == null || topics.isEmpty()) {
return false;
} else {
Thread.sleep(5000L);
return true;
}
}

@Override
public Set<String> getTopics() {
ensureInitialized();
return Collections.unmodifiableSet(topics);
}


@Override
public void close() {
if (getKafkaClient() != null) {
getKafkaClient().close();
}
}

/**
* Returns an instance of {@code AdminClient} for use.
*
* @return instance of AdminClient.
*/
abstract AdminClient getKafkaClient();

}
Loading

0 comments on commit 157002c

Please sign in to comment.