From 66d98c8d663f9a999cc53afbc26744feeaa84bff Mon Sep 17 00:00:00 2001 From: shicong Date: Mon, 12 Nov 2018 07:59:00 -0800 Subject: [PATCH] Add SetNumInstancesCommand in Nomulus tool to adjust the number of instances for a given service and version at runtime. Note that this CL only supports the adjustment for a given service and version. I will add another functionality to let this command be able to detect all non-live versions automatically and apply the adjustment. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=221092001 --- java/google/registry/tools/RegistryTool.java | 1 + .../registry/tools/RegistryToolComponent.java | 1 + .../tools/SetNumInstancesCommand.java | 66 ++++++++++++ .../registry/util/AppEngineServiceUtils.java | 3 + .../util/AppEngineServiceUtilsImpl.java | 9 ++ .../tools/SetNumInstancesCommandTest.java | 101 ++++++++++++++++++ .../util/AppEngineServiceUtilsImplTest.java | 35 ++++++ 7 files changed, 216 insertions(+) create mode 100644 java/google/registry/tools/SetNumInstancesCommand.java create mode 100644 javatests/google/registry/tools/SetNumInstancesCommandTest.java diff --git a/java/google/registry/tools/RegistryTool.java b/java/google/registry/tools/RegistryTool.java index ac47090d9..0c1d57e5d 100644 --- a/java/google/registry/tools/RegistryTool.java +++ b/java/google/registry/tools/RegistryTool.java @@ -104,6 +104,7 @@ public final class RegistryTool { .put("resave_environment_entities", ResaveEnvironmentEntitiesCommand.class) .put("resave_epp_resource", ResaveEppResourceCommand.class) .put("send_escrow_report_to_icann", SendEscrowReportToIcannCommand.class) + .put("set_num_instances", SetNumInstancesCommand.class) .put("setup_ote", SetupOteCommand.class) .put("uniform_rapid_suspension", UniformRapidSuspensionCommand.class) .put("unlock_domain", UnlockDomainCommand.class) diff --git a/java/google/registry/tools/RegistryToolComponent.java b/java/google/registry/tools/RegistryToolComponent.java index 2c8363671..3b56dd4a9 100644 --- a/java/google/registry/tools/RegistryToolComponent.java +++ b/java/google/registry/tools/RegistryToolComponent.java @@ -98,6 +98,7 @@ interface RegistryToolComponent { void inject(PendingEscrowCommand command); void inject(RenewDomainCommand command); void inject(SendEscrowReportToIcannCommand command); + void inject(SetNumInstancesCommand command); void inject(SetupOteCommand command); void inject(UnlockDomainCommand command); void inject(UpdateCursorsCommand command); diff --git a/java/google/registry/tools/SetNumInstancesCommand.java b/java/google/registry/tools/SetNumInstancesCommand.java new file mode 100644 index 000000000..9e2ef091d --- /dev/null +++ b/java/google/registry/tools/SetNumInstancesCommand.java @@ -0,0 +1,66 @@ +// Copyright 2018 The Nomulus Authors. 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. +// See the License for the specific language governing permissions and +// limitations under the License. + +package google.registry.tools; + +import static com.google.common.base.Preconditions.checkArgument; + +import com.beust.jcommander.Parameter; +import com.beust.jcommander.Parameters; +import com.google.common.flogger.FluentLogger; +import google.registry.util.AppEngineServiceUtils; +import javax.inject.Inject; + +/** A command to set the number of instances for an App Engine service. */ +@Parameters( + separators = " =", + commandDescription = + "Set the number of instances for a given service and version. " + + "Note that this command only works for manual scaling service.") +final class SetNumInstancesCommand implements CommandWithRemoteApi { + + private static final FluentLogger logger = FluentLogger.forEnclosingClass(); + + @Parameter( + names = "--service", + description = "Name of the App Engine service, e.g., default.", + required = true) + private String service; + + @Parameter( + names = "--version", + description = "Name of the service's version, e.g., canary.", + required = true) + private String version; + + @Parameter( + names = "--numInstances", + description = "The new number of instances for the version.", + required = true) + private Long numInstances; + + @Inject AppEngineServiceUtils appEngineServiceUtils; + + @Override + public void run() throws Exception { + checkArgument(!service.isEmpty(), "Service must be specified"); + checkArgument(!version.isEmpty(), "Version must be specified"); + checkArgument(numInstances > 0, "Number of instances must be greater than zero"); + + appEngineServiceUtils.setNumInstances(service, version, numInstances); + logger.atInfo().log( + "Successfully set version %s of service %s to %d instances.", + version, service, numInstances); + } +} diff --git a/java/google/registry/util/AppEngineServiceUtils.java b/java/google/registry/util/AppEngineServiceUtils.java index 991cdcbca..b4aefcd08 100644 --- a/java/google/registry/util/AppEngineServiceUtils.java +++ b/java/google/registry/util/AppEngineServiceUtils.java @@ -37,4 +37,7 @@ public interface AppEngineServiceUtils { /** Returns a host name to use for the given service and version. */ String getVersionHostname(String service, String version); + + /** Set the number of instances at runtime for a given service and version. */ + void setNumInstances(String service, String version, long numInstances); } diff --git a/java/google/registry/util/AppEngineServiceUtilsImpl.java b/java/google/registry/util/AppEngineServiceUtilsImpl.java index 6c7073e57..71b700468 100644 --- a/java/google/registry/util/AppEngineServiceUtilsImpl.java +++ b/java/google/registry/util/AppEngineServiceUtilsImpl.java @@ -14,6 +14,7 @@ package google.registry.util; +import static com.google.common.base.Preconditions.checkArgument; import static google.registry.util.PreconditionsUtils.checkArgumentNotNull; import com.google.appengine.api.modules.ModulesService; @@ -52,6 +53,14 @@ public class AppEngineServiceUtilsImpl implements AppEngineServiceUtils { return modulesService.getVersionHostname(service, version); } + @Override + public void setNumInstances(String service, String version, long numInstances) { + checkArgumentNotNull(service, "Must specify the service"); + checkArgumentNotNull(version, "Must specify the version"); + checkArgument(numInstances > 0, "Number of instances must be greater than 0"); + modulesService.setNumInstances(service, version, numInstances); + } + /** Dagger module for AppEngineServiceUtils. */ @Module public abstract static class AppEngineServiceUtilsModule { diff --git a/javatests/google/registry/tools/SetNumInstancesCommandTest.java b/javatests/google/registry/tools/SetNumInstancesCommandTest.java new file mode 100644 index 000000000..543fe066a --- /dev/null +++ b/javatests/google/registry/tools/SetNumInstancesCommandTest.java @@ -0,0 +1,101 @@ +// Copyright 2018 The Nomulus Authors. 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. +// See the License for the specific language governing permissions and +// limitations under the License. + +package google.registry.tools; + +import static com.google.common.truth.Truth.assertThat; +import static google.registry.testing.JUnitBackports.assertThrows; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import com.beust.jcommander.ParameterException; +import google.registry.testing.InjectRule; +import google.registry.util.AppEngineServiceUtils; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.mockito.Mock; + +/** Unit tests for {@link SetNumInstancesCommand}. */ +public class SetNumInstancesCommandTest extends CommandTestCase { + + @Rule public final InjectRule inject = new InjectRule(); + + @Mock AppEngineServiceUtils appEngineServiceUtils; + + @Before + public void before() { + command = new SetNumInstancesCommand(); + command.appEngineServiceUtils = appEngineServiceUtils; + } + + @Test + public void test_missingService_throwsException() { + ParameterException thrown = + assertThrows( + ParameterException.class, () -> runCommand("--version=version", "--numInstances=5")); + assertThat(thrown).hasMessageThat().contains("The following option is required: --service"); + } + + @Test + public void test_emptyService_throwsException() { + IllegalArgumentException thrown = + assertThrows( + IllegalArgumentException.class, + () -> runCommand("--service=", "--version=version", "--numInstances=5")); + assertThat(thrown).hasMessageThat().contains("Service must be specified"); + } + + @Test + public void test_missingVersion_throwsException() { + ParameterException thrown = + assertThrows( + ParameterException.class, () -> runCommand("--service=service", "--numInstances=5")); + assertThat(thrown).hasMessageThat().contains("The following option is required: --version"); + } + + @Test + public void test_emptyVersion_throwsException() { + IllegalArgumentException thrown = + assertThrows( + IllegalArgumentException.class, + () -> runCommand("--service=service", "--version=", "--numInstances=5")); + assertThat(thrown).hasMessageThat().contains("Version must be specified"); + } + + @Test + public void test_missingNumInstances_throwsException() { + ParameterException thrown = + assertThrows( + ParameterException.class, () -> runCommand("--service=service", "--version=version")); + assertThat(thrown) + .hasMessageThat() + .contains("The following option is required: --numInstances"); + } + + @Test + public void test_invalidNumInstances_throwsException() { + IllegalArgumentException thrown = + assertThrows( + IllegalArgumentException.class, + () -> runCommand("--service=service", "--version=version", "--numInstances=-5")); + assertThat(thrown).hasMessageThat().contains("Number of instances must be greater than zero"); + } + + @Test + public void test_validParameters_succeeds() throws Exception { + runCommand("--service=service", "--version=version", "--numInstances=10"); + verify(appEngineServiceUtils, times(1)).setNumInstances("service", "version", 10L); + } +} diff --git a/javatests/google/registry/util/AppEngineServiceUtilsImplTest.java b/javatests/google/registry/util/AppEngineServiceUtilsImplTest.java index c8c00cb4b..067bae32a 100644 --- a/javatests/google/registry/util/AppEngineServiceUtilsImplTest.java +++ b/javatests/google/registry/util/AppEngineServiceUtilsImplTest.java @@ -19,6 +19,8 @@ import static google.registry.testing.JUnitBackports.assertThrows; import static org.mockito.Matchers.anyString; import static org.mockito.Matchers.eq; import static org.mockito.Matchers.isNull; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import com.google.appengine.api.modules.ModulesService; @@ -75,4 +77,37 @@ public class AppEngineServiceUtilsImplTest { () -> appEngineServiceUtils.getVersionHostname("servicename", null)); assertThat(thrown).hasMessageThat().isEqualTo("Must specify the version"); } + + @Test + public void test_setNumInstances_worksWithValidParameters() { + appEngineServiceUtils.setNumInstances("service", "version", 10L); + verify(modulesService, times(1)).setNumInstances("service", "version", 10L); + } + + @Test + public void test_setNumInstances_throwsWhenServiceIsNull() { + IllegalArgumentException thrown = + assertThrows( + IllegalArgumentException.class, + () -> appEngineServiceUtils.setNumInstances(null, "version", 10L)); + assertThat(thrown).hasMessageThat().isEqualTo("Must specify the service"); + } + + @Test + public void test_setNumInstances_throwsWhenVersionIsNull() { + IllegalArgumentException thrown = + assertThrows( + IllegalArgumentException.class, + () -> appEngineServiceUtils.setNumInstances("service", null, 10L)); + assertThat(thrown).hasMessageThat().isEqualTo("Must specify the version"); + } + + @Test + public void test_setNumInstances_throwsWhenNumInstancesIsInvalid() { + IllegalArgumentException thrown = + assertThrows( + IllegalArgumentException.class, + () -> appEngineServiceUtils.setNumInstances("service", "version", -10L)); + assertThat(thrown).hasMessageThat().isEqualTo("Number of instances must be greater than 0"); + } }