Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(provider/gce): Support named ports for global LBs. #4154

Merged
merged 1 commit into from
Sep 27, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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