All Downloads are FREE. Search and download functionalities are using the official Maven repository.

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