From 41eaf509b693eec7ba9e7ac19b1d037e8120fb70 Mon Sep 17 00:00:00 2001 From: "Jingwei.Zhang" Date: Thu, 30 Jun 2022 11:06:42 +0800 Subject: [PATCH] feat: support quota info when extend volume 1. Fix attached instance display in volume list page 2. Support quota info when extend volume 3. Disable click submit button when left quota is not enough to extend 4. Refactor instance detail fetch by instance store, not the client 5. Refactor instance locked hint by using validator, not notice after submit 6. Update instanceName default value in modal action Change-Id: Ie5e492d1d550b8283b634fcd9fa645394d8ce504 --- src/containers/Action/ModalAction/index.jsx | 6 +- .../Volume/actions/ExtendVolume.jsx | 184 +++++++++++------- src/resources/cinder/volume.jsx | 22 +-- 3 files changed, 127 insertions(+), 85 deletions(-) diff --git a/src/containers/Action/ModalAction/index.jsx b/src/containers/Action/ModalAction/index.jsx index 5589fbba..fc94a647 100644 --- a/src/containers/Action/ModalAction/index.jsx +++ b/src/containers/Action/ModalAction/index.jsx @@ -91,7 +91,7 @@ export default class ModalAction extends BaseForm { } get instanceName() { - return (this.item || {}).name || (this.values || {}).name; + return (this.item || {}).name || (this.values || {}).name || this.itemId; } get isAsyncAction() { @@ -150,6 +150,10 @@ export default class ModalAction extends BaseForm { return item || this.containerProps.detail || { name: '' }; } + get itemId() { + return (this.item || {}).id; + } + get items() { const { items } = this.props; return items; diff --git a/src/pages/storage/containers/Volume/actions/ExtendVolume.jsx b/src/pages/storage/containers/Volume/actions/ExtendVolume.jsx index 81cfd267..56b485ca 100644 --- a/src/pages/storage/containers/Volume/actions/ExtendVolume.jsx +++ b/src/pages/storage/containers/Volume/actions/ExtendVolume.jsx @@ -15,10 +15,16 @@ import { inject, observer } from 'mobx-react'; import { ModalAction } from 'containers/Action'; import globalVolumeStore, { VolumeStore } from 'stores/cinder/volume'; -import { isAvailableOrInUse } from 'resources/cinder/volume'; -import { get } from 'lodash'; -import client from 'client'; -import Notify from 'components/Notify'; +import globalProjectStore from 'stores/keystone/project'; +import globalServerStore from 'stores/nova/instance'; +import { + isAvailableOrInUse, + setCreateVolumeSize, + checkQuotaDisable, + getQuotaInfo, + fetchQuota, +} from 'resources/cinder/volume'; +import { isEmpty } from 'lodash'; export class ExtendVolume extends ModalAction { static id = 'extend-snapshot'; @@ -29,37 +35,95 @@ export class ExtendVolume extends ModalAction { return t('Extend volume'); } - get defaultValue() { - const { name, id, volume_type, size } = this.item; - const value = { - volume: `${name || id}(${volume_type} | ${size}GiB)`, - new_size: size + 1, - }; - return value; - } - static policy = 'volume:extend'; static allowed = (item) => Promise.resolve(isAvailableOrInUse(item)); + init() { + this.store = globalVolumeStore; + this.state.showNotice = true; + this.volumeStore = new VolumeStore(); + this.projectStore = globalProjectStore; + fetchQuota(this, 1, this.item.volume_type); + this.checkAttachedServer(); + } + get tips() { return t('After the volume is expanded, the volume cannot be reduced.'); } - async getQuota() { - await this.volumeStore.fetchQuota(); - this.updateDefaultValue(); + static get disableSubmit() { + return checkQuotaDisable(false); + } + + static get showQuota() { + return true; + } + + get showQuota() { + return true; + } + + get quotaInfo() { + const { quota = {}, quotaLoading } = this.state; + if (quotaLoading || isEmpty(quota)) { + return []; + } + // eslint-disable-next-line no-unused-vars + const [volumeData, sizeData, typeData, typeSizeData] = getQuotaInfo( + this, + false + ); + const { type, ...rest } = sizeData; + return [rest, typeSizeData]; + } + + async checkAttachedServer() { + const instanceIds = (this.item.attachments || []).map((it) => it.server_id); + if (!instanceIds.length) { + return; + } + const reqs = instanceIds.map((id) => + globalServerStore.pureFetchDetail({ id }) + ); + const results = await Promise.allSettled(reqs); + const lockedInstances = results + .filter(({ status }) => { + return status === 'fulfilled'; + }) + .map((it) => it.value) + .filter((server) => server.locked) + .map(({ name }) => name); + if (lockedInstances.length) { + const name = lockedInstances.join(', '); + const lockedError = t( + 'The server {name} is locked. Please unlock first.', + { name } + ); + this.setState({ + lockedError, + }); + } } get isQuotaLimited() { - const { gigabytes: { limit } = {} } = this.volumeStore.quotaSet || {}; + const { gigabytes: { limit } = {} } = this.projectStore.cinderQuota || {}; return limit !== -1; } get leftSize() { - const { gigabytes: { limit = 10, in_use = 0 } = {} } = - this.volumeStore.quotaSet || {}; - return limit - in_use; + const { gigabytes: { left = 0 } = {} } = + this.projectStore.cinderQuota || {}; + return left; + } + + get itemSize() { + const { size } = this.item; + return size; + } + + get minSize() { + return this.itemSize + 1; } get maxSize() { @@ -67,21 +131,29 @@ export class ExtendVolume extends ModalAction { return currentSize + this.leftSize; } - isQuotaEnough() { - return !this.isQuotaLimited || this.leftSize >= 1; + get defaultValue() { + const { name, id, volume_type, size } = this.item; + const value = { + volume: `${name || id}(${volume_type} | ${size}GiB)`, + new_size: this.minSize, + }; + return value; } - get formItems() { - const { size } = this.item; - const minSize = size + 1; - if (!this.isQuotaEnough()) { - return [ - { - type: 'label', - component: t('Quota is not enough for extend volume.'), - }, - ]; + onSizeChange = (value) => { + const add = value - this.itemSize; + setCreateVolumeSize(add); + }; + + checkInstance = () => { + const { lockedError } = this.state; + if (!lockedError) { + return Promise.resolve(); } + return Promise.reject(lockedError); + }; + + get formItems() { return [ { name: 'volume', @@ -94,63 +166,29 @@ export class ExtendVolume extends ModalAction { label: t('Capacity (GiB)'), type: 'slider-input', max: this.maxSize, - min: minSize, - description: `${minSize}GiB-${this.maxSize}GiB`, + min: this.minSize, + description: `${this.minSize}GiB-${this.maxSize}GiB`, required: true, display: this.isQuotaLimited, + onChange: this.onSizeChange, + validator: this.checkInstance, }, { name: 'new_size', label: t('Capacity (GiB)'), type: 'input-int', - min: minSize, + min: this.minSize, required: true, display: !this.isQuotaLimited, + onChange: this.onSizeChange, + validator: this.checkInstance, }, ]; } - init() { - this.store = globalVolumeStore; - this.state.showNotice = true; - this.volumeStore = new VolumeStore(); - - this.getQuota(); - } - - get showNotice() { - return this.state.showNotice; - } - onSubmit = async (values) => { - if (!this.isQuotaEnough()) { - this.setState({ - showNotice: false, - }); - return Promise.resolve(); - } - const { new_size } = values; const { id } = this.item; - - const instanceId = get(this.item, 'attachments[0].server_id'); - if (instanceId) { - const { server } = await client.nova.servers.show(instanceId); - if (server.locked) { - Notify.errorWithDetail( - t('The server {name} is locked. Please unlock first.', { - name: server.name, - }), - t('The server {name} is locked. Please unlock first.', { - name: server.name, - }) - ); - this.setState({ - showNotice: false, - }); - return; - } - } return this.store.extendSize(id, { new_size }); }; } diff --git a/src/resources/cinder/volume.jsx b/src/resources/cinder/volume.jsx index 9ec29588..aa95758a 100644 --- a/src/resources/cinder/volume.jsx +++ b/src/resources/cinder/volume.jsx @@ -293,7 +293,7 @@ export const getVolumeColumnsList = (self) => { {it.device} on{' '} {self.getLinkRender( 'instanceDetail', - it.server_name, + it.server_name || it.server_id, { id: it.server_id }, { tab: 'volumes' } )} @@ -355,10 +355,10 @@ export function setCreateVolumeCount(value) { globalVolumeStore.setCreateVolumeCount(value); } -export async function fetchQuota(self, size) { +export async function fetchQuota(self, size, type = '') { setCreateVolumeCount(1); setCreateVolumeSize(size); - setCreateVolumeType(''); + setCreateVolumeType(type); self.setState({ quota: {}, quotaLoading: true, @@ -400,7 +400,7 @@ const getErrorMessage = ({ name, left, input }) => { return error; }; -export const getAdd = (cinderQuota) => { +export const getAdd = (cinderQuota, withCountCheck = true) => { if (isEmpty(cinderQuota)) { return {}; } @@ -424,7 +424,7 @@ export const getAdd = (cinderQuota) => { add: count, addSize: totalSize, }; - if (left >= 0 && left < count) { + if (withCountCheck && left >= 0 && left < count) { const error = getErrorMessage({ name: t('volume'), left, @@ -443,7 +443,7 @@ export const getAdd = (cinderQuota) => { if (isEmpty(typeQuota)) { return create; } - if (typeLeft >= 0 && typeLeft < count) { + if (withCountCheck && typeLeft >= 0 && typeLeft < count) { const error = getErrorMessage({ name: t('{name} type', { name: type }), left: typeLeft, @@ -462,7 +462,7 @@ export const getAdd = (cinderQuota) => { return create; }; -export const getQuotaInfo = (self) => { +export const getQuotaInfo = (self, withCountCheck = true) => { const { volumeTypeForCreate: name } = globalVolumeStore; const { quota = {}, quotaLoading } = self.state; if (quotaLoading || isEmpty(quota)) { @@ -474,7 +474,7 @@ export const getQuotaInfo = (self) => { typeQuota = {}, typeSizeQuota = {}, } = getQuota(quota); - const { add, addSize } = getAdd(quota); + const { add, addSize } = getAdd(quota, withCountCheck); const volumeData = { ...volumes, add, @@ -508,10 +508,10 @@ export const getQuotaInfo = (self) => { return [volumeData, sizeData, typeData, typeSizeData]; }; -export const checkQuotaDisable = () => { +export const checkQuotaDisable = (withCountCheck = true) => { const { cinderQuota = {} } = globalProjectStore; - const { add } = getAdd(cinderQuota); - return add === 0; + const { add, addSize } = getAdd(cinderQuota, withCountCheck); + return withCountCheck ? add === 0 : addSize === 0; }; export const onVolumeSizeChange = (value) => {