Add admin tasks for listing, setting, and deleting feature flags.

This commit is contained in:
Jon Chambers 2021-01-20 16:07:41 -05:00 committed by Jon Chambers
parent 90a938fe2b
commit c606c1664f
7 changed files with 216 additions and 0 deletions

View File

@ -145,12 +145,15 @@ import org.whispersystems.textsecuregcm.websocket.DeadLetterHandler;
import org.whispersystems.textsecuregcm.websocket.ProvisioningConnectListener;
import org.whispersystems.textsecuregcm.websocket.WebSocketAccountAuthenticator;
import org.whispersystems.textsecuregcm.workers.CertificateCommand;
import org.whispersystems.textsecuregcm.workers.DeleteFeatureFlagTask;
import org.whispersystems.textsecuregcm.workers.DeleteUserCommand;
import org.whispersystems.textsecuregcm.workers.DisableRequestLoggingTask;
import org.whispersystems.textsecuregcm.workers.EnableRequestLoggingTask;
import org.whispersystems.textsecuregcm.workers.GetRedisCommandStatsCommand;
import org.whispersystems.textsecuregcm.workers.GetRedisSlowlogCommand;
import org.whispersystems.textsecuregcm.workers.ListFeatureFlagsTask;
import org.whispersystems.textsecuregcm.workers.SetCrawlerAccelerationTask;
import org.whispersystems.textsecuregcm.workers.SetFeatureFlagTask;
import org.whispersystems.textsecuregcm.workers.VacuumCommand;
import org.whispersystems.textsecuregcm.workers.ZkParamsCommand;
import org.whispersystems.websocket.WebSocketResourceProviderFactory;
@ -460,6 +463,9 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
environment.admin().addTask(new EnableRequestLoggingTask());
environment.admin().addTask(new DisableRequestLoggingTask());
environment.admin().addTask(new SetCrawlerAccelerationTask(accountDatabaseCrawlerCache));
environment.admin().addTask(new ListFeatureFlagsTask(featureFlagsManager));
environment.admin().addTask(new SetFeatureFlagTask(featureFlagsManager));
environment.admin().addTask(new DeleteFeatureFlagTask(featureFlagsManager));
///

View File

@ -0,0 +1,31 @@
/*
* Copyright 2021 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.workers;
import io.dropwizard.servlets.tasks.Task;
import org.whispersystems.textsecuregcm.storage.FeatureFlagsManager;
import java.io.PrintWriter;
public abstract class AbstractFeatureFlagTask extends Task {
private final FeatureFlagsManager featureFlagsManager;
protected AbstractFeatureFlagTask(final String name, final FeatureFlagsManager featureFlagsManager) {
super(name);
this.featureFlagsManager = featureFlagsManager;
}
protected FeatureFlagsManager getFeatureFlagsManager() {
return featureFlagsManager;
}
protected void printFeatureFlags(final PrintWriter out) {
out.println("Feature flags:");
featureFlagsManager.getAllFlags().forEach((flag, active) -> out.println(flag + ": " + active));
}
}

View File

@ -0,0 +1,35 @@
/*
* Copyright 2021 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.workers;
import org.whispersystems.textsecuregcm.storage.FeatureFlagsManager;
import java.io.PrintWriter;
import java.util.Collections;
import java.util.List;
import java.util.Map;
public class DeleteFeatureFlagTask extends AbstractFeatureFlagTask {
public DeleteFeatureFlagTask(final FeatureFlagsManager featureFlagsManager) {
super("delete-feature-flag", featureFlagsManager);
}
@Override
public void execute(final Map<String, List<String>> parameters, final PrintWriter out) {
if (parameters.containsKey("flag")) {
for (final String flag : parameters.getOrDefault("flag", Collections.emptyList())) {
out.println("Deleting feature flag: " + flag);
getFeatureFlagsManager().deleteFeatureFlag(flag);
}
out.println();
printFeatureFlags(out);
} else {
out.println("Usage: delete-feature-flag?flag=FLAG_NAME[&flag=FLAG_NAME2&flag=...]");
}
}
}

View File

@ -0,0 +1,24 @@
/*
* Copyright 2021 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.workers;
import org.whispersystems.textsecuregcm.storage.FeatureFlagsManager;
import java.io.PrintWriter;
import java.util.List;
import java.util.Map;
public class ListFeatureFlagsTask extends AbstractFeatureFlagTask {
public ListFeatureFlagsTask(final FeatureFlagsManager featureFlagsManager) {
super("list-feature-flags", featureFlagsManager);
}
@Override
public void execute(final Map<String, List<String>> parameters, final PrintWriter out) {
printFeatureFlags(out);
}
}

View File

@ -0,0 +1,40 @@
/*
* Copyright 2021 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.workers;
import org.whispersystems.textsecuregcm.storage.FeatureFlagsManager;
import java.io.PrintWriter;
import java.util.List;
import java.util.Map;
import java.util.Optional;
public class SetFeatureFlagTask extends AbstractFeatureFlagTask {
public SetFeatureFlagTask(final FeatureFlagsManager featureFlagsManager) {
super("set-feature-flag", featureFlagsManager);
}
@Override
public void execute(final Map<String, List<String>> parameters, final PrintWriter out) {
final Optional<String> maybeFlag = Optional.ofNullable(parameters.get("flag"))
.flatMap(values -> values.stream().findFirst());
final Optional<Boolean> maybeActive = Optional.ofNullable(parameters.get("active"))
.flatMap(values -> values.stream().findFirst())
.map(Boolean::valueOf);
if (maybeFlag.isPresent() && maybeActive.isPresent()) {
getFeatureFlagsManager().setFeatureFlag(maybeFlag.get(), maybeActive.get());
out.format("Set %s to %s\n", maybeFlag.get(), maybeActive.get());
out.println();
printFeatureFlags(out);
} else {
out.println("Usage: set-feature-flag?flag=FLAG_NAME&value=[true|false]");
}
}
}

View File

@ -0,0 +1,40 @@
/*
* Copyright 2021 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.workers;
import org.junit.Before;
import org.junit.Test;
import org.whispersystems.textsecuregcm.storage.FeatureFlagsManager;
import java.io.PrintWriter;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
public class DeleteFeatureFlagTaskTest {
private FeatureFlagsManager featureFlagsManager;
@Before
public void setUp() {
featureFlagsManager = mock(FeatureFlagsManager.class);
when(featureFlagsManager.getAllFlags()).thenReturn(Collections.emptyMap());
}
@Test
public void testExecute() {
final DeleteFeatureFlagTask task = new DeleteFeatureFlagTask(featureFlagsManager);
task.execute(Map.of("flag", List.of("test-flag-1", "test-flag-2")), mock(PrintWriter.class));
verify(featureFlagsManager).deleteFeatureFlag("test-flag-1");
verify(featureFlagsManager).deleteFeatureFlag("test-flag-2");
}
}

View File

@ -0,0 +1,40 @@
/*
* Copyright 2021 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.workers;
import org.junit.Before;
import org.junit.Test;
import org.whispersystems.textsecuregcm.storage.FeatureFlagsManager;
import java.io.PrintWriter;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
public class SetFeatureFlagTaskTest {
private FeatureFlagsManager featureFlagsManager;
@Before
public void setUp() {
featureFlagsManager = mock(FeatureFlagsManager.class);
when(featureFlagsManager.getAllFlags()).thenReturn(Collections.emptyMap());
}
@Test
public void testExecute() {
final SetFeatureFlagTask task = new SetFeatureFlagTask(featureFlagsManager);
task.execute(Map.of("flag", List.of("test-flag"), "active", List.of("true")), mock(PrintWriter.class));
verify(featureFlagsManager).setFeatureFlag("test-flag", true);
}
}