From 8d339356375404e0f449875727aa237cf4ef4ee4 Mon Sep 17 00:00:00 2001 From: Michael Plump Date: Mon, 17 Jun 2019 13:54:26 -0400 Subject: [PATCH] refactor(gce): Add a wrapper around compute.instanceTemplates() (#3790) * refactor(gce): introduce a wrapper around compute.instanceTemplates() * refactor(gce): Refactor the GCE client wrapper to not execute requests immediately This will allow for batch requests (in a future commit) * test(gce): Add tests for the two GoogleServerGroupManagers implementations. * Add copyright headers to the clouddriver/google/compute/* files. --- .../AbstractGoogleServerGroupManagers.java | 83 +++--- ...tory.java => GoogleComputeApiFactory.java} | 27 +- .../GoogleComputeOperationRequest.java | 26 ++ .../GoogleComputeOperationRequestImpl.java | 50 ++++ .../google/compute/GoogleComputeRequest.java | 24 ++ .../compute/GoogleComputeRequestImpl.java | 72 +++++ .../compute/GoogleServerGroupManagers.java | 24 +- .../GoogleServerGroupOperationPoller.java | 20 -- .../google/compute/InstanceTemplates.java | 91 ++++++ .../RegionGoogleServerGroupManagers.java | 47 ++- .../google/compute/RegionalOperation.java | 36 --- .../compute/WaitableComputeOperation.java | 9 - .../google/compute/ZonalOperation.java | 36 --- .../ZoneGoogleServerGroupManagers.java | 47 ++- ...tStatefulDiskAtomicOperationConverter.java | 11 +- ...entGoogleServerGroupAtomicOperation.groovy | 8 +- ...royGoogleServerGroupAtomicOperation.groovy | 8 +- .../ops/SetStatefulDiskAtomicOperation.java | 23 +- .../ComputeOperationMockHttpTransport.java | 45 +++ .../google/compute/InstanceTemplatesTest.java | 224 ++++++++++++++ .../RegionGoogleServerGroupManagersTest.java | 273 ++++++++++++++++++ .../google/compute/SteppingClock.java | 42 +++ .../ZoneGoogleServerGroupManagersTest.java | 273 ++++++++++++++++++ ...tefulDiskAtomicOperationConverterTest.java | 5 +- ...eServerGroupAtomicOperationUnitSpec.groovy | 4 +- ...eServerGroupAtomicOperationUnitSpec.groovy | 8 +- ...StatefulDiskAtomicOperationUnitSpec.groovy | 23 +- 27 files changed, 1326 insertions(+), 213 deletions(-) rename clouddriver-google/src/main/groovy/com/netflix/spinnaker/clouddriver/google/compute/{GoogleServerGroupManagersFactory.java => GoogleComputeApiFactory.java} (52%) create mode 100644 clouddriver-google/src/main/groovy/com/netflix/spinnaker/clouddriver/google/compute/GoogleComputeOperationRequest.java create mode 100644 clouddriver-google/src/main/groovy/com/netflix/spinnaker/clouddriver/google/compute/GoogleComputeOperationRequestImpl.java create mode 100644 clouddriver-google/src/main/groovy/com/netflix/spinnaker/clouddriver/google/compute/GoogleComputeRequest.java create mode 100644 clouddriver-google/src/main/groovy/com/netflix/spinnaker/clouddriver/google/compute/GoogleComputeRequestImpl.java delete mode 100644 clouddriver-google/src/main/groovy/com/netflix/spinnaker/clouddriver/google/compute/GoogleServerGroupOperationPoller.java create mode 100644 clouddriver-google/src/main/groovy/com/netflix/spinnaker/clouddriver/google/compute/InstanceTemplates.java delete mode 100644 clouddriver-google/src/main/groovy/com/netflix/spinnaker/clouddriver/google/compute/RegionalOperation.java delete mode 100644 clouddriver-google/src/main/groovy/com/netflix/spinnaker/clouddriver/google/compute/WaitableComputeOperation.java delete mode 100644 clouddriver-google/src/main/groovy/com/netflix/spinnaker/clouddriver/google/compute/ZonalOperation.java create mode 100644 clouddriver-google/src/test/groovy/com/netflix/spinnaker/clouddriver/google/compute/ComputeOperationMockHttpTransport.java create mode 100644 clouddriver-google/src/test/groovy/com/netflix/spinnaker/clouddriver/google/compute/InstanceTemplatesTest.java create mode 100644 clouddriver-google/src/test/groovy/com/netflix/spinnaker/clouddriver/google/compute/RegionGoogleServerGroupManagersTest.java create mode 100644 clouddriver-google/src/test/groovy/com/netflix/spinnaker/clouddriver/google/compute/SteppingClock.java create mode 100644 clouddriver-google/src/test/groovy/com/netflix/spinnaker/clouddriver/google/compute/ZoneGoogleServerGroupManagersTest.java diff --git a/clouddriver-google/src/main/groovy/com/netflix/spinnaker/clouddriver/google/compute/AbstractGoogleServerGroupManagers.java b/clouddriver-google/src/main/groovy/com/netflix/spinnaker/clouddriver/google/compute/AbstractGoogleServerGroupManagers.java index 24012aeb17b..0e6d7ea5bc2 100644 --- a/clouddriver-google/src/main/groovy/com/netflix/spinnaker/clouddriver/google/compute/AbstractGoogleServerGroupManagers.java +++ b/clouddriver-google/src/main/groovy/com/netflix/spinnaker/clouddriver/google/compute/AbstractGoogleServerGroupManagers.java @@ -1,84 +1,99 @@ +/* + * 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.clouddriver.google.compute; -import com.google.api.client.googleapis.services.AbstractGoogleClientRequest; import com.google.api.services.compute.ComputeRequest; import com.google.api.services.compute.model.InstanceGroupManager; import com.google.api.services.compute.model.Operation; -import com.google.common.collect.ImmutableList; import com.netflix.spectator.api.Registry; -import com.netflix.spinnaker.clouddriver.google.GoogleExecutor; -import com.netflix.spinnaker.clouddriver.google.security.AccountForClient; +import com.netflix.spinnaker.clouddriver.google.compute.GoogleComputeOperationRequestImpl.OperationWaiter; +import com.netflix.spinnaker.clouddriver.google.deploy.GoogleOperationPoller; import com.netflix.spinnaker.clouddriver.google.security.GoogleNamedAccountCredentials; import java.io.IOException; import java.util.List; +import java.util.Map; -public abstract class AbstractGoogleServerGroupManagers implements GoogleServerGroupManagers { +abstract class AbstractGoogleServerGroupManagers implements GoogleServerGroupManagers { private final GoogleNamedAccountCredentials credentials; + private final GoogleOperationPoller poller; private final Registry registry; private final String instanceGroupName; AbstractGoogleServerGroupManagers( - GoogleNamedAccountCredentials credentials, Registry registry, String instanceGroupName) { + GoogleNamedAccountCredentials credentials, + GoogleOperationPoller poller, + Registry registry, + String instanceGroupName) { this.credentials = credentials; + this.poller = poller; this.registry = registry; this.instanceGroupName = instanceGroupName; } @Override - public WaitableComputeOperation abandonInstances(List instances) throws IOException { - return wrapOperation(timeExecute(performAbandonInstances(instances), "abandonInstances")); + public GoogleComputeOperationRequest abandonInstances(List instances) throws IOException { + return wrapOperationRequest(performAbandonInstances(instances), "abandonInstances"); } abstract ComputeRequest performAbandonInstances(List instances) throws IOException; @Override - public WaitableComputeOperation delete() throws IOException { - return wrapOperation(timeExecute(performDelete(), "delete")); + public GoogleComputeOperationRequest delete() throws IOException { + return wrapOperationRequest(performDelete(), "delete"); } abstract ComputeRequest performDelete() throws IOException; @Override - public InstanceGroupManager get() throws IOException { - return timeExecute(performGet(), "get"); + public GoogleComputeRequest get() throws IOException { + return wrapRequest(performGet(), "get"); } abstract ComputeRequest performGet() throws IOException; @Override - public WaitableComputeOperation update(InstanceGroupManager content) throws IOException { - return wrapOperation(timeExecute(performUpdate(content), "update")); + public GoogleComputeOperationRequest update(InstanceGroupManager content) throws IOException { + return wrapOperationRequest(performUpdate(content), "update"); } abstract ComputeRequest performUpdate(InstanceGroupManager content) throws IOException; - abstract WaitableComputeOperation wrapOperation(Operation operation); - - private T timeExecute(AbstractGoogleClientRequest request, String api) throws IOException { - return GoogleExecutor.timeExecute( - registry, - request, - "google.api", - String.format("compute.%s.%s", getManagersType(), api), - getTimeExecuteTags(request)); + private GoogleComputeRequest wrapRequest(ComputeRequest request, String api) { + return new GoogleComputeRequestImpl<>( + request, registry, getMetricName(api), getRegionOrZoneTags()); } - private String[] getTimeExecuteTags(AbstractGoogleClientRequest request) { - String account = AccountForClient.getAccount(request.getAbstractGoogleClient()); - return ImmutableList.builder() - .add("account") - .add(account) - .addAll(getRegionOrZoneTags()) - .build() - .toArray(new String[] {}); + private GoogleComputeOperationRequest wrapOperationRequest( + ComputeRequest request, String api) { + + OperationWaiter waiter = getOperationWaiter(credentials, poller); + return new GoogleComputeOperationRequestImpl( + request, registry, getMetricName(api), getRegionOrZoneTags(), waiter); } - GoogleNamedAccountCredentials getCredentials() { - return credentials; + private String getMetricName(String api) { + return String.format("compute.%s.%s", getManagersType(), api); } + abstract OperationWaiter getOperationWaiter( + GoogleNamedAccountCredentials credentials, GoogleOperationPoller poller); + String getProject() { return credentials.getProject(); } @@ -89,5 +104,5 @@ String getInstanceGroupName() { abstract String getManagersType(); - abstract List getRegionOrZoneTags(); + abstract Map getRegionOrZoneTags(); } diff --git a/clouddriver-google/src/main/groovy/com/netflix/spinnaker/clouddriver/google/compute/GoogleServerGroupManagersFactory.java b/clouddriver-google/src/main/groovy/com/netflix/spinnaker/clouddriver/google/compute/GoogleComputeApiFactory.java similarity index 52% rename from clouddriver-google/src/main/groovy/com/netflix/spinnaker/clouddriver/google/compute/GoogleServerGroupManagersFactory.java rename to clouddriver-google/src/main/groovy/com/netflix/spinnaker/clouddriver/google/compute/GoogleComputeApiFactory.java index aae921077e4..232e5d84281 100644 --- a/clouddriver-google/src/main/groovy/com/netflix/spinnaker/clouddriver/google/compute/GoogleServerGroupManagersFactory.java +++ b/clouddriver-google/src/main/groovy/com/netflix/spinnaker/clouddriver/google/compute/GoogleComputeApiFactory.java @@ -1,3 +1,19 @@ +/* + * 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.clouddriver.google.compute; import com.netflix.spectator.api.Registry; @@ -8,19 +24,18 @@ import org.springframework.stereotype.Service; @Service -public class GoogleServerGroupManagersFactory { +public class GoogleComputeApiFactory { private final GoogleOperationPoller operationPoller; private final Registry registry; @Autowired - public GoogleServerGroupManagersFactory( - GoogleOperationPoller operationPoller, Registry registry) { + public GoogleComputeApiFactory(GoogleOperationPoller operationPoller, Registry registry) { this.operationPoller = operationPoller; this.registry = registry; } - public GoogleServerGroupManagers getManagers( + public GoogleServerGroupManagers createServerGroupManagers( GoogleNamedAccountCredentials credentials, GoogleServerGroup.View serverGroup) { return serverGroup.getRegional() ? new RegionGoogleServerGroupManagers( @@ -28,4 +43,8 @@ public GoogleServerGroupManagers getManagers( : new ZoneGoogleServerGroupManagers( credentials, operationPoller, registry, serverGroup.getName(), serverGroup.getZone()); } + + public InstanceTemplates createInstanceTemplates(GoogleNamedAccountCredentials credentials) { + return new InstanceTemplates(credentials, operationPoller, registry); + } } diff --git a/clouddriver-google/src/main/groovy/com/netflix/spinnaker/clouddriver/google/compute/GoogleComputeOperationRequest.java b/clouddriver-google/src/main/groovy/com/netflix/spinnaker/clouddriver/google/compute/GoogleComputeOperationRequest.java new file mode 100644 index 00000000000..e0036db2fe1 --- /dev/null +++ b/clouddriver-google/src/main/groovy/com/netflix/spinnaker/clouddriver/google/compute/GoogleComputeOperationRequest.java @@ -0,0 +1,26 @@ +/* + * 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.clouddriver.google.compute; + +import com.google.api.services.compute.model.Operation; +import com.netflix.spinnaker.clouddriver.data.task.Task; +import java.io.IOException; + +public interface GoogleComputeOperationRequest extends GoogleComputeRequest { + + Operation executeAndWait(Task task, String phase) throws IOException; +} diff --git a/clouddriver-google/src/main/groovy/com/netflix/spinnaker/clouddriver/google/compute/GoogleComputeOperationRequestImpl.java b/clouddriver-google/src/main/groovy/com/netflix/spinnaker/clouddriver/google/compute/GoogleComputeOperationRequestImpl.java new file mode 100644 index 00000000000..448e1dc8b41 --- /dev/null +++ b/clouddriver-google/src/main/groovy/com/netflix/spinnaker/clouddriver/google/compute/GoogleComputeOperationRequestImpl.java @@ -0,0 +1,50 @@ +/* + * 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.clouddriver.google.compute; + +import com.google.api.services.compute.ComputeRequest; +import com.google.api.services.compute.model.Operation; +import com.netflix.spectator.api.Registry; +import com.netflix.spinnaker.clouddriver.data.task.Task; +import java.io.IOException; +import java.util.Map; + +final class GoogleComputeOperationRequestImpl extends GoogleComputeRequestImpl + implements GoogleComputeOperationRequest { + + @FunctionalInterface + interface OperationWaiter { + Operation wait(Operation operation, Task task, String phase); + } + + private final OperationWaiter operationWaiter; + + GoogleComputeOperationRequestImpl( + ComputeRequest request, + Registry registry, + String metricName, + Map tags, + OperationWaiter operationWaiter) { + super(request, registry, metricName, tags); + this.operationWaiter = operationWaiter; + } + + @Override + public Operation executeAndWait(Task task, String phase) throws IOException { + return operationWaiter.wait(execute(), task, phase); + } +} diff --git a/clouddriver-google/src/main/groovy/com/netflix/spinnaker/clouddriver/google/compute/GoogleComputeRequest.java b/clouddriver-google/src/main/groovy/com/netflix/spinnaker/clouddriver/google/compute/GoogleComputeRequest.java new file mode 100644 index 00000000000..31089389b25 --- /dev/null +++ b/clouddriver-google/src/main/groovy/com/netflix/spinnaker/clouddriver/google/compute/GoogleComputeRequest.java @@ -0,0 +1,24 @@ +/* + * 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.clouddriver.google.compute; + +import java.io.IOException; + +public interface GoogleComputeRequest { + + T execute() throws IOException; +} diff --git a/clouddriver-google/src/main/groovy/com/netflix/spinnaker/clouddriver/google/compute/GoogleComputeRequestImpl.java b/clouddriver-google/src/main/groovy/com/netflix/spinnaker/clouddriver/google/compute/GoogleComputeRequestImpl.java new file mode 100644 index 00000000000..0e579f0fff5 --- /dev/null +++ b/clouddriver-google/src/main/groovy/com/netflix/spinnaker/clouddriver/google/compute/GoogleComputeRequestImpl.java @@ -0,0 +1,72 @@ +/* + * 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.clouddriver.google.compute; + +import static java.util.stream.Collectors.toList; + +import com.google.api.client.googleapis.services.AbstractGoogleClientRequest; +import com.google.api.services.compute.ComputeRequest; +import com.google.common.collect.ImmutableList; +import com.netflix.spectator.api.Registry; +import com.netflix.spinnaker.clouddriver.google.GoogleExecutor; +import com.netflix.spinnaker.clouddriver.google.security.AccountForClient; +import java.io.IOException; +import java.util.List; +import java.util.Map; +import java.util.stream.Stream; + +class GoogleComputeRequestImpl implements GoogleComputeRequest { + + private final ComputeRequest request; + private final Registry registry; + private final String metricName; + private final Map tags; + + GoogleComputeRequestImpl( + ComputeRequest request, Registry registry, String metricName, Map tags) { + this.request = request; + this.registry = registry; + this.metricName = metricName; + this.tags = tags; + } + + @Override + public T execute() throws IOException { + return timeExecute(request); + } + + private T timeExecute(AbstractGoogleClientRequest request) throws IOException { + return GoogleExecutor.timeExecute( + registry, request, "google.api", metricName, getTimeExecuteTags(request)); + } + + private String[] getTimeExecuteTags(AbstractGoogleClientRequest request) { + String account = AccountForClient.getAccount(request.getAbstractGoogleClient()); + return ImmutableList.builder() + .add("account") + .add(account) + .addAll(flattenTags()) + .build() + .toArray(new String[] {}); + } + + private List flattenTags() { + return tags.entrySet().stream() + .flatMap(e -> Stream.of(e.getKey(), e.getValue())) + .collect(toList()); + } +} diff --git a/clouddriver-google/src/main/groovy/com/netflix/spinnaker/clouddriver/google/compute/GoogleServerGroupManagers.java b/clouddriver-google/src/main/groovy/com/netflix/spinnaker/clouddriver/google/compute/GoogleServerGroupManagers.java index c3f31cfc13a..460ccd7d625 100644 --- a/clouddriver-google/src/main/groovy/com/netflix/spinnaker/clouddriver/google/compute/GoogleServerGroupManagers.java +++ b/clouddriver-google/src/main/groovy/com/netflix/spinnaker/clouddriver/google/compute/GoogleServerGroupManagers.java @@ -1,3 +1,19 @@ +/* + * 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.clouddriver.google.compute; import com.google.api.services.compute.Compute.InstanceGroupManagers; @@ -13,11 +29,11 @@ */ public interface GoogleServerGroupManagers { - WaitableComputeOperation abandonInstances(List instances) throws IOException; + GoogleComputeOperationRequest abandonInstances(List instances) throws IOException; - WaitableComputeOperation delete() throws IOException; + GoogleComputeOperationRequest delete() throws IOException; - InstanceGroupManager get() throws IOException; + GoogleComputeRequest get() throws IOException; - WaitableComputeOperation update(InstanceGroupManager content) throws IOException; + GoogleComputeOperationRequest update(InstanceGroupManager content) throws IOException; } diff --git a/clouddriver-google/src/main/groovy/com/netflix/spinnaker/clouddriver/google/compute/GoogleServerGroupOperationPoller.java b/clouddriver-google/src/main/groovy/com/netflix/spinnaker/clouddriver/google/compute/GoogleServerGroupOperationPoller.java deleted file mode 100644 index 01cf9b6ef25..00000000000 --- a/clouddriver-google/src/main/groovy/com/netflix/spinnaker/clouddriver/google/compute/GoogleServerGroupOperationPoller.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.netflix.spinnaker.clouddriver.google.compute; - -import com.google.api.services.compute.model.Operation; -import com.netflix.spinnaker.clouddriver.data.task.Task; -import com.netflix.spinnaker.clouddriver.google.deploy.GoogleOperationPoller; - -public abstract class GoogleServerGroupOperationPoller { - - private final GoogleOperationPoller poller; - - GoogleServerGroupOperationPoller(GoogleOperationPoller poller) { - this.poller = poller; - } - - public abstract void waitForOperation(Operation operation, Long timeout, Task task, String phase); - - GoogleOperationPoller getPoller() { - return poller; - } -} diff --git a/clouddriver-google/src/main/groovy/com/netflix/spinnaker/clouddriver/google/compute/InstanceTemplates.java b/clouddriver-google/src/main/groovy/com/netflix/spinnaker/clouddriver/google/compute/InstanceTemplates.java new file mode 100644 index 00000000000..9b77db98f9c --- /dev/null +++ b/clouddriver-google/src/main/groovy/com/netflix/spinnaker/clouddriver/google/compute/InstanceTemplates.java @@ -0,0 +1,91 @@ +/* + * 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.clouddriver.google.compute; + +import com.google.api.services.compute.ComputeRequest; +import com.google.api.services.compute.model.InstanceTemplate; +import com.google.api.services.compute.model.Operation; +import com.google.common.collect.ImmutableMap; +import com.netflix.spectator.api.Registry; +import com.netflix.spinnaker.clouddriver.google.GoogleExecutor; +import com.netflix.spinnaker.clouddriver.google.compute.GoogleComputeOperationRequestImpl.OperationWaiter; +import com.netflix.spinnaker.clouddriver.google.deploy.GCEUtil; +import com.netflix.spinnaker.clouddriver.google.deploy.GoogleOperationPoller; +import com.netflix.spinnaker.clouddriver.google.security.GoogleNamedAccountCredentials; +import java.io.IOException; + +public class InstanceTemplates { + + public static final ImmutableMap TAGS = + ImmutableMap.of(GoogleExecutor.getTAG_SCOPE(), GoogleExecutor.getSCOPE_GLOBAL()); + + private final GoogleNamedAccountCredentials credentials; + private final GoogleOperationPoller operationPoller; + private final Registry registry; + + InstanceTemplates( + GoogleNamedAccountCredentials credentials, + GoogleOperationPoller operationPoller, + Registry registry) { + this.credentials = credentials; + this.operationPoller = operationPoller; + this.registry = registry; + } + + public GoogleComputeOperationRequest delete(String name) throws IOException { + + ComputeRequest request = + credentials.getCompute().instanceTemplates().delete(credentials.getProject(), name); + return wrapOperationRequest(request, "delete"); + } + + public GoogleComputeRequest get(String name) throws IOException { + ComputeRequest request = + credentials.getCompute().instanceTemplates().get(credentials.getProject(), name); + return wrapRequest(request, "get"); + } + + public GoogleComputeOperationRequest insert(InstanceTemplate template) throws IOException { + ComputeRequest request = + credentials.getCompute().instanceTemplates().insert(credentials.getProject(), template); + return wrapOperationRequest(request, "insert"); + } + + private GoogleComputeRequest wrapRequest(ComputeRequest request, String api) { + return new GoogleComputeRequestImpl<>(request, registry, getMetricName(api), TAGS); + } + + private GoogleComputeOperationRequest wrapOperationRequest( + ComputeRequest request, String api) { + OperationWaiter waiter = + (operation, task, phase) -> + operationPoller.waitForGlobalOperation( + credentials.getCompute(), + credentials.getProject(), + operation.getName(), + /* timeoutSeconds= */ null, + task, + GCEUtil.getLocalName(operation.getTargetLink()), + phase); + return new GoogleComputeOperationRequestImpl( + request, registry, getMetricName(api), TAGS, waiter); + } + + private String getMetricName(String api) { + return "compute.instanceTemplates." + api; + } +} diff --git a/clouddriver-google/src/main/groovy/com/netflix/spinnaker/clouddriver/google/compute/RegionGoogleServerGroupManagers.java b/clouddriver-google/src/main/groovy/com/netflix/spinnaker/clouddriver/google/compute/RegionGoogleServerGroupManagers.java index c933afa6fae..187976f91f9 100644 --- a/clouddriver-google/src/main/groovy/com/netflix/spinnaker/clouddriver/google/compute/RegionGoogleServerGroupManagers.java +++ b/clouddriver-google/src/main/groovy/com/netflix/spinnaker/clouddriver/google/compute/RegionGoogleServerGroupManagers.java @@ -1,3 +1,19 @@ +/* + * 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.clouddriver.google.compute; import com.google.api.services.compute.Compute; @@ -5,29 +21,30 @@ import com.google.api.services.compute.model.InstanceGroupManager; import com.google.api.services.compute.model.Operation; import com.google.api.services.compute.model.RegionInstanceGroupManagersAbandonInstancesRequest; -import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; import com.netflix.spectator.api.Registry; import com.netflix.spinnaker.clouddriver.google.GoogleExecutor; +import com.netflix.spinnaker.clouddriver.google.compute.GoogleComputeOperationRequestImpl.OperationWaiter; +import com.netflix.spinnaker.clouddriver.google.deploy.GCEUtil; import com.netflix.spinnaker.clouddriver.google.deploy.GoogleOperationPoller; import com.netflix.spinnaker.clouddriver.google.security.GoogleNamedAccountCredentials; import java.io.IOException; import java.util.List; +import java.util.Map; -class RegionGoogleServerGroupManagers extends AbstractGoogleServerGroupManagers { +final class RegionGoogleServerGroupManagers extends AbstractGoogleServerGroupManagers { private final Compute.RegionInstanceGroupManagers managers; - private final GoogleOperationPoller operationPoller; private final String region; RegionGoogleServerGroupManagers( GoogleNamedAccountCredentials credentials, - GoogleOperationPoller operationPoller, + GoogleOperationPoller poller, Registry registry, String instanceGroupName, String region) { - super(credentials, registry, instanceGroupName); + super(credentials, poller, registry, instanceGroupName); this.managers = credentials.getCompute().regionInstanceGroupManagers(); - this.operationPoller = operationPoller; this.region = region; } @@ -56,8 +73,18 @@ ComputeRequest performUpdate(InstanceGroupManager content) throws IOE } @Override - WaitableComputeOperation wrapOperation(Operation operation) { - return new RegionalOperation(operation, getCredentials(), operationPoller); + OperationWaiter getOperationWaiter( + GoogleNamedAccountCredentials credentials, GoogleOperationPoller poller) { + return (operation, task, phase) -> + poller.waitForRegionalOperation( + credentials.getCompute(), + credentials.getProject(), + GCEUtil.getLocalName(operation.getRegion()), + operation.getName(), + /* timeoutSeconds= */ null, + task, + GCEUtil.getLocalName(operation.getTargetLink()), + phase); } @Override @@ -66,8 +93,8 @@ String getManagersType() { } @Override - List getRegionOrZoneTags() { - return ImmutableList.of( + Map getRegionOrZoneTags() { + return ImmutableMap.of( GoogleExecutor.getTAG_SCOPE(), GoogleExecutor.getSCOPE_REGIONAL(), GoogleExecutor.getTAG_REGION(), diff --git a/clouddriver-google/src/main/groovy/com/netflix/spinnaker/clouddriver/google/compute/RegionalOperation.java b/clouddriver-google/src/main/groovy/com/netflix/spinnaker/clouddriver/google/compute/RegionalOperation.java deleted file mode 100644 index b666006ed7d..00000000000 --- a/clouddriver-google/src/main/groovy/com/netflix/spinnaker/clouddriver/google/compute/RegionalOperation.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.netflix.spinnaker.clouddriver.google.compute; - -import com.google.api.services.compute.model.Operation; -import com.netflix.spinnaker.clouddriver.data.task.Task; -import com.netflix.spinnaker.clouddriver.google.deploy.GCEUtil; -import com.netflix.spinnaker.clouddriver.google.deploy.GoogleOperationPoller; -import com.netflix.spinnaker.clouddriver.google.security.GoogleNamedAccountCredentials; - -class RegionalOperation implements WaitableComputeOperation { - - private final Operation operation; - private final GoogleNamedAccountCredentials credentials; - private final GoogleOperationPoller poller; - - RegionalOperation( - Operation operation, - GoogleNamedAccountCredentials credentials, - GoogleOperationPoller poller) { - this.operation = operation; - this.credentials = credentials; - this.poller = poller; - } - - @Override - public Operation waitForDone(Task task, String phase) { - return poller.waitForRegionalOperation( - credentials.getCompute(), - credentials.getProject(), - GCEUtil.getLocalName(operation.getRegion()), - operation.getName(), - /* timeoutSeconds= */ null, - task, - GCEUtil.getLocalName(operation.getTargetLink()), - phase); - } -} diff --git a/clouddriver-google/src/main/groovy/com/netflix/spinnaker/clouddriver/google/compute/WaitableComputeOperation.java b/clouddriver-google/src/main/groovy/com/netflix/spinnaker/clouddriver/google/compute/WaitableComputeOperation.java deleted file mode 100644 index 07d0b5e123c..00000000000 --- a/clouddriver-google/src/main/groovy/com/netflix/spinnaker/clouddriver/google/compute/WaitableComputeOperation.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.netflix.spinnaker.clouddriver.google.compute; - -import com.google.api.services.compute.model.Operation; -import com.netflix.spinnaker.clouddriver.data.task.Task; - -public interface WaitableComputeOperation { - - Operation waitForDone(Task task, String phase); -} diff --git a/clouddriver-google/src/main/groovy/com/netflix/spinnaker/clouddriver/google/compute/ZonalOperation.java b/clouddriver-google/src/main/groovy/com/netflix/spinnaker/clouddriver/google/compute/ZonalOperation.java deleted file mode 100644 index 3639db818e5..00000000000 --- a/clouddriver-google/src/main/groovy/com/netflix/spinnaker/clouddriver/google/compute/ZonalOperation.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.netflix.spinnaker.clouddriver.google.compute; - -import com.google.api.services.compute.model.Operation; -import com.netflix.spinnaker.clouddriver.data.task.Task; -import com.netflix.spinnaker.clouddriver.google.deploy.GCEUtil; -import com.netflix.spinnaker.clouddriver.google.deploy.GoogleOperationPoller; -import com.netflix.spinnaker.clouddriver.google.security.GoogleNamedAccountCredentials; - -class ZonalOperation implements WaitableComputeOperation { - - private final Operation operation; - private final GoogleNamedAccountCredentials credentials; - private final GoogleOperationPoller poller; - - ZonalOperation( - Operation operation, - GoogleNamedAccountCredentials credentials, - GoogleOperationPoller poller) { - this.operation = operation; - this.credentials = credentials; - this.poller = poller; - } - - @Override - public Operation waitForDone(Task task, String phase) { - return poller.waitForZonalOperation( - credentials.getCompute(), - credentials.getProject(), - GCEUtil.getLocalName(operation.getZone()), - operation.getName(), - /* timeoutSeconds= */ null, - task, - GCEUtil.getLocalName(operation.getTargetLink()), - phase); - } -} diff --git a/clouddriver-google/src/main/groovy/com/netflix/spinnaker/clouddriver/google/compute/ZoneGoogleServerGroupManagers.java b/clouddriver-google/src/main/groovy/com/netflix/spinnaker/clouddriver/google/compute/ZoneGoogleServerGroupManagers.java index 5f7c4272da4..2a1b35bf604 100644 --- a/clouddriver-google/src/main/groovy/com/netflix/spinnaker/clouddriver/google/compute/ZoneGoogleServerGroupManagers.java +++ b/clouddriver-google/src/main/groovy/com/netflix/spinnaker/clouddriver/google/compute/ZoneGoogleServerGroupManagers.java @@ -1,3 +1,19 @@ +/* + * 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.clouddriver.google.compute; import com.google.api.services.compute.Compute; @@ -5,29 +21,30 @@ import com.google.api.services.compute.model.InstanceGroupManager; import com.google.api.services.compute.model.InstanceGroupManagersAbandonInstancesRequest; import com.google.api.services.compute.model.Operation; -import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; import com.netflix.spectator.api.Registry; import com.netflix.spinnaker.clouddriver.google.GoogleExecutor; +import com.netflix.spinnaker.clouddriver.google.compute.GoogleComputeOperationRequestImpl.OperationWaiter; +import com.netflix.spinnaker.clouddriver.google.deploy.GCEUtil; import com.netflix.spinnaker.clouddriver.google.deploy.GoogleOperationPoller; import com.netflix.spinnaker.clouddriver.google.security.GoogleNamedAccountCredentials; import java.io.IOException; import java.util.List; +import java.util.Map; -class ZoneGoogleServerGroupManagers extends AbstractGoogleServerGroupManagers { +final class ZoneGoogleServerGroupManagers extends AbstractGoogleServerGroupManagers { private final Compute.InstanceGroupManagers managers; - private final GoogleOperationPoller operationPoller; private final String zone; ZoneGoogleServerGroupManagers( GoogleNamedAccountCredentials credentials, - GoogleOperationPoller operationPoller, + GoogleOperationPoller poller, Registry registry, String instanceGroupName, String zone) { - super(credentials, registry, instanceGroupName); + super(credentials, poller, registry, instanceGroupName); this.managers = credentials.getCompute().instanceGroupManagers(); - this.operationPoller = operationPoller; this.zone = zone; } @@ -56,8 +73,18 @@ ComputeRequest performUpdate(InstanceGroupManager content) throws IOE } @Override - WaitableComputeOperation wrapOperation(Operation operation) { - return new ZonalOperation(operation, getCredentials(), operationPoller); + OperationWaiter getOperationWaiter( + GoogleNamedAccountCredentials credentials, GoogleOperationPoller poller) { + return (operation, task, phase) -> + poller.waitForZonalOperation( + credentials.getCompute(), + credentials.getProject(), + GCEUtil.getLocalName(operation.getZone()), + operation.getName(), + /* timeoutSeconds= */ null, + task, + GCEUtil.getLocalName(operation.getTargetLink()), + phase); } @Override @@ -66,8 +93,8 @@ String getManagersType() { } @Override - List getRegionOrZoneTags() { - return ImmutableList.of( + Map getRegionOrZoneTags() { + return ImmutableMap.of( GoogleExecutor.getTAG_SCOPE(), GoogleExecutor.getSCOPE_ZONAL(), GoogleExecutor.getTAG_ZONE(), diff --git a/clouddriver-google/src/main/groovy/com/netflix/spinnaker/clouddriver/google/deploy/converters/SetStatefulDiskAtomicOperationConverter.java b/clouddriver-google/src/main/groovy/com/netflix/spinnaker/clouddriver/google/deploy/converters/SetStatefulDiskAtomicOperationConverter.java index a0f75a26e2b..23f3dfef89d 100644 --- a/clouddriver-google/src/main/groovy/com/netflix/spinnaker/clouddriver/google/deploy/converters/SetStatefulDiskAtomicOperationConverter.java +++ b/clouddriver-google/src/main/groovy/com/netflix/spinnaker/clouddriver/google/deploy/converters/SetStatefulDiskAtomicOperationConverter.java @@ -1,7 +1,7 @@ package com.netflix.spinnaker.clouddriver.google.deploy.converters; import com.netflix.spinnaker.clouddriver.google.GoogleOperation; -import com.netflix.spinnaker.clouddriver.google.compute.GoogleServerGroupManagersFactory; +import com.netflix.spinnaker.clouddriver.google.compute.GoogleComputeApiFactory; import com.netflix.spinnaker.clouddriver.google.deploy.description.SetStatefulDiskDescription; import com.netflix.spinnaker.clouddriver.google.deploy.ops.SetStatefulDiskAtomicOperation; import com.netflix.spinnaker.clouddriver.google.provider.view.GoogleClusterProvider; @@ -17,20 +17,19 @@ public class SetStatefulDiskAtomicOperationConverter extends AbstractAtomicOperationsCredentialsSupport { private final GoogleClusterProvider clusterProvider; - private final GoogleServerGroupManagersFactory serverGroupManagersFactory; + private final GoogleComputeApiFactory computeApiFactory; @Autowired public SetStatefulDiskAtomicOperationConverter( - GoogleClusterProvider clusterProvider, - GoogleServerGroupManagersFactory serverGroupManagersFactory) { + GoogleClusterProvider clusterProvider, GoogleComputeApiFactory computeApiFactory) { this.clusterProvider = clusterProvider; - this.serverGroupManagersFactory = serverGroupManagersFactory; + this.computeApiFactory = computeApiFactory; } @Override public SetStatefulDiskAtomicOperation convertOperation(Map input) { return new SetStatefulDiskAtomicOperation( - clusterProvider, serverGroupManagersFactory, convertDescription(input)); + clusterProvider, computeApiFactory, convertDescription(input)); } @Override diff --git a/clouddriver-google/src/main/groovy/com/netflix/spinnaker/clouddriver/google/deploy/ops/AbandonAndDecrementGoogleServerGroupAtomicOperation.groovy b/clouddriver-google/src/main/groovy/com/netflix/spinnaker/clouddriver/google/deploy/ops/AbandonAndDecrementGoogleServerGroupAtomicOperation.groovy index 16be18bf920..32b7b2b6e19 100644 --- a/clouddriver-google/src/main/groovy/com/netflix/spinnaker/clouddriver/google/deploy/ops/AbandonAndDecrementGoogleServerGroupAtomicOperation.groovy +++ b/clouddriver-google/src/main/groovy/com/netflix/spinnaker/clouddriver/google/deploy/ops/AbandonAndDecrementGoogleServerGroupAtomicOperation.groovy @@ -20,7 +20,7 @@ package com.netflix.spinnaker.clouddriver.google.deploy.ops import com.netflix.spinnaker.clouddriver.data.task.Task import com.netflix.spinnaker.clouddriver.data.task.TaskRepository import com.netflix.spinnaker.clouddriver.google.deploy.GCEUtil -import com.netflix.spinnaker.clouddriver.google.compute.GoogleServerGroupManagersFactory +import com.netflix.spinnaker.clouddriver.google.compute.GoogleComputeApiFactory import com.netflix.spinnaker.clouddriver.google.deploy.description.AbandonAndDecrementGoogleServerGroupDescription import com.netflix.spinnaker.clouddriver.google.provider.view.GoogleClusterProvider import org.springframework.beans.factory.annotation.Autowired @@ -46,7 +46,7 @@ class AbandonAndDecrementGoogleServerGroupAtomicOperation extends GoogleAtomicOp GoogleClusterProvider googleClusterProvider @Autowired - GoogleServerGroupManagersFactory serverGroupManagersFactory + GoogleComputeApiFactory computeApiFactory AbandonAndDecrementGoogleServerGroupAtomicOperation(AbandonAndDecrementGoogleServerGroupDescription description) { this.description = description @@ -70,8 +70,8 @@ class AbandonAndDecrementGoogleServerGroupAtomicOperation extends GoogleAtomicOp def instanceIds = description.instanceIds def instanceUrls = GCEUtil.collectInstanceUrls(serverGroup, instanceIds) - def serverGroupManagers = serverGroupManagersFactory.getManagers(credentials, serverGroup) - serverGroupManagers.abandonInstances(instanceUrls) + def serverGroupManagers = computeApiFactory.createServerGroupManagers(credentials, serverGroup) + serverGroupManagers.abandonInstances(instanceUrls).execute() task.updateStatus BASE_PHASE, "Done abandoning and decrementing instances " + "(${description.instanceIds.join(", ")}) from server group $serverGroupName in $region." diff --git a/clouddriver-google/src/main/groovy/com/netflix/spinnaker/clouddriver/google/deploy/ops/DestroyGoogleServerGroupAtomicOperation.groovy b/clouddriver-google/src/main/groovy/com/netflix/spinnaker/clouddriver/google/deploy/ops/DestroyGoogleServerGroupAtomicOperation.groovy index f36bcac5909..c82838fc05f 100644 --- a/clouddriver-google/src/main/groovy/com/netflix/spinnaker/clouddriver/google/deploy/ops/DestroyGoogleServerGroupAtomicOperation.groovy +++ b/clouddriver-google/src/main/groovy/com/netflix/spinnaker/clouddriver/google/deploy/ops/DestroyGoogleServerGroupAtomicOperation.groovy @@ -23,7 +23,7 @@ import com.netflix.spinnaker.clouddriver.google.deploy.GCEUtil import com.netflix.spinnaker.clouddriver.google.deploy.GoogleOperationPoller import com.netflix.spinnaker.clouddriver.google.deploy.SafeRetry import com.netflix.spinnaker.clouddriver.google.deploy.description.DestroyGoogleServerGroupDescription -import com.netflix.spinnaker.clouddriver.google.compute.GoogleServerGroupManagersFactory +import com.netflix.spinnaker.clouddriver.google.compute.GoogleComputeApiFactory import com.netflix.spinnaker.clouddriver.google.model.GoogleServerGroup import com.netflix.spinnaker.clouddriver.google.provider.view.GoogleClusterProvider import com.netflix.spinnaker.clouddriver.google.provider.view.GoogleLoadBalancerProvider @@ -56,7 +56,7 @@ class DestroyGoogleServerGroupAtomicOperation extends GoogleAtomicOperation private static final String BASE_PHASE = "SET_STATEFUL_DISK"; private final GoogleClusterProvider clusterProvider; - private final GoogleServerGroupManagersFactory serverGroupManagersFactory; + private final GoogleComputeApiFactory computeApiFactory; private final SetStatefulDiskDescription description; public SetStatefulDiskAtomicOperation( GoogleClusterProvider clusterProvider, - GoogleServerGroupManagersFactory serverGroupManagersFactory, + GoogleComputeApiFactory computeApiFactory, SetStatefulDiskDescription description) { this.clusterProvider = clusterProvider; - this.serverGroupManagersFactory = serverGroupManagersFactory; + this.computeApiFactory = computeApiFactory; this.description = description; } @@ -65,23 +64,19 @@ public Void operate(List priorOutputs) { try { GoogleServerGroupManagers managers = - serverGroupManagersFactory.getManagers(description.getCredentials(), serverGroup); + computeApiFactory.createServerGroupManagers(description.getCredentials(), serverGroup); task.updateStatus(BASE_PHASE, "Retrieving current instance group definition"); - InstanceGroupManager instanceGroupManager = managers.get(); - - task.updateStatus(BASE_PHASE, "Retrieving associated instance template definition"); + InstanceGroupManager instanceGroupManager = managers.get().execute(); setStatefulPolicy(instanceGroupManager); task.updateStatus(BASE_PHASE, "Storing updated instance group definition"); - WaitableComputeOperation operation = managers.update(instanceGroupManager); - - task.updateStatus(BASE_PHASE, "Waiting for update to complete"); - - operation.waitForDone(TaskRepository.threadLocalTask.get(), BASE_PHASE); + managers + .update(instanceGroupManager) + .executeAndWait(TaskRepository.threadLocalTask.get(), BASE_PHASE); return null; } catch (IOException e) { diff --git a/clouddriver-google/src/test/groovy/com/netflix/spinnaker/clouddriver/google/compute/ComputeOperationMockHttpTransport.java b/clouddriver-google/src/test/groovy/com/netflix/spinnaker/clouddriver/google/compute/ComputeOperationMockHttpTransport.java new file mode 100644 index 00000000000..a6913e27dfc --- /dev/null +++ b/clouddriver-google/src/test/groovy/com/netflix/spinnaker/clouddriver/google/compute/ComputeOperationMockHttpTransport.java @@ -0,0 +1,45 @@ +/* + * 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.clouddriver.google.compute; + +import com.google.api.client.http.HttpTransport; +import com.google.api.client.http.LowLevelHttpRequest; +import com.google.api.client.testing.http.MockLowLevelHttpRequest; +import com.google.api.client.testing.http.MockLowLevelHttpResponse; + +class ComputeOperationMockHttpTransport extends HttpTransport { + + private final MockLowLevelHttpResponse createOperationResponse; + + ComputeOperationMockHttpTransport(MockLowLevelHttpResponse createOperationResponse) { + this.createOperationResponse = createOperationResponse; + } + + @Override + protected LowLevelHttpRequest buildRequest(String method, String url) { + if (url.toLowerCase().contains("operation")) { + return new MockLowLevelHttpRequest(url) + .setResponse( + new MockLowLevelHttpResponse() + .setStatusCode(200) + .setContent( + "" + "{" + " \"name\": \"opName\"," + " \"status\": \"DONE\"" + "}")); + } else { + return new MockLowLevelHttpRequest(url).setResponse(createOperationResponse); + } + } +} diff --git a/clouddriver-google/src/test/groovy/com/netflix/spinnaker/clouddriver/google/compute/InstanceTemplatesTest.java b/clouddriver-google/src/test/groovy/com/netflix/spinnaker/clouddriver/google/compute/InstanceTemplatesTest.java new file mode 100644 index 00000000000..eeb78dcc807 --- /dev/null +++ b/clouddriver-google/src/test/groovy/com/netflix/spinnaker/clouddriver/google/compute/InstanceTemplatesTest.java @@ -0,0 +1,224 @@ +/* + * 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.clouddriver.google.compute; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIOException; + +import com.google.api.client.http.HttpTransport; +import com.google.api.client.json.jackson2.JacksonFactory; +import com.google.api.client.testing.http.MockHttpTransport; +import com.google.api.client.testing.http.MockLowLevelHttpResponse; +import com.google.api.services.compute.Compute; +import com.google.api.services.compute.model.InstanceTemplate; +import com.netflix.spectator.api.BasicTag; +import com.netflix.spectator.api.DefaultRegistry; +import com.netflix.spectator.api.NoopRegistry; +import com.netflix.spectator.api.Registry; +import com.netflix.spectator.api.Tag; +import com.netflix.spectator.api.Timer; +import com.netflix.spinnaker.clouddriver.data.task.DefaultTask; +import com.netflix.spinnaker.clouddriver.google.config.GoogleConfigurationProperties; +import com.netflix.spinnaker.clouddriver.google.deploy.GoogleOperationPoller; +import com.netflix.spinnaker.clouddriver.google.deploy.SafeRetry; +import com.netflix.spinnaker.clouddriver.google.security.FakeGoogleCredentials; +import com.netflix.spinnaker.clouddriver.google.security.GoogleNamedAccountCredentials; +import java.io.IOException; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class InstanceTemplatesTest { + + private static final int CLOCK_STEP_TIME_MS = 1234; + private static final int CLOCK_STEP_TIME_NS = 1234 * 1000000; + + @Test + public void delete_success() throws IOException { + + HttpTransport transport = + new ComputeOperationMockHttpTransport( + new MockLowLevelHttpResponse().setStatusCode(200).setContent("{\"name\": \"xyzzy\"}")); + + InstanceTemplates instanceTemplates = createInstanceTemplates(transport); + + instanceTemplates + .delete("my-instance-template") + .executeAndWait(new DefaultTask("task"), "phase"); + } + + @Test + public void delete_failure() { + + HttpTransport transport = + new MockHttpTransport.Builder() + .setLowLevelHttpResponse( + new MockLowLevelHttpResponse().setStatusCode(404).setContent("{}")) + .build(); + + InstanceTemplates instanceTemplates = createInstanceTemplates(transport); + + assertThatIOException() + .isThrownBy(() -> instanceTemplates.delete("my-instance-template").execute()); + } + + @Test + public void insert_success() throws IOException { + + HttpTransport transport = + new ComputeOperationMockHttpTransport( + new MockLowLevelHttpResponse().setStatusCode(200).setContent("{\"name\": \"xyzzy\"}")); + + InstanceTemplates instanceTemplates = createInstanceTemplates(transport); + + instanceTemplates + .insert(new InstanceTemplate()) + .executeAndWait(new DefaultTask("task"), "phase"); + } + + @Test + public void insert_failure() { + + HttpTransport transport = + new MockHttpTransport.Builder() + .setLowLevelHttpResponse( + new MockLowLevelHttpResponse().setStatusCode(404).setContent("{}")) + .build(); + + InstanceTemplates instanceTemplates = createInstanceTemplates(transport); + + assertThatIOException() + .isThrownBy(() -> instanceTemplates.insert(new InstanceTemplate()).execute()); + } + + @Test + public void get_success() throws IOException { + + MockHttpTransport transport = + new MockHttpTransport.Builder() + .setLowLevelHttpResponse( + new MockLowLevelHttpResponse() + .setStatusCode(200) + .setContent("{\"name\": \"my-instance-template\"}")) + .build(); + InstanceTemplates instanceTemplates = createInstanceTemplates(transport); + + InstanceTemplate template = instanceTemplates.get("hello").execute(); + + assertThat(template.getName()).isEqualTo("my-instance-template"); + } + + @Test + public void get_error() { + + MockHttpTransport transport = + new MockHttpTransport.Builder() + .setLowLevelHttpResponse( + new MockLowLevelHttpResponse().setStatusCode(404).setContent("{}")) + .build(); + InstanceTemplates instanceTemplates = createInstanceTemplates(transport); + + assertThatIOException().isThrownBy(() -> instanceTemplates.get("hello").execute()); + } + + @Test + public void get_successMetrics() throws IOException { + + Registry registry = new DefaultRegistry(new SteppingClock(CLOCK_STEP_TIME_MS)); + MockHttpTransport transport = + new MockHttpTransport.Builder() + .setLowLevelHttpResponse( + new MockLowLevelHttpResponse() + .setStatusCode(200) + .setContent("{\"name\": \"my-instance-template\"}")) + .build(); + InstanceTemplates instanceTemplates = createInstanceTemplates(transport, registry); + + instanceTemplates.get("hello").execute(); + + assertThat(registry.timers().count()).isEqualTo(1); + Timer timer = registry.timers().findFirst().orElseThrow(AssertionError::new); + assertThat(timer.id().name()).isEqualTo("google.api"); + // TODO(plumpy): Come up with something better than AccountForClient (which uses a bunch of + // global state) so that we can test for the account tags + assertThat(timer.id().tags()) + .contains( + tag("api", "compute.instanceTemplates.get"), + tag("scope", "global"), + tag("status", "2xx"), + tag("success", "true")); + assertThat(timer.totalTime()).isEqualTo(CLOCK_STEP_TIME_NS); + } + + @Test + public void get_errorMetrics() { + + Registry registry = new DefaultRegistry(new SteppingClock(CLOCK_STEP_TIME_MS)); + MockHttpTransport transport = + new MockHttpTransport.Builder() + .setLowLevelHttpResponse( + new MockLowLevelHttpResponse().setStatusCode(404).setContent("{}")) + .build(); + InstanceTemplates instanceTemplates = createInstanceTemplates(transport, registry); + + try { + instanceTemplates.get("hello").execute(); + } catch (IOException expected) { + } + + assertThat(registry.timers().count()).isEqualTo(1); + Timer timer = registry.timers().findFirst().orElseThrow(AssertionError::new); + assertThat(timer.id().name()).isEqualTo("google.api"); + assertThat(timer.id().tags()) + .contains( + tag("api", "compute.instanceTemplates.get"), + tag("scope", "global"), + tag("status", "4xx"), + tag("success", "false")); + assertThat(timer.totalTime()).isEqualTo(CLOCK_STEP_TIME_NS); + } + + private static InstanceTemplates createInstanceTemplates(HttpTransport transport) { + return createInstanceTemplates(transport, new NoopRegistry()); + } + + private static InstanceTemplates createInstanceTemplates( + HttpTransport transport, Registry registry) { + Compute compute = + new Compute( + transport, JacksonFactory.getDefaultInstance(), /* httpRequestInitializer= */ null); + GoogleNamedAccountCredentials credentials = + new GoogleNamedAccountCredentials.Builder() + .name("spin-user") + .project("myproject") + .credentials(new FakeGoogleCredentials()) + .compute(compute) + .build(); + GoogleOperationPoller poller = new GoogleOperationPoller(); + poller.setGoogleConfigurationProperties(new GoogleConfigurationProperties()); + poller.setRegistry(registry); + SafeRetry safeRetry = new SafeRetry(); + safeRetry.setMaxRetries(10L); + poller.setSafeRetry(safeRetry); + return new InstanceTemplates(credentials, poller, registry); + } + + private static Tag tag(String key, String value) { + return new BasicTag(key, value); + } +} diff --git a/clouddriver-google/src/test/groovy/com/netflix/spinnaker/clouddriver/google/compute/RegionGoogleServerGroupManagersTest.java b/clouddriver-google/src/test/groovy/com/netflix/spinnaker/clouddriver/google/compute/RegionGoogleServerGroupManagersTest.java new file mode 100644 index 00000000000..ba8dd5081f2 --- /dev/null +++ b/clouddriver-google/src/test/groovy/com/netflix/spinnaker/clouddriver/google/compute/RegionGoogleServerGroupManagersTest.java @@ -0,0 +1,273 @@ +/* + * 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.clouddriver.google.compute; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIOException; + +import com.google.api.client.http.HttpTransport; +import com.google.api.client.json.jackson2.JacksonFactory; +import com.google.api.client.testing.http.MockHttpTransport; +import com.google.api.client.testing.http.MockLowLevelHttpResponse; +import com.google.api.services.compute.Compute; +import com.google.api.services.compute.model.InstanceGroupManager; +import com.google.common.collect.ImmutableList; +import com.netflix.spectator.api.BasicTag; +import com.netflix.spectator.api.DefaultRegistry; +import com.netflix.spectator.api.NoopRegistry; +import com.netflix.spectator.api.Registry; +import com.netflix.spectator.api.Tag; +import com.netflix.spectator.api.Timer; +import com.netflix.spinnaker.clouddriver.data.task.DefaultTask; +import com.netflix.spinnaker.clouddriver.google.config.GoogleConfigurationProperties; +import com.netflix.spinnaker.clouddriver.google.deploy.GoogleOperationPoller; +import com.netflix.spinnaker.clouddriver.google.deploy.SafeRetry; +import com.netflix.spinnaker.clouddriver.google.security.FakeGoogleCredentials; +import com.netflix.spinnaker.clouddriver.google.security.GoogleNamedAccountCredentials; +import java.io.IOException; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class RegionGoogleServerGroupManagersTest { + + private static final String REGION = "us-central1"; + private static final int CLOCK_STEP_TIME_MS = 1234; + private static final int CLOCK_STEP_TIME_NS = 1234 * 1000000; + + @Test + public void abandonInstances_success() throws IOException { + + HttpTransport transport = + new ComputeOperationMockHttpTransport( + new MockLowLevelHttpResponse() + .setStatusCode(200) + .setContent( + "" + + "{" + + " \"name\": \"xyzzy\"," + + " \"region\": \"http://compute/regions/us-central1\"" + + "}")); + + RegionGoogleServerGroupManagers managers = createManagers(transport); + + managers + .abandonInstances(ImmutableList.of("myServerGroup")) + .executeAndWait(new DefaultTask("task"), "phase"); + } + + @Test + public void abandonInstances_failure() { + + HttpTransport transport = + new MockHttpTransport.Builder() + .setLowLevelHttpResponse( + new MockLowLevelHttpResponse().setStatusCode(404).setContent("{}")) + .build(); + + RegionGoogleServerGroupManagers managers = createManagers(transport); + + assertThatIOException() + .isThrownBy(() -> managers.abandonInstances(ImmutableList.of("myServerGroup")).execute()); + } + + @Test + public void delete_success() throws IOException { + + HttpTransport transport = + new ComputeOperationMockHttpTransport( + new MockLowLevelHttpResponse() + .setStatusCode(200) + .setContent( + "" + + "{" + + " \"name\": \"xyzzy\"," + + " \"region\": \"http://compute/regions/us-central1\"" + + "}")); + + RegionGoogleServerGroupManagers managers = createManagers(transport); + + managers.delete().executeAndWait(new DefaultTask("task"), "phase"); + } + + @Test + public void delete_failure() { + + HttpTransport transport = + new MockHttpTransport.Builder() + .setLowLevelHttpResponse( + new MockLowLevelHttpResponse().setStatusCode(404).setContent("{}")) + .build(); + + RegionGoogleServerGroupManagers managers = createManagers(transport); + + assertThatIOException().isThrownBy(() -> managers.delete().execute()); + } + + @Test + public void get_success() throws IOException { + + MockHttpTransport transport = + new MockHttpTransport.Builder() + .setLowLevelHttpResponse( + new MockLowLevelHttpResponse() + .setStatusCode(200) + .setContent("{\"name\": \"myServerGroup\"}")) + .build(); + RegionGoogleServerGroupManagers managers = createManagers(transport); + + InstanceGroupManager manager = managers.get().execute(); + + assertThat(manager.getName()).isEqualTo("myServerGroup"); + } + + @Test + public void get_error() { + + MockHttpTransport transport = + new MockHttpTransport.Builder() + .setLowLevelHttpResponse( + new MockLowLevelHttpResponse().setStatusCode(404).setContent("{}")) + .build(); + RegionGoogleServerGroupManagers managers = createManagers(transport); + + assertThatIOException().isThrownBy(() -> managers.get().execute()); + } + + @Test + public void get_successMetrics() throws IOException { + + Registry registry = new DefaultRegistry(new SteppingClock(CLOCK_STEP_TIME_MS)); + MockHttpTransport transport = + new MockHttpTransport.Builder() + .setLowLevelHttpResponse( + new MockLowLevelHttpResponse() + .setStatusCode(200) + .setContent("{\"name\": \"myServerGroup\"}")) + .build(); + RegionGoogleServerGroupManagers managers = createManagers(transport, registry); + + managers.get().execute(); + + assertThat(registry.timers().count()).isEqualTo(1); + Timer timer = registry.timers().findFirst().orElseThrow(AssertionError::new); + assertThat(timer.id().name()).isEqualTo("google.api"); + // TODO(plumpy): Come up with something better than AccountForClient (which uses a bunch of + // global state) so that we can test for the account tags + assertThat(timer.id().tags()) + .contains( + tag("api", "compute.regionInstanceGroupManagers.get"), + tag("scope", "regional"), + tag("region", REGION), + tag("status", "2xx"), + tag("success", "true")); + assertThat(timer.totalTime()).isEqualTo(CLOCK_STEP_TIME_NS); + } + + @Test + public void get_errorMetrics() { + + Registry registry = new DefaultRegistry(new SteppingClock(CLOCK_STEP_TIME_MS)); + MockHttpTransport transport = + new MockHttpTransport.Builder() + .setLowLevelHttpResponse( + new MockLowLevelHttpResponse().setStatusCode(404).setContent("{}")) + .build(); + RegionGoogleServerGroupManagers managers = createManagers(transport, registry); + + try { + managers.get().execute(); + } catch (IOException expected) { + } + + assertThat(registry.timers().count()).isEqualTo(1); + Timer timer = registry.timers().findFirst().orElseThrow(AssertionError::new); + assertThat(timer.id().name()).isEqualTo("google.api"); + assertThat(timer.id().tags()) + .contains( + tag("api", "compute.regionInstanceGroupManagers.get"), + tag("scope", "regional"), + tag("region", REGION), + tag("status", "4xx"), + tag("success", "false")); + assertThat(timer.totalTime()).isEqualTo(CLOCK_STEP_TIME_NS); + } + + @Test + public void update_success() throws IOException { + + HttpTransport transport = + new ComputeOperationMockHttpTransport( + new MockLowLevelHttpResponse() + .setStatusCode(200) + .setContent( + "" + + "{" + + " \"name\": \"xyzzy\"," + + " \"region\": \"http://compute/regions/us-central1\"" + + "}")); + + RegionGoogleServerGroupManagers managers = createManagers(transport); + + managers.update(new InstanceGroupManager()).executeAndWait(new DefaultTask("task"), "phase"); + } + + @Test + public void update_failure() { + + HttpTransport transport = + new MockHttpTransport.Builder() + .setLowLevelHttpResponse( + new MockLowLevelHttpResponse().setStatusCode(404).setContent("{}")) + .build(); + + RegionGoogleServerGroupManagers managers = createManagers(transport); + + assertThatIOException().isThrownBy(() -> managers.update(new InstanceGroupManager()).execute()); + } + + private static RegionGoogleServerGroupManagers createManagers(HttpTransport transport) { + return createManagers(transport, new NoopRegistry()); + } + + private static RegionGoogleServerGroupManagers createManagers( + HttpTransport transport, Registry registry) { + Compute compute = + new Compute( + transport, JacksonFactory.getDefaultInstance(), /* httpRequestInitializer= */ null); + GoogleNamedAccountCredentials credentials = + new GoogleNamedAccountCredentials.Builder() + .name("spin-user") + .project("myproject") + .credentials(new FakeGoogleCredentials()) + .compute(compute) + .build(); + GoogleOperationPoller poller = new GoogleOperationPoller(); + poller.setGoogleConfigurationProperties(new GoogleConfigurationProperties()); + poller.setRegistry(registry); + SafeRetry safeRetry = new SafeRetry(); + safeRetry.setMaxRetries(10L); + poller.setSafeRetry(safeRetry); + return new RegionGoogleServerGroupManagers( + credentials, poller, registry, "myInstanceGroup", REGION); + } + + private static Tag tag(String key, String value) { + return new BasicTag(key, value); + } +} diff --git a/clouddriver-google/src/test/groovy/com/netflix/spinnaker/clouddriver/google/compute/SteppingClock.java b/clouddriver-google/src/test/groovy/com/netflix/spinnaker/clouddriver/google/compute/SteppingClock.java new file mode 100644 index 00000000000..9e9912f293d --- /dev/null +++ b/clouddriver-google/src/test/groovy/com/netflix/spinnaker/clouddriver/google/compute/SteppingClock.java @@ -0,0 +1,42 @@ +/* + * 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.clouddriver.google.compute; + +import com.netflix.spectator.api.Clock; +import java.time.Duration; + +class SteppingClock implements Clock { + + private long currentTimeMs = 0; + private final int msAdjustmentBetweenCalls; + + public SteppingClock(int msAdjustmentBetweenCalls) { + this.msAdjustmentBetweenCalls = msAdjustmentBetweenCalls; + } + + @Override + public long wallTime() { + currentTimeMs += msAdjustmentBetweenCalls; + return currentTimeMs; + } + + @Override + public long monotonicTime() { + currentTimeMs += msAdjustmentBetweenCalls; + return Duration.ofMillis(currentTimeMs).toNanos(); + } +} diff --git a/clouddriver-google/src/test/groovy/com/netflix/spinnaker/clouddriver/google/compute/ZoneGoogleServerGroupManagersTest.java b/clouddriver-google/src/test/groovy/com/netflix/spinnaker/clouddriver/google/compute/ZoneGoogleServerGroupManagersTest.java new file mode 100644 index 00000000000..8117f3bfbcd --- /dev/null +++ b/clouddriver-google/src/test/groovy/com/netflix/spinnaker/clouddriver/google/compute/ZoneGoogleServerGroupManagersTest.java @@ -0,0 +1,273 @@ +/* + * 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.clouddriver.google.compute; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIOException; + +import com.google.api.client.http.HttpTransport; +import com.google.api.client.json.jackson2.JacksonFactory; +import com.google.api.client.testing.http.MockHttpTransport; +import com.google.api.client.testing.http.MockLowLevelHttpResponse; +import com.google.api.services.compute.Compute; +import com.google.api.services.compute.model.InstanceGroupManager; +import com.google.common.collect.ImmutableList; +import com.netflix.spectator.api.BasicTag; +import com.netflix.spectator.api.DefaultRegistry; +import com.netflix.spectator.api.NoopRegistry; +import com.netflix.spectator.api.Registry; +import com.netflix.spectator.api.Tag; +import com.netflix.spectator.api.Timer; +import com.netflix.spinnaker.clouddriver.data.task.DefaultTask; +import com.netflix.spinnaker.clouddriver.google.config.GoogleConfigurationProperties; +import com.netflix.spinnaker.clouddriver.google.deploy.GoogleOperationPoller; +import com.netflix.spinnaker.clouddriver.google.deploy.SafeRetry; +import com.netflix.spinnaker.clouddriver.google.security.FakeGoogleCredentials; +import com.netflix.spinnaker.clouddriver.google.security.GoogleNamedAccountCredentials; +import java.io.IOException; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class ZoneGoogleServerGroupManagersTest { + + private static final String ZONE = "us-central1-f"; + private static final int CLOCK_STEP_TIME_MS = 1234; + private static final int CLOCK_STEP_TIME_NS = 1234 * 1000000; + + @Test + public void abandonInstances_success() throws IOException { + + HttpTransport transport = + new ComputeOperationMockHttpTransport( + new MockLowLevelHttpResponse() + .setStatusCode(200) + .setContent( + "" + + "{" + + " \"name\": \"xyzzy\"," + + " \"zone\": \"http://compute/zones/us-central1-f\"" + + "}")); + + ZoneGoogleServerGroupManagers managers = createManagers(transport); + + managers + .abandonInstances(ImmutableList.of("myServerGroup")) + .executeAndWait(new DefaultTask("task"), "phase"); + } + + @Test + public void abandonInstances_failure() { + + HttpTransport transport = + new MockHttpTransport.Builder() + .setLowLevelHttpResponse( + new MockLowLevelHttpResponse().setStatusCode(404).setContent("{}")) + .build(); + + ZoneGoogleServerGroupManagers managers = createManagers(transport); + + assertThatIOException() + .isThrownBy(() -> managers.abandonInstances(ImmutableList.of("myServerGroup")).execute()); + } + + @Test + public void delete_success() throws IOException { + + HttpTransport transport = + new ComputeOperationMockHttpTransport( + new MockLowLevelHttpResponse() + .setStatusCode(200) + .setContent( + "" + + "{" + + " \"name\": \"xyzzy\"," + + " \"zone\": \"http://compute/zones/us-central1-f\"" + + "}")); + + ZoneGoogleServerGroupManagers managers = createManagers(transport); + + managers.delete().executeAndWait(new DefaultTask("task"), "phase"); + } + + @Test + public void delete_failure() { + + HttpTransport transport = + new MockHttpTransport.Builder() + .setLowLevelHttpResponse( + new MockLowLevelHttpResponse().setStatusCode(404).setContent("{}")) + .build(); + + ZoneGoogleServerGroupManagers managers = createManagers(transport); + + assertThatIOException().isThrownBy(() -> managers.delete().execute()); + } + + @Test + public void get_success() throws IOException { + + MockHttpTransport transport = + new MockHttpTransport.Builder() + .setLowLevelHttpResponse( + new MockLowLevelHttpResponse() + .setStatusCode(200) + .setContent("{\"name\": \"myServerGroup\"}")) + .build(); + ZoneGoogleServerGroupManagers managers = createManagers(transport); + + InstanceGroupManager manager = managers.get().execute(); + + assertThat(manager.getName()).isEqualTo("myServerGroup"); + } + + @Test + public void get_error() { + + MockHttpTransport transport = + new MockHttpTransport.Builder() + .setLowLevelHttpResponse( + new MockLowLevelHttpResponse().setStatusCode(404).setContent("{}")) + .build(); + ZoneGoogleServerGroupManagers managers = createManagers(transport); + + assertThatIOException().isThrownBy(() -> managers.get().execute()); + } + + @Test + public void get_successMetrics() throws IOException { + + Registry registry = new DefaultRegistry(new SteppingClock(CLOCK_STEP_TIME_MS)); + MockHttpTransport transport = + new MockHttpTransport.Builder() + .setLowLevelHttpResponse( + new MockLowLevelHttpResponse() + .setStatusCode(200) + .setContent("{\"name\": \"myServerGroup\"}")) + .build(); + ZoneGoogleServerGroupManagers managers = createManagers(transport, registry); + + managers.get().execute(); + + assertThat(registry.timers().count()).isEqualTo(1); + Timer timer = registry.timers().findFirst().orElseThrow(AssertionError::new); + assertThat(timer.id().name()).isEqualTo("google.api"); + // TODO(plumpy): Come up with something better than AccountForClient (which uses a bunch of + // global state) so that we can test for the account tags + assertThat(timer.id().tags()) + .contains( + tag("api", "compute.instanceGroupManagers.get"), + tag("scope", "zonal"), + tag("zone", ZONE), + tag("status", "2xx"), + tag("success", "true")); + assertThat(timer.totalTime()).isEqualTo(CLOCK_STEP_TIME_NS); + } + + @Test + public void get_errorMetrics() { + + Registry registry = new DefaultRegistry(new SteppingClock(CLOCK_STEP_TIME_MS)); + MockHttpTransport transport = + new MockHttpTransport.Builder() + .setLowLevelHttpResponse( + new MockLowLevelHttpResponse().setStatusCode(404).setContent("{}")) + .build(); + ZoneGoogleServerGroupManagers managers = createManagers(transport, registry); + + try { + managers.get().execute(); + } catch (IOException expected) { + } + + assertThat(registry.timers().count()).isEqualTo(1); + Timer timer = registry.timers().findFirst().orElseThrow(AssertionError::new); + assertThat(timer.id().name()).isEqualTo("google.api"); + assertThat(timer.id().tags()) + .contains( + tag("api", "compute.instanceGroupManagers.get"), + tag("scope", "zonal"), + tag("zone", ZONE), + tag("status", "4xx"), + tag("success", "false")); + assertThat(timer.totalTime()).isEqualTo(CLOCK_STEP_TIME_NS); + } + + @Test + public void update_success() throws IOException { + + HttpTransport transport = + new ComputeOperationMockHttpTransport( + new MockLowLevelHttpResponse() + .setStatusCode(200) + .setContent( + "" + + "{" + + " \"name\": \"xyzzy\"," + + " \"zone\": \"http://compute/zones/us-central1-f\"" + + "}")); + + ZoneGoogleServerGroupManagers managers = createManagers(transport); + + managers.update(new InstanceGroupManager()).executeAndWait(new DefaultTask("task"), "phase"); + } + + @Test + public void update_failure() { + + HttpTransport transport = + new MockHttpTransport.Builder() + .setLowLevelHttpResponse( + new MockLowLevelHttpResponse().setStatusCode(404).setContent("{}")) + .build(); + + ZoneGoogleServerGroupManagers managers = createManagers(transport); + + assertThatIOException().isThrownBy(() -> managers.update(new InstanceGroupManager()).execute()); + } + + private static ZoneGoogleServerGroupManagers createManagers(HttpTransport transport) { + return createManagers(transport, new NoopRegistry()); + } + + private static ZoneGoogleServerGroupManagers createManagers( + HttpTransport transport, Registry registry) { + Compute compute = + new Compute( + transport, JacksonFactory.getDefaultInstance(), /* httpRequestInitializer= */ null); + GoogleNamedAccountCredentials credentials = + new GoogleNamedAccountCredentials.Builder() + .name("spin-user") + .project("myproject") + .credentials(new FakeGoogleCredentials()) + .compute(compute) + .build(); + GoogleOperationPoller poller = new GoogleOperationPoller(); + poller.setGoogleConfigurationProperties(new GoogleConfigurationProperties()); + poller.setRegistry(registry); + SafeRetry safeRetry = new SafeRetry(); + safeRetry.setMaxRetries(10L); + poller.setSafeRetry(safeRetry); + return new ZoneGoogleServerGroupManagers( + credentials, poller, registry, "myInstanceGroup", ZONE); + } + + private static Tag tag(String key, String value) { + return new BasicTag(key, value); + } +} diff --git a/clouddriver-google/src/test/groovy/com/netflix/spinnaker/clouddriver/google/deploy/converters/SetStatefulDiskAtomicOperationConverterTest.java b/clouddriver-google/src/test/groovy/com/netflix/spinnaker/clouddriver/google/deploy/converters/SetStatefulDiskAtomicOperationConverterTest.java index d69228e63a0..51cb5e2c90c 100644 --- a/clouddriver-google/src/test/groovy/com/netflix/spinnaker/clouddriver/google/deploy/converters/SetStatefulDiskAtomicOperationConverterTest.java +++ b/clouddriver-google/src/test/groovy/com/netflix/spinnaker/clouddriver/google/deploy/converters/SetStatefulDiskAtomicOperationConverterTest.java @@ -6,7 +6,7 @@ import static org.mockito.Mockito.when; import com.fasterxml.jackson.databind.ObjectMapper; -import com.netflix.spinnaker.clouddriver.google.compute.GoogleServerGroupManagersFactory; +import com.netflix.spinnaker.clouddriver.google.compute.GoogleComputeApiFactory; import com.netflix.spinnaker.clouddriver.google.deploy.description.SetStatefulDiskDescription; import com.netflix.spinnaker.clouddriver.google.deploy.ops.SetStatefulDiskAtomicOperation; import com.netflix.spinnaker.clouddriver.google.provider.view.GoogleClusterProvider; @@ -33,8 +33,7 @@ public class SetStatefulDiskAtomicOperationConverterTest { @Before public void setUp() { GoogleClusterProvider clusterProvider = mock(GoogleClusterProvider.class); - GoogleServerGroupManagersFactory serverGroupManagersFactory = - mock(GoogleServerGroupManagersFactory.class); + GoogleComputeApiFactory serverGroupManagersFactory = mock(GoogleComputeApiFactory.class); converter = new SetStatefulDiskAtomicOperationConverter(clusterProvider, serverGroupManagersFactory); diff --git a/clouddriver-google/src/test/groovy/com/netflix/spinnaker/clouddriver/google/deploy/ops/AbandonAndDecrementGoogleServerGroupAtomicOperationUnitSpec.groovy b/clouddriver-google/src/test/groovy/com/netflix/spinnaker/clouddriver/google/deploy/ops/AbandonAndDecrementGoogleServerGroupAtomicOperationUnitSpec.groovy index ca6667fa375..57b935a2949 100644 --- a/clouddriver-google/src/test/groovy/com/netflix/spinnaker/clouddriver/google/deploy/ops/AbandonAndDecrementGoogleServerGroupAtomicOperationUnitSpec.groovy +++ b/clouddriver-google/src/test/groovy/com/netflix/spinnaker/clouddriver/google/deploy/ops/AbandonAndDecrementGoogleServerGroupAtomicOperationUnitSpec.groovy @@ -24,7 +24,7 @@ import com.netflix.spinnaker.clouddriver.data.task.TaskRepository import com.netflix.spinnaker.clouddriver.google.deploy.GCEUtil import com.netflix.spinnaker.clouddriver.google.deploy.GoogleOperationPoller import com.netflix.spinnaker.clouddriver.google.deploy.description.AbandonAndDecrementGoogleServerGroupDescription -import com.netflix.spinnaker.clouddriver.google.compute.GoogleServerGroupManagersFactory +import com.netflix.spinnaker.clouddriver.google.compute.GoogleComputeApiFactory import com.netflix.spinnaker.clouddriver.google.model.GoogleInstance import com.netflix.spinnaker.clouddriver.google.model.GoogleServerGroup import com.netflix.spinnaker.clouddriver.google.provider.view.GoogleClusterProvider @@ -83,7 +83,7 @@ class AbandonAndDecrementGoogleServerGroupAtomicOperationUnitSpec extends Specif @Subject def operation = new AbandonAndDecrementGoogleServerGroupAtomicOperation(description) operation.registry = registry operation.googleClusterProvider = googleClusterProviderMock - operation.serverGroupManagersFactory = new GoogleServerGroupManagersFactory(Mock(GoogleOperationPoller), registry) + operation.computeApiFactory = new GoogleComputeApiFactory(Mock(GoogleOperationPoller), registry) when: operation.operate([]) diff --git a/clouddriver-google/src/test/groovy/com/netflix/spinnaker/clouddriver/google/deploy/ops/DestroyGoogleServerGroupAtomicOperationUnitSpec.groovy b/clouddriver-google/src/test/groovy/com/netflix/spinnaker/clouddriver/google/deploy/ops/DestroyGoogleServerGroupAtomicOperationUnitSpec.groovy index 933b57f974c..5f437393bf6 100644 --- a/clouddriver-google/src/test/groovy/com/netflix/spinnaker/clouddriver/google/deploy/ops/DestroyGoogleServerGroupAtomicOperationUnitSpec.groovy +++ b/clouddriver-google/src/test/groovy/com/netflix/spinnaker/clouddriver/google/deploy/ops/DestroyGoogleServerGroupAtomicOperationUnitSpec.groovy @@ -32,7 +32,7 @@ import com.netflix.spinnaker.clouddriver.google.deploy.GCEUtil import com.netflix.spinnaker.clouddriver.google.deploy.GoogleOperationPoller import com.netflix.spinnaker.clouddriver.google.deploy.SafeRetry import com.netflix.spinnaker.clouddriver.google.deploy.description.DestroyGoogleServerGroupDescription -import com.netflix.spinnaker.clouddriver.google.compute.GoogleServerGroupManagersFactory +import com.netflix.spinnaker.clouddriver.google.compute.GoogleComputeApiFactory import com.netflix.spinnaker.clouddriver.google.model.GoogleServerGroup import com.netflix.spinnaker.clouddriver.google.model.loadbalancing.GoogleBackendService import com.netflix.spinnaker.clouddriver.google.model.loadbalancing.GoogleHttpLoadBalancer @@ -115,7 +115,7 @@ class DestroyGoogleServerGroupAtomicOperationUnitSpec extends Specification { operation.safeRetry = safeRetry operation.googleClusterProvider = googleClusterProviderMock operation.googleLoadBalancerProvider = googleLoadBalancerProviderMock - operation.serverGroupManagersFactory = new GoogleServerGroupManagersFactory(operation.googleOperationPoller, registry) + operation.computeApiFactory = new GoogleComputeApiFactory(operation.googleOperationPoller, registry) when: operation.operate([]) @@ -225,7 +225,7 @@ class DestroyGoogleServerGroupAtomicOperationUnitSpec extends Specification { operation.safeRetry = safeRetry operation.googleClusterProvider = googleClusterProviderMock operation.googleLoadBalancerProvider = googleLoadBalancerProviderMock - operation.serverGroupManagersFactory = new GoogleServerGroupManagersFactory(operation.googleOperationPoller, registry) + operation.computeApiFactory = new GoogleComputeApiFactory(operation.googleOperationPoller, registry) when: operation.operate([]) @@ -362,7 +362,7 @@ class DestroyGoogleServerGroupAtomicOperationUnitSpec extends Specification { operation.safeRetry = safeRetry operation.googleClusterProvider = googleClusterProviderMock operation.googleLoadBalancerProvider = googleLoadBalancerProviderMock - operation.serverGroupManagersFactory = new GoogleServerGroupManagersFactory(operation.googleOperationPoller, registry) + operation.computeApiFactory = new GoogleComputeApiFactory(operation.googleOperationPoller, registry) when: def closure = operation.destroyHttpLoadBalancerBackends(computeMock, PROJECT_NAME, serverGroup, googleLoadBalancerProviderMock) diff --git a/clouddriver-google/src/test/groovy/com/netflix/spinnaker/clouddriver/google/deploy/ops/SetStatefulDiskAtomicOperationUnitSpec.groovy b/clouddriver-google/src/test/groovy/com/netflix/spinnaker/clouddriver/google/deploy/ops/SetStatefulDiskAtomicOperationUnitSpec.groovy index d724afc2953..81c028e1e27 100644 --- a/clouddriver-google/src/test/groovy/com/netflix/spinnaker/clouddriver/google/deploy/ops/SetStatefulDiskAtomicOperationUnitSpec.groovy +++ b/clouddriver-google/src/test/groovy/com/netflix/spinnaker/clouddriver/google/deploy/ops/SetStatefulDiskAtomicOperationUnitSpec.groovy @@ -1,14 +1,12 @@ package com.netflix.spinnaker.clouddriver.google.deploy.ops import com.google.api.services.compute.model.InstanceGroupManager -import com.google.api.services.compute.model.Operation import com.netflix.spinnaker.clouddriver.data.task.Task import com.netflix.spinnaker.clouddriver.data.task.TaskRepository -import com.netflix.spinnaker.clouddriver.google.compute.WaitableComputeOperation +import com.netflix.spinnaker.clouddriver.google.compute.GoogleComputeOperationRequest import com.netflix.spinnaker.clouddriver.google.deploy.description.SetStatefulDiskDescription import com.netflix.spinnaker.clouddriver.google.compute.GoogleServerGroupManagers -import com.netflix.spinnaker.clouddriver.google.compute.GoogleServerGroupManagersFactory -import com.netflix.spinnaker.clouddriver.google.compute.GoogleServerGroupOperationPoller +import com.netflix.spinnaker.clouddriver.google.compute.GoogleComputeApiFactory import com.netflix.spinnaker.clouddriver.google.model.GoogleServerGroup import com.netflix.spinnaker.clouddriver.google.provider.view.GoogleClusterProvider import com.netflix.spinnaker.clouddriver.google.security.FakeGoogleCredentials @@ -28,20 +26,18 @@ class SetStatefulDiskAtomicOperationUnitSpec extends Specification { Task task GoogleClusterProvider clusterProvider - GoogleServerGroupManagersFactory serverGroupManagersFactory + GoogleComputeApiFactory computeApiFactory GoogleServerGroupManagers serverGroupManagers GoogleNamedAccountCredentials credentials - GoogleServerGroupOperationPoller poller def setup() { task = Mock(Task) TaskRepository.threadLocalTask.set(task) - poller = Mock(GoogleServerGroupOperationPoller) serverGroupManagers = Mock(GoogleServerGroupManagers) - serverGroupManagersFactory = Mock(GoogleServerGroupManagersFactory) { - _ * getManagers(*_) >> serverGroupManagers + computeApiFactory = Mock(GoogleComputeApiFactory) { + _ * createServerGroupManagers(*_) >> serverGroupManagers } clusterProvider = Mock(GoogleClusterProvider) { @@ -56,9 +52,10 @@ class SetStatefulDiskAtomicOperationUnitSpec extends Specification { region: REGION, deviceName: DEVICE_NAME, credentials: CREDENTIALS) - def operation = new SetStatefulDiskAtomicOperation(clusterProvider, serverGroupManagersFactory, description) - def updateOp = Mock(WaitableComputeOperation) - _ * serverGroupManagers.get() >> new InstanceGroupManager() + def operation = new SetStatefulDiskAtomicOperation(clusterProvider, computeApiFactory, description) + def updateOp = Mock(GoogleComputeOperationRequest) + def getManagerRequest = { new InstanceGroupManager() } + _ * serverGroupManagers.get() >> getManagerRequest when: operation.operate([]) @@ -67,6 +64,6 @@ class SetStatefulDiskAtomicOperationUnitSpec extends Specification { 1 * serverGroupManagers.update({ it.getStatefulPolicy().getPreservedState().getDisks().containsKey(DEVICE_NAME) }) >> updateOp - 1 * updateOp.waitForDone(task, /* phase= */ _) + 1 * updateOp.executeAndWait(task, /* phase= */ _) } }