ui: deploy VM - FIX missing custom iops field (#5199)

Fixes #5192
This commit is contained in:
Hoang Nguyen 2021-07-15 14:23:50 +07:00 committed by GitHub
parent 6b5adb7ed5
commit 7123269937
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 244 additions and 26 deletions

View File

@ -2692,7 +2692,7 @@
"message.delete.vpn.customer.gateway": "Please confirm that you want to delete this VPN Customer Gateway",
"message.delete.vpn.gateway": "Please confirm that you want to delete this VPN Gateway",
"message.deleting.vm": "Deleting VM",
"message.deployasis": "Selected template is Deploy As-Is i.e., the VM is deployed by importing an OVA with vApps directly into vCenter. Root disk(s) resize is allowed only on stopped VMs for such templates.",
"message.deployasis": "Selected template is Deploy As-Is i.e., the VM is deployed by importing an OVA with vApps directly into vCenter. Root disk(s) resize is allowed only on stopped VMs for such templates.",
"message.desc.add.new.lb.sticky.rule": "Add new LB sticky rule",
"message.desc.advanced.zone": "For more sophisticated network topologies. This network model provides the most flexibility in defining guest networks and providing custom network offerings such as firewall, VPN, or load balancer support.",
"message.desc.basic.zone": "Provide a single network where each VM instance is assigned an IP directly from the network. Guest isolation can be provided through layer-3 means such as security groups (IP address source filtering).",
@ -2850,6 +2850,7 @@
"message.error.upload.template.description": "Only one template can be uploaded at a time",
"message.error.url": "Please enter URL",
"message.error.username": "Enter your username",
"message.error.valid.iops.range": "Please enter a valid IOPS range",
"message.error.vcenter.datacenter": "Please enter vCenter Datacenter",
"message.error.vcenter.datastore": "Please enter vCenter Datastore",
"message.error.vcenter.host": "Please enter vCenter Host",

View File

@ -31,7 +31,7 @@ export default {
params: { isrecursive: 'true' },
columns: ['name', 'displaytext', 'cpunumber', 'cpuspeed', 'memory', 'domain', 'zone', 'order'],
details: () => {
var fields = ['name', 'id', 'displaytext', 'offerha', 'provisioningtype', 'storagetype', 'iscustomized', 'limitcpuuse', 'cpunumber', 'cpuspeed', 'memory', 'hosttags', 'tags', 'domain', 'zone', 'created']
var fields = ['name', 'id', 'displaytext', 'offerha', 'provisioningtype', 'storagetype', 'iscustomized', 'iscustomizediops', 'limitcpuuse', 'cpunumber', 'cpuspeed', 'memory', 'hosttags', 'tags', 'domain', 'zone', 'created']
if (store.getters.apis.createServiceOffering &&
store.getters.apis.createServiceOffering.params.filter(x => x.name === 'storagepolicy').length > 0) {
fields.splice(6, 0, 'vspherestoragepolicy')
@ -124,7 +124,7 @@ export default {
params: { isrecursive: 'true' },
columns: ['name', 'displaytext', 'disksize', 'domain', 'zone', 'order'],
details: () => {
var fields = ['name', 'id', 'displaytext', 'disksize', 'provisioningtype', 'storagetype', 'iscustomized', 'tags', 'domain', 'zone', 'created']
var fields = ['name', 'id', 'displaytext', 'disksize', 'provisioningtype', 'storagetype', 'iscustomized', 'iscustomizediops', 'tags', 'domain', 'zone', 'created']
if (store.getters.apis.createDiskOffering &&
store.getters.apis.createDiskOffering.params.filter(x => x.name === 'storagepolicy').length > 0) {
fields.splice(6, 0, 'vspherestoragepolicy')

View File

@ -107,7 +107,7 @@
<span>
{{ $t('label.override.rootdisk.size') }}
<a-switch
:checked="showRootDiskSizeChanger && rootDiskSizeFixed > 0"
:default-checked="showRootDiskSizeChanger && rootDiskSizeFixed > 0"
:disabled="rootDiskSizeFixed > 0 || template.deployasis"
@change="val => { this.showRootDiskSizeChanger = val }"
style="margin-left: 10px;"/>
@ -117,6 +117,7 @@
v-show="showRootDiskSizeChanger"
input-decorator="rootdisksize"
:preFillContent="dataPreFill"
:isCustomized="true"
:minDiskSize="dataPreFill.minrootdisksize"
@update-disk-size="updateFieldValue"
style="margin-top: 10px;"/>
@ -202,7 +203,7 @@
@handle-search-filter="($event) => handleSearchFilter('serviceOfferings', $event)"
></compute-offering-selection>
<compute-selection
v-if="serviceOffering && serviceOffering.iscustomized"
v-if="serviceOffering && (serviceOffering.iscustomized || serviceOffering.iscustomizediops)"
cpunumber-input-decorator="cpunumber"
cpuspeed-input-decorator="cpuspeed"
memory-input-decorator="memory"
@ -213,6 +214,10 @@
:maxCpu="'serviceofferingdetails' in serviceOffering ? serviceOffering.serviceofferingdetails.maxcpunumber*1 : Number.MAX_SAFE_INTEGER"
:minMemory="'serviceofferingdetails' in serviceOffering ? serviceOffering.serviceofferingdetails.minmemory*1 : 0"
:maxMemory="'serviceofferingdetails' in serviceOffering ? serviceOffering.serviceofferingdetails.maxmemory*1 : Number.MAX_SAFE_INTEGER"
:isCustomized="serviceOffering.iscustomized"
:isCustomizedIOps="'iscustomizediops' in serviceOffering && serviceOffering.iscustomizediops"
@handler-error="handlerError"
@update-iops-value="updateIOPSValue"
@update-compute-cpunumber="updateFieldValue"
@update-compute-cpuspeed="updateFieldValue"
@update-compute-memory="updateFieldValue" />
@ -260,14 +265,19 @@
:loading="loading.diskOfferings"
:preFillContent="dataPreFill"
:isIsoSelected="tabKey==='isoid'"
@on-selected-disk-size="onSelectDiskSize"
@select-disk-offering-item="($event) => updateDiskOffering($event)"
@handle-search-filter="($event) => handleSearchFilter('diskOfferings', $event)"
></disk-offering-selection>
<disk-size-selection
v-if="diskOffering && diskOffering.iscustomized"
v-if="diskOffering && (diskOffering.iscustomized || diskOffering.iscustomizediops)"
input-decorator="size"
:preFillContent="dataPreFill"
@update-disk-size="updateFieldValue" />
:diskSelected="diskSelected"
:isCustomized="diskOffering.iscustomized"
@handler-error="handlerError"
@update-disk-size="updateFieldValue"
@update-iops-value="updateIOPSValue"/>
<a-form-item class="form-item-hidden">
<a-input v-decorator="['size']"/>
</a-form-item>
@ -789,7 +799,13 @@ export default {
showDetails: false,
showRootDiskSizeChanger: false,
securitygroupids: [],
rootDiskSizeFixed: 0
rootDiskSizeFixed: 0,
error: false,
diskSelected: {},
diskIOpsMin: 0,
diskIOpsMax: 0,
minIOPs: 0,
maxIOPs: 0
}
},
computed: {
@ -983,6 +999,12 @@ export default {
},
showSecurityGroupSection () {
return (this.networks.length > 0 && this.zone.securitygroupsenabled) || (this.zone && this.zone.networktype === 'Basic')
},
isCustomizedDiskIOPS () {
return this.diskSelected?.iscustomizediops || false
},
isCustomizedIOPS () {
return this.serviceOffering?.iscustomizediops || false
}
},
watch: {
@ -1424,6 +1446,13 @@ export default {
})
return
}
if (this.error) {
this.$notification.error({
message: this.$t('message.request.failed'),
description: this.$t('error.form.message')
})
return
}
this.loading.deploy = true
@ -1474,6 +1503,10 @@ export default {
if (this.selectedTemplateConfiguration) {
deployVmData['details[0].configurationId'] = this.selectedTemplateConfiguration.id
}
if (this.isCustomizedIOPS) {
deployVmData['details[0].minIops'] = this.minIOPs
deployVmData['details[0].maxIops'] = this.maxIOPs
}
// step 4: select disk offering
if (!this.template.deployasis && this.template.childtemplates && this.template.childtemplates.length > 0) {
if (values.multidiskoffering) {
@ -1492,6 +1525,10 @@ export default {
deployVmData.size = values.size
}
}
if (this.isCustomizedDiskIOPS) {
deployVmData['details[0].minIopsDo'] = this.diskIOpsMin
deployVmData['details[0].maxIopsDo'] = this.diskIOpsMax
}
// step 5: select an affinity group
deployVmData.affinitygroupids = (values.affinitygroupids || []).join(',')
// step 6: select network
@ -1994,6 +2031,15 @@ export default {
this.rootDiskSizeFixed = offering.rootdisksize
this.showRootDiskSizeChanger = false
}
},
handlerError (error) {
this.error = error
},
onSelectDiskSize (rowSelected) {
this.diskSelected = rowSelected
},
updateIOPSValue (input, value) {
this[input] = value
}
}
}

View File

@ -19,7 +19,7 @@
<a-card>
<a-col>
<a-row>
<a-col :md="colContraned" :lg="colContraned">
<a-col :md="colContraned" :lg="colContraned" v-if="isCustomized">
<a-form-item
:label="$t('label.cpunumber')"
:validate-status="errors.cpu.status"
@ -43,7 +43,7 @@
</a-row>
</a-form-item>
</a-col>
<a-col :md="8" :lg="8" v-show="!isConstrained">
<a-col :md="8" :lg="8" v-show="!isConstrained" v-if="isCustomized">
<a-form-item
:label="$t('label.cpuspeed')"
:validate-status="errors.cpuspeed.status"
@ -55,7 +55,7 @@
/>
</a-form-item>
</a-col>
<a-col :md="colContraned" :lg="colContraned">
<a-col :md="colContraned" :lg="colContraned" v-if="isCustomized">
<a-form-item
:label="$t('label.memory.mb')"
:validate-status="errors.memory.status"
@ -78,6 +78,18 @@
</a-row>
</a-form-item>
</a-col>
<a-col :md="8" v-if="isCustomizedIOps">
<a-form-item :label="$t('label.miniops')">
<a-input-number v-model="minIOps" @change="updateIOpsValue" />
<p v-if="errorMinIOps" style="color: red"> {{ $t(errorMinIOps) }} </p>
</a-form-item>
</a-col>
<a-col :md="8" v-if="isCustomizedIOps">
<a-form-item :label="$t('label.maxiops')">
<a-input-number v-model="maxIOps" @change="updateIOpsValue" />
<p v-if="errorMaxIOps" style="color: red"> {{ $t(errorMaxIOps) }} </p>
</a-form-item>
</a-col>
</a-row>
</a-col>
</a-card>
@ -126,6 +138,14 @@ export default {
preFillContent: {
type: Object,
default: () => {}
},
isCustomized: {
type: Boolean,
default: false
},
isCustomizedIOps: {
type: Boolean,
default: false
}
},
data () {
@ -146,7 +166,11 @@ export default {
status: '',
message: ''
}
}
},
minIOps: null,
maxIOps: null,
errorMinIOps: false,
errorMaxIOps: false
}
},
computed: {
@ -162,7 +186,9 @@ export default {
}
},
mounted () {
this.fillValue()
if (this.isCustomized) {
this.fillValue()
}
},
methods: {
fillValue () {
@ -247,6 +273,34 @@ export default {
}
return true
},
updateIOpsValue () {
let flag = true
this.errorMinIOps = false
this.errorMaxIOps = false
if (this.minIOps < 0) {
this.errorMinIOps = `${this.$t('message.error.limit.value')} 0`
flag = false
}
if (this.maxIOps < 0) {
this.errorMaxIOps = `${this.$t('message.error.limit.value')} 0`
flag = false
}
if (!flag) {
this.$emit('handler-error', true)
return
}
if (this.minIOps > this.maxIOps) {
this.errorMinIOps = this.$t('message.error.valid.iops.range')
this.errorMaxIOps = this.$t('message.error.valid.iops.range')
this.$emit('handler-error', true)
return
}
this.$emit('update-iops-value', 'minIOPs', this.minIOps)
this.$emit('update-iops-value', 'maxIOPs', this.maxIOps)
this.$emit('handler-error', false)
}
}
}

View File

@ -128,7 +128,8 @@ export default {
page: 1,
pageSize: 10,
keyword: null
}
},
diskSelected: {}
}
},
created () {
@ -213,8 +214,13 @@ export default {
}
},
onSelectRow (value) {
const rowSelected = this.items.filter(item => item.id === value[0])
if (rowSelected && rowSelected.length > 0) {
this.diskSelected = rowSelected[0]
}
this.selectedRowKeys = value
this.$emit('select-disk-offering-item', value[0])
this.$emit('on-selected-disk-size', this.diskSelected)
},
handleSearch (value) {
this.filter = value
@ -237,8 +243,13 @@ export default {
return {
on: {
click: () => {
const rowSelected = this.items.filter(item => item.id === record.key)
if (rowSelected && rowSelected.length > 0) {
this.diskSelected = rowSelected[0]
}
this.selectedRowKeys = [record.key]
this.$emit('select-disk-offering-item', record.key)
this.$emit('on-selected-disk-size', this.diskSelected)
}
}
}

View File

@ -16,11 +16,11 @@
// under the License.
<template>
<a-form-item
:label="inputDecorator === 'rootdisksize' ? $t('label.root.disk.size') : $t('label.disksize')"
class="form-item">
<a-row :gutter="12">
<a-col :md="4" :lg="4">
<a-row :span="24" :style="{ marginTop: '20px' }">
<a-col :span="isCustomizedDiskIOps ? 8 : 24" v-if="isCustomized">
<a-form-item
:label="inputDecorator === 'rootdisksize' ? $t('label.root.disk.size') : $t('label.disksize')"
class="form-item">
<span style="display: inline-flex">
<a-input-number
autoFocus
@ -29,10 +29,22 @@
/>
<span style="padding-top: 6px; margin-left: 5px">GB</span>
</span>
</a-col>
</a-row>
<p v-if="error" style="color: red"> {{ $t(error) }} </p>
</a-form-item>
<p v-if="error" style="color: red"> {{ $t(error) }} </p>
</a-form-item>
</a-col>
<a-col :span="8" v-if="isCustomizedDiskIOps">
<a-form-item :label="$t('label.diskiopsmin')">
<a-input-number v-model="minIOps" @change="updateDiskIOps" />
<p v-if="errorMinIOps" style="color: red"> {{ $t(errorMinIOps) }} </p>
</a-form-item>
</a-col>
<a-col :span="8" v-if="isCustomizedDiskIOps">
<a-form-item :label="$t('label.diskiopsmax')">
<a-input-number v-model="maxIOps" @change="updateDiskIOps" />
<p v-if="errorMaxIOps" style="color: red"> {{ $t(errorMaxIOps) }} </p>
</a-form-item>
</a-col>
</a-row>
</template>
<script>
@ -50,6 +62,14 @@ export default {
minDiskSize: {
type: Number,
default: 0
},
diskSelected: {
type: Object,
default: () => {}
},
isCustomized: {
type: Boolean,
default: false
}
},
watch: {
@ -60,10 +80,19 @@ export default {
}
}
},
computed: {
isCustomizedDiskIOps () {
return this.diskSelected?.iscustomizediops || false
}
},
data () {
return {
inputValue: 0,
error: false
error: false,
minIOps: null,
maxIOps: null,
errorMinIOps: false,
errorMaxIOps: false
}
},
mounted () {
@ -87,6 +116,34 @@ export default {
}
this.error = false
this.$emit('update-disk-size', this.inputDecorator, value)
},
updateDiskIOps () {
let flag = true
this.errorMinIOps = false
this.errorMaxIOps = false
if (this.minIOps < 0) {
this.errorMinIOps = `${this.$t('message.error.limit.value')} 0`
flag = false
}
if (this.maxIOps < 0) {
this.errorMaxIOps = `${this.$t('message.error.limit.value')} 0`
flag = false
}
if (!flag) {
this.$emit('handler-error', true)
return
}
if (this.minIOps > this.maxIOps) {
this.errorMinIOps = this.$t('message.error.valid.iops.range')
this.errorMaxIOps = this.$t('message.error.valid.iops.range')
this.$emit('handler-error', true)
return
}
this.$emit('update-iops-value', 'diskIOpsMin', this.minIOps)
this.$emit('update-iops-value', 'diskIOpsMax', this.maxIOps)
this.$emit('handler-error', false)
}
}
}

View File

@ -65,7 +65,7 @@
initialValue: selectedDiskOfferingId,
rules: [{ required: true, message: $t('message.error.select') }]}]"
:loading="loading"
@change="id => (customDiskOffering = offerings.filter(x => x.id === id)[0].iscustomized || false)"
@change="id => onChangeDiskOffering(id)"
>
<a-select-option
v-for="(offering, index) in offerings"
@ -89,6 +89,48 @@
:placeholder="$t('label.disksize')"/>
</a-form-item>
</span>
<span v-if="isCustomizedDiskIOps">
<a-form-item>
<span slot="label">
{{ $t('label.miniops') }}
<a-tooltip :title="apiParams.miniops.description">
<a-icon type="info-circle" style="color: rgba(0,0,0,.45)" />
</a-tooltip>
</span>
<a-input
v-decorator="['miniops', {
rules: [{
validator: (rule, value, callback) => {
if (value && (isNaN(value) || value <= 0)) {
callback(this.$t('message.error.number'))
}
callback()
}
}]
}]"
:placeholder="this.$t('label.miniops')"/>
</a-form-item>
<a-form-item>
<span slot="label">
{{ $t('label.maxiops') }}
<a-tooltip :title="apiParams.maxiops.description">
<a-icon type="info-circle" style="color: rgba(0,0,0,.45)" />
</a-tooltip>
</span>
<a-input
v-decorator="['maxiops', {
rules: [{
validator: (rule, value, callback) => {
if (value && (isNaN(value) || value <= 0)) {
callback(this.$t('message.error.number'))
}
callback()
}
}]
}]"
:placeholder="this.$t('label.maxiops')"/>
</a-form-item>
</span>
<div :span="24" class="action-button">
<a-button @click="closeModal">{{ $t('label.cancel') }}</a-button>
<a-button type="primary" @click="handleSubmit">{{ $t('label.ok') }}</a-button>
@ -109,7 +151,8 @@ export default {
selectedZoneId: '',
selectedDiskOfferingId: '',
customDiskOffering: false,
loading: false
loading: false,
isCustomizedDiskIOps: false
}
},
beforeCreate () {
@ -139,6 +182,7 @@ export default {
this.offerings = json.listdiskofferingsresponse.diskoffering || []
this.selectedDiskOfferingId = this.offerings[0].id || ''
this.customDiskOffering = this.offerings[0].iscustomized || false
this.isCustomizedDiskIOps = this.offerings[0]?.iscustomizediops || false
}).finally(() => {
this.loading = false
})
@ -180,6 +224,11 @@ export default {
},
closeModal () {
this.$emit('close-action')
},
onChangeDiskOffering (id) {
const offering = this.offerings.filter(x => x.id === id)
this.customDiskOffering = offering[0]?.iscustomized || false
this.isCustomizedDiskIOps = offering[0]?.iscustomizediops || false
}
}
}