Skip to content

Commit

Permalink
perf(google): improve performance of GCE image selection (#7208) (#7209)
Browse files Browse the repository at this point in the history
  • Loading branch information
spinnakerbot authored and maggieneterval committed Jul 10, 2019
1 parent 768c210 commit 4787689
Show file tree
Hide file tree
Showing 8 changed files with 95 additions and 96 deletions.
61 changes: 61 additions & 0 deletions app/scripts/modules/google/src/image/ImageSelect.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { module } from 'angular';

import { chain } from 'lodash';
import * as React from 'react';
import { Async, AutocompleteResult, Option } from 'react-select';
import { react2angular } from 'react2angular';

interface IImage {
imageName: string;
}

interface IImageSelectProps {
availableImages: IImage[];
selectedImage: string;
selectImage: (image: string, target?: any) => void;
target?: any;
}

export class ImageSelect extends React.Component<IImageSelectProps> {
private loadOptions = (inputValue: string): Promise<AutocompleteResult<string>> => {
return new Promise(resolve => {
if (!inputValue || inputValue.length < 3) {
resolve({
options: [],
complete: false,
});
} else {
const filteredOptions = chain(this.props.availableImages)
.filter(i => i.imageName.toLowerCase().includes(inputValue))
.take(20)
.map(i => ({ value: i.imageName, label: i.imageName }))
.value();
resolve({
options: filteredOptions,
complete: false,
});
}
});
};

public render() {
return (
<Async
cache={null}
clearable={false}
ignoreAccents={false}
loadOptions={this.loadOptions}
onChange={(selected: Option<string>) => this.props.selectImage(selected.value, this.props.target)}
placeholder="Type at least 3 characters to search for an image..."
searchPromptText="Type at least 3 characters to search for an image..."
value={{ value: this.props.selectedImage, label: this.props.selectedImage }}
/>
);
}
}

export const GCE_IMAGE_SELECT = 'spinnaker.gce.imageSelect';
module(GCE_IMAGE_SELECT, []).component(
'gceImageSelect',
react2angular(ImageSelect, ['availableImages', 'selectedImage', 'selectImage', 'target']),
);
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ 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_IMAGE_SELECT } from '../../image/ImageSelect';

module.exports = angular.module('spinnaker.serverGroup.configure.gce', [
require('../../autoscalingPolicy/components/basicSettings/basicSettings.component').name,
Expand All @@ -19,6 +20,7 @@ module.exports = angular.module('spinnaker.serverGroup.configure.gce', [
GCE_CUSTOM_INSTANCE_CONFIGURER,
GCE_DISK_CONFIGURER,
GCE_ACCELERATOR_CONFIGURER,
GCE_IMAGE_SELECT,
require('../serverGroup.transformer').name,
require('./serverGroupConfiguration.service').name,
require('./wizard/advancedSettings/advancedSettingsSelector.directive').name,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -368,7 +368,6 @@ module.exports = angular
viewState: {
instanceProfile: 'custom',
allImageSelection: null,
useAllImageSelection: false,
useSimpleCapacity: true,
usePreferredZones: true,
listImplicitSecurityGroups: false,
Expand Down Expand Up @@ -449,7 +448,6 @@ module.exports = angular
},
viewState: {
allImageSelection: null,
useAllImageSelection: false,
useSimpleCapacity: !serverGroup.autoscalingPolicy,
usePreferredZones: false,
listImplicitSecurityGroups: false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,23 +84,13 @@ module.exports = angular
];

function configureCommand(application, command) {
let imageLoader;
if (command.viewState.disableImageSelection) {
imageLoader = $q.when(null);
} else {
imageLoader = command.viewState.imageId
? loadImagesFromImageName(command)
: loadImagesFromApplicationName(application, command.selectedProvider, command.credentials);
}

return $q
.all({
credentialsKeyedByAccount: AccountService.getCredentialsKeyedByAccount('gce'),
securityGroups: securityGroupReader.getAllSecurityGroups(),
networks: NetworkReader.listNetworksByProvider('gce'),
subnets: SubnetReader.listSubnetsByProvider('gce'),
loadBalancers: loadBalancerReader.listLoadBalancers('gce'),
packageImages: imageLoader,
allImages: loadAllImages(command.credentials),
instanceTypes: gceInstanceTypeService.getAllTypesByRegion(),
persistentDiskTypes: $q.when(angular.copy(persistentDiskTypes)),
Expand Down Expand Up @@ -153,15 +143,6 @@ module.exports = angular
});
}

function loadImagesFromApplicationName(application, provider, account) {
return gceImageReader.findImages({
account: account,
provider: provider,
q: application.name.replace(/_/g, '[_\\-]') + '*',
});
}

// Used to populate the image selection dropdowns in the persistent disk configurer.
function loadAllImages(account) {
return gceImageReader.findImages({
account: account,
Expand All @@ -170,25 +151,6 @@ module.exports = angular
});
}

function loadImagesFromImageName(command) {
command.image = command.viewState.imageId;

let packageBase = command.image.split('_')[0];
const parts = packageBase.split('-');
if (parts.length > 3) {
packageBase = parts.slice(0, -3).join('-');
}
if (!packageBase || packageBase.length < 3) {
return [{ account: command.credentials, imageName: command.image }];
}

return gceImageReader.findImages({
account: command.credentials,
provider: command.selectedProvider,
q: packageBase + '*',
});
}

function configureInstanceTypes(command) {
const result = { dirty: {} };
if (command.region) {
Expand Down Expand Up @@ -346,13 +308,12 @@ module.exports = angular
}

function configureImages(command) {
command.image = command.viewState.imageId;
const result = { dirty: {} };
if (command.credentials !== command.viewState.lastImageAccount) {
command.viewState.lastImageAccount = command.credentials;
const filteredImages = extractFilteredImages(command);
command.backingData.filtered.images = filteredImages;
if (
!_.chain(filteredImages)
!_.chain(command.backingData.allImages)
.find({ imageName: command.image })
.value()
) {
Expand Down Expand Up @@ -488,13 +449,6 @@ module.exports = angular
}
}

function extractFilteredImages(command) {
return _.chain(command.backingData.packageImages)
.filter({ account: command.credentials })
.uniq()
.value();
}

function refreshLoadBalancers(command, skipCommandReconfiguration) {
return loadBalancerReader.listLoadBalancers('gce').then(function(loadBalancers) {
command.backingData.loadBalancers = loadBalancers;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { module, IComponentOptions, IComponentController } from 'angular';
import { module, IComponentOptions, IComponentController, IScope } from 'angular';
import { get, without } from 'lodash';

interface IGceDisk {
Expand All @@ -17,6 +17,9 @@ class GceDiskConfigurerController implements IComponentController {
public disks: IGceDisk[];
private updateDisks: (arg: { disks: IGceDisk[] }) => void;

public static $inject = ['$scope'];
constructor(public $scope: IScope) {}

public $onInit(): void {
this.setLocalSSDCount();
this.setPersistentDisks();
Expand Down Expand Up @@ -56,6 +59,14 @@ class GceDiskConfigurerController implements IComponentController {
this.updateDisks({ disks });
}

public handleImageChange = (imageName: string, target: IGceDisk) => {
// called from a React component
this.$scope.$apply(() => {
target.sourceImage = imageName;
this.handlePersistentDiskChange();
});
};

public addPersistentDisk(): void {
this.persistentDisks = this.persistentDisks.concat([{ type: 'pd-ssd', sizeGb: 10, sourceImage: null }]);
this.handlePersistentDiskChange();
Expand Down Expand Up @@ -153,16 +164,13 @@ const gceDiskConfigurer: IComponentOptions = {
This disk will use the image inferred from this pipeline's execution context.
</p>
</div>
<ui-select ng-if="$index > 0"
ng-model="disk.sourceImage"
ng-change="$ctrl.handlePersistentDiskChange()"
class="form-control input-sm"
required>
<ui-select-match placeholder="Select an image...">{{$select.selected.imageName || 'Select an image...'}}</ui-select-match>
<ui-select-choices repeat="image.imageName as image in $ctrl.command.backingData.allImages | filter: { imageName: $select.search }">
<span ng-bind-html="image.imageName | highlight: $select.search"></span>
</ui-select-choices>
</ui-select>
<gce-image-select
ng-if="$index > 0"
available-images="$ctrl.command.backingData.allImages"
selected-image="disk.sourceImage"
select-image="$ctrl.handleImageChange"
target="disk"
/>
</td>
<td ng-if="$index > 0">
<a class="btn btn-link sm-label" style="margin-top: 0;" ng-click="$ctrl.removePersistentDisk($index)">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -138,15 +138,6 @@ module.exports = angular
gceServerGroupConfigurationService
.configureCommand(application, serverGroupCommand)
.then(function() {
const mode = serverGroupCommand.viewState.mode;
if (mode === 'clone' || mode === 'create') {
if (
!serverGroupCommand.backingData.packageImages ||
!serverGroupCommand.backingData.packageImages.length
) {
serverGroupCommand.viewState.useAllImageSelection = true;
}
}
$scope.state.loaded = true;
initializeSelectOptions();
initializeWatches();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,15 +41,17 @@ module.exports = angular
const imageSearchResultsStream = new Subject();
imageSearchResultsStream.switchMap(fetchImagesForAccount).subscribe(images => {
$scope.command.backingData.allImages = images;
$scope.command.backingData.packageImages = images;
});

this.accountUpdated = () => {
imageSearchResultsStream.next();
};

this.enableAllImageSearch = () => {
$scope.command.viewState.useAllImageSelection = true;
this.selectImage = image => {
// called from a React component
$scope.$apply(() => {
$scope.command.image = image;
});
};

angular.extend(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,29 +114,12 @@
Image
<help-field key="gce.serverGroup.imageName"></help-field>
</div>
<div class="col-md-9" ng-if="command.viewState.useAllImageSelection">
<ui-select ng-model="command.image" class="form-control input-sm" required>
<ui-select-match placeholder="Search for an image..."
>{{$select.selected.imageName || 'Search for an image...'}}</ui-select-match
>
<ui-select-choices
repeat="result.imageName as result in command.backingData.allImages | filter: { imageName: $select.search }"
>
<span ng-bind-html="result.imageName | highlight: $select.search"></span>
</ui-select-choices>
</ui-select>
</div>
<div class="col-md-9" ng-if="!command.viewState.useAllImageSelection">
<ui-select class="form-control input-sm" required ng-model="command.image">
<ui-select-match placeholder="Pick an image">{{$select.selected.imageName}}</ui-select-match>
<ui-select-choices
repeat="image.imageName as image in command.backingData.allImages | filter: { imageName: $select.search }"
>
<span ng-bind-html="image.imageName | highlight: $select.search"></span>
</ui-select-choices>
</ui-select>
<a href ng-click="basicSettingsCtrl.enableAllImageSearch()">Search All Images</a
><help-field key="aws.serverGroup.allImages"></help-field>
<div class="col-md-7">
<gce-image-select
available-images="command.backingData.allImages"
selected-image="command.image"
select-image="basicSettingsCtrl.selectImage"
/>
</div>
</div>
<div class="form-group">
Expand Down

0 comments on commit 4787689

Please sign in to comment.