package org.corfudb.runtime.view;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import lombok.*;
import lombok.extern.slf4j.Slf4j;
import org.corfudb.runtime.CorfuRuntime;
import org.corfudb.runtime.clients.*;
import org.corfudb.runtime.exceptions.QuorumUnreachableException;
import org.corfudb.runtime.exceptions.WrongEpochException;
import org.corfudb.runtime.view.replication.*;
import org.corfudb.runtime.view.stream.BackpointerStreamView;
import org.corfudb.runtime.view.stream.IStreamView;
import java.util.Map;
import java.util.HashSet;
import java.util.List;
import java.util.ArrayList;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Stream;
import static java.util.Objects.requireNonNull;
/**
* This class represents the layout of a Corfu instance.
* Created by mwei on 12/8/15.
*/
@Slf4j
@Data
@ToString(exclude = {"runtime"})
@EqualsAndHashCode
public class Layout implements Cloneable {
/**
* A Gson parser.
*/
static final Gson parser = new GsonBuilder()
.registerTypeAdapter(Layout.class, new LayoutDeserializer())
.create();
/**
* A list of layout servers in the layout.
*/
@Getter
List<String> layoutServers;
/**
* A list of sequencers in the layout.
*/
@Getter
List<String> sequencers;
/**
* A list of the segments in the layout.
*/
@Getter
List<LayoutSegment> segments;
/**
* A list of unresponsive nodes in the layout.
*/
@Getter
List<String> unresponsiveServers;
/**
* The epoch of this layout.
*/
@Getter
@Setter
long epoch;
/**
* The org.corfudb.runtime this layout is associated with.
*/
@Getter
@Setter
transient CorfuRuntime runtime;
/* Defensive constructor since we can create a Layout from a JSON file. JSON deserialize is forced through
* this constructor.
*/
public Layout(@NonNull List<String> layoutServers, @NonNull List<String> sequencers,
@NonNull List<LayoutSegment> segments, @NonNull List<String> unresponsiveServers, long epoch) {
this.layoutServers = layoutServers;
this.sequencers = sequencers;
this.segments = segments;
this.unresponsiveServers = unresponsiveServers;
this.epoch = epoch;
/* Assert that we constructed a valid Layout */
if (this.layoutServers.size() == 0) throw new IllegalArgumentException("Empty list of LayoutServers");
if (this.sequencers.size() == 0) throw new IllegalArgumentException("Empty list of Sequencers");
if (this.segments.size() == 0) throw new IllegalArgumentException("Empty list of segments");
for (Layout.LayoutSegment segment : segments) {
requireNonNull(segment.stripes);
if (segment.stripes.size() == 0) throw new IllegalArgumentException("One segment has an empty list of stripes");
}
}
public Layout(List<String> layoutServers, List<String> sequencers, List<LayoutSegment> segments, long epoch) {
this(layoutServers, sequencers, segments, new ArrayList<String>(), epoch);
}
/**
* Get a layout from a JSON string.
*/
public static Layout fromJSONString(String json) {
/* Empty Json file creates an null Layout */
return requireNonNull(parser.fromJson(json, Layout.class));
}
/**
* Move each server in the system to the epoch of this layout.
*
* @throws WrongEpochException If any server is in a higher epoch.
* @throws QuorumUnreachableException If enough number of servers cannot be sealed.
*/
public void moveServersToEpoch()
throws WrongEpochException, QuorumUnreachableException {
log.debug("Requested move of servers to new epoch {} servers are {}", epoch, getAllServers());
// Set remote epoch on all servers in layout.
Map<String, CompletableFuture<Boolean>> resultMap = SealServersHelper.asyncSetRemoteEpoch(this);
// Validate if we received enough layout server responses.
SealServersHelper.waitForLayoutSeal(layoutServers, resultMap);
// Validate if we received enough log unit server responses depending on the replication mode.
for (LayoutSegment layoutSegment : getSegments()) {
layoutSegment.getReplicationMode().validateSegmentSeal(layoutSegment, resultMap);
}
log.debug("Layout has been sealed successfully.");
}
/**
* This function returns a set of all the servers in the layout.
*
* @return A set containing all servers in the layout.
*/
public Set<String> getAllServers() {
Set<String> allServers = new HashSet<>();
layoutServers.forEach(allServers::add);
sequencers.forEach(allServers::add);
segments.forEach(x ->
x.getStripes().forEach(y ->
y.getLogServers().forEach(allServers::add)));
return allServers;
}
/**
* Return the layout client for a particular index.
*
* @param index The index to return a layout client for.
* @return The layout client at that index, or null, if there is
* no client at that index.
*/
public LayoutClient getLayoutClient(int index) {
try {
String s = layoutServers.get(index);
return runtime.getRouter(s).getClient(LayoutClient.class);
} catch (IndexOutOfBoundsException ix) {
return null;
}
}
/**
* Get a java stream representing all layout clients for this layout.
*
* @return A java stream representing all layout clients.
*/
public Stream<LayoutClient> getLayoutClientStream() {
return layoutServers.stream()
.map(runtime::getRouter)
.map(x -> x.getClient(LayoutClient.class));
}
/**
* Return the sequencer client for a particular index.
*
* @param index The index to return a sequencer client for.
* @return The sequencer client at that index, or null, if there is
* no client at that index.
*/
public SequencerClient getSequencer(int index) {
try {
String s = sequencers.get(index);
return runtime.getRouter(s).getClient(SequencerClient.class);
} catch (IndexOutOfBoundsException ix) {
return null;
}
}
public long getLocalAddress(long globalAddress) {
for (LayoutSegment ls : segments) {
if (ls.start <= globalAddress && (ls.end > globalAddress || ls.end == -1)) {
// TODO: this does not account for shifting segments.
return globalAddress / ls.getNumberOfStripes();
}
}
throw new RuntimeException("Unmapped address!");
}
public long getGlobalAddress(LayoutStripe stripe, long localAddress) {
for (LayoutSegment ls : segments) {
if (ls.getStripes().contains(stripe)) {
for (int i = 0; i < ls.getNumberOfStripes(); i++) {
if (ls.getStripes().get(i).equals(stripe)) {
return (localAddress * ls.getNumberOfStripes()) + i;
}
}
}
}
throw new RuntimeException("Unmapped address!");
}
public LayoutStripe getStripe(long globalAddress) {
for (LayoutSegment ls : segments) {
if (ls.start <= globalAddress && (ls.end > globalAddress || ls.end == -1)) {
// TODO: this does not account for shifting segments.
return ls.getStripes().get((int) (globalAddress % ls.getNumberOfStripes()));
}
}
throw new RuntimeException("Unmapped address!");
}
public int getNumReplexUnits(int whichReplex) {
return segments.get(segments.size() - 1).replexes.get(whichReplex).getLogServers().size();
}
public int getReplexUnitIndex(int whichReplex, UUID streamID) {
return streamID.hashCode() % getNumReplexUnits(whichReplex);
}
public LayoutSegment getSegment(long globalAddress) {
for (LayoutSegment ls : segments) {
if (ls.start <= globalAddress && (ls.end > globalAddress || ls.end == -1)) {
return ls;
}
}
throw new RuntimeException("Unmapped address " + Long.toString(globalAddress) + "!");
}
/**
* Get the length of a segment at a particular address.
*
* @param address The address to check.
* @return The length (number of servers) of that segment, or 0 if empty.
*/
public int getSegmentLength(long address) {
return getStripe(address).getLogServers().size();
}
/**
* Get the replication mode of a segment at a particular address.
*
* @param address The address to check.
* @return The replication mode of the segment, or null if empty.
*/
public ReplicationMode getReplicationMode(long address) {
for (LayoutSegment ls : segments) {
if (ls.start <= address && (ls.end > address || ls.end == -1)) {
return ls.getReplicationMode();
}
}
return null;
}
/**
* Get a log unit client at a given index of a particular address.
*
* @param address The address to check.
* @param index The index of the segment.
* @return A log unit client, if present. Null otherwise.
*/
public LogUnitClient getLogUnitClient(long address, int index) {
return runtime.getRouter(getStripe(address).getLogServers().get(index)).getClient(LogUnitClient.class);
}
/**
* Get the layout as a JSON string.
*/
public String asJSONString() {
return parser.toJson(this);
}
/**
* Creates and returns a copy of this object. The precise meaning
* of "copy" may depend on the class of the object. The general
* intent is that, for any object {@code x}, the expression:
* <blockquote>
* <pre>
* x.clone() != x</pre></blockquote>
* will be true, and that the expression:
* <blockquote>
* <pre>
* x.clone().getClass() == x.getClass()</pre></blockquote>
* will be {@code true}, but these are not absolute requirements.
* While it is typically the case that:
* <blockquote>
* <pre>
* x.clone().equals(x)</pre></blockquote>
* will be {@code true}, this is not an absolute requirement.
*
* By convention, the returned object should be obtained by calling
* {@code super.clone}. If a class and all of its superclasses (except
* {@code Object}) obey this convention, it will be the case that
* {@code x.clone().getClass() == x.getClass()}.
*
* By convention, the object returned by this method should be independent
* of this object (which is being cloned). To achieve this independence,
* it may be necessary to modify one or more fields of the object returned
* by {@code super.clone} before returning it. Typically, this means
* copying any mutable objects that comprise the internal "deep structure"
* of the object being cloned and replacing the references to these
* objects with references to the copies. If a class contains only
* primitive fields or references to immutable objects, then it is usually
* the case that no fields in the object returned by {@code super.clone}
* need to be modified.
*
* The method {@code clone} for class {@code Object} performs a
* specific cloning operation. First, if the class of this object does
* not implement the interface {@code Cloneable}, then a
* {@code CloneNotSupportedException} is thrown. Note that all arrays
* are considered to implement the interface {@code Cloneable} and that
* the return type of the {@code clone} method of an array type {@code T[]}
* is {@code T[]} where T is any reference or primitive type.
* Otherwise, this method creates a new instance of the class of this
* object and initializes all its fields with exactly the contents of
* the corresponding fields of this object, as if by assignment; the
* contents of the fields are not themselves cloned. Thus, this method
* performs a "shallow copy" of this object, not a "deep copy" operation.
*
* The class {@code Object} does not itself implement the interface
* {@code Cloneable}, so calling the {@code clone} method on an object
* whose class is {@code Object} will result in throwing an
* exception at run time.
*
* @return a clone of this instance.
* @throws CloneNotSupportedException if the object's class does not
* support the {@code Cloneable} interface. Subclasses
* that override the {@code clone} method can also
* throw this exception to indicate that an instance cannot
* be cloned.
* @see Cloneable
*/
@Override
public Object clone() throws CloneNotSupportedException {
super.clone();
return parser.fromJson(asJSONString(), Layout.class);
}
public enum ReplicationMode {
CHAIN_REPLICATION {
@Override
public void validateSegmentSeal(LayoutSegment layoutSegment,
Map<String, CompletableFuture<Boolean>> completableFutureMap)
throws QuorumUnreachableException {
SealServersHelper.waitForChainSegmentSeal(layoutSegment, completableFutureMap);
}
@Override
public IStreamView getStreamView(CorfuRuntime r, UUID streamId) {
return new BackpointerStreamView(r, streamId);
}
@Override
public IReplicationProtocol getReplicationProtocol(CorfuRuntime r) {
if (r.isHoleFillingDisabled()) {
return new ChainReplicationProtocol(new NeverHoleFillPolicy(100));
} else {
return new ChainReplicationProtocol(new ReadWaitHoleFillPolicy(100,
r.getParameters().getHoleFillRetry()));
}
}
},
QUORUM_REPLICATION {
@Override
public void validateSegmentSeal(LayoutSegment layoutSegment,
Map<String, CompletableFuture<Boolean>> completableFutureMap)
throws QuorumUnreachableException {
//TODO: Take care of log unit servers which were not sealed.
SealServersHelper.waitForQuorumSegmentSeal(layoutSegment, completableFutureMap);
}
@Override
public IStreamView getStreamView(CorfuRuntime r, UUID streamId) {
return new BackpointerStreamView(r, streamId);
}
@Override
public IReplicationProtocol getReplicationProtocol(CorfuRuntime r) {
if (r.isHoleFillingDisabled()) {
return new QuorumReplicationProtocol(new NeverHoleFillPolicy(100));
} else {
return new QuorumReplicationProtocol(new ReadWaitHoleFillPolicy(100,
r.getParameters().getHoleFillRetry()));
}
}
},
REPLEX {
@Override
public void validateSegmentSeal(LayoutSegment layoutSegment,
Map<String, CompletableFuture<Boolean>> completableFutureMap)
throws QuorumUnreachableException {
throw new UnsupportedOperationException("unsupported seal");
}
@Override
public IStreamView getStreamView(CorfuRuntime r, UUID streamId) {
throw new UnsupportedOperationException("unsupported in this release");
}
},
NO_REPLICATION {
@Override
public void validateSegmentSeal(LayoutSegment layoutSegment,
Map<String, CompletableFuture<Boolean>> completableFutureMap)
throws QuorumUnreachableException {
throw new UnsupportedOperationException("unsupported seal");
}
@Override
public IStreamView getStreamView(CorfuRuntime r, UUID streamId) {
throw new UnsupportedOperationException("Stream view used without a replication mode");
}
};
/**
* Seals the layout segment.
*/
public abstract void validateSegmentSeal(LayoutSegment layoutSegment,
Map<String, CompletableFuture<Boolean>> completableFutureMap)
throws QuorumUnreachableException;
public abstract IStreamView getStreamView(CorfuRuntime r, UUID streamId);
public IReplicationProtocol getReplicationProtocol(CorfuRuntime r) {
throw new UnsupportedOperationException();
}
}
@Data
@Getter
@Setter
public static class LayoutSegment {
/**
* The replication mode of the segment.
*/
ReplicationMode replicationMode;
/**
* The address the layout segment starts at.
*/
long start;
/**
* The address the layout segment ends at.
*/
long end;
/**
* A list of log servers for this segment.
*/
List<LayoutStripe> stripes;
public LayoutSegment(@NonNull ReplicationMode replicationMode, long start, long end,
@NonNull List<LayoutStripe> stripes) {
this.replicationMode = replicationMode;
this.start = start;
this.end = end;
this.stripes = stripes;
}
List<LayoutStripe> replexes; // A list of replexes. Each LayoutStripe is a replex, because it is just a list of
// servers. Select one node from each LayoutStripe (replex) to append to.
// For now, there is only 1 replex, which are the stream homes.
public int getNumberOfStripes() { return stripes.size(); }
public int getNumberOfReplexes() { return replexes.size(); }
}
@Data
@Getter
public static class LayoutStripe {
final List<String> logServers;
public LayoutStripe(@NonNull List<String> logServers) {
this.logServers = logServers;
}
}
}