com.netflix.spinnaker.clouddriver.google.provider.agent.GoogleNetworkLoadBalancerCachingAgent.groovy Maven / Gradle / Ivy
/*
* Copyright 2016 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.provider.agent
import com.fasterxml.jackson.databind.ObjectMapper
import com.google.api.client.googleapis.batch.json.JsonBatchCallback
import com.google.api.client.googleapis.json.GoogleJsonError
import com.google.api.client.http.HttpHeaders
import com.google.api.services.compute.ComputeRequest
import com.google.api.services.compute.model.ForwardingRule
import com.google.api.services.compute.model.ForwardingRuleList
import com.google.api.services.compute.model.InstanceReference
import com.netflix.spectator.api.Registry
import com.netflix.spinnaker.clouddriver.google.deploy.GCEUtil
import com.netflix.spinnaker.clouddriver.google.model.GoogleHealthCheck
import com.netflix.spinnaker.clouddriver.google.model.callbacks.Utils
import com.netflix.spinnaker.clouddriver.google.model.loadbalancing.GoogleLoadBalancer
import com.netflix.spinnaker.clouddriver.google.model.loadbalancing.GoogleNetworkLoadBalancer
import com.netflix.spinnaker.clouddriver.google.provider.agent.util.LoadBalancerHealthResolution
import com.netflix.spinnaker.clouddriver.google.provider.agent.util.PaginatedRequest
import com.netflix.spinnaker.clouddriver.google.provider.agent.util.TargetPoolHealthRequest
import com.netflix.spinnaker.clouddriver.google.security.GoogleNamedAccountCredentials
import com.netflix.spinnaker.clouddriver.google.batch.GoogleBatchRequest
import groovy.util.logging.Slf4j
import org.slf4j.LoggerFactory
@Slf4j
class GoogleNetworkLoadBalancerCachingAgent extends AbstractGoogleLoadBalancerCachingAgent {
/**
* Local cache of targetPoolInstanceHealth keyed by TargetPool name.
*
* It turns out that the types in the GCE Batch callbacks aren't the actual Compute
* types for some reason, which is why this map is String -> Object.
*/
Map tpNameToInstanceHealthsMap = [:]
Set queuedTpHealthRequests = new HashSet<>()
Set resolutions = new HashSet<>()
GoogleNetworkLoadBalancerCachingAgent(String clouddriverUserAgentApplicationName,
GoogleNamedAccountCredentials credentials,
ObjectMapper objectMapper,
Registry registry,
String region) {
super(clouddriverUserAgentApplicationName,
credentials,
objectMapper,
registry,
region)
}
@Override
List constructLoadBalancers(String onDemandLoadBalancerName = null) {
List loadBalancers = []
List failedLoadBalancers = []
// Reset the local getHealth caches/queues each caching agent cycle.
tpNameToInstanceHealthsMap = [:]
queuedTpHealthRequests = new HashSet<>()
resolutions = new HashSet<>()
GoogleBatchRequest forwardingRulesRequest = buildGoogleBatchRequest()
GoogleBatchRequest targetPoolsRequest = buildGoogleBatchRequest()
GoogleBatchRequest httpHealthChecksRequest = buildGoogleBatchRequest()
GoogleBatchRequest instanceHealthRequest = buildGoogleBatchRequest()
ForwardingRuleCallbacks forwardingRuleCallbacks = new ForwardingRuleCallbacks(
loadBalancers: loadBalancers,
failedLoadBalancers: failedLoadBalancers,
targetPoolsRequest: targetPoolsRequest,
httpHealthChecksRequest: httpHealthChecksRequest,
instanceHealthRequest: instanceHealthRequest,
)
if (onDemandLoadBalancerName) {
ForwardingRuleCallbacks.ForwardingRuleSingletonCallback frCallback = forwardingRuleCallbacks.newForwardingRuleSingletonCallback()
forwardingRulesRequest.queue(compute.forwardingRules().get(project, region, onDemandLoadBalancerName), frCallback)
} else {
ForwardingRuleCallbacks.ForwardingRuleListCallback frlCallback = forwardingRuleCallbacks.newForwardingRuleListCallback()
new PaginatedRequest(this) {
@Override
protected ComputeRequest request(String pageToken) {
return compute.forwardingRules().list(project, region).setPageToken(pageToken)
}
@Override
protected String getNextPageToken(ForwardingRuleList forwardingRuleList) {
return forwardingRuleList.getNextPageToken()
}
}.queue(forwardingRulesRequest, frlCallback, "NetworkLoadBalancerCaching.forwardingRules")
}
executeIfRequestsAreQueued(forwardingRulesRequest, "NetworkLoadBalancerCaching.forwardingRules")
executeIfRequestsAreQueued(targetPoolsRequest, "NetworkLoadBalancerCaching.targetPools")
executeIfRequestsAreQueued(httpHealthChecksRequest, "NetworkLoadBalancerCaching.httpHealthChecks")
executeIfRequestsAreQueued(instanceHealthRequest, "NetworkLoadBalancerCaching.instanceHealth")
resolutions.each { LoadBalancerHealthResolution resolution ->
tpNameToInstanceHealthsMap.get(resolution.getTarget()).each { targetPoolHealth ->
GCEUtil.handleHealthObject(resolution.getGoogleLoadBalancer(), targetPoolHealth)
}
}
return loadBalancers.findAll {!(it.name in failedLoadBalancers)}
}
class ForwardingRuleCallbacks {
List loadBalancers
List failedLoadBalancers = []
GoogleBatchRequest targetPoolsRequest
// Pass through objects
GoogleBatchRequest httpHealthChecksRequest
GoogleBatchRequest instanceHealthRequest
ForwardingRuleSingletonCallback newForwardingRuleSingletonCallback() {
return new ForwardingRuleSingletonCallback()
}
ForwardingRuleListCallback newForwardingRuleListCallback() {
return new ForwardingRuleListCallback()
}
class ForwardingRuleSingletonCallback extends JsonBatchCallback {
@Override
void onFailure(GoogleJsonError e, HttpHeaders responseHeaders) throws IOException {
// 404 is thrown if the forwarding rule does not exist in the given region. Any other exception needs to be propagated.
if (e.code != 404) {
def errorJson = new ObjectMapper().writerWithDefaultPrettyPrinter().writeValueAsString(e)
log.error errorJson
}
}
@Override
void onSuccess(ForwardingRule forwardingRule, HttpHeaders responseHeaders) throws IOException {
if (forwardingRule.target) {
cacheRemainderOfLoadBalancerResourceGraph(forwardingRule)
} else {
throw new IllegalArgumentException("Not responsible for on demand caching of load balancers without target pools.")
}
}
}
class ForwardingRuleListCallback extends JsonBatchCallback implements FailureLogger {
@Override
void onSuccess(ForwardingRuleList forwardingRuleList, HttpHeaders responseHeaders) throws IOException {
forwardingRuleList?.items?.each { ForwardingRule forwardingRule ->
if (forwardingRule.target) {
cacheRemainderOfLoadBalancerResourceGraph(forwardingRule)
}
}
}
@Override
void onFailure(GoogleJsonError e, HttpHeaders responseHeaders) throws IOException {
LoggerFactory.getLogger(this.class).error e.getMessage()
}
}
void cacheRemainderOfLoadBalancerResourceGraph(ForwardingRule forwardingRule) {
def newLoadBalancer = new GoogleNetworkLoadBalancer(
name: forwardingRule.name,
account: accountName,
region: region,
createdTime: Utils.getTimeFromTimestamp(forwardingRule.creationTimestamp),
ipAddress: forwardingRule.IPAddress,
ipProtocol: forwardingRule.IPProtocol,
portRange: forwardingRule.portRange,
healths: [])
loadBalancers << newLoadBalancer
def forwardingRuleTokens = forwardingRule.target.split("/")
if (forwardingRuleTokens[forwardingRuleTokens.size() - 2] != "targetVpnGateways"
&& forwardingRuleTokens[forwardingRuleTokens.size() - 2] != "targetInstances") {
def targetPoolName = Utils.getLocalName(forwardingRule.target)
def targetPoolsCallback = new TargetPoolCallback(
googleLoadBalancer: newLoadBalancer,
httpHealthChecksRequest: httpHealthChecksRequest,
instanceHealthRequest: instanceHealthRequest,
subject: newLoadBalancer.name,
failedSubjects: failedLoadBalancers
)
targetPoolsRequest.queue(compute.targetPools().get(project, region, targetPoolName), targetPoolsCallback)
}
}
}
class TargetPoolCallback extends JsonBatchCallback implements FailedSubjectChronicler {
GoogleNetworkLoadBalancer googleLoadBalancer
GoogleBatchRequest httpHealthChecksRequest
GoogleBatchRequest instanceHealthRequest
@Override
void onSuccess(TargetPool targetPool, HttpHeaders responseHeaders) throws IOException {
googleLoadBalancer.targetPool = targetPool?.selfLink
googleLoadBalancer.sessionAffinity = targetPool?.sessionAffinity
boolean hasHealthChecks = targetPool?.healthChecks
targetPool?.healthChecks?.each { def healthCheckUrl ->
def localHealthCheckName = Utils.getLocalName(healthCheckUrl)
def httpHealthCheckCallback = new HttpHealthCheckCallback(
googleLoadBalancer: googleLoadBalancer,
targetPool: targetPool,
instanceHealthRequest: instanceHealthRequest,
subject: googleLoadBalancer.name,
failedSubjects: failedSubjects
)
httpHealthChecksRequest.queue(compute.httpHealthChecks().get(project, localHealthCheckName), httpHealthCheckCallback)
}
if (!hasHealthChecks) {
new TargetPoolInstanceHealthCallInvoker(googleLoadBalancer: googleLoadBalancer,
targetPool: targetPool,
instanceHealthRequest: instanceHealthRequest).doCall()
}
}
@Override
void onFailure(GoogleJsonError e, HttpHeaders responseHeaders) throws IOException {
log.warn("Failed to read a component of subject ${subject}. The platform error message was:\n ${e.getMessage()}. \nReporting it as 'Failed' to the caching agent. ")
if (failedSubjects != null) {
failedSubjects << subject
}
}
}
class HttpHealthCheckCallback extends JsonBatchCallback implements FailedSubjectChronicler {
GoogleNetworkLoadBalancer googleLoadBalancer
def targetPool
GoogleBatchRequest instanceHealthRequest
@Override
void onSuccess(HttpHealthCheck httpHealthCheck, HttpHeaders responseHeaders) throws IOException {
if (httpHealthCheck) {
googleLoadBalancer.healthCheck = new GoogleHealthCheck(
name: httpHealthCheck.name,
healthCheckType: GoogleHealthCheck.HealthCheckType.HTTP, // Uses HTTP even though it's a network LB -- https://cloud.google.com/compute/docs/load-balancing/network
port: httpHealthCheck.port,
requestPath: httpHealthCheck.requestPath,
checkIntervalSec: httpHealthCheck.checkIntervalSec,
timeoutSec: httpHealthCheck.timeoutSec,
unhealthyThreshold: httpHealthCheck.unhealthyThreshold,
healthyThreshold: httpHealthCheck.healthyThreshold)
}
new TargetPoolInstanceHealthCallInvoker(googleLoadBalancer: googleLoadBalancer,
targetPool: targetPool,
instanceHealthRequest: instanceHealthRequest).doCall()
}
@Override
void onFailure(GoogleJsonError e, HttpHeaders responseHeaders) throws IOException {
log.warn("Failed to read a component of subject ${subject}. The platform error message was:\n ${e.getMessage()}. \nReporting it as 'Failed' to the caching agent. ")
if (failedSubjects != null) {
failedSubjects << subject
}
}
}
class TargetPoolInstanceHealthCallInvoker {
GoogleNetworkLoadBalancer googleLoadBalancer
def targetPool
GoogleBatchRequest instanceHealthRequest
def doCall() {
def region = Utils.getLocalName(targetPool.region as String)
def targetPoolName = targetPool.name as String
targetPool?.instances?.each { String instanceUrl ->
def instanceReference = new InstanceReference(instance: instanceUrl)
def instanceHealthCallback = new TargetPoolInstanceHealthCallback(targetPoolName: targetPoolName)
// Make only the group health request calls we need to.
TargetPoolHealthRequest tphr = new TargetPoolHealthRequest(project, region, targetPoolName, instanceReference.getInstance())
if (!queuedTpHealthRequests.contains(tphr)) {
// The groupHealthCallback updates the local cache along with running handleHealthObject.
log.debug("Queueing a batch call for getHealth(): {}", tphr)
queuedTpHealthRequests.add(tphr)
instanceHealthRequest
.queue(compute.targetPools().getHealth(project, region, targetPoolName, instanceReference),
instanceHealthCallback)
} else {
log.debug("Passing, batch call result cached for getHealth(): {}", tphr)
}
resolutions.add(new LoadBalancerHealthResolution(googleLoadBalancer, targetPoolName))
}
}
}
class TargetPoolInstanceHealthCallback extends JsonBatchCallback {
String targetPoolName
@Override
void onSuccess(TargetPoolInstanceHealth targetPoolInstanceHealth, HttpHeaders responseHeaders) throws IOException {
if (!tpNameToInstanceHealthsMap.containsKey(targetPoolName)) {
tpNameToInstanceHealthsMap.put(targetPoolName, [targetPoolInstanceHealth])
} else {
tpNameToInstanceHealthsMap.get(targetPoolName) << targetPoolInstanceHealth
}
}
@Override
void onFailure(GoogleJsonError e, HttpHeaders responseHeaders) throws IOException {
log.error "Error while querying target pool instance health: {}", e.getMessage()
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy