/**
* 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 java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* PartitionLayout of {@link Partition}s and {@link Replica}s on an Ambry cluster (see {@link HardwareLayout}).
*/
class PartitionLayout {
private static final long MinPartitionId = 0;
private final HardwareLayout hardwareLayout;
private final String clusterName;
private final long version;
private final Map<ByteBuffer, Partition> partitionMap;
private long maxPartitionId;
private long allocatedRawCapacityInBytes;
private long allocatedUsableCapacityInBytes;
private final Logger logger = LoggerFactory.getLogger(getClass());
public PartitionLayout(HardwareLayout hardwareLayout, JSONObject jsonObject) throws JSONException {
if (logger.isTraceEnabled()) {
logger.trace("PartitionLayout " + hardwareLayout + ", " + jsonObject.toString());
}
this.hardwareLayout = hardwareLayout;
this.clusterName = jsonObject.getString("clusterName");
this.version = jsonObject.getLong("version");
this.partitionMap = new HashMap<ByteBuffer, Partition>();
for (int i = 0; i < jsonObject.getJSONArray("partitions").length(); ++i) {
addPartition(new Partition(this, jsonObject.getJSONArray("partitions").getJSONObject(i)));
}
validate();
}
// Constructor for initial PartitionLayout.
public PartitionLayout(HardwareLayout hardwareLayout) {
if (logger.isTraceEnabled()) {
logger.trace("PartitionLayout " + hardwareLayout);
}
this.hardwareLayout = hardwareLayout;
this.clusterName = hardwareLayout.getClusterName();
this.version = 1;
this.maxPartitionId = MinPartitionId;
this.partitionMap = new HashMap<ByteBuffer, Partition>();
validate();
}
public HardwareLayout getHardwareLayout() {
return hardwareLayout;
}
public String getClusterName() {
return clusterName;
}
public long getVersion() {
return version;
}
public long getPartitionCount() {
return partitionMap.size();
}
public long getPartitionInStateCount(PartitionState partitionState) {
int count = 0;
for (Partition partition : partitionMap.values()) {
if (partition.getPartitionState() == partitionState) {
count++;
}
}
return count;
}
public List<PartitionId> getPartitions() {
return new ArrayList<PartitionId>(partitionMap.values());
}
public List<PartitionId> getWritablePartitions() {
List<PartitionId> writablePartitions = new ArrayList();
List<PartitionId> healthyWritablePartitions = new ArrayList();
for (Partition partition : partitionMap.values()) {
if (partition.getPartitionState() == PartitionState.READ_WRITE) {
writablePartitions.add(partition);
boolean up = true;
for (Replica replica : partition.getReplicas()) {
if (replica.isDown()) {
up = false;
break;
}
}
if (up) {
healthyWritablePartitions.add(partition);
}
}
}
return healthyWritablePartitions.isEmpty() ? writablePartitions : healthyWritablePartitions;
}
public long getAllocatedRawCapacityInBytes() {
return allocatedRawCapacityInBytes;
}
private long calculateAllocatedRawCapacityInBytes() {
long allocatedRawCapacityInBytes = 0;
for (Partition partition : partitionMap.values()) {
allocatedRawCapacityInBytes += partition.getAllocatedRawCapacityInBytes();
}
return allocatedRawCapacityInBytes;
}
public long getAllocatedUsableCapacityInBytes() {
return allocatedUsableCapacityInBytes;
}
private long calculateAllocatedUsableCapacityInBytes() {
long allocatedUsableCapacityInBytes = 0;
for (Partition partition : partitionMap.values()) {
allocatedUsableCapacityInBytes += partition.getReplicaCapacityInBytes();
}
return allocatedUsableCapacityInBytes;
}
/**
* Adds Partition to and validates Partition is unique. A duplicate Partition results in an exception.
*/
private void addPartition(Partition partition) {
if (partitionMap.put(ByteBuffer.wrap(partition.getBytes()), partition) != null) {
throw new IllegalStateException("Duplicate Partition detected: " + partition.toString());
}
if (partition.getId() >= maxPartitionId) {
maxPartitionId = partition.getId() + 1;
}
}
protected void validateClusterName() {
if (clusterName == null) {
throw new IllegalStateException("ClusterName cannot be null.");
}
if (!hardwareLayout.getClusterName().equals(clusterName)) {
throw new IllegalStateException(
"PartitionLayout cluster name does not match that of HardwareLayout: " + clusterName + " != " + hardwareLayout
.getClusterName());
}
}
protected void validatePartitionIds() {
for (Partition partition : partitionMap.values()) {
long partitionId = partition.getId();
if (partitionId < MinPartitionId) {
throw new IllegalStateException("Partition has invalid ID: Less than " + MinPartitionId);
}
if (partitionId >= maxPartitionId) {
throw new IllegalStateException("Partition has invalid ID: Greater than or equal to " + maxPartitionId);
}
}
}
protected void validateUniqueness() {
// Validate uniqueness of each logical component. Partition uniqueness is validated by method addPartition.
Set<Replica> replicaSet = new HashSet<Replica>();
for (Partition partition : partitionMap.values()) {
for (Replica replica : partition.getReplicas()) {
if (!replicaSet.add(replica)) {
throw new IllegalStateException("Duplicate Replica detected: " + replica.toString());
}
}
}
}
protected void validate() {
logger.trace("begin validate.");
validateClusterName();
validatePartitionIds();
validateUniqueness();
this.allocatedRawCapacityInBytes = calculateAllocatedRawCapacityInBytes();
this.allocatedUsableCapacityInBytes = calculateAllocatedUsableCapacityInBytes();
logger.trace("complete validate.");
}
protected long getNewPartitionId() {
long currentPartitionId = maxPartitionId;
maxPartitionId++;
return currentPartitionId;
}
// Creates a Partition and corresponding Replicas for each specified disk
public Partition addNewPartition(List<Disk> disks, long replicaCapacityInBytes) {
if (disks == null || disks.size() == 0) {
throw new IllegalArgumentException("Disks either null or of zero length.");
}
Partition partition = new Partition(getNewPartitionId(), PartitionState.READ_WRITE, replicaCapacityInBytes);
for (Disk disk : disks) {
partition.addReplica(new Replica(partition, disk));
}
addPartition(partition);
validate();
return partition;
}
// Adds replicas to the partition for each specified disk
public void addNewReplicas(Partition partition, List<Disk> disks) {
if (partition == null || disks == null || disks.size() == 0) {
throw new IllegalArgumentException("Partition or disks is null or disks is of zero length");
}
for (Disk disk : disks) {
partition.addReplica(new Replica(partition, disk));
}
validate();
}
/**
* Gets Partition with specified byte-serialized ID.
*
* @param stream byte-serialized partition ID
* @return requested Partition else null.
*/
public Partition getPartition(InputStream stream) throws IOException {
byte[] partitionBytes = Partition.readPartitionBytesFromStream(stream);
return partitionMap.get(ByteBuffer.wrap(partitionBytes));
}
public JSONObject toJSONObject() throws JSONException {
JSONObject jsonObject = new JSONObject().put("clusterName", hardwareLayout.getClusterName())
.put("version", version)
.put("partitions", new JSONArray());
for (Partition partition : partitionMap.values()) {
jsonObject.accumulate("partitions", partition.toJSONObject());
}
return jsonObject;
}
@Override
public String toString() {
try {
return toJSONObject().toString(2);
} catch (JSONException e) {
logger.error("JSONException caught in toString: {}", e.getCause());
}
return null;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
PartitionLayout that = (PartitionLayout) o;
if (!clusterName.equals(that.clusterName)) {
return false;
}
return hardwareLayout.equals(that.hardwareLayout);
}
}