From b605ac01c49c93234db865adcd3f69036c2f2cb3 Mon Sep 17 00:00:00 2001 From: "Jingwei.Zhang" Date: Wed, 29 Jun 2022 15:38:34 +0800 Subject: [PATCH] feat: support quota info when snapshot create volume 1. Support quota info when snapshot create volume 2. Disable click submit button when quota is insufficient Change-Id: I81a3bde0f4bd7bdc403bea8176754c5dc5a32373 --- .../Snapshot/actions/CreateVolume.jsx | 38 +++- src/resources/cinder/volume.jsx | 185 ++++++++++++++++++ src/stores/cinder/volume.js | 39 +++- 3 files changed, 256 insertions(+), 6 deletions(-) diff --git a/src/pages/storage/containers/Snapshot/actions/CreateVolume.jsx b/src/pages/storage/containers/Snapshot/actions/CreateVolume.jsx index 3eab7a5b..464835e2 100644 --- a/src/pages/storage/containers/Snapshot/actions/CreateVolume.jsx +++ b/src/pages/storage/containers/Snapshot/actions/CreateVolume.jsx @@ -15,6 +15,14 @@ import { inject, observer } from 'mobx-react'; import { ModalAction } from 'containers/Action'; import globalVolumeStore from 'stores/cinder/volume'; +import { + getQuotaInfo, + checkQuotaDisable, + fetchQuota, + setCreateVolumeType, + onVolumeSizeChange, + onVolumeTypeChange, +} from 'resources/cinder/volume'; export class CreateVolume extends ModalAction { static id = 'create'; @@ -22,8 +30,9 @@ export class CreateVolume extends ModalAction { static title = t('Create Volume'); init() { - this.volumeStore = globalVolumeStore; + this.store = globalVolumeStore; this.getVolumeTypes(); + fetchQuota(this, this.item.size); } get name() { @@ -34,6 +43,22 @@ export class CreateVolume extends ModalAction { static allowed = () => Promise.resolve(true); + static get disableSubmit() { + return checkQuotaDisable(); + } + + static get showQuota() { + return true; + } + + get showQuota() { + return true; + } + + get quotaInfo() { + return getQuotaInfo(this); + } + get volumeTypeParams() { return {}; } @@ -42,19 +67,21 @@ export class CreateVolume extends ModalAction { const { volume_id: id } = this.item; // eslint-disable-next-line no-unused-vars const [_, volume] = await Promise.all([ - this.volumeStore.fetchVolumeTypes(this.volumeTypeParams), - this.volumeStore.fetchDetail({ id }), + this.store.fetchVolumeTypes(this.volumeTypeParams), + this.store.fetchDetail({ id }), ]); const { volume_type: volumeType } = volume; const typeItem = this.volumeTypes.find((it) => it.label === volumeType); if (typeItem) { this.volumeType = typeItem.value; + setCreateVolumeType(volumeType); } this.updateFormValue('volume_type', this.volumeType); + this.updateDefaultValue(); } get volumeTypes() { - return this.volumeStore.volumeTypes || []; + return this.store.volumeTypes || []; } get defaultValue() { @@ -94,6 +121,7 @@ export class CreateVolume extends ModalAction { min: this.minSize, extra: `${t('Min size')}: ${this.minSize}GiB`, required: true, + onChange: onVolumeSizeChange, }, { name: 'more', @@ -107,6 +135,8 @@ export class CreateVolume extends ModalAction { options: this.volumeTypes, placeholder: t('Please select volume type'), hidden: !more, + onChange: onVolumeTypeChange, + allowClear: false, }, ]; } diff --git a/src/resources/cinder/volume.jsx b/src/resources/cinder/volume.jsx index af061d3b..9ec29588 100644 --- a/src/resources/cinder/volume.jsx +++ b/src/resources/cinder/volume.jsx @@ -15,6 +15,9 @@ import React from 'react'; import { yesNoOptions } from 'utils/constants'; import { toLocalTimeFilter } from 'utils/index'; +import globalProjectStore from 'stores/keystone/project'; +import globalVolumeStore from 'stores/cinder/volume'; +import { isEmpty } from 'lodash'; export const volumeStatus = { available: t('Available'), @@ -338,3 +341,185 @@ export const getVolumeColumnsList = (self) => { } return columns; }; + +// deal with quota +export function setCreateVolumeSize(value) { + globalVolumeStore.setCreateVolumeSize(value); +} + +export function setCreateVolumeType(value) { + globalVolumeStore.setCreateVolumeType(value); +} + +export function setCreateVolumeCount(value) { + globalVolumeStore.setCreateVolumeCount(value); +} + +export async function fetchQuota(self, size) { + setCreateVolumeCount(1); + setCreateVolumeSize(size); + setCreateVolumeType(''); + self.setState({ + quota: {}, + quotaLoading: true, + }); + const result = await globalProjectStore.fetchProjectCinderQuota(); + self.setState({ + quota: result, + quotaLoading: false, + }); +} + +export const getQuota = (cinderQuota) => { + if (isEmpty(cinderQuota)) { + return {}; + } + const { volumeTypeForCreate } = globalVolumeStore; + const { volumes = {}, gigabytes = {} } = cinderQuota || {}; + const typeQuotaKey = `volumes_${volumeTypeForCreate}`; + const sizeQuotaKey = `gigabytes_${volumeTypeForCreate}`; + const typeQuota = (cinderQuota || {})[typeQuotaKey] || {}; + const typeSizeQuota = (cinderQuota || {})[sizeQuotaKey] || {}; + return { + volumes, + gigabytes, + typeQuota, + typeSizeQuota, + }; +}; + +const getErrorMessage = ({ name, left, input }) => { + const error = t( + 'Quota: Insufficient { name } quota to create resources, please adjust resource quantity or quota(left { left }, input { input }).', + { + name, + left, + input, + } + ); + return error; +}; + +export const getAdd = (cinderQuota) => { + if (isEmpty(cinderQuota)) { + return {}; + } + const { volumes, gigabytes, typeQuota, typeSizeQuota } = + getQuota(cinderQuota); + const { left = 0 } = volumes || {}; + const { left: sizeLeft = 0, limit: sizeLimit } = gigabytes || {}; + const { left: typeLeft = 0 } = typeQuota || {}; + const { left: typeSizeLeft = 0, limit: typeSizeLimit } = typeSizeQuota || {}; + const { + volumeSizeForCreate: size = 0, + volumeCountForCreate: count = 1, + volumeTypeForCreate: type = '', + } = globalVolumeStore; + const zero = { + add: 0, + addSize: 0, + }; + const totalSize = size * count; + const create = { + add: count, + addSize: totalSize, + }; + if (left >= 0 && left < count) { + const error = getErrorMessage({ + name: t('volume'), + left, + input: count, + }); + return { ...zero, error }; + } + if (sizeLimit !== -1 && sizeLeft < totalSize) { + const error = getErrorMessage({ + name: t('gigabytes'), + left: sizeLeft, + input: totalSize, + }); + return { ...zero, error }; + } + if (isEmpty(typeQuota)) { + return create; + } + if (typeLeft >= 0 && typeLeft < count) { + const error = getErrorMessage({ + name: t('{name} type', { name: type }), + left: typeLeft, + input: count, + }); + return { ...zero, error }; + } + if (typeSizeLimit !== -1 && typeSizeLeft < totalSize) { + const error = getErrorMessage({ + name: t('{name} type gigabytes', { name: type }), + left: typeSizeLeft, + input: totalSize, + }); + return { ...zero, error }; + } + return create; +}; + +export const getQuotaInfo = (self) => { + const { volumeTypeForCreate: name } = globalVolumeStore; + const { quota = {}, quotaLoading } = self.state; + if (quotaLoading || isEmpty(quota)) { + return []; + } + const { + volumes = {}, + gigabytes = {}, + typeQuota = {}, + typeSizeQuota = {}, + } = getQuota(quota); + const { add, addSize } = getAdd(quota); + const volumeData = { + ...volumes, + add, + name: 'volume', + title: t('Volume'), + }; + const sizeData = { + ...gigabytes, + add: addSize, + name: 'gigabytes', + title: t('Gigabytes (GiB)'), + type: 'line', + }; + if (!name) { + return [volumeData, sizeData]; + } + const typeData = { + ...typeQuota, + add, + name: 'type', + title: t('{name} type', { name }), + type: 'line', + }; + const typeSizeData = { + ...typeSizeQuota, + add: addSize, + name: 'typeSize', + title: t('{name} type gigabytes', { name }), + type: 'line', + }; + return [volumeData, sizeData, typeData, typeSizeData]; +}; + +export const checkQuotaDisable = () => { + const { cinderQuota = {} } = globalProjectStore; + const { add } = getAdd(cinderQuota); + return add === 0; +}; + +export const onVolumeSizeChange = (value) => { + setCreateVolumeSize(value); +}; + +export const onVolumeTypeChange = (value) => { + const { volumeTypes = [] } = globalVolumeStore; + const item = volumeTypes.find((it) => it.value === value); + setCreateVolumeType(item.label); +}; diff --git a/src/stores/cinder/volume.js b/src/stores/cinder/volume.js index 11d2bbb6..36374c29 100644 --- a/src/stores/cinder/volume.js +++ b/src/stores/cinder/volume.js @@ -13,7 +13,6 @@ // limitations under the License. import { action, observable } from 'mobx'; -import { isOsDisk } from 'resources/cinder/volume'; import { renderFilterMap } from 'utils/index'; import client from 'client'; import Base from 'stores/base'; @@ -32,6 +31,15 @@ export class VolumeStore extends Base { @observable quotaSet = {}; + @observable + volumeTypeForCreate = ''; + + @observable + volumeSizeForCreate = 0; + + @observable + volumeCountForCreate = 1; + get client() { return client.cinder.volumes; } @@ -56,10 +64,15 @@ export class VolumeStore extends Base { return this.skylineClient.extension.volumes(params); } + isOsDisk(item) { + const { isOsDisk } = require('resources/cinder/volume'); + return isOsDisk(item); + } + get mapper() { return (volume) => ({ ...volume, - disk_tag: isOsDisk(volume) ? 'os_disk' : 'data_disk', + disk_tag: this.isOsDisk(volume) ? 'os_disk' : 'data_disk', description: volume.description || (volume.origin_data || {}).description, delete_interval: volume.metadata && volume.metadata.delete_interval @@ -214,6 +227,28 @@ export class VolumeStore extends Base { })); this.originalVolumeTypes = data || []; } + + @action + setCreateVolumeSize(size = 0) { + this.volumeSizeForCreate = size; + } + + @action + setCreateVolumeType(type = '') { + this.volumeTypeForCreate = type; + } + + @action + setCreateVolumeCount(count = 1) { + this.volumeCountForCreate = count; + } + + @action + setCreateVolumeInfo({ size = 0, type = '', count = 1 } = {}) { + this.setCreateVolumeSize(size); + this.setCreateVolumeType(type); + this.setCreateVolumeCount(count); + } } const globalVolumeStore = new VolumeStore();