V2vTransfer.vue 39 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236
  1. <template>
  2. <base-dialog class="v2vtransfer-dialog" @cancel="cancelDialog" :width="1300">
  3. <div slot="header">{{$t('compute.v2vtransfer.label')}}</div>
  4. <div slot="body">
  5. <dialog-selected-tips :name="$t('dictionary.server')" :count="params.data.length" :action="$t('compute.v2vtransfer.label')" />
  6. <dialog-table :data="params.data" :columns="columns" />
  7. <a-form :form="form.fc" hideRequiredMark v-bind="formItemLayout">
  8. <a-form-item :label="$t('compute.v2vtransfer.type')">
  9. <a-radio v-decorator="decorators.type">{{ $t('compute.v2vtransfer.kvm') }} <help-tooltip name="v2vTransferType" /></a-radio>
  10. </a-form-item>
  11. <a-form-item :label="$t('dictionary.project')">
  12. <domain-project
  13. :decorators="decorators.projectDomain"
  14. :fc="form.fc"
  15. :labelInValue="false" />
  16. </a-form-item>
  17. <a-form-item :label="$t('compute.text_177')" class="mb-0">
  18. <cloudregion-zone
  19. :zone-params="zoneParams"
  20. :cloudregion-params="cloudregionParams"
  21. :decorator="decorators.cloudregionZone"
  22. :disabledRegion="true"
  23. filterBrandResource="compute_engine" />
  24. </a-form-item>
  25. <a-form-item :label="$t('compute.text_49')" v-show="selectedItems.length === 1 && isRenderSystemDisk">
  26. <system-disk
  27. v-if="isRenderSystemDisk"
  28. :decorator="decorators.systemDisk"
  29. :type="type"
  30. :hypervisor="hypervisor"
  31. :form="form"
  32. :defaultSize="sysdisk.value"
  33. :defaultType="form.fd.defaultType"
  34. :capability-data="form.fi.capability"
  35. :ignoreStorageStatus="true"
  36. :storageParams="systemDiskStorageParams"
  37. :forceElements="['storage']"
  38. sizeDisabled />
  39. </a-form-item>
  40. <a-form-item :label="$t('compute.text_50')" v-show="selectedItems.length === 1 && isRenderDataDisk">
  41. <data-disk
  42. v-if="isRenderDataDisk"
  43. ref="dataDiskRef"
  44. :decorator="decorators.dataDisk"
  45. :type="type"
  46. :form="form"
  47. :hypervisor="hypervisor"
  48. :capability-data="form.fi.capability"
  49. :domain="domain"
  50. :storageParams="dataDiskStorageParams"
  51. :forceElements="['storage']"
  52. :isAddDiskShow="false"
  53. forceSizeDisabled
  54. sizeDisabled />
  55. </a-form-item>
  56. <a-form-item :label="$t('compute.network_mode')">
  57. <a-radio-group v-decorator="decorators.network_mode" @change="networkModeHandle">
  58. <a-radio-button value="old">{{$t('compute.network_mode.old')}}<help-tooltip class="ml-1" :text="$t('compute.network_mode.old_tips')" /></a-radio-button>
  59. <a-tooltip :title="networkModeTooltips">
  60. <a-radio-button value="new" :disabled="networkModeTooltips">{{$t('compute.network_mode.new')}}</a-radio-button>
  61. </a-tooltip>
  62. </a-radio-group>
  63. </a-form-item>
  64. <a-form-item v-if="!isNetworkModeNew" :label="$t('compute.network_check_result')">
  65. <a-spin v-if="networkCheckLoading">
  66. <a-icon slot="indicator" type="loading" spin />
  67. </a-spin>
  68. <span v-else>
  69. <span v-if="checkNetworkResultSuccess" class="success">{{$t('compute.network_check_result.success')}}</span>
  70. <span v-else class="error">
  71. {{$t('compute.network_check_result.error')}}
  72. <a-icon type="sync" class="mr-2 pointer" :spin="spinLoading" @click="networkCheckHandle" />
  73. <help-link :href="href">{{$t('compute.network_check_result.new_create')}}</help-link>
  74. </span>
  75. </span>
  76. </a-form-item>
  77. <a-form-item v-if="isNetworkModeNew" :label="$t('compute.text_104')" class="mb-0">
  78. <server-network
  79. :form="form"
  80. :decorator="decorators.network"
  81. :network-resource-mapper="networkResourceMapper"
  82. :network-list-params="networkParams"
  83. :schedtag-params="resourcesParams.schedtag"
  84. :vpcResource="vpcResource"
  85. :networkVpcParams="resourcesParams.vpcParams"
  86. :showMacConfig="firstData.hypervisor === 'kvm'"
  87. :showDeviceConfig="firstData.hypervisor === 'kvm'"
  88. :isDialog="true"
  89. :hiddenNetworkOptions="['default', 'schedtag']"
  90. defaultNetworkType="manual"
  91. :hiddenAdd="true" />
  92. </a-form-item>
  93. <a-form-item
  94. :label="$t('compute.text_111')"
  95. :validate-status="message ? 'warning' : hostValidateStatus"
  96. :help="message || hostValidateMsg">
  97. <list-select
  98. v-decorator="decorators.host"
  99. :list-props="resourceProps"
  100. :formatter="v => v.name"
  101. :multiple="false"
  102. :placeholder="$t('compute.text_314')"
  103. :dialog-params="{ title: $t('compute.text_111'), width: 1060 }"
  104. @change="hostChangeHandle" />
  105. </a-form-item>
  106. </a-form>
  107. </div>
  108. <div slot="footer">
  109. <a-tooltip :title="handleConfirmDisabledTooltip">
  110. <a-button type="primary" @click="handleConfirm" :loading="loading" :disabled="handleConfirmDisabled">{{ $t('dialog.ok') }}</a-button>
  111. </a-tooltip>
  112. <a-button class="ml-2" @click="cancelDialog">{{ $t('dialog.cancel') }}</a-button>
  113. </div>
  114. </base-dialog>
  115. </template>
  116. <script>
  117. import _ from 'lodash'
  118. import * as R from 'ramda'
  119. import { mapGetters } from 'vuex'
  120. import { typeClouds, diskSupportTypeMedium, findPlatform, getOriginDiskKey } from '@/utils/common/hypervisor'
  121. import DialogMixin from '@/mixins/dialog'
  122. import WindowsMixin from '@/mixins/windows'
  123. import ListSelect from '@/sections/ListSelect'
  124. import DomainProject from '@/sections/DomainProject'
  125. import CloudregionZone from '@/sections/CloudregionZone'
  126. import validateForm, { isRequired, isWithinRange } from '@/utils/validate'
  127. import { STORAGE_TYPES } from '@/constants/compute'
  128. // import { HYPERVISORS_MAP } from '@/constants'
  129. import { checkIpV6, getIpv6Start } from '@Compute/utils/createServer'
  130. import DataDisk from '@Compute/sections/DataDisk'
  131. import SystemDisk from '@Compute/views/vminstance/create/components/SystemDisk'
  132. import { NETWORK_OPTIONS_MAP, MEDIUM_MAP } from '@Compute/constants'
  133. import ServerNetwork from '@Compute/sections/ServerNetwork'
  134. import ResourceProps from '../mixins/resourceProps'
  135. export default {
  136. name: 'VmV2vTransferDialog',
  137. provide () {
  138. return {
  139. form: this.form,
  140. }
  141. },
  142. components: {
  143. ListSelect,
  144. DomainProject,
  145. ServerNetwork,
  146. CloudregionZone,
  147. DataDisk,
  148. SystemDisk,
  149. },
  150. mixins: [DialogMixin, WindowsMixin, ResourceProps],
  151. data () {
  152. const firstData = this.params.data[0]
  153. const checkIpInSegment = (i, networkData) => {
  154. return (rule, value, cb) => {
  155. const isIn = isWithinRange(value, networkData.guest_ip_start, networkData.guest_ip_end)
  156. if (isIn) {
  157. cb()
  158. } else {
  159. cb(new Error(this.$t('compute.text_205')))
  160. }
  161. }
  162. }
  163. function diskValidator (rule, value, callback) {
  164. if (R.isNil(value) || R.isEmpty(value)) {
  165. return callback(new Error(this.$t('compute.text_206')))
  166. }
  167. if (!value.startsWith('/')) {
  168. return callback(new Error(this.$t('compute.text_207')))
  169. }
  170. if (value === '/') {
  171. return callback(new Error(this.$t('compute.text_208')))
  172. }
  173. callback()
  174. }
  175. return {
  176. loading: false,
  177. networkCheckLoading: false,
  178. spinLoading: false,
  179. form: {
  180. fc: this.$form.createForm(this, {
  181. onValuesChange: (props, values) => {
  182. Object.keys(values).forEach((key) => {
  183. this.$set(this.form.fd, key, values[key])
  184. })
  185. },
  186. }),
  187. fd: {
  188. defaultType: null,
  189. },
  190. fi: {
  191. capability: {},
  192. },
  193. },
  194. hosts: [],
  195. message: '',
  196. decorators: {
  197. type: [
  198. 'type',
  199. {
  200. initialValue: true,
  201. valuePropName: 'checked',
  202. },
  203. ],
  204. projectDomain: {
  205. domain: [
  206. 'domain',
  207. {
  208. initialValue: firstData.domain_id,
  209. },
  210. ],
  211. project: [
  212. 'project',
  213. {
  214. initialValue: firstData.tenant_id,
  215. },
  216. ],
  217. },
  218. cloudregionZone: {
  219. cloudregion: [
  220. 'cloudregion',
  221. {
  222. initialValue: { key: '', label: '' },
  223. rules: [
  224. { validator: isRequired(), message: this.$t('compute.text_212') },
  225. ],
  226. },
  227. ],
  228. zone: [
  229. 'zone',
  230. {
  231. initialValue: { key: '', label: '' },
  232. rules: [
  233. { validator: isRequired(), message: this.$t('compute.text_213') },
  234. ],
  235. },
  236. ],
  237. },
  238. host: [
  239. 'host',
  240. {
  241. rules: [
  242. { required: false, message: this.$t('compute.text_314'), trigger: 'change' },
  243. ],
  244. },
  245. ],
  246. systemDisk: {
  247. type: [
  248. 'systemDiskType',
  249. {
  250. rules: [
  251. { validator: isRequired(), message: this.$t('compute.text_121') },
  252. ],
  253. },
  254. ],
  255. size: [
  256. 'systemDiskSize',
  257. {
  258. rules: [
  259. { required: true, message: this.$t('compute.text_122') },
  260. ],
  261. },
  262. ],
  263. schedtag: [
  264. 'systemDiskSchedtag',
  265. {
  266. validateTrigger: ['change', 'blur'],
  267. rules: [{
  268. required: true,
  269. message: this.$t('compute.text_123'),
  270. }],
  271. },
  272. ],
  273. policy: [
  274. 'systemDiskPolicy',
  275. {
  276. validateTrigger: ['blur', 'change'],
  277. rules: [{
  278. required: true,
  279. message: this.$t('compute.text_123'),
  280. }],
  281. },
  282. ],
  283. storage: [
  284. 'systemDiskStorage',
  285. {
  286. rules: [{
  287. required: true,
  288. message: this.$t('compute.text_1351'),
  289. }],
  290. },
  291. ],
  292. },
  293. dataDisk: {
  294. type: i => [
  295. `dataDiskTypes[${i}]`,
  296. {
  297. rules: [
  298. { validator: isRequired(), message: this.$t('compute.text_121') },
  299. ],
  300. },
  301. ],
  302. size: i => [
  303. `dataDiskSizes[${i}]`,
  304. {
  305. rules: [
  306. { required: true, message: this.$t('compute.text_122') },
  307. ],
  308. },
  309. ],
  310. schedtag: i => [
  311. `dataDiskSchedtags[${i}]`,
  312. {
  313. validateTrigger: ['change', 'blur'],
  314. rules: [{
  315. required: true,
  316. message: this.$t('compute.text_123'),
  317. }],
  318. },
  319. ],
  320. policy: i => [
  321. `dataDiskPolicys[${i}]`,
  322. {
  323. validateTrigger: ['blur', 'change'],
  324. rules: [{
  325. required: true,
  326. message: this.$t('compute.text_123'),
  327. }],
  328. },
  329. ],
  330. snapshot: i => [
  331. `dataDiskSnapshots[${i}]`,
  332. {
  333. validateTrigger: ['blur', 'change'],
  334. rules: [{
  335. required: true,
  336. message: this.$t('compute.text_124'),
  337. }],
  338. },
  339. ],
  340. filetype: i => [
  341. `dataDiskFiletypes[${i}]`,
  342. {
  343. validateTrigger: ['blur', 'change'],
  344. rules: [{
  345. required: true,
  346. message: this.$t('compute.text_125'),
  347. }],
  348. },
  349. ],
  350. mountPath: i => [
  351. `dataDiskMountPaths[${i}]`,
  352. {
  353. validateTrigger: ['blur', 'change'],
  354. rules: [{
  355. required: true,
  356. message: this.$t('compute.text_126'),
  357. }, {
  358. validator: diskValidator,
  359. }],
  360. },
  361. ],
  362. storage: i => [
  363. `dataDiskStorages[${i}]`,
  364. {
  365. rules: [{
  366. required: true,
  367. message: this.$t('compute.text_1351'),
  368. }],
  369. },
  370. ],
  371. preallocation: i => [
  372. `dataDiskPreallocation[${i}]`,
  373. ],
  374. },
  375. network: {
  376. networkType: [
  377. 'networkType',
  378. {
  379. initialValue: NETWORK_OPTIONS_MAP.default.key,
  380. },
  381. ],
  382. networkConfig: {
  383. vpcs: i => [
  384. `vpcs[${i}]`,
  385. {
  386. validateTrigger: ['change', 'blur'],
  387. rules: [{
  388. required: true,
  389. message: this.$t('compute.text_194'),
  390. }],
  391. },
  392. ],
  393. networks: i => [
  394. `networks[${i}]`,
  395. {
  396. validateTrigger: ['change', 'blur'],
  397. rules: [{
  398. required: true,
  399. message: this.$t('compute.text_217'),
  400. }],
  401. },
  402. ],
  403. ips: (i, networkData) => [
  404. `networkIps[${i}]`,
  405. {
  406. validateFirst: true,
  407. validateTrigger: ['blur', 'change'],
  408. rules: [
  409. {
  410. required: true,
  411. message: this.$t('compute.text_218'),
  412. },
  413. {
  414. validator: validateForm('IPv4'),
  415. },
  416. {
  417. validator: checkIpInSegment(i, networkData),
  418. },
  419. ],
  420. },
  421. ],
  422. ips6: (i, networkData) => [
  423. `networkIpsAddress6[${i}]`,
  424. {
  425. validateFirst: true,
  426. validateTrigger: ['blur', 'change'],
  427. rules: [
  428. {
  429. required: true,
  430. message: this.$t('compute.complete_ipv6_address'),
  431. },
  432. {
  433. validator: checkIpV6(i, networkData),
  434. },
  435. ],
  436. },
  437. ],
  438. macs: (i, networkData) => [
  439. `networkMacs[${i}]`,
  440. {
  441. validateFirst: true,
  442. validateTrigger: ['blur', 'change'],
  443. rules: [
  444. {
  445. required: true,
  446. message: this.$t('compute.text_806'),
  447. },
  448. {
  449. validator: validateForm('mac'),
  450. },
  451. ],
  452. },
  453. ],
  454. ipv6s: (i, networkData) => [
  455. `networkIPv6s[${i}]`,
  456. {
  457. validateFirst: true,
  458. validateTrigger: ['blur', 'change'],
  459. },
  460. ],
  461. ipv6_mode: (i, networkData) => [
  462. `networkIPv6Modes[${i}]`,
  463. {
  464. validateTrigger: ['change', 'blur'],
  465. },
  466. ],
  467. devices: i => [
  468. `networkDevices[${i}]`,
  469. {
  470. validateTrigger: ['change', 'blur'],
  471. rules: [{
  472. required: true,
  473. message: this.$t('compute.sriov_device_tips'),
  474. }],
  475. },
  476. ],
  477. },
  478. networkSchedtag: {
  479. schedtags: i => [
  480. `networkSchedtags[${i}]`,
  481. {
  482. validateTrigger: ['change', 'blur'],
  483. rules: [{
  484. required: true,
  485. message: this.$t('compute.text_123'),
  486. }],
  487. },
  488. ],
  489. policys: (i, networkData) => [
  490. `networkPolicys[${i}]`,
  491. {
  492. validateTrigger: ['blur', 'change'],
  493. rules: [{
  494. required: true,
  495. message: this.$t('common_256'),
  496. }],
  497. },
  498. ],
  499. devices: i => [
  500. `networkDevices[${i}]`,
  501. {
  502. validateTrigger: ['change', 'blur'],
  503. rules: [{
  504. required: true,
  505. message: this.$t('compute.sriov_device_tips'),
  506. }],
  507. },
  508. ],
  509. },
  510. },
  511. schedPolicy: {
  512. schedPolicyType: [
  513. 'schedPolicyType',
  514. {
  515. initialValue: 'default',
  516. },
  517. ],
  518. schedPolicyHost: [
  519. 'schedPolicyHost',
  520. {
  521. rules: [
  522. { required: true, message: this.$t('compute.text_219') },
  523. ],
  524. },
  525. ],
  526. policySchedtag: {
  527. schedtags: i => [
  528. `policySchedtagSchedtags[${i}]`,
  529. {
  530. validateTrigger: ['change', 'blur'],
  531. rules: [{
  532. required: true,
  533. message: this.$t('compute.text_123'),
  534. }],
  535. },
  536. ],
  537. policys: (i, networkData) => [
  538. `policySchedtagPolicys[${i}]`,
  539. {
  540. validateTrigger: ['blur', 'change'],
  541. rules: [{
  542. required: true,
  543. message: this.$t('common_256'),
  544. }],
  545. },
  546. ],
  547. },
  548. },
  549. network_mode: [
  550. 'network_mode',
  551. {
  552. initialValue: 'old',
  553. },
  554. ],
  555. },
  556. formItemLayout: {
  557. wrapperCol: {
  558. span: 20,
  559. },
  560. labelCol: {
  561. span: 4,
  562. },
  563. },
  564. checkNetworkResultSuccess: false,
  565. dataDiskInterval: null,
  566. sysdisk: {},
  567. }
  568. },
  569. computed: {
  570. ...mapGetters(['scope', 'isAdminMode']),
  571. firstData () {
  572. return this.params.data[0]
  573. },
  574. isSingle () {
  575. return this.params.data.length === 1
  576. },
  577. hostsParams () {
  578. const hostIds = this.forcastData?.filtered_candidates?.map(v => v.id) || []
  579. const { domain, zone } = this.form.fd
  580. const ret = {
  581. scope: this.scope,
  582. limit: 10,
  583. enabled: 1,
  584. host_status: 'online',
  585. brand: typeClouds.brandMap.OneCloud.brand,
  586. os_arch: this.firstData.os_arch,
  587. project_domain: domain,
  588. }
  589. if (hostIds && hostIds.length > 0) {
  590. ret.filter = `id.notin(${hostIds.join(',')})`
  591. }
  592. if (zone) {
  593. ret.zone = zone.key
  594. }
  595. return ret
  596. },
  597. hostsOptions () {
  598. const hostIds = this.forcastData?.filtered_candidates?.map(v => v.id) || []
  599. if (this.forcastData?.can_create === false) {
  600. return []
  601. }
  602. return this.hosts.filter(v => {
  603. return !hostIds.includes(v.id) && v.id !== this.firstData.host_id
  604. }).map(v => {
  605. return {
  606. key: v.id,
  607. label: v.name,
  608. }
  609. })
  610. },
  611. hostValidateStatus () {
  612. if (this.forcastData && this.hostsOptions?.length === 0) {
  613. return 'error'
  614. }
  615. return 'success'
  616. },
  617. hostValidateMsg () {
  618. if (this.forcastData && this.hostsOptions?.length === 0) {
  619. return this.$t('compute.transfer_host')
  620. }
  621. return this.$t('compute.v2v_transfer.host_tooltip')
  622. },
  623. columns () {
  624. const fields = ['name', 'status', 'host', 'ips', 'region', 'tenant']
  625. const columnsMap = {}
  626. this.params.columns.forEach(item => {
  627. const { field } = item
  628. if (fields.indexOf(field) > -1) {
  629. columnsMap[field] = item
  630. }
  631. })
  632. return fields.map(field => {
  633. return columnsMap[field]
  634. })
  635. },
  636. networkParams () {
  637. const ret = {
  638. scope: this.scope,
  639. usable: true,
  640. brand: typeClouds.brandMap.OneCloud.brand,
  641. }
  642. const { domain, zone } = this.form.fd
  643. if (domain) {
  644. ret.project_domain = domain
  645. }
  646. if (zone) {
  647. ret.zone = zone.key
  648. }
  649. return ret
  650. },
  651. vpcResource () {
  652. const { cloudregion } = this.form.fd
  653. if (!cloudregion) return ''
  654. return `cloudregions/${cloudregion.key}/vpcs`
  655. },
  656. resourcesParams () {
  657. const schedtag = {
  658. limit: 1024,
  659. 'filter.0': 'resource_type.equals(networks)',
  660. }
  661. const vpcParams = {
  662. limit: 0,
  663. usable: true,
  664. brand: typeClouds.brandMap.OneCloud.brand,
  665. }
  666. const { domain, zone } = this.form.fd
  667. if (domain) {
  668. vpcParams.project_domain = domain
  669. }
  670. if (zone) {
  671. vpcParams.zone_id = zone.key
  672. }
  673. return {
  674. schedtag,
  675. vpcParams,
  676. }
  677. },
  678. cloudregionParams () {
  679. return {
  680. cloud_env: 'onpremise',
  681. usable: true,
  682. show_emulated: true,
  683. scope: this.scope,
  684. }
  685. },
  686. zoneParams () {
  687. return {
  688. usable: true,
  689. show_emulated: true,
  690. order_by: 'created_at',
  691. order: 'asc',
  692. scope: this.scope,
  693. }
  694. },
  695. isNetworkModeNew () {
  696. return this.form.fd.network_mode === 'new'
  697. },
  698. href () {
  699. const url = this.$router.resolve('/network2')
  700. return url.href
  701. },
  702. networkModeTooltips () {
  703. if (!this.isSingle) {
  704. return this.$t('compute.network_mode.new_tips')
  705. }
  706. return undefined
  707. },
  708. isDisabledCreate () {
  709. if (this.isNetworkModeNew) return false
  710. return !this.checkNetworkResultSuccess
  711. },
  712. handleConfirmDisabled () {
  713. if (this.isDisabledCreate) return this.isDisabledCreate
  714. return this.forcastData && this.hostsOptions?.length === 0
  715. },
  716. handleConfirmDisabledTooltip () {
  717. if (this.isDisabledCreate) {
  718. return this.$t('compute.network_check_result.error')
  719. }
  720. return ''
  721. },
  722. selectedItems () {
  723. return this.params.data
  724. },
  725. selectedItem () {
  726. return this.params.data[0]
  727. },
  728. type () {
  729. const brand = typeClouds.brandMap.OneCloud.brand
  730. return findPlatform(brand)
  731. },
  732. hypervisor () {
  733. return 'kvm'
  734. },
  735. isRenderSystemDisk () {
  736. return this.hypervisor && this.form.fi.capability.storage_types3 && this.form.fd.defaultType
  737. },
  738. isRenderDataDisk () {
  739. if (!(this.hypervisor && this.form.fi.capability.storage_types3)) return false
  740. if (this.selectedItems.length > 1) return false
  741. if (!(this.selectedItems[0].disks_info || []).some(item => item.disk_type === 'data')) return false
  742. return true
  743. },
  744. systemDiskStorageParams () {
  745. const params = {
  746. scope: this.$store.getters.scope,
  747. usable: true,
  748. brand: typeClouds.brandMap.OneCloud.brand,
  749. }
  750. const { systemDiskType = {} } = this.form.fd
  751. const hypervisor = this.hypervisor
  752. let key = systemDiskType.key || ''
  753. // 磁盘区分介质
  754. if (diskSupportTypeMedium(hypervisor)) {
  755. key = getOriginDiskKey(key)
  756. }
  757. if (key) {
  758. params.filter = [`storage_type.contains("${key}")`]
  759. }
  760. return params
  761. },
  762. dataDiskStorageParams () {
  763. const { systemDiskType = {}, dataDiskTypes = {} } = this.form.fd
  764. const hypervisor = this.hypervisor
  765. let key = systemDiskType.key || ''
  766. const datadisks = Object.values(dataDiskTypes)
  767. datadisks.forEach(item => {
  768. if (item.key) key = item.key
  769. })
  770. // 磁盘区分介质
  771. if (diskSupportTypeMedium(hypervisor)) {
  772. key = getOriginDiskKey(key)
  773. }
  774. const params = {
  775. scope: this.$store.getters.scope,
  776. usable: true, // 包含了 enable:true, status为online的数据
  777. brand: typeClouds.brandMap.OneCloud.brand, // kvm,vmware支持指定存储
  778. }
  779. if (key) {
  780. params.filter = [`storage_type.contains("${key}")`]
  781. }
  782. return params
  783. },
  784. },
  785. watch: {
  786. 'form.fd.zone' (newV, oldV) {
  787. if (newV?.key && (newV.key !== oldV.key)) {
  788. this.capability(newV.key)
  789. }
  790. },
  791. },
  792. created () {
  793. this.capability = _.debounce(this._capability, 1000)
  794. this.queryData()
  795. },
  796. methods: {
  797. networkModeHandle (e) {
  798. if (e.target.value === 'new') {
  799. this.$nextTick(() => {
  800. this.form.fc.setFieldsValue({
  801. networkType: NETWORK_OPTIONS_MAP.manual.key,
  802. })
  803. })
  804. }
  805. },
  806. networkCheckHandle () {
  807. this.spinLoading = true
  808. this.batchConvertPrecheck()
  809. setTimeout(() => {
  810. this.spinLoading = false
  811. }, 2000)
  812. },
  813. queryData () {
  814. this.queryHosts()
  815. this.batchConvertPrecheck()
  816. },
  817. getDiskTypeObj (type, medium) {
  818. const storageItem = STORAGE_TYPES[this.hypervisor]
  819. const diskKey = `${type}/${medium}`
  820. if (this.form.fi.capability.storage_types3[this.hypervisor]) {
  821. const keys = Object.keys(this.form.fi.capability.storage_types3[this.hypervisor])
  822. if (keys.includes(diskKey)) {
  823. return { type, medium, label: R.is(Object, storageItem) ? (storageItem[type]?.label || type) : type }
  824. } else {
  825. let target = ''
  826. const index = keys.findIndex(key => key.startsWith('local'))
  827. if (index > -1) {
  828. target = keys[index]
  829. } else {
  830. target = keys.length ? keys[0] : ''
  831. }
  832. return { type: target.split('/')[0], medium: target.split('/')[1], label: R.is(Object, storageItem) ? (storageItem[type]?.label || type) : type }
  833. }
  834. }
  835. return { type: '', medium: '', label: '' }
  836. },
  837. // v2v 不支持 rdb 存储
  838. filterDiskType (data) {
  839. const d = R.clone(data)
  840. const keys = Object.keys(d)
  841. keys.forEach(key => {
  842. if (key.includes('storage_types')) {
  843. const v = d[key]
  844. if (R.is(Array, v)) {
  845. d[key] = v.filter(str => !str.startsWith('rbd/'))
  846. }
  847. if (R.is(Object, v)) {
  848. const ks = Object.keys(v)
  849. ks.forEach(k => {
  850. if (R.is(Array, d[key][k])) {
  851. d[key][k] = d[key][k].filter(str => !str.startsWith('rbd/'))
  852. }
  853. if (R.is(Object, d[key][k])) {
  854. const kss = Object.keys(d[key][k])
  855. kss.forEach(kk => {
  856. if (kk.startsWith('rbd/')) {
  857. delete d[key][k][kk]
  858. }
  859. })
  860. }
  861. })
  862. }
  863. }
  864. })
  865. return d
  866. },
  867. _capability (zoneId) { // 可用区查询
  868. const data = { show_emulated: true, scope: this.scope }
  869. const m = new this.$Manager('zones')
  870. m.get({
  871. id: `${zoneId}/capability`,
  872. params: data,
  873. }).then(({ data = {} }) => {
  874. this.form.fi.capability = this.filterDiskType(data)
  875. const conf = this.maxConfig()
  876. this.form.fd.datadisks = conf[2]
  877. this.form.fd.sysdisks = conf[3]
  878. this.beforeDataDisks = [...this.form.fd.datadisks]
  879. if (this.form.fd.sysdisks && this.form.fd.sysdisks.length === 1) {
  880. this.sysdisk = this.form.fd.sysdisks[0]
  881. const storageItem = STORAGE_TYPES[this.selectedItem.hypervisor]
  882. // 磁盘区分介质
  883. let diskKey = ''
  884. let diskLabel = R.is(Object, storageItem) ? (storageItem[diskKey]?.label || diskKey) : diskKey
  885. const { medium_type } = this.selectedItem.disks_info[0] || {}
  886. const diskTypeObj = this.getDiskTypeObj(this.sysdisk.type, medium_type)
  887. if (diskTypeObj.type && diskTypeObj.medium) {
  888. diskKey = `${diskTypeObj.type}/${diskTypeObj.medium}`
  889. diskLabel = `${diskTypeObj.label}(${MEDIUM_MAP[diskTypeObj.medium]})`
  890. }
  891. this.form.fd.defaultType = {
  892. [this.decorators.systemDisk.type[0]]: { key: diskKey, label: diskLabel },
  893. }
  894. }
  895. const dataDisks = this.selectedItem.disks_info.filter(item => item.disk_type === 'data' || item.disk_type === 'swap')
  896. const { type: dataDiskType, medium_type: dataDiskMedium } = dataDisks[0] || {}
  897. const diskTypeObj = this.getDiskTypeObj(dataDiskType, dataDiskMedium)
  898. this.$nextTick(() => {
  899. this.diskLoaded = true
  900. this.dataDiskInterval = setInterval(() => {
  901. if (this.isRenderDataDisk && this.$refs.dataDiskRef) {
  902. this.form.fd.datadisks.forEach((v, i) => {
  903. this.$refs.dataDiskRef.add({ size: v.value, min: v.value, diskType: diskTypeObj.type, disabled: false, sizeDisabled: true, medium: diskTypeObj.medium, ...v })
  904. })
  905. clearInterval(this.dataDiskInterval)
  906. this.dataDiskInterval = null
  907. }
  908. }, 500)
  909. })
  910. })
  911. },
  912. networkResourceMapper (list) {
  913. return list
  914. .map(val => {
  915. const remain = val.ports - val.ports_used
  916. if (remain <= 0) {
  917. return {
  918. ...val,
  919. __disabled: true,
  920. }
  921. }
  922. return val
  923. })
  924. .sort((a, b) => (b.ports - b.ports_used) - (a.ports - a.ports_used))
  925. },
  926. /**
  927. * 组装网络数据
  928. *
  929. * @returns { Array }
  930. * @memberof GenCreateData
  931. */
  932. async genNetworks (values) {
  933. let ret = [{ exit: false }]
  934. // 指定 IP 子网
  935. if (this.form.fd.networkType === NETWORK_OPTIONS_MAP.manual.key) {
  936. ret = []
  937. const { networkIPv6s, networkIpsAddress6, networkIPv6Modes } = await this.form.fc.validateFields()
  938. R.forEachObjIndexed((value, key) => {
  939. const obj = {
  940. network: value,
  941. }
  942. if (this.form.fd.networkIps) {
  943. const address = this.form.fd.networkIps[key]
  944. if (address) {
  945. obj.address = address
  946. }
  947. }
  948. if (this.form.fd.networkMacs) {
  949. const mac = this.form.fd.networkMacs[key]
  950. if (mac) {
  951. obj.mac = mac
  952. }
  953. }
  954. if (networkIPv6s && networkIPv6s[key]) {
  955. obj.require_ipv6 = true
  956. }
  957. const target = this.form.fi.networkList.filter(item => item.key === key)
  958. if (networkIpsAddress6 && networkIpsAddress6[key]) {
  959. const ipv6Last = networkIpsAddress6[key]
  960. const ipv6First = getIpv6Start(target[0]?.network?.guest_ip6_start)
  961. obj.address6 = ipv6First + ipv6Last
  962. }
  963. if (obj.require_ipv6) {
  964. if (networkIPv6Modes && networkIPv6Modes[key] === 'only') {
  965. obj.strict_ipv6 = true
  966. }
  967. }
  968. if (!target[0]?.network?.guest_ip_start && !target[0]?.network?.guest_ip_end && target[0]?.network?.guest_ip6_start) {
  969. obj.strict_ipv6 = true
  970. }
  971. if (this.form.fd.networkExits) {
  972. const exit = this.form.fd.networkExits[key]
  973. if (exit) {
  974. obj.exit = true
  975. }
  976. }
  977. if (this.form.fd.networkDevices) {
  978. const device = this.form.fd.networkDevices[key]
  979. if (device) {
  980. obj.sriov_device = { model: device }
  981. }
  982. }
  983. ret.push(obj)
  984. }, values.networks)
  985. }
  986. // 指定 调度标签
  987. if (this.form.fd.networkType === NETWORK_OPTIONS_MAP.schedtag.key) {
  988. ret = []
  989. R.forEachObjIndexed((value, key) => {
  990. const obj = {
  991. id: value,
  992. }
  993. const strategy = this.form.fd.networkPolicys[key]
  994. if (strategy) {
  995. obj.strategy = strategy
  996. }
  997. if (this.form.fd.networkDevices) {
  998. const device = this.form.fd.networkDevices[key]
  999. if (device) {
  1000. obj.sriov_device = { model: device }
  1001. }
  1002. }
  1003. ret.push({
  1004. schedtags: [obj],
  1005. })
  1006. }, values.networkSchedtags)
  1007. }
  1008. return ret
  1009. },
  1010. async doTransfer (ids, values) {
  1011. const data = {
  1012. prefer_host: values.host,
  1013. target_hypervisor: this.firstData.hypervisor,
  1014. }
  1015. if (this.isNetworkModeNew) {
  1016. data.networks = await this.genNetworks(values)
  1017. }
  1018. if (this.selectedItems.length === 1) {
  1019. data.disks = this.genDiskData(values)
  1020. }
  1021. return this.params.onManager('batchPerformAction', {
  1022. id: ids,
  1023. steadyStatus: ['running', 'ready'],
  1024. managerArgs: {
  1025. action: 'convert-to-kvm',
  1026. data,
  1027. },
  1028. })
  1029. },
  1030. async handleConfirm () {
  1031. this.loading = true
  1032. try {
  1033. const values = await this.form.fc.validateFields()
  1034. const ids = this.params.data.map(item => item.id)
  1035. await this.doTransfer(ids, values)
  1036. this.$message.success(this.$t('common.success'))
  1037. this.params.successCallback && this.params.successCallback()
  1038. this.cancelDialog()
  1039. } finally {
  1040. this.loading = false
  1041. }
  1042. },
  1043. queryHosts () {
  1044. const hostsManager = new this.$Manager('hosts')
  1045. hostsManager.list({ params: this.hostsParams }).then((res) => {
  1046. this.hosts = res.data.data || []
  1047. }).catch((err) => {
  1048. console.log(err)
  1049. throw err
  1050. })
  1051. },
  1052. hostChangeHandle (hostId) {
  1053. const hostArr = this.params.data.filter(v => v.host_id === hostId)
  1054. if (hostArr.length > 0) {
  1055. this.message = this.$t('compute.transfer_mutiple_dialog_alert', [hostArr.length])
  1056. } else {
  1057. this.message = ''
  1058. }
  1059. },
  1060. async batchConvertPrecheck () {
  1061. try {
  1062. this.checkNetworkResultSuccess = false
  1063. this.networkCheckLoading = true
  1064. const ids = this.params.data.map(item => item.id)
  1065. const m = new this.$Manager('servers')
  1066. const res = await m.performClassAction({ action: 'batch-convert-precheck', data: { guest_ids: ids } })
  1067. if (!res.data?.network_failed) {
  1068. this.checkNetworkResultSuccess = true
  1069. }
  1070. } catch (error) {
  1071. throw error
  1072. } finally {
  1073. this.networkCheckLoading = false
  1074. }
  1075. },
  1076. genDiskData (values) {
  1077. const sysDisk = []
  1078. const dataDisk = []
  1079. const len = this.form.fd.sysdisks?.length || -1
  1080. if (len) {
  1081. const sysDiskType = this.form.fd.systemDiskType?.key
  1082. const systemDisk = {
  1083. index: 0,
  1084. disk_type: 'sys',
  1085. backend: getOriginDiskKey(sysDiskType),
  1086. size: this.form.fd.systemDiskSize * 1024,
  1087. }
  1088. // 磁盘介质
  1089. if (this.form.fi.systemDiskMedium) {
  1090. systemDisk.medium = this.form.fi.systemDiskMedium
  1091. }
  1092. if (this.form.fd.systemDiskSchedtag) {
  1093. systemDisk.schedtags = [
  1094. { id: this.form.fd.systemDiskSchedtag },
  1095. ]
  1096. if (this.form.fd.systemDiskPolicy && this.form.fd.systemDiskPolicy) {
  1097. systemDisk.schedtags[0].strategy = this.form.fd.systemDiskPolicy
  1098. }
  1099. }
  1100. if (this.form.fd.systemDiskStorage) {
  1101. systemDisk.storage_id = this.form.fd.systemDiskStorage
  1102. }
  1103. if (this.form.fd.systemDiskIops) {
  1104. systemDisk.iops = this.form.fd.systemDiskIops
  1105. }
  1106. if (this.form.fd.systemDiskThroughput) {
  1107. systemDisk.throughput = this.form.fd.systemDiskThroughput
  1108. }
  1109. if (this.form.fd.systemDiskPreallocation) {
  1110. systemDisk.preallocation = this.form.fd.systemDiskPreallocation
  1111. }
  1112. if (this.form.fd.systemDiskAutoReset) {
  1113. systemDisk.auto_reset = this.form.fd.systemDiskAutoReset
  1114. }
  1115. sysDisk.push(systemDisk)
  1116. }
  1117. if (this.$refs.dataDiskRef) {
  1118. let index = len >= 1 ? len - 1 : len
  1119. const dataDisks = this.$refs.dataDiskRef.dataDisks
  1120. R.forEachObjIndexed((value, key) => {
  1121. const diskObj = {
  1122. disk_type: 'data',
  1123. index: ++index,
  1124. }
  1125. if (values.dataDiskSizes && values.dataDiskSizes[key]) {
  1126. diskObj.size = values.dataDiskSizes[key] * 1024
  1127. }
  1128. if (values.dataDiskTypes) {
  1129. if (values.dataDiskTypes[key]) {
  1130. // 磁盘区分介质
  1131. let diskKey = values.dataDiskTypes[key].key
  1132. if (diskSupportTypeMedium(this.selectedItem.hypervisor)) {
  1133. diskKey = getOriginDiskKey(diskKey)
  1134. }
  1135. diskObj.backend = diskKey
  1136. } else {
  1137. if (_.get(dataDisks, '[0].diskType.key')) {
  1138. // 磁盘区分介质
  1139. let diskKey = _.get(dataDisks, '[0].diskType.key') // 默认添加的盘和第一块保持一致
  1140. if (diskSupportTypeMedium(this.selectedItem.hypervisor)) {
  1141. diskKey = getOriginDiskKey(diskKey)
  1142. }
  1143. diskObj.backend = diskKey
  1144. }
  1145. }
  1146. }
  1147. if (values.dataDiskFiletypes && values.dataDiskFiletypes[key]) {
  1148. diskObj.filetype = values.dataDiskFiletypes[key]
  1149. }
  1150. if (values.dataDiskMountPaths && values.dataDiskMountPaths[key]) {
  1151. diskObj.mountpoint = values.dataDiskMountPaths[key]
  1152. }
  1153. if (values.dataDiskSnapshots && values.dataDiskSnapshots[key]) {
  1154. diskObj.snapshot_id = values.dataDiskSnapshots[key]
  1155. }
  1156. if (values.dataDiskSchedtags && values.dataDiskSchedtags[key]) {
  1157. diskObj.schedtags = [
  1158. { id: values.dataDiskSchedtags[key] },
  1159. ]
  1160. if (values.dataDiskPolicys && values.dataDiskPolicys[key]) {
  1161. diskObj.schedtags[0].strategy = values.dataDiskPolicys[key]
  1162. }
  1163. }
  1164. if (values.dataDiskStorages && values.dataDiskStorages[key]) {
  1165. diskObj.storage_id = values.dataDiskStorages[key]
  1166. }
  1167. if (values.dataDiskPreallocation && values.dataDiskPreallocation[key]) {
  1168. diskObj.preallocation = values.dataDiskPreallocation[key]
  1169. }
  1170. // 磁盘区分介质
  1171. if (values.dataDiskTypes && values.dataDiskTypes[key]) {
  1172. const { key: dataDiskKey = '' } = values.dataDiskTypes[key] || {}
  1173. const medium = dataDiskKey.split('/')[1]
  1174. if (diskSupportTypeMedium(this.selectedItem.hypervisor) && medium) {
  1175. diskObj.medium = medium
  1176. }
  1177. }
  1178. dataDisk.push(diskObj)
  1179. }, values.dataDiskSizes)
  1180. if (_.get(this.params, 'data[0].disks_info[0].disk_type') === 'data') {
  1181. dataDisk.shift() // 因为第一块盘的disk_type是data,说明无系统盘,第一块盘是ISO启动的,需要去掉
  1182. }
  1183. }
  1184. return [...sysDisk, ...dataDisk]
  1185. },
  1186. maxConfig () {
  1187. let cpu = 0
  1188. let mem = 0
  1189. const datadisks = []
  1190. const sysdisks = []
  1191. for (let i = 0; i < this.params.data.length; i++) {
  1192. if (cpu < this.params.data[i].vcpu_count) {
  1193. cpu = this.params.data[i].vcpu_count
  1194. }
  1195. if (mem < this.params.data[i].vmem_size) {
  1196. mem = this.params.data[i].vmem_size
  1197. }
  1198. if (this.params.data[i].disks_info) {
  1199. this.params.data[i].disks_info.forEach((item) => {
  1200. if (item.disk_type !== 'sys') { // 数据盘
  1201. datadisks.push({ value: item.size / 1024, type: item.storage_type, medium_type: item.medium_type })
  1202. } else { // 系统盘
  1203. sysdisks.push({ value: item.size / 1024, type: item.storage_type, medium_type: item.medium_type })
  1204. }
  1205. })
  1206. }
  1207. }
  1208. return [cpu, mem / 1024, datadisks, sysdisks]
  1209. },
  1210. },
  1211. }
  1212. </script>
  1213. <style lang="scss">
  1214. .v2vtransfer-dialog .ant-col-20 {
  1215. width: 83%;
  1216. .success {
  1217. color: #0cbd09;
  1218. }
  1219. .error {
  1220. color: #f00b0b;
  1221. }
  1222. .pointer {
  1223. cursor: pointer;
  1224. }
  1225. }
  1226. </style>