index.vue 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697
  1. <template>
  2. <div>
  3. <page-header :title="$t('k8s.text_146')" />
  4. <a-alert :showIcon="true" type="info" banner class="mt-2">
  5. <template slot="message">
  6. <div>
  7. <p>{{$t('k8s.text_147')}}</p>
  8. <p>{{$t('k8s.text_148')}}</p>
  9. <p>{{$t('k8s.text_149')}}</p>
  10. </div>
  11. </template>
  12. </a-alert>
  13. <a-alert :showIcon="true" type="error" v-if="!preCheckResp.pass">
  14. <template slot="message" v-if="showDocsLink()">
  15. <div>
  16. {{$t('k8s.provider_image_not_prepared')}}
  17. <help-link :href="docs[provider.hypervisor]">{{$t('k8s.ref_prepare_doc')}}</help-link>
  18. </div>
  19. </template>
  20. </a-alert>
  21. <page-body needMarginBottom>
  22. <a-form
  23. class="mt-3"
  24. :form="form.fc">
  25. <a-form-item
  26. :label="$t('k8s.text_412')"
  27. v-bind="formItemLayout">
  28. <base-select
  29. v-if="$store.getters.isAdminMode"
  30. v-decorator="decorators.project_domain_id"
  31. resource="domains"
  32. version="v1"
  33. :params="params.domain"
  34. @change="domainChange"
  35. is-default-select
  36. filterable />
  37. <div v-else>{{ $store.getters.userInfo.projectDomain }}</div>
  38. </a-form-item>
  39. <a-form-item
  40. :label="$t('k8s.platform')"
  41. v-bind="formItemLayout">
  42. <a-radio-group
  43. v-decorator="decorators.provider(provider)"
  44. v-if="providers.length"
  45. @change="providerChange">
  46. <a-radio-button v-for="item in providers" :key="item.value" :value="item">
  47. {{ item.label }}
  48. </a-radio-button>
  49. </a-radio-group>
  50. <div v-else>{{ $t('common_467') }}</div>
  51. </a-form-item>
  52. <a-form-item :label="$t('k8s.region')" class="mb-0" v-bind="formItemLayout" v-if="provider.brand">
  53. <cloudregion-vpc
  54. :cloudregion-params="cloudregionParams"
  55. :vpc-params="params.vpcParams"
  56. :decorator="decorators.regionVpc" />
  57. </a-form-item>
  58. <a-form-item
  59. :label="$t('k8s.text_41')"
  60. v-bind="formItemLayout">
  61. <a-input v-decorator="decorators.name" :placeholder="$t('validator.serverName')" />
  62. </a-form-item>
  63. <a-form-item :label="$t('k8s.text_152')" v-if="cloudregionId" v-bind="formItemLayout">
  64. <server-config
  65. :form="form"
  66. :cloudregionId="cloudregionId"
  67. :decorator="decorators.serverConfig"
  68. :hypervisor="hypervisor"
  69. :platform="platform"
  70. :networkParams="params.network" />
  71. </a-form-item>
  72. <a-form-item :label="$t('k8s.text_153')" v-bind="formItemLayout">
  73. <base-select
  74. v-decorator="decorators.version"
  75. :options="k8sVersionOps"
  76. :filterable="true"
  77. :select-props="{ placeholder: $t('k8s.text_154') }" />
  78. </a-form-item>
  79. <!-- <a-form-item :label="$t('k8s.text_155')" v-bind="formItemLayout" v-if="isAdminMode">
  80. <a-switch :checkedChildren="$t('k8s.text_156')" :unCheckedChildren="$t('k8s.text_157')" v-decorator="decorators.is_public" />
  81. </a-form-item> -->
  82. <a-form-item :label="$t('k8s.text_158')" v-bind="formItemLayout">
  83. <a-switch :checkedChildren="$t('k8s.text_156')" :unCheckedChildren="$t('k8s.text_157')" v-decorator="decorators.isConfigImage" @change="isConfigImageChange" />
  84. </a-form-item>
  85. <a-form-item
  86. v-if="isConfigImage"
  87. :label="$t('k8s.text_52')"
  88. v-bind="formItemLayout"
  89. :extra="$t('k8s.text_159')">
  90. <a-input v-decorator="decorators.image_repository_url" :placeholder="$t('k8s.text_160')" />
  91. </a-form-item>
  92. <a-form-item v-if="isConfigImage" :wrapper-col="{ span: 20, offset: 3 }">
  93. <a-checkbox v-decorator="decorators.image_repository_insecure">{{$t('k8s.text_161')}}</a-checkbox>
  94. </a-form-item>
  95. </a-form>
  96. </page-body>
  97. <page-footer>
  98. <div slot="right">
  99. <a-button class="mr-2" type="primary" @click="handleConfirm" :loading="loading">{{$t('common.create')}}</a-button>
  100. <a-button @click="cancel">{{ $t('dialog.cancel') }}</a-button>
  101. </div>
  102. </page-footer>
  103. </div>
  104. </template>
  105. <script>
  106. /* import * as R from 'ramda' */
  107. import { mapGetters } from 'vuex'
  108. import { HYPERVISORS_MAP } from '@/constants'
  109. import { IMAGES_TYPE_MAP } from '@/constants/compute'
  110. import { DOCS_MAP, showDocsLink } from '@/constants/docs'
  111. import ServerConfig from '@K8S/sections/serverConfig'
  112. import CloudregionVpc from '@/sections/CloudregionVpc'
  113. import { isWithinRange, isRequired } from '@/utils/validate'
  114. import { findPlatform } from '@/utils/common/hypervisor'
  115. import i18n from '@/locales'
  116. import { KUBE_PROVIDER, K8S_HYPERVISORS_MAP } from '../constants'
  117. function checkIpInSegment (i, networkData) {
  118. return (rule, value, _callback) => {
  119. const isIn = isWithinRange(value, networkData.guest_ip_start, networkData.guest_ip_end)
  120. if (isIn) {
  121. _callback()
  122. } else {
  123. _callback(this.$t('k8s.text_163'))
  124. }
  125. }
  126. }
  127. export default {
  128. name: 'ClusterCreate',
  129. components: {
  130. CloudregionVpc,
  131. ServerConfig,
  132. },
  133. data () {
  134. return {
  135. showDocsLink,
  136. loading: false,
  137. providers: [],
  138. provider: {},
  139. cloudregionId: '',
  140. capability: {},
  141. preCheckResp: {
  142. pass: true,
  143. },
  144. docs: DOCS_MAP.cluster(),
  145. form: {
  146. fc: this.$form.createForm(this, {
  147. onValuesChange: (props, values) => {
  148. if (values.vpc && values.vpc.key) {
  149. this.params.network = {
  150. vpc: values.vpc.key,
  151. filter: 'server_type.notin(ipmi, pxe)',
  152. scope: this.scope,
  153. }
  154. }
  155. if (values.cloudregion && values.cloudregion.key) {
  156. this.cloudregionId = values.cloudregion.key
  157. }
  158. Object.keys(values).forEach((key) => {
  159. this.form.fd[key] = values[key]
  160. })
  161. },
  162. }),
  163. fi: {
  164. /* hypervisors: ['kvm'], */
  165. /* capability: {}, */
  166. },
  167. fd: {
  168. /* hypervisor: hyperOpts[0].value, */
  169. project_domain_id: this.$store.getters.projectDomainId,
  170. },
  171. },
  172. decorators: {
  173. project_domain_id: [
  174. 'project_domain_id',
  175. {
  176. rules: [
  177. { required: true, message: this.$t('k8s.text_413') },
  178. ],
  179. },
  180. ],
  181. provider: defaultProvider => [
  182. 'provider',
  183. {
  184. initialValue: defaultProvider,
  185. rules: [
  186. { required: true, message: this.$t('k8s.text_164') },
  187. ],
  188. },
  189. ],
  190. regionVpc: {
  191. cloudregion: [
  192. 'cloudregion',
  193. {
  194. initialValue: { key: '', label: '' },
  195. rules: [
  196. { required: true, message: this.$t('k8s.text_165') },
  197. ],
  198. },
  199. ],
  200. vpc: [
  201. 'vpc',
  202. {
  203. initialValue: { key: '', label: '' },
  204. rules: [
  205. { required: true, message: this.$t('k8s.text_165') },
  206. ],
  207. },
  208. ],
  209. },
  210. name: [
  211. 'name',
  212. {
  213. validateTrigger: 'blur',
  214. rules: [
  215. { required: true, message: this.$t('k8s.text_60') },
  216. { validator: this.$validate('serverName') },
  217. ],
  218. },
  219. ],
  220. serverConfig: {
  221. network: i => [
  222. `network[${i}]`,
  223. {
  224. rules: [
  225. { required: true, message: this.$t('k8s.text_122') },
  226. ],
  227. },
  228. ],
  229. ip: (i, networkData) => [
  230. `ip[${i}]`,
  231. {
  232. validateFirst: true,
  233. validateTrigger: ['blur', 'change'],
  234. rules: [{
  235. required: true,
  236. message: this.$t('k8s.text_169'),
  237. }, {
  238. validator: checkIpInSegment(i, networkData),
  239. }],
  240. },
  241. ],
  242. sku: i => [
  243. `sku[${i}]`,
  244. {
  245. rules: [
  246. { validator: isRequired(true, 'id'), message: this.$t('compute.text_216') },
  247. ],
  248. },
  249. ],
  250. imageOS: i => [
  251. `imageOS[${i}]`,
  252. {
  253. os: [
  254. `os[${i}]`,
  255. {
  256. initialValue: '',
  257. rules: [
  258. { required: true, message: i18n.t('compute.text_153') },
  259. ],
  260. },
  261. ],
  262. image: [
  263. `image[${i}]`,
  264. {
  265. initialValue: { key: '', label: '' },
  266. rules: [
  267. { validator: isRequired(), message: i18n.t('compute.text_214') },
  268. ],
  269. },
  270. ],
  271. imageType: [
  272. `imageType[${i}]`,
  273. {
  274. initialValue: IMAGES_TYPE_MAP.standard.key,
  275. },
  276. ],
  277. },
  278. ],
  279. /*
  280. * vcpu_count: i => [
  281. * `vcpu_count[${i}]`,
  282. * {
  283. * initialValue: 4,
  284. * rules: [
  285. * { required: true, message: this.$t('k8s.text_99') },
  286. * { validator: this.validator('vcpu_count') },
  287. * ],
  288. * },
  289. * ],
  290. * vmem_size: i => [
  291. * `vmem_size[${i}]`,
  292. * {
  293. * initialValue: 4,
  294. * rules: [
  295. * { required: true, message: this.$t('k8s.text_167') },
  296. * { validator: this.validator('vmem_size') },
  297. * ],
  298. * },
  299. * ],
  300. */
  301. disk: i => ({
  302. type: [
  303. `systemDiskType[${i}]`,
  304. {
  305. rules: [
  306. { validator: isRequired(), message: this.$t('compute.text_121') },
  307. ],
  308. },
  309. ],
  310. size: [
  311. `systemDiskSize[${i}]`,
  312. {
  313. rules: [
  314. { required: true, message: this.$t('compute.text_122') },
  315. ],
  316. },
  317. ],
  318. schedtag: [
  319. `systemDiskSchedtag[${i}]`,
  320. {
  321. validateTrigger: ['change', 'blur'],
  322. rules: [{
  323. required: true,
  324. message: this.$t('compute.text_123'),
  325. }],
  326. },
  327. ],
  328. policy: [
  329. `systemDiskPolicy[${i}]`,
  330. {
  331. validateTrigger: ['blur', 'change'],
  332. rules: [{
  333. required: true,
  334. message: this.$t('compute.text_123'),
  335. }],
  336. },
  337. ],
  338. }),
  339. num: i => [
  340. `num[${i}]`,
  341. {
  342. initialValue: 1,
  343. rules: [
  344. { required: true, message: this.$t('k8s.text_170') },
  345. ],
  346. },
  347. ],
  348. role: i => [
  349. `role[${i}]`,
  350. {
  351. initialValue: 'controlplane',
  352. rules: [
  353. { required: true },
  354. ],
  355. },
  356. ],
  357. },
  358. version: [
  359. 'version',
  360. ],
  361. is_public: [
  362. 'is_public',
  363. {
  364. valuePropName: 'checked',
  365. initialValue: false,
  366. },
  367. ],
  368. isConfigImage: [
  369. 'isConfigImage',
  370. {
  371. valuePropName: 'checked',
  372. initialValue: false,
  373. },
  374. ],
  375. image_repository_url: [
  376. 'image_repository_url',
  377. {
  378. rules: [
  379. { required: true, message: this.$t('k8s.text_53') },
  380. ],
  381. },
  382. ],
  383. image_repository_insecure: [
  384. 'image_repository_insecure',
  385. {
  386. valuePropName: 'checked',
  387. initialValue: true,
  388. },
  389. ],
  390. },
  391. isConfigImage: false,
  392. formItemLayout: {
  393. wrapperCol: {
  394. md: { span: 18 },
  395. xl: { span: 20 },
  396. xxl: { span: 22 },
  397. },
  398. labelCol: {
  399. md: { span: 6 },
  400. xl: { span: 4 },
  401. xxl: { span: 2 },
  402. },
  403. },
  404. params: {
  405. vpcParams: {
  406. usable: true,
  407. show_emulated: true,
  408. order_by: 'created_at',
  409. order: 'asc',
  410. scope: this.scope,
  411. 'filter.0': 'external_access_mode.in(eip,eip-distgw)',
  412. },
  413. network: {},
  414. k8sVersions: () => {
  415. return {
  416. provider: this.hypervisor || KUBE_PROVIDER,
  417. resource_type: 'guest',
  418. }
  419. },
  420. domain: {
  421. scope: this.$store.getters.scope,
  422. limit: 0,
  423. },
  424. },
  425. k8sVersionOps: [],
  426. }
  427. },
  428. provide () {
  429. return {
  430. form: this.form,
  431. }
  432. },
  433. computed: {
  434. ...mapGetters(['userInfo', 'scope', 'isAdminMode', 'isDomainMode']),
  435. cloudregionParams () {
  436. const provider = this.provider
  437. /* const type = findPlatform(provider, 'hypervisor') */
  438. const param = {
  439. usable: true,
  440. usable_vpc: true,
  441. project_domain: this.form.fd.project_domain_id,
  442. brand: provider.brand,
  443. }
  444. /*
  445. * switch (type) {
  446. * case 'idc':
  447. * param = { cloud_env: 'onpremise' }
  448. * break
  449. * case 'public':
  450. * param = { cloud_env: 'public' }
  451. * break
  452. * case 'private':
  453. * param = { cloud_env: 'private', show_emulated: true }
  454. * break
  455. * default :
  456. * param = { is_on_premise: true }
  457. * }
  458. */
  459. return param
  460. },
  461. hypervisor () {
  462. return this.provider.hypervisor
  463. },
  464. platform () {
  465. return findPlatform(this.hypervisor, 'hypervisor')
  466. },
  467. },
  468. created () {
  469. this.clustersM = new this.$Manager('kubeclusters', 'v1')
  470. this.form.fc.getFieldDecorator('cloudregion', { preserve: true })
  471. /* this.form.fc.getFieldDecorator('zone', { preserve: true }) */
  472. this.isDomainMode && this.fetchCapability(this.userInfo.projectDomainId)
  473. },
  474. methods: {
  475. /*
  476. * async fetchCapability (id, resource) {
  477. * const params = {
  478. * show_emulated: true,
  479. * resource_type: 'shared',
  480. * scope: this.scope,
  481. * }
  482. * const capabilityParams = { id, spec: 'capability', params }
  483. * if (!id) return
  484. * this.capabilityParams = capabilityParams
  485. * try {
  486. * const { data } = await new this.$Manager(`${resource}s`).getSpecific(this.capabilityParams)
  487. * let hypervisors = R.is(Object, data) ? (data.hypervisors || []) : []
  488. * if (hypervisors.includes(HYPERVISORS_MAP.kvm.key)) { // kvm 排序为第一个
  489. * hypervisors = [HYPERVISORS_MAP.kvm.key].concat(hypervisors).filter(val => val !== 'baremetal')
  490. * }
  491. * hypervisors = Array.from(new Set(hypervisors))
  492. * this.form.fi.hypervisors = hypervisors
  493. * this.form.fi.capability = data
  494. * } catch (error) {
  495. * throw error
  496. * }
  497. * },
  498. */
  499. domainChange (val) {
  500. this.form.fd.project_domain_id = val
  501. this.fetchCapability(val)
  502. },
  503. fetchCapability (domainId) {
  504. new this.$Manager('capabilities', 'v2').list({
  505. params: {
  506. domain: domainId,
  507. scope: this.scope,
  508. },
  509. }).then(({ data }) => {
  510. this.capability = data.data[0]
  511. this.updateProviders(this.capability)
  512. })
  513. },
  514. updateProviders (capability) {
  515. const brands = capability.brands || []
  516. const providers = Object.entries(K8S_HYPERVISORS_MAP).filter(item => {
  517. const itemProvider = item[1].provider
  518. return brands.includes(itemProvider)
  519. }).map(item => item[1])
  520. this.providers = providers
  521. if (this.providers.length > 0) {
  522. // set default provider
  523. this.provider = this.providers[0]
  524. this.preCheckK8sProvider(this.provider)
  525. }
  526. },
  527. providerChange (e) {
  528. this.provider = e.target.value
  529. this.cloudregionId = ''
  530. this.preCheckK8sProvider(this.provider)
  531. /*
  532. * const type = findPlatform(provider, 'hypervisor')
  533. * let param = {
  534. * usable: true,
  535. * project_domain: this.form.fd.project_domain_id,
  536. * brand: provider.brand,
  537. * }
  538. * switch (type) {
  539. * case 'idc':
  540. * param = { cloud_env: 'onpremise' }
  541. * break
  542. * case 'public':
  543. * param = { cloud_env: 'public' }
  544. * break
  545. * case 'private':
  546. * param = { cloud_env: 'private', show_emulated: true }
  547. * break
  548. * default :
  549. * param = { is_on_premise: true }
  550. * }
  551. * this.params.cloudregion = {
  552. * ...this.params.cloudregion,
  553. * ...param,
  554. * }
  555. */
  556. },
  557. preCheckK8sProvider (provider) {
  558. this.clustersM.performAction({
  559. id: 'pre-check',
  560. action: '',
  561. data: {
  562. provider: this.provider.hypervisor,
  563. resource_type: 'guest',
  564. },
  565. }).then(({ data }) => {
  566. this.preCheckResp = data
  567. this.fetchK8sVersions()
  568. })
  569. },
  570. fetchK8sVersions () {
  571. new this.$Manager('kubeclusters/k8s-versions', 'v1').list({
  572. params: this.params.k8sVersions(),
  573. }).then(({ data }) => {
  574. data.map((item, index) => {
  575. this.k8sVersionOps.push({ key: item, label: item })
  576. })
  577. this.form.fc.setFieldsValue({ version: data[0] })
  578. })
  579. },
  580. isConfigImageChange (val) {
  581. this.isConfigImage = val
  582. },
  583. validator (type) {
  584. return (rule, value, _callback) => {
  585. if (type === 'vcpu_count') {
  586. if (value < 4 || value > 32) {
  587. return _callback(this.$t('k8s.text_172'))
  588. }
  589. } else if (type === 'vmem_size') {
  590. if (value < 4 || value > 128) {
  591. return _callback(this.$t('k8s.text_173'))
  592. }
  593. } else if (type === 'disk') {
  594. if (value < 1 || value > 500) {
  595. return _callback(this.$t('k8s.text_174'))
  596. }
  597. }
  598. return _callback()
  599. }
  600. },
  601. k8sVersionsLabelFormat (val) {
  602. return val.model
  603. },
  604. doCreate (data) {
  605. return new this.$Manager('kubeclusters', 'v1').create({
  606. data,
  607. })
  608. },
  609. genData (data) {
  610. const { provider, version: k8sVersion, name, vpc, cloudregion, sku } = data
  611. const hypervisor = provider.hypervisor
  612. const values = {
  613. project_domain_id: data.project_domain_id,
  614. version: k8sVersion,
  615. machines: [],
  616. name,
  617. resource_type: 'guest',
  618. cloudregion_id: cloudregion.key,
  619. vpc_id: vpc.key,
  620. }
  621. if (data.image_repository_url) {
  622. values.image_repository = {}
  623. values.image_repository.url = data.image_repository_url
  624. }
  625. if (data.image_repository_insecure) values.image_repository.insecure = data.image_repository_insecure
  626. Object.keys(data.sku).map(key => {
  627. const disks = [{
  628. index: 0,
  629. size: data.systemDiskSize[key] * 1024,
  630. backend: data.systemDiskType[key].key,
  631. }]
  632. if (data.systemDiskSchedtag) {
  633. if (data.systemDiskSchedtag[key] && data.systemDiskPolicy[key]) {
  634. disks[0].schedtags = [{ id: data.systemDiskSchedtag[key], strategy: data.systemDiskPolicy[key] }]
  635. }
  636. }
  637. if (hypervisor === HYPERVISORS_MAP.kvm.key || hypervisor === HYPERVISORS_MAP.cloudpods.key) {
  638. const backend = disks[0].backend
  639. disks[0].backend = backend.split('/')[0]
  640. disks[0].medium = backend.split('/')[1]
  641. }
  642. if (data.image && data.image[key]) {
  643. disks[0].image_id = data.image[key].key
  644. }
  645. const machinesItem = {
  646. vm: {
  647. /* vcpu_count: data.vcpu_count[key], */
  648. /* vmem_size: data.vmem_size[key] * 1024, */
  649. instance_type: sku[key].name,
  650. hypervisor,
  651. disks,
  652. nets: [{ network: data.network[key] }],
  653. },
  654. }
  655. if (data.ip && data.ip[key]) machinesItem.vm.nets[0].address = data.ip[key]
  656. if (data.num[key] > 1) {
  657. for (let i = 0; i < data.num[key]; i++) {
  658. values.machines.push({ config: machinesItem, role: data.role[key], resource_type: 'vm' })
  659. }
  660. } else {
  661. values.machines.push({ config: machinesItem, role: data.role[key], resource_type: 'vm' })
  662. }
  663. })
  664. return values
  665. },
  666. async handleConfirm () {
  667. if (!this.providers || !this.providers.length) {
  668. this.$message.error(this.$t('common_468'))
  669. return
  670. }
  671. try {
  672. const values = await this.form.fc.validateFields()
  673. const genValues = this.genData(values)
  674. const hasControl = genValues.machines.some(val => val.role === 'controlplane')
  675. if (!hasControl) {
  676. this.$message.warning(this.$t('k8s.text_175'))
  677. this.loading = false
  678. return
  679. }
  680. this.loading = true
  681. await this.doCreate(genValues)
  682. this.loading = false
  683. this.$router.push('/k8s-cluster')
  684. } catch (error) {
  685. this.loading = false
  686. throw error
  687. }
  688. },
  689. cancel () {
  690. this.$router.push('/k8s-cluster')
  691. },
  692. },
  693. }
  694. </script>