/**
* Copyright 2016 LinkedIn Corp. All rights reserved.
*
* 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.
*/
package com.github.ambry.clustermap;
import com.github.ambry.utils.Utils;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static com.github.ambry.clustermap.ClusterMapUtils.*;
/**
* An extension of {@link PartitionId} to be used within the {@link StaticClusterManager}.
*
* A Partition is the unit of data management in Ambry. Each Partition is uniquely identifiable by an ID. Partitions
* consist of one or more {@link Replica}s. Replicas ensure that a Partition is available and reliable.
*/
class Partition extends PartitionId {
private static final short Version_Field_Size_In_Bytes = 2;
private static final short Current_Version = 1;
private static final int Partition_Size_In_Bytes = Version_Field_Size_In_Bytes + 8;
private Long id;
PartitionState partitionState;
long replicaCapacityInBytes;
List<Replica> replicas;
private Logger logger = LoggerFactory.getLogger(getClass());
// For constructing new Partition
Partition(long id, PartitionState partitionState, long replicaCapacityInBytes) {
logger.trace("Partition " + id + ", " + partitionState + ", " + replicaCapacityInBytes);
this.id = id;
this.partitionState = partitionState;
this.replicaCapacityInBytes = replicaCapacityInBytes;
this.replicas = new ArrayList<Replica>();
validate();
}
Partition(PartitionLayout partitionLayout, JSONObject jsonObject) throws JSONException {
this(partitionLayout.getHardwareLayout(), jsonObject);
}
Partition(HardwareLayout hardwareLayout, JSONObject jsonObject) throws JSONException {
if (logger.isTraceEnabled()) {
logger.trace("Partition " + jsonObject.toString());
}
this.id = jsonObject.getLong("id");
this.partitionState = PartitionState.valueOf(jsonObject.getString("partitionState"));
this.replicaCapacityInBytes = jsonObject.getLong("replicaCapacityInBytes");
this.replicas = new ArrayList<Replica>(jsonObject.getJSONArray("replicas").length());
for (int i = 0; i < jsonObject.getJSONArray("replicas").length(); ++i) {
this.replicas.add(i, new Replica(hardwareLayout, this, jsonObject.getJSONArray("replicas").getJSONObject(i)));
}
validate();
}
static byte[] readPartitionBytesFromStream(InputStream stream) throws IOException {
byte[] partitionBytes = Utils.readBytesFromStream(stream, Partition_Size_In_Bytes);
return partitionBytes;
}
@Override
public byte[] getBytes() {
return ClusterMapUtils.serializeShortAndLong(Current_Version, id);
}
@Override
public List<ReplicaId> getReplicaIds() {
List<Replica> replicas = getReplicas();
return new ArrayList<ReplicaId>(replicas);
}
@Override
public PartitionState getPartitionState() {
return partitionState;
}
@Override
public boolean isEqual(String partitionId) {
return id.toString().equals(partitionId);
}
long getAllocatedRawCapacityInBytes() {
return replicaCapacityInBytes * replicas.size();
}
long getReplicaCapacityInBytes() {
return replicaCapacityInBytes;
}
List<Replica> getReplicas() {
return replicas;
}
long getId() {
return id;
}
/**
* Construct name based on Partition ID appropriate for use as a file or directory name.
*
* @return string representation of the Partition's ID for use as part of file system path.
*/
String toPathString() {
return Long.toString(id);
}
// For constructing new Partition
void addReplica(Replica replica) {
replicas.add(replica);
validate();
}
private void validateConstraints() {
// Ensure each replica is on distinct Disk and DataNode.
Set<DataNode> dataNodeSet = new HashSet<DataNode>();
Set<Disk> diskSet = new HashSet<Disk>();
for (Replica replica : replicas) {
if (!diskSet.add((Disk) replica.getDiskId())) {
throw new IllegalStateException(
"Multiple Replicas for same Partition are layed out on same Disk: " + toString());
}
if (!dataNodeSet.add(((Disk) replica.getDiskId()).getDataNode())) {
throw new IllegalStateException(
"Multiple Replicas for same Partition are layed out on same DataNode: " + toString());
}
}
}
private void validate() {
logger.trace("begin validate.");
validateReplicaCapacityInBytes(replicaCapacityInBytes);
validateConstraints();
logger.trace("complete validate.");
}
JSONObject toJSONObject() throws JSONException {
JSONObject jsonObject = new JSONObject().put("id", id)
.put("partitionState", partitionState)
.put("replicaCapacityInBytes", replicaCapacityInBytes)
.put("replicas", new JSONArray());
for (Replica replica : replicas) {
jsonObject.accumulate("replicas", replica.toJSONObject());
}
return jsonObject;
}
/**
* Generates a {@link String} representation that uniquely identifies this partition. The string
* is in the format of {@code Partition[i]}, where {@code i} is a {@code long} id number uniquely
* associated with this partition.
* @return The {@link String} representation of this partition.
*/
@Override
public String toString() {
return "Partition[" + toPathString() + "]";
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Partition partition = (Partition) o;
return id.equals(partition.id);
}
@Override
public int hashCode() {
return (int) (id ^ (id >>> 32));
}
@Override
public int compareTo(PartitionId o) {
if (o == null) {
throw new NullPointerException("input argument null");
}
Partition other = (Partition) o;
return id.compareTo(other.id);
}
void onPartitionReadOnly() {
// no-op
}
}