You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ratis.apache.org by sz...@apache.org on 2018/09/07 20:21:04 UTC
incubator-ratis git commit: RATIS-272. LogService: Design ideal API.
Contributed by Josh Elser
Repository: incubator-ratis
Updated Branches:
refs/heads/master acd507e6e -> ef48512da
RATIS-272. LogService: Design ideal API. Contributed by Josh Elser
Project: http://git-wip-us.apache.org/repos/asf/incubator-ratis/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-ratis/commit/ef48512d
Tree: http://git-wip-us.apache.org/repos/asf/incubator-ratis/tree/ef48512d
Diff: http://git-wip-us.apache.org/repos/asf/incubator-ratis/diff/ef48512d
Branch: refs/heads/master
Commit: ef48512da368f7d52ffddd3a8f02890bbdff2b39
Parents: acd507e
Author: Tsz Wo Nicholas Sze <sz...@apache.org>
Authored: Fri Sep 7 13:19:20 2018 -0700
Committer: Tsz Wo Nicholas Sze <sz...@apache.org>
Committed: Fri Sep 7 13:19:20 2018 -0700
----------------------------------------------------------------------
pom.xml | 7 +
ratis-logservice/pom.xml | 116 ++++++++++++++++
.../ratis/logservice/LogServiceFactory.java | 44 ++++++
.../apache/ratis/logservice/api/LogName.java | 71 ++++++++++
.../apache/ratis/logservice/api/LogReader.java | 88 ++++++++++++
.../apache/ratis/logservice/api/LogService.java | 139 +++++++++++++++++++
.../apache/ratis/logservice/api/LogStream.java | 79 +++++++++++
.../logservice/api/LogStreamConfiguration.java | 64 +++++++++
.../apache/ratis/logservice/api/LogWriter.java | 64 +++++++++
.../ratis/logservice/api/RecordListener.java | 33 +++++
.../ratis/logservice/dummy/DummyLogReader.java | 76 ++++++++++
.../ratis/logservice/dummy/DummyLogService.java | 92 ++++++++++++
.../ratis/logservice/dummy/DummyLogStream.java | 83 +++++++++++
.../ratis/logservice/dummy/DummyLogWriter.java | 51 +++++++
.../ratis/logservice/api/TestApiExample.java | 69 +++++++++
.../src/test/resources/log4j.properties | 18 +++
16 files changed, 1094 insertions(+)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/incubator-ratis/blob/ef48512d/pom.xml
----------------------------------------------------------------------
diff --git a/pom.xml b/pom.xml
index c3fb15a..133b0ea 100644
--- a/pom.xml
+++ b/pom.xml
@@ -52,6 +52,7 @@
<module>ratis-assembly</module>
<module>ratis-examples</module>
<module>ratis-replicated-map</module>
+ <module>ratis-logservice</module>
</modules>
<pluginRepositories>
@@ -302,6 +303,12 @@
<scope>test</scope>
</dependency>
+ <dependency>
+ <groupId>org.apache.ratis</groupId>
+ <artifactId>ratis-logservice</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+
<!-- External dependencies -->
<dependency>
<groupId>org.slf4j</groupId>
http://git-wip-us.apache.org/repos/asf/incubator-ratis/blob/ef48512d/ratis-logservice/pom.xml
----------------------------------------------------------------------
diff --git a/ratis-logservice/pom.xml b/ratis-logservice/pom.xml
new file mode 100644
index 0000000..bfe002e
--- /dev/null
+++ b/ratis-logservice/pom.xml
@@ -0,0 +1,116 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License. See accompanying LICENSE file.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <artifactId>ratis</artifactId>
+ <groupId>org.apache.ratis</groupId>
+ <version>0.3.0-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>ratis-logservice</artifactId>
+ <name>Apache Ratis LogService</name>
+
+ <dependencies>
+ <!-- Ratis dependencies -->
+ <dependency>
+ <artifactId>ratis-proto-shaded</artifactId>
+ <groupId>org.apache.ratis</groupId>
+ </dependency>
+ <dependency>
+ <artifactId>ratis-common</artifactId>
+ <groupId>org.apache.ratis</groupId>
+ </dependency>
+ <dependency>
+ <artifactId>ratis-client</artifactId>
+ <groupId>org.apache.ratis</groupId>
+ </dependency>
+ <dependency>
+ <artifactId>ratis-server</artifactId>
+ <groupId>org.apache.ratis</groupId>
+ </dependency>
+ <dependency>
+ <artifactId>ratis-grpc</artifactId>
+ <groupId>org.apache.ratis</groupId>
+ </dependency>
+ <dependency>
+ <artifactId>ratis-netty</artifactId>
+ <groupId>org.apache.ratis</groupId>
+ </dependency>
+ <!-- Third-party dependencies -->
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-api</artifactId>
+ </dependency>
+ <!-- Test Ratis dependencies -->
+ <dependency>
+ <artifactId>ratis-netty</artifactId>
+ <groupId>org.apache.ratis</groupId>
+ <scope>test</scope>
+ <type>test-jar</type>
+ </dependency>
+ <dependency>
+ <artifactId>ratis-grpc</artifactId>
+ <groupId>org.apache.ratis</groupId>
+ <scope>test</scope>
+ <type>test-jar</type>
+ </dependency>
+ <dependency>
+ <artifactId>ratis-server</artifactId>
+ <groupId>org.apache.ratis</groupId>
+ <scope>test</scope>
+ <type>test-jar</type>
+ </dependency>
+ <dependency>
+ <artifactId>ratis-common</artifactId>
+ <groupId>org.apache.ratis</groupId>
+ <scope>test</scope>
+ <type>test-jar</type>
+ </dependency>
+ <dependency>
+ <artifactId>ratis-client</artifactId>
+ <groupId>org.apache.ratis</groupId>
+ <scope>test</scope>
+ <type>test-jar</type>
+ </dependency>
+ <dependency>
+ <artifactId>ratis-hadoop</artifactId>
+ <groupId>org.apache.ratis</groupId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <artifactId>ratis-hadoop</artifactId>
+ <groupId>org.apache.ratis</groupId>
+ <scope>test</scope>
+ <type>test-jar</type>
+ </dependency>
+ <dependency>
+ <artifactId>ratis-hadoop-shaded</artifactId>
+ <groupId>org.apache.ratis</groupId>
+ <scope>test</scope>
+ </dependency>
+ <!-- Test third-party dependencies -->
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.mockito</groupId>
+ <artifactId>mockito-all</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+</project>
http://git-wip-us.apache.org/repos/asf/incubator-ratis/blob/ef48512d/ratis-logservice/src/main/java/org/apache/ratis/logservice/LogServiceFactory.java
----------------------------------------------------------------------
diff --git a/ratis-logservice/src/main/java/org/apache/ratis/logservice/LogServiceFactory.java b/ratis-logservice/src/main/java/org/apache/ratis/logservice/LogServiceFactory.java
new file mode 100644
index 0000000..4ef801e
--- /dev/null
+++ b/ratis-logservice/src/main/java/org/apache/ratis/logservice/LogServiceFactory.java
@@ -0,0 +1,44 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.ratis.logservice;
+
+import org.apache.ratis.client.RaftClient;
+import org.apache.ratis.logservice.api.LogService;
+
+public class LogServiceFactory {
+ private static final LogServiceFactory INSTANCE = new LogServiceFactory();
+
+ private LogServiceFactory() {}
+
+ /**
+ * Creates an implementation of {@link LogService} using the given {@link RaftClient}.
+ *
+ * @param raftClient The client to a Raft quorum.
+ */
+ public LogService createLogService(RaftClient raftClient) {
+ //TODO return new LogServiceImpl();
+ return null;
+ }
+
+ /**
+ * Returns an instance of the factory to create {@link LogService} instances.
+ */
+ public static LogServiceFactory getInstance() {
+ return INSTANCE;
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-ratis/blob/ef48512d/ratis-logservice/src/main/java/org/apache/ratis/logservice/api/LogName.java
----------------------------------------------------------------------
diff --git a/ratis-logservice/src/main/java/org/apache/ratis/logservice/api/LogName.java b/ratis-logservice/src/main/java/org/apache/ratis/logservice/api/LogName.java
new file mode 100644
index 0000000..3227845
--- /dev/null
+++ b/ratis-logservice/src/main/java/org/apache/ratis/logservice/api/LogName.java
@@ -0,0 +1,71 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.ratis.logservice.api;
+
+import static java.util.Objects.requireNonNull;
+
+import java.util.Objects;
+
+/**
+ * Identifier to uniquely identify a {@link LogStream}.
+ */
+public class LogName {
+ // It's pretty likely that what uniquely defines a LogStream
+ // to change over time. We should account for this by making an
+ // API which can naturally evolve.
+ private final String name;
+
+ private LogName(String name) {
+ this.name = requireNonNull(name);
+ }
+
+ /**
+ * Returns the unique name which identifies a LogStream.
+ *
+ * Impl Note: This class uses a String to uniquely identify this LogName (and the corresponding LogStream)
+ * from others. This is purely an implementation detail; the intent is that any data should be capable
+ * of identifying one LogStream/LogName from another. Users need only know how to construct a {@link LogName}
+ * and then use that in their application.
+ */
+ String getName() {
+ return name;
+ }
+
+ @Override public boolean equals(Object o) {
+ if (!(o instanceof LogName)) {
+ return false;
+ }
+ return Objects.equals(name, ((LogName) o).getName());
+ }
+
+ @Override public int hashCode() {
+ return name.hashCode();
+ }
+
+ @Override public String toString() {
+ return "LogName['" + name + "']";
+ }
+
+ /**
+ * Creates a {@link LogName} given the provided string.
+ */
+ public static LogName of(String name) {
+ // TODO Limit allowed characters in the name?
+ return new LogName(name);
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-ratis/blob/ef48512d/ratis-logservice/src/main/java/org/apache/ratis/logservice/api/LogReader.java
----------------------------------------------------------------------
diff --git a/ratis-logservice/src/main/java/org/apache/ratis/logservice/api/LogReader.java b/ratis-logservice/src/main/java/org/apache/ratis/logservice/api/LogReader.java
new file mode 100644
index 0000000..6db3b6e
--- /dev/null
+++ b/ratis-logservice/src/main/java/org/apache/ratis/logservice/api/LogReader.java
@@ -0,0 +1,88 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.ratis.logservice.api;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.List;
+
+/**
+ * Synchronous client interface to read from a LogStream.
+ */
+public interface LogReader extends AutoCloseable {
+
+ /**
+ * Seeks to the position before the record at the provided {@code recordId} in the LogStream.
+ *
+ * @param offset A non-negative, offset in the LogStream
+ * @return A future for when the operation is completed.
+ */
+ void seek(long recordId) throws IOException;
+
+ /**
+ * Reads the next record from the LogStream at the current position and advances the current position
+ * to after the record which was just returned.
+ *
+ * @return The data for the next record.
+ */
+ ByteBuffer readNext() throws IOException;
+
+ /**
+ * Reads the next record from the LogStream at the current position into the provided {@link buffer} and
+ * advances the current position to the point after the record just read.
+ *
+ * The provided buffer must be capable of holding one complete record from the Log. If the provided buffer is
+ * too small, an exception will be thrown.
+ *
+ * @param buffer A buffer to read the record into
+ */
+ void readNext(ByteBuffer buffer) throws IOException;
+
+ /**
+ * Reads the next {@code numRecords} records from the LogStream, starting at the current position. This method
+ * may return fewer than requested records if the LogStream does not have sufficient records to return.
+ *
+ * @param numRecords The number of records to return
+ * @return The records, no more than the requested {@code numRecords} amount.
+ */
+ List<ByteBuffer> readBulk(int numRecords) throws IOException;
+
+ /**
+ * Fills the provided {@code List<ByteBuffer>} with records from the LogStream, starting at the current position.
+ * This method will attempt to fill all of the {@code ByteBuffer}'s that were provided, as long as there are
+ * records in the {@code LogStream} to support this. This method will return the number of buffers that were
+ * filled.
+ *
+ * Each provided buffer must be capable of holding one complete record from the Log. If the provided buffer is
+ * too small, an exception will be thrown.
+ *
+ * @param buffers A non-empty list of non-null ByteBuffers.
+ * @return The number of records returns, equivalent to the number of filled buffers.
+ */
+ int readBulk(List<ByteBuffer> buffers) throws IOException;
+
+ /**
+ * Returns the current position of this Reader. The position is a {@code recordId}.
+ */
+ long getPosition();
+
+ /**
+ * Overrides {@link #close()} in {@link AutoCloseable} to throw an IOException.
+ */
+ void close() throws IOException;
+}
http://git-wip-us.apache.org/repos/asf/incubator-ratis/blob/ef48512d/ratis-logservice/src/main/java/org/apache/ratis/logservice/api/LogService.java
----------------------------------------------------------------------
diff --git a/ratis-logservice/src/main/java/org/apache/ratis/logservice/api/LogService.java b/ratis-logservice/src/main/java/org/apache/ratis/logservice/api/LogService.java
new file mode 100644
index 0000000..860cb33
--- /dev/null
+++ b/ratis-logservice/src/main/java/org/apache/ratis/logservice/api/LogService.java
@@ -0,0 +1,139 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.ratis.logservice.api;
+
+import java.io.IOException;
+import java.util.Iterator;
+
+import org.apache.ratis.logservice.api.LogStream.State;
+
+/**
+ * Entry point for interacting with the Ratis LogService.
+ */
+public interface LogService extends AutoCloseable {
+ /*
+ * How to create a LogStream
+ */
+
+ /**
+ * Creates a new {@link LogStream} identified by the given name with default
+ * configuration. Throws an exception if a {@link LogStream} with the given
+ * name already exists.
+ *
+ * @param name Unique name for this LogStream.
+ */
+ LogStream createLog(LogName name);
+
+ /**
+ * Creates a new {@link LogStream} identified by the given name. Throws
+ * an exception if a {@link LogStream} with the given name already exists.
+ *
+ * @param name Unique name for this LogStream.
+ * @param config Configuration object for this LogStream
+ */
+ LogStream createLog(LogName name, LogStreamConfiguration config);
+
+ /*
+ * How to get LogStreams that already exist
+ */
+ /**
+ * Fetches the {@link LogStream} identified by the given name.
+ *
+ * @param name The name of the LogStream
+ */
+ LogStream getLog(LogName name);
+
+ /**
+ * Lists all {@link LogStream} instances known by this LogService.
+ */
+ Iterator<LogStream> listLogs();
+
+ /*
+ * How to close, archive, and delete LogStreams
+ */
+
+ /**
+ * Moves the {@link LogStream} identified by the {@code name} from {@link State.OPEN} to {@link State.CLOSED}.
+ * If the log is not {@link State#OPEN}, this method returns an error.
+ *
+ * @param name The name of the log to close
+ */
+ // TODO this name sucks, confusion WRT the Java Closeable interface.
+ void closeLog(LogName name);
+
+ /**
+ * Returns the current {@link State} of the log identified by {@code name}.
+ *
+ * @param name The name of a log
+ */
+ State getState(LogName name);
+
+ /**
+ * Archives the given log out of the state machine and into a configurable long-term storage. A log must be
+ * in {@link State#CLOSED} to archive it.
+ *
+ * @param name The name of the log to archive.
+ */
+ void archiveLog(LogName name);
+
+ /**
+ * Deletes the {@link LogStream}.
+ * @param name The name of the LogStream
+ */
+ void deleteLog(LogName name);
+
+ /*
+ * Change the configuration of a LogStream or manipulate a LogStream's listeners
+ */
+
+ /**
+ * Updates a log with the new configuration object, overriding
+ * the previous configuration.
+ *
+ * @param config The new configuration object
+ */
+ void updateConfiguration(LogName name, LogStreamConfiguration config);
+
+ /**
+ * Registers a {@link RecordListener} with the log which will receive all records written using
+ * the unique name provided by {@link RecorderListener#getName()}.
+ *
+ * Impl spec: The name returned by a {@link RecordListener} instance uniquely identifies it against other
+ * instances.
+ *
+ * @param the log's name
+ * @param listener The listener to register
+ */
+ void addRecordListener(LogName name, RecordListener listener);
+
+ /**
+ * Removes a {@link RecordListener) for the log.
+ *
+ * Impl spec: The name returned by a {@link RecordListener} instance uniquely identifies it against
+ * other instances.
+ *
+ * @param the log's name
+ * @param listener The listener to remove
+ */
+ void removeRecordListener(LogName name, RecordListener listener);
+
+ /**
+ * Overrides {@link #close()} in {@link AutoCloseable} to throw an IOException.
+ */
+ void close() throws IOException;
+}
http://git-wip-us.apache.org/repos/asf/incubator-ratis/blob/ef48512d/ratis-logservice/src/main/java/org/apache/ratis/logservice/api/LogStream.java
----------------------------------------------------------------------
diff --git a/ratis-logservice/src/main/java/org/apache/ratis/logservice/api/LogStream.java b/ratis-logservice/src/main/java/org/apache/ratis/logservice/api/LogStream.java
new file mode 100644
index 0000000..fea213e
--- /dev/null
+++ b/ratis-logservice/src/main/java/org/apache/ratis/logservice/api/LogStream.java
@@ -0,0 +1,79 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.ratis.logservice.api;
+
+import java.io.IOException;
+import java.util.Set;
+
+/**
+ * A distributed log with "infinite" length that supports reads and writes.
+ */
+public interface LogStream {
+
+ /**
+ * An enumeration that defines the current state of a LogStream
+ */
+ public enum State {
+ OPEN,
+ CLOSED;
+ }
+
+ /**
+ * Returns the unique name to identify this log.
+ */
+ LogName getName();
+
+ /**
+ * Returns the current state of this log.
+ */
+ State getState();
+
+ /**
+ * Returns the size of this LogStream in bytes.
+ */
+ long getSize();
+
+ /**
+ * Creates a reader to read this LogStream.
+ *
+ * @return A synchronous reader
+ */
+ LogReader createReader();
+
+ /**
+ * Creates a write to write to this LogStream.
+ *
+ * @return A synchronous writer
+ */
+ LogWriter createWriter();
+
+ /**
+ * Returns the recordId of the last record in this LogStream. For an empty log, the recordId is {@code 0}.
+ */
+ long getLastRecordId();
+
+ /**
+ * Returns all {@link RecordListeners} for this LogStream.
+ */
+ Set<RecordListener> getRecordListeners();
+
+ /**
+ * Returns a copy of the Configuration for this LogStream.
+ */
+ LogStreamConfiguration getConfiguration();
+}
http://git-wip-us.apache.org/repos/asf/incubator-ratis/blob/ef48512d/ratis-logservice/src/main/java/org/apache/ratis/logservice/api/LogStreamConfiguration.java
----------------------------------------------------------------------
diff --git a/ratis-logservice/src/main/java/org/apache/ratis/logservice/api/LogStreamConfiguration.java b/ratis-logservice/src/main/java/org/apache/ratis/logservice/api/LogStreamConfiguration.java
new file mode 100644
index 0000000..12aa030
--- /dev/null
+++ b/ratis-logservice/src/main/java/org/apache/ratis/logservice/api/LogStreamConfiguration.java
@@ -0,0 +1,64 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.ratis.logservice.api;
+
+import java.util.Map;
+import java.util.Map.Entry;
+
+/**
+ * An encapsulation of configuration for a LogStream.
+ */
+public interface LogStreamConfiguration {
+
+ /**
+ * Fetches the value for the given key from the configuration. If there is no entry for
+ * the given key, {@code null} is returned.
+ *
+ * @param key The configuration key
+ */
+ String get(String key);
+
+ /**
+ * Sets the given key and value into this configuration. The configuration key may
+ * not be null. A null value removes the key from the configuration.
+ *
+ * @param key Configuration key, must be non-null
+ * @param value Configuration value
+ */
+ void set(String key, String value);
+
+ /**
+ * Removes any entry with the given key from the configuration. If there is no entry
+ * for the given key, this method returns without error. The provided key must be
+ * non-null.
+ *
+ * @param key The configuration key, must be non-null
+ */
+ void remove(String key);
+
+ /**
+ * Sets the collection of key-value pairs into the configuration. This is functionally
+ * equivalent to calling {@link #set(String, String)} numerous time.
+ */
+ void setMany(Iterable<Entry<String,String>> entries);
+
+ /**
+ * Returns an immutable view over the configuration as a {@code Map}.
+ */
+ Map<String,String> asMap();
+}
http://git-wip-us.apache.org/repos/asf/incubator-ratis/blob/ef48512d/ratis-logservice/src/main/java/org/apache/ratis/logservice/api/LogWriter.java
----------------------------------------------------------------------
diff --git a/ratis-logservice/src/main/java/org/apache/ratis/logservice/api/LogWriter.java b/ratis-logservice/src/main/java/org/apache/ratis/logservice/api/LogWriter.java
new file mode 100644
index 0000000..5a51a3c
--- /dev/null
+++ b/ratis-logservice/src/main/java/org/apache/ratis/logservice/api/LogWriter.java
@@ -0,0 +1,64 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.ratis.logservice.api;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.List;
+
+/**
+ * Synchronous client interface to write to a LogStream.
+ */
+public interface LogWriter extends AutoCloseable {
+
+ /**
+ * Appends the given data as a record in the LogStream.
+ *
+ * @param data The record to append
+ * @return The recordId for the record just written
+ */
+ long write(ByteBuffer data) throws IOException;
+
+ /**
+ * Appends each entry of data as a new record in the LogStream. If this method returns
+ * successfully, all records can be considered persisted. Otherwise, none can be assumed
+ * to have been written.
+ *
+ * @param records Records to append
+ * @return The largest recordId assigned to the records written
+ */
+ default long write(List<ByteBuffer> records) throws IOException {
+ for (ByteBuffer record : records) {
+ write(record);
+ }
+ return records.size();
+ }
+
+ /**
+ * Guarantees that all previous data appended by {@link #write(ByteBuffer)} are persisted
+ * and durable in the LogStream.
+ *
+ * @return The recordId prior to which all records are durable
+ */
+ long sync() throws IOException;
+
+ /**
+ * Overrides {@link #close()} in {@link AutoCloseable} to throw an IOException.
+ */
+ void close() throws IOException;
+}
http://git-wip-us.apache.org/repos/asf/incubator-ratis/blob/ef48512d/ratis-logservice/src/main/java/org/apache/ratis/logservice/api/RecordListener.java
----------------------------------------------------------------------
diff --git a/ratis-logservice/src/main/java/org/apache/ratis/logservice/api/RecordListener.java b/ratis-logservice/src/main/java/org/apache/ratis/logservice/api/RecordListener.java
new file mode 100644
index 0000000..a62d071
--- /dev/null
+++ b/ratis-logservice/src/main/java/org/apache/ratis/logservice/api/RecordListener.java
@@ -0,0 +1,33 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.ratis.logservice.api;
+
+import java.nio.ByteBuffer;
+import java.util.function.Consumer;
+
+/**
+ * Interface that, when registered with a {@link LogStream}, will receive all records written
+ * to that LogStream until it is removed.
+ */
+public interface RecordListener extends Consumer<ByteBuffer> {
+
+ /**
+ * Returns a name to identify this listener from others.
+ */
+ String getName();
+}
http://git-wip-us.apache.org/repos/asf/incubator-ratis/blob/ef48512d/ratis-logservice/src/main/java/org/apache/ratis/logservice/dummy/DummyLogReader.java
----------------------------------------------------------------------
diff --git a/ratis-logservice/src/main/java/org/apache/ratis/logservice/dummy/DummyLogReader.java b/ratis-logservice/src/main/java/org/apache/ratis/logservice/dummy/DummyLogReader.java
new file mode 100644
index 0000000..71d2164
--- /dev/null
+++ b/ratis-logservice/src/main/java/org/apache/ratis/logservice/dummy/DummyLogReader.java
@@ -0,0 +1,76 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.ratis.logservice.dummy;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.ratis.logservice.api.LogReader;
+
+public class DummyLogReader implements LogReader {
+ private static final byte[] IMMUTABLE_BYTES = new byte[0];
+
+ @Override
+ public void close() {}
+
+ @Override
+ public void seek(long recordId) throws IOException {
+ // Noop.
+ return;
+ }
+
+ @Override
+ public ByteBuffer readNext() throws IOException {
+ return ByteBuffer.wrap(IMMUTABLE_BYTES);
+ }
+
+ @Override
+ public List<ByteBuffer> readBulk(int numRecords) throws IOException {
+ ArrayList<ByteBuffer> records = new ArrayList<>(numRecords);
+ for (int i = 0; i < numRecords; i++) {
+ records.add(ByteBuffer.wrap(IMMUTABLE_BYTES));
+ }
+ return records;
+ }
+
+ @Override
+ public void readNext(ByteBuffer buffer) throws IOException {
+ buffer.clear();
+ if (buffer.remaining() < IMMUTABLE_BYTES.length) {
+ throw new IllegalArgumentException("Cannot read data into buffer of size " + buffer.remaining());
+ }
+ buffer.put(IMMUTABLE_BYTES);
+ buffer.flip();
+ }
+
+ @Override
+ public int readBulk(List<ByteBuffer> buffers) throws IOException {
+ for (ByteBuffer buffer : buffers) {
+ readNext(buffer);
+ }
+ return buffers.size();
+ }
+
+ @Override
+ public long getPosition() {
+ // Always at the head of the list
+ return 0;
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-ratis/blob/ef48512d/ratis-logservice/src/main/java/org/apache/ratis/logservice/dummy/DummyLogService.java
----------------------------------------------------------------------
diff --git a/ratis-logservice/src/main/java/org/apache/ratis/logservice/dummy/DummyLogService.java b/ratis-logservice/src/main/java/org/apache/ratis/logservice/dummy/DummyLogService.java
new file mode 100644
index 0000000..691556a
--- /dev/null
+++ b/ratis-logservice/src/main/java/org/apache/ratis/logservice/dummy/DummyLogService.java
@@ -0,0 +1,92 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.ratis.logservice.dummy;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.apache.ratis.logservice.api.LogName;
+import org.apache.ratis.logservice.api.LogService;
+import org.apache.ratis.logservice.api.LogStream;
+import org.apache.ratis.logservice.api.LogStream.State;
+import org.apache.ratis.logservice.api.LogStreamConfiguration;
+import org.apache.ratis.logservice.api.RecordListener;
+
+public class DummyLogService implements LogService {
+ final ConcurrentHashMap<LogName,Set<RecordListener>> recordListeners = new ConcurrentHashMap<>();
+
+ @Override
+ public LogStream createLog(LogName name) {
+ return new DummyLogStream(this, name);
+ }
+
+ @Override
+ public LogStream createLog(LogName name, LogStreamConfiguration config) {
+ return new DummyLogStream(this, name);
+ }
+
+ @Override
+ public LogStream getLog(LogName name) {
+ return new DummyLogStream(this, name);
+ }
+
+ @Override
+ public Iterator<LogStream> listLogs() {
+ return Collections.<LogStream> emptyList().iterator();
+ }
+
+ @Override public void closeLog(LogName name) {}
+
+ @Override
+ public State getState(LogName name) {
+ return State.OPEN;
+ }
+
+ @Override public void archiveLog(LogName name) {}
+
+ @Override public void deleteLog(LogName name) {}
+
+ @Override public void updateConfiguration(LogName name, LogStreamConfiguration config) {}
+
+ @Override public void addRecordListener(LogName name, RecordListener listener) {
+ recordListeners.compute(name, (key, currentValue) -> {
+ if (currentValue == null) {
+ return new HashSet<RecordListener>(Collections.singleton(listener));
+ }
+ currentValue.add(listener);
+ return currentValue;
+ });
+ }
+
+ @Override public void removeRecordListener(LogName name, RecordListener listener) {
+ recordListeners.compute(name, (key, currentValue) -> {
+ if (currentValue == null) {
+ return null;
+ }
+ currentValue.remove(listener);
+ return currentValue;
+ });
+ }
+
+ @Override public void close() throws IOException {}
+
+}
http://git-wip-us.apache.org/repos/asf/incubator-ratis/blob/ef48512d/ratis-logservice/src/main/java/org/apache/ratis/logservice/dummy/DummyLogStream.java
----------------------------------------------------------------------
diff --git a/ratis-logservice/src/main/java/org/apache/ratis/logservice/dummy/DummyLogStream.java b/ratis-logservice/src/main/java/org/apache/ratis/logservice/dummy/DummyLogStream.java
new file mode 100644
index 0000000..c52a160
--- /dev/null
+++ b/ratis-logservice/src/main/java/org/apache/ratis/logservice/dummy/DummyLogStream.java
@@ -0,0 +1,83 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.ratis.logservice.dummy;
+
+import java.util.Collections;
+import java.util.Objects;
+import java.util.Set;
+
+import org.apache.ratis.logservice.api.LogName;
+import org.apache.ratis.logservice.api.LogReader;
+import org.apache.ratis.logservice.api.LogStream;
+import org.apache.ratis.logservice.api.LogStreamConfiguration;
+import org.apache.ratis.logservice.api.LogWriter;
+import org.apache.ratis.logservice.api.RecordListener;
+
+public class DummyLogStream implements LogStream {
+ private final LogName name;
+ private final DummyLogService service;
+
+ public DummyLogStream(DummyLogService service, LogName name) {
+ this.service = Objects.requireNonNull(service);
+ this.name = Objects.requireNonNull(name);
+ }
+
+ @Override
+ public LogName getName() {
+ return name;
+ }
+
+ @Override
+ public long getSize() {
+ return 0;
+ }
+
+ @Override
+ public LogReader createReader() {
+ return new DummyLogReader();
+ }
+
+ @Override
+ public LogWriter createWriter() {
+ return new DummyLogWriter();
+ }
+
+ @Override
+ public Set<RecordListener> getRecordListeners() {
+ Set<RecordListener> listeners = service.recordListeners.get(name);
+ if (listeners == null) {
+ return Collections.emptySet();
+ }
+ return Collections.unmodifiableSet(listeners);
+ }
+
+ @Override
+ public State getState() {
+ return State.OPEN;
+ }
+
+ @Override
+ public long getLastRecordId() {
+ return 0;
+ }
+
+ @Override
+ public LogStreamConfiguration getConfiguration() {
+ return null;
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-ratis/blob/ef48512d/ratis-logservice/src/main/java/org/apache/ratis/logservice/dummy/DummyLogWriter.java
----------------------------------------------------------------------
diff --git a/ratis-logservice/src/main/java/org/apache/ratis/logservice/dummy/DummyLogWriter.java b/ratis-logservice/src/main/java/org/apache/ratis/logservice/dummy/DummyLogWriter.java
new file mode 100644
index 0000000..c9f689d
--- /dev/null
+++ b/ratis-logservice/src/main/java/org/apache/ratis/logservice/dummy/DummyLogWriter.java
@@ -0,0 +1,51 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.ratis.logservice.dummy;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicLong;
+
+import org.apache.ratis.logservice.api.LogWriter;
+
+public class DummyLogWriter implements LogWriter {
+ private final AtomicLong counter;
+
+ public DummyLogWriter() {
+ this.counter = new AtomicLong(-1);
+ }
+
+ @Override public void close() {}
+
+ @Override
+ public long write(ByteBuffer data) throws IOException {
+ return counter.incrementAndGet();
+ }
+
+ @Override
+ public long write(List<ByteBuffer> records) throws IOException {
+ return counter.addAndGet(records.size());
+ }
+
+ @Override
+ public long sync() throws IOException {
+ return counter.get();
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/incubator-ratis/blob/ef48512d/ratis-logservice/src/test/java/org/apache/ratis/logservice/api/TestApiExample.java
----------------------------------------------------------------------
diff --git a/ratis-logservice/src/test/java/org/apache/ratis/logservice/api/TestApiExample.java b/ratis-logservice/src/test/java/org/apache/ratis/logservice/api/TestApiExample.java
new file mode 100644
index 0000000..9808b23
--- /dev/null
+++ b/ratis-logservice/src/test/java/org/apache/ratis/logservice/api/TestApiExample.java
@@ -0,0 +1,69 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.ratis.logservice.api;
+
+import static org.junit.Assert.assertEquals;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.ExecutionException;
+
+import org.apache.ratis.logservice.dummy.DummyLogService;
+import org.junit.Test;
+
+/**
+ * Example usage of the LogService API with dummy objects.
+ */
+public class TestApiExample {
+
+ byte[] intToBytes(int i) {
+ return Integer.toString(i).getBytes(StandardCharsets.UTF_8);
+ }
+
+ @Test
+ public void test() throws IOException, InterruptedException, ExecutionException {
+ try (LogService svc = new DummyLogService()) {
+ LogStream log1 = svc.createLog(LogName.of("log1"));
+ // Write some data
+ try (LogWriter writer = log1.createWriter()) {
+ for (int i = 0; i < 5; i++) {
+ writer.write(ByteBuffer.wrap(intToBytes(i)));
+ }
+
+ List<ByteBuffer> records = new ArrayList<>(5);
+ for (int i = 5; i < 10; i++) {
+ records.add(ByteBuffer.wrap(intToBytes(i)));
+ }
+ writer.write(records);
+ }
+
+ // Read some data
+ try (LogReader reader = log1.createReader()) {
+ // Seek the reader
+ reader.seek(0);
+ List<ByteBuffer> records = reader.readBulk(10);
+ assertEquals(10, records.size());
+ }
+
+ svc.deleteLog(log1.getName());
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-ratis/blob/ef48512d/ratis-logservice/src/test/resources/log4j.properties
----------------------------------------------------------------------
diff --git a/ratis-logservice/src/test/resources/log4j.properties b/ratis-logservice/src/test/resources/log4j.properties
new file mode 100644
index 0000000..ced0687
--- /dev/null
+++ b/ratis-logservice/src/test/resources/log4j.properties
@@ -0,0 +1,18 @@
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+# log4j configuration used during build and unit tests
+
+log4j.rootLogger=info,stdout
+log4j.threshold=ALL
+log4j.appender.stdout=org.apache.log4j.ConsoleAppender
+log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
+log4j.appender.stdout.layout.ConversionPattern=%d{ISO8601} %-5p %c{2} (%F:%M(%L)) - %m%n