Skip to content

Commit

Permalink
feat(provider/google): Adds support for multiple named ports in load …
Browse files Browse the repository at this point in the history
…balancers. (#1930)
  • Loading branch information
jtk54 authored Sep 27, 2017
1 parent 8e5f114 commit 99f92bf
Show file tree
Hide file tree
Showing 14 changed files with 64 additions and 28 deletions.
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ allprojects {
apply plugin: 'groovy'

ext {
spinnakerDependenciesVersion = project.hasProperty('spinnakerDependenciesVersion') ? project.property('spinnakerDependenciesVersion') : '0.110.4'
spinnakerDependenciesVersion = project.hasProperty('spinnakerDependenciesVersion') ? project.property('spinnakerDependenciesVersion') : '0.110.5'
}

def checkLocalVersions = [spinnakerDependenciesVersion: spinnakerDependenciesVersion]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1048,7 +1048,7 @@ class GCEUtil {
)
}

// Note: listeningPort is not set in this method.
// Note: namedPorts are not set in this method.
static GoogleHttpLoadBalancingPolicy loadBalancingPolicyFromBackend(Backend backend) {
def backendBalancingMode = GoogleLoadBalancingPolicy.BalancingMode.valueOf(backend.balancingMode)
return new GoogleHttpLoadBalancingPolicy(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -238,15 +238,24 @@ class BasicGoogleDeployHandler implements DeployHandler<BasicGoogleDeployDescrip
instanceMetadata[(GoogleServerGroup.View.LOAD_BALANCING_POLICY)] = objectMapper.writeValueAsString(loadBalancingPolicy)
backendToAdd = GCEUtil.backendFromLoadBalancingPolicy(loadBalancingPolicy)
} else if (sourcePolicyJson) {
// We don't have to update the metadata here, since we are reading these properties directly from it.
backendToAdd = GCEUtil.backendFromLoadBalancingPolicy(objectMapper.readValue(sourcePolicyJson, GoogleHttpLoadBalancingPolicy))
GoogleHttpLoadBalancingPolicy newPolicy = objectMapper.readValue(sourcePolicyJson, GoogleHttpLoadBalancingPolicy)
if (newPolicy.listeningPort) {
log.warn("Translated old load balancer instance metadata entry to new format")
newPolicy.setNamedPorts([new NamedPort(name: GoogleHttpLoadBalancingPolicy.HTTP_DEFAULT_PORT_NAME, port: newPolicy.listeningPort)])
newPolicy.listeningPort = null // Deprecated.
// Note: For backwards compatibility with old metadata formats, we need to re-set the instance metadata field.
instanceMetadata[(GoogleServerGroup.View.LOAD_BALANCING_POLICY)] = objectMapper.writeValueAsString(newPolicy)
}
backendToAdd = GCEUtil.backendFromLoadBalancingPolicy(newPolicy)
} else {
log.warn("No load balancing policy found in the operation description or the source server group, adding defaults")
instanceMetadata[(GoogleServerGroup.View.LOAD_BALANCING_POLICY)] = objectMapper.writeValueAsString(
// Sane defaults in case of a create with no LoadBalancingPolicy specified.
new GoogleHttpLoadBalancingPolicy(
balancingMode: GoogleLoadBalancingPolicy.BalancingMode.UTILIZATION,
maxUtilization: 0.80,
capacityScaler: 1.0,
namedPorts: [new NamedPort(name: GoogleHttpLoadBalancingPolicy.HTTP_DEFAULT_PORT_NAME, port: GoogleHttpLoadBalancingPolicy.HTTP_DEFAULT_PORT)]
)
)
backendToAdd = new Backend()
Expand Down Expand Up @@ -405,32 +414,28 @@ class BasicGoogleDeployHandler implements DeployHandler<BasicGoogleDeployDescrip
.setAutoHealingPolicies(autoHealingPolicy)

if (hasBackendServices && (description?.loadBalancingPolicy || description?.source?.serverGroupName)) {
NamedPort namedPort = null
List<NamedPort> namedPorts = []
def sourceGroupName = description?.source?.serverGroupName

// Note: this favors the explicitly specified load balancing policy over the source server group.
if (sourceGroupName && !description?.loadBalancingPolicy) {
def sourceServerGroup = googleClusterProvider.getServerGroup(description.accountName, description.source.region, sourceGroupName)
if (!sourceServerGroup) {
log.warn("Could not locate source server group ${sourceGroupName} to update named port.")
}
namedPort = new NamedPort(
name: GoogleHttpLoadBalancingPolicy.HTTP_PORT_NAME,
port: sourceServerGroup?.namedPorts[(GoogleHttpLoadBalancingPolicy.HTTP_PORT_NAME)] ?: GoogleHttpLoadBalancingPolicy.HTTP_DEFAULT_PORT,
)
namedPorts = sourceServerGroup?.namedPorts?.collect { name, port -> new NamedPort(name: name, port: port) }
} else {
namedPort = new NamedPort(
name: GoogleHttpLoadBalancingPolicy.HTTP_PORT_NAME,
port: description?.loadBalancingPolicy?.listeningPort ?: GoogleHttpLoadBalancingPolicy.HTTP_DEFAULT_PORT
)
namedPorts = description?.loadBalancingPolicy?.namedPorts
}
if (!namedPort) {

if (!namedPorts) {
log.warn("Could not locate named port on either load balancing policy or source server group. Setting default named port.")
namedPort = new NamedPort(
name: GoogleHttpLoadBalancingPolicy.HTTP_PORT_NAME,
namedPorts << new NamedPort(
name: GoogleHttpLoadBalancingPolicy.HTTP_DEFAULT_PORT_NAME,
port: GoogleHttpLoadBalancingPolicy.HTTP_DEFAULT_PORT,
)
}
instanceGroupManager.setNamedPorts([namedPort])
instanceGroupManager.setNamedPorts(namedPorts)
}

def willUpdateBackendServices = !description.disableTraffic && hasBackendServices
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,8 @@ class UpsertGoogleHttpLoadBalancerAtomicOperation extends UpsertGoogleLoadBalanc
Boolean differentSessionAffinity = GoogleSessionAffinity.valueOf(existingService.getSessionAffinity()) != backendService.sessionAffinity
Boolean differentSessionCookieTtl = existingService.getAffinityCookieTtlSec() != backendService.affinityCookieTtlSec
Boolean differentCDN = existingService.getEnableCDN() != backendService.enableCDN
if (differentHealthChecks || differentSessionAffinity || differentSessionCookieTtl || differentCDN) {
Boolean differentPortName = existingService.getPortName() != backendService.portName
if (differentHealthChecks || differentSessionAffinity || differentSessionCookieTtl || differentCDN || differentPortName) {
serviceNeedsUpdatedSet.add(backendService.name)
}
}
Expand Down Expand Up @@ -299,7 +300,7 @@ class UpsertGoogleHttpLoadBalancerAtomicOperation extends UpsertGoogleLoadBalanc
task.updateStatus BASE_PHASE, "Creating backend service $backendServiceName..."
BackendService bs = new BackendService(
name: backendServiceName,
portName: GoogleHttpLoadBalancingPolicy.HTTP_PORT_NAME,
portName: backendService.portName ?: GoogleHttpLoadBalancingPolicy.HTTP_DEFAULT_PORT_NAME,
healthChecks: [GCEUtil.buildHttpHealthCheckUrl(project, backendService.healthCheck.name)],
sessionAffinity: sessionAffinity,
enableCDN: backendService.enableCDN,
Expand All @@ -317,7 +318,7 @@ class UpsertGoogleHttpLoadBalancerAtomicOperation extends UpsertGoogleLoadBalanc
task.updateStatus BASE_PHASE, "Updating backend service $backendServiceName..."
def bsToUpdate = existingServices.find { it.name == backendServiceName }
def hcName = backendService.healthCheck.name
bsToUpdate.portName = GoogleHttpLoadBalancingPolicy.HTTP_PORT_NAME
bsToUpdate.portName = backendService.portName ?: GoogleHttpLoadBalancingPolicy.HTTP_DEFAULT_PORT_NAME
bsToUpdate.healthChecks = [GCEUtil.buildHttpHealthCheckUrl(project, hcName)]
bsToUpdate.sessionAffinity = sessionAffinity
bsToUpdate.enableCDN = backendService.enableCDN
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import com.netflix.spinnaker.clouddriver.google.deploy.GoogleOperationPoller
import com.netflix.spinnaker.clouddriver.google.deploy.SafeRetry
import com.netflix.spinnaker.clouddriver.google.deploy.description.UpsertGoogleLoadBalancerDescription
import com.netflix.spinnaker.clouddriver.google.model.GoogleHealthCheck
import com.netflix.spinnaker.clouddriver.google.model.loadbalancing.GoogleHttpLoadBalancingPolicy
import com.netflix.spinnaker.clouddriver.google.model.loadbalancing.GoogleSessionAffinity
import groovy.util.logging.Slf4j
import org.springframework.beans.factory.annotation.Autowired
Expand Down Expand Up @@ -131,9 +132,10 @@ class UpsertGoogleSslLoadBalancerAtomicOperation extends UpsertGoogleLoadBalance
) as BackendService
if (existingBackendService) {
Boolean differentHealthChecks = existingBackendService.getHealthChecks().collect { GCEUtil.getLocalName(it) } != [healthCheckName]
Boolean differentPortName = existingBackendService.getPortName() != description.backendService.portName
Boolean differentSessionAffinity = GoogleSessionAffinity.valueOf(existingBackendService.getSessionAffinity()) != description.backendService.sessionAffinity ||
existingBackendService.getAffinityCookieTtlSec() != description.backendService.affinityCookieTtlSec
needToUpdateBackendService = differentHealthChecks || differentSessionAffinity
needToUpdateBackendService = differentHealthChecks || differentPortName || differentSessionAffinity
}

// Note: SSL LBs only use HealthCheck objects, _not_ Http(s)HealthChecks. The actual check (i.e. Ssl, Tcp, Http(s))
Expand Down Expand Up @@ -196,6 +198,7 @@ class UpsertGoogleSslLoadBalancerAtomicOperation extends UpsertGoogleLoadBalance
task.updateStatus BASE_PHASE, "Creating backend service ${description.backendService.name}..."
BackendService bs = new BackendService(
name: backendServiceName,
portName: description.backendService.portName ?: GoogleHttpLoadBalancingPolicy.HTTP_DEFAULT_PORT_NAME,
healthChecks: [GCEUtil.buildHealthCheckUrl(project, healthCheckName)],
sessionAffinity: description.backendService.sessionAffinity ?: 'NONE',
affinityCookieTtlSec: description.backendService.affinityCookieTtlSec,
Expand All @@ -221,6 +224,7 @@ class UpsertGoogleSslLoadBalancerAtomicOperation extends UpsertGoogleLoadBalance
existingBackendService.affinityCookieTtlSec = description.backendService.affinityCookieTtlSec
existingBackendService.loadBalancingScheme = 'EXTERNAL'
existingBackendService.protocol = description.ipProtocol
existingBackendService.portName = description.backendService.portName ?: GoogleHttpLoadBalancingPolicy.HTTP_DEFAULT_PORT_NAME
backendServiceOp = safeRetry.doRetry(
{ timeExecute(
compute.backendServices().update(project, existingBackendService.getName(), existingBackendService),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import com.netflix.spinnaker.clouddriver.google.deploy.GoogleOperationPoller
import com.netflix.spinnaker.clouddriver.google.deploy.SafeRetry
import com.netflix.spinnaker.clouddriver.google.deploy.description.UpsertGoogleLoadBalancerDescription
import com.netflix.spinnaker.clouddriver.google.model.GoogleHealthCheck
import com.netflix.spinnaker.clouddriver.google.model.loadbalancing.GoogleHttpLoadBalancingPolicy
import com.netflix.spinnaker.clouddriver.google.model.loadbalancing.GoogleSessionAffinity
import groovy.util.logging.Slf4j
import org.springframework.beans.factory.annotation.Autowired
Expand Down Expand Up @@ -130,9 +131,10 @@ class UpsertGoogleTcpLoadBalancerAtomicOperation extends UpsertGoogleLoadBalance
) as BackendService
if (existingBackendService) {
Boolean differentHealthChecks = existingBackendService.getHealthChecks().collect { GCEUtil.getLocalName(it) } != [healthCheckName]
Boolean differentPortName = existingBackendService.getPortName() != description.backendService.portName
Boolean differentSessionAffinity = GoogleSessionAffinity.valueOf(existingBackendService.getSessionAffinity()) != description.backendService.sessionAffinity ||
existingBackendService.getAffinityCookieTtlSec() != description.backendService.affinityCookieTtlSec
needToUpdateBackendService = differentHealthChecks || differentSessionAffinity
needToUpdateBackendService = differentHealthChecks || differentPortName || differentSessionAffinity
}

// Note: TCP LBs only use HealthCheck objects, _not_ Http(s)HealthChecks. The actual check (i.e. Ssl, Tcp, Http(s))
Expand Down Expand Up @@ -195,6 +197,7 @@ class UpsertGoogleTcpLoadBalancerAtomicOperation extends UpsertGoogleLoadBalance
task.updateStatus BASE_PHASE, "Creating backend service ${description.backendService.name}..."
BackendService bs = new BackendService(
name: backendServiceName,
portName: description.backendService.portName ?: GoogleHttpLoadBalancingPolicy.HTTP_DEFAULT_PORT_NAME,
healthChecks: [GCEUtil.buildHealthCheckUrl(project, healthCheckName)],
sessionAffinity: description.backendService.sessionAffinity ?: 'NONE',
affinityCookieTtlSec: description.backendService.affinityCookieTtlSec,
Expand All @@ -220,6 +223,7 @@ class UpsertGoogleTcpLoadBalancerAtomicOperation extends UpsertGoogleLoadBalance
existingBackendService.affinityCookieTtlSec = description.backendService.affinityCookieTtlSec
existingBackendService.loadBalancingScheme = 'EXTERNAL'
existingBackendService.protocol = description.ipProtocol
existingBackendService.portName = description.backendService.portName ?: GoogleHttpLoadBalancingPolicy.HTTP_DEFAULT_PORT_NAME
backendServiceOp = safeRetry.doRetry(
{ timeExecute(
compute.backendServices().update(project, existingBackendService.getName(), existingBackendService),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,15 @@ class GoogleBackendService {
Integer affinityCookieTtlSec
GoogleLoadBalancingScheme loadBalancingScheme

/**
* The portName this backend service will forward traffic to.
*
* Load balancers in GCP specify a port name, and each server group added to a
* load balancer needs to specify a mapping from that port name to a port to actually
* receive traffic.
*/
String portName = GoogleHttpLoadBalancingPolicy.HTTP_DEFAULT_PORT_NAME

/**
* Specifies whether edge caching is enabled or not. Only applicable for Https LBs.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
package com.netflix.spinnaker.clouddriver.google.model.loadbalancing

import com.fasterxml.jackson.annotation.JsonIgnore
import com.fasterxml.jackson.annotation.JsonIgnoreProperties
import com.google.api.services.compute.model.NamedPort

/**
* For Http(s), balancingMode must be either UTILIZATION or RATE.
Expand All @@ -25,9 +27,10 @@ import com.fasterxml.jackson.annotation.JsonIgnore
* For Ssl/Tcp, balancingMode must be either UTILIZATION or CONNECTION.
* maxUtilization must be set if UTILIZATION, maxConnectionsPerInstance if CONNECTION.
*/
@JsonIgnoreProperties(ignoreUnknown = true)
class GoogleHttpLoadBalancingPolicy extends GoogleLoadBalancingPolicy {
@JsonIgnore
static final String HTTP_PORT_NAME = 'http'
static final String HTTP_DEFAULT_PORT_NAME = 'http'

@JsonIgnore
static final Integer HTTP_DEFAULT_PORT = 80
Expand All @@ -38,6 +41,9 @@ class GoogleHttpLoadBalancingPolicy extends GoogleLoadBalancingPolicy {

Float maxConnectionsPerInstance

@Deprecated
Integer listeningPort

/**
* Additional scaler option that sets the current max usage of the server group for either balancingMode.
* Valid values are 0.0 through 1.0.
Expand All @@ -46,7 +52,7 @@ class GoogleHttpLoadBalancingPolicy extends GoogleLoadBalancingPolicy {
Float capacityScaler

/**
* Port that the HTTP LB will forward traffic to on the server group.
* List of named ports load balancers use to forward traffic to server groups.
*/
Integer listeningPort
List<NamedPort> namedPorts
}
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ class GoogleInfrastructureProvider extends AgentSchedulerAware implements Search
sessionAffinity: backendService.attributes.sessionAffinity as String,
affinityCookieTtlSec: backendService.attributes.affinityCookieTtlSec as String,
enableCDN: backendService.attributes.enableCDN as String,
portName: backendService.attributes.portName as String,
region: GCEUtil.getLocalName(backendService.attributes?.region as String)
]
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import com.netflix.spinnaker.cats.provider.ProviderCache
import com.netflix.spinnaker.clouddriver.google.cache.CacheResultBuilder
import com.netflix.spinnaker.clouddriver.google.cache.Keys
import com.netflix.spinnaker.clouddriver.google.model.loadbalancing.GoogleBackendService
import com.netflix.spinnaker.clouddriver.google.model.loadbalancing.GoogleHttpLoadBalancingPolicy
import com.netflix.spinnaker.clouddriver.google.security.GoogleNamedAccountCredentials
import groovy.util.logging.Slf4j

Expand Down Expand Up @@ -97,6 +98,7 @@ class GoogleBackendServiceCachingAgent extends AbstractGoogleCachingAgent {
attributes.affinityCookieTtlSec = backendService.affinityCookieTtlSec
attributes.region = backendService.region
attributes.enableCDN = backendService.enableCDN
attributes.portName = backendService.portName
}
}

Expand All @@ -113,7 +115,8 @@ class GoogleBackendServiceCachingAgent extends AbstractGoogleCachingAgent {
sessionAffinity: bs.sessionAffinity,
affinityCookieTtlSec: bs.affinityCookieTtlSec,
enableCDN: bs.enableCDN,
region: bs.region ?: 'global'
region: bs.region ?: 'global',
portName: bs.portName ?: GoogleHttpLoadBalancingPolicy.HTTP_DEFAULT_PORT_NAME,
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -338,7 +338,7 @@ class GoogleHttpLoadBalancerCachingAgent extends AbstractGoogleLoadBalancerCachi
void onFailure(GoogleJsonError e, HttpHeaders responseHeaders) throws IOException {
if (e.getCode() == 404) {
log.warn(e.getMessage())
googleLoadBalancer.containsBackendBucket = true;
googleLoadBalancer.containsBackendBucket = true
} else {
throw new GoogleOperationException(e.getMessage())
}
Expand All @@ -362,6 +362,7 @@ class GoogleHttpLoadBalancerCachingAgent extends AbstractGoogleLoadBalancerCachi
service.sessionAffinity = GoogleSessionAffinity.valueOf(backendService.sessionAffinity)
service.affinityCookieTtlSec = backendService.affinityCookieTtlSec
service.enableCDN = backendService.enableCDN
service.portName = backendService.portName ?: GoogleHttpLoadBalancingPolicy.HTTP_DEFAULT_PORT_NAME
service.backends = backendService.backends?.collect { Backend backend ->
new GoogleLoadBalancedBackend(
serverGroupUrl: backend.group,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,7 @@ class GoogleSslLoadBalancerCachingAgent extends AbstractGoogleLoadBalancerCachin
loadBalancingScheme: backendService.loadBalancingScheme,
sessionAffinity: backendService.sessionAffinity,
affinityCookieTtlSec: backendService.affinityCookieTtlSec,
portName: backendService.portName ?: GoogleHttpLoadBalancingPolicy.HTTP_DEFAULT_PORT_NAME,
backends: backendService.backends?.collect { Backend backend ->
new GoogleLoadBalancedBackend(
serverGroupUrl: backend.group,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,7 @@ class GoogleTcpLoadBalancerCachingAgent extends AbstractGoogleLoadBalancerCachin
loadBalancingScheme: backendService.loadBalancingScheme,
sessionAffinity: backendService.sessionAffinity,
affinityCookieTtlSec: backendService.affinityCookieTtlSec,
portName: backendService.portName ?: GoogleHttpLoadBalancingPolicy.HTTP_DEFAULT_PORT_NAME,
backends: backendService.backends?.collect { Backend backend ->
new GoogleLoadBalancedBackend(
serverGroupUrl: backend.group,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -414,7 +414,7 @@ class GCEUtilSpec extends Specification {
'metadata': new Metadata(items: [
new Metadata.Items(
key: (GoogleServerGroup.View.LOAD_BALANCING_POLICY),
value: "{\"balancingMode\": \"UTILIZATION\",\"maxUtilization\": 0.80, \"listeningPort\": 8080, \"capacityScaler\": 0.77}"
value: "{\"balancingMode\": \"UTILIZATION\",\"maxUtilization\": 0.80, \"namedPorts\": [{\"name\": \"http\", \"port\": 8080}], \"capacityScaler\": 0.77}"
),
new Metadata.Items(
key: (GoogleServerGroup.View.BACKEND_SERVICE_NAMES),
Expand Down

0 comments on commit 99f92bf

Please sign in to comment.