diff --git a/src/locales/en.json b/src/locales/en.json index 56d5953a..0e2e2833 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -1940,6 +1940,7 @@ "Request ID": "Request ID", "Require": "Require", "Require(Need multithreading)": "Require(Need multithreading)", + "Required Data Disk": "Required Data Disk", "Rescue": "Rescue", "Rescued": "Rescued", "Rescuing": "Rescuing", diff --git a/src/locales/zh.json b/src/locales/zh.json index 2e47d273..cfbfabec 100644 --- a/src/locales/zh.json +++ b/src/locales/zh.json @@ -1940,6 +1940,7 @@ "Request ID": "请求ID", "Require": "强制", "Require(Need multithreading)": "Require(必须有多线程)", + "Required Data Disk": "所需数据盘", "Rescue": "救援", "Rescued": "已救援", "Rescuing": "救援中", diff --git a/src/pages/compute/containers/Instance/actions/StepCreate/BaseStep/index.jsx b/src/pages/compute/containers/Instance/actions/StepCreate/BaseStep/index.jsx index e6e396c5..cef6794b 100644 --- a/src/pages/compute/containers/Instance/actions/StepCreate/BaseStep/index.jsx +++ b/src/pages/compute/containers/Instance/actions/StepCreate/BaseStep/index.jsx @@ -34,6 +34,11 @@ import { import Base from 'components/Form'; import InstanceVolume from 'components/FormItem/InstanceVolume'; import { isGpuCategory } from 'resources/nova/flavor'; +import { + volumeTypes, + getDiskInfo, + getInstanceSnapshotDataDisk, +} from 'resources/cinder/snapshot'; import FlavorSelectTable from '../../../components/FlavorSelectTable'; export class BaseStep extends Base { @@ -126,11 +131,7 @@ export class BaseStep extends Base { } get volumeTypes() { - return (this.volumeTypeStore.list.data || []).map((it) => ({ - label: it.name, - value: it.id, - originData: toJS(it), - })); + return volumeTypes(); } get volumes() { @@ -337,62 +338,53 @@ export class BaseStep extends Base { if (!id) { this.updateContext({ instanceSnapshotDisk: null, + instanceSnapshotDataVolumes: [], }); this.setState({ instanceSnapshotDisk: null, instanceSnapshotMinSize: 0, + instanceSnapshotDataVolumes: [], }); return; } - const detail = await this.instanceSnapshotStore.fetchDetail({ id }); + const detail = + await this.instanceSnapshotStore.fetchInstanceSnapshotVolumeData({ id }); const { - snapshotDetail: { size: snapshotSize = 0, volume_type_id } = {}, + snapshotDetail: { size: snapshotSize = 0 } = {}, block_device_mapping = '', volumeDetail, + snapshotDetail, + instanceSnapshotDataVolumes = [], } = detail; if (!volumeDetail) { this.updateContext({ instanceSnapshotDisk: null, + instanceSnapshotDataVolumes: [], }); this.setState({ instanceSnapshotDisk: null, instanceSnapshotMinSize: 0, + instanceSnapshotDataVolumes: [], }); } const minSize = Math.max(min_disk, size, snapshotSize); - let bdm = {}; - try { - bdm = JSON.parse(block_device_mapping); - } catch (e) {} - const { volume_type } = volumeDetail; - const { delete_on_termination } = bdm[0] || {}; - const deleteType = delete_on_termination ? 1 : 0; - const deleteTypeLabel = delete_on_termination - ? t('Deleted with the instance') - : t('Not deleted with the instance'); - const volumeTypeId = - volume_type_id || - (this.volumeTypes.find((it) => it.label === volume_type) || {}).value; - const volumeTypeItem = this.volumeTypes.find( - (it) => it.value === volumeTypeId - ); - const instanceSnapshotDisk = volumeDetail - ? { - type: volumeTypeId, - typeOption: volumeTypeItem, - size: snapshotSize, - deleteType, - deleteTypeLabel, - } - : null; + const bdmFormatData = JSON.parse(block_device_mapping) || []; + const systemDiskBdm = bdmFormatData[0] || {}; + const instanceSnapshotDisk = getDiskInfo({ + volumeDetail, + snapshotDetail, + selfBdmData: systemDiskBdm, + }); this.updateFormValue('instanceSnapshotDisk', instanceSnapshotDisk); this.updateContext({ instanceSnapshotDisk, + instanceSnapshotDataVolumes, }); this.setState({ instanceSnapshotDisk, instanceSnapshotMinSize: minSize, + instanceSnapshotDataVolumes, }); }; @@ -426,8 +418,14 @@ export class BaseStep extends Base { return instanceSnapshotDisk || oldDisk; }; - renderSnapshotDisk = () => { - const disk = this.getInstanceSnapshotDisk(); + getSnapshotDataDisks = () => { + const { instanceSnapshotDataVolumes } = this.state; + const { instanceSnapshotDataVolumes: oldSnapshotDataVolumes } = + this.props.context; + return instanceSnapshotDataVolumes || oldSnapshotDataVolumes || []; + }; + + renderInstanceSnapshotDisk = (disk) => { if (disk === null) { return null; } @@ -455,6 +453,28 @@ export class BaseStep extends Base { ); }; + renderSnapshotDisk = () => { + const disk = this.getInstanceSnapshotDisk(); + return this.renderInstanceSnapshotDisk(disk); + }; + + renderSnapshotDataDisk = () => { + const dataDisks = this.getSnapshotDataDisks(); + return ( + <> + {dataDisks?.map((i) => { + const disk = getInstanceSnapshotDataDisk(i); + const id = i?.id || i?.snapshot_id; + return ( +
+ {this.renderInstanceSnapshotDisk(disk)} +
+ ); + })} + + ); + }; + get imageColumns() { return getImageColumns(this); } @@ -533,6 +553,15 @@ export class BaseStep extends Base { ); } + get hideInstanceSnapshotSystemDisk() { + return this.showSystemDisk || this.sourceTypeIsVolume; + } + + get hideInstanceSnapshotDataDisk() { + if (this.hideInstanceSnapshotSystemDisk) return true; + return this.getSnapshotDataDisks().length === 0; + } + getFlavorComponent() { return ; } @@ -669,9 +698,15 @@ export class BaseStep extends Base { { name: 'instanceSnapshotDisk', label: t('System Disk'), - hidden: this.showSystemDisk, + hidden: this.hideInstanceSnapshotSystemDisk, component: this.renderSnapshotDisk(), }, + { + name: 'instanceSnapshotDataDisk', + label: t('Required Data Disk'), + hidden: this.hideInstanceSnapshotDataDisk, + component: this.renderSnapshotDataDisk(), + }, { name: 'dataDisk', label: t('Data Disk'), diff --git a/src/resources/cinder/snapshot.jsx b/src/resources/cinder/snapshot.jsx new file mode 100644 index 00000000..c0e3d4fe --- /dev/null +++ b/src/resources/cinder/snapshot.jsx @@ -0,0 +1,60 @@ +// Copyright 2022 99cloud +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import globalVolumeTypeStore from 'stores/cinder/volume-type'; +import { toJS } from 'mobx'; + +export const volumeTypes = () => { + return (globalVolumeTypeStore.list.data || []).map((it) => ({ + label: it.name, + value: it.id, + originData: toJS(it), + })); +}; + +export const getDiskInfo = (detail) => { + const { + snapshotDetail: { size = 0 } = {}, + volumeDetail: { volume_type } = {}, + selfBdmData = {}, + } = detail || {}; + const { delete_on_termination } = selfBdmData; + const deleteType = delete_on_termination ? 1 : 0; + const deleteTypeLabel = delete_on_termination + ? t('Deleted with the instance') + : t('Not deleted with the instance'); + const volumeTypeItem = volumeTypes().find((it) => it.label === volume_type); + const diskInfo = { + type: volumeTypeItem?.value, + typeOption: volumeTypeItem, + size, + deleteType, + deleteTypeLabel, + }; + return diskInfo; +}; + +export const getInstanceSnapshotDataDisk = (disk) => { + const { + volumeDetail, + snapshotDetail, + bdmFormatData: dataDiskBdm = {}, + } = disk || {}; + const instanceSnapshotDataDisk = getDiskInfo({ + volumeDetail, + snapshotDetail, + selfBdmData: dataDiskBdm, + }); + return instanceSnapshotDataDisk; +}; diff --git a/src/stores/glance/instance-snapshot.js b/src/stores/glance/instance-snapshot.js index 75f08d87..77d6b25f 100644 --- a/src/stores/glance/instance-snapshot.js +++ b/src/stores/glance/instance-snapshot.js @@ -14,6 +14,7 @@ import client from 'client'; import { isSnapshot } from 'src/resources/glance/image'; +import { cloneDeep } from 'lodash'; import Base from '../base'; export class InstanceSnapshotStore extends Base { @@ -152,6 +153,43 @@ export class InstanceSnapshotStore extends Base { item.instanceDetail = instanceResult.server || {}; return item; } + + async fetchInstanceSnapshotVolumeData({ id }) { + const snapshotDetailInfo = await this.client.show(id); + const instanceSnapshotDetail = await this.detailDidFetch( + snapshotDetailInfo + ); + const { block_device_mapping: bdm = '[]' } = instanceSnapshotDetail; + const bdmFormatData = JSON.parse(bdm) || []; + if (!bdmFormatData?.length) { + return instanceSnapshotDetail; + } + const snapshotsOfDataDisk = bdmFormatData?.filter( + (it) => it.boot_index !== 0 + ); + const snapshotsReqs = snapshotsOfDataDisk.map(async (i) => { + const snapshot = cloneDeep(i); + const { snapshot_id } = i; + const snapshotResult = await client.cinder.snapshots.show(snapshot_id); + const snapshotDetail = snapshotResult?.snapshot || {}; + snapshot.snapshotDetail = snapshotDetail; + snapshot.bdmFormatData = i; + return snapshot; + }); + const snapshotsOfDataDiskRes = await Promise.all(snapshotsReqs); + const volumesReqs = snapshotsOfDataDiskRes.map(async (i) => { + const { volume_id } = i.snapshotDetail; + const volumesResult = await client.cinder.volumes.show(volume_id); + const volumeDetail = volumesResult?.volume || {}; + i.volumeDetail = volumeDetail; + return i; + }); + const instanceSnapshotDataVolumes = await Promise.all(volumesReqs); + return { + ...instanceSnapshotDetail, + instanceSnapshotDataVolumes, + }; + } } const globalInstanceSnapshotStore = new InstanceSnapshotStore();