SystemDisk.vue 12 KB

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