Weight when defining service

This commit is contained in:
Vincent Fiduccia 2019-11-14 16:03:18 -07:00
parent 65c2de2cd8
commit 26bbd9d30d
No known key found for this signature in database
GPG Key ID: 2B29AD6BB2BB2582
11 changed files with 282 additions and 151 deletions

View File

@ -23,6 +23,10 @@ export default {
},
props: {
value: {
type: Object,
required: true,
},
isDemo: {
type: Boolean,
default: false
@ -39,10 +43,6 @@ export default {
type: Object,
required: true,
},
value: {
type: Object,
required: true,
},
mode: {
type: String,
required: true,
@ -50,6 +50,10 @@ export default {
realMode: {
type: String,
default: null,
},
registerAfterHook: {
type: Function,
default: null,
}
},
@ -91,7 +95,9 @@ export default {
build,
scaleInput,
scaleMode,
buildModeLabels: BUILD_MODES,
buildModeLabels: BUILD_MODES,
initialWeightPercent: this.value.weightsPercent.desired,
weightPercent: this.value.weightsPercent.desired,
};
},
@ -117,6 +123,40 @@ export default {
};
});
},
showWeight() {
return this.realMode === 'stage' || this.realMode === 'edit';
},
showVersion() {
return this.realMode === 'stage' || this.realMode === 'edit';
},
showScale() {
return true;
},
showNamespace() {
return this.realMode === 'create';
},
extraColumns() {
const out = [];
if ( this.showVersion ) {
out.push('version');
}
if ( this.showWeight ) {
out.push('weight');
}
if ( this.showScale ) {
out.push('scale');
}
return out;
}
},
watch: {
@ -125,6 +165,20 @@ export default {
},
},
created() {
this.registerAfterHook(() => {
if ( this.realMode !== 'stage' && this.realMode !== 'edit' ) {
return;
}
if ( this.weightPercent === this.initialWeightPercent ) {
return;
}
return this.value.saveWeightPercent(this.weightPercent);
});
},
methods: {
update() {
switch (this.buildMode) {
@ -188,7 +242,8 @@ export default {
:value="value"
:mode="mode"
:name-label="isSidecar ? 'Container Name' : 'Service Name'"
:extra-column="!isSidecar"
:extra-columns="extraColumns"
:namespaced="showNamespace"
>
<template v-if="isSidecar" #name>
<LabeledInput
@ -210,7 +265,19 @@ export default {
@input="updateGeneratedName"
/>
</template>
<template v-if="!isSidecar" #extra>
<template #version>
<LabeledInput
key="version"
v-model="spec.version"
:mode="mode"
label="Version"
:disabled="realMode !== 'stage'"
@input="updateGeneratedName"
/>
</template>
<template #scale>
<LabeledInput
key="scale"
v-model="scaleInput"
@ -226,14 +293,23 @@ export default {
</template>
</LabeledInput>
</template>
<template v-if="realMode === 'edit' || realMode === 'stage'" #namespace>
<template #weight>
<LabeledInput
key="version"
v-model="spec.version"
:mode="mode"
label="Version"
@input="updateGeneratedName"
/>
ref="weightPercent"
v-model.number="weightPercent"
type="number"
label="Weight"
size="4"
min="0"
max="100"
>
<template #suffix>
<div class="addon">
%
</div>
</template>
</LabeledInput>
</template>
</NameNsDescription>
<div class="spacer"></div>

View File

@ -7,7 +7,7 @@ import Labels from './Labels';
import Security from './Security';
import Upgrading from './Upgrading';
import Volumes from './Volumes';
import { CONFIG_MAP, SECRET } from '@/config/types';
import { CONFIG_MAP, SECRET, RIO } from '@/config/types';
import LoadDeps from '@/mixins/load-deps';
import Loading from '@/components/Loading';
import Tab from '@/components/Tabbed/Tab';
@ -108,6 +108,7 @@ export default {
const hash = await allHash({
configMaps: this.$store.dispatch('cluster/findAll', { type: CONFIG_MAP }),
secrets: this.$store.dispatch('cluster/findAll', { type: SECRET }),
services: this.$store.dispatch('cluster/findAll', { type: RIO.SERVICE }),
});
this.allSecrets = hash.secrets;
@ -199,6 +200,7 @@ function matchingNamespaceGroupedByKey(ary, namespace) {
:mode="mode"
:real-mode="realMode || mode"
:is-demo="isDemo"
:register-after-hook="registerAfterHook"
/>
<div class="spacer"></div>
<a href="#" @click.prevent="toggleTabs">

View File

@ -22,9 +22,9 @@ export default {
type: Boolean,
default: true,
},
extraColumn: {
type: Boolean,
default: false,
extraColumns: {
type: Array,
default: null
},
nameLabel: {
type: String,
@ -112,11 +112,11 @@ export default {
},
colSpan() {
const cols = 1 + (this.namespaced ? 1 : 0) + (this.extraColumn ? 1 : 0);
const cols = 1 + (this.namespaced ? 1 : 0) + this.extraColumns.length;
const span = 12 / cols;
return `span-${ span }`;
}
},
},
watch: {
@ -164,8 +164,8 @@ export default {
/>
</slot>
</div>
<div v-if="extraColumn" :class="{col: true, [colSpan]: true}">
<slot name="extra">
<div v-for="slot in extraColumns" :key="slot" :class="{col: true, [colSpan]: true}">
<slot :name="slot">
</slot>
</div>
</div>

View File

@ -1,6 +1,4 @@
<script>
import { RIO } from '@/config/types';
import { filterBy } from '@/utils/array';
import LabeledInput from '@/components/form/LabeledInput';
export default {
@ -26,33 +24,8 @@ export default {
},
computed: {
servicesForApp() {
const services = this.$store.getters['cluster/all'](RIO.SERVICE);
return filterBy(services, {
'app': this.row.app,
'metadata.namespace': this.row.metadata.namespace,
});
},
totalForApp() {
let desired = 0;
let current = 0;
let count = 0;
for ( const service of this.servicesForApp ) {
const weights = service.weights;
desired += weights.desired || 0;
current += weights.current || 0;
count++;
}
return {
desired,
current,
count
};
return this.row.weightsOfApp;
},
desired() {
@ -82,7 +55,7 @@ export default {
},
canAdjust() {
return this.totalForApp.count > 1;
return this.totalForApp.count > 1 && this.current !== 100;
},
newWeightValid() {
@ -94,59 +67,15 @@ export default {
methods: {
onShown() {
this.$nextTick(() => {
setTimeout(() => {
this.$refs.newPercent.focus();
});
}, 250);
},
setWeight() {
const currentPercent = this.desired || 0;
const newPercent = this.newPercent || 0;
const totalWeight = this.totalForApp.desired;
const count = this.totalForApp.count;
if ( currentPercent === 100 ) {
if ( newPercent === 100 ) {
return;
} else if ( newPercent === 0 ) {
this.row.saveWeight(0);
return;
}
const weight = newWeight(100 - newPercent) / (count - 1);
for ( const svc of this.servicesForApp ) {
if ( svc === this.row ) {
continue;
}
svc.saveWeight(weight);
}
} else if ( totalWeight === 0 || newPercent === 100 ) {
this.row.saveWeight(10000);
for ( const svc of this.servicesForApp ) {
if ( svc === this.row ) {
continue;
}
svc.saveWeight(0);
}
} else {
const weight = newWeight(newPercent);
this.row.saveWeight(weight);
}
function newWeight(percent) {
if ( percent === 0 ) {
return 0;
}
const out = Math.round(totalWeight / (1 - (percent / 100))) - totalWeight;
return out;
}
this.row.saveWeightPercent(newPercent);
},
}
};

View File

@ -75,6 +75,14 @@ export const FRIENDLY = {
model.spec.app = model.app;
delete model.spec.version;
}
if ( mode === _CREATE ) {
model.spec.weight = 10000;
} else if ( mode === _CLONE ) {
delete model.spec.weight;
} else if ( mode === _STAGE ) {
model.spec.weight = 0;
}
},
},

View File

@ -1,5 +1,3 @@
import { RIO } from './types';
// Note: 'id' is always the last sort, so you don't have to specify it here.
export const STATE = {

View File

@ -1,5 +1,6 @@
import { allHash } from '@/utils/promise';
import { findBy } from '@/utils/array';
import { sortBy } from '@/utils/sort';
let NEXT_ID = 1;
@ -21,11 +22,11 @@ export default {
throw new Error('Must specify key');
}
const hooks = (this[key] || []).sortBy('priority', 'name');
const hooks = sortBy(this[key] || [], ['priority', 'name']);
const promises = {};
hooks.forEach((x) => {
promises[x.name] = x.fn(...args);
promises[x.name] = x.fn.apply(this, args);
});
return allHash(promises);

View File

@ -1,4 +1,4 @@
import ChildHook from './child-hook';
import ChildHook, { BEFORE_SAVE_HOOKS, AFTER_SAVE_HOOKS } from './child-hook';
import { _CREATE, _EDIT, _VIEW } from '@/config/query-params';
export default {
@ -82,28 +82,27 @@ export default {
async save(buttonDone) {
this.errors = null;
try {
await this.applyHooks(BEFORE_SAVE_HOOKS);
if ( this.isCreate ) {
await this.schema.followLink('collection', {
urlSuffix: ( this.namespaceSuffixOnCreate ? `/${ this.value.metadata.namespace }` : null),
method: 'POST',
headers: {
'content-type': 'application/json',
accept: 'application/json',
},
data: this.value,
});
let url = this.schema.linkFor('collection');
if ( this.namespaceSuffixOnCreate ) {
url += `/${ this.value.metadata.namespace }`;
}
const res = await this.value.save({ url });
Object.assign(this.value, res);
await this.value.$dispatch('load', this.value);
} else {
await this.value.followLink('update', {
method: 'PUT',
headers: {
'content-type': 'application/json',
accept: 'application/json',
},
data: this.value,
});
await this.value.save();
}
await this.applyHooks(AFTER_SAVE_HOOKS);
buttonDone(true);
this.done();
} catch (err) {
if ( err && err.response && err.response.data ) {

View File

@ -1,10 +1,11 @@
import day from 'dayjs';
import { insertAt } from '@/utils/array';
import { insertAt, filterBy } from '@/utils/array';
import { ADD_SIDECAR, _FLAGGED, MODE, _STAGE } from '@/config/query-params';
import { escapeHtml } from '@/utils/string';
import { DATE_FORMAT, TIME_FORMAT } from '@/store/prefs';
import { addParams } from '@/utils/url';
import { PRIVATE } from '@/plugins/norman/resource-proxy';
import { RIO } from '@/config/types';
const EMPTY = {};
@ -228,8 +229,136 @@ export default {
};
},
allVersions() {
const services = this.$getters['all'](RIO.SERVICE);
const out = filterBy(services, {
'app': this.app,
'metadata.namespace': this.metadata.namespace,
});
return out;
},
weightsOfApp() {
let desired = 0;
let current = 0;
let count = 0;
for ( const service of this.allVersions ) {
const weights = service.weights;
desired += weights.desired || 0;
current += weights.current || 0;
count++;
}
return {
desired,
current,
count
};
},
weights() {
let current = 0;
let desired = 0;
const fromSpec = this.spec.weight;
if ( this.status ) {
const fromStatus = this.status.computedWeight;
if ( typeof fromStatus === 'number' ) {
current = fromStatus;
} else if ( typeof fromSpec === 'number' ) {
current = fromSpec;
}
if ( typeof fromSpec === 'number' ) {
desired = fromSpec;
} else if ( typeof fromStatus === 'number' ) {
desired = fromStatus;
}
}
return { current, desired };
},
weightsPercent() {
const self = this.weights;
const app = this.weightsOfApp;
let desired = 0;
let current = 0;
if ( self.desired && app.desired ) {
desired = self.desired / app.desired * 100;
}
if ( self.current && app.current ) {
current = self.current / app.current * 100;
}
return { current, desired };
},
saveWeightPercent() {
return (newPercent) => {
const appInfo = this.weightsOfApp;
const totalWeight = appInfo.desired;
const currentPercent = (totalWeight === 0 ? 0 : this.weights.desired / totalWeight);
const currentWeight = this.spec.weight || 0;
const totalOfOthers = totalWeight - currentWeight;
const count = appInfo.count;
if ( currentPercent === 100 ) {
if ( newPercent === 100 ) {
return;
} else if ( newPercent === 0 ) {
return this.saveWeight(0);
}
const weight = newWeight(100 - newPercent) / (count - 1);
for ( const svc of this.allVersions ) {
if ( svc.id === this.id ) {
continue;
}
svc.saveWeight(weight);
}
} else if ( totalOfOthers === 0 || newPercent === 100 ) {
this.saveWeight(10000);
for ( const svc of this.allVersions ) {
if ( svc.id === this.id ) {
continue;
}
svc.saveWeight(0);
}
} else {
const weight = newWeight(newPercent);
this.saveWeight(weight);
}
function newWeight(percent) {
if ( percent === 0 ) {
return 0;
}
const out = Math.round(totalOfOthers / (1 - (percent / 100))) - totalOfOthers;
return out;
}
};
},
saveWeight() {
return async(neu) => {
console.log('Save Weight', this.spec.app, this.spec.version, neu);
try {
await this.patch([{
op: 'replace',
@ -242,32 +371,6 @@ export default {
};
},
weights() {
let current = 0;
let desired = 0;
const spec = this.spec.weight;
if ( !this.status ) {
return { current, desired };
}
const status = this.status.computedWeight;
if ( typeof status === 'number' ) {
current = status;
} else if ( typeof spec === 'number' ) {
current = spec;
}
if ( typeof spec === 'number' ) {
desired = spec;
} else if ( typeof status === 'number' ) {
desired = status;
}
return { current, desired };
},
async pauseOrResume(pause = true) {
try {
await this.patch({

View File

@ -36,7 +36,6 @@ export default {
},
async asyncData(ctx) {
const { route } = ctx;
const { resource } = ctx.params;
const friendly = FRIENDLY[resource];
const type = friendly.type;

View File

@ -433,20 +433,36 @@ export default {
},
save() {
return (opt = {}) => {
return async(opt = {}) => {
delete this.__rehydrate;
if ( !opt.url ) {
opt.url = this.linkFor('self');
opt.url = this.linkFor('update') || this.linkFor('self');
}
if ( !opt.method ) {
opt.method = (this.id ? 'put' : 'post');
}
if ( !opt.headers ) {
opt.headers = {};
}
if ( !opt.headers['content-type'] ) {
opt.headers['content-type'] = 'application/json';
}
if ( !opt.headers['accept'] ) {
opt.headers['accept'] = 'application/json';
}
opt.data = this;
return this.$dispatch('request', opt);
const res = await this.$dispatch('request', opt);
await this.$dispatch('load', res);
return res;
};
},