Service Upgrade

This commit is contained in:
Vincent Fiduccia 2015-10-23 15:48:39 -07:00
parent 69f901ce61
commit 2262a91372
13 changed files with 235 additions and 64 deletions

View File

@ -13,9 +13,9 @@ export default Ember.Component.extend({
didInitAttrs() { didInitAttrs() {
var initial; var initial;
if ( this.get('value') ) if ( this.get('initialValue') )
{ {
initial = (this.get('value')||'').replace(/^docker:/,''); initial = (this.get('initialValue')||'').replace(/^docker:/,'');
} }
if ( !initial ) if ( !initial )

View File

@ -4,6 +4,7 @@ export default Ember.Component.extend({
// Inputs // Inputs
model: null, model: null,
namePlaceholder: '', namePlaceholder: '',
nameDisabled: false,
descriptionPlaceholder: '', descriptionPlaceholder: '',
tagName: '', tagName: '',

View File

@ -3,7 +3,7 @@
<label class="form-control-static">Name</label> <label class="form-control-static">Name</label>
</div> </div>
<div class="col-sm-12 col-md-8"> <div class="col-sm-12 col-md-8">
{{input type="text" value=model.name classNames="form-control" placeholder=namePlaceholder}} {{input type="text" value=model.name classNames="form-control" placeholder=namePlaceholder disabled=nameDisabled}}
</div> </div>
</div> </div>

View File

@ -0,0 +1,19 @@
import Ember from 'ember';
export default Ember.Component.extend({
batchSize: 1,
interval: 2,
startFirst: false,
didInitAttrs() {
this.optionsChanged();
},
optionsChanged() {
this.sendAction('changed', {
batchSize: this.get('batchSize'),
intervalMillis: this.get('interval')*1000,
startFirst: this.get('startFirst'),
});
},
});

View File

@ -0,0 +1,25 @@
<div class="row">
<div class="col-sm-12 col-md-2 form-label">
<label>Upgrade</label>
</div>
<div class="col-sm-12 col-md-8">
<div class="row">
<div class="col-md-4">
<label>Batch Size</label>
{{input type="number" min="1" class="form-control" value=batchSize}}
</div>
<div class="col-md-4">
<label>Batch Interval</label>
<div class="input-group">
{{input type="number" class="form-control" value=interval}}
<span class="input-group-addon">sec</span>
</div>
</div>
<div class="col-md-4">
<label>Start Behavior</label>
<label class="form-control-static" style="color: inherit; text-transform: none;">{{input type="checkbox" checked=startFirst}} Start before Stop</label>
</div>
</div>
</div>
</div>

View File

@ -7,6 +7,7 @@ export default Ember.Component.extend(NewOrEdit, SelectTab, {
isStandalone: true, isStandalone: true,
isService: false, isService: false,
isSidekick: false, isSidekick: false,
isUpgrade: false,
primaryResource: null, primaryResource: null,
launchConfig: null, launchConfig: null,
service: null, service: null,
@ -18,6 +19,7 @@ export default Ember.Component.extend(NewOrEdit, SelectTab, {
isRequestedHost: null, isRequestedHost: null,
portsAsStrArray: null, portsAsStrArray: null,
launchConfigIndex: -1, launchConfigIndex: -1,
upgradeOptions: null,
// Errors from components // Errors from components
commandErrors: null, commandErrors: null,
@ -90,6 +92,10 @@ export default Ember.Component.extend(NewOrEdit, SelectTab, {
this.set('serviceLinksArray', links); this.set('serviceLinksArray', links);
}, },
setUpgrade(upgrade) {
this.set('upgradeOptions', upgrade);
},
done() { done() {
this.sendAction('done'); this.sendAction('done');
}, },
@ -124,6 +130,48 @@ export default Ember.Component.extend(NewOrEdit, SelectTab, {
} }
}.property('launchConfigIndex'), }.property('launchConfigIndex'),
activeLabel: function() {
var idx = this.get('launchConfigIndex');
var str = '';
if ( this.get('hasSidekicks') )
{
if ( idx === -1 )
{
str = 'Primary Service: ';
}
else
{
str += 'Sidekick Service: ';
}
}
if ( idx === -1 )
{
if ( this.get('service.name') )
{
str += this.get('service.name');
}
else
{
str += '(No name)';
}
}
else
{
if ( this.get('activeLaunchConfig.name') )
{
str += this.get('activeLaunchConfig.name');
}
else
{
str += '(Sidekick #' + (idx+1) + ')';
}
}
return str;
}.property('service.name','activeLaunchConfig.name','launchConfigIndex','hasSidekicks'),
// ---------------------------------- // ----------------------------------
// Labels // Labels
// ---------------------------------- // ----------------------------------
@ -178,8 +226,27 @@ export default Ember.Component.extend(NewOrEdit, SelectTab, {
return this._super.apply(this,arguments); return this._super.apply(this,arguments);
}, },
doSave() {
if ( this.get('isService') && this.get('isUpgrade') )
{
return this.get('service').doAction('upgrade', {
inServiceStrategy: {
batchSize: this.get('upgradeOptions.batchSize'),
intervalMillis: this.get('upgradeOptions.intervalMillis'),
launchConfig: this.get('service.launchConfig'),
secondaryLaunchConfigs: this.get('service.secondaryLaunchConfigs'),
startFirst: this.get('upgradeOptions.startFirst'),
},
});
}
else
{
return this._super.apply(this,arguments);
}
},
didSave() { didSave() {
if ( this.get('isService') ) if ( this.get('isService') && !this.get('isUpgrade') )
{ {
// Returns a promise // Returns a promise
return this.setServiceLinks(); return this.setServiceLinks();

View File

@ -1,6 +1,6 @@
{{#unless isSidekick}} {{#unless isSidekick}}
<section class="horizontal-form container-fluid"> <section class="horizontal-form container-fluid">
<h2>Add <h2>{{if isUpgrade 'Upgrade' 'Add'}}
{{#if isService}} {{#if isService}}
Service Service
{{#if hasSidekicks}} {{#if hasSidekicks}}
@ -13,7 +13,7 @@
</h2> </h2>
{{top-errors errors=errors}} {{top-errors errors=errors}}
{{#if (and isService (not isSidekick))}} {{#if isService}}
{{form-scale {{form-scale
initialLabels=launchConfig.labels initialLabels=launchConfig.labels
initialScale=service.scale initialScale=service.scale
@ -24,25 +24,29 @@
setScale=(action 'setScale') setScale=(action 'setScale')
}} }}
{{#if isUpgrade}}
{{form-upgrade
changed=(action 'setUpgrade')
}}
{{/if}}
{{#if (and isUpgrade (not hasSidekicks))}}
{{form-divider}} {{form-divider}}
{{else}}
{{#if hasSidekicks}} <div class="over-hr r-mt5">
<div class="row"> <span style="text-transform: none;">
<div class="col-sm-12 col-md-2 form-label r-mt5">
<label>Service</label>
</div>
<div class="col-sm-12 col-md-8">
<nav> <nav>
<ul class="pagination pagination-sm r-m0"> <ul class="pagination pagination-sm r-m0">
<li class="{{if (eq launchConfigIndex -1) 'active'}}"><a href="#" {{action "selectLaunchConfig" -1}}>{{default-str service.name default="(Primary Service)"}}</a></li> <li class="{{if (eq launchConfigIndex -1) 'active'}}"><a href="#" {{action "selectLaunchConfig" -1}}>{{default-str service.name default="(Primary Service)"}}</a></li>
{{#each service.secondaryLaunchConfigs as |slc index|}} {{#each service.secondaryLaunchConfigs as |slc index|}}
<li class="{{if (eq launchConfigIndex index) 'active'}}"><a href="#" {{action "selectLaunchConfig" index}}>{{default-str slc.name (concat-str '(Sidekick #' (one-louder index) ')')}}</a></li> <li class="{{if (eq launchConfigIndex index) 'active'}}"><a href="#" {{action "selectLaunchConfig" index}}>{{default-str slc.name (concat-str '(Sidekick #' (one-louder index) ')')}}</a></li>
{{/each}} {{/each}}
<li><a {{action "addSidekick"}} tooltip="Add Sidekick"><i class="icon icon-plus"/></a></li> {{#unless isUpgrade}}
<li class="hand"><a {{action "addSidekick"}}><i class="icon icon-plus"/> Add Sidekick</a></li>
{{/unless}}
</ul> </ul>
</nav> </nav>
</div> </span>
</div> </div>
{{/if}} {{/if}}
{{/if}} {{/if}}
@ -51,7 +55,12 @@
<div data-launchindex="-1"> <div data-launchindex="-1">
<section class="horizontal-form container-fluid"> <section class="horizontal-form container-fluid">
{{form-name-description model=primaryResource namePlaceholder="e.g. myapp" descriptionPlaceholder="My application"}} {{form-name-description
model=primaryResource
namePlaceholder="e.g. myapp"
nameDisabled=isUpgrade
descriptionPlaceholder="My application"
}}
{{form-divider}} {{form-divider}}
@ -61,7 +70,7 @@
{{form-ports initialPorts=launchConfig.ports errors=portErrors portsAsStrArray=launchConfig.ports}} {{form-ports initialPorts=launchConfig.ports errors=portErrors portsAsStrArray=launchConfig.ports}}
{{#if isService}} {{#if (and isService (not isSidekick))}}
{{form-divider}} {{form-divider}}
{{form-service-links {{form-service-links
@ -150,6 +159,7 @@
{{new-container {{new-container
isService=true isService=true
isSidekick=true isSidekick=true
isUpgrade=isUpgrade
launchConfig=slc launchConfig=slc
service=slc service=slc
primaryResource=slc primaryResource=slc
@ -160,12 +170,10 @@
{{/each}} {{/each}}
{{/if}} {{/if}}
{{#unless isSidekick}} {{#unless isSidekick}}
{{#save-cancel save="save" cancel="cancel"}} {{#save-cancel createLabel=(if isUpgrade 'Upgrade' 'Create') save="save" cancel="cancel"}}
{{#if isService}} {{#if (and (not isUpgrade) (not-eq launchConfigIndex -1))}}
<button class="btn btn-default" {{action "addSidekick"}}>Add a Sidekick</button>
{{/if}}
{{#if (not-eq launchConfigIndex -1)}}
<button class="btn btn-default" {{action "removeSidekick"}}>Remove this Sidekick</button> <button class="btn btn-default" {{action "removeSidekick"}}>Remove this Sidekick</button>
{{/if}} {{/if}}
{{/save-cancel}} {{/save-cancel}}

View File

@ -4,7 +4,7 @@
</div> </div>
<div class="pod-state {{stateBackground}}"><span>{{model.displayState}}</span></div> <div class="pod-state {{stateBackground}}"><span>{{model.displayState}}</span></div>
<div class="pod-icon"><i class="icon icon-lg {{model.stateIcon}} {{iconColor}}"></i></div> <div class="pod-icon"><i class="{{model.stateIcon}} icon-lg fa-lg {{iconColor}}"></i></div>
<div class="pod-name"> <div class="pod-name">
{{#link-to "service" model.environmentId model.id}}{{model.displayName}}{{/link-to}} {{#link-to "service" model.environmentId model.id}}{{model.displayName}}{{/link-to}}
{{#if model.showTransitioningMessage}} {{#if model.showTransitioningMessage}}

View File

@ -113,12 +113,16 @@ export default Ember.Mixin.create({
isDeleted: Ember.computed.equal('state','removed'), isDeleted: Ember.computed.equal('state','removed'),
isPurged: Ember.computed.equal('state','purged'), isPurged: Ember.computed.equal('state','purged'),
relevantState: function() {
return this.get('combinedState') || this.get('state');
}.property('combinedState','state'),
displayState: function() { displayState: function() {
var state = this.get('state')||''; var state = this.get('relevantState')||'';
return state.split(/-/).map((word) => { return state.split(/-/).map((word) => {
return Util.ucFirst(word); return Util.ucFirst(word);
}).join('-'); }).join('-');
}.property('state'), }.property('relevantState'),
showTransitioningMessage: function() { showTransitioningMessage: function() {
var trans = this.get('transitioning'); var trans = this.get('transitioning');
@ -127,42 +131,63 @@ export default Ember.Mixin.create({
stateIcon: function() { stateIcon: function() {
var trans = this.get('transitioning'); var trans = this.get('transitioning');
var icon = '';
if ( trans === 'yes' ) if ( trans === 'yes' )
{ {
return 'icon icon-spinner icon-spin'; icon = 'icon icon-spinner icon-spin';
} }
else if ( trans === 'error' ) else if ( trans === 'error' )
{ {
return 'icon icon-alert text-danger'; icon = 'icon icon-alert text-danger';
} }
else else
{ {
var map = this.constructor.stateMap; var map = this.constructor.stateMap;
var key = (this.get('state')||'').toLowerCase(); var key = (this.get('relevantState')||'').toLowerCase();
if ( map && map[key] && map[key].icon !== undefined) if ( map && map[key] && map[key].icon !== undefined)
{ {
if ( typeof map[key].icon === 'function' ) if ( typeof map[key].icon === 'function' )
{ {
return map[key].icon(this); icon = map[key].icon(this);
} }
else else
{ {
return map[key].icon; icon = map[key].icon;
} }
} }
if ( defaultStateMap[key] && defaultStateMap[key].icon ) if ( !icon && defaultStateMap[key] && defaultStateMap[key].icon )
{ {
return defaultStateMap[key].icon; icon = defaultStateMap[key].icon;
} }
return this.constructor.defaultStateIcon; if ( !icon )
{
icon = this.constructor.defaultStateIcon;
} }
}.property('state','transitioning'),
if ( icon.indexOf('fa-') >= 0 )
{
if ( icon.indexOf('fa ') === -1 )
{
icon = 'fa ' + icon;
}
}
else
{
if ( icon.indexOf('icon ') === -1 )
{
icon = 'icon ' + icon;
}
}
}
return icon;
}.property('relevantState','transitioning'),
stateColor: function() { stateColor: function() {
var map = this.constructor.stateMap; var map = this.constructor.stateMap;
var key = (this.get('state')||'').toLowerCase(); var key = (this.get('relevantState')||'').toLowerCase();
if ( map && map[key] && map[key].color !== undefined ) if ( map && map[key] && map[key].color !== undefined )
{ {
if ( typeof map[key].color === 'function' ) if ( typeof map[key].color === 'function' )
@ -181,7 +206,7 @@ export default Ember.Mixin.create({
} }
return this.constructor.defaultStateColor; return this.constructor.defaultStateColor;
}.property('state','transitioning'), }.property('relevantState','transitioning'),
stateBackground: function() { stateBackground: function() {
return this.get('stateColor').replace("text-","bg-"); return this.get('stateColor').replace("text-","bg-");

View File

@ -13,32 +13,31 @@ var Service = Resource.extend(ReadLabels, {
type: 'service', type: 'service',
actions: { actions: {
activate: function() { activate() {
return this.doAction('activate'); return this.doAction('activate');
}, },
deactivate: function() { deactivate() {
return this.doAction('deactivate'); return this.doAction('deactivate');
}, },
cancelUpgrade: function() { cancelUpgrade() {
return this.doAction('cancelupgrade'); return this.doAction('cancelupgrade');
}, },
cancelRollback: function() { cancelRollback() {
return this.doAction('cancelrollback'); return this.doAction('cancelrollback');
}, },
finishUpgrade: function() { finishUpgrade() {
return this.doAction('finishupgrade'); return this.doAction('finishupgrade');
}, },
rollback: function() { rollback() {
return this.doAction('rollback'); return this.doAction('rollback');
}, },
edit() {
edit: function() {
var type = this.get('type').toLowerCase(); var type = this.get('type').toLowerCase();
if ( type === 'loadbalancerservice' ) if ( type === 'loadbalancerservice' )
{ {
@ -72,12 +71,12 @@ var Service = Resource.extend(ReadLabels, {
} }
}, },
scaleUp: function() { scaleUp() {
this.incrementProperty('scale'); this.incrementProperty('scale');
this.saveScale(); this.saveScale();
}, },
scaleDown: function() { scaleDown() {
if ( this.get('scale') >= 1 ) if ( this.get('scale') >= 1 )
{ {
this.decrementProperty('scale'); this.decrementProperty('scale');
@ -85,7 +84,15 @@ var Service = Resource.extend(ReadLabels, {
} }
}, },
clone: function() { upgrade() {
this.get('application').transitionToRoute('service.new', {queryParams: {
serviceId: this.get('id'),
upgrade: true,
environmentId: this.get('environmentId'),
}});
},
clone() {
var route; var route;
switch ( this.get('type').toLowerCase() ) switch ( this.get('type').toLowerCase() )
{ {
@ -104,7 +111,7 @@ var Service = Resource.extend(ReadLabels, {
}, },
scaleTimer: null, scaleTimer: null,
saveScale: function() { saveScale() {
if ( this.get('scaleTimer') ) if ( this.get('scaleTimer') )
{ {
Ember.run.cancel(this.get('scaleTimer')); Ember.run.cancel(this.get('scaleTimer'));
@ -126,6 +133,7 @@ var Service = Resource.extend(ReadLabels, {
{ label: 'Delete', icon: 'icon icon-trash', action: 'promptDelete', enabled: !!a.remove, altAction: 'delete', color: 'text-warning' }, { label: 'Delete', icon: 'icon icon-trash', action: 'promptDelete', enabled: !!a.remove, altAction: 'delete', color: 'text-warning' },
{ label: 'Purge', icon: '', action: 'purge', enabled: !!a.purge }, { label: 'Purge', icon: '', action: 'purge', enabled: !!a.purge },
{ divider: true }, { divider: true },
{ label: 'Upgrade', icon: 'fa fa-arrow-circle-o-up',action: 'upgrade', enabled: !!a.upgrade },
{ label: 'Finish Upgrade', icon: 'fa fa-thumbs-o-up', action: 'finishUpgrade', enabled: !!a.finishupgrade }, { label: 'Finish Upgrade', icon: 'fa fa-thumbs-o-up', action: 'finishUpgrade', enabled: !!a.finishupgrade },
{ label: 'Cancel Upgrade', icon: 'fa fa-life-ring', action: 'cancelUpgrade', enabled: !!a.cancelupgrade }, { label: 'Cancel Upgrade', icon: 'fa fa-life-ring', action: 'cancelUpgrade', enabled: !!a.cancelupgrade },
{ label: 'Rollback', icon: 'fa fa-history', action: 'rollback', enabled: !!a.rollback }, { label: 'Rollback', icon: 'fa fa-history', action: 'rollback', enabled: !!a.rollback },
@ -357,8 +365,14 @@ Service.reopenClass({
stateMap: { stateMap: {
'active': {icon: activeIcon, color: 'text-success'}, 'active': {icon: activeIcon, color: 'text-success'},
'upgrading': {icon: 'icon-arrow-up', color: 'text-info'}, 'canceled-rollback': {icon: 'fa fa-life-ring', color: 'text-info'},
'canceling-upgrade':{icon: 'icon-arrow-down', color: 'text-info'}, 'canceled-upgrade': {icon: 'fa fa-life-ring', color: 'text-info'},
'canceling-rollback': {icon: 'fa fa-life-ring', color: 'text-info'},
'canceling-upgrade': {icon: 'fa fa-life-ring', color: 'text-info'},
'finishing-upgrade': {icon: 'fa fa-arrow-circle-o-up', color: 'text-info'},
'rolling-back': {icon: 'fa fa-history', color: 'text-info'},
'upgraded': {icon: 'fa fa-arrow-circle-o-up', color: 'text-info'},
'upgrading': {icon: 'fa fa-arrow-circle-o-up', color: 'text-info'},
} }
}); });

View File

@ -1,10 +1,11 @@
import Ember from 'ember'; import Ember from 'ember';
export default Ember.Controller.extend({ export default Ember.Controller.extend({
queryParams: ['environmentId','serviceId','containerId'], queryParams: ['environmentId','serviceId','containerId','upgrade'],
environmentId: null, environmentId: null,
serviceId: null, serviceId: null,
containerId: null, containerId: null,
upgrade: null,
actions: { actions: {
done() { done() {

View File

@ -28,6 +28,15 @@ export default Ember.Route.extend({
var serviceLinks = []; var serviceLinks = [];
var secondaryLaunchConfigs = []; var secondaryLaunchConfigs = [];
if ( params.upgrade )
{
return Ember.Object.create({
service: serviceOrContainer.clone(),
allHosts: allHosts,
allServices: allServices,
});
}
var instanceData, serviceData, healthCheckData; var instanceData, serviceData, healthCheckData;
if ( serviceOrContainer ) if ( serviceOrContainer )
{ {
@ -100,6 +109,7 @@ export default Ember.Route.extend({
controller.set('environmentId', null); controller.set('environmentId', null);
controller.set('serviceId', null); controller.set('serviceId', null);
controller.set('containerId', null); controller.set('containerId', null);
controller.set('upgrade', null);
} }
} }
}); });

View File

@ -1,5 +1,6 @@
{{new-container {{new-container
isService=true isService=true
isUpgrade=upgrade
isSidekick=false isSidekick=false
launchConfig=model.service.launchConfig launchConfig=model.service.launchConfig
service=model.service service=model.service