Skip to content

Commit

Permalink
feat(titus): Rollback UI
Browse files Browse the repository at this point in the history
Essentially a copy & paste from AWS with very minor modifications!
  • Loading branch information
ajordens authored and tomaslin committed Mar 16, 2018
1 parent dff9f1b commit 55820d8
Show file tree
Hide file tree
Showing 4 changed files with 268 additions and 0 deletions.
124 changes: 124 additions & 0 deletions serverGroup/details/rollback/rollbackServerGroup.controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
'use strict';

const angular = require('angular');

import { get } from 'lodash';
import { SERVER_GROUP_WRITER, TASK_MONITOR_BUILDER } from '@spinnaker/core';

module.exports = angular.module('spinnaker.titus.serverGroup.details.rollback.controller', [
SERVER_GROUP_WRITER,
TASK_MONITOR_BUILDER,
])
.controller('titusRollbackServerGroupCtrl', function ($scope, $uibModalInstance, serverGroupWriter,
taskMonitorBuilder,
application, serverGroup, disabledServerGroups, allServerGroups) {
$scope.serverGroup = serverGroup;
$scope.disabledServerGroups = disabledServerGroups.sort((a, b) => b.name.localeCompare(a.name));
$scope.allServerGroups = allServerGroups.sort((a, b) => b.name.localeCompare(a.name));
$scope.verification = {};

var desired = serverGroup.capacity.desired;

var rollbackType = 'EXPLICIT';

if (allServerGroups.length === 0 && serverGroup.entityTags) {
const previousServerGroup = get(serverGroup, 'entityTags.creationMetadata.value.previousServerGroup');
if (previousServerGroup) {
rollbackType = 'PREVIOUS_IMAGE';
$scope.previousServerGroup = {
name: previousServerGroup.name,
imageName: previousServerGroup.imageName
};

if (previousServerGroup.imageId && previousServerGroup.imageId !== previousServerGroup.imageName) {
$scope.previousServerGroup.imageId = previousServerGroup.imageId;
}
}
}

if (desired < 10) {
var healthyPercent = 100;
} else if (desired < 20) {
// accept 1 instance in an unknown state during rollback
healthyPercent = 90;
} else {
healthyPercent = 95;
}

$scope.command = {
rollbackType: rollbackType,
rollbackContext: {
rollbackServerGroupName: serverGroup.name,
targetHealthyRollbackPercentage: healthyPercent
}
};

$scope.minHealthy = function(percent) {
return Math.ceil(desired * percent / 100);
};

if (application && application.attributes) {
if (application.attributes.platformHealthOnlyShowOverride && application.attributes.platformHealthOnly) {
$scope.command.interestingHealthProviderNames = ['Titus'];
}

$scope.command.platformHealthOnlyShowOverride = application.attributes.platformHealthOnlyShowOverride;
}

this.isValid = function () {
var command = $scope.command;
if (!$scope.verification.verified) {
return false;
}

if (rollbackType === 'PREVIOUS_IMAGE') {
// no need to validate when using an explicit image
return true;
}

return command.rollbackContext.restoreServerGroupName !== undefined;
};

$scope.taskMonitor = taskMonitorBuilder.buildTaskMonitor({
application: application,
title: 'Rollback ' + serverGroup.name,
modalInstance: $uibModalInstance,
});

this.rollback = function () {
if (!this.isValid()) {
return;
}

var submitMethod = function () {
return serverGroupWriter.rollbackServerGroup(serverGroup, application, $scope.command);
};

$scope.taskMonitor.submit(submitMethod);
};

this.cancel = function () {
$uibModalInstance.dismiss();
};

this.label = function (serverGroup) {
if (!serverGroup) {
return '';
}

if (!serverGroup.buildInfo || !serverGroup.buildInfo.images) {
return serverGroup.name;
}

var imageName = serverGroup.buildInfo.images[0];
if (imageName.indexOf('/') > 0) {
imageName = imageName.substring(imageName.indexOf('/') + 1);
}

return serverGroup.name + ' (' + imageName + ')'
};

this.group = function (serverGroup) {
return serverGroup.isDisabled ? 'Disabled Server Groups' : 'Enabled Server Groups';
};
});
119 changes: 119 additions & 0 deletions serverGroup/details/rollback/rollbackServerGroup.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
<div modal-page class="confirmation-modal">
<task-monitor monitor="taskMonitor"></task-monitor>
<form role="form">
<modal-close dismiss="$dismiss()"></modal-close>
<div class="modal-header">
<h3>Rollback {{serverGroup.name}}</h3>
</div>
<div class="modal-body confirmation-modal">
<div class="row">
<div class="col-sm-3 sm-label-right">
Restore to
</div>
<div class="col-sm-8">
<ui-select ng-model="command.rollbackContext.restoreServerGroupName"
class="form-control input-sm"
ng-if="command.rollbackType === 'EXPLICIT'">
<ui-select-match placeholder="Select...">{{ctrl.label($select.selected)}}</ui-select-match>
<ui-select-choices group-by="ctrl.group" repeat="serverGroup.name as serverGroup in allServerGroups">
<span ng-bind-html="ctrl.label(serverGroup)"></span>
</ui-select-choices>
</ui-select>
<div ng-if="command.rollbackType === 'PREVIOUS_IMAGE'" style="margin-top: 5px">
{{ previousServerGroup.name }} <span class="small">(no longer deployed)</span><br/>
<span class="small">
<strong>Image</strong>: {{ previousServerGroup.imageName}}
<span ng-if="previousServerGroup.imageId">({{ previousServerGroup.imageId }})</span><br/>
</span>
</div>
</div>
</div>

<div class="row" ng-if="command.platformHealthOnlyShowOverride">
<div class="col-sm-10 col-sm-offset-1">
<platform-health-override command="command"
platform-health-type="'Titus'"
show-help-details="true"
field-columns="12">
</platform-health-override>
</div>
</div>

<div class="row">
<task-reason command="command"></task-reason>
</div>

<div class="row">
<div class="col-sm-11 col-sm-offset-1">
Consider rollback successful when
<input type="number"
min="0"
max="100"
ng-model="command.rollbackContext.targetHealthyRollbackPercentage"
class="form-control input-sm inline-number"
/>
percent of instances are healthy.
</div>
</div>

<div class="row">
<div class="col-sm-4 sm-label-right">
Rollback Operations
</div>
</div>
<div class="row" ng-if="command.rollbackType === 'EXPLICIT'">
<div class="col-sm-11 col-sm-offset-1">
<ol>
<li>Enable <em>{{ command.rollbackContext.restoreServerGroupName || 'previous server group' }}</em></li>
<li>Resize <em>{{ command.rollbackContext.restoreServerGroupName || 'previous server group' }}</em> to [
<strong>min</strong>: {{serverGroup.capacity.desired}},
<strong>max</strong>: {{ serverGroup.capacity.max }},
<strong>desired</strong>: {{ serverGroup.capacity.desired }}
]<br/>(minimum capacity pinned at {{serverGroup.capacity.desired}} to prevent autoscaling down during rollback)
<li ng-if="command.rollbackContext.targetHealthyRollbackPercentage < 100">
Wait for at least {{minHealthy(command.rollbackContext.targetHealthyRollbackPercentage)}}
instances to report as healthy
</li>
</li>
<li>Disable {{ serverGroup.name }}</li>
<li>Restore minimum capacity of <em>{{ command.rollbackContext.restoreServerGroupName || 'previous server group' }}</em> [
<strong>min</strong>: {{ serverGroup.capacity.min }}
]</li>
</ol>
<p>
This rollback will affect server groups in {{ serverGroup.account }} ({{ serverGroup.region }}).
</p>
</div>
</div>
<div class="row" ng-if="command.rollbackType === 'PREVIOUS_IMAGE'">
<div class="col-sm-11 col-sm-offset-1">
<ol>
<li>Deploy <em>{{ previousServerGroup.imageId }}</em> [
<strong>min</strong>: {{serverGroup.capacity.desired}},
<strong>max</strong>: {{ serverGroup.capacity.max }},
<strong>desired</strong>: {{ serverGroup.capacity.desired }}
]<br/>(minimum capacity pinned at {{serverGroup.capacity.desired}} to prevent autoscaling down during deploy)
<li ng-if="command.rollbackContext.targetHealthyRollbackPercentage < 100">
Wait for at least {{minHealthy(command.rollbackContext.targetHealthyRollbackPercentage)}}
instances to report as healthy
</li>
</li>
<li>Disable {{ serverGroup.name }}</li>
<li>Restore minimum capacity of <em>new server group</em> [
<strong>min</strong>: {{ serverGroup.capacity.min }}
]</li>
</ol>
<p>
This rollback will affect server groups in {{ serverGroup.account }} ({{ serverGroup.region }}).
</p>
</div>
</div>

</div>
<aws-footer action="ctrl.rollback()"
cancel="ctrl.cancel()"
is-valid="ctrl.isValid()"
account="serverGroup.account"
verification="verification"></aws-footer>
</form>
</div>
2 changes: 2 additions & 0 deletions serverGroup/details/serverGroupDetails.html
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ <h3 select-on-dbl-click>
Titus Job Actions <span class="caret"></span>
</button>
<ul class="dropdown-menu" uib-dropdown-menu role="menu">
<li><a href ng-if=" !serverGroup.isDisabled" ng-click="ctrl.rollbackServerGroup()">Rollback</a></li>
<li role="presentation" class="divider" ng-if="!serverGroup.isDisabled"></li>
<li><a href ng-click="ctrl.resizeServerGroup()">Resize</a></li>
<li><a href ng-if="!serverGroup.isDisabled" ng-click="ctrl.disableServerGroup()">Disable</a></li>
<li><a href ng-if="serverGroup.isDisabled" ng-click="ctrl.enableServerGroup()">Enable</a></li>
Expand Down
23 changes: 23 additions & 0 deletions serverGroup/details/serverGroupDetails.titus.controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ module.exports = angular.module('spinnaker.serverGroup.details.titus.controller'
CONFIRMATION_MODAL_SERVICE,
SERVER_GROUP_WRITER,
require('./resize/resizeServerGroup.controller').name,
require('./rollback/rollbackServerGroup.controller').name,
CLUSTER_TARGET_BUILDER,
SCALING_POLICY_MODULE,
])
Expand Down Expand Up @@ -282,5 +283,27 @@ module.exports = angular.module('spinnaker.serverGroup.details.titus.controller'
}
});
};

this.rollbackServerGroup = function rollbackServerGroup() {
var serverGroup = $scope.serverGroup;
$uibModal.open({
templateUrl: require('./rollback/rollbackServerGroup.html'),
controller: 'titusRollbackServerGroupCtrl as ctrl',
resolve: {
serverGroup: () => serverGroup,
disabledServerGroups: () => {
var cluster = _.find(application.clusters, { name: serverGroup.cluster, account: serverGroup.account });
return _.filter(cluster.serverGroups, { isDisabled: true, region: serverGroup.region });
},
allServerGroups: () => application.getDataSource('serverGroups').data.filter(g =>
g.cluster === serverGroup.cluster &&
g.region === serverGroup.region &&
g.account === serverGroup.account &&
g.name !== serverGroup.name
),
application: () => application
}
});
};
}
);

0 comments on commit 55820d8

Please sign in to comment.