Skip to content

Commit ef4bbdc

Browse files
mergify[bot]armory-abedoniklink108
authored
feat(gcp): provide a configurable option to bypass gcp account health check. (backport #6093) (#6098)
* feat(gcp): provide a configurable option to bypass gcp account health check. * feat(gcp): replace with in-line check solution. --------- Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> (cherry picked from commit df2bb06) Co-authored-by: armory-abedonik <106548537+armory-abedonik@users.noreply.github.com> Co-authored-by: Cameron Motevasselani <cmotevasselani@gmail.com>
1 parent 75e00c7 commit ef4bbdc

File tree

4 files changed

+184
-16
lines changed

4 files changed

+184
-16
lines changed

clouddriver-google/src/main/groovy/com/netflix/spinnaker/clouddriver/google/config/GoogleConfigurationProperties.groovy

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,25 @@ package com.netflix.spinnaker.clouddriver.google.config
1818

1919
import com.netflix.spinnaker.clouddriver.consul.config.ConsulConfig
2020
import com.netflix.spinnaker.clouddriver.googlecommon.config.GoogleCommonManagedAccount
21+
import groovy.transform.Canonical
2122
import groovy.transform.ToString
23+
import org.springframework.boot.context.properties.NestedConfigurationProperty
2224

2325
class GoogleConfigurationProperties {
2426
public static final int ASYNC_OPERATION_TIMEOUT_SECONDS_DEFAULT = 300
2527
public static final int ASYNC_OPERATION_MAX_POLLING_INTERVAL_SECONDS = 8
2628

29+
/**
30+
* health check related config settings
31+
*/
32+
@Canonical
33+
static class HealthConfig {
34+
/**
35+
* flag to toggle verifying account health check. by default, account health check is enabled.
36+
*/
37+
boolean verifyAccountHealth = true
38+
}
39+
2740
@ToString(includeNames = true)
2841
static class ManagedAccount extends GoogleCommonManagedAccount {
2942
boolean alphaListed
@@ -45,4 +58,7 @@ class GoogleConfigurationProperties {
4558
// Takes a list of regions you want indexed. Will default to indexing all regions if left
4659
// unspecified. An empty list will index no regions.
4760
List<String> defaultRegions
61+
62+
@NestedConfigurationProperty
63+
final HealthConfig health = new HealthConfig()
4864
}

clouddriver-google/src/main/groovy/com/netflix/spinnaker/config/GoogleConfiguration.groovy

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616

1717
package com.netflix.spinnaker.config
1818

19-
2019
import com.netflix.spinnaker.clouddriver.google.config.GoogleConfigurationProperties
2120
import com.netflix.spinnaker.clouddriver.google.config.GoogleCredentialsConfiguration
2221

@@ -49,6 +48,7 @@ class GoogleConfiguration {
4948
}
5049

5150
@Bean
51+
@ConditionalOnProperty("google.health.verifyAccountHealth")
5252
GoogleHealthIndicator googleHealthIndicator() {
5353
new GoogleHealthIndicator()
5454
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
/*
2+
* Copyright 2023 Armory, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.netflix.spinnaker.clouddriver.google.health
18+
19+
20+
import com.google.api.services.compute.model.Project
21+
import com.google.common.collect.ImmutableList
22+
import com.google.common.collect.ImmutableMap
23+
import com.netflix.spectator.api.NoopRegistry
24+
import com.netflix.spectator.api.Registry
25+
import com.netflix.spinnaker.clouddriver.google.provider.agent.StubComputeFactory
26+
import com.netflix.spinnaker.clouddriver.google.security.GoogleNamedAccountCredentials
27+
import com.netflix.spinnaker.credentials.CredentialsRepository
28+
import com.netflix.spinnaker.credentials.CredentialsTypeBaseConfiguration
29+
import org.springframework.boot.actuate.health.Status
30+
import org.springframework.context.ApplicationContext
31+
import spock.lang.Specification
32+
import spock.lang.Unroll
33+
34+
class GoogleHealthIndicatorSpec extends Specification {
35+
36+
private static final String ACCOUNT_NAME = "partypups"
37+
private static final String PROJECT = "myproject"
38+
private static final String REGION = "myregion"
39+
private static final String ZONE = REGION + "-myzone"
40+
private static final Registry REGISTRY = new NoopRegistry()
41+
42+
@Unroll
43+
def "health succeeds when google is reachable"() {
44+
setup:
45+
def applicationContext = Mock(ApplicationContext)
46+
def project = new Project()
47+
project.setName(PROJECT)
48+
49+
def compute = new StubComputeFactory()
50+
.setProjects(project)
51+
.create()
52+
53+
def googleNamedAccountCredentials =
54+
new GoogleNamedAccountCredentials.Builder()
55+
.project(PROJECT)
56+
.name(ACCOUNT_NAME)
57+
.compute(compute)
58+
.regionToZonesMap(ImmutableMap.of(REGION, ImmutableList.of(ZONE)))
59+
.build()
60+
61+
def credentials = [googleNamedAccountCredentials]
62+
def credentialsRepository = Stub(CredentialsRepository) {
63+
getAll() >> credentials
64+
}
65+
66+
def credentialsTypeBaseConfiguration = new CredentialsTypeBaseConfiguration(applicationContext, null)
67+
credentialsTypeBaseConfiguration.credentialsRepository = credentialsRepository
68+
69+
def indicator = new GoogleHealthIndicator()
70+
indicator.registry = REGISTRY
71+
indicator.credentialsTypeBaseConfiguration = credentialsTypeBaseConfiguration
72+
73+
when:
74+
indicator.checkHealth()
75+
def health = indicator.health()
76+
77+
then:
78+
health.status == Status.UP
79+
health.details.isEmpty()
80+
}
81+
82+
@Unroll
83+
def "health throws exception when google appears unreachable"() {
84+
setup:
85+
def applicationContext = Mock(ApplicationContext)
86+
def project = new Project()
87+
project.setName(PROJECT)
88+
89+
def compute = new StubComputeFactory()
90+
.setProjects(project)
91+
.setProjectException(new IOException("Read timed out"))
92+
.create()
93+
94+
def googleNamedAccountCredentials =
95+
new GoogleNamedAccountCredentials.Builder()
96+
.project(PROJECT)
97+
.name(ACCOUNT_NAME)
98+
.compute(compute)
99+
.regionToZonesMap(ImmutableMap.of(REGION, ImmutableList.of(ZONE)))
100+
.build()
101+
102+
def credentials = [googleNamedAccountCredentials]
103+
def credentialsRepository = Stub(CredentialsRepository) {
104+
getAll() >> credentials
105+
}
106+
107+
def credentialsTypeBaseConfiguration = new CredentialsTypeBaseConfiguration(applicationContext, null)
108+
credentialsTypeBaseConfiguration.credentialsRepository = credentialsRepository
109+
110+
def indicator = new GoogleHealthIndicator()
111+
indicator.registry = REGISTRY
112+
indicator.credentialsTypeBaseConfiguration = credentialsTypeBaseConfiguration
113+
114+
when:
115+
indicator.checkHealth()
116+
def health = indicator.health()
117+
118+
then:
119+
thrown(GoogleHealthIndicator.GoogleIOException)
120+
121+
health == null
122+
}
123+
}

clouddriver-google/src/test/groovy/com/netflix/spinnaker/clouddriver/google/provider/agent/StubComputeFactory.java

Lines changed: 44 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -32,19 +32,7 @@
3232
import com.google.api.client.testing.http.MockLowLevelHttpRequest;
3333
import com.google.api.client.testing.http.MockLowLevelHttpResponse;
3434
import com.google.api.services.compute.Compute;
35-
import com.google.api.services.compute.model.Autoscaler;
36-
import com.google.api.services.compute.model.AutoscalerAggregatedList;
37-
import com.google.api.services.compute.model.AutoscalerList;
38-
import com.google.api.services.compute.model.AutoscalersScopedList;
39-
import com.google.api.services.compute.model.Instance;
40-
import com.google.api.services.compute.model.InstanceAggregatedList;
41-
import com.google.api.services.compute.model.InstanceGroupManager;
42-
import com.google.api.services.compute.model.InstanceGroupManagerList;
43-
import com.google.api.services.compute.model.InstanceList;
44-
import com.google.api.services.compute.model.InstanceTemplate;
45-
import com.google.api.services.compute.model.InstanceTemplateList;
46-
import com.google.api.services.compute.model.InstancesScopedList;
47-
import com.google.api.services.compute.model.RegionInstanceGroupManagerList;
35+
import com.google.api.services.compute.model.*;
4836
import com.google.common.collect.ImmutableList;
4937
import com.google.common.collect.ImmutableListMultimap;
5038
import com.google.common.collect.ImmutableMap;
@@ -72,8 +60,10 @@ final class StubComputeFactory {
7260

7361
private static final JsonFactory JSON_FACTORY = JacksonFactory.getDefaultInstance();
7462

63+
private static final String COMPUTE_PATH_PREFIX = "/compute/[-.a-zA-Z0-9]+";
64+
7565
private static final String COMPUTE_PROJECT_PATH_PREFIX =
76-
"/compute/[-.a-zA-Z0-9]+/projects/[-.a-zA-Z0-9]+";
66+
COMPUTE_PATH_PREFIX + "/projects/[-.a-zA-Z0-9]+";
7767

7868
private static final Pattern BATCH_COMPUTE_PATTERN =
7969
Pattern.compile("/batch/compute/[-.a-zA-Z0-9]+");
@@ -114,11 +104,15 @@ final class StubComputeFactory {
114104
Pattern.compile(COMPUTE_PROJECT_PATH_PREFIX + "/regions/([-a-z0-9]+)/autoscalers");
115105
private static final Pattern AGGREGATED_AUTOSCALERS_PATTERN =
116106
Pattern.compile(COMPUTE_PROJECT_PATH_PREFIX + "/aggregated/autoscalers");
107+
private static final Pattern GET_PROJECT_PATTERN =
108+
Pattern.compile(COMPUTE_PATH_PREFIX + "/projects/([-.a-zA-Z0-9]+)");
117109

118110
private List<InstanceGroupManager> instanceGroupManagers = new ArrayList<>();
119111
private List<InstanceTemplate> instanceTemplates = new ArrayList<>();
120112
private List<Instance> instances = new ArrayList<>();
121113
private List<Autoscaler> autoscalers = new ArrayList<>();
114+
private List<Project> projects = new ArrayList<>();
115+
private Exception projectException;
122116

123117
StubComputeFactory setInstanceGroupManagers(InstanceGroupManager... instanceGroupManagers) {
124118
this.instanceGroupManagers = ImmutableList.copyOf(instanceGroupManagers);
@@ -140,6 +134,16 @@ StubComputeFactory setAutoscalers(Autoscaler... autoscalers) {
140134
return this;
141135
}
142136

137+
StubComputeFactory setProjects(Project... projects) {
138+
this.projects = ImmutableList.copyOf(projects);
139+
return this;
140+
}
141+
142+
StubComputeFactory setProjectException(Exception projectException) {
143+
this.projectException = projectException;
144+
return this;
145+
}
146+
143147
Compute create() {
144148
HttpTransport httpTransport =
145149
new StubHttpTransport()
@@ -166,7 +170,8 @@ Compute create() {
166170
.addGetResponse(
167171
LIST_REGIONAL_AUTOSCALERS_PATTERN,
168172
new PathBasedJsonResponseGenerator(this::regionalAutoscalerList))
169-
.addGetResponse(AGGREGATED_AUTOSCALERS_PATTERN, this::autoscalerAggregatedList);
173+
.addGetResponse(AGGREGATED_AUTOSCALERS_PATTERN, this::autoscalerAggregatedList)
174+
.addGetResponse(GET_PROJECT_PATTERN, this::project);
170175
return new Compute(
171176
httpTransport, JacksonFactory.getDefaultInstance(), /* httpRequestInitializer= */ null);
172177
}
@@ -322,6 +327,21 @@ private MockLowLevelHttpResponse autoscalerAggregatedList(LowLevelHttpRequest re
322327
return jsonResponse(new AutoscalerAggregatedList().setItems(autoscalers));
323328
}
324329

330+
private MockLowLevelHttpResponse project(MockLowLevelHttpRequest request) {
331+
if (projectException != null) {
332+
return errorResponse(500, projectException);
333+
}
334+
335+
Matcher matcher = GET_PROJECT_PATTERN.matcher(getPath(request));
336+
checkState(matcher.matches());
337+
String name = matcher.group(1);
338+
return projects.stream()
339+
.filter(project -> name.equals(project.getName()))
340+
.findFirst()
341+
.map(StubComputeFactory::jsonResponse)
342+
.orElse(errorResponse(404));
343+
}
344+
325345
private static <T> ImmutableListMultimap<String, T> aggregate(
326346
Collection<T> items, Function<T, String> zoneFunction, Function<T, String> regionFunction) {
327347
return items.stream()
@@ -345,9 +365,18 @@ private static <T> String getAggregateKey(
345365
}
346366

347367
private static MockLowLevelHttpResponse errorResponse(int statusCode) {
368+
return errorResponse(statusCode, null);
369+
}
370+
371+
private static MockLowLevelHttpResponse errorResponse(int statusCode, Exception exception) {
348372
GoogleJsonErrorContainer errorContainer = new GoogleJsonErrorContainer();
349373
GoogleJsonError error = new GoogleJsonError();
350374
error.setCode(statusCode);
375+
376+
if (exception != null) {
377+
error.setMessage(exception.getMessage());
378+
}
379+
351380
errorContainer.setError(error);
352381
return jsonResponse(statusCode, errorContainer);
353382
}

0 commit comments

Comments
 (0)