Skip to content

Commit

Permalink
feat(provider/gce): Support named ports for global LBs.
Browse files Browse the repository at this point in the history
  • Loading branch information
jtk54 committed Sep 27, 2017
1 parent 64d385f commit 5cbbd4b
Show file tree
Hide file tree
Showing 11 changed files with 172 additions and 25 deletions.
8 changes: 7 additions & 1 deletion app/scripts/modules/google/src/domain/backendService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,10 @@ export interface IGceBackendService {
backends: any[];
healthCheck: IGceHealthCheck;
sessionAffinity: string;
};
portName: string;
}

export interface INamedPort {
name: string;
port: number;
}
2 changes: 2 additions & 0 deletions app/scripts/modules/google/src/help/gce.help.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ const helpContents: {[key: string]: string} = {
'gce.loadBalancer.advancedSettings.healthyThreshold': '<p>Configures the number of healthy observations before reinstituting an instance into the load balancer’s traffic rotation.</p><p>Default: <b>10</b></p>',
'gce.loadBalancer.advancedSettings.unhealthyThreshold': '<p>Configures the number of unhealthy observations before deservicing an instance from the load balancer.</p><p>Default: <b>2</b></p>',
'gce.loadBalancer.healthCheck': '(Optional) <b>Health Checks</b> use HTTP requests to determine if a VM instance is healthy.',
'gce.loadBalancer.portName': '(Required) The <b>Port Name</b> this backend service will forward traffic to. Load balancers in GCP specify a <b>Port Name</b>, and each server group added to a load balancer needs to specify a mapping from that <b>Port Name</b> to a port to actually receive traffic.',
'gce.loadBalancer.portRange': '(Optional) Only packets addressed to ports in the specified <b>Port Range</b> will be forwarded. If left empty, all ports are forwarded. Must be a single port number or two port numbers separated by a dash. Each port number must be between 1 and 65535, inclusive. For example: 5000-5999.',
'gce.securityGroup.sourceCIDRs': 'Traffic is only allowed from sources that match one of these CIDR ranges, or one of the source tags above.',
'gce.securityGroup.sourceTags': 'Traffic is only allowed from sources that match one of these tags, or one of the source CIDR ranges below.',
Expand Down Expand Up @@ -91,6 +92,7 @@ const helpContents: {[key: string]: string} = {
An additional control to manage your maximum CPU utilization or RPS.
If you want your instances to operate at a max 80% CPU utilization, set your balancing mode to 80% max CPU utilization and your capacity to 100%.
If you want to cut instance utilization by half, set your balancing mode to 80% max CPU utilization and your capacity to 50%. Input must be a number between 0 and 100.`,
'gce.serverGroup.loadBalancingPolicy.portName': 'A load balancer sends traffic to an instance group through a named port. Input must be a port name.',
'gce.serverGroup.loadBalancingPolicy.listeningPort': 'A load balancer sends traffic to an instance group through a named port. Input must be a port number (i.e., between 1 and 65535).',
'gce.serverGroup.traffic': 'Registers the server group with any associated load balancers. These registrations are used by Spinnaker to determine if the server group is enabled.',
'pipeline.config.gce.bake.accountName': '<p>(Optional) The name of a Google account configured within Rosco. If left blank, the first configured account will be used.</p>',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@ <h3 us-spinner="{radius:30, width:8, length: 16}"></h3>
<v2-wizard-page key="listener" label="Listener" mark-complete-on-view="false">
<ng-include src="ctrl.pages.listener"></ng-include>
</v2-wizard-page>
<v2-wizard-page key="portName"
label="Port Name"
ng-if="ctrl.hasPortName"
mark-complete-on-view="false">
<ng-include src="ctrl.pages.portName"></ng-include>
</v2-wizard-page>
<v2-wizard-page key="healthCheck" label="Health Check" mark-complete-on-view="false">
<ng-include src="ctrl.pages.healthCheck"></ng-include>
</v2-wizard-page>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@ <h3 us-spinner="{radius:30, width:8, length: 16}"></h3>
<v2-wizard-page key="listener" label="Listener" mark-complete-on-view="false">
<ng-include src="ctrl.pages.listener"></ng-include>
</v2-wizard-page>
<v2-wizard-page key="portName"
label="Port Name"
ng-if="ctrl.hasPortName"
mark-complete-on-view="false">
<ng-include src="ctrl.pages.portName"></ng-include>
</v2-wizard-page>
<v2-wizard-page key="healthCheck" label="Health Check" mark-complete-on-view="false">
<ng-include src="ctrl.pages.healthCheck"></ng-include>
</v2-wizard-page>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,19 @@
</div>
</div>

<div class="form-group">
<div class="col-md-4 sm-label-right">
<b>Port Name</b>
<help-field key="gce.loadBalancer.portName"></help-field>
</div>
<div class="col-md-4">
<input required
type="string"
class="form-control input-sm"
ng-model="$ctrl.backendService.portName"/>
</div>
</div>

<div class="form-group">
<div class="col-md-4 sm-label-right">
<b>Enable CDN</b>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ class SslLoadBalancerCtrl extends CommonGceLoadBalancerCtrl implements IControll
'listener': require('./listener.html'),
'healthCheck': require('../common/commonHealthCheckPage.html'),
'advancedSettings': require('../common/commonAdvancedSettingsPage.html'),
'portName': require('./portName'),
};
public sessionAffinityViewToModelMap: any = {
'None': 'NONE',
Expand All @@ -87,6 +88,7 @@ class SslLoadBalancerCtrl extends CommonGceLoadBalancerCtrl implements IControll
public viewState: ViewState = new ViewState('None');
public maxCookieTtl = 60 * 60 * 24; // One day.
public taskMonitor: any;
public hasPortName = true;

private sessionAffinityModelToViewMap: any = _.invert(this.sessionAffinityViewToModelMap);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<div class="container-fluid form-horizontal">
<ng-form name="portName">
<div class="form-group">
<div class="col-md-4 sm-label-right">
Port Name
<help-field key="gce.loadBalancer.portName"></help-field>
</div>
<div class="col-md-4">
<input type="text"
required
class="form-control input-sm"
ng-model="ctrl.loadBalancer.backendService.portName"/>
</div>
</div>
</ng-form>
</div>
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ class TcpLoadBalancerCtrl extends CommonGceLoadBalancerCtrl implements ng.ICompo
'listener': require('./listener.html'),
'healthCheck': require('../common/commonHealthCheckPage.html'),
'advancedSettings': require('../common/commonAdvancedSettingsPage.html'),
'portName': require('./portName'),
};
public sessionAffinityViewToModelMap: any = {
'None': 'NONE',
Expand All @@ -85,6 +86,7 @@ class TcpLoadBalancerCtrl extends CommonGceLoadBalancerCtrl implements ng.ICompo
public viewState: ViewState = new ViewState('None');
public maxCookieTtl = 60 * 60 * 24; // One day.
public taskMonitor: any;
public hasPortName = true;

private sessionAffinityModelToViewMap: any = _.invert(this.sessionAffinityViewToModelMap);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<div class="container-fluid form-horizontal">
<ng-form name="portName">
<div class="form-group">
<div class="col-md-4 sm-label-right">
Port Name
<help-field key="gce.loadBalancer.portName"></help-field>
</div>
<div class="col-md-4">
<input type="text"
required
class="form-control input-sm"
ng-model="ctrl.loadBalancer.backendService.portName"/>
</div>
</div>
</ng-form>
</div>
Original file line number Diff line number Diff line change
@@ -1,19 +1,49 @@
<ng-form name="loadBalancingPolicySubForm">
<collapsible-section heading="Port Name Mapping" expanded="true" subsection="true">
<div class="form-group">
<div class="col-md-5 sm-label-right">
<span>http</span><help-field key="gce.serverGroup.loadBalancingPolicy.listeningPort"></help-field>
<div class="form-group row" ng-repeat="namedPort in $ctrl.command.loadBalancingPolicy.namedPorts">
<div class="col-md-12">
<div class="col-md-2 sm-label-right">
<b>Name</b>
<help-field key="gce.serverGroup.loadBalancingPolicy.portName"></help-field>
</div>
<div class="col-md-3">
<ui-select
ng-model="namedPort.name"
class="form-control input-sm">
<ui-select-match>{{namedPort.name}}</ui-select-match>
<ui-select-choices repeat="portName in $ctrl.getPortNames()">
<span ng-bind-html="portName"></span>
</ui-select-choices>
</ui-select>
</div>
<div class="col-md-2 sm-label-right">
<b>Port</b>
<help-field key="gce.serverGroup.loadBalancingPolicy.listeningPort"></help-field>
</div>
<div class="col-md-3">
<input type="number"
name="listeningPort"
required
class="form-control input-sm"
ng-model="namedPort.port"
max="{{ $ctrl.maxPort }}"
min="1"/>
</div>
<div class="col-md-2 sm-label-left">
<span class="glyphicon glyphicon-trash" uib-tooltip="Remove Named Port"
ng-click="$ctrl.removeNamedPort($index)"></span>
</div>
<div class="col-md-4 error-message"
ng-if="loadBalancingPolicySubForm.listeningPort.$error.min || loadBalancingPolicySubForm.listeningPort.$error.max">
Must be between 1 and {{ $ctrl.maxPort }}.
</div>
</div>
</div>
<div class="col-md-3"><input type="number"
name="listeningPort"
required
class="form-control input-sm"
ng-model="$ctrl.command.loadBalancingPolicy.listeningPort"
max="{{ $ctrl.maxPort }}"
min="1"/></div>
<div class="col-md-4 error-message"
ng-if="loadBalancingPolicySubForm.listeningPort.$error.min || loadBalancingPolicySubForm.listeningPort.$error.max">
Must be between 1 and {{ $ctrl.maxPort }}.
<div class="col-md-11">
<button class="btn btn-block add-new" ng-click="$ctrl.addNamedPort()">
<span class="glyphicon glyphicon-plus-sign"></span> Add Port Name Mapping
</button>
</div>
</div>
</collapsible-section>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,22 +1,28 @@
import { IComponentOptions, IController, module } from 'angular';
import { set, get, has, without } from 'lodash';
import { set, get, has, without, intersection, chain } from 'lodash';
import './loadBalancingPolicySelector.component.less';
import { IGceBackendService, INamedPort } from 'google/domain';

class GceLoadBalancingPolicySelectorController implements IController {

public maxPort = 65535;
public command: any;
[key: string]: any;
public globalBackendServices: IGceBackendService[];

public setModel (propertyName: string, viewValue: number): void {
constructor(private gceBackendServiceReader: any) {
'ngInject';
}

public setModel(propertyName: string, viewValue: number): void {
set(this, propertyName, viewValue / 100);
};

public setView (propertyName: string , modelValue: number): void {
public setView(propertyName: string, modelValue: number): void {
this[propertyName] = this.decimalToPercent(modelValue);
};

public onBalancingModeChange (mode: string): void {
public onBalancingModeChange(mode: string): void {
const keys: string[] = ['maxUtilization', 'maxRatePerInstance', 'maxConnectionsPerInstance'];
let toDelete: string[] = [];
switch (mode) {
Expand All @@ -36,14 +42,14 @@ class GceLoadBalancingPolicySelectorController implements IController {
toDelete.forEach((key) => delete this.command.loadBalancingPolicy[key]);
}

public getBalancingModes (): string[] {
public getBalancingModes(): string[] {
let balancingModes: string[] = [];
/*
* Three cases:
* - If we have only HTTP(S) load balancers, our balancing mode can be RATE or UTILIZATION.
* - If we have only SSL/TCP load balancers, our balancing mode can be CONNECTION or UTILIZATION.
* - If we have both, only UTILIZATION.
* */
* Three cases:
* - If we have only HTTP(S) load balancers, our balancing mode can be RATE or UTILIZATION.
* - If we have only SSL/TCP load balancers, our balancing mode can be CONNECTION or UTILIZATION.
* - If we have both, only UTILIZATION.
* */
if (has(this, 'command.backingData.filtered.loadBalancerIndex')) {
const index = this.command.backingData.filtered.loadBalancerIndex;
const selected = this.command.loadBalancers;
Expand All @@ -67,11 +73,53 @@ class GceLoadBalancingPolicySelectorController implements IController {
return balancingModes;
}

public $onDestroy (): void {
public $onInit(): void {
this.gceBackendServiceReader.listBackendServices('globalBackendService')
.then((services: IGceBackendService[]) => {
this.globalBackendServices = services;
});
}

public $onDestroy(): void {
delete this.command.loadBalancingPolicy;
}

private decimalToPercent (value: number): number {
public addNamedPort() {
if (!this.command.loadBalancingPolicy.namedPorts) {
this.command.loadBalancingPolicy.namedPorts = [];
}

this.command.loadBalancingPolicy.namedPorts.push({name: '', port: 80});
}

public removeNamedPort(index: number) {
this.command.loadBalancingPolicy.namedPorts.splice(index, 1);
}

public getPortNames(): string[] {
const index = this.command.backingData.filtered.loadBalancerIndex;
const selected = this.command.loadBalancers;
const inUsePortNames = this.command.loadBalancingPolicy.namedPorts.map((namedPort: INamedPort) => namedPort.name);

const getThem = (globalBackendServices: IGceBackendService[], loadBalancer: string): string[] => {
switch (get(index[loadBalancer], 'loadBalancerType')) {
case 'SSL':
case 'TCP':
case 'HTTP':
const lbBackendServices: string[] = get(index[loadBalancer], 'backendServices');
const filteredBackendServices = globalBackendServices.filter((service: IGceBackendService) => lbBackendServices.includes(service.name));
const portNames = filteredBackendServices.map((service: IGceBackendService) => service.portName);
const portNameIntersection = intersection(portNames, inUsePortNames);
return portNames.filter(portName => !portNameIntersection.includes(portName));
default:
return [];
}
};

return chain(selected).flatMap((lbName: string) => getThem(this.globalBackendServices, lbName)).uniq().value();
}

private decimalToPercent(value: number): number {
if (value === 0) {
return 0;
}
Expand Down

0 comments on commit 5cbbd4b

Please sign in to comment.