Skip to content

Commit

Permalink
fix(google): differentiate among autohealing health check kinds
Browse files Browse the repository at this point in the history
  • Loading branch information
maggieneterval committed Jan 17, 2019
1 parent 467cb42 commit b920442
Show file tree
Hide file tree
Showing 9 changed files with 171 additions and 18 deletions.
3 changes: 3 additions & 0 deletions app/scripts/modules/google/src/domain/autoHealingPolicy.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import { IGceHealthCheckKind } from 'google/domain/healthCheck';

export interface IGceAutoHealingPolicy {
healthCheck?: string;
healthCheckKind?: IGceHealthCheckKind;
initialDelaySec?: number;
maxUnavailable?: IMaxUnavailable;
}
Expand Down
8 changes: 8 additions & 0 deletions app/scripts/modules/google/src/domain/healthCheck.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,12 @@ export interface IGceHealthCheck {
timeoutSec: number;
unhealthyThreshold: number;
healthyThreshold: number;
kind: IGceHealthCheckKind;
selfLink: string;
}

export enum IGceHealthCheckKind {
healthCheck = 'healthCheck',
httpHealthCheck = 'httpHealthCheck',
httpsHealthCheck = 'httpsHealthCheck',
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import { IGceHealthCheck, IGceHealthCheckKind } from 'google/domain';
import {
parseHealthCheckUrl,
getHealthCheckOptions,
getDuplicateHealthCheckNames,
} from 'google/healthCheck/healthCheckUtils';

describe('Health check display utils', () => {
let healthChecks: IGceHealthCheck[];
beforeEach(() => {
healthChecks = getSampleHealthChecks();
});
describe('parseHealthCheckUrl', () => {
it('extracts name and kind from health check url', () => {
healthChecks.forEach(hc => {
expect(parseHealthCheckUrl(hc.selfLink)).toEqual({
healthCheckName: hc.name,
healthCheckKind: hc.kind,
});
});
});
});
describe('getHealthCheckOptions', () => {
it('adds appropriate displayName to each health check, including kind when name is duplicate', () => {
expect(getHealthCheckOptions(healthChecks).map(hc => hc.displayName)).toEqual([
'hello (healthCheck)',
'hello (httpsHealthCheck)',
'ping',
]);
});
});
describe('getDuplicateHealthCheckNames', () => {
it('returns list of names that occur more than once in list of health checks', () => {
const duplicates = getDuplicateHealthCheckNames(healthChecks);
expect(duplicates.size).toEqual(1);
expect(duplicates.has('hello')).toBeTruthy();
});
});
});

function getSampleHealthChecks(): IGceHealthCheck[] {
return [
{
account: 'my-gce-account',
checkIntervalSec: 1,
healthCheckType: 'HTTP',
healthyThreshold: 1,
kind: IGceHealthCheckKind.healthCheck,
name: 'hello',
port: 8080,
requestPath: '/hello',
selfLink: 'https://www.googleapis.com/compute/beta/projects/my-project/global/healthChecks/hello',
timeoutSec: 1,
unhealthyThreshold: 1,
},
{
account: 'my-gce-account',
checkIntervalSec: 1,
healthCheckType: 'HTTPS',
healthyThreshold: 1,
kind: IGceHealthCheckKind.httpsHealthCheck,
name: 'hello',
port: 8080,
requestPath: '/hello',
selfLink: 'https://www.googleapis.com/compute/beta/projects/my-project/global/httpsHealthChecks/hello',
timeoutSec: 1,
unhealthyThreshold: 1,
},
{
account: 'my-gce-account',
checkIntervalSec: 1,
healthCheckType: 'HTTP',
healthyThreshold: 1,
kind: IGceHealthCheckKind.httpHealthCheck,
name: 'ping',
port: 8080,
requestPath: '/ping',
selfLink: 'https://www.googleapis.com/compute/beta/projects/my-project/global/httpHealthChecks/ping',
timeoutSec: 1,
unhealthyThreshold: 1,
},
];
}
47 changes: 47 additions & 0 deletions app/scripts/modules/google/src/healthCheck/healthCheckUtils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { IGceHealthCheck, IGceHealthCheckKind } from 'google/domain';

export interface IGceHealthCheckOption {
displayName: string;
kind: IGceHealthCheckKind;
name: string;
selfLink: string;
}

export function parseHealthCheckUrl(url: string): { healthCheckName: string; healthCheckKind: IGceHealthCheckKind } {
const healthCheckPathParts = url.split('/');
if (healthCheckPathParts.length < 2) {
throw new Error(`Health check url ${url} missing expected segments.`);
}
const healthCheckName = healthCheckPathParts[healthCheckPathParts.length - 1];
const healthCheckKind = healthCheckPathParts[healthCheckPathParts.length - 2].slice(0, -1);
return {
healthCheckName,
healthCheckKind: healthCheckKind as IGceHealthCheckKind,
};
}

export function getHealthCheckOptions(healthChecks: IGceHealthCheck[]): IGceHealthCheckOption[] {
const duplicateNames = getDuplicateHealthCheckNames(healthChecks);
return healthChecks.map(hc => {
const isNameDupe = duplicateNames.has(hc.name);
return {
displayName: isNameDupe ? `${hc.name} (${hc.kind})` : hc.name,
kind: hc.kind,
name: hc.name,
selfLink: hc.selfLink,
};
});
}

export function getDuplicateHealthCheckNames(healthChecks: IGceHealthCheck[]): Set<string> {
const allNames = new Set();
const duplicateNames = new Set();
healthChecks.forEach(hc => {
if (allNames.has(hc.name)) {
duplicateNames.add(hc.name);
} else {
allNames.add(hc.name);
}
});
return duplicateNames;
}
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@ module.exports = angular

if (autoHealingPolicyHealthCheck) {
command.autoHealingPolicy = {
healthCheck: autoHealingPolicyHealthCheck,
healthCheck: healthCheckUrl,
initialDelaySec: autoHealingPolicy.initialDelaySec,
};
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {

import { GCEProviderSettings } from 'google/gce.settings';
import { GCE_HEALTH_CHECK_READER } from 'google/healthCheck/healthCheck.read.service';
import { getHealthCheckOptions } from 'google/healthCheck/healthCheckUtils';
import { GCE_HTTP_LOAD_BALANCER_UTILS } from 'google/loadBalancer/httpLoadBalancerUtils.service';
import { LOAD_BALANCER_SET_TRANSFORMER } from 'google/loadBalancer/loadBalancer.setTransformer';

Expand Down Expand Up @@ -133,6 +134,7 @@ module.exports = angular
const healthChecks = getHealthChecks(command);
if (
!_.chain(healthChecks)
.map('selfLink')
.includes(command.autoHealingPolicy.healthCheck)
.value()
) {
Expand Down Expand Up @@ -357,10 +359,8 @@ module.exports = angular
}

function getHealthChecks(command) {
return _.chain(command.backingData.healthChecks)
.filter({ account: command.credentials })
.map('name')
.value();
const matchingHealthChecks = _.filter(command.backingData.healthChecks, { account: command.credentials });
return getHealthCheckOptions(matchingHealthChecks);
}

function configureHealthChecks(command) {
Expand All @@ -376,6 +376,7 @@ module.exports = angular
if (
_.has(command, 'autoHealingPolicy.healthCheck') &&
!_.chain(filteredData.healthChecks)
.map('selfLink')
.includes(command.autoHealingPolicy.healthCheck)
.value()
) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@
</div>
<div class="col-md-6">
<ui-select ng-model="$ctrl.autoHealingPolicy.healthCheck" class="form-control input-sm" required>
<ui-select-match placeholder="Select...">{{$select.selected}}</ui-select-match>
<ui-select-choices repeat="healthCheck in $ctrl.healthChecks | filter: $select.search">
<span ng-bind-html="healthCheck | highlight: $select.search"></span>
<ui-select-match placeholder="Select...">{{$select.selected.displayName}}</ui-select-match>
<ui-select-choices repeat="healthCheck.selfLink as healthCheck in $ctrl.healthChecks | orderBy: 'displayName' | filter: { displayName: $select.search }">
<span ng-bind-html="healthCheck.displayName | highlight: $select.search"></span>
</ui-select-choices>
</ui-select>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import _ from 'lodash';

import { FirewallLabels, INSTANCE_TYPE_SERVICE, ModalWizard, TaskMonitor } from '@spinnaker/core';

import { parseHealthCheckUrl } from 'google/healthCheck/healthCheckUtils';

module.exports = angular
.module('spinnaker.serverGroup.configure.gce.cloneServerGroup', [
require('@uirouter/angularjs').default,
Expand Down Expand Up @@ -370,6 +372,14 @@ module.exports = angular
if ($scope.command.viewState.mode === 'editPipeline' || $scope.command.viewState.mode === 'createPipeline') {
return $uibModalInstance.close($scope.command);
}

const healthCheckUrl = $scope.command.autoHealingPolicy.healthCheck;
if (healthCheckUrl) {
const { healthCheckName, healthCheckKind } = parseHealthCheckUrl(healthCheckUrl);
$scope.command.autoHealingPolicy.healthCheck = healthCheckName;
$scope.command.autoHealingPolicy.healthCheckKind = healthCheckKind;
}

$scope.taskMonitor.submit(function() {
const promise = serverGroupWriter.cloneServerGroup(angular.copy($scope.command), application);

Expand All @@ -379,6 +389,7 @@ module.exports = angular
$scope.command.tags = origTags;
$scope.command.loadBalancers = origLoadBalancers;
$scope.command.securityGroups = gceTagManager.inferSecurityGroupIdsFromTags($scope.command.tags);
$scope.command.autoHealingPolicy.healthCheck = healthCheckUrl;

return promise;
});
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
import { Application, TaskMonitor } from '@spinnaker/core';
import { IController, module } from 'angular';
import { IModalServiceInstance } from 'angular-ui-bootstrap';
import { chain, cloneDeep, last } from 'lodash';
import { cloneDeep } from 'lodash';

import { Application, TaskMonitor } from '@spinnaker/core';

import { IGceAutoHealingPolicy, IGceServerGroup } from 'google/domain/index';
import { GCE_HEALTH_CHECK_READER, GceHealthCheckReader } from 'google/healthCheck/healthCheck.read.service';
import { getHealthCheckOptions, IGceHealthCheckOption, parseHealthCheckUrl } from 'google/healthCheck/healthCheckUtils';

import './upsertAutoHealingPolicy.modal.less';

class GceUpsertAutoHealingPolicyModalCtrl implements IController {
public autoHealingPolicy: IGceAutoHealingPolicy;
public taskMonitor: TaskMonitor;
public healthChecks: string[];
public healthChecks: IGceHealthCheckOption[];
public action: 'Edit' | 'New';
public isNew: boolean;
public submitButtonLabel: string;
Expand All @@ -29,6 +31,9 @@ class GceUpsertAutoHealingPolicyModalCtrl implements IController {

public submit(): void {
const submitMethod = () => {
const { healthCheckName, healthCheckKind } = parseHealthCheckUrl(this.autoHealingPolicy.healthCheck);
this.autoHealingPolicy.healthCheck = healthCheckName;
this.autoHealingPolicy.healthCheckKind = healthCheckKind;
return this.gceAutoscalingPolicyWriter.upsertAutoHealingPolicy(
this.application,
this.serverGroup,
Expand All @@ -48,10 +53,8 @@ class GceUpsertAutoHealingPolicyModalCtrl implements IController {

public onHealthCheckRefresh(): void {
this.gceHealthCheckReader.listHealthChecks().then(healthChecks => {
this.healthChecks = chain(healthChecks)
.filter({ account: this.serverGroup.account })
.map('name')
.value() as string[];
const matchingHealthChecks = healthChecks.filter(hc => hc.account === this.serverGroup.account);
this.healthChecks = getHealthCheckOptions(matchingHealthChecks);
});
}

Expand All @@ -62,9 +65,6 @@ class GceUpsertAutoHealingPolicyModalCtrl implements IController {
this.submitButtonLabel = this.isNew ? 'Create' : 'Update';
if (!this.isNew) {
this.autoHealingPolicy = cloneDeep(this.serverGroup.autoHealingPolicy);
if (this.autoHealingPolicy.healthCheck) {
this.autoHealingPolicy.healthCheck = last(this.autoHealingPolicy.healthCheck.split('/'));
}
}
this.taskMonitor = new TaskMonitor({
application: this.application,
Expand Down

0 comments on commit b920442

Please sign in to comment.