SystemDisk.vue 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483
  1. <template>
  2. <div class="system-disk">
  3. <disk
  4. ref="disk"
  5. diskKey="system"
  6. :max="max"
  7. :min="min"
  8. :form="form"
  9. :decorator="decorator"
  10. :hypervisor="hypervisor"
  11. :types-map="typesMap"
  12. :elements="elements"
  13. :disabled="disabled"
  14. :image-type="imageType"
  15. :storageParams="storageParams"
  16. :storageHostParams="storageHostParams"
  17. :schedtagParams="getSchedtagParams()"
  18. :size-disabled="sizeDisabled || disabled"
  19. :storage-status-map="storageStatusMap"
  20. :isStorageShow="isStorageShow"
  21. :isIopsShow="isIopsShow"
  22. :isThroughputShow="isThroughputShow"
  23. :iopsLimit="iopsLimit"
  24. :isAutoResetShow="isAutoResetShow"
  25. :defaultIops="defaultIops"
  26. :defaultThroughput="defaultThroughput"
  27. @showStorageChange="showStorageChange"
  28. @diskTypeChange="setDiskMedium"
  29. @storageHostChange="(val) => $emit('storageHostChange', val)" />
  30. </div>
  31. </template>
  32. <script>
  33. import _ from 'lodash'
  34. import * as R from 'ramda'
  35. import Disk from '@Compute/sections/Disk'
  36. import { MEDIUM_MAP } from '@Compute/constants'
  37. import { IMAGES_TYPE_MAP, STORAGE_TYPES, DISK_LABEL_MAP } from '@/constants/compute'
  38. import { HYPERVISORS_MAP } from '@/constants'
  39. import { findAndUnshift, findAndPush } from '@/utils/utils'
  40. import { diskSupportTypeMedium, getOriginDiskKey } from '@/utils/common/hypervisor'
  41. // 磁盘最小值
  42. export const DISK_MIN_SIZE = 10
  43. // let isFirstSetDefaultSize = true
  44. export default {
  45. name: 'SystemDisk',
  46. components: {
  47. Disk,
  48. },
  49. props: {
  50. form: {
  51. type: Object,
  52. required: true,
  53. validator: val => val.fd && val.fc,
  54. },
  55. type: {
  56. type: String,
  57. required: true,
  58. validator: val => ['idc', 'private', 'public'].includes(val),
  59. },
  60. hypervisor: {
  61. },
  62. sku: {
  63. type: Object,
  64. },
  65. capabilityData: {
  66. type: Object,
  67. required: true,
  68. },
  69. image: {
  70. type: Object,
  71. },
  72. imageType: {
  73. type: String,
  74. },
  75. decorator: {
  76. type: Object,
  77. required: true,
  78. },
  79. disabled: {
  80. type: Boolean,
  81. default: false,
  82. },
  83. isHostImageType: {
  84. type: Boolean,
  85. default: false,
  86. },
  87. domain: {
  88. type: String,
  89. default: 'default',
  90. },
  91. sizeDisabled: {
  92. type: Boolean,
  93. default: false,
  94. },
  95. defaultSize: {
  96. type: Number,
  97. },
  98. defaultIops: {
  99. type: Number,
  100. },
  101. defaultThroughput: {
  102. type: Number,
  103. },
  104. defaultType: {
  105. type: Object,
  106. },
  107. isServertemplate: {
  108. type: Boolean,
  109. default: false,
  110. },
  111. storageParams: {
  112. type: Object,
  113. },
  114. storageHostParams: Object,
  115. ignoreStorageStatus: {
  116. type: Boolean,
  117. default: false,
  118. },
  119. isStorageShow: {
  120. type: Boolean,
  121. default: false,
  122. },
  123. isIopsShow: {
  124. type: Boolean,
  125. default: false,
  126. },
  127. isThroughputShow: {
  128. type: Boolean,
  129. default: false,
  130. },
  131. isAutoResetShow: {
  132. type: Boolean,
  133. default: false,
  134. },
  135. forceElements: {
  136. type: Array,
  137. },
  138. },
  139. computed: {
  140. isPublic () {
  141. return this.type === 'public'
  142. },
  143. isPrivate () {
  144. return this.type === 'private'
  145. },
  146. isIDC () {
  147. return this.type === 'idc'
  148. },
  149. isVMware () {
  150. return this.form.fd.hypervisor === HYPERVISORS_MAP.esxi.key
  151. },
  152. isAws () {
  153. return this.hypervisor === HYPERVISORS_MAP.aws.key
  154. },
  155. isCNware () {
  156. return this.hypervisor === HYPERVISORS_MAP.cnware.key
  157. },
  158. imageMinDisk () {
  159. const image = this.image
  160. let minSize = 0
  161. if (!image) return 0
  162. if (this.isHostImageType) {
  163. if (image.root_image) {
  164. minSize = (image.root_image.min_disk_mb / 1024) || 0
  165. }
  166. } else if (image.info) {
  167. minSize = ((image.info.min_disk_mb || image.info.min_disk) / 1024) || 0
  168. } else {
  169. minSize = ((image.min_disk_mb || image.min_disk) / 1024) || 0
  170. }
  171. return Math.ceil(minSize)
  172. },
  173. kvmSkuSysMaxDisk () {
  174. if (this.form.fd.hypervisor !== HYPERVISORS_MAP.kvm.key) return 0
  175. if (!this.sku) return 0
  176. return this.sku.sys_disk_max_size_gb || 0
  177. },
  178. elements () {
  179. const ret = ['disk-select']
  180. if (this.isIDC) {
  181. ret.push('schedtag')
  182. if (this.form.fd.hypervisor === HYPERVISORS_MAP.esxi.key || this.form.fd.hypervisor === HYPERVISORS_MAP.kvm.key) {
  183. ret.push('storage') // vmware,kvm 支持指定块存储
  184. }
  185. // if (this.isStorageShow) {
  186. // return ret // 指定块存储后,系统盘和数据盘均确定且不在支持设置调度标签
  187. // } else {
  188. // ret.push('schedtag')
  189. // }
  190. }
  191. if (this.isAws) {
  192. if (this.currentTypeObj?.key === 'gp3') {
  193. ret.push('iops', 'throughput')
  194. }
  195. if (this.currentTypeObj?.key === 'io1') {
  196. ret.push('iops')
  197. }
  198. }
  199. if (this.forceElements) {
  200. return this.forceElements
  201. }
  202. return ret
  203. },
  204. iopsLimit () {
  205. let ret = { min: 0 }
  206. if (this.isAws) {
  207. // gp3 iops 不能超过磁盘500倍
  208. if (this.currentTypeObj?.key === 'gp3') {
  209. ret = { min: 3000, max: 16000 }
  210. const { systemDiskSize } = this.form.fd
  211. if (systemDiskSize) {
  212. ret.max = systemDiskSize * 500 < ret.max ? systemDiskSize * 500 : ret.max
  213. }
  214. }
  215. // io1 iops 不能超过磁盘50倍
  216. if (this.currentTypeObj?.key === 'io1') {
  217. ret = { min: 100, max: 64000 }
  218. const { systemDiskSize } = this.form.fd
  219. if (systemDiskSize) {
  220. ret.max = systemDiskSize * 50 < ret.max ? systemDiskSize * 50 : ret.max
  221. }
  222. }
  223. }
  224. return ret
  225. },
  226. typesMap () {
  227. const ret = {}
  228. const hyper = this.getHypervisor()
  229. if (!hyper) return ret
  230. const hypervisorDisks = { ...STORAGE_TYPES[hyper] } || {}
  231. if (!this.capabilityData || !this.capabilityData.storage_types2) return ret
  232. let currentTypes = this.capabilityData.storage_types2[hyper] || []
  233. if (!R.isNil(this.sku) && !R.isEmpty(this.sku)) {
  234. if (this.sku.sys_disk_type && !this.defaultSize) { // 有 defaultSize 表示是调整配置,不需要根据sku信息过滤
  235. const skuDiskTypes = this.sku.sys_disk_type.split(',')
  236. if (skuDiskTypes && skuDiskTypes.length) {
  237. currentTypes = currentTypes.filter(val => {
  238. const type = val.split('/')[0]
  239. return skuDiskTypes.includes(type)
  240. })
  241. }
  242. } else {
  243. for (const obj in hypervisorDisks) {
  244. if (hypervisorDisks[obj].skuFamily && !hypervisorDisks[obj].skuFamily.includes(this.sku.instance_type_family)) {
  245. delete hypervisorDisks[obj]
  246. }
  247. }
  248. }
  249. } else {
  250. if (this.isPublic) {
  251. currentTypes = []
  252. }
  253. }
  254. const localIndex = currentTypes.findIndex(item => item.includes('local'))
  255. const novaIndex = currentTypes.findIndex(item => item.includes('nova'))
  256. if (localIndex !== -1 && localIndex !== 0) { // 将local放置首位
  257. currentTypes = findAndUnshift(currentTypes, item => item.includes('local'))
  258. }
  259. if (novaIndex !== -1 && novaIndex !== (currentTypes.length - 1)) { // 将nova放置到最后
  260. currentTypes = findAndPush(currentTypes, item => item.includes('nova'))
  261. }
  262. for (let i = 0, len = currentTypes.length; i < len; i++) {
  263. const typeItemArr = currentTypes[i].split('/')
  264. const type = typeItemArr[0]
  265. const medium = typeItemArr[1]
  266. let opt = hypervisorDisks[type] || this.getExtraDiskOpt(type)
  267. // 磁盘区分介质
  268. if (diskSupportTypeMedium(hyper)) {
  269. opt = {
  270. ...opt,
  271. key: `${type}/${medium}`,
  272. label: `${opt.label}(${MEDIUM_MAP[medium]})`,
  273. }
  274. }
  275. if (opt && !opt.sysUnusable) {
  276. // 新建ucloud虚拟机时,系统盘类型选择普通本地盘或SSD本地盘,其大小只能是系统镜像min_disk大小
  277. let max = opt.sysMax
  278. if (hyper === HYPERVISORS_MAP.ucloud.key && ['LOCAL_NORMAL', 'LOCAL_SSD'].includes(getOriginDiskKey(opt.key))) {
  279. max = this.imageMinDisk
  280. }
  281. // 谷歌云共享核心磁盘最多为3072GB
  282. if (hyper === HYPERVISORS_MAP.google.key && this.sku && ['e2-micro', 'e2-small', 'e2-medium', 'f1-micro', 'g1-small'].includes(this.sku.name)) {
  283. max = 3072
  284. }
  285. ret[opt.key] = {
  286. ...opt,
  287. medium,
  288. sysMin: Math.max(this.imageMinDisk, opt.sysMin, DISK_MIN_SIZE),
  289. sysMax: max,
  290. label: opt.key === 'nova' ? this.$t('compute.text_1141') : (DISK_LABEL_MAP[opt.label] || opt.label),
  291. }
  292. if (this.hypervisor === HYPERVISORS_MAP.google.key) {
  293. ret[opt.key].sysMin = opt.sysMin
  294. }
  295. }
  296. }
  297. if (this.hypervisor === HYPERVISORS_MAP.google.key) {
  298. delete ret['local-ssd']
  299. }
  300. if (this.hypervisor === HYPERVISORS_MAP.qcloud.key) {
  301. delete ret.local_nvme
  302. delete ret.local_pro
  303. }
  304. this.$nextTick(this.setDefaultType)
  305. return ret
  306. },
  307. currentTypeObj () {
  308. if (R.is(Object, this.typesMap) && this.form.fd[this.decorator.type[0]] && this.form.fd[this.decorator.type[0]].key) {
  309. return this.typesMap[this.form.fd[this.decorator.type[0]].key] || {}
  310. }
  311. return {}
  312. },
  313. currentDiskCapability () {
  314. if (this.hypervisor !== HYPERVISORS_MAP.kvm.key) return {}
  315. const instance_capabilities = this.capabilityData?.instance_capabilities || []
  316. const storages = instance_capabilities.find(item => item.hypervisor === this.hypervisor)?.storages
  317. const sys_disk = storages?.sys_disk || []
  318. const currentDisk = sys_disk.find(item => this.currentTypeObj.key?.startsWith(item.storage_type))
  319. return currentDisk
  320. },
  321. max () {
  322. if (this.kvmSkuSysMaxDisk && this.kvmSkuSysMaxDisk > this.min) {
  323. return this.kvmSkuSysMaxDisk
  324. }
  325. if (!this.currentDiskCapability?.max_size_gb) {
  326. return this.currentTypeObj.sysMax || this.defaultSize
  327. }
  328. return Math.min(this.currentDiskCapability?.max_size_gb, (this.currentTypeObj.sysMax || this.defaultSize))
  329. },
  330. min () {
  331. if (!this.currentDiskCapability?.min_size_gb) {
  332. return this.currentTypeObj.sysMin || 0
  333. }
  334. return Math.max(this.currentDiskCapability?.min_size_gb, (this.currentTypeObj.sysMin || 0))
  335. },
  336. storageStatusMap () {
  337. var statusMap = {
  338. type: '',
  339. tooltip: '',
  340. isError: false,
  341. }
  342. if (this.ignoreStorageStatus || !this.form.fd.systemDiskType || !this.form.fd.systemDiskType.key) return statusMap
  343. if (this.capabilityData.storage_types3 && this.hypervisor && this.hypervisor === HYPERVISORS_MAP.openstack.hypervisor) {
  344. const storageTypes3 = this.capabilityData.storage_types3[this.hypervisor] || {}
  345. const storages = []
  346. for (const prop in storageTypes3) {
  347. if (prop.startsWith(this.currentTypeObj.key)) {
  348. storages.push(storageTypes3[prop])
  349. }
  350. }
  351. const isAllEmpty = storages.every(v => v.capacity === 0)
  352. if (isAllEmpty) {
  353. // 没有设置容量:XXX存储的容量没有设置,无法创建虚拟机,请到存储--块存储进行设置,如无法查看请联系管理员设置
  354. statusMap = { type: 'error', tooltip: this.$t('compute.text_1142', [this.currentTypeObj.key]), isError: true }
  355. return
  356. }
  357. const isNotEnough = storages.every(v => v.free_capacity === 0 || v.free_capacity / 1024 < this.form.fd.systemDiskSize)
  358. if (isNotEnough) {
  359. // 选择磁盘容量不足:XXX存储的容量不足,无法创建虚拟机,请到存储--块存储进行查看,如无法查看请联系管理员查看
  360. statusMap = { type: 'error', tooltip: this.$t('compute.text_1143', [this.currentTypeObj.key]), isError: true }
  361. return
  362. }
  363. }
  364. this.$bus.$emit('VMCreateDisabled', statusMap.isError)
  365. return statusMap
  366. },
  367. },
  368. watch: {
  369. imageMinDisk (val) {
  370. if (this.isVMware) {
  371. this.form.fc.setFieldsValue({
  372. [this.decorator.size[0]]: val,
  373. })
  374. }
  375. },
  376. },
  377. created () {
  378. this.setDefaultType = _.debounce(this.setDefaultType, 1000)
  379. },
  380. methods: {
  381. setDefaultType () {
  382. if (R.isNil(this.typesMap) || R.isEmpty(this.typesMap)) {
  383. this.form.fc.setFieldsValue({
  384. [this.decorator.type[0]]: { key: '', label: '' },
  385. [this.decorator.size[0]]: 0,
  386. })
  387. return
  388. }
  389. if ([IMAGES_TYPE_MAP.host.key, IMAGES_TYPE_MAP.snapshot.key].includes(this.form.fd.imageType)) return // 主机镜像和主机快照设置默认值交给外层处理
  390. const keys = Object.keys(this.typesMap)
  391. let firstKey = keys[0]
  392. const { systemDiskSize, systemDiskType } = this.form.fd
  393. if (systemDiskSize && systemDiskType && this.typesMap[systemDiskType.key]) { // 此前选定的系统盘类型,仍然有,则保留之前的配置
  394. firstKey = systemDiskType.key
  395. }
  396. const diskMsg = this.typesMap[firstKey]
  397. this.form.fc.setFieldsValue(this.defaultType || {
  398. [this.decorator.type[0]]: { key: diskMsg.key, label: diskMsg.label },
  399. })
  400. this.setDiskMedium(diskMsg)
  401. this.$nextTick(() => { // 解决磁盘大小 inputNumber 第一次点击变为0 的bug
  402. const initSize = this.defaultSize && this.defaultSize > this.imageMinDisk ? this.defaultSize : this.imageMinDisk
  403. let newDiskSize = initSize || +diskMsg.sysMin
  404. if (systemDiskSize && systemDiskType && this.decorator.size[0] === 'systemDiskSize') { // 保留之前选择的系统盘大小
  405. newDiskSize = (systemDiskSize >= diskMsg.sysMin && systemDiskSize < diskMsg.sysMax) ? systemDiskSize : newDiskSize
  406. }
  407. this.form.fc.setFieldsValue({
  408. [this.decorator.size[0]]: newDiskSize,
  409. })
  410. })
  411. },
  412. getExtraDiskOpt (type) {
  413. const hyper = this.getHypervisor()
  414. // 腾讯云过滤掉local_basic和local_ssd类型的盘
  415. if (hyper === HYPERVISORS_MAP.qcloud.key) {
  416. if (['local_basic', 'local_ssd'].includes(type)) {
  417. return
  418. }
  419. }
  420. // VMware过滤掉rbd类型的盘
  421. if (hyper === HYPERVISORS_MAP.esxi.key) {
  422. if (['rbd'].includes(type)) {
  423. return
  424. }
  425. }
  426. return {
  427. label: `${type}`,
  428. key: `${type}`,
  429. min: 1,
  430. max: 3 * 1024,
  431. sysMin: 10,
  432. sysMax: 500,
  433. }
  434. },
  435. getSchedtagParams () {
  436. const params = {
  437. with_meta: true,
  438. cloud_env: 'onpremise',
  439. resource_type: 'storages',
  440. limit: 0,
  441. }
  442. const scopeParams = {}
  443. if (this.$store.getters.isAdminMode) {
  444. scopeParams.project_domain = this.domain
  445. } else {
  446. scopeParams.scope = this.$store.getters.scope
  447. }
  448. return {
  449. ...params,
  450. ...scopeParams,
  451. }
  452. },
  453. getHypervisor () {
  454. let ret = this.hypervisor
  455. if (this.isPublic) {
  456. if (this.sku && this.sku.provider) {
  457. ret = this.sku.provider.toLowerCase()
  458. }
  459. }
  460. return ret
  461. },
  462. showStorageChange (v) {
  463. if (this.form.fi) {
  464. this.$set(this.form.fi, 'showStorage', v)
  465. }
  466. const decoratorKey = _.get(this.decorator, 'systemDisk.storage[0]') || 'systemDiskStorage'
  467. if (!v) {
  468. this.$set(this.form.fd, decoratorKey, undefined)
  469. }
  470. },
  471. setDiskMedium (v) {
  472. if (this.form.fi) {
  473. this.$set(this.form.fi, 'systemDiskMedium', _.get(this.typesMap, `[${v.key}].medium`))
  474. }
  475. },
  476. },
  477. }
  478. </script>