Skip to content

Commit

Permalink
feat(gcp): provide a configurable option to bypass gcp account health…
Browse files Browse the repository at this point in the history
… 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>
  • Loading branch information
3 people committed Nov 22, 2023
1 parent 75e00c7 commit ef4bbdc
Show file tree
Hide file tree
Showing 4 changed files with 184 additions and 16 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,25 @@ package com.netflix.spinnaker.clouddriver.google.config

import com.netflix.spinnaker.clouddriver.consul.config.ConsulConfig
import com.netflix.spinnaker.clouddriver.googlecommon.config.GoogleCommonManagedAccount
import groovy.transform.Canonical
import groovy.transform.ToString
import org.springframework.boot.context.properties.NestedConfigurationProperty

class GoogleConfigurationProperties {
public static final int ASYNC_OPERATION_TIMEOUT_SECONDS_DEFAULT = 300
public static final int ASYNC_OPERATION_MAX_POLLING_INTERVAL_SECONDS = 8

/**
* health check related config settings
*/
@Canonical
static class HealthConfig {
/**
* flag to toggle verifying account health check. by default, account health check is enabled.
*/
boolean verifyAccountHealth = true
}

@ToString(includeNames = true)
static class ManagedAccount extends GoogleCommonManagedAccount {
boolean alphaListed
Expand All @@ -45,4 +58,7 @@ class GoogleConfigurationProperties {
// Takes a list of regions you want indexed. Will default to indexing all regions if left
// unspecified. An empty list will index no regions.
List<String> defaultRegions

@NestedConfigurationProperty
final HealthConfig health = new HealthConfig()
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@

package com.netflix.spinnaker.config


import com.netflix.spinnaker.clouddriver.google.config.GoogleConfigurationProperties
import com.netflix.spinnaker.clouddriver.google.config.GoogleCredentialsConfiguration

Expand Down Expand Up @@ -49,6 +48,7 @@ class GoogleConfiguration {
}

@Bean
@ConditionalOnProperty("google.health.verifyAccountHealth")
GoogleHealthIndicator googleHealthIndicator() {
new GoogleHealthIndicator()
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
/*
* Copyright 2023 Armory, 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.health


import com.google.api.services.compute.model.Project
import com.google.common.collect.ImmutableList
import com.google.common.collect.ImmutableMap
import com.netflix.spectator.api.NoopRegistry
import com.netflix.spectator.api.Registry
import com.netflix.spinnaker.clouddriver.google.provider.agent.StubComputeFactory
import com.netflix.spinnaker.clouddriver.google.security.GoogleNamedAccountCredentials
import com.netflix.spinnaker.credentials.CredentialsRepository
import com.netflix.spinnaker.credentials.CredentialsTypeBaseConfiguration
import org.springframework.boot.actuate.health.Status
import org.springframework.context.ApplicationContext
import spock.lang.Specification
import spock.lang.Unroll

class GoogleHealthIndicatorSpec extends Specification {

private static final String ACCOUNT_NAME = "partypups"
private static final String PROJECT = "myproject"
private static final String REGION = "myregion"
private static final String ZONE = REGION + "-myzone"
private static final Registry REGISTRY = new NoopRegistry()

@Unroll
def "health succeeds when google is reachable"() {
setup:
def applicationContext = Mock(ApplicationContext)
def project = new Project()
project.setName(PROJECT)

def compute = new StubComputeFactory()
.setProjects(project)
.create()

def googleNamedAccountCredentials =
new GoogleNamedAccountCredentials.Builder()
.project(PROJECT)
.name(ACCOUNT_NAME)
.compute(compute)
.regionToZonesMap(ImmutableMap.of(REGION, ImmutableList.of(ZONE)))
.build()

def credentials = [googleNamedAccountCredentials]
def credentialsRepository = Stub(CredentialsRepository) {
getAll() >> credentials
}

def credentialsTypeBaseConfiguration = new CredentialsTypeBaseConfiguration(applicationContext, null)
credentialsTypeBaseConfiguration.credentialsRepository = credentialsRepository

def indicator = new GoogleHealthIndicator()
indicator.registry = REGISTRY
indicator.credentialsTypeBaseConfiguration = credentialsTypeBaseConfiguration

when:
indicator.checkHealth()
def health = indicator.health()

then:
health.status == Status.UP
health.details.isEmpty()
}

@Unroll
def "health throws exception when google appears unreachable"() {
setup:
def applicationContext = Mock(ApplicationContext)
def project = new Project()
project.setName(PROJECT)

def compute = new StubComputeFactory()
.setProjects(project)
.setProjectException(new IOException("Read timed out"))
.create()

def googleNamedAccountCredentials =
new GoogleNamedAccountCredentials.Builder()
.project(PROJECT)
.name(ACCOUNT_NAME)
.compute(compute)
.regionToZonesMap(ImmutableMap.of(REGION, ImmutableList.of(ZONE)))
.build()

def credentials = [googleNamedAccountCredentials]
def credentialsRepository = Stub(CredentialsRepository) {
getAll() >> credentials
}

def credentialsTypeBaseConfiguration = new CredentialsTypeBaseConfiguration(applicationContext, null)
credentialsTypeBaseConfiguration.credentialsRepository = credentialsRepository

def indicator = new GoogleHealthIndicator()
indicator.registry = REGISTRY
indicator.credentialsTypeBaseConfiguration = credentialsTypeBaseConfiguration

when:
indicator.checkHealth()
def health = indicator.health()

then:
thrown(GoogleHealthIndicator.GoogleIOException)

health == null
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,19 +32,7 @@
import com.google.api.client.testing.http.MockLowLevelHttpRequest;
import com.google.api.client.testing.http.MockLowLevelHttpResponse;
import com.google.api.services.compute.Compute;
import com.google.api.services.compute.model.Autoscaler;
import com.google.api.services.compute.model.AutoscalerAggregatedList;
import com.google.api.services.compute.model.AutoscalerList;
import com.google.api.services.compute.model.AutoscalersScopedList;
import com.google.api.services.compute.model.Instance;
import com.google.api.services.compute.model.InstanceAggregatedList;
import com.google.api.services.compute.model.InstanceGroupManager;
import com.google.api.services.compute.model.InstanceGroupManagerList;
import com.google.api.services.compute.model.InstanceList;
import com.google.api.services.compute.model.InstanceTemplate;
import com.google.api.services.compute.model.InstanceTemplateList;
import com.google.api.services.compute.model.InstancesScopedList;
import com.google.api.services.compute.model.RegionInstanceGroupManagerList;
import com.google.api.services.compute.model.*;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableListMultimap;
import com.google.common.collect.ImmutableMap;
Expand Down Expand Up @@ -72,8 +60,10 @@ final class StubComputeFactory {

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

private static final String COMPUTE_PATH_PREFIX = "/compute/[-.a-zA-Z0-9]+";

private static final String COMPUTE_PROJECT_PATH_PREFIX =
"/compute/[-.a-zA-Z0-9]+/projects/[-.a-zA-Z0-9]+";
COMPUTE_PATH_PREFIX + "/projects/[-.a-zA-Z0-9]+";

private static final Pattern BATCH_COMPUTE_PATTERN =
Pattern.compile("/batch/compute/[-.a-zA-Z0-9]+");
Expand Down Expand Up @@ -114,11 +104,15 @@ final class StubComputeFactory {
Pattern.compile(COMPUTE_PROJECT_PATH_PREFIX + "/regions/([-a-z0-9]+)/autoscalers");
private static final Pattern AGGREGATED_AUTOSCALERS_PATTERN =
Pattern.compile(COMPUTE_PROJECT_PATH_PREFIX + "/aggregated/autoscalers");
private static final Pattern GET_PROJECT_PATTERN =
Pattern.compile(COMPUTE_PATH_PREFIX + "/projects/([-.a-zA-Z0-9]+)");

private List<InstanceGroupManager> instanceGroupManagers = new ArrayList<>();
private List<InstanceTemplate> instanceTemplates = new ArrayList<>();
private List<Instance> instances = new ArrayList<>();
private List<Autoscaler> autoscalers = new ArrayList<>();
private List<Project> projects = new ArrayList<>();
private Exception projectException;

StubComputeFactory setInstanceGroupManagers(InstanceGroupManager... instanceGroupManagers) {
this.instanceGroupManagers = ImmutableList.copyOf(instanceGroupManagers);
Expand All @@ -140,6 +134,16 @@ StubComputeFactory setAutoscalers(Autoscaler... autoscalers) {
return this;
}

StubComputeFactory setProjects(Project... projects) {
this.projects = ImmutableList.copyOf(projects);
return this;
}

StubComputeFactory setProjectException(Exception projectException) {
this.projectException = projectException;
return this;
}

Compute create() {
HttpTransport httpTransport =
new StubHttpTransport()
Expand All @@ -166,7 +170,8 @@ Compute create() {
.addGetResponse(
LIST_REGIONAL_AUTOSCALERS_PATTERN,
new PathBasedJsonResponseGenerator(this::regionalAutoscalerList))
.addGetResponse(AGGREGATED_AUTOSCALERS_PATTERN, this::autoscalerAggregatedList);
.addGetResponse(AGGREGATED_AUTOSCALERS_PATTERN, this::autoscalerAggregatedList)
.addGetResponse(GET_PROJECT_PATTERN, this::project);
return new Compute(
httpTransport, JacksonFactory.getDefaultInstance(), /* httpRequestInitializer= */ null);
}
Expand Down Expand Up @@ -322,6 +327,21 @@ private MockLowLevelHttpResponse autoscalerAggregatedList(LowLevelHttpRequest re
return jsonResponse(new AutoscalerAggregatedList().setItems(autoscalers));
}

private MockLowLevelHttpResponse project(MockLowLevelHttpRequest request) {
if (projectException != null) {
return errorResponse(500, projectException);
}

Matcher matcher = GET_PROJECT_PATTERN.matcher(getPath(request));
checkState(matcher.matches());
String name = matcher.group(1);
return projects.stream()
.filter(project -> name.equals(project.getName()))
.findFirst()
.map(StubComputeFactory::jsonResponse)
.orElse(errorResponse(404));
}

private static <T> ImmutableListMultimap<String, T> aggregate(
Collection<T> items, Function<T, String> zoneFunction, Function<T, String> regionFunction) {
return items.stream()
Expand All @@ -345,9 +365,18 @@ private static <T> String getAggregateKey(
}

private static MockLowLevelHttpResponse errorResponse(int statusCode) {
return errorResponse(statusCode, null);
}

private static MockLowLevelHttpResponse errorResponse(int statusCode, Exception exception) {
GoogleJsonErrorContainer errorContainer = new GoogleJsonErrorContainer();
GoogleJsonError error = new GoogleJsonError();
error.setCode(statusCode);

if (exception != null) {
error.setMessage(exception.getMessage());
}

errorContainer.setError(error);
return jsonResponse(statusCode, errorContainer);
}
Expand Down

0 comments on commit ef4bbdc

Please sign in to comment.