Merge "feat: support quota info when instance create snapshot"
This commit is contained in:
commit
d3ffb6264d
@ -2236,6 +2236,7 @@
|
|||||||
"The instance deleted immediately cannot be restored": "The instance deleted immediately cannot be restored",
|
"The instance deleted immediately cannot be restored": "The instance deleted immediately cannot be restored",
|
||||||
"The instance has been locked. If you want to do more, please unlock it first.": "The instance has been locked. If you want to do more, please unlock it first.",
|
"The instance has been locked. If you want to do more, please unlock it first.": "The instance has been locked. If you want to do more, please unlock it first.",
|
||||||
"The instance is not shut down, unable to restore.": "The instance is not shut down, unable to restore.",
|
"The instance is not shut down, unable to restore.": "The instance is not shut down, unable to restore.",
|
||||||
|
"The instance which is boot from volume will create snapshots for each mounted volumes": "The instance which is boot from volume will create snapshots for each mounted volumes",
|
||||||
"The instances in the affinity group are allocated to the same physical machine as much as possible, and when there are no more physical machines to allocate, the normal allocation strategy is returned.": "The instances in the affinity group are allocated to the same physical machine as much as possible, and when there are no more physical machines to allocate, the normal allocation strategy is returned.",
|
"The instances in the affinity group are allocated to the same physical machine as much as possible, and when there are no more physical machines to allocate, the normal allocation strategy is returned.": "The instances in the affinity group are allocated to the same physical machine as much as possible, and when there are no more physical machines to allocate, the normal allocation strategy is returned.",
|
||||||
"The instances in the affinity group are strictly allocated to the same physical machine. When there are no more physical machines to allocate, the allocation fails.": "The instances in the affinity group are strictly allocated to the same physical machine. When there are no more physical machines to allocate, the allocation fails.",
|
"The instances in the affinity group are strictly allocated to the same physical machine. When there are no more physical machines to allocate, the allocation fails.": "The instances in the affinity group are strictly allocated to the same physical machine. When there are no more physical machines to allocate, the allocation fails.",
|
||||||
"The instances in the anti-affinity group are allocated to different physical machines as much as possible. When there are no more physical machines to allocate, the normal allocation strategy is returned.": "The instances in the anti-affinity group are allocated to different physical machines as much as possible. When there are no more physical machines to allocate, the normal allocation strategy is returned.",
|
"The instances in the anti-affinity group are allocated to different physical machines as much as possible. When there are no more physical machines to allocate, the normal allocation strategy is returned.": "The instances in the anti-affinity group are allocated to different physical machines as much as possible. When there are no more physical machines to allocate, the normal allocation strategy is returned.",
|
||||||
|
@ -2236,6 +2236,7 @@
|
|||||||
"The instance deleted immediately cannot be restored": "立即删除的云主机无法恢复",
|
"The instance deleted immediately cannot be restored": "立即删除的云主机无法恢复",
|
||||||
"The instance has been locked. If you want to do more, please unlock it first.": "该云主机已被锁定。如果要做更多操作,请先解锁。",
|
"The instance has been locked. If you want to do more, please unlock it first.": "该云主机已被锁定。如果要做更多操作,请先解锁。",
|
||||||
"The instance is not shut down, unable to restore.": "云主机不处于关机状态,不支持恢复备份操作。",
|
"The instance is not shut down, unable to restore.": "云主机不处于关机状态,不支持恢复备份操作。",
|
||||||
|
"The instance which is boot from volume will create snapshots for each mounted volumes.": "从卷启动的云主机将为每个挂载的卷创建快照。",
|
||||||
"The instances in the affinity group are allocated to the same physical machine as much as possible, and when there are no more physical machines to allocate, the normal allocation strategy is returned.": "将亲和组内的云主机尽量分配到不同物理机上,当没有更多物理机可分配时,回归普通分配策略。",
|
"The instances in the affinity group are allocated to the same physical machine as much as possible, and when there are no more physical machines to allocate, the normal allocation strategy is returned.": "将亲和组内的云主机尽量分配到不同物理机上,当没有更多物理机可分配时,回归普通分配策略。",
|
||||||
"The instances in the affinity group are strictly allocated to the same physical machine. When there are no more physical machines to allocate, the allocation fails.": "将亲和组内的云主机严格分配到同一物理机上,当没有更多物理机可分配时,则分配失败。",
|
"The instances in the affinity group are strictly allocated to the same physical machine. When there are no more physical machines to allocate, the allocation fails.": "将亲和组内的云主机严格分配到同一物理机上,当没有更多物理机可分配时,则分配失败。",
|
||||||
"The instances in the anti-affinity group are allocated to different physical machines as much as possible. When there are no more physical machines to allocate, the normal allocation strategy is returned.": "将反亲和组内的云主机尽量分配到不同物理机上,当没有更多物理机可分配时,回归普通分配策略。",
|
"The instances in the anti-affinity group are allocated to different physical machines as much as possible. When there are no more physical machines to allocate, the normal allocation strategy is returned.": "将反亲和组内的云主机尽量分配到不同物理机上,当没有更多物理机可分配时,回归普通分配策略。",
|
||||||
|
@ -12,12 +12,99 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
import { inject, observer } from 'mobx-react';
|
import { inject, observer } from 'mobx-react';
|
||||||
|
import { Table } from 'antd';
|
||||||
import globalServerStore from 'stores/nova/instance';
|
import globalServerStore from 'stores/nova/instance';
|
||||||
import { ModalAction } from 'containers/Action';
|
import { ModalAction } from 'containers/Action';
|
||||||
import { checkStatus, isIronicInstance } from 'resources/nova/instance';
|
import {
|
||||||
import globalInstanceVolumeStore from 'stores/nova/instance-volume';
|
checkStatus,
|
||||||
|
isIronicInstance,
|
||||||
|
isBootFromVolume,
|
||||||
|
} from 'resources/nova/instance';
|
||||||
|
import { InstanceVolumeStore } from 'stores/nova/instance-volume';
|
||||||
import globalVolumeTypeStore from 'stores/cinder/volume-type';
|
import globalVolumeTypeStore from 'stores/cinder/volume-type';
|
||||||
|
import globalProjectStore from 'stores/keystone/project';
|
||||||
|
|
||||||
|
export const getWishes = () => {
|
||||||
|
const { volumesForSnapshot = [] } = globalServerStore;
|
||||||
|
if (!volumesForSnapshot.length) {
|
||||||
|
return {
|
||||||
|
total: 0,
|
||||||
|
types: {},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
const types = volumesForSnapshot.reduce((pre, cur) => {
|
||||||
|
const { volume_type: type } = cur;
|
||||||
|
if (pre[type]) {
|
||||||
|
pre[type] += 1;
|
||||||
|
} else {
|
||||||
|
pre[type] = 1;
|
||||||
|
}
|
||||||
|
return pre;
|
||||||
|
}, {});
|
||||||
|
return { types, total: volumesForSnapshot.length };
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getQuota = (cinderQuota) => {
|
||||||
|
const { snapshots: snapshotQuota = {} } = cinderQuota || {};
|
||||||
|
const { types = {} } = getWishes();
|
||||||
|
const typesQuota = Object.keys(types || {}).reduce((pre, cur) => {
|
||||||
|
pre[cur] = (cinderQuota || {})[`snapshots_${cur}`] || {};
|
||||||
|
return pre;
|
||||||
|
}, {});
|
||||||
|
return {
|
||||||
|
snapshotQuota,
|
||||||
|
...typesQuota,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getZero = (cinderQuota) => {
|
||||||
|
const { types = {} } = getWishes();
|
||||||
|
const allQuota = getQuota(cinderQuota) || {};
|
||||||
|
const { snapshotQuota = {} } = allQuota;
|
||||||
|
const zero = [
|
||||||
|
{ ...snapshotQuota, add: 0, name: 'snapshot', title: t('Snapshot') },
|
||||||
|
];
|
||||||
|
Object.keys(types).forEach((type) => {
|
||||||
|
const typeQuota = allQuota[type] || {};
|
||||||
|
zero.push({
|
||||||
|
...typeQuota,
|
||||||
|
add: 0,
|
||||||
|
name: type,
|
||||||
|
title: t('{name} type snapshots', { name: type }),
|
||||||
|
type: 'line',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return zero;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getAdd = (cinderQuota) => {
|
||||||
|
const zero = getZero(cinderQuota);
|
||||||
|
const { types = {}, total = 0 } = getWishes();
|
||||||
|
const allQuota = getQuota(cinderQuota) || {};
|
||||||
|
const { snapshotQuota } = allQuota;
|
||||||
|
const { left = 0 } = snapshotQuota || {};
|
||||||
|
if (left !== -1 && left < total) {
|
||||||
|
return zero;
|
||||||
|
}
|
||||||
|
const needQuota = JSON.parse(JSON.stringify(zero));
|
||||||
|
needQuota[0].add = total;
|
||||||
|
let typeQuotaIsOK = true;
|
||||||
|
Object.keys(types).forEach((type, index) => {
|
||||||
|
if (typeQuotaIsOK) {
|
||||||
|
const typeQuota = allQuota[type];
|
||||||
|
const { left: typeLeft = 0 } = typeQuota || {};
|
||||||
|
const typeTotal = types[type];
|
||||||
|
if (typeLeft !== -1 && typeLeft < typeTotal) {
|
||||||
|
typeQuotaIsOK = false;
|
||||||
|
} else {
|
||||||
|
needQuota[index + 1].add = typeTotal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return typeQuotaIsOK ? needQuota : zero;
|
||||||
|
};
|
||||||
|
|
||||||
export class CreateSnapshot extends ModalAction {
|
export class CreateSnapshot extends ModalAction {
|
||||||
static id = 'create-snapshot';
|
static id = 'create-snapshot';
|
||||||
@ -26,8 +113,9 @@ export class CreateSnapshot extends ModalAction {
|
|||||||
|
|
||||||
init() {
|
init() {
|
||||||
this.store = globalServerStore;
|
this.store = globalServerStore;
|
||||||
this.volumeStore = globalInstanceVolumeStore;
|
this.volumeStore = new InstanceVolumeStore();
|
||||||
this.volumeTypeStore = globalVolumeTypeStore;
|
this.volumeTypeStore = globalVolumeTypeStore;
|
||||||
|
this.getQuota();
|
||||||
}
|
}
|
||||||
|
|
||||||
get name() {
|
get name() {
|
||||||
@ -35,15 +123,117 @@ export class CreateSnapshot extends ModalAction {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get tips() {
|
get tips() {
|
||||||
return t(
|
const volumeTip = t(
|
||||||
'A snapshot is an image which preserves the disk state of a running instance, which can be used to start a new instance.'
|
'The instance which is boot from volume will create snapshots for each mounted volumes.'
|
||||||
);
|
);
|
||||||
|
return (
|
||||||
|
t(
|
||||||
|
'A snapshot is an image which preserves the disk state of a running instance, which can be used to start a new instance.'
|
||||||
|
) + volumeTip
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static get modalSize() {
|
||||||
|
return 'middle';
|
||||||
|
}
|
||||||
|
|
||||||
|
getModalSize() {
|
||||||
|
return 'middle';
|
||||||
}
|
}
|
||||||
|
|
||||||
get instanceName() {
|
get instanceName() {
|
||||||
return this.values.snapshot;
|
return this.values.snapshot;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get isBootFromVolume() {
|
||||||
|
return isBootFromVolume(this.item);
|
||||||
|
}
|
||||||
|
|
||||||
|
get showQuota() {
|
||||||
|
return this.isBootFromVolume;
|
||||||
|
}
|
||||||
|
|
||||||
|
get quotaInfo() {
|
||||||
|
const { quota, quotaLoading } = this.state;
|
||||||
|
if (quotaLoading) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
return getAdd(quota);
|
||||||
|
}
|
||||||
|
|
||||||
|
static get disableSubmit() {
|
||||||
|
const { volumesForSnapshot = [] } = globalServerStore;
|
||||||
|
if (!volumesForSnapshot.length) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const { cinderQuota } = globalProjectStore;
|
||||||
|
const quotaInfo = getAdd(cinderQuota);
|
||||||
|
if (quotaInfo[0].add === 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getQuota() {
|
||||||
|
this.store.setVolumesForSnapshot([]);
|
||||||
|
this.setState({
|
||||||
|
quota: {},
|
||||||
|
quotaLoading: true,
|
||||||
|
});
|
||||||
|
const reqs = [
|
||||||
|
globalProjectStore.fetchProjectCinderQuota(),
|
||||||
|
this.isBootFromVolume
|
||||||
|
? this.volumeStore.fetchList({ serverId: this.item.id })
|
||||||
|
: null,
|
||||||
|
];
|
||||||
|
const [quota, volumes] = await Promise.all(reqs);
|
||||||
|
this.store.setVolumesForSnapshot(volumes || []);
|
||||||
|
this.setState({
|
||||||
|
quota,
|
||||||
|
quotaLoading: false,
|
||||||
|
volumes: volumes || [],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getVolumes() {
|
||||||
|
if (!this.isBootFromVolume) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const { volumes = [] } = this.state;
|
||||||
|
const columns = [
|
||||||
|
{
|
||||||
|
dataIndex: 'id',
|
||||||
|
title: t('ID/Name'),
|
||||||
|
render: (value, record) => {
|
||||||
|
const { name } = record;
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div>{value}</div>
|
||||||
|
<div>{name || '-'}</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
dataIndex: 'size',
|
||||||
|
title: t('Size'),
|
||||||
|
render: (value) => `${value}GiB`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
dataIndex: 'volume_type',
|
||||||
|
title: t('Volume Type'),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
return (
|
||||||
|
<Table
|
||||||
|
columns={columns}
|
||||||
|
dataSource={volumes}
|
||||||
|
rowKey="id"
|
||||||
|
pagination={false}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
get defaultValue() {
|
get defaultValue() {
|
||||||
const { name } = this.item;
|
const { name } = this.item;
|
||||||
const value = {
|
const value = {
|
||||||
@ -62,7 +252,7 @@ export class CreateSnapshot extends ModalAction {
|
|||||||
Promise.resolve(this.isSnapshotReadyState(item) && !isIronicInstance(item));
|
Promise.resolve(this.isSnapshotReadyState(item) && !isIronicInstance(item));
|
||||||
|
|
||||||
get formItems() {
|
get formItems() {
|
||||||
return [
|
const items = [
|
||||||
{
|
{
|
||||||
name: 'instance',
|
name: 'instance',
|
||||||
label: t('Instance'),
|
label: t('Instance'),
|
||||||
@ -77,6 +267,15 @@ export class CreateSnapshot extends ModalAction {
|
|||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
if (this.isBootFromVolume) {
|
||||||
|
items.push({
|
||||||
|
name: 'volumes',
|
||||||
|
label: t('Volumes'),
|
||||||
|
type: 'label',
|
||||||
|
content: this.getVolumes(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return items;
|
||||||
}
|
}
|
||||||
|
|
||||||
onSubmit = (values) => {
|
onSubmit = (values) => {
|
||||||
|
@ -22,6 +22,7 @@ import { projectTagsColors } from 'src/utils/constants';
|
|||||||
|
|
||||||
import lockSvg from 'asset/image/lock.svg';
|
import lockSvg from 'asset/image/lock.svg';
|
||||||
import unlockSvg from 'asset/image/unlock.svg';
|
import unlockSvg from 'asset/image/unlock.svg';
|
||||||
|
import { isEmpty } from 'lodash';
|
||||||
|
|
||||||
const lockIcon = (
|
const lockIcon = (
|
||||||
<Tooltip
|
<Tooltip
|
||||||
@ -636,3 +637,11 @@ export const SimpleTag = ({ tag, index }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const allowAttachInterfaceStatus = ['active', 'paused', 'stopped'];
|
export const allowAttachInterfaceStatus = ['active', 'paused', 'stopped'];
|
||||||
|
|
||||||
|
export const isBootFromVolume = (item) => {
|
||||||
|
const { origin_data: originData } = item || {};
|
||||||
|
if (originData && !isEmpty(originData)) {
|
||||||
|
return !originData.image;
|
||||||
|
}
|
||||||
|
return !item.image;
|
||||||
|
};
|
||||||
|
@ -32,6 +32,9 @@ export class ServerStore extends Base {
|
|||||||
@observable
|
@observable
|
||||||
serverSnapshots = [];
|
serverSnapshots = [];
|
||||||
|
|
||||||
|
@observable
|
||||||
|
volumesForSnapshot = [];
|
||||||
|
|
||||||
get client() {
|
get client() {
|
||||||
return client.nova.servers;
|
return client.nova.servers;
|
||||||
}
|
}
|
||||||
@ -470,6 +473,11 @@ export class ServerStore extends Base {
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
setVolumesForSnapshot(volumes) {
|
||||||
|
this.volumesForSnapshot = volumes;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const globalServerStore = new ServerStore();
|
const globalServerStore = new ServerStore();
|
||||||
|
Loading…
Reference in New Issue
Block a user