/**
* 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.config.ClusterMapConfig;
import com.github.ambry.network.Port;
import com.github.ambry.network.PortType;
import com.github.ambry.utils.Utils;
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;
import static com.github.ambry.clustermap.ClusterMapUtils.*;
/**
* An extension of {@link DataNodeId} to be used within the {@link StaticClusterManager}.
*
* DataNode is uniquely identified by its hostname and port. A DataNode is in a {@link Datacenter}. A DataNode has zero
* or more {@link Disk}s.
*/
class DataNode extends DataNodeId {
private final Datacenter datacenter;
private final String hostname;
private final int portNum;
private final Map<PortType, Port> ports;
private final ArrayList<Disk> disks;
private final long rawCapacityInBytes;
private final ResourceStatePolicy dataNodeStatePolicy;
private final long rackId;
private final ArrayList<String> sslEnabledDataCenters;
private final Logger logger = LoggerFactory.getLogger(getClass());
DataNode(Datacenter datacenter, JSONObject jsonObject, ClusterMapConfig clusterMapConfig) throws JSONException {
if (logger.isTraceEnabled()) {
logger.trace("DataNode " + jsonObject.toString());
}
this.datacenter = datacenter;
this.sslEnabledDataCenters = Utils.splitString(clusterMapConfig.clusterMapSslEnabledDatacenters, ",");
this.hostname = getFullyQualifiedDomainName(jsonObject.getString("hostname"));
this.portNum = jsonObject.getInt("port");
try {
ResourceStatePolicyFactory resourceStatePolicyFactory =
Utils.getObj(clusterMapConfig.clusterMapResourceStatePolicyFactory, this,
HardwareState.valueOf(jsonObject.getString("hardwareState")), clusterMapConfig);
this.dataNodeStatePolicy = resourceStatePolicyFactory.getResourceStatePolicy();
} catch (Exception e) {
logger.error("Error creating resource state policy when instantiating a datanode " + e);
throw new IllegalStateException(
"Error creating resource state policy when instantiating a datanode: " + hostname + ":" + portNum, e);
}
JSONArray diskJSONArray = jsonObject.getJSONArray("disks");
this.disks = new ArrayList<Disk>(diskJSONArray.length());
for (int i = 0; i < diskJSONArray.length(); ++i) {
this.disks.add(new Disk(this, diskJSONArray.getJSONObject(i), clusterMapConfig));
}
this.rawCapacityInBytes = calculateRawCapacityInBytes();
this.ports = new HashMap<PortType, Port>();
this.ports.put(PortType.PLAINTEXT, new Port(portNum, PortType.PLAINTEXT));
populatePorts(jsonObject);
if (jsonObject.has("rackId")) {
this.rackId = jsonObject.getLong("rackId");
if (this.rackId < 0) {
throw new IllegalStateException("Invalid rackId : " + this.rackId + " is less than 0");
}
} else {
this.rackId = UNKNOWN_RACK_ID;
}
validate();
}
private void populatePorts(JSONObject jsonObject) throws JSONException {
if (jsonObject.has("sslport")) {
int sslPortNum = jsonObject.getInt("sslport");
this.ports.put(PortType.SSL, new Port(sslPortNum, PortType.SSL));
}
}
@Override
public String getHostname() {
return hostname;
}
@Override
public int getPort() {
return portNum;
}
@Override
public boolean hasSSLPort() {
return ports.containsKey(PortType.SSL);
}
/**
* Gets the DataNode's SSL port number.
*
* @return Port number upon which to establish an SSL encrypted connection with the DataNodeId.
* @throws IllegalStateException Thrown if no SSL port exists.
*/
@Override
public int getSSLPort() {
if (hasSSLPort()) {
return ports.get(PortType.SSL).getPort();
} else {
throw new IllegalStateException("No SSL port exists for the Data Node " + hostname + ":" + portNum);
}
}
/**
* Returns the {@link Port} of this node to connect to. A {@link Port} will be automatically selected based on if
* there is a need of establishing an SSL connection.
*
* @return {@link Port} to which the caller can connect to.
* @throws IllegalStateException Thrown if an SSL connection is needed but no SSL port exists.
*/
@Override
public Port getPortToConnectTo() {
if (sslEnabledDataCenters.contains(datacenter.getName())) {
if (ports.containsKey(PortType.SSL)) {
return ports.get(PortType.SSL);
} else {
throw new IllegalStateException("No SSL Port exists for the data node " + hostname + ":" + portNum);
}
}
return ports.get(PortType.PLAINTEXT);
}
@Override
public HardwareState getState() {
return dataNodeStatePolicy.isDown() ? HardwareState.UNAVAILABLE : HardwareState.AVAILABLE;
}
void onNodeTimeout() {
dataNodeStatePolicy.onError();
}
void onNodeResponse() {
dataNodeStatePolicy.onSuccess();
}
boolean isDown() {
return dataNodeStatePolicy.isDown();
}
@Override
public String getDatacenterName() {
return getDatacenter().getName();
}
Datacenter getDatacenter() {
return datacenter;
}
long getRawCapacityInBytes() {
return rawCapacityInBytes;
}
private long calculateRawCapacityInBytes() {
long capacityInBytes = 0;
for (Disk disk : disks) {
capacityInBytes += disk.getRawCapacityInBytes();
}
return capacityInBytes;
}
List<Disk> getDisks() {
return disks;
}
@Override
public long getRackId() {
return rackId;
}
protected void validateDatacenter() {
if (datacenter == null) {
throw new IllegalStateException("Datacenter cannot be null.");
}
}
private void validateHostname() {
String fqdn = getFullyQualifiedDomainName(hostname);
if (!fqdn.equals(hostname)) {
throw new IllegalStateException(
"Hostname for DataNode (" + hostname + ") does not match its fully qualified domain name: " + fqdn + ".");
}
}
private void validatePorts() {
Set<Integer> portNumbers = new HashSet<Integer>();
for (PortType portType : ports.keySet()) {
int portNo = ports.get(portType).getPort();
if (portNumbers.contains(portNo)) {
throw new IllegalStateException("Same port number " + portNo + " found for two port types");
}
if (portNo < MIN_PORT) {
throw new IllegalStateException("Invalid " + portType + " port : " + portNo + " is less than " + MIN_PORT);
} else if (portNo > MAX_PORT) {
throw new IllegalStateException("Invalid " + portType + " port : " + portNo + " is greater than " + MAX_PORT);
}
portNumbers.add(portNo);
}
}
private void validate() {
logger.trace("begin validate.");
validateDatacenter();
validateHostname();
validatePorts();
for (Disk disk : disks) {
disk.validate();
}
logger.trace("complete validate.");
}
JSONObject toJSONObject() throws JSONException {
JSONObject jsonObject = new JSONObject().put("hostname", hostname).put("port", portNum);
addSSLPortToJson(jsonObject);
if (rackId >= 0) {
jsonObject.put("rackId", getRackId());
}
jsonObject.put("hardwareState",
dataNodeStatePolicy.isHardDown() ? HardwareState.UNAVAILABLE : HardwareState.AVAILABLE)
.put("disks", new JSONArray());
for (Disk disk : disks) {
jsonObject.accumulate("disks", disk.toJSONObject());
}
return jsonObject;
}
private void addSSLPortToJson(JSONObject jsonObject) throws JSONException {
for (PortType portType : ports.keySet()) {
if (portType == PortType.SSL) {
jsonObject.put("sslport", ports.get(portType).getPort());
}
}
}
@Override
public String toString() {
return "DataNode[" + getHostname() + ":" + getPort() + "]";
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
DataNode dataNode = (DataNode) o;
if (portNum != dataNode.portNum) {
return false;
}
return hostname.equals(dataNode.hostname);
}
@Override
public int hashCode() {
int result = hostname.hashCode();
result = 31 * result + portNum;
return result;
}
@Override
public int compareTo(DataNodeId o) {
if (o == null) {
throw new NullPointerException("input argument null");
}
DataNode other = (DataNode) o;
int compare = (portNum < other.portNum) ? -1 : ((portNum == other.portNum) ? 0 : 1);
if (compare == 0) {
compare = hostname.compareTo(other.hostname);
}
return compare;
}
}