From 4781dc48b1cc6d49e703b60e5c04fcaa967438db Mon Sep 17 00:00:00 2001 From: Michael Plump Date: Wed, 26 Jun 2019 09:52:22 -0400 Subject: [PATCH] feat(gce): Add StatefullyUpdateBootDisk{Task,Stage} (#3007) * feat(gce): Add StatefullyUpdateBootDisk{Task,Stage} * style(google): add missing @Nonnull * style(gce): Fix copyright headers --- orca-clouddriver/orca-clouddriver.gradle | 1 + .../providers/gce/SetStatefulDiskStage.java | 23 ++--- .../gce/StatefullyUpdateBootImageStage.java | 57 +++++++++++ .../providers/gce/SetStatefulDiskTask.java | 23 ++--- .../gce/StatefullyUpdateBootImageTask.java | 95 +++++++++++++++++++ .../gce/SetStatefulDiskTaskTest.java | 49 +++++----- .../gce/StatefullyUpdateBootImageTest.java | 84 ++++++++++++++++ 7 files changed, 279 insertions(+), 53 deletions(-) create mode 100644 orca-clouddriver/src/main/groovy/com/netflix/spinnaker/orca/clouddriver/pipeline/providers/gce/StatefullyUpdateBootImageStage.java create mode 100644 orca-clouddriver/src/main/groovy/com/netflix/spinnaker/orca/clouddriver/tasks/providers/gce/StatefullyUpdateBootImageTask.java create mode 100644 orca-clouddriver/src/test/java/com/netflix/spinnaker/orca/clouddriver/tasks/providers/gce/StatefullyUpdateBootImageTest.java diff --git a/orca-clouddriver/orca-clouddriver.gradle b/orca-clouddriver/orca-clouddriver.gradle index 7bd12aefd3..c9c705cbb9 100644 --- a/orca-clouddriver/orca-clouddriver.gradle +++ b/orca-clouddriver/orca-clouddriver.gradle @@ -46,6 +46,7 @@ dependencies { testImplementation("org.junit.jupiter:junit-jupiter-api") testImplementation("org.assertj:assertj-core") testImplementation("org.mockito:mockito-core:2.25.0") + testImplementation("org.mockito:mockito-junit-jupiter") testImplementation("com.netflix.spinnaker.fiat:fiat-core:$fiatVersion") testRuntimeOnly("org.junit.jupiter:junit-jupiter-api") diff --git a/orca-clouddriver/src/main/groovy/com/netflix/spinnaker/orca/clouddriver/pipeline/providers/gce/SetStatefulDiskStage.java b/orca-clouddriver/src/main/groovy/com/netflix/spinnaker/orca/clouddriver/pipeline/providers/gce/SetStatefulDiskStage.java index a6d5b79f23..9942bc6829 100644 --- a/orca-clouddriver/src/main/groovy/com/netflix/spinnaker/orca/clouddriver/pipeline/providers/gce/SetStatefulDiskStage.java +++ b/orca-clouddriver/src/main/groovy/com/netflix/spinnaker/orca/clouddriver/pipeline/providers/gce/SetStatefulDiskStage.java @@ -1,20 +1,17 @@ /* + * Copyright 2019 Google, Inc. * - * * Copyright 2019 Google, Inc. - * * - * * 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. + * 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 com.netflix.spinnaker.orca.clouddriver.pipeline.providers.gce; diff --git a/orca-clouddriver/src/main/groovy/com/netflix/spinnaker/orca/clouddriver/pipeline/providers/gce/StatefullyUpdateBootImageStage.java b/orca-clouddriver/src/main/groovy/com/netflix/spinnaker/orca/clouddriver/pipeline/providers/gce/StatefullyUpdateBootImageStage.java new file mode 100644 index 0000000000..cfe1338533 --- /dev/null +++ b/orca-clouddriver/src/main/groovy/com/netflix/spinnaker/orca/clouddriver/pipeline/providers/gce/StatefullyUpdateBootImageStage.java @@ -0,0 +1,57 @@ +/* + * Copyright 2019 Google, Inc. + * + * 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 com.netflix.spinnaker.orca.clouddriver.pipeline.providers.gce; + +import com.netflix.spinnaker.kork.dynamicconfig.DynamicConfigService; +import com.netflix.spinnaker.orca.clouddriver.tasks.providers.gce.StatefullyUpdateBootImageTask; +import com.netflix.spinnaker.orca.clouddriver.tasks.servergroup.ServerGroupCacheForceRefreshTask; +import com.netflix.spinnaker.orca.pipeline.StageDefinitionBuilder; +import com.netflix.spinnaker.orca.pipeline.TaskNode; +import com.netflix.spinnaker.orca.pipeline.model.Stage; +import javax.annotation.Nonnull; +import lombok.Data; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +@Component +public final class StatefullyUpdateBootImageStage implements StageDefinitionBuilder { + + private final DynamicConfigService dynamicConfigService; + + @Autowired + StatefullyUpdateBootImageStage(DynamicConfigService dynamicConfigService) { + this.dynamicConfigService = dynamicConfigService; + } + + @Override + public void taskGraph(@Nonnull Stage stage, TaskNode.Builder builder) { + builder.withTask("statefullyUpdateBootDisk", StatefullyUpdateBootImageTask.class); + + if (isForceCacheRefreshEnabled(dynamicConfigService)) { + builder.withTask("forceCacheRefresh", ServerGroupCacheForceRefreshTask.class); + } + } + + @Data + public static class StageData { + + public String accountName; + public String serverGroupName; + public String region; + public String bootImage; + } +} diff --git a/orca-clouddriver/src/main/groovy/com/netflix/spinnaker/orca/clouddriver/tasks/providers/gce/SetStatefulDiskTask.java b/orca-clouddriver/src/main/groovy/com/netflix/spinnaker/orca/clouddriver/tasks/providers/gce/SetStatefulDiskTask.java index 733366de67..209be32da0 100644 --- a/orca-clouddriver/src/main/groovy/com/netflix/spinnaker/orca/clouddriver/tasks/providers/gce/SetStatefulDiskTask.java +++ b/orca-clouddriver/src/main/groovy/com/netflix/spinnaker/orca/clouddriver/tasks/providers/gce/SetStatefulDiskTask.java @@ -1,20 +1,17 @@ /* + * Copyright 2019 Google, Inc. * - * * Copyright 2019 Google, Inc. - * * - * * 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. + * 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 com.netflix.spinnaker.orca.clouddriver.tasks.providers.gce; diff --git a/orca-clouddriver/src/main/groovy/com/netflix/spinnaker/orca/clouddriver/tasks/providers/gce/StatefullyUpdateBootImageTask.java b/orca-clouddriver/src/main/groovy/com/netflix/spinnaker/orca/clouddriver/tasks/providers/gce/StatefullyUpdateBootImageTask.java new file mode 100644 index 0000000000..1fb6e9cf72 --- /dev/null +++ b/orca-clouddriver/src/main/groovy/com/netflix/spinnaker/orca/clouddriver/tasks/providers/gce/StatefullyUpdateBootImageTask.java @@ -0,0 +1,95 @@ +/* + * Copyright 2019 Google, Inc. + * + * 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 com.netflix.spinnaker.orca.clouddriver.tasks.providers.gce; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkState; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.netflix.spinnaker.orca.ExecutionStatus; +import com.netflix.spinnaker.orca.TaskResult; +import com.netflix.spinnaker.orca.clouddriver.KatoService; +import com.netflix.spinnaker.orca.clouddriver.model.TaskId; +import com.netflix.spinnaker.orca.clouddriver.pipeline.providers.gce.StatefullyUpdateBootImageStage.StageData; +import com.netflix.spinnaker.orca.clouddriver.pipeline.servergroup.support.TargetServerGroup; +import com.netflix.spinnaker.orca.clouddriver.pipeline.servergroup.support.TargetServerGroupResolver; +import com.netflix.spinnaker.orca.clouddriver.tasks.AbstractCloudProviderAwareTask; +import com.netflix.spinnaker.orca.pipeline.model.Stage; +import java.util.List; +import java.util.Map; +import javax.annotation.Nonnull; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import rx.Observable; + +@Component +public final class StatefullyUpdateBootImageTask extends AbstractCloudProviderAwareTask { + + private static final String KATO_OP_NAME = "statefullyUpdateBootImage"; + + private final KatoService katoService; + private final TargetServerGroupResolver resolver; + + @Autowired + StatefullyUpdateBootImageTask(KatoService katoService, TargetServerGroupResolver resolver) { + this.katoService = katoService; + this.resolver = resolver; + } + + @Nonnull + @Override + public TaskResult execute(@Nonnull Stage stage) { + StageData data = stage.mapTo(StageData.class); + + List resolvedServerGroups = resolver.resolve(stage); + checkArgument( + resolvedServerGroups.size() > 0, + "Could not find a server group named %s for %s in %s", + data.serverGroupName, + data.accountName, + data.region); + checkState( + resolvedServerGroups.size() == 1, + "Found multiple server groups named %s for %s in %s", + data.serverGroupName, + data.accountName, + data.region); + TargetServerGroup serverGroup = resolvedServerGroups.get(0); + + ImmutableMap opData = + ImmutableMap.of( + "credentials", getCredentials(stage), + "serverGroupName", serverGroup.getName(), + "region", data.getRegion(), + "bootImage", data.bootImage); + + Map operation = ImmutableMap.of(KATO_OP_NAME, opData); + Observable observable = + katoService.requestOperations(getCloudProvider(stage), ImmutableList.of(operation)); + observable.toBlocking().first(); + + ImmutableMap> modifiedServerGroups = + ImmutableMap.of(data.getRegion(), ImmutableList.of(serverGroup.getName())); + ImmutableMap context = + ImmutableMap.of( + "notification.type", KATO_OP_NAME.toLowerCase(), + "serverGroupName", serverGroup.getName(), + "deploy.server.groups", modifiedServerGroups); + return TaskResult.builder(ExecutionStatus.SUCCEEDED).context(context).build(); + } +} diff --git a/orca-clouddriver/src/test/java/com/netflix/spinnaker/orca/clouddriver/tasks/providers/gce/SetStatefulDiskTaskTest.java b/orca-clouddriver/src/test/java/com/netflix/spinnaker/orca/clouddriver/tasks/providers/gce/SetStatefulDiskTaskTest.java index 8464fc885b..5000c4c0a8 100644 --- a/orca-clouddriver/src/test/java/com/netflix/spinnaker/orca/clouddriver/tasks/providers/gce/SetStatefulDiskTaskTest.java +++ b/orca-clouddriver/src/test/java/com/netflix/spinnaker/orca/clouddriver/tasks/providers/gce/SetStatefulDiskTaskTest.java @@ -1,27 +1,23 @@ /* + * Copyright 2019 Google, Inc. * - * * Copyright 2019 Google, Inc. - * * - * * 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. + * 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 com.netflix.spinnaker.orca.clouddriver.tasks.providers.gce; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -33,29 +29,28 @@ import com.netflix.spinnaker.orca.clouddriver.pipeline.servergroup.support.TargetServerGroup; import com.netflix.spinnaker.orca.clouddriver.pipeline.servergroup.support.TargetServerGroupResolver; import com.netflix.spinnaker.orca.pipeline.model.Stage; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; import rx.Observable; -@RunWith(JUnit4.class) -public class SetStatefulDiskTaskTest { +@ExtendWith(MockitoExtension.class) +class SetStatefulDiskTaskTest { private SetStatefulDiskTask task; - private KatoService katoService; - private TargetServerGroupResolver resolver; + @Mock private KatoService katoService; + @Mock private TargetServerGroupResolver resolver; - @Before - public void setUp() { - katoService = mock(KatoService.class); - resolver = mock(TargetServerGroupResolver.class); + @BeforeEach + void setUp() { task = new SetStatefulDiskTask(katoService, resolver); } @Test - public void success() { + void success() { when(resolver.resolve(any())) .thenReturn( ImmutableList.of(new TargetServerGroup(ImmutableMap.of("name", "testapp-v000")))); diff --git a/orca-clouddriver/src/test/java/com/netflix/spinnaker/orca/clouddriver/tasks/providers/gce/StatefullyUpdateBootImageTest.java b/orca-clouddriver/src/test/java/com/netflix/spinnaker/orca/clouddriver/tasks/providers/gce/StatefullyUpdateBootImageTest.java new file mode 100644 index 0000000000..fa833f5790 --- /dev/null +++ b/orca-clouddriver/src/test/java/com/netflix/spinnaker/orca/clouddriver/tasks/providers/gce/StatefullyUpdateBootImageTest.java @@ -0,0 +1,84 @@ +/* + * Copyright 2019 Google, Inc. + * + * 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 com.netflix.spinnaker.orca.clouddriver.tasks.providers.gce; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.netflix.spinnaker.orca.TaskResult; +import com.netflix.spinnaker.orca.clouddriver.KatoService; +import com.netflix.spinnaker.orca.clouddriver.model.TaskId; +import com.netflix.spinnaker.orca.clouddriver.pipeline.servergroup.support.TargetServerGroup; +import com.netflix.spinnaker.orca.clouddriver.pipeline.servergroup.support.TargetServerGroupResolver; +import com.netflix.spinnaker.orca.pipeline.model.Stage; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import rx.Observable; + +@ExtendWith(MockitoExtension.class) +final class StatefullyUpdateBootImageTest { + + private StatefullyUpdateBootImageTask task; + + @Mock private KatoService katoService; + @Mock private TargetServerGroupResolver resolver; + + @BeforeEach + void setUp() { + task = new StatefullyUpdateBootImageTask(katoService, resolver); + } + + @Test + void success() { + when(resolver.resolve(any())) + .thenReturn( + ImmutableList.of(new TargetServerGroup(ImmutableMap.of("name", "testapp-v000")))); + when(katoService.requestOperations(any(), any())) + .thenReturn(Observable.just(new TaskId("10111"))); + + Stage stage = new Stage(); + stage.getContext().put("cloudProvider", "gce"); + stage.getContext().put("credentials", "spinnaker-test"); + stage.getContext().put("serverGroupName", "testapp-v000"); + stage.getContext().put("region", "us-desertoasis1"); + stage.getContext().put("bootImage", "new-kool-os"); + + TaskResult result = task.execute(stage); + + ImmutableMap operationParams = + ImmutableMap.of( + "credentials", "spinnaker-test", + "serverGroupName", "testapp-v000", + "region", "us-desertoasis1", + "bootImage", "new-kool-os"); + verify(katoService) + .requestOperations( + "gce", ImmutableList.of(ImmutableMap.of("statefullyUpdateBootImage", operationParams))); + + assertThat(result.getContext().get("notification.type")).isEqualTo("statefullyupdatebootimage"); + assertThat(result.getContext().get("serverGroupName")).isEqualTo("testapp-v000"); + assertThat(result.getContext().get("deploy.server.groups")) + .isEqualTo(ImmutableMap.of("us-desertoasis1", ImmutableList.of("testapp-v000"))); + } +}