/** * 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.config.VerifiableProperties; import com.github.ambry.network.PortType; import java.util.Properties; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import org.junit.Test; import static org.junit.Assert.*; // TestDataNode permits DataNode to be constructed with a null Datacenter. class TestDataNode extends DataNode { public TestDataNode(String dataCenterName, JSONObject jsonObject, ClusterMapConfig clusterMapConfig) throws JSONException { super(new TestDatacenter(TestUtils.getJsonDatacenter(dataCenterName, new JSONArray()), clusterMapConfig), jsonObject, clusterMapConfig); } @Override public void validateDatacenter() { // Null OK } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } TestDataNode testDataNode = (TestDataNode) o; if (!getHostname().equals(testDataNode.getHostname())) { return false; } if (getPort() != testDataNode.getPort()) { return false; } if (getState() != testDataNode.getState()) { return false; } if (getRackId() != testDataNode.getRackId()) { return false; } return getRawCapacityInBytes() == testDataNode.getRawCapacityInBytes(); } } /** * Tests {@link DataNode} class. */ public class DataNodeTest { private static final int diskCount = 10; private static final long diskCapacityInBytes = 1000 * 1024 * 1024 * 1024L; private Properties props; public DataNodeTest() { props = new Properties(); props.setProperty("clustermap.cluster.name", "test"); props.setProperty("clustermap.datacenter.name", "dc1"); props.setProperty("clustermap.host.name", "localhost"); } JSONArray getDisks() throws JSONException { return TestUtils.getJsonArrayDisks(diskCount, "/mnt", HardwareState.AVAILABLE, diskCapacityInBytes); } @Test public void basics() throws JSONException { JSONObject jsonObject = TestUtils.getJsonDataNode(TestUtils.getLocalHost(), 6666, 7666, HardwareState.AVAILABLE, getDisks()); ClusterMapConfig clusterMapConfig = new ClusterMapConfig(new VerifiableProperties(props)); DataNode dataNode = new TestDataNode("datacenter", jsonObject, clusterMapConfig); assertEquals(dataNode.getHostname(), TestUtils.getLocalHost()); assertEquals(dataNode.getPort(), 6666); assertEquals(dataNode.getState(), HardwareState.AVAILABLE); assertEquals(dataNode.getDisks().size(), diskCount); assertEquals(dataNode.getRawCapacityInBytes(), diskCount * diskCapacityInBytes); assertEquals(-1, dataNode.getRackId()); assertEquals(dataNode.toJSONObject().toString(), jsonObject.toString()); assertEquals(dataNode, new TestDataNode("datacenter", dataNode.toJSONObject(), clusterMapConfig)); // Test with defined rackId jsonObject = TestUtils.getJsonDataNode(TestUtils.getLocalHost(), 6666, 7666, 42, HardwareState.AVAILABLE, getDisks()); dataNode = new TestDataNode("datacenter", jsonObject, clusterMapConfig); assertEquals(42, dataNode.getRackId()); assertEquals(dataNode.toJSONObject().toString(), jsonObject.toString()); assertEquals(dataNode, new TestDataNode("datacenter", dataNode.toJSONObject(), clusterMapConfig)); } public void failValidation(JSONObject jsonObject, ClusterMapConfig clusterMapConfig) throws JSONException { try { new TestDataNode("datacenter", jsonObject, clusterMapConfig); fail("Construction of TestDataNode should have failed validation."); } catch (IllegalStateException e) { // Expected. } } @Test public void validation() throws JSONException { JSONObject jsonObject; ClusterMapConfig clusterMapConfig = new ClusterMapConfig(new VerifiableProperties(props)); try { // Null DataNode jsonObject = TestUtils.getJsonDataNode(TestUtils.getLocalHost(), 6666, 7666, HardwareState.AVAILABLE, getDisks()); new DataNode(null, jsonObject, clusterMapConfig); fail("Should have failed validation."); } catch (IllegalStateException e) { // Expected. } // Bad hostname jsonObject = TestUtils.getJsonDataNode("", 6666, 7666, HardwareState.AVAILABLE, getDisks()); failValidation(jsonObject, clusterMapConfig); // Bad hostname (http://tools.ietf.org/html/rfc6761 defines 'invalid' top level domain) jsonObject = TestUtils.getJsonDataNode("hostname.invalid", 6666, 7666, HardwareState.AVAILABLE, getDisks()); failValidation(jsonObject, clusterMapConfig); // Bad port (too small) jsonObject = TestUtils.getJsonDataNode(TestUtils.getLocalHost(), -1, 7666, HardwareState.AVAILABLE, getDisks()); failValidation(jsonObject, clusterMapConfig); // Bad ssl port (too small) jsonObject = TestUtils.getJsonDataNode(TestUtils.getLocalHost(), 6666, -1, HardwareState.AVAILABLE, getDisks()); failValidation(jsonObject, clusterMapConfig); // Bad port (too big) jsonObject = TestUtils.getJsonDataNode(TestUtils.getLocalHost(), 100 * 1000, 7666, HardwareState.AVAILABLE, getDisks()); failValidation(jsonObject, clusterMapConfig); // Bad ssl port (too big) jsonObject = TestUtils.getJsonDataNode(TestUtils.getLocalHost(), 6666, 100 * 1000, HardwareState.AVAILABLE, getDisks()); failValidation(jsonObject, clusterMapConfig); // same port number for plain text and ssl port jsonObject = TestUtils.getJsonDataNode(TestUtils.getLocalHost(), 6666, 6666, HardwareState.AVAILABLE, getDisks()); failValidation(jsonObject, clusterMapConfig); // bad rack ID jsonObject = TestUtils.getJsonDataNode(TestUtils.getLocalHost(), 6666, 7666, -2, HardwareState.AVAILABLE, getDisks()); failValidation(jsonObject, clusterMapConfig); } @Test public void testSoftState() throws JSONException, InterruptedException { JSONObject jsonObject = TestUtils.getJsonDataNode(TestUtils.getLocalHost(), 6666, 7666, HardwareState.AVAILABLE, getDisks()); Properties props = new Properties(); props.setProperty("clustermap.fixedtimeout.datanode.retry.backoff.ms", Integer.toString(2000)); props.setProperty("clustermap.cluster.name", "test"); props.setProperty("clustermap.datacenter.name", "dc1"); props.setProperty("clustermap.host.name", "localhost"); ClusterMapConfig clusterMapConfig = new ClusterMapConfig(new VerifiableProperties(props)); int threshold = clusterMapConfig.clusterMapFixedTimeoutDatanodeErrorThreshold; long retryBackoffMs = clusterMapConfig.clusterMapFixedTimeoutDataNodeRetryBackoffMs; DataNode dataNode = new TestDataNode("datacenter", jsonObject, clusterMapConfig); for (int i = 0; i < threshold; i++) { ensure(dataNode, HardwareState.AVAILABLE); dataNode.onNodeTimeout(); } // After threshold number of continuous errors, the resource should be unavailable ensure(dataNode, HardwareState.UNAVAILABLE); Thread.sleep(retryBackoffMs + 1); // If retryBackoffMs has passed, the resource should be available. ensure(dataNode, HardwareState.AVAILABLE); //A single timeout should make the node unavailable now dataNode.onNodeTimeout(); ensure(dataNode, HardwareState.UNAVAILABLE); //A single response should make the node available now dataNode.onNodeResponse(); ensure(dataNode, HardwareState.AVAILABLE); } /** * Validate {@link DataNodeId#getPortToConnectTo()} returns port type corresponding to the * SSL enabled datacenter list specified in {@link ClusterMapConfig}. * @throws Exception */ @Test public void validateGetPort() throws Exception { ClusterMapConfig clusterMapConfig; Properties props = new Properties(); props.setProperty("clustermap.ssl.enabled.datacenters", "datacenter1,datacenter2,datacenter3"); props.setProperty("clustermap.cluster.name", "test"); props.setProperty("clustermap.datacenter.name", "dc1"); props.setProperty("clustermap.host.name", "localhost"); clusterMapConfig = new ClusterMapConfig(new VerifiableProperties(props)); System.out.println(clusterMapConfig.clusterMapSslEnabledDatacenters); JSONObject jsonObject = TestUtils.getJsonDataNode(TestUtils.getLocalHost(), 6666, 7666, HardwareState.AVAILABLE, getDisks()); DataNode dataNode = new TestDataNode("datacenter2", jsonObject, clusterMapConfig); assertEquals("The datacenter of the data node is in the ssl enabled datacenter list. SSL port should be returned", PortType.SSL, dataNode.getPortToConnectTo().getPortType()); dataNode = new TestDataNode("datacenter5", jsonObject, clusterMapConfig); assertEquals( "The datacenter of the data node is not in the ssl enabled datacenter list. Plaintext port should be returned", PortType.PLAINTEXT, dataNode.getPortToConnectTo().getPortType()); jsonObject.remove("sslport"); dataNode = new TestDataNode("datacenter1", jsonObject, clusterMapConfig); try { dataNode.getPortToConnectTo(); fail("Should have thrown Exception because there is no sslPort."); } catch (IllegalStateException e) { // The datacenter of the data node is in the ssl enabled datacenter list, but the data node does not have an ssl // port to connect. Exception should be thrown. } } void ensure(DataNode dataNode, HardwareState state) { assertEquals(dataNode.getState(), state); for (DiskId disk : dataNode.getDisks()) { assertEquals(disk.getState(), state); } } }