From 5f59400e9e3bb8be383bd6d07ab163e26903b48c Mon Sep 17 00:00:00 2001 From: wujun <897415845@qq.com> Date: Tue, 10 Jan 2023 16:43:30 +0800 Subject: [PATCH] support multi nic --- .../components/boot-order-select/component.js | 40 +++ .../components/boot-order-select/template.hbs | 6 + .../components/driver-harvester/component.js | 228 +++++++++++++++--- .../components/driver-harvester/template.hbs | 182 +++++++++++--- .../components/boot-order-select/component.js | 1 + lib/shared/addon/helpers/has-property.js | 7 + lib/shared/app/helpers/has-property.js | 1 + translations/en-us.yaml | 22 ++ 8 files changed, 418 insertions(+), 69 deletions(-) create mode 100644 lib/nodes/addon/components/boot-order-select/component.js create mode 100644 lib/nodes/addon/components/boot-order-select/template.hbs create mode 100644 lib/nodes/app/components/boot-order-select/component.js create mode 100644 lib/shared/addon/helpers/has-property.js create mode 100644 lib/shared/app/helpers/has-property.js diff --git a/lib/nodes/addon/components/boot-order-select/component.js b/lib/nodes/addon/components/boot-order-select/component.js new file mode 100644 index 000000000..05996b081 --- /dev/null +++ b/lib/nodes/addon/components/boot-order-select/component.js @@ -0,0 +1,40 @@ +import Component from '@ember/component'; +import layout from './template'; +import { inject as service } from '@ember/service' +import { get, computed } from '@ember/object'; + +export default Component.extend({ + scope: service(), + session: service(), + + layout, + model: null, + idx: '', + disk: {}, + disks: [], + + bootOrderContent: computed('disk.bootOrder', 'disks.@each.bootOrder', 'idx', function() { + let bootOrderContent = []; + const bootOrder = get(this, 'disk').bootOrder; + const bootOrders = get(this, 'disks').map((disk) => { + return disk.bootOrder; + }) + + const disks = get(this, 'disks') || []; + const idx = get(this, 'idx'); + + for (let i = 0; i < disks.length + 1; i++) { + if (!bootOrders.includes(i) || i === 0 || i === bootOrder) { + if (!(idx === 0 && i === 0)) { + bootOrderContent.push({ + label: i === 0 ? 'N/A' : `${ i }`, + value: i + }); + } + } + } + + + return bootOrderContent + }), +}); diff --git a/lib/nodes/addon/components/boot-order-select/template.hbs b/lib/nodes/addon/components/boot-order-select/template.hbs new file mode 100644 index 000000000..9decc115c --- /dev/null +++ b/lib/nodes/addon/components/boot-order-select/template.hbs @@ -0,0 +1,6 @@ + \ No newline at end of file diff --git a/lib/nodes/addon/components/driver-harvester/component.js b/lib/nodes/addon/components/driver-harvester/component.js index 8c1c71e24..dc576c879 100644 --- a/lib/nodes/addon/components/driver-harvester/component.js +++ b/lib/nodes/addon/components/driver-harvester/component.js @@ -48,22 +48,26 @@ export default Component.extend(NodeDriver, { driverName: DRIVER, model: {}, - currentCluster: null, - clusters: [], - clusterContent: [], - imageContent: [], - networkContent: [], - namespaceContent: [], - nodes: [], - namespaces: [], - nodeSchedulings: [], - podSchedulings: [], - networkDataContent: [], - userDataContent: [], - controller: null, - signal: '', - isImportMode: true, - loading: false, + currentCluster: null, + clusters: [], + clusterContent: [], + imageContent: [], + networkContent: [], + namespaceContent: [], + nodes: [], + namespaces: [], + nodeSchedulings: [], + podSchedulings: [], + networkDataContent: [], + storageClassContent: [], + defaultStorageClass: '', + userDataContent: [], + controller: null, + signal: '', + isImportMode: true, + loading: false, + disks: [], + interfaces: [], config: alias(`model.${ CONFIG }`), @@ -78,6 +82,10 @@ export default Component.extend(NodeDriver, { if (!!get(this, 'config.vmAffinity')) { this.initSchedulings(); } + + this.initDisks() + + this.initInterfaces() }, actions: { @@ -99,10 +107,47 @@ export default Component.extend(NodeDriver, { this.get('nodeSchedulings').pushObject(neu); }, + addVolume(type) { + let neu = {} + + if (type === 'volume') { + neu = { + storageClassName: get(this, 'defaultStorageClass'), + size: 10, + bootOrder: 0 + }; + } else if (type === 'image') { + neu = { + imageName: '', + size: 40, + bootOrder: 0 + }; + } + + this.get('disks').pushObject(neu); + }, + + addNetwork() { + const neu = { + networkName: '', + macAddress: '' + } + + this.get('interfaces').pushObject(neu); + }, + removeNodeScheduling(scheduling) { this.get('nodeSchedulings').removeObject(scheduling); }, + removeDisk(disk) { + this.get('disks').removeObject(disk); + }, + + removeNetwork(network) { + this.get('interfaces').removeObject(network); + }, + updateNodeScheduling() { this.parseNodeScheduling(); }, @@ -124,7 +169,7 @@ export default Component.extend(NodeDriver, { updatePodScheduling() { this.parsePodScheduling(); - } + }, }, clearData: observer('currentCredential.id', function() { @@ -135,6 +180,31 @@ export default Component.extend(NodeDriver, { set(this, 'podSchedulings', []); set(this, 'vmAffinity', {}); set(this, 'config.vmAffinity', ''); + set(this, 'config.diskInfo', ''); + set(this, 'config.networkInfo', ''); + + this.initDisks() + + this.initInterfaces() + }), + + setDiskInfo: observer('disks.@each.{imageName,bootOrder,storageClassName,size}', function() { + const diskInfo = { + disks: get(this, 'disks').map((disk) => { + return { + ...disk, + size: Number(disk.size), + }; + }) + }; + + set(this, 'config.diskInfo', JSON.stringify(diskInfo)); + }), + + setNetworkInfo: observer('interfaces.@each.{networkName,macAddress}', function() { + const networkInfo = { interfaces: get(this, 'interfaces') }; + + set(this, 'config.networkInfo', JSON.stringify(networkInfo)) }), nodeSchedulingsChanged: observer('nodeSchedulings.[]', function() { @@ -256,10 +326,15 @@ export default Component.extend(NodeDriver, { }) const storageClass = resp.storageClass.body.data || []; - const storageClassContent = storageClass.map((s) => { + let defaultStorageClass = ''; + const storageClassContent = storageClass.filter((s) => !s.parameters?.backingImage).map((s) => { const isDefault = s.metadata?.annotations?.['storageclass.kubernetes.io/is-default-class'] === 'true'; const label = isDefault ? `${ s.metadata.name } (${ this.intl.t('generic.default') })` : s.metadata.name; + if (isDefault) { + defaultStorageClass = s.metadata.name; + } + return { label, value: s.metadata.name, @@ -272,7 +347,8 @@ export default Component.extend(NodeDriver, { namespaceContent, userDataContent, networkDataContent, - storageClassContent + storageClassContent, + defaultStorageClass }); }).catch((err) => { setProperties(this, { @@ -339,7 +415,9 @@ export default Component.extend(NodeDriver, { networkData: '', vmNamespace: '', userData: '', - vmAffinity: '' + vmAffinity: '', + diskInfo: '', + networkInfo: '' }); set(this, `model.${ CONFIG }`, config); @@ -357,24 +435,14 @@ export default Component.extend(NodeDriver, { errors.push(this.intl.t('generic.required', { key: this.intl.t('nodeDriver.harvester.namespace.label') })); } - if (!get(this, 'config.diskBus')) { - errors.push(this.intl.t('generic.required', { key: this.intl.t('nodeDriver.harvester.diskBus.label') })); - } - - if (!get(this, 'config.imageName')) { - errors.push(this.intl.t('generic.required', { key: this.intl.t('nodeDriver.harvester.imageName.label') })); - } - - if (!get(this, 'config.networkName')) { - errors.push(this.intl.t('generic.required', { key: this.intl.t('nodeDriver.harvester.networkName.label') })); - } - if (!get(this, 'config.sshUser')) { errors.push(this.intl.t('generic.required', { key: this.intl.t('nodeDriver.harvester.sshUser.label') })); } this.validateScheduling(errors); + this.validateDiskAndNetwork(errors); + // Set the array of errors for display, // and return true if saving should continue. @@ -411,6 +479,10 @@ export default Component.extend(NodeDriver, { && Object.getPrototypeOf(obj) === Object.prototype; }, + isImageVolume(volume) { + return Object.prototype.hasOwnProperty.call(volume, 'imageName'); + }, + initSchedulings() { const nodeSchedulings = []; const podSchedulings = []; @@ -496,6 +568,59 @@ export default Component.extend(NodeDriver, { set(this, 'podSchedulings', podSchedulings); }, + initDisks() { + let disks = []; + + if (!get(this, 'config.diskInfo')) { + const imageName = get(this, 'config.imageName') || ''; + + disks = [{ + imageName, + bootOrder: 1, + size: 40, + }]; + + if (get(this, 'config.diskBus')) { + disks[0].bus = get(this, 'config.diskBus'); + } + + const diskInfo = JSON.stringify({ disks }); + + set(this, 'config.diskInfo', diskInfo); + } else { + const diskInfo = get(this, 'config.diskInfo'); + + disks = JSON.parse(diskInfo).disks || []; + } + set(this, 'disks', disks); + }, + + initInterfaces() { + let _interfaces = []; + + if (!get(this, 'config.networkInfo')) { + const networkName = get(this, 'config.networkName') || ''; + + _interfaces = [{ + networkName, + macAddress: '', + }]; + + if (get(this, 'config.networkModel')) { + _interfaces[0].model = get(this, 'config.networkModel'); + } + + const networkInfo = JSON.stringify({ interfaces: _interfaces }); + + set(this, 'config.networkInfo', networkInfo); + } else { + const networkInfo = get(this, 'config.networkInfo'); + + _interfaces = JSON.parse(networkInfo).interfaces || []; + } + set(this, 'interfaces', _interfaces); + }, + parseNodeScheduling() { const arr = this.nodeSchedulings; const out = {}; @@ -633,6 +758,43 @@ export default Component.extend(NodeDriver, { if (nodeHasMissingKey || podHasMissingKey) { errors.push(this.intl.t('generic.required', { key: this.intl.t('formNodeRequirement.key.label') })); } - } + }, + isValidMac(value) { + return /^[A-Fa-f0-9]{2}(-[A-Fa-f0-9]{2}){5}$|^[A-Fa-f0-9]{2}(:[A-Fa-f0-9]{2}){5}$/.test(value); + }, + + validateDiskAndNetwork(errors) { + const disks = get(this, 'disks'); + + disks.forEach((disk) => { + if (Object.prototype.hasOwnProperty.call(disk, 'imageName') && !disk.imageName) { + errors.push(this.intl.t('generic.required', { key: this.intl.t('nodeDriver.harvester.imageName.label') })); + } + + if (Object.prototype.hasOwnProperty.call(disk, 'storageClassName') && !disk.storageClassName) { + errors.push(this.intl.t('generic.required', { key: this.intl.t('nodeDriver.harvester.storageClass.label') })); + } + + if (!disk.size) { + errors.push(this.intl.t('generic.required', { key: this.intl.t('nodeDriver.harvester.diskSize.label') })); + } + + if (!disk.size) { + errors.push(this.intl.t('generic.required', { key: this.intl.t('nodeDriver.harvester.diskSize.label') })); + } + }); + + const interfaces = get(this, 'interfaces'); + + interfaces.forEach((_interface) => { + if (!_interface.networkName) { + errors.push(this.intl.t('generic.required', { key: this.intl.t('nodeDriver.harvester.networkName.label') })); + } + + if (_interface.macAddress && !this.isValidMac(_interface.macAddress)) { + errors.push(this.intl.t('generic.required', { key: this.intl.t('nodeDriver.harvester.network.macFormat') })); + } + }); + } }); diff --git a/lib/nodes/addon/components/driver-harvester/template.hbs b/lib/nodes/addon/components/driver-harvester/template.hbs index cd797c2df..3d2692078 100644 --- a/lib/nodes/addon/components/driver-harvester/template.hbs +++ b/lib/nodes/addon/components/driver-harvester/template.hbs @@ -114,49 +114,159 @@
-
- - -
+ +
+ {{#each disks as |disk idx|}} +
+
+
+ {{#if (has-property disk 'imageName') }} +

{{t 'nodeDriver.harvester.disks.diskType.imageVolume'}}

+ {{else}} +

{{t 'nodeDriver.harvester.disks.diskType.volume'}}

+ {{/if}} +
+
+ +
+
+ +
+
+ {{#if (has-property disk 'imageName') }} + + + {{else}} + + + {{/if}} +
-
- -
- {{input-integer - min=1 - value=config.diskSize - classNames="form-control" - placeholder=(t "nodeDriver.harvester.diskSize.placeholder") - }} -
- {{t "nodeDriver.harvester.diskSize.unit"}} +
+ + + {{boot-order-select disk=disk disks=disks idx=idx}} +
+
+ +
+
+ +
+ {{input-integer + min=1 + value=disk.size + classNames="form-control" + placeholder=(t "nodeDriver.harvester.diskSize.placeholder") + }} +
+ {{t "nodeDriver.harvester.diskSize.unit"}} +
+
-
+ {{/each}} + + + + + +
-
- - -
+
+ {{#each interfaces as |network idx|}} +
+
+
+

{{t "nodeDriver.harvester.network.label"}}

+
+ +
+ +
+
+ +
+
+ + +
+ +
+ + {{input + type="text" + value=network.macAddress + classNames="form-control" + }} +
+
+
+ {{/each}} + + {{/if}} {{/accordion-list-item}} diff --git a/lib/nodes/app/components/boot-order-select/component.js b/lib/nodes/app/components/boot-order-select/component.js new file mode 100644 index 000000000..4c99e83d4 --- /dev/null +++ b/lib/nodes/app/components/boot-order-select/component.js @@ -0,0 +1 @@ +export { default } from 'nodes/components/boot-order-select/component'; diff --git a/lib/shared/addon/helpers/has-property.js b/lib/shared/addon/helpers/has-property.js new file mode 100644 index 000000000..1dba86534 --- /dev/null +++ b/lib/shared/addon/helpers/has-property.js @@ -0,0 +1,7 @@ +import { helper } from '@ember/component/helper'; + +export function hasProperty(params) { + return Object.prototype.hasOwnProperty.call(params[0], params[1]); +} + +export default helper(hasProperty); diff --git a/lib/shared/app/helpers/has-property.js b/lib/shared/app/helpers/has-property.js new file mode 100644 index 000000000..ec5895190 --- /dev/null +++ b/lib/shared/app/helpers/has-property.js @@ -0,0 +1 @@ +export { default } from 'shared/helpers/has-property'; diff --git a/translations/en-us.yaml b/translations/en-us.yaml index 7e805ff01..1d4e61b35 100644 --- a/translations/en-us.yaml +++ b/translations/en-us.yaml @@ -9773,6 +9773,9 @@ nodeDriver: imageName: label: Image placeholder: Please select a image + storageClass: + label: Storage Class + placeholder: Please select a storageClass diskBus: label: Bus access: @@ -9796,6 +9799,9 @@ nodeDriver: label: SSH User placeholder: e.g. ubuntu toolTip: SSH user to login with the selected OS image. + macAddress: + label: Mac Address + toolTip: MAC address as seen inside the guest system. networkName: label: Network Name placeholder: Please select a network @@ -9812,6 +9818,22 @@ nodeDriver: namespace: label: Namespace placeholder: e.g. default + disks: + title: Volumes + diskType: + volume: Volume + imageVolume: Image Volume + bootOrder: + label: bootOrder + addVolume: + volume: Add Volume + imageVolume: Add Image Volume + network: + title: Networks + label: Network + macFormat: 'Invalid MAC address format.' + addNetwork: + network: Add Network diskSize: label: Disk unit: GiB