Skip to content

Commit d8ea9be

Browse files
feat: introduce rwxNetwork setting (#746) (#750)
* feat: add rwxNetwork setting * fix: network payload --------- (cherry picked from commit d194964) Signed-off-by: Andy Lee <andy.lee@suse.com> Co-authored-by: Andy Lee <andy.lee@suse.com>
1 parent f8a479c commit d8ea9be

4 files changed

Lines changed: 382 additions & 0 deletions

File tree

Lines changed: 371 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,371 @@
1+
<script>
2+
import { LabeledInput } from '@components/Form/LabeledInput';
3+
import LabeledSelect from '@shell/components/form/LabeledSelect.vue';
4+
import { RadioGroup } from '@components/Form/Radio';
5+
import ArrayList from '@shell/components/form/ArrayList';
6+
import { isValidCIDR } from '@shell/utils/validators/cidr';
7+
import { _EDIT } from '@shell/config/query-params';
8+
import { Banner } from '@components/Banner';
9+
import { allHash } from '@shell/utils/promise';
10+
import { HCI } from '../../types';
11+
import { NETWORK_TYPE } from '../../config/types';
12+
13+
const { L2VLAN, UNTAGGED } = NETWORK_TYPE;
14+
const SHARE_STORAGE_NETWORK = 'share-storage-network';
15+
const NETWORK = 'network';
16+
17+
const DEFAULT_DEDICATED_NETWORK = {
18+
vlan: '',
19+
clusterNetwork: '',
20+
range: '',
21+
exclude: [],
22+
};
23+
24+
export default {
25+
name: 'RwxNetworkSetting',
26+
27+
components: {
28+
RadioGroup,
29+
Banner,
30+
ArrayList,
31+
LabeledInput,
32+
LabeledSelect,
33+
},
34+
35+
props: {
36+
registerBeforeHook: {
37+
type: Function,
38+
required: true,
39+
},
40+
41+
mode: {
42+
type: String,
43+
default: _EDIT,
44+
},
45+
46+
value: {
47+
type: Object,
48+
default: () => {
49+
return {};
50+
},
51+
},
52+
},
53+
54+
async fetch() {
55+
const inStore = this.$store.getters['currentProduct'].inStore;
56+
57+
await allHash({
58+
clusterNetworks: this.$store.dispatch(`${ inStore }/findAll`, { type: HCI.CLUSTER_NETWORK }),
59+
vlanStatus: this.$store.dispatch(`${ inStore }/findAll`, { type: HCI.VLAN_STATUS }),
60+
});
61+
},
62+
63+
data() {
64+
let enabled = false; // enabled / disabled options
65+
let shareStorageNetwork = false; // shareStorageNetwork / dedicatedRwxNetwork options
66+
let dedicatedNetwork = { ...DEFAULT_DEDICATED_NETWORK };
67+
let networkType = L2VLAN;
68+
let exclude = [];
69+
70+
try {
71+
const parsedValue = JSON.parse(this.value.value || this.value.default || '{}');
72+
const parsedNetwork = parsedValue?.[NETWORK] || parsedValue || {};
73+
74+
if (parsedValue && typeof parsedValue === 'object') {
75+
shareStorageNetwork = !!parsedValue[SHARE_STORAGE_NETWORK];
76+
networkType = 'vlan' in parsedNetwork ? L2VLAN : UNTAGGED;
77+
dedicatedNetwork = {
78+
vlan: parsedNetwork.vlan || '',
79+
clusterNetwork: parsedNetwork.clusterNetwork || '',
80+
range: parsedNetwork.range || '',
81+
};
82+
exclude = parsedNetwork?.exclude?.toString().split(',') || [];
83+
enabled = shareStorageNetwork || !!(parsedNetwork.vlan || parsedNetwork.clusterNetwork || parsedNetwork.range);
84+
}
85+
} catch (error) {
86+
enabled = false;
87+
shareStorageNetwork = false;
88+
dedicatedNetwork = { ...DEFAULT_DEDICATED_NETWORK };
89+
}
90+
91+
return {
92+
enabled,
93+
shareStorageNetwork,
94+
dedicatedNetwork,
95+
networkType,
96+
exclude,
97+
defaultAddValue: '',
98+
};
99+
},
100+
101+
created() {
102+
if (this.registerBeforeHook) {
103+
this.registerBeforeHook(this.willSave, 'willSave');
104+
}
105+
},
106+
107+
computed: {
108+
showDedicatedNetworkConfig() {
109+
return this.enabled && !this.shareStorageNetwork;
110+
},
111+
112+
showVlan() {
113+
return this.networkType === L2VLAN;
114+
},
115+
116+
networkTypes() {
117+
return [L2VLAN, UNTAGGED];
118+
},
119+
120+
clusterNetworkOptions() {
121+
const inStore = this.$store.getters['currentProduct'].inStore;
122+
const clusterNetworks = this.$store.getters[`${ inStore }/all`](HCI.CLUSTER_NETWORK) || [];
123+
const clusterNetworksOptions = this.networkType === UNTAGGED ? clusterNetworks.filter((network) => network.id !== 'mgmt') : clusterNetworks;
124+
125+
return clusterNetworksOptions.map((network) => {
126+
const disabled = !network.isReadyForStorageNetwork;
127+
128+
return {
129+
label: disabled ? `${ network.id } (${ this.t('generic.notReady') })` : network.id,
130+
value: network.id,
131+
disabled,
132+
};
133+
});
134+
},
135+
},
136+
137+
methods: {
138+
onUpdateEnabled() {
139+
if (!this.enabled) {
140+
this.shareStorageNetwork = false;
141+
this.dedicatedNetwork = { ...DEFAULT_DEDICATED_NETWORK };
142+
}
143+
144+
this.update();
145+
},
146+
147+
onUpdateNetworkType() {
148+
if (this.shareStorageNetwork) {
149+
this.dedicatedNetwork = { ...DEFAULT_DEDICATED_NETWORK };
150+
}
151+
152+
this.update();
153+
},
154+
155+
onUpdateDedicatedType(neu) {
156+
this.dedicatedNetwork.clusterNetwork = '';
157+
158+
if (neu === L2VLAN) {
159+
this.dedicatedNetwork.vlan = '';
160+
} else {
161+
delete this.dedicatedNetwork.vlan;
162+
}
163+
164+
this.update();
165+
},
166+
167+
inputVlan(neu) {
168+
if (neu === '') {
169+
this.dedicatedNetwork.vlan = '';
170+
this.update();
171+
172+
return;
173+
}
174+
175+
const newValue = Number(neu);
176+
177+
if (newValue > 4094) {
178+
this.dedicatedNetwork.vlan = 4094;
179+
} else if (newValue < 1) {
180+
this.dedicatedNetwork.vlan = 1;
181+
} else {
182+
this.dedicatedNetwork.vlan = newValue;
183+
}
184+
185+
this.update();
186+
},
187+
188+
useDefault() {
189+
this.enabled = false;
190+
this.shareStorageNetwork = false;
191+
this.dedicatedNetwork = { ...DEFAULT_DEDICATED_NETWORK };
192+
this.update();
193+
},
194+
195+
update() {
196+
const value = { [SHARE_STORAGE_NETWORK]: false };
197+
198+
if (this.enabled && this.shareStorageNetwork) {
199+
value[SHARE_STORAGE_NETWORK] = true;
200+
}
201+
202+
if (this.showDedicatedNetworkConfig) {
203+
value[NETWORK] = {};
204+
205+
if (this.showVlan) {
206+
value[NETWORK].vlan = this.dedicatedNetwork.vlan;
207+
}
208+
209+
value[NETWORK].clusterNetwork = this.dedicatedNetwork.clusterNetwork;
210+
value[NETWORK].range = this.dedicatedNetwork.range;
211+
212+
const excludeList = this.exclude.filter((ip) => ip);
213+
214+
if (Array.isArray(excludeList) && excludeList.length > 0) {
215+
value[NETWORK].exclude = excludeList;
216+
}
217+
}
218+
219+
this.value.value = JSON.stringify(value);
220+
},
221+
222+
willSave() {
223+
this.update();
224+
225+
if (!this.showDedicatedNetworkConfig) {
226+
return Promise.resolve();
227+
}
228+
229+
const errors = [];
230+
231+
if (this.showVlan && !this.dedicatedNetwork.vlan) {
232+
errors.push(this.t('validation.required', { key: this.t('harvester.setting.storageNetwork.vlan') }, true));
233+
}
234+
235+
if (!this.dedicatedNetwork.clusterNetwork) {
236+
errors.push(this.t('validation.required', { key: this.t('harvester.setting.storageNetwork.clusterNetwork') }, true));
237+
}
238+
239+
if (!this.dedicatedNetwork.range) {
240+
errors.push(this.t('validation.required', { key: this.t('harvester.setting.storageNetwork.range.label') }, true));
241+
} else if (!isValidCIDR(this.dedicatedNetwork.range)) {
242+
errors.push(this.t('harvester.setting.storageNetwork.range.invalid', null, true));
243+
}
244+
245+
if (this.exclude) {
246+
const hasInvalidCIDR = this.exclude.find((cidr) => {
247+
return cidr && !isValidCIDR(cidr);
248+
});
249+
250+
if (hasInvalidCIDR) {
251+
errors.push(this.t('harvester.setting.storageNetwork.exclude.invalid', null, true));
252+
}
253+
}
254+
255+
if (errors.length > 0) {
256+
return Promise.reject(errors);
257+
}
258+
259+
return Promise.resolve();
260+
},
261+
},
262+
};
263+
</script>
264+
265+
<template>
266+
<div :class="mode">
267+
<Banner color="warning">
268+
<t
269+
k="harvester.setting.rwxNetwork.warning"
270+
:raw="true"
271+
/>
272+
</Banner>
273+
<RadioGroup
274+
v-model:value="enabled"
275+
class="mb-20"
276+
name="rwx-network-enable"
277+
:options="[true,false]"
278+
:labels="[t('generic.enabled'), t('generic.disabled')]"
279+
@update:value="onUpdateEnabled"
280+
/>
281+
282+
<RadioGroup
283+
v-if="enabled"
284+
v-model:value="shareStorageNetwork"
285+
class="mb-20"
286+
name="rwx-network-type"
287+
:options="[true,false]"
288+
:labels="[t('harvester.setting.rwxNetwork.shareStorageNetwork'), t('harvester.setting.rwxNetwork.dedicatedRwxNetwork')]"
289+
@update:value="onUpdateNetworkType"
290+
/>
291+
<Banner
292+
v-if="shareStorageNetwork"
293+
class="mb-20"
294+
color="warning"
295+
>
296+
<t
297+
k="harvester.setting.rwxNetwork.shareStorageNetworkWarning"
298+
:raw="true"
299+
/>
300+
</Banner>
301+
<template v-if="showDedicatedNetworkConfig">
302+
<LabeledSelect
303+
v-model:value="networkType"
304+
class="mb-20"
305+
:options="networkTypes"
306+
:mode="mode"
307+
:label="t('harvester.fields.type')"
308+
required
309+
@update:value="onUpdateDedicatedType"
310+
/>
311+
312+
<LabeledInput
313+
v-if="showVlan"
314+
v-model:value.number="dedicatedNetwork.vlan"
315+
type="number"
316+
class="mb-20"
317+
:mode="mode"
318+
required
319+
placeholder="e.g. 1 - 4094"
320+
label-key="harvester.setting.storageNetwork.vlan"
321+
@update:value="inputVlan"
322+
/>
323+
324+
<LabeledSelect
325+
v-model:value="dedicatedNetwork.clusterNetwork"
326+
label-key="harvester.setting.storageNetwork.clusterNetwork"
327+
class="mb-20"
328+
required
329+
:options="clusterNetworkOptions"
330+
@update:value="update"
331+
/>
332+
333+
<LabeledInput
334+
v-model:value="dedicatedNetwork.range"
335+
class="mb-5"
336+
:mode="mode"
337+
required
338+
:placeholder="t('harvester.setting.storageNetwork.range.placeholder')"
339+
label-key="harvester.setting.storageNetwork.range.label"
340+
@update:value="update"
341+
/>
342+
343+
<ArrayList
344+
v-model:value="exclude"
345+
:show-header="true"
346+
:default-add-value="defaultAddValue"
347+
:mode="mode"
348+
:add-label="t('harvester.setting.storageNetwork.exclude.addIp')"
349+
class="mt-20"
350+
@update:value="update"
351+
>
352+
<template #column-headers>
353+
<div class="box mb-10">
354+
<div class="key">
355+
{{ t('harvester.setting.storageNetwork.exclude.label') }}
356+
</div>
357+
</div>
358+
</template>
359+
<template #columns="scope">
360+
<div class="key">
361+
<input
362+
v-model="scope.row.value"
363+
:placeholder="t('harvester.setting.storageNetwork.exclude.placeholder')"
364+
@update:value="update"
365+
/>
366+
</div>
367+
</template>
368+
</ArrayList>
369+
</template>
370+
</div>
371+
</template>

pkg/harvester/config/feature-flags.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ const FEATURE_FLAGS = {
6464
'clusterRegistrationTLSVerify',
6565
'vGPUAsPCIDevice',
6666
'instanceManagerResourcesSetting',
67+
'rwxNetworkSetting',
6768
],
6869
};
6970

pkg/harvester/config/settings.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ export const HCI_SETTING = {
2020
SUPPORT_BUNDLE_IMAGE: 'support-bundle-image',
2121
SUPPORT_BUNDLE_NODE_COLLECTION_TIMEOUT: 'support-bundle-node-collection-timeout',
2222
STORAGE_NETWORK: 'storage-network',
23+
RWX_NETWORK: 'rwx-network',
2324
VM_FORCE_RESET_POLICY: 'vm-force-reset-policy',
2425
SSL_CERTIFICATES: 'ssl-certificates',
2526
SSL_PARAMETERS: 'ssl-parameters',
@@ -81,6 +82,9 @@ export const HCI_ALLOWED_SETTINGS = {
8182
[HCI_SETTING.STORAGE_NETWORK]: {
8283
kind: 'custom', from: 'import', canReset: true
8384
},
85+
[HCI_SETTING.RWX_NETWORK]: {
86+
kind: 'custom', from: 'import', canReset: true, featureFlag: 'rwxNetworkSetting'
87+
},
8488
[HCI_SETTING.VM_FORCE_RESET_POLICY]: { kind: 'json', from: 'import' },
8589
[HCI_SETTING.SSL_CERTIFICATES]: { kind: 'json', from: 'import' },
8690
[HCI_SETTING.SSL_PARAMETERS]: {

0 commit comments

Comments
 (0)