Skip to content

Commit

Permalink
feat(cf): Added support for rolling red black deployments (#6897)
Browse files Browse the repository at this point in the history
Co-Authored-By: Joris Melchior <joris.melchior@gmail.com>
Co-Authored-By: Ria Stein <eleftheria.kousathana@gmail.com>
  • Loading branch information
3 people authored and jkschneider committed Apr 25, 2019
1 parent 960048f commit 7269d2e
Show file tree
Hide file tree
Showing 4 changed files with 197 additions and 7 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,11 @@ import {
} from '@spinnaker/core';

import { IRedBlackCommand } from 'cloudfoundry/deploymentStrategy/strategies/redblack/redblack.strategy';
import { AdditionalFields } from 'cloudfoundry/deploymentStrategy/strategies/redblack/AdditionalFields';
import { AdditionalFields as AdditionalRedBlackFields } from 'cloudfoundry/deploymentStrategy/strategies/redblack/AdditionalFields';
import {
AdditionalFields as AdditionalRollingRedBlackFields,
IRollingRedBlackCommand,
} from 'cloudfoundry/deploymentStrategy/strategies/rollingredblack/AdditionalFields';

export interface ICloudFoundryDeploymentStrategySelectorProps {
command: IServerGroupCommand;
Expand Down Expand Up @@ -48,7 +52,7 @@ export class CloudFoundryDeploymentStrategySelector extends React.Component<
'Disables <i>all</i> previous server groups in the cluster as soon as new server group passes health checks',
key: 'redblack',
additionalFields: ['maxRemainingAsgs'],
AdditionalFieldsComponent: AdditionalFields,
AdditionalFieldsComponent: AdditionalRedBlackFields,
initializationMethod: (command: IRedBlackCommand) => {
defaultsDeep(command, {
rollback: {
Expand All @@ -61,6 +65,26 @@ export class CloudFoundryDeploymentStrategySelector extends React.Component<
});
},
},
{
label: 'Rolling Red/Black',
description:
'Gradually replaces <i>all</i> previous server group instances in the cluster as soon as new server group instances pass health checks',
key: 'cfrollingredblack',
additionalFields: ['targetPercentages'],
AdditionalFieldsComponent: AdditionalRollingRedBlackFields,
initializationMethod: (command: IRollingRedBlackCommand) => {
defaultsDeep(command, {
rollback: {
onFailure: false,
},
targetPercentages: command.targetPercentages ? command.targetPercentages : [50, 100], // defaultsDeep does not work with arrays
delayBeforeDisableSec: 0,
delayBeforeScaleDownSec: 0,
maxRemainingAsgs: 2,
scaleDown: false,
});
},
},
],
currentStrategy: null,
AdditionalFieldsComponent: undefined,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
import * as React from 'react';
import { set } from 'lodash';

import { HelpField, IDeploymentStrategyAdditionalFieldsProps, IServerGroupCommand } from '@spinnaker/core';

export interface IRollingRedBlackCommand extends IServerGroupCommand {
targetPercentages: number[];
delayBeforeDisableSec: number;
delayBeforeScaleDownSec: number;
maxRemainingAsgs: number;
rollback: {
onFailure: boolean;
};
scaleDown: boolean;
}

export interface IRollingRedBlackStrategyAdditionalFieldsProps extends IDeploymentStrategyAdditionalFieldsProps {
command: IRollingRedBlackCommand;
}

export class AdditionalFields extends React.Component<IRollingRedBlackStrategyAdditionalFieldsProps> {
private handlePercentChange = (key: string, value: any, index: number) => {
const percentages = this.props.command.targetPercentages;
percentages[index] = value;
set(this.props.command, key, percentages);
this.forceUpdate();
};

private onDelete = (index: number) => {
const percentages = this.props.command.targetPercentages;
percentages.splice(index, 1);
set(this.props.command, 'targetPercentages', percentages);
this.forceUpdate();
};

private addPercent = () => {
const percentages = this.props.command.targetPercentages;
percentages.push(100);
set(this.props.command, 'targetPercentages', percentages);
this.forceUpdate();
};

private handleChange = (key: string, value: any) => {
set(this.props.command, key, value);
this.forceUpdate();
};

public render() {
const { command } = this.props;
return (
<div className="form-group">
<div className="col-md-12">
<div className="col-md-12 checkbox">
<label>
<input
type="checkbox"
checked={command.rollback.onFailure}
onChange={e => this.handleChange('rollback.onFailure', e.target.checked)}
/>
Rollback to previous server group if deployment fails <HelpField id="strategy.rollingRedBlack.rollback" />
</label>
</div>
<div className="col-md-12 form-inline sp-margin-m-bottom sp-margin-m-top">
<label>
Wait Before Scale Down &nbsp;
<HelpField content="Time to wait before scaling down source server group" />
</label>
<div>
<input
className="form-control input-sm"
style={{ width: '60px' }}
min="0"
type="number"
value={command.delayBeforeDisableSec}
onChange={e => this.handleChange('delayBeforeDisableSec', e.target.value)}
placeholder="0"
/>
&nbsp; seconds
</div>
</div>
<div className="col-md-12 form-inline sp-margin-m-bottom">
<label>
Maximum number of server groups to leave &nbsp;
<HelpField id="strategy.redblack.maxRemainingAsgs" />
</label>
<div>
<input
className="form-control input-sm"
style={{ width: '60px' }}
min="2"
type="number"
value={command.maxRemainingAsgs}
onChange={e => this.handleChange('maxRemainingAsgs', e.target.value)}
/>
&nbsp; server groups
</div>
</div>
<div className="col-md-12 form-inline sp-margin-m-bottom">
<label>
Wait Before Cluster Scale Down &nbsp;
<HelpField content="Time to wait before scaling down the cluster after deployment" />
</label>
<div>
<input
className="form-control input-sm"
style={{ width: '60px' }}
min="0"
type="number"
value={command.delayBeforeScaleDownSec}
onChange={e => this.handleChange('delayBeforeScaleDownSec', e.target.value)}
placeholder="0"
/>
&nbsp; seconds
</div>
</div>
<label>
Scale by percentages &nbsp;
<HelpField content="Steps to get to full capacity in percent" />
</label>
<table className="table table-condensed packed metadata">
<tbody>
{command.targetPercentages.map((percentage: number, index: number) => {
return (
<tr key={index}>
<td>
<div className="sp-margin-m-bottom">
<input
className="form-control"
min="0"
type="number"
value={percentage}
onChange={e => this.handlePercentChange('targetPercentages', e.target.value, index)}
placeholder="100"
/>
</div>
</td>
<td>
<a className="btn btn-link sm-label" onClick={() => this.onDelete(index)}>
<span className="glyphicon glyphicon-trash" />
</a>
</td>
</tr>
);
})}
</tbody>
<tfoot>
<tr>
<td colSpan={2}>
<button type="button" className="add-new col-md-12" onClick={() => this.addPercent()}>
<span className="glyphicon glyphicon-plus-sign" /> Add percentage
</button>
</td>
</tr>
</tfoot>
</table>
</div>
</div>
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,8 @@ export class CloudFoundryServerGroupCommandBuilder {
command.rollback = originalCluster.rollback;
command.strategy = originalCluster.strategy;
command.startApplication = originalCluster.startApplication;
command.targetPercentages = originalCluster.targetPercentages;
command.delayBeforeScaleDownSec = originalCluster.delayBeforeScaleDownSec;
if (originalCluster.stack) {
command.stack = originalCluster.stack;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,16 @@ import { ICloudFoundryEnvVar } from 'cloudfoundry/domain';
export interface ICloudFoundryCreateServerGroupCommand extends IServerGroupCommand {
// clone server group model
account?: string;
source?: ICloudFoundrySource;
delayBeforeScaleDownSec?: number;
rollback?: boolean;
source?: ICloudFoundrySource;
target?: string;
targetCluster?: string;
targetPercentages?: number[];

// deploy server group model
delayBeforeDisableSec?: number;
applicationArtifact?: ICloudFoundryArtifact;
delayBeforeDisableSec?: number;
manifest?: ICloudFoundryManifest;
maxRemainingAsgs?: number;
startApplication: boolean;
Expand Down Expand Up @@ -52,14 +54,16 @@ export interface ICloudFoundryManifestDirectSource {
export interface ICloudFoundryDeployConfiguration {
account: string;
application: string;
delayBeforeDisableSec?: number;
applicationArtifact: ICloudFoundryArtifact;
delayBeforeDisableSec?: number;
delayBeforeScaleDownSec?: number;
freeFormDetails?: string;
manifest: ICloudFoundryManifest;
maxRemainingAsgs?: number;
region: string;
rollback?: boolean;
stack?: string;
freeFormDetails?: string;
strategy?: string;
startApplication: boolean;
strategy?: string;
targetPercentages?: number[];
}

0 comments on commit 7269d2e

Please sign in to comment.