blob: 610c08a584e0e26267042fd4c8703d1596874734 [file] [log] [blame]
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.hadoop.conf;
import com.google.common.base.Optional;
import com.google.common.base.Supplier;
import com.google.common.collect.Lists;
import org.apache.hadoop.test.GenericTestUtils;
import org.apache.hadoop.util.Time;
import org.apache.hadoop.conf.ReconfigurationUtil.PropertyChange;
import org.junit.Test;
import org.junit.Before;
import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.*;
import static org.junit.Assert.assertEquals;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.spy;
import java.io.IOException;
import java.util.Collection;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeoutException;
public class TestReconfiguration {
private Configuration conf1;
private Configuration conf2;
private static final String PROP1 = "test.prop.one";
private static final String PROP2 = "test.prop.two";
private static final String PROP3 = "test.prop.three";
private static final String PROP4 = "test.prop.four";
private static final String PROP5 = "test.prop.five";
private static final String VAL1 = "val1";
private static final String VAL2 = "val2";
@Before
public void setUp () {
conf1 = new Configuration();
conf2 = new Configuration();
// set some test properties
conf1.set(PROP1, VAL1);
conf1.set(PROP2, VAL1);
conf1.set(PROP3, VAL1);
conf2.set(PROP1, VAL1); // same as conf1
conf2.set(PROP2, VAL2); // different value as conf1
// PROP3 not set in conf2
conf2.set(PROP4, VAL1); // not set in conf1
}
/**
* Test ReconfigurationUtil.getChangedProperties.
*/
@Test
public void testGetChangedProperties() {
Collection<ReconfigurationUtil.PropertyChange> changes =
ReconfigurationUtil.getChangedProperties(conf2, conf1);
assertTrue("expected 3 changed properties but got " + changes.size(),
changes.size() == 3);
boolean changeFound = false;
boolean unsetFound = false;
boolean setFound = false;
for (ReconfigurationUtil.PropertyChange c: changes) {
if (c.prop.equals(PROP2) && c.oldVal != null && c.oldVal.equals(VAL1) &&
c.newVal != null && c.newVal.equals(VAL2)) {
changeFound = true;
} else if (c.prop.equals(PROP3) && c.oldVal != null && c.oldVal.equals(VAL1) &&
c.newVal == null) {
unsetFound = true;
} else if (c.prop.equals(PROP4) && c.oldVal == null &&
c.newVal != null && c.newVal.equals(VAL1)) {
setFound = true;
}
}
assertTrue("not all changes have been applied",
changeFound && unsetFound && setFound);
}
/**
* a simple reconfigurable class
*/
public static class ReconfigurableDummy extends ReconfigurableBase
implements Runnable {
public volatile boolean running = true;
public ReconfigurableDummy(Configuration conf) {
super(conf);
}
@Override
protected Configuration getNewConf() {
return new Configuration();
}
@Override
public Collection<String> getReconfigurableProperties() {
return Arrays.asList(PROP1, PROP2, PROP4);
}
@Override
public synchronized String reconfigurePropertyImpl(
String property, String newVal) throws ReconfigurationException {
// do nothing
return newVal;
}
/**
* Run until PROP1 is no longer VAL1.
*/
@Override
public void run() {
while (running && getConf().get(PROP1).equals(VAL1)) {
try {
Thread.sleep(1);
} catch (InterruptedException ignore) {
// do nothing
}
}
}
}
/**
* Test reconfiguring a Reconfigurable.
*/
@Test
public void testReconfigure() {
ReconfigurableDummy dummy = new ReconfigurableDummy(conf1);
assertTrue(PROP1 + " set to wrong value ",
dummy.getConf().get(PROP1).equals(VAL1));
assertTrue(PROP2 + " set to wrong value ",
dummy.getConf().get(PROP2).equals(VAL1));
assertTrue(PROP3 + " set to wrong value ",
dummy.getConf().get(PROP3).equals(VAL1));
assertTrue(PROP4 + " set to wrong value ",
dummy.getConf().get(PROP4) == null);
assertTrue(PROP5 + " set to wrong value ",
dummy.getConf().get(PROP5) == null);
assertTrue(PROP1 + " should be reconfigurable ",
dummy.isPropertyReconfigurable(PROP1));
assertTrue(PROP2 + " should be reconfigurable ",
dummy.isPropertyReconfigurable(PROP2));
assertFalse(PROP3 + " should not be reconfigurable ",
dummy.isPropertyReconfigurable(PROP3));
assertTrue(PROP4 + " should be reconfigurable ",
dummy.isPropertyReconfigurable(PROP4));
assertFalse(PROP5 + " should not be reconfigurable ",
dummy.isPropertyReconfigurable(PROP5));
// change something to the same value as before
{
boolean exceptionCaught = false;
try {
dummy.reconfigureProperty(PROP1, VAL1);
assertTrue(PROP1 + " set to wrong value ",
dummy.getConf().get(PROP1).equals(VAL1));
} catch (ReconfigurationException e) {
exceptionCaught = true;
}
assertFalse("received unexpected exception",
exceptionCaught);
}
// change something to null
{
boolean exceptionCaught = false;
try {
dummy.reconfigureProperty(PROP1, null);
assertTrue(PROP1 + "set to wrong value ",
dummy.getConf().get(PROP1) == null);
} catch (ReconfigurationException e) {
exceptionCaught = true;
}
assertFalse("received unexpected exception",
exceptionCaught);
}
// change something to a different value than before
{
boolean exceptionCaught = false;
try {
dummy.reconfigureProperty(PROP1, VAL2);
assertTrue(PROP1 + "set to wrong value ",
dummy.getConf().get(PROP1).equals(VAL2));
} catch (ReconfigurationException e) {
exceptionCaught = true;
}
assertFalse("received unexpected exception",
exceptionCaught);
}
// set unset property to null
{
boolean exceptionCaught = false;
try {
dummy.reconfigureProperty(PROP4, null);
assertTrue(PROP4 + "set to wrong value ",
dummy.getConf().get(PROP4) == null);
} catch (ReconfigurationException e) {
exceptionCaught = true;
}
assertFalse("received unexpected exception",
exceptionCaught);
}
// set unset property
{
boolean exceptionCaught = false;
try {
dummy.reconfigureProperty(PROP4, VAL1);
assertTrue(PROP4 + "set to wrong value ",
dummy.getConf().get(PROP4).equals(VAL1));
} catch (ReconfigurationException e) {
exceptionCaught = true;
}
assertFalse("received unexpected exception",
exceptionCaught);
}
// try to set unset property to null (not reconfigurable)
{
boolean exceptionCaught = false;
try {
dummy.reconfigureProperty(PROP5, null);
} catch (ReconfigurationException e) {
exceptionCaught = true;
}
assertTrue("did not receive expected exception",
exceptionCaught);
}
// try to set unset property to value (not reconfigurable)
{
boolean exceptionCaught = false;
try {
dummy.reconfigureProperty(PROP5, VAL1);
} catch (ReconfigurationException e) {
exceptionCaught = true;
}
assertTrue("did not receive expected exception",
exceptionCaught);
}
// try to change property to value (not reconfigurable)
{
boolean exceptionCaught = false;
try {
dummy.reconfigureProperty(PROP3, VAL2);
} catch (ReconfigurationException e) {
exceptionCaught = true;
}
assertTrue("did not receive expected exception",
exceptionCaught);
}
// try to change property to null (not reconfigurable)
{
boolean exceptionCaught = false;
try {
dummy.reconfigureProperty(PROP3, null);
} catch (ReconfigurationException e) {
exceptionCaught = true;
}
assertTrue("did not receive expected exception",
exceptionCaught);
}
}
/**
* Test whether configuration changes are visible in another thread.
*/
@Test
public void testThread() throws ReconfigurationException {
ReconfigurableDummy dummy = new ReconfigurableDummy(conf1);
assertTrue(dummy.getConf().get(PROP1).equals(VAL1));
Thread dummyThread = new Thread(dummy);
dummyThread.start();
try {
Thread.sleep(500);
} catch (InterruptedException ignore) {
// do nothing
}
dummy.reconfigureProperty(PROP1, VAL2);
long endWait = Time.now() + 2000;
while (dummyThread.isAlive() && Time.now() < endWait) {
try {
Thread.sleep(50);
} catch (InterruptedException ignore) {
// do nothing
}
}
assertFalse("dummy thread should not be alive",
dummyThread.isAlive());
dummy.running = false;
try {
dummyThread.join();
} catch (InterruptedException ignore) {
// do nothing
}
assertTrue(PROP1 + " is set to wrong value",
dummy.getConf().get(PROP1).equals(VAL2));
}
private static class AsyncReconfigurableDummy extends ReconfigurableBase {
AsyncReconfigurableDummy(Configuration conf) {
super(conf);
}
@Override
protected Configuration getNewConf() {
return new Configuration();
}
final CountDownLatch latch = new CountDownLatch(1);
@Override
public Collection<String> getReconfigurableProperties() {
return Arrays.asList(PROP1, PROP2, PROP4);
}
@Override
public synchronized String reconfigurePropertyImpl(String property,
String newVal) throws ReconfigurationException {
try {
latch.await();
} catch (InterruptedException e) {
// Ignore
}
return newVal;
}
}
private static void waitAsyncReconfigureTaskFinish(ReconfigurableBase rb)
throws InterruptedException {
ReconfigurationTaskStatus status = null;
int count = 20;
while (count > 0) {
status = rb.getReconfigurationTaskStatus();
if (status.stopped()) {
break;
}
count--;
Thread.sleep(500);
}
assert(status.stopped());
}
@Test
public void testAsyncReconfigure()
throws ReconfigurationException, IOException, InterruptedException {
AsyncReconfigurableDummy dummy = spy(new AsyncReconfigurableDummy(conf1));
List<PropertyChange> changes = Lists.newArrayList();
changes.add(new PropertyChange("name1", "new1", "old1"));
changes.add(new PropertyChange("name2", "new2", "old2"));
changes.add(new PropertyChange("name3", "new3", "old3"));
doReturn(changes).when(dummy).getChangedProperties(
any(Configuration.class), any(Configuration.class));
doReturn(true).when(dummy).isPropertyReconfigurable(eq("name1"));
doReturn(false).when(dummy).isPropertyReconfigurable(eq("name2"));
doReturn(true).when(dummy).isPropertyReconfigurable(eq("name3"));
doReturn("dummy").when(dummy)
.reconfigurePropertyImpl(eq("name1"), anyString());
doReturn("dummy").when(dummy)
.reconfigurePropertyImpl(eq("name2"), anyString());
doThrow(new ReconfigurationException("NAME3", "NEW3", "OLD3",
new IOException("io exception")))
.when(dummy).reconfigurePropertyImpl(eq("name3"), anyString());
dummy.startReconfigurationTask();
waitAsyncReconfigureTaskFinish(dummy);
ReconfigurationTaskStatus status = dummy.getReconfigurationTaskStatus();
assertEquals(2, status.getStatus().size());
for (Map.Entry<PropertyChange, Optional<String>> result :
status.getStatus().entrySet()) {
PropertyChange change = result.getKey();
if (change.prop.equals("name1")) {
assertFalse(result.getValue().isPresent());
} else if (change.prop.equals("name2")) {
assertThat(result.getValue().get(),
containsString("Property name2 is not reconfigurable"));
} else if (change.prop.equals("name3")) {
assertThat(result.getValue().get(), containsString("io exception"));
} else {
fail("Unknown property: " + change.prop);
}
}
}
@Test(timeout=30000)
public void testStartReconfigurationFailureDueToExistingRunningTask()
throws InterruptedException, IOException {
AsyncReconfigurableDummy dummy = spy(new AsyncReconfigurableDummy(conf1));
List<PropertyChange> changes = Lists.newArrayList(
new PropertyChange(PROP1, "new1", "old1")
);
doReturn(changes).when(dummy).getChangedProperties(
any(Configuration.class), any(Configuration.class));
ReconfigurationTaskStatus status = dummy.getReconfigurationTaskStatus();
assertFalse(status.hasTask());
dummy.startReconfigurationTask();
status = dummy.getReconfigurationTaskStatus();
assertTrue(status.hasTask());
assertFalse(status.stopped());
// An active reconfiguration task is running.
try {
dummy.startReconfigurationTask();
fail("Expect to throw IOException.");
} catch (IOException e) {
GenericTestUtils.assertExceptionContains(
"Another reconfiguration task is running", e);
}
status = dummy.getReconfigurationTaskStatus();
assertTrue(status.hasTask());
assertFalse(status.stopped());
dummy.latch.countDown();
waitAsyncReconfigureTaskFinish(dummy);
status = dummy.getReconfigurationTaskStatus();
assertTrue(status.hasTask());
assertTrue(status.stopped());
// The first task has finished.
dummy.startReconfigurationTask();
waitAsyncReconfigureTaskFinish(dummy);
ReconfigurationTaskStatus status2 = dummy.getReconfigurationTaskStatus();
assertTrue(status2.getStartTime() >= status.getEndTime());
dummy.shutdownReconfigurationTask();
try {
dummy.startReconfigurationTask();
fail("Expect to throw IOException");
} catch (IOException e) {
GenericTestUtils.assertExceptionContains("The server is stopped", e);
}
}
/**
* Ensure that {@link ReconfigurableBase#reconfigureProperty} updates the
* parent's cached configuration on success.
* @throws IOException
*/
@Test (timeout=300000)
public void testConfIsUpdatedOnSuccess() throws ReconfigurationException {
final String property = "FOO";
final String value1 = "value1";
final String value2 = "value2";
final Configuration conf = new Configuration();
conf.set(property, value1);
final Configuration newConf = new Configuration();
newConf.set(property, value2);
final ReconfigurableBase reconfigurable = makeReconfigurable(
conf, newConf, Arrays.asList(property));
reconfigurable.reconfigureProperty(property, value2);
assertThat(reconfigurable.getConf().get(property), is(value2));
}
/**
* Ensure that {@link ReconfigurableBase#startReconfigurationTask} updates
* its parent's cached configuration on success.
* @throws IOException
*/
@Test (timeout=300000)
public void testConfIsUpdatedOnSuccessAsync() throws ReconfigurationException,
TimeoutException, InterruptedException, IOException {
final String property = "FOO";
final String value1 = "value1";
final String value2 = "value2";
final Configuration conf = new Configuration();
conf.set(property, value1);
final Configuration newConf = new Configuration();
newConf.set(property, value2);
final ReconfigurableBase reconfigurable = makeReconfigurable(
conf, newConf, Arrays.asList(property));
// Kick off a reconfiguration task and wait until it completes.
reconfigurable.startReconfigurationTask();
GenericTestUtils.waitFor(new Supplier<Boolean>() {
@Override
public Boolean get() {
return reconfigurable.getReconfigurationTaskStatus().stopped();
}
}, 100, 60000);
assertThat(reconfigurable.getConf().get(property), is(value2));
}
/**
* Ensure that {@link ReconfigurableBase#reconfigureProperty} unsets the
* property in its parent's configuration when the new value is null.
* @throws IOException
*/
@Test (timeout=300000)
public void testConfIsUnset() throws ReconfigurationException {
final String property = "FOO";
final String value1 = "value1";
final Configuration conf = new Configuration();
conf.set(property, value1);
final Configuration newConf = new Configuration();
final ReconfigurableBase reconfigurable = makeReconfigurable(
conf, newConf, Arrays.asList(property));
reconfigurable.reconfigureProperty(property, null);
assertNull(reconfigurable.getConf().get(property));
}
/**
* Ensure that {@link ReconfigurableBase#startReconfigurationTask} unsets the
* property in its parent's configuration when the new value is null.
* @throws IOException
*/
@Test (timeout=300000)
public void testConfIsUnsetAsync() throws ReconfigurationException,
IOException, TimeoutException, InterruptedException {
final String property = "FOO";
final String value1 = "value1";
final Configuration conf = new Configuration();
conf.set(property, value1);
final Configuration newConf = new Configuration();
final ReconfigurableBase reconfigurable = makeReconfigurable(
conf, newConf, Arrays.asList(property));
// Kick off a reconfiguration task and wait until it completes.
reconfigurable.startReconfigurationTask();
GenericTestUtils.waitFor(new Supplier<Boolean>() {
@Override
public Boolean get() {
return reconfigurable.getReconfigurationTaskStatus().stopped();
}
}, 100, 60000);
assertNull(reconfigurable.getConf().get(property));
}
private ReconfigurableBase makeReconfigurable(
final Configuration oldConf, final Configuration newConf,
final Collection<String> reconfigurableProperties) {
return new ReconfigurableBase(oldConf) {
@Override
protected Configuration getNewConf() {
return newConf;
}
@Override
public Collection<String> getReconfigurableProperties() {
return reconfigurableProperties;
}
@Override
protected String reconfigurePropertyImpl(
String property, String newVal) throws ReconfigurationException {
return newVal;
}
};
}
}