
com.netflix.spinnaker.clouddriver.google.provider.view.GoogleLoadBalancerProvider.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.annotation.JsonIgnore
import com.fasterxml.jackson.annotation.JsonProperty
import com.fasterxml.jackson.databind.ObjectMapper
import com.google.common.base.Strings
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.google.GoogleCloudProvider
import com.netflix.spinnaker.clouddriver.google.cache.Keys
import com.netflix.spinnaker.clouddriver.google.deploy.GCEUtil
import com.netflix.spinnaker.clouddriver.google.model.GoogleHealthCheck
import com.netflix.spinnaker.clouddriver.google.model.GoogleServerGroup
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.*
import com.netflix.spinnaker.clouddriver.model.LoadBalancerInstance
import com.netflix.spinnaker.clouddriver.model.LoadBalancerProvider
import com.netflix.spinnaker.clouddriver.model.LoadBalancerServerGroup
import com.netflix.spinnaker.clouddriver.security.AccountCredentialsProvider
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.stereotype.Component
import static com.netflix.spinnaker.clouddriver.google.cache.Keys.Namespace.*
@Component
class GoogleLoadBalancerProvider implements LoadBalancerProvider {
final String cloudProvider = GoogleCloudProvider.ID
@Autowired
Cache cacheView
@Autowired
ObjectMapper objectMapper
@Autowired
AccountCredentialsProvider accountCredentialsProvider
@Override
Set getApplicationLoadBalancers(String application) {
String pattern = Keys.getLoadBalancerKey("*", "*", "${application}*")
Set identifiers = cacheView.filterIdentifiers(LOAD_BALANCERS.ns, pattern).toSet()
// It is possible to configure a server group in application A to use a load balancer from
// application B. Therefore, if (and only if) we have not already retrieved load balancer
// identifiers for all applications, we need to retrieve identifiers for every load balancer
// associated with a server group from this application.
if (!Strings.isNullOrEmpty(application)) {
Collection applicationServerGroups = cacheView.getAll(
SERVER_GROUPS.ns,
cacheView.filterIdentifiers(SERVER_GROUPS.ns, "${GoogleCloudProvider.ID}:*:${application}-*")
)
applicationServerGroups.each { CacheData serverGroup ->
Collection relatedLoadBalancers = serverGroup.relationships[LOAD_BALANCERS.ns] ?: []
relatedLoadBalancers.each { String lb ->
identifiers.add(lb)
}
}
}
// TODO(duftler): De-frigga this.
cacheView.getAll(LOAD_BALANCERS.ns,
identifiers,
RelationshipCacheFilter.include(SERVER_GROUPS.ns, INSTANCES.ns)).collect { CacheData loadBalancerCacheData ->
loadBalancersFromCacheData(loadBalancerCacheData, (loadBalancerCacheData?.relationships?.get(INSTANCES.ns) ?: []) as Set)
} as Set
}
GoogleLoadBalancerView loadBalancersFromCacheData(CacheData loadBalancerCacheData, Set allApplicationInstanceKeys) {
GoogleLoadBalancer loadBalancer = null
switch (GoogleLoadBalancerType.valueOf(loadBalancerCacheData.attributes?.type as String)) {
case GoogleLoadBalancerType.INTERNAL:
loadBalancer = objectMapper.convertValue(loadBalancerCacheData.attributes, GoogleInternalLoadBalancer)
break
case GoogleLoadBalancerType.INTERNAL_MANAGED:
loadBalancer = objectMapper.convertValue(loadBalancerCacheData.attributes, GoogleInternalHttpLoadBalancer)
break
case GoogleLoadBalancerType.HTTP:
loadBalancer = objectMapper.convertValue(loadBalancerCacheData.attributes, GoogleHttpLoadBalancer)
break
case GoogleLoadBalancerType.NETWORK:
loadBalancer = objectMapper.convertValue(loadBalancerCacheData.attributes, GoogleNetworkLoadBalancer)
break
case GoogleLoadBalancerType.SSL:
loadBalancer = objectMapper.convertValue(loadBalancerCacheData.attributes, GoogleSslLoadBalancer)
break
case GoogleLoadBalancerType.TCP:
loadBalancer = objectMapper.convertValue(loadBalancerCacheData.attributes, GoogleTcpLoadBalancer)
break
default:
loadBalancer = null
break
}
GoogleLoadBalancerView loadBalancerView = loadBalancer?.view
def serverGroupKeys = loadBalancerCacheData?.relationships[SERVER_GROUPS.ns]
if (!serverGroupKeys) {
return loadBalancerView
}
cacheView.getAll(SERVER_GROUPS.ns, serverGroupKeys)?.each { CacheData serverGroupCacheData ->
if (!serverGroupCacheData) {
return
}
GoogleServerGroup serverGroup = objectMapper.convertValue(serverGroupCacheData.attributes, GoogleServerGroup)
// We have to calculate the L7, ILB, or SSL disabled state with respect to this server group since it's not
// set on the way to the cache.
Boolean isDisabled = false
switch (loadBalancer.type) {
case GoogleLoadBalancerType.HTTP:
def isDisabledFromHttp = Utils.determineHttpLoadBalancerDisabledState(loadBalancer, serverGroup)
isDisabled = serverGroup.asg.get(GCEUtil.REGIONAL_LOAD_BALANCER_NAMES) ? // We assume these are L4 load balancers, and the state has been calculated on the way to the cache.
isDisabledFromHttp && serverGroup.disabled : isDisabledFromHttp
break
case GoogleLoadBalancerType.INTERNAL:
// A server group shouldn't be internally and externally (L4/L7/SSL) load balanced at the same time.
isDisabled = Utils.determineInternalLoadBalancerDisabledState(loadBalancer, serverGroup)
break
case GoogleLoadBalancerType.INTERNAL_MANAGED:
isDisabled = Utils.determineInternalHttpLoadBalancerDisabledState(loadBalancer, serverGroup)
break
case GoogleLoadBalancerType.NETWORK:
isDisabled = serverGroup.disabled
break
case GoogleLoadBalancerType.SSL:
def isDisabledFromSsl = Utils.determineSslLoadBalancerDisabledState(loadBalancer, serverGroup)
isDisabled = serverGroup.asg.get(GCEUtil.REGIONAL_LOAD_BALANCER_NAMES) ? // We assume these are L4 load balancers, and the state has been calculated on the way to the cache.
isDisabledFromSsl && serverGroup.disabled : isDisabledFromSsl
break
case GoogleLoadBalancerType.TCP:
def isDisabledFromTcp = Utils.determineTcpLoadBalancerDisabledState(loadBalancer, serverGroup)
isDisabled = serverGroup.asg.get(GCEUtil.REGIONAL_LOAD_BALANCER_NAMES) ? // We assume these are L4 load balancers, and the state has been calculated on the way to the cache.
isDisabledFromTcp && serverGroup.disabled : isDisabledFromTcp
break
default:
throw new IllegalStateException("Illegal type ${loadBalancer.type} for load balancer ${loadBalancer.name}")
break
}
def loadBalancerServerGroup = new LoadBalancerServerGroup(
name: serverGroup.name,
region: serverGroup.region,
isDisabled: isDisabled,
detachedInstances: [],
instances: [],
cloudProvider: GoogleCloudProvider.ID,
)
// TODO(duftler): De-frigga this.
def serverGroupInstancePattern = Keys.getInstanceKey(loadBalancer.account, serverGroup.region, "$serverGroup.name-.*")
def instanceKeys = allApplicationInstanceKeys.findAll { it ==~ serverGroupInstancePattern }
def instanceNames = instanceKeys.collect {
Keys.parse(it)?.name
}
loadBalancer.healths.each { GoogleLoadBalancerHealth googleLoadBalancerHealth ->
if (!instanceNames.remove(googleLoadBalancerHealth.instanceName)) {
return
}
loadBalancerServerGroup.instances << new LoadBalancerInstance(
id: googleLoadBalancerHealth.instanceName,
zone: googleLoadBalancerHealth.instanceZone,
health: [
"state" : googleLoadBalancerHealth.lbHealthSummaries[0].state as String,
"description": googleLoadBalancerHealth.lbHealthSummaries[0].description
]
)
}
loadBalancerServerGroup.detachedInstances = instanceNames // Any remaining instances are considered detached.
loadBalancerView.serverGroups << loadBalancerServerGroup
}
return loadBalancerView
}
List list() {
def loadBalancerViewsByName = getApplicationLoadBalancers("").groupBy { it.name }
loadBalancerViewsByName.collect { String name, List views ->
def summary = new GoogleLoadBalancerAccountRegionSummary(name: name)
views.each { GoogleLoadBalancerView view ->
def loadBalancerType = view.loadBalancerType
def backendServices = []
def urlMapName
switch (loadBalancerType) {
case (GoogleLoadBalancerType.HTTP):
GoogleHttpLoadBalancer.View httpView = view as GoogleHttpLoadBalancer.View
backendServices = Utils.getBackendServicesFromHttpLoadBalancerView(httpView).collect { it.name }
urlMapName = httpView.urlMapName
break
case (GoogleLoadBalancerType.INTERNAL_MANAGED):
GoogleInternalHttpLoadBalancer.InternalHttpLbView httpView = view as GoogleInternalHttpLoadBalancer.InternalHttpLbView
backendServices = Utils.getBackendServicesFromInternalHttpLoadBalancerView(httpView).collect { it.name }
urlMapName = httpView.urlMapName
break
case (GoogleLoadBalancerType.INTERNAL):
GoogleInternalLoadBalancer.View ilbView = view as GoogleInternalLoadBalancer.View
backendServices << ilbView.backendService.name
break
case (GoogleLoadBalancerType.SSL):
GoogleSslLoadBalancer.View sslView = view as GoogleSslLoadBalancer.View
backendServices << sslView.backendService.name
break
case (GoogleLoadBalancerType.TCP):
GoogleTcpLoadBalancer.View tcpView = view as GoogleTcpLoadBalancer.View
backendServices << tcpView.backendService.name
break
default:
// No backend services to add.
break
}
summary.mappedAccounts[view.account].mappedRegions[view.region].loadBalancers << new GoogleLoadBalancerSummary(
account: view.account,
region: view.region,
name: view.name,
loadBalancerType: loadBalancerType,
backendServices: backendServices.unique() as List ?: null,
urlMapName: urlMapName
)
}
summary
}
}
GoogleLoadBalancerAccountRegionSummary get(String name) {
// TODO(ttomsu): It's inefficient to pull everything back and (possibly) discard most of it.
// Refactor when addressing https://github.com/spinnaker/spinnaker/issues/807
list().find { it.name == name }
}
List byAccountAndRegionAndName(String account,
String region,
String name) {
GoogleLoadBalancerView view = getApplicationLoadBalancers(name).find { view ->
view.account == account && view.region == region
}
if (!view) {
return []
}
def backendServiceHealthChecks = [:]
String instancePort
String loadBalancerPort
String sessionAffinity
switch (view.loadBalancerType) {
case GoogleLoadBalancerType.NETWORK:
GoogleNetworkLoadBalancer.View nlbView = view as GoogleNetworkLoadBalancer.View
sessionAffinity = nlbView.sessionAffinity
instancePort = Utils.derivePortOrPortRange(view.portRange)
loadBalancerPort = Utils.derivePortOrPortRange(view.portRange)
break
case GoogleLoadBalancerType.HTTP:
instancePort = 'http'
loadBalancerPort = Utils.derivePortOrPortRange(view.portRange)
GoogleHttpLoadBalancer.View httpView = view as GoogleHttpLoadBalancer.View
List backendServices = Utils.getBackendServicesFromHttpLoadBalancerView(httpView)
backendServiceHealthChecks = backendServices.collectEntries { [it.name, it.healthCheck.view] }
break
case GoogleLoadBalancerType.INTERNAL_MANAGED:
instancePort = 'http'
loadBalancerPort = Utils.derivePortOrPortRange(view.portRange)
GoogleInternalHttpLoadBalancer.InternalHttpLbView httpView = view as GoogleInternalHttpLoadBalancer.InternalHttpLbView
List backendServices = Utils.getBackendServicesFromInternalHttpLoadBalancerView(httpView)
backendServiceHealthChecks = backendServices.collectEntries { [it.name, it.healthCheck.view] }
break
case GoogleLoadBalancerType.INTERNAL:
GoogleInternalLoadBalancer.View ilbView = view as GoogleInternalLoadBalancer.View
def portString = ilbView.ports.join(",")
instancePort = portString
loadBalancerPort = portString
break
case GoogleLoadBalancerType.SSL:
instancePort = Utils.derivePortOrPortRange(view.portRange)
loadBalancerPort = Utils.derivePortOrPortRange(view.portRange)
break
case GoogleLoadBalancerType.TCP:
instancePort = Utils.derivePortOrPortRange(view.portRange)
loadBalancerPort = Utils.derivePortOrPortRange(view.portRange)
break
default:
throw new IllegalStateException("Load balancer ${view.name} is an unknown load balancer type.")
break
}
[new GoogleLoadBalancerDetails(loadBalancerName: view.name,
loadBalancerType: view.loadBalancerType,
createdTime: view.createdTime,
dnsname: view.ipAddress,
ipAddress: view.ipAddress,
sessionAffinity: sessionAffinity,
healthCheck: (view.hasProperty("healthCheck") && view.healthCheck) ? view.healthCheck : null,
backendServiceHealthChecks: backendServiceHealthChecks ?: null,
listenerDescriptions: [[
listener: new ListenerDescription(
instancePort: instancePort,
loadBalancerPort: loadBalancerPort,
instanceProtocol: view.ipProtocol,
protocol: view.ipProtocol
)
]])]
}
static class GoogleLoadBalancerAccountRegionSummary implements LoadBalancerProvider.Item {
String name
@JsonIgnore
Map mappedAccounts = [:].withDefault {
String accountName -> new GoogleLoadBalancerAccount(name: accountName)
}
@JsonProperty("accounts")
List getByAccounts() {
mappedAccounts.values() as List
}
}
static class GoogleLoadBalancerAccount implements LoadBalancerProvider.ByAccount {
String name
@JsonIgnore
Map mappedRegions = [:].withDefault {
String region -> new GoogleLoadBalancerAccountRegion(name: region)
}
@JsonProperty("regions")
List getByRegions() {
mappedRegions.values() as List
}
}
static class GoogleLoadBalancerAccountRegion implements LoadBalancerProvider.ByRegion {
String name
List loadBalancers = []
}
static class GoogleLoadBalancerSummary implements LoadBalancerProvider.Details {
GoogleLoadBalancerType loadBalancerType
String account
String region
String name
String type = GoogleCloudProvider.ID
List backendServices
String urlMapName
}
static class GoogleLoadBalancerDetails implements LoadBalancerProvider.Details {
Long createdTime
String dnsname
String ipAddress
String loadBalancerName
GoogleLoadBalancerType loadBalancerType
GoogleHealthCheck.View healthCheck
String sessionAffinity
Map backendServiceHealthChecks = [:]
// TODO(ttomsu): Bizarre nesting of data. Necessary?
List
© 2015 - 2025 Weber Informatics LLC | Privacy Policy