Skip to content

Commit

Permalink
feat(google): permit guest accelerator config for regional server gro…
Browse files Browse the repository at this point in the history
…ups with zones explicitly selected (#7528)

* refactor(google): convert accelerator config component to react

* feat(google): permit guest accelerator config for regional server groups with zones explicitly selected
  • Loading branch information
maggieneterval committed Oct 15, 2019
1 parent b999312 commit b41a8c6
Show file tree
Hide file tree
Showing 10 changed files with 432 additions and 170 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { GCE_AUTOHEALING_POLICY_SELECTOR } from './wizard/autoHealingPolicy/auto
import { GCE_CACHE_REFRESH } from 'google/cache/cacheRefresh.component';
import { GCE_CUSTOM_INSTANCE_CONFIGURER } from './wizard/customInstance/customInstanceConfigurer.component';
import { GCE_DISK_CONFIGURER } from './wizard/advancedSettings/diskConfigurer.component';
import { GCE_ACCELERATOR_CONFIGURER } from './wizard/advancedSettings/acceleratorConfigurer.component';
import { GCE_ACCELERATOR_CONFIGURER } from './wizard/advancedSettings/GceAcceleratorConfigurer';
import { GCE_IMAGE_SELECT } from '../../image/ImageSelect';

module.exports = angular.module('spinnaker.serverGroup.configure.gce', [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ 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';
import { GceImageReader } from 'google/image';
import { GceAcceleratorService } from 'google/serverGroup/configure/wizard/advancedSettings/gceAccelerator.service';

module.exports = angular
.module('spinnaker.serverGroup.configure.gce.configuration.service', [
Expand Down Expand Up @@ -256,17 +257,7 @@ module.exports = angular

function configureAccelerators(command) {
const result = { dirty: {} };
const { credentials, zone, backingData } = command;
const accountBackingData = _.get(backingData, ['credentialsKeyedByAccount', credentials], {});
const acceleratorMap = accountBackingData.zoneToAcceleratorTypesMap || {};
const acceleratorTypes = _.get(acceleratorMap, [zone, 'acceleratorTypes', 'acceleratorTypes'], []).map(a => {
const maxCards = _.isFinite(a.maximumCardsPerInstance) ? a.maximumCardsPerInstance : 4;
const availableCardCounts = [];
for (let i = 1; i <= maxCards; i *= 2) {
availableCardCounts.push(i);
}
return _.assign({}, a, { availableCardCounts });
});
const acceleratorTypes = GceAcceleratorService.getAvailableAccelerators(command);
_.set(command, ['viewState', 'acceleratorTypes'], acceleratorTypes);
result.dirty.acceleratorTypes = true;
const chosenAccelerators = _.get(command, 'acceleratorConfigs', []);
Expand Down Expand Up @@ -713,6 +704,14 @@ module.exports = angular
return result;
};

cmd.selectZonesChanged = function selectZonesChanged(command) {
const result = { dirty: {} };
command.viewState.dirty = command.viewState.dirty || {};
angular.extend(command.viewState.dirty, result.dirty);
angular.extend(command.viewState.dirty, configureAccelerators(command).dirty);
return result;
};

cmd.customInstanceChanged = function customInstanceChanged(command) {
const result = { dirty: {} };

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,221 @@
import { module } from 'angular';

import { get, isEmpty } from 'lodash';
import * as React from 'react';
import Select, { Option } from 'react-select';
import { react2angular } from 'react2angular';

import { HelpField } from '@spinnaker/core';

import { IGceAcceleratorType } from './gceAccelerator.service';

interface IGceAcceleratorConfig {
acceleratorType: string;
acceleratorCount: number;
}

export interface IGceAcceleratorConfigurerProps {
acceleratorConfigs: IGceAcceleratorConfig[];
availableAccelerators: IGceAcceleratorType[];
regional: boolean;
setAcceleratorConfigs: (configs: IGceAcceleratorConfig[]) => void;
zone: string;
}

export function GceAcceleratorConfigurer({
acceleratorConfigs = [],
availableAccelerators = [],
regional,
setAcceleratorConfigs,
zone,
}: IGceAcceleratorConfigurerProps) {
const addAccelerator = (): void => {
if (isEmpty(availableAccelerators)) {
return;
}
setAcceleratorConfigs(
acceleratorConfigs.concat([
{
acceleratorType: availableAccelerators[0].name,
acceleratorCount: 1,
},
]),
);
};

const removeAccelerator = (indexToRemove: number): void => {
setAcceleratorConfigs(
acceleratorConfigs.filter((_config, index) => {
return indexToRemove !== index;
}),
);
};

const getTypeOptions = (): Array<Option<string>> => {
return availableAccelerators.map(({ description, name }) => {
return {
label: description,
value: name,
};
});
};

const getAvailableCardCounts = (acceleratorType: string): number[] => {
const acceleratorConfig = availableAccelerators.find(({ name }) => name === acceleratorType);
return get(acceleratorConfig, 'availableCardCounts', []);
};

const getCountOptions = (acceleratorType: string): Array<Option<number>> => {
return getAvailableCardCounts(acceleratorType).map(count => ({
label: count.toString(),
value: count,
}));
};

const getNearestCount = (type: string, currentCount: number): number => {
const availableCounts = getAvailableCardCounts(type);
if (availableCounts.includes(currentCount)) {
return currentCount;
}
return availableCounts.reduce((nearest, count) => {
if (count > currentCount) {
return nearest;
}
return count;
}, 1);
};

const onTypeChange = (indexToUpdate: number, type: string): void => {
setAcceleratorConfigs(
acceleratorConfigs.map((config, index) => {
if (indexToUpdate !== index) {
return config;
}
return {
acceleratorType: type,
acceleratorCount: getNearestCount(type, config.acceleratorCount),
};
}),
);
};

const onCountChange = (indexToUpdate: number, count: number) => {
setAcceleratorConfigs(
acceleratorConfigs.map((config, index) => {
if (indexToUpdate !== index) {
return config;
}
return {
...config,
acceleratorCount: count,
};
}),
);
};

return (
<div className="form-group">
<div className="sm-label-left">
Accelerators <HelpField id="gce.serverGroup.accelerator" />
</div>
<table className="table table-condensed packed tags">
<thead>
<tr>
<th style={{ width: '75%' }}>Type</th>
<th style={{ width: '15%' }}>Count</th>
<th />
</tr>
</thead>
<tbody>
{acceleratorConfigs.map(({ acceleratorType, acceleratorCount }, i) => (
<tr key={i}>
<td>
<Select
clearable={false}
onChange={(option: Option<string>) => onTypeChange(i, option.value)}
options={getTypeOptions()}
value={acceleratorType}
/>
</td>
<td>
<Select
clearable={false}
onChange={(option: Option<number>) => onCountChange(i, option.value)}
options={getCountOptions(acceleratorType)}
value={acceleratorCount}
/>
</td>
<td>
<button className="btn btn-link" onClick={() => removeAccelerator(i)}>
<span className="glyphicon glyphicon-trash" />
</button>
</td>
</tr>
))}
{!isEmpty(acceleratorConfigs) && (
<tr>
<td colSpan={3}>
Adding Accelerators places constraints on the instances that you can deploy. See
<a href="https://cloud.google.com/compute/docs/gpus/#restrictions" target="_blank">
{' '}
the complete list of these restrictions
</a>{' '}
for more information.
</td>
</tr>
)}
{!isEmpty(availableAccelerators) && (
<tr>
<td colSpan={3}>
<button className="btn btn-block btn-sm add-new" onClick={addAccelerator}>
<span className="glyphicon glyphicon-plus-sign" /> Add Accelerator
</button>
</td>
</tr>
)}
{isEmpty(availableAccelerators) && !regional && !zone && (
<tr>
<td colSpan={3}>
A zone must be selected to configure accelerators. Please note: the set of available accelerator types
are limited by zone. See{' '}
<a href="https://cloud.google.com/compute/docs/gpus/#gpus-list" target="_blank">
the complete list of types in each zone
</a>{' '}
for more information.
</td>
</tr>
)}
{isEmpty(availableAccelerators) && !regional && zone && (
<tr>
<td colSpan={3}>There are no accelerators available in the currently selected zone.</td>
</tr>
)}
{isEmpty(availableAccelerators) && regional && (
<tr>
<td colSpan={3}>
There are no accelerators available in all of the currently selected zone(s). Please explicitly select
only zones that each support the accelerators you would like to configure. See{' '}
<a href="https://cloud.google.com/compute/docs/gpus/#gpus-list" target="_blank">
the complete list of types in each zone
</a>{' '}
for more information.
</td>
</tr>
)}
</tbody>
</table>
</div>
);
}

export const GCE_ACCELERATOR_CONFIGURER = 'spinnaker.gce.accelerator.component';
module(GCE_ACCELERATOR_CONFIGURER, []).component(
'gceAcceleratorConfigurer',
react2angular(GceAcceleratorConfigurer, [
'acceleratorConfigs',
'availableAccelerators',
'regional',
'setAcceleratorConfigs',
'zone',
]),
);
Loading

0 comments on commit b41a8c6

Please sign in to comment.