From 5d4e4f20070c4a1d00608509123da5d5df688935 Mon Sep 17 00:00:00 2001 From: Guanghao Zhang Date: Fri, 7 Jul 2017 21:13:38 +0800 Subject: [PATCH] HBASE-18317 Implement async admin operations for Normalizer/CleanerChore/CatalogJanitor --- .../hadoop/hbase/client/AsyncAdmin.java | 131 ++++++++---- .../hadoop/hbase/client/AsyncHBaseAdmin.java | 75 +++++-- .../hbase/client/RawAsyncHBaseAdmin.java | 190 +++++++++++++++--- .../client/TestAsyncBalancerAdminApi.java | 54 ----- .../hbase/client/TestAsyncToolAdminApi.java | 127 ++++++++++++ 5 files changed, 441 insertions(+), 136 deletions(-) delete mode 100644 hbase-server/src/test/java/org/apache/hadoop/hbase/client/TestAsyncBalancerAdminApi.java create mode 100644 hbase-server/src/test/java/org/apache/hadoop/hbase/client/TestAsyncToolAdminApi.java diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/AsyncAdmin.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/AsyncAdmin.java index 8ade2095b8c..8411a5bafc1 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/AsyncAdmin.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/AsyncAdmin.java @@ -283,40 +283,6 @@ public interface AsyncAdmin { */ CompletableFuture> listNamespaceDescriptors(); - /** - * Turn the load balancer on or off. - * @param on - * @return Previous balancer value wrapped by a {@link CompletableFuture}. - */ - CompletableFuture setBalancerOn(boolean on); - - /** - * Invoke the balancer. Will run the balancer and if regions to move, it will go ahead and do the - * reassignments. Can NOT run for various reasons. Check logs. - * @return True if balancer ran, false otherwise. The return value will be wrapped by a - * {@link CompletableFuture}. - */ - default CompletableFuture balance() { - return balance(false); - } - - /** - * Invoke the balancer. Will run the balancer and if regions to move, it will go ahead and do the - * reassignments. If there is region in transition, force parameter of true would still run - * balancer. Can *not* run for other reasons. Check logs. - * @param forcible whether we should force balance even if there is region in transition. - * @return True if balancer ran, false otherwise. The return value will be wrapped by a - * {@link CompletableFuture}. - */ - CompletableFuture balance(boolean forcible); - - /** - * Query the current state of the balancer. - * @return true if the balance switch is on, false otherwise The return value will be wrapped by a - * {@link CompletableFuture}. - */ - CompletableFuture isBalancerOn(); - /** * Close a region. For expert-admins Runs close on the regionserver. The master will not be * informed of the close. @@ -891,4 +857,101 @@ public interface AsyncAdmin { * @return the last major compaction timestamp wrapped by a {@link CompletableFuture} */ CompletableFuture> getLastMajorCompactionTimestampForRegion(byte[] regionName); + + /** + * Turn the load balancer on or off. + * @param on + * @return Previous balancer value wrapped by a {@link CompletableFuture}. + */ + CompletableFuture setBalancerOn(boolean on); + + /** + * Invoke the balancer. Will run the balancer and if regions to move, it will go ahead and do the + * reassignments. Can NOT run for various reasons. Check logs. + * @return True if balancer ran, false otherwise. The return value will be wrapped by a + * {@link CompletableFuture}. + */ + default CompletableFuture balance() { + return balance(false); + } + + /** + * Invoke the balancer. Will run the balancer and if regions to move, it will go ahead and do the + * reassignments. If there is region in transition, force parameter of true would still run + * balancer. Can *not* run for other reasons. Check logs. + * @param forcible whether we should force balance even if there is region in transition. + * @return True if balancer ran, false otherwise. The return value will be wrapped by a + * {@link CompletableFuture}. + */ + CompletableFuture balance(boolean forcible); + + /** + * Query the current state of the balancer. + * @return true if the balance switch is on, false otherwise. The return value will be wrapped by a + * {@link CompletableFuture}. + */ + CompletableFuture isBalancerOn(); + + /** + * Set region normalizer on/off. + * @param on whether normalizer should be on or off + * @return Previous normalizer value wrapped by a {@link CompletableFuture} + */ + CompletableFuture setNormalizerOn(boolean on); + + /** + * Query the current state of the region normalizer + * @return true if region normalizer is on, false otherwise. The return value will be wrapped by a + * {@link CompletableFuture} + */ + CompletableFuture isNormalizerOn(); + + /** + * Invoke region normalizer. Can NOT run for various reasons. Check logs. + * @return true if region normalizer ran, false otherwise. The return value will be wrapped by a + * {@link CompletableFuture} + */ + CompletableFuture normalize(); + + /** + * Turn the cleaner chore on/off. + * @param on + * @return Previous cleaner state wrapped by a {@link CompletableFuture} + */ + CompletableFuture setCleanerChoreOn(boolean on); + + /** + * Query the current state of the cleaner chore. + * @return true if cleaner chore is on, false otherwise. The return value will be wrapped by + * a {@link CompletableFuture} + */ + CompletableFuture isCleanerChoreOn(); + + /** + * Ask for cleaner chore to run. + * @return true if cleaner chore ran, false otherwise. The return value will be wrapped by a + * {@link CompletableFuture} + */ + CompletableFuture runCleanerChore(); + + /** + * Turn the catalog janitor on/off. + * @param on + * @return the previous state wrapped by a {@link CompletableFuture} + */ + CompletableFuture setCatalogJanitorOn(boolean on); + + /** + * Query on the catalog janitor state. + * @return true if the catalog janitor is on, false otherwise. The return value will be + * wrapped by a {@link CompletableFuture} + */ + CompletableFuture isCatalogJanitorOn(); + + /** + * Ask for a scan of the catalog table. + * @return the number of entries cleaned. The return value will be wrapped by a + * {@link CompletableFuture} + */ + CompletableFuture runCatalogJanitor(); } diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/AsyncHBaseAdmin.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/AsyncHBaseAdmin.java index 2998133a0de..7c572db5dcb 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/AsyncHBaseAdmin.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/AsyncHBaseAdmin.java @@ -201,21 +201,6 @@ public class AsyncHBaseAdmin implements AsyncAdmin { return wrap(rawAdmin.listNamespaceDescriptors()); } - @Override - public CompletableFuture setBalancerOn(boolean on) { - return wrap(rawAdmin.setBalancerOn(on)); - } - - @Override - public CompletableFuture balance(boolean forcible) { - return wrap(rawAdmin.balance(forcible)); - } - - @Override - public CompletableFuture isBalancerOn() { - return wrap(rawAdmin.isBalancerOn()); - } - @Override public CompletableFuture closeRegion(byte[] regionName, Optional serverName) { return wrap(rawAdmin.closeRegion(regionName, serverName)); @@ -489,4 +474,64 @@ public class AsyncHBaseAdmin implements AsyncAdmin { byte[] regionName) { return wrap(rawAdmin.getLastMajorCompactionTimestampForRegion(regionName)); } + + @Override + public CompletableFuture setBalancerOn(boolean on) { + return wrap(rawAdmin.setBalancerOn(on)); + } + + @Override + public CompletableFuture balance(boolean forcible) { + return wrap(rawAdmin.balance(forcible)); + } + + @Override + public CompletableFuture isBalancerOn() { + return wrap(rawAdmin.isBalancerOn()); + } + + @Override + public CompletableFuture setNormalizerOn(boolean on) { + return wrap(rawAdmin.setNormalizerOn(on)); + } + + @Override + public CompletableFuture isNormalizerOn() { + return wrap(rawAdmin.isNormalizerOn()); + } + + @Override + public CompletableFuture normalize() { + return wrap(rawAdmin.normalize()); + } + + @Override + public CompletableFuture setCleanerChoreOn(boolean enabled) { + return wrap(rawAdmin.setCleanerChoreOn(enabled)); + } + + @Override + public CompletableFuture isCleanerChoreOn() { + return wrap(rawAdmin.isCleanerChoreOn()); + } + + @Override + public CompletableFuture runCleanerChore() { + return wrap(rawAdmin.runCleanerChore()); + } + + @Override + public CompletableFuture setCatalogJanitorOn(boolean enabled) { + return wrap(rawAdmin.setCatalogJanitorOn(enabled)); + } + + @Override + public CompletableFuture isCatalogJanitorOn() { + return wrap(rawAdmin.isCatalogJanitorOn()); + } + + @Override + public CompletableFuture runCatalogJanitor() { + return wrap(rawAdmin.runCatalogJanitor()); + } } diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/RawAsyncHBaseAdmin.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/RawAsyncHBaseAdmin.java index b1197540ca3..e8c15a53097 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/RawAsyncHBaseAdmin.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/RawAsyncHBaseAdmin.java @@ -116,6 +116,8 @@ import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.DeleteSnap import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.DeleteSnapshotResponse; import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.DisableTableRequest; import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.DisableTableResponse; +import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.EnableCatalogJanitorRequest; +import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.EnableCatalogJanitorResponse; import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.EnableTableRequest; import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.EnableTableResponse; import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.DeleteColumnRequest; @@ -142,8 +144,14 @@ import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.DeleteTabl import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.DeleteTableResponse; import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.IsBalancerEnabledRequest; import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.IsBalancerEnabledResponse; +import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.IsCatalogJanitorEnabledRequest; +import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.IsCatalogJanitorEnabledResponse; +import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.IsCleanerChoreEnabledRequest; +import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.IsCleanerChoreEnabledResponse; import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.IsInMaintenanceModeRequest; import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.IsInMaintenanceModeResponse; +import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.IsNormalizerEnabledRequest; +import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.IsNormalizerEnabledResponse; import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.IsProcedureDoneRequest; import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.IsProcedureDoneResponse; import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.IsSnapshotDoneRequest; @@ -164,12 +172,22 @@ import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.ModifyName import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.ModifyNamespaceResponse; import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.MoveRegionRequest; import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.MoveRegionResponse; +import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.NormalizeRequest; +import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.NormalizeResponse; import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.OfflineRegionRequest; import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.OfflineRegionResponse; import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.RestoreSnapshotRequest; import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.RestoreSnapshotResponse; +import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.RunCatalogScanRequest; +import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.RunCatalogScanResponse; +import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.RunCleanerChoreRequest; +import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.RunCleanerChoreResponse; import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.SetBalancerRunningRequest; import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.SetBalancerRunningResponse; +import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.SetCleanerChoreRunningRequest; +import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.SetCleanerChoreRunningResponse; +import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.SetNormalizerRunningRequest; +import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.SetNormalizerRunningResponse; import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.SetQuotaRequest; import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.SetQuotaResponse; import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.SnapshotRequest; @@ -673,39 +691,6 @@ public class RawAsyncHBaseAdmin implements AsyncAdmin { .toNamespaceDescriptorList(resp))).call(); } - @Override - public CompletableFuture setBalancerOn(final boolean on) { - return this - . newMasterCaller() - .action( - (controller, stub) -> this - . call(controller, - stub, RequestConverter.buildSetBalancerRunningRequest(on, true), - (s, c, req, done) -> s.setBalancerRunning(c, req, done), - (resp) -> resp.getPrevBalanceValue())).call(); - } - - @Override - public CompletableFuture balance(boolean forcible) { - return this - . newMasterCaller() - .action( - (controller, stub) -> this. call(controller, - stub, RequestConverter.buildBalanceRequest(forcible), - (s, c, req, done) -> s.balance(c, req, done), (resp) -> resp.getBalancerRan())).call(); - } - - @Override - public CompletableFuture isBalancerOn() { - return this - . newMasterCaller() - .action( - (controller, stub) -> this. call( - controller, stub, RequestConverter.buildIsBalancerEnabledRequest(), - (s, c, req, done) -> s.isBalancerEnabled(c, req, done), (resp) -> resp.getEnabled())) - .call(); - } - @Override public CompletableFuture closeRegion(byte[] regionName, Optional serverName) { CompletableFuture future = new CompletableFuture<>(); @@ -2486,4 +2471,143 @@ public class RawAsyncHBaseAdmin implements AsyncAdmin { }); return future; } + + @Override + public CompletableFuture setBalancerOn(final boolean on) { + return this + . newMasterCaller() + .action( + (controller, stub) -> this + . call(controller, + stub, RequestConverter.buildSetBalancerRunningRequest(on, true), + (s, c, req, done) -> s.setBalancerRunning(c, req, done), + (resp) -> resp.getPrevBalanceValue())).call(); + } + + @Override + public CompletableFuture balance(boolean forcible) { + return this + . newMasterCaller() + .action( + (controller, stub) -> this. call(controller, + stub, RequestConverter.buildBalanceRequest(forcible), + (s, c, req, done) -> s.balance(c, req, done), (resp) -> resp.getBalancerRan())).call(); + } + + @Override + public CompletableFuture isBalancerOn() { + return this + . newMasterCaller() + .action( + (controller, stub) -> this. call( + controller, stub, RequestConverter.buildIsBalancerEnabledRequest(), + (s, c, req, done) -> s.isBalancerEnabled(c, req, done), (resp) -> resp.getEnabled())) + .call(); + } + + @Override + public CompletableFuture setNormalizerOn(boolean on) { + return this + . newMasterCaller() + .action( + (controller, stub) -> this + . call( + controller, stub, RequestConverter.buildSetNormalizerRunningRequest(on), (s, c, + req, done) -> s.setNormalizerRunning(c, req, done), (resp) -> resp + .getPrevNormalizerValue())).call(); + } + + @Override + public CompletableFuture isNormalizerOn() { + return this + . newMasterCaller() + .action( + (controller, stub) -> this + . call(controller, + stub, RequestConverter.buildIsNormalizerEnabledRequest(), + (s, c, req, done) -> s.isNormalizerEnabled(c, req, done), + (resp) -> resp.getEnabled())).call(); + } + + @Override + public CompletableFuture normalize() { + return this + . newMasterCaller() + .action( + (controller, stub) -> this. call( + controller, stub, RequestConverter.buildNormalizeRequest(), + (s, c, req, done) -> s.normalize(c, req, done), (resp) -> resp.getNormalizerRan())) + .call(); + } + + @Override + public CompletableFuture setCleanerChoreOn(boolean enabled) { + return this + . newMasterCaller() + .action( + (controller, stub) -> this + . call( + controller, stub, RequestConverter.buildSetCleanerChoreRunningRequest(enabled), (s, + c, req, done) -> s.setCleanerChoreRunning(c, req, done), (resp) -> resp + .getPrevValue())).call(); + } + + @Override + public CompletableFuture isCleanerChoreOn() { + return this + . newMasterCaller() + .action( + (controller, stub) -> this + . call( + controller, stub, RequestConverter.buildIsCleanerChoreEnabledRequest(), (s, c, req, + done) -> s.isCleanerChoreEnabled(c, req, done), (resp) -> resp.getValue())) + .call(); + } + + @Override + public CompletableFuture runCleanerChore() { + return this + . newMasterCaller() + .action( + (controller, stub) -> this + . call(controller, stub, + RequestConverter.buildRunCleanerChoreRequest(), + (s, c, req, done) -> s.runCleanerChore(c, req, done), + (resp) -> resp.getCleanerChoreRan())).call(); + } + + @Override + public CompletableFuture setCatalogJanitorOn(boolean enabled) { + return this + . newMasterCaller() + .action( + (controller, stub) -> this + . call( + controller, stub, RequestConverter.buildEnableCatalogJanitorRequest(enabled), (s, + c, req, done) -> s.enableCatalogJanitor(c, req, done), (resp) -> resp + .getPrevValue())).call(); + } + + @Override + public CompletableFuture isCatalogJanitorOn() { + return this + . newMasterCaller() + .action( + (controller, stub) -> this + . call( + controller, stub, RequestConverter.buildIsCatalogJanitorEnabledRequest(), (s, c, + req, done) -> s.isCatalogJanitorEnabled(c, req, done), (resp) -> resp + .getValue())).call(); + } + + @Override + public CompletableFuture runCatalogJanitor() { + return this + . newMasterCaller() + .action( + (controller, stub) -> this. call( + controller, stub, RequestConverter.buildCatalogScanRequest(), + (s, c, req, done) -> s.runCatalogScan(c, req, done), (resp) -> resp.getScanResult())) + .call(); + } } \ No newline at end of file diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/client/TestAsyncBalancerAdminApi.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/client/TestAsyncBalancerAdminApi.java deleted file mode 100644 index 995e0aa33f7..00000000000 --- a/hbase-server/src/test/java/org/apache/hadoop/hbase/client/TestAsyncBalancerAdminApi.java +++ /dev/null @@ -1,54 +0,0 @@ -/** - * 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.hbase.client; - -import static org.junit.Assert.assertEquals; - -import org.apache.hadoop.hbase.testclassification.ClientTests; -import org.apache.hadoop.hbase.testclassification.MediumTests; -import org.junit.Test; -import org.junit.experimental.categories.Category; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; - -@RunWith(Parameterized.class) -@Category({ MediumTests.class, ClientTests.class }) -public class TestAsyncBalancerAdminApi extends TestAsyncAdminBase { - - @Test - public void testBalancer() throws Exception { - boolean initialState = admin.isBalancerOn().get(); - - // Start the balancer, wait for it. - boolean prevState = admin.setBalancerOn(!initialState).get(); - - // The previous state should be the original state we observed - assertEquals(initialState, prevState); - - // Current state should be opposite of the original - assertEquals(!initialState, admin.isBalancerOn().get()); - - // Reset it back to what it was - prevState = admin.setBalancerOn(initialState).get(); - - // The previous state should be the opposite of the initial state - assertEquals(!initialState, prevState); - // Current state should be the original state again - assertEquals(initialState, admin.isBalancerOn().get()); - } -} diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/client/TestAsyncToolAdminApi.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/client/TestAsyncToolAdminApi.java new file mode 100644 index 00000000000..a773188e331 --- /dev/null +++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/client/TestAsyncToolAdminApi.java @@ -0,0 +1,127 @@ +/** + * 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.hbase.client; + +import static org.junit.Assert.assertEquals; + +import org.apache.hadoop.hbase.testclassification.ClientTests; +import org.apache.hadoop.hbase.testclassification.MediumTests; +import org.junit.Test; +import org.junit.experimental.categories.Category; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +/** + * Test the admin operations for Balancer, Normalizer, CleanerChore, and CatalogJanitor. + */ +@RunWith(Parameterized.class) +@Category({ MediumTests.class, ClientTests.class }) +public class TestAsyncToolAdminApi extends TestAsyncAdminBase { + + @Test + public void testBalancer() throws Exception { + boolean initialState = admin.isBalancerOn().get(); + + // Start the balancer, wait for it. + boolean prevState = admin.setBalancerOn(!initialState).get(); + + // The previous state should be the original state we observed + assertEquals(initialState, prevState); + + // Current state should be opposite of the original + assertEquals(!initialState, admin.isBalancerOn().get()); + + // Reset it back to what it was + prevState = admin.setBalancerOn(initialState).get(); + + // The previous state should be the opposite of the initial state + assertEquals(!initialState, prevState); + + // Current state should be the original state again + assertEquals(initialState, admin.isBalancerOn().get()); + } + + @Test + public void testNormalizer() throws Exception { + boolean initialState = admin.isNormalizerOn().get(); + + // flip state + boolean prevState = admin.setNormalizerOn(!initialState).get(); + + // The previous state should be the original state we observed + assertEquals(initialState, prevState); + + // Current state should be opposite of the original + assertEquals(!initialState, admin.isNormalizerOn().get()); + + // Reset it back to what it was + prevState = admin.setNormalizerOn(initialState).get(); + + // The previous state should be the opposite of the initial state + assertEquals(!initialState, prevState); + + // Current state should be the original state again + assertEquals(initialState, admin.isNormalizerOn().get()); + } + + @Test + public void testCleanerChore() throws Exception { + boolean initialState = admin.isCleanerChoreOn().get(); + + // flip state + boolean prevState = admin.setCleanerChoreOn(!initialState).get(); + + // The previous state should be the original state we observed + assertEquals(initialState, prevState); + + // Current state should be opposite of the original + assertEquals(!initialState, admin.isCleanerChoreOn().get()); + + // Reset it back to what it was + prevState = admin.setCleanerChoreOn(initialState).get(); + + // The previous state should be the opposite of the initial state + assertEquals(!initialState, prevState); + + // Current state should be the original state again + assertEquals(initialState, admin.isCleanerChoreOn().get()); + } + + @Test + public void testCatalogJanitor() throws Exception { + boolean initialState = admin.isCatalogJanitorOn().get(); + + // flip state + boolean prevState = admin.setCatalogJanitorOn(!initialState).get(); + + // The previous state should be the original state we observed + assertEquals(initialState, prevState); + + // Current state should be opposite of the original + assertEquals(!initialState, admin.isCatalogJanitorOn().get()); + + // Reset it back to what it was + prevState = admin.setCatalogJanitorOn(initialState).get(); + + // The previous state should be the opposite of the initial state + assertEquals(!initialState, prevState); + + // Current state should be the original state again + assertEquals(initialState, admin.isCatalogJanitorOn().get()); + } +}