// Copyright 2021 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 React from 'react';
import { inject, observer } from 'mobx-react';
import { toJS } from 'mobx';
import { Row, Col } from 'antd';
import { volumeStatus, canCreateInstance } from 'resources/cinder/volume';
import globalServerStore from 'stores/nova/instance';
import globalImageStore from 'stores/glance/image';
import globalInstanceSnapshotStore from 'stores/glance/instance-snapshot';
import globalVolumeTypeStore from 'stores/cinder/volume-type';
import globalAvailabilityZoneStore from 'stores/nova/zone';
import { VolumeStore } from 'stores/cinder/volume';
import {
canImageCreateInstance,
getImageSystemTabs,
getImageOS,
getImageColumns,
imageFormats,
imageStatus,
} from 'resources/glance/image';
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 {
init() {
this.serverStore = globalServerStore;
this.imageStore = globalImageStore;
this.volumeStore = new VolumeStore();
this.volumeTypeStore = globalVolumeTypeStore;
this.instanceSnapshotStore = globalInstanceSnapshotStore;
this.getAvailZones();
this.getImages();
this.getVolumeTypes();
this.getVolumes();
this.getInstanceSnapshots();
this.initSourceChange();
}
get title() {
return 'BaseStep';
}
get name() {
return 'BaseStep';
}
get isStep() {
return true;
}
get defaultValue() {
const { volume, snapshot } = this.locationParams;
let source = this.imageSourceType;
if (volume) {
source = this.volumeSourceType;
} else if (snapshot) {
source = this.snapshotSourceType;
}
const values = {
systemDisk: this.defaultVolumeType,
source,
project: this.currentProjectName,
dataDisk: [],
};
if (source.value === 'image') {
values.bootFromVolume = true;
}
return values;
}
get availableZones() {
return (globalAvailabilityZoneStore.list.data || [])
.filter((it) => it.zoneState.available)
.map((it) => ({
value: it.zoneName,
label: it.zoneName,
}));
}
get images() {
const { imageTab } = this.state;
const { image } = this.locationParams;
const data = image
? [toJS(this.imageStore.detail)]
: this.imageStore.list.data || [];
const images = data.filter((it) => {
if (!canImageCreateInstance(it)) {
return false;
}
if (imageTab) {
return getImageOS(it) === imageTab;
}
return it;
});
return images.map((it) => ({
...it,
key: it.id,
}));
}
get snapshots() {
const { snapshot } = this.locationParams;
if (!snapshot) {
const {
list: { data },
} = this.instanceSnapshotStore;
return data || [];
}
return [toJS(this.instanceSnapshotStore.detail)];
}
get enableCinder() {
return this.props.rootStore.checkEndpoint('cinder');
}
get volumeTypes() {
return volumeTypes();
}
get volumes() {
const { volume } = this.locationParams;
if (volume) {
return [toJS(this.volumeStore.detail)].filter((it) =>
canCreateInstance(it)
);
}
return (this.volumeStore.list.data || [])
.filter((it) => canCreateInstance(it))
.map((it) => ({
...it,
key: it.id,
}));
}
get defaultVolumeType() {
const data = {
size: 10,
deleteType: 1,
};
return data;
}
get sourceTypes() {
const { image, snapshot, volume } = this.locationParams;
const types = [
{ label: t('Image'), value: 'image', disabled: volume || snapshot },
{
label: t('Instance Snapshot'),
value: 'instanceSnapshot',
disabled: image || volume,
},
];
if (this.enableCinder) {
types.push({
label: t('Bootable Volume'),
value: 'bootableVolume',
disabled: image || snapshot,
});
}
return types;
}
get imageSourceType() {
return this.sourceTypes.find((it) => it.value === 'image');
}
get snapshotSourceType() {
return this.sourceTypes.find((it) => it.value === 'instanceSnapshot');
}
get volumeSourceType() {
return this.enableCinder
? this.sourceTypes.find((it) => it.value === 'bootableVolume')
: {};
}
allowed = () => Promise.resolve();
async getAvailZones() {
await globalAvailabilityZoneStore.fetchListWithoutDetail();
if (this.availableZones.length) {
this.updateFormValue('availableZone', this.availableZones[0]);
}
}
async getImages() {
const { volume, image, snapshot } = this.locationParams;
if (volume || snapshot) {
return;
}
if (image) {
await this.imageStore.fetchDetail({ id: image });
} else {
await this.imageStore.fetchList({ all_projects: this.hasAdminRole });
}
}
async getVolumeTypes() {
if (this.enableCinder) {
await this.volumeTypeStore.fetchList();
}
}
async getVolumes() {
const { image, snapshot, volume } = this.locationParams;
if (image || snapshot) {
return;
}
if (!this.enableCinder) {
return;
}
if (volume) {
await this.volumeStore.fetchDetail({
id: volume,
});
this.updateContext({
source: this.volumeSourceType,
});
} else {
await this.volumeStore.fetchList({
sortKey: 'bootable',
sortOrder: 'ascend',
});
}
}
async getInstanceSnapshots() {
const { image, snapshot, volume } = this.locationParams;
if (image || volume) {
return;
}
if (!snapshot) {
this.instanceSnapshotStore.fetchList();
return;
}
await this.instanceSnapshotStore.fetchDetail({ id: snapshot });
}
onImageTabChange = (value) => {
this.setState({
imageTab: value,
});
};
get systemTabs() {
return getImageSystemTabs();
}
checkSystemDisk = (rule, value) => {
const { size = 10, type } = value || {};
const minSize = this.getSystemDiskMinSize();
if (!type) {
// eslint-disable-next-line prefer-promise-reject-errors
return Promise.reject('');
}
if (!size) {
return Promise.reject(new Error(t('Please set the system disk size!')));
}
if (size < minSize) {
return Promise.reject(
new Error(
t('Please set a size no less than {minSize} GiB!', { minSize })
)
);
}
return Promise.resolve();
};
get nameForStateUpdate() {
return [
'source',
'image',
'instanceSnapshot',
'bootableVolume',
'flavor',
'bootFromVolume',
];
}
getSystemDiskMinSize() {
const flavorSize = (this.state.flavor || {}).disk || 0;
let imageSize = 0;
if (this.sourceTypeIsImage) {
const { min_disk = 0, size = 0 } = this.state.image || {};
const sizeGiB = Math.ceil(size / 1024 / 1024 / 1024);
imageSize = Math.max(min_disk, sizeGiB, 1);
return Math.max(flavorSize, imageSize, 1);
}
if (this.sourceTypeIsSnapshot) {
const { instanceSnapshotMinSize = 0 } = this.state;
return Math.max(flavorSize, instanceSnapshotMinSize, 1);
}
return Math.max(flavorSize, 1);
}
get sourceTypeIsImage() {
const { source } = this.state;
return source === this.imageSourceType.value;
}
get sourceTypeIsSnapshot() {
const { source } = this.state;
return source === this.snapshotSourceType.value;
}
get sourceTypeIsVolume() {
const { source } = this.state;
return source === this.volumeSourceType.value;
}
getImageExtraWords() {
const { flavor: { category } = {} } = this.state;
if (isGpuCategory(category)) {
return t(
'For GPU type, you need to install GPU drivers in the instance operating system.'
);
}
return '';
}
initSourceChange() {
const { snapshot, volume } = this.locationParams;
if (snapshot) {
this.onSourceChange(this.snapshotSourceType);
} else if (volume) {
this.onSourceChange(this.volumeSourceType);
} else {
this.onSourceChange(this.imageSourceType);
}
}
onFlavorChange = (value) => {
this.updateContext({
flavor: value,
});
};
onChangeBootFromVolume = (value) => {
const newData = {
bootFromVolume: value,
};
if (!value) {
newData.dataDisk = [];
this.updateFormValue('dataDisk', []);
}
this.updateContext(newData);
};
onInstanceSnapshotChange = async (value) => {
const { min_disk, size, id } = value.selectedRows[0] || {};
if (!id) {
this.updateContext({
instanceSnapshotDisk: null,
instanceSnapshotDataVolumes: [],
});
this.setState({
instanceSnapshotDisk: null,
instanceSnapshotMinSize: 0,
instanceSnapshotDataVolumes: [],
});
return;
}
const detail =
await this.instanceSnapshotStore.fetchInstanceSnapshotVolumeData({ id });
const {
snapshotDetail: { size: snapshotSize = 0 } = {},
block_device_mapping = '',
volumeDetail,
snapshotDetail,
instanceSnapshotDataVolumes = [],
} = detail;
if (!volumeDetail) {
this.updateFormValue('bootFromVolume', true);
this.updateContext({
instanceSnapshotDisk: null,
instanceSnapshotDataVolumes: [],
bootFromVolume: true,
});
this.setState({
instanceSnapshotDisk: null,
instanceSnapshotMinSize: 0,
instanceSnapshotDataVolumes: [],
bootFromVolume: true,
});
}
const minSize = Math.max(min_disk, size, snapshotSize);
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,
});
};
onBootableVolumeChange = (value) => {
this.updateContext({
bootableVolume: value,
});
};
onSystemDiskChange = (value) => {
this.updateContext({
systemDisk: value,
});
};
onSourceChange(value) {
this.updateContext({
source: value,
});
}
onDataDiskChange = (value) => {
this.updateContext({
dataDisk: value,
});
};
getInstanceSnapshotDisk = () => {
const { instanceSnapshotDisk } = this.state;
const { instanceSnapshotDisk: oldDisk } = this.props.context;
return instanceSnapshotDisk || oldDisk;
};
getSnapshotDataDisks = () => {
const { instanceSnapshotDataVolumes } = this.state;
const { instanceSnapshotDataVolumes: oldSnapshotDataVolumes } =
this.props.context;
return instanceSnapshotDataVolumes || oldSnapshotDataVolumes || [];
};
renderInstanceSnapshotDisk = (disk) => {
if (disk === null) {
return null;
}
const { deleteTypeLabel, typeOption = {}, size } = disk || {};
if (!size) {
return null;
}
const style = {
marginRight: 10,
maxWidth: '20%',
};
return (