diff --git a/pkg/harvester/config/feature-flags.js b/pkg/harvester/config/feature-flags.js
index 7e8a1f00..ee702552 100644
--- a/pkg/harvester/config/feature-flags.js
+++ b/pkg/harvester/config/feature-flags.js
@@ -42,7 +42,8 @@ const FEATURE_FLAGS = {
'vmMachineTypes',
'customSupportBundle',
'csiOnlineExpandValidation',
- 'vmNetworkMigration'
+ 'vmNetworkMigration',
+ 'kubeovnVpcSubnet'
]
};
diff --git a/pkg/harvester/config/harvester-cluster.js b/pkg/harvester/config/harvester-cluster.js
index b7dba1ad..eb32c92d 100644
--- a/pkg/harvester/config/harvester-cluster.js
+++ b/pkg/harvester/config/harvester-cluster.js
@@ -424,6 +424,7 @@ export function init($plugin, store) {
[
HCI.CLUSTER_NETWORK,
HCI.NETWORK_ATTACHMENT,
+ HCI.VPC,
HCI.LB,
HCI.IP_POOL,
],
@@ -550,6 +551,21 @@ export function init($plugin, store) {
exact: false
});
+ configureType(HCI.VPC, { hiddenNamespaceGroupButton: true, canYaml: false });
+
+ virtualType({
+ labelKey: 'harvester.vpc.label',
+ name: HCI.VPC,
+ namespaced: true,
+ weight: 187,
+ route: {
+ name: `${ PRODUCT_NAME }-c-cluster-resource`,
+ params: { resource: HCI.VPC }
+ },
+ exact: false,
+ ifHaveType: HCI.VPC,
+ });
+
configureType(HCI.SNAPSHOT, {
isCreatable: false,
location: {
diff --git a/pkg/harvester/config/harvester-map.js b/pkg/harvester/config/harvester-map.js
index 75f2b5ad..cef1d5f2 100644
--- a/pkg/harvester/config/harvester-map.js
+++ b/pkg/harvester/config/harvester-map.js
@@ -70,6 +70,7 @@ export const ADD_ONS = {
RANCHER_MONITORING: 'rancher-monitoring',
VM_IMPORT_CONTROLLER: 'vm-import-controller',
LVM_DRIVER: 'lvm.driver.harvesterhci.io',
+ KUBEOVN_OPERATOR: 'kubeovn-operator',
};
export const CSI_SECRETS = {
diff --git a/pkg/harvester/config/query-params.js b/pkg/harvester/config/query-params.js
index 6c56a44c..5faf9f63 100644
--- a/pkg/harvester/config/query-params.js
+++ b/pkg/harvester/config/query-params.js
@@ -1 +1,2 @@
export const CLUSTER_NETWORK = 'clusterNetwork';
+export const VPC = 'vpc';
diff --git a/pkg/harvester/config/table-headers.js b/pkg/harvester/config/table-headers.js
index 2dc69822..b753aa5b 100644
--- a/pkg/harvester/config/table-headers.js
+++ b/pkg/harvester/config/table-headers.js
@@ -104,3 +104,30 @@ export const HARVESTER_DESCRIPTION = {
...DESCRIPTION,
width: 150,
};
+
+// The CIDR_BLOCK column in VPC list page
+export const CIDR_BLOCK = {
+ name: 'cidrBlock',
+ labelKey: 'harvester.subnet.cidrBlock.label',
+ sort: 'cidrBlock',
+ value: 'spec.cidrBlock',
+ align: 'left',
+};
+
+// The Protocol column in VPC list page
+export const PROTOCOL = {
+ name: 'protocol',
+ labelKey: 'harvester.subnet.protocol.label',
+ sort: 'protocol',
+ value: 'spec.protocol',
+ align: 'left',
+};
+
+// The Provider column in VPC list page
+export const PROVIDER = {
+ name: 'provider',
+ labelKey: 'harvester.subnet.provider.label',
+ sort: 'provider',
+ value: 'spec.provider',
+ align: 'left',
+};
diff --git a/pkg/harvester/config/types.js b/pkg/harvester/config/types.js
index 552f9361..b336ca4a 100644
--- a/pkg/harvester/config/types.js
+++ b/pkg/harvester/config/types.js
@@ -5,10 +5,16 @@ export const BACKUP_TYPE = {
export const NETWORK_TYPE = {
L2VLAN: 'L2VlanNetwork',
- UNTAGGED: 'UntaggedNetwork'
+ UNTAGGED: 'UntaggedNetwork',
+ OVERLAY: 'OverlayNetwork',
};
export const VOLUME_MODE = {
BLOCK: 'Block',
FILE_SYSTEM: 'Filesystem'
};
+
+export const NETWORK_PROTOCOL = {
+ IPv4: 'IPv4',
+ IPv6: 'IPv6',
+};
diff --git a/pkg/harvester/edit/harvesterhci.io.networkattachmentdefinition.vue b/pkg/harvester/edit/harvesterhci.io.networkattachmentdefinition.vue
index b362ec63..3b03d562 100644
--- a/pkg/harvester/edit/harvesterhci.io.networkattachmentdefinition.vue
+++ b/pkg/harvester/edit/harvesterhci.io.networkattachmentdefinition.vue
@@ -6,14 +6,13 @@ import { LabeledInput } from '@components/Form/LabeledInput';
import { RadioGroup } from '@components/Form/Radio';
import NameNsDescription from '@shell/components/form/NameNsDescription';
import LabeledSelect from '@shell/components/form/LabeledSelect';
-
import { HCI as HCI_LABELS_ANNOTATIONS } from '@pkg/harvester/config/labels-annotations';
import CreateEditView from '@shell/mixins/create-edit-view';
import { allHash } from '@shell/utils/promise';
import { HCI } from '../types';
import { NETWORK_TYPE } from '../config/types';
-const { L2VLAN, UNTAGGED } = NETWORK_TYPE;
+const { L2VLAN, UNTAGGED, OVERLAY } = NETWORK_TYPE;
const AUTO = 'auto';
const MANUAL = 'manual';
@@ -44,13 +43,10 @@ export default {
data() {
const config = JSON.parse(this.value.spec.config);
+
const annotations = this.value?.metadata?.annotations || {};
const layer3Network = JSON.parse(annotations[HCI_LABELS_ANNOTATIONS.NETWORK_ROUTE] || '{}');
- if ((config.bridge || '').endsWith('-br')) {
- config.bridge = config.bridge.slice(0, -3);
- }
-
const type = this.value.vlanType || L2VLAN ;
return {
@@ -78,6 +74,30 @@ export default {
},
computed: {
+ clusterBridge: {
+ get() {
+ if (!this.config.bridge) {
+ return '';
+ }
+
+ // remove -br suffix if exists
+ return this.config?.bridge?.endsWith('-br') ? this.config.bridge.slice(0, -3) : '';
+ },
+
+ set(neu) {
+ if (neu === '') {
+ this.config.bridge = '';
+
+ return;
+ }
+
+ if (!neu.endsWith('-br')) {
+ this.config.bridge = `${ neu }-br`;
+ } else {
+ this.config.bridge = neu;
+ }
+ }
+ },
modeOptions() {
return [{
label: this.t('harvester.network.layer3Network.mode.auto'),
@@ -88,6 +108,14 @@ export default {
}];
},
+ kubeovnVpcSubnetSupport() {
+ return this.$store.getters['harvester-common/getFeatureEnabled']('kubeovnVpcSubnet');
+ },
+
+ longhornV2LVMSupport() {
+ return this.$store.getters['harvester-common/getFeatureEnabled']('longhornV2LVMSupport');
+ },
+
clusterNetworkOptions() {
const inStore = this.$store.getters['currentProduct'].inStore;
const clusterNetworks = this.$store.getters[`${ inStore }/all`](HCI.CLUSTER_NETWORK) || [];
@@ -103,8 +131,30 @@ export default {
});
},
- networkType() {
- return [L2VLAN, UNTAGGED];
+ networkTypes() {
+ const types = [L2VLAN, UNTAGGED];
+
+ if (this.kubeovnVpcSubnetSupport) {
+ types.push(OVERLAY);
+ }
+
+ return types;
+ },
+
+ isL2VlanNetwork() {
+ if (this.isView) {
+ return this.value.vlanType === L2VLAN;
+ }
+
+ return this.type === L2VLAN;
+ },
+
+ isOverlayNetwork() {
+ if (this.isView) {
+ return this.value.vlanType === OVERLAY;
+ }
+
+ return this.type === OVERLAY;
},
isUntaggedNetwork() {
@@ -116,36 +166,54 @@ export default {
}
},
+ watch: {
+ type(newType) {
+ if (newType === OVERLAY) {
+ this.config.type = 'kube-ovn';
+ this.config.provider = `${ this.value.metadata.name }.${ this.value.metadata.namespace }.ovn`;
+ this.config.server_socket = '/run/openvswitch/kube-ovn-daemon.sock';
+ } else {
+ this.config.type = 'bridge';
+ this.config.promiscMode = true;
+ this.config.ipam = {};
+ this.config.bridge = '';
+ delete this.config.provider;
+ delete this.config.server_socket;
+ }
+ }
+ },
+
methods: {
async saveNetwork(buttonCb) {
const errors = [];
- if (!this.config.vlan && !this.isUntaggedNetwork) {
- errors.push(this.$store.getters['i18n/t']('validation.required', { key: this.t('tableHeaders.networkVlan') }));
- }
-
- if (!this.config.bridge) {
- errors.push(this.$store.getters['i18n/t']('validation.required', { key: this.t('harvester.network.clusterNetwork.label') }));
- }
-
- if (this.layer3Network.mode === MANUAL) {
- if (!this.layer3Network.gateway) {
- errors.push(this.$store.getters['i18n/t']('validation.required', { key: this.t('harvester.network.layer3Network.gateway.label') }));
+ if (this.isL2VlanNetwork || this.isUntaggedNetwork) {
+ if (!this.config.vlan && !this.isUntaggedNetwork) {
+ errors.push(this.$store.getters['i18n/t']('validation.required', { key: this.t('tableHeaders.networkVlan') }));
}
- if (!this.layer3Network.cidr) {
- errors.push(this.$store.getters['i18n/t']('validation.required', { key: this.t('harvester.network.layer3Network.cidr.label') }));
+
+ if (!this.config.bridge) {
+ errors.push(this.$store.getters['i18n/t']('validation.required', { key: this.t('harvester.network.clusterNetwork.label') }));
}
+
+ if (this.layer3Network.mode === MANUAL) {
+ if (!this.layer3Network.gateway) {
+ errors.push(this.$store.getters['i18n/t']('validation.required', { key: this.t('harvester.network.layer3Network.gateway.label') }));
+ }
+ if (!this.layer3Network.cidr) {
+ errors.push(this.$store.getters['i18n/t']('validation.required', { key: this.t('harvester.network.layer3Network.cidr.label') }));
+ }
+ }
+
+ if (errors.length > 0) {
+ buttonCb(false);
+ this.errors = errors;
+
+ return false;
+ }
+ this.value.setAnnotation(HCI_LABELS_ANNOTATIONS.NETWORK_ROUTE, JSON.stringify(this.layer3Network));
}
- if (errors.length > 0) {
- buttonCb(false);
- this.errors = errors;
-
- return false;
- }
-
- this.value.setAnnotation(HCI_LABELS_ANNOTATIONS.NETWORK_ROUTE, JSON.stringify(this.layer3Network));
-
await this.save(buttonCb);
},
@@ -169,14 +237,19 @@ export default {
updateBeforeSave() {
this.config.name = this.value.metadata.name;
+ if (this.isOverlayNetwork) {
+ this.config.provider = `${ this.value.metadata.name }.${ this.value.metadata.namespace }.ovn`;
+ delete this.config.bridge;
+ delete this.config.promiscMode;
+ delete this.config.vlan;
+ delete this.config.ipam;
+ }
+
if (this.isUntaggedNetwork) {
delete this.config.vlan;
}
- this.value.spec.config = JSON.stringify({
- ...this.config,
- bridge: `${ this.config.bridge }-br`,
- });
+ this.value.spec.config = JSON.stringify({ ...this.config });
},
}
};
@@ -212,14 +285,15 @@ export default {