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

com.netflix.spinnaker.clouddriver.google.provider.view.GoogleClusterProvider.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.view

import com.fasterxml.jackson.databind.ObjectMapper
import com.google.common.collect.ImmutableSet
import com.netflix.spinnaker.cats.cache.Cache
import com.netflix.spinnaker.cats.cache.CacheData
import com.netflix.spinnaker.cats.cache.RelationshipCacheFilter
import com.netflix.spinnaker.clouddriver.consul.provider.ConsulProviderUtils
import com.netflix.spinnaker.clouddriver.google.GoogleCloudProvider
import com.netflix.spinnaker.clouddriver.google.cache.Keys
import com.netflix.spinnaker.clouddriver.google.model.*
import com.netflix.spinnaker.clouddriver.google.model.callbacks.Utils
import com.netflix.spinnaker.clouddriver.google.model.health.GoogleLoadBalancerHealth
import com.netflix.spinnaker.clouddriver.google.model.loadbalancing.GoogleHttpLoadBalancer
import com.netflix.spinnaker.clouddriver.google.model.loadbalancing.GoogleInternalHttpLoadBalancer
import com.netflix.spinnaker.clouddriver.google.model.loadbalancing.GoogleInternalLoadBalancer
import com.netflix.spinnaker.clouddriver.google.model.loadbalancing.GoogleLoadBalancer
import com.netflix.spinnaker.clouddriver.google.model.loadbalancing.GoogleLoadBalancerType
import com.netflix.spinnaker.clouddriver.google.model.loadbalancing.GoogleNetworkLoadBalancer
import com.netflix.spinnaker.clouddriver.google.model.loadbalancing.GoogleSslLoadBalancer
import com.netflix.spinnaker.clouddriver.google.model.loadbalancing.GoogleTcpLoadBalancer
import com.netflix.spinnaker.clouddriver.model.ClusterProvider
import groovy.util.logging.Slf4j
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.stereotype.Component

import static com.netflix.spinnaker.clouddriver.google.cache.Keys.Namespace.*

@Component
@Slf4j
class GoogleClusterProvider implements ClusterProvider {
  @Autowired
  GoogleCloudProvider googleCloudProvider

  @Autowired
  Cache cacheView

  @Autowired
  ObjectMapper objectMapper

  @Autowired
  GoogleApplicationProvider applicationProvider

  @Autowired
  GoogleInstanceProvider instanceProvider

  @Autowired
  GoogleSecurityGroupProvider securityGroupProvider

  @Override
  Map> getClusters() {
    cacheView.getAll(CLUSTERS.ns).groupBy { it.accountName }
  }

  @Override
  Map> getClusterDetails(String applicationName) {
    getClusters(applicationName, true /* detailed */).collectEntries { k, v -> [k, new HashSet<>(v)] }
  }

  @Override
  Map> getClusterSummaries(String applicationName) {
    getClusters(applicationName, false /* detailed */).collectEntries { k, v -> [k, new HashSet<>(v)] }
  }

  Map> getClusters(String applicationName, boolean includeInstanceDetails) {
    GoogleApplicationProvider.ApplicationCacheData applicationCacheData = applicationProvider.getApplicationCacheData(applicationName)

    if (applicationCacheData == null) {
      return new HashMap<>()
    }

    Set clusterIdentifiers = applicationCacheData.getClusterIdentifiers();
    Collection clusterCacheData = cacheView.getAll(
      CLUSTERS.ns,
      clusterIdentifiers,
      RelationshipCacheFilter.include(SERVER_GROUPS.ns)
    )

    Set instanceIdentifiers = includeInstanceDetails ?
      applicationCacheData.getInstanceIdentifiers() :
      Collections.emptySet()
    Collection instanceCacheData = instanceProvider.getInstanceCacheData(instanceIdentifiers)

    Map> clustersByAccount = new HashMap<>()
    Map> securityGroupsByAccount = new HashMap<>()

    clusterCacheData.each { cacheData ->
      String accountName = cacheData.getAttributes().get("accountName")
      Set accountSecurityGroups = securityGroupsByAccount.computeIfAbsent(
        accountName,
        { a -> securityGroupProvider.getAllByAccount(false, accountName) }
      )
      Set accountClusters = clustersByAccount.computeIfAbsent(
        accountName,
        { a -> new HashSet() }
      )
      accountClusters.add(clusterFromCacheData(cacheData, instanceCacheData, accountSecurityGroups))
    }

    return clustersByAccount
  }

  @Override
  Set getClusters(String applicationName, String account) {
    getClusterDetails(applicationName)?.get(account)
  }

  @Override
  GoogleCluster.View getCluster(String application, String account, String name, boolean includeDetails) {
    CacheData clusterData = cacheView.get(
      CLUSTERS.ns,
      Keys.getClusterKey(account, application, name),
      RelationshipCacheFilter.include(SERVER_GROUPS.ns, INSTANCES.ns))

    Set allClusterInstanceKeys = includeDetails ? (clusterData?.relationships?.get(INSTANCES.ns) ?: []) : [] as Set

    return clusterData ? clusterFromCacheData(clusterData, allClusterInstanceKeys) : null
  }

  @Override
  GoogleCluster.View getCluster(String applicationName, String accountName, String clusterName) {
    return getCluster(applicationName, accountName, clusterName, true)
  }

  @Override
  GoogleServerGroup.View getServerGroup(String account, String region, String name, boolean includeDetails) {
    def cacheData = searchCacheForServerGroup(Keys.getServerGroupKey(name, "*", account, region))

    if (!cacheData) {
      // No regional server group was found, so attempt to query for all zonal server groups in the region.
      cacheData = searchCacheForServerGroup(Keys.getServerGroupKey(name, "*", account, region, "*"))
    }

    if (cacheData) {
      def securityGroups = securityGroupProvider.getAllByAccount(false, account)

      def instanceKeys = cacheData.relationships?.get(INSTANCES.ns) ?: []
      def instances = instanceProvider.getInstances(account, instanceKeys as List, securityGroups)
      def loadBalancers = loadBalancersFromKeys(cacheData.relationships[LOAD_BALANCERS.ns] as List)
      return serverGroupFromCacheData(cacheData, account, instances, securityGroups, loadBalancers)?.view
    }
  }

  private CacheData searchCacheForServerGroup(String pattern) {
    def identifiers = cacheView.filterIdentifiers(SERVER_GROUPS.ns, pattern)
    def cacheDataResults = cacheView.getAll(SERVER_GROUPS.ns,
      identifiers,
      RelationshipCacheFilter.include(LOAD_BALANCERS.ns, INSTANCES.ns))

    if (cacheDataResults) {
      return cacheDataResults.first()
    }
    return null
  }

  @Override
  GoogleServerGroup.View getServerGroup(String account, String region, String name) {
    return getServerGroup(account, region, name, true)
  }

  @Override
  String getCloudProviderId() {
    return googleCloudProvider.id
  }

  @Override
  boolean supportsMinimalClusters() {
    return false
  }

  GoogleCluster.View clusterFromCacheData(CacheData clusterCacheData, Set instanceKeySuperSet) {
    return clusterFromCacheData(
      clusterCacheData,
      instanceProvider.getInstanceCacheData(instanceKeySuperSet),
      securityGroupProvider.getAllByAccount(false, (String) clusterCacheData.getAttributes().get("accountName"))
    )
  }

  GoogleCluster.View clusterFromCacheData(
    CacheData clusterCacheData,
    Collection instanceCacheDataSuperSet,
    Set securityGroups)
  {
    GoogleCluster.View clusterView = objectMapper.convertValue(clusterCacheData.attributes, GoogleCluster)?.view

    def serverGroupKeys = clusterCacheData.relationships[SERVER_GROUPS.ns]
    if (serverGroupKeys) {
      log.debug("Server group keys from cluster relationships: ${serverGroupKeys}")
      def filter = RelationshipCacheFilter.include(LOAD_BALANCERS.ns)

      def serverGroupData = cacheView.getAll(SERVER_GROUPS.ns, serverGroupKeys, filter)
      log.debug("Retrieved cache data for server groups: ${serverGroupData?.collect { it?.attributes?.name }}")

      def instanceCacheData = instanceCacheDataSuperSet.findAll { instance ->
        instance.relationships.get(CLUSTERS.ns)?.collect { Keys.parse(it).cluster }?.any { it.contains(clusterView.name) }
      }

      def instances = instanceProvider.getInstancesFromCacheData(clusterView.accountName, instanceCacheData, securityGroups)

      def loadBalancerKeys = serverGroupData.collect { serverGroup ->
        serverGroup.relationships[LOAD_BALANCERS.ns] as List
      }.flatten()
      def loadBalancers = loadBalancersFromKeys(loadBalancerKeys as List)

      serverGroupData.each { CacheData serverGroupCacheData ->
        GoogleServerGroup serverGroup = serverGroupFromCacheData(serverGroupCacheData, clusterView.accountName, instances, securityGroups, loadBalancers)
        clusterView.serverGroups << serverGroup.view
        clusterView.loadBalancers.addAll(serverGroup.loadBalancers)
      }
      log.debug("Server groups added to cluster: ${clusterView?.serverGroups?.collect { it?.name }}")
    }

    clusterView
  }

  Set loadBalancersFromKeys(List loadBalancerKeys) {
    return cacheView.getAll(LOAD_BALANCERS.ns, loadBalancerKeys).collect {
      def loadBalancer = null
      switch (GoogleLoadBalancerType.valueOf(it.attributes?.type as String)) {
        case GoogleLoadBalancerType.INTERNAL:
          loadBalancer = objectMapper.convertValue(it.attributes, GoogleInternalLoadBalancer)
          break
        case GoogleLoadBalancerType.HTTP:
          loadBalancer = objectMapper.convertValue(it.attributes, GoogleHttpLoadBalancer)
          break
        case GoogleLoadBalancerType.INTERNAL_MANAGED:
          loadBalancer = objectMapper.convertValue(it.attributes, GoogleInternalHttpLoadBalancer)
          break
        case GoogleLoadBalancerType.NETWORK:
          loadBalancer = objectMapper.convertValue(it.attributes, GoogleNetworkLoadBalancer)
          break
        case GoogleLoadBalancerType.SSL:
          loadBalancer = objectMapper.convertValue(it.attributes, GoogleSslLoadBalancer)
          break
        case GoogleLoadBalancerType.TCP:
          loadBalancer = objectMapper.convertValue(it.attributes, GoogleTcpLoadBalancer)
          break
        default:
          loadBalancer = null
          break
      }
      return loadBalancer
    } as Set
  }

  GoogleServerGroup serverGroupFromCacheData(CacheData cacheData,
                                             String account,
                                             List instances,
                                             Set securityGroups,
                                             Set loadBalancers) {
    GoogleServerGroup serverGroup = objectMapper.convertValue(cacheData.attributes, GoogleServerGroup)
    serverGroup.account = account

    def loadBalancerKeys = cacheData.relationships[LOAD_BALANCERS.ns]
    loadBalancers = loadBalancers.findAll { loadBalancer ->
      def loadBalancerKey = Keys.getLoadBalancerKey(loadBalancer.region, loadBalancer.account, loadBalancer.name)
      return loadBalancerKeys?.contains(loadBalancerKey) ? loadBalancer : null
    }
    serverGroup.loadBalancers = loadBalancers*.view

    serverGroup.securityGroups = GoogleSecurityGroupProvider.getMatchingSecurityGroupNames(
        account,
        securityGroups,
        serverGroup.instanceTemplateTags,
        serverGroup.instanceTemplateServiceAccounts,
        serverGroup.networkName)

    if (instances) {
      serverGroup.instances = instances.findAll { GoogleInstance instance ->
        instance.serverGroup == serverGroup.name && instance.region == serverGroup.region
      }

      serverGroup.instances.each { GoogleInstance instance ->
        def foundHealths = getLoadBalancerHealths(instance.name, loadBalancers as List)
        if (foundHealths) {
          instance.loadBalancerHealths = foundHealths
        }
      }
    }

    // Time to aggregate health states that can't be computed during the server group fetch operation.
    def internalLoadBalancers = loadBalancers.findAll { it.type == GoogleLoadBalancerType.INTERNAL }
    def internalDisabledStates = internalLoadBalancers.collect { loadBalancer ->
      Utils.determineInternalLoadBalancerDisabledState(loadBalancer, serverGroup)
    }

    def httpLoadBalancers = loadBalancers.findAll { it.type == GoogleLoadBalancerType.HTTP }
    def httpDisabledStates = httpLoadBalancers.collect { loadBalancer ->
        Utils.determineHttpLoadBalancerDisabledState(loadBalancer, serverGroup)
    }

    def internalHttpLoadBalancers = loadBalancers.findAll { it.type == GoogleLoadBalancerType.INTERNAL_MANAGED }
    def internalHttpDisabledStates = internalHttpLoadBalancers.collect { loadBalancer ->
        Utils.determineInternalHttpLoadBalancerDisabledState(loadBalancer, serverGroup)
    }

    def sslLoadBalancers = loadBalancers.findAll { it.type == GoogleLoadBalancerType.SSL }
    def sslDisabledStates = sslLoadBalancers.collect { loadBalancer ->
      Utils.determineSslLoadBalancerDisabledState(loadBalancer, serverGroup)
    }

    def tcpLoadBalancers = loadBalancers.findAll { it.type == GoogleLoadBalancerType.TCP }
    def tcpDisabledStates = tcpLoadBalancers.collect { loadBalancer ->
      Utils.determineTcpLoadBalancerDisabledState(loadBalancer, serverGroup)
    }

    // Health states for Consul.
    def consulNodes = serverGroup.instances?.collect { it.consulNode } ?: []
    def consulDiscoverable = ConsulProviderUtils.consulServerGroupDiscoverable(consulNodes)
    def consulDisabled = false
    if (consulDiscoverable) {
      consulDisabled = ConsulProviderUtils.serverGroupDisabled(consulNodes)
    }

    def isDisabled = true
    // TODO: Extend this for future load balancers that calculate disabled state after caching.
    def anyDisabledStates = internalDisabledStates || httpDisabledStates || sslDisabledStates || tcpDisabledStates
    def disabledStatesSizeMatch = internalDisabledStates.size() + httpDisabledStates.size() + sslDisabledStates.size() + tcpDisabledStates.size() == loadBalancers.size()
    def excludesNetwork = anyDisabledStates && disabledStatesSizeMatch

    if (httpDisabledStates) {
      isDisabled &= httpDisabledStates.every { it }
    }
    if (internalDisabledStates) {
      isDisabled &= internalDisabledStates.every { it }
    }
    if (internalHttpDisabledStates) {
      isDisabled &= internalHttpDisabledStates.every { it }
    }
    if (sslDisabledStates) {
      isDisabled &= sslDisabledStates.every { it }
    }
    if (tcpDisabledStates) {
      isDisabled &= tcpDisabledStates.every { it }
    }
    serverGroup.disabled = excludesNetwork ? isDisabled : isDisabled && serverGroup.disabled

    // Now that disabled is set based on L7 & L4 state, we need to take Consul into account.
    if (consulDiscoverable) {
      // If there are no load balancers to determine enable/disabled status we rely on Consul exclusively.
      if (loadBalancers.size() == 0) {
          serverGroup.disabled = true
      }
      // If the server group is disabled, but Consul isn't, we say the server group is discoverable.
      // If the server group isn't disabled, but Consul is, we say the server group can be reached via load balancer.
      // If the server group and Consul are both disabled, the server group remains disabled.
      // If the server group and Consul are both not disabled, the server group is not disabled.
      serverGroup.disabled &= consulDisabled
      serverGroup.discovery = true
    }

    serverGroup
  }

  static List getLoadBalancerHealths(String instanceName, List loadBalancers) {
    loadBalancers*.healths.findResults { List glbhs ->
      glbhs.findAll { GoogleLoadBalancerHealth glbh ->
        glbh.instanceName == instanceName
      }
    }.flatten()
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy