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}}
+
-
-
-
-
+ {{/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