diff --git a/modules/api/cmd/kubermatic-api/swagger.json b/modules/api/cmd/kubermatic-api/swagger.json
index cdf2a31e66..ae09ebfda6 100644
--- a/modules/api/cmd/kubermatic-api/swagger.json
+++ b/modules/api/cmd/kubermatic-api/swagger.json
@@ -41488,6 +41488,13 @@
"description": "VMwareCloudDirectorNodeSpec VMware Cloud Director node settings",
"type": "object",
"properties": {
+ "additionalNetworks": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "x-go-name": "AdditionalNetworks"
+ },
"catalog": {
"type": "string",
"x-go-name": "Catalog"
diff --git a/modules/api/pkg/api/v1/types.go b/modules/api/pkg/api/v1/types.go
index 68ee2c698b..1a0b742e9a 100644
--- a/modules/api/pkg/api/v1/types.go
+++ b/modules/api/pkg/api/v1/types.go
@@ -2575,19 +2575,20 @@ func (spec *OpenNebulaNodeSpec) MarshalJSON() ([]byte, error) {
// VMwareCloudDirectorNodeSpec VMware Cloud Director node settings
// swagger:model VMwareCloudDirectorNodeSpec
type VMwareCloudDirectorNodeSpec struct {
- CPUs int `json:"cpus"`
- CPUCores int `json:"cpuCores"`
- MemoryMB int `json:"memoryMB"`
- DiskSizeGB *int64 `json:"diskSizeGB,omitempty"`
- DiskIOPS *int64 `json:"diskIOPS,omitempty"`
- Template string `json:"template"`
- Catalog string `json:"catalog"`
- StorageProfile string `json:"storageProfile"`
- IPAllocationMode vmwareclouddirector.IPAllocationMode `json:"ipAllocationMode,omitempty"`
- VApp string `json:"vapp,omitempty"`
- Network string `json:"network,omitempty"`
- PlacementPolicy *string `json:"placementPolicy,omitempty"`
- SizingPolicy *string `json:"sizingPolicy,omitempty"`
+ CPUs int `json:"cpus"`
+ CPUCores int `json:"cpuCores"`
+ MemoryMB int `json:"memoryMB"`
+ DiskSizeGB *int64 `json:"diskSizeGB,omitempty"`
+ DiskIOPS *int64 `json:"diskIOPS,omitempty"`
+ Template string `json:"template"`
+ Catalog string `json:"catalog"`
+ StorageProfile string `json:"storageProfile"`
+ IPAllocationMode vmwareclouddirector.IPAllocationMode `json:"ipAllocationMode,omitempty"`
+ VApp string `json:"vapp,omitempty"`
+ Network string `json:"network,omitempty"`
+ AdditionalNetworks []string `json:"additionalNetworks,omitempty"`
+ PlacementPolicy *string `json:"placementPolicy,omitempty"`
+ SizingPolicy *string `json:"sizingPolicy,omitempty"`
// Additional metadata to set
// required: false
Metadata map[string]string `json:"metadata,omitempty"`
@@ -2629,35 +2630,37 @@ func (spec *VMwareCloudDirectorNodeSpec) MarshalJSON() ([]byte, error) {
}
res := struct {
- CPUs int `json:"cpus"`
- CPUCores int `json:"cpuCores"`
- MemoryMB int `json:"memoryMB"`
- DiskSizeGB *int64 `json:"diskSizeGB,omitempty"`
- DiskIOPS *int64 `json:"diskIOPS,omitempty"`
- Catalog string `json:"catalog"`
- Template string `json:"template"`
- StorageProfile string `json:"storageProfile,omitempty"`
- IPAllocationMode vmwareclouddirector.IPAllocationMode `json:"ipAllocationMode,omitempty"`
- VApp string `json:"vapp,omitempty"`
- Network string `json:"network,omitempty"`
- Metadata map[string]string `json:"metadata,omitempty"`
- PlacementPolicy *string `json:"placementPolicy,omitempty"`
- SizingPolicy *string `json:"sizingPolicy,omitempty"`
+ CPUs int `json:"cpus"`
+ CPUCores int `json:"cpuCores"`
+ MemoryMB int `json:"memoryMB"`
+ DiskSizeGB *int64 `json:"diskSizeGB,omitempty"`
+ DiskIOPS *int64 `json:"diskIOPS,omitempty"`
+ Catalog string `json:"catalog"`
+ Template string `json:"template"`
+ StorageProfile string `json:"storageProfile,omitempty"`
+ IPAllocationMode vmwareclouddirector.IPAllocationMode `json:"ipAllocationMode,omitempty"`
+ VApp string `json:"vapp,omitempty"`
+ Network string `json:"network,omitempty"`
+ AdditionalNetworks []string `json:"additionalNetworks,omitempty"`
+ Metadata map[string]string `json:"metadata,omitempty"`
+ PlacementPolicy *string `json:"placementPolicy,omitempty"`
+ SizingPolicy *string `json:"sizingPolicy,omitempty"`
}{
- CPUs: spec.CPUs,
- CPUCores: spec.CPUCores,
- MemoryMB: spec.MemoryMB,
- DiskSizeGB: spec.DiskSizeGB,
- DiskIOPS: spec.DiskIOPS,
- Catalog: spec.Catalog,
- Template: spec.Template,
- StorageProfile: spec.StorageProfile,
- IPAllocationMode: spec.IPAllocationMode,
- VApp: spec.VApp,
- Network: spec.Network,
- Metadata: spec.Metadata,
- PlacementPolicy: spec.PlacementPolicy,
- SizingPolicy: spec.SizingPolicy,
+ CPUs: spec.CPUs,
+ CPUCores: spec.CPUCores,
+ MemoryMB: spec.MemoryMB,
+ DiskSizeGB: spec.DiskSizeGB,
+ DiskIOPS: spec.DiskIOPS,
+ Catalog: spec.Catalog,
+ Template: spec.Template,
+ StorageProfile: spec.StorageProfile,
+ IPAllocationMode: spec.IPAllocationMode,
+ VApp: spec.VApp,
+ Network: spec.Network,
+ AdditionalNetworks: spec.AdditionalNetworks,
+ Metadata: spec.Metadata,
+ PlacementPolicy: spec.PlacementPolicy,
+ SizingPolicy: spec.SizingPolicy,
}
return json.Marshal(&res)
diff --git a/modules/api/pkg/machine/convert.go b/modules/api/pkg/machine/convert.go
index 91818e52a7..92997e918b 100644
--- a/modules/api/pkg/machine/convert.go
+++ b/modules/api/pkg/machine/convert.go
@@ -257,17 +257,32 @@ func GetAPIV2NodeCloudSpec(machineSpec clusterv1alpha1.MachineSpec) (*apiv1.Node
if err := json.Unmarshal(decodedProviderSpec.CloudProviderSpec.Raw, &config); err != nil {
return nil, fmt.Errorf("failed to parse VMWare Cloud Director config: %w", err)
}
+
+ var networks []string
+
+ for _, network := range config.Networks {
+ networks = append(networks, network.Value)
+ }
+
+ network := config.Network.Value
+
+ if len(networks) > 0 {
+ network = networks[0]
+ networks = networks[1:]
+ }
+
cloudSpec.VMwareCloudDirector = &apiv1.VMwareCloudDirectorNodeSpec{
- CPUs: int(config.CPUs),
- CPUCores: int(config.CPUCores),
- MemoryMB: int(config.MemoryMB),
- DiskSizeGB: config.DiskSizeGB,
- Template: config.Template.Value,
- Catalog: config.Catalog.Value,
- DiskIOPS: config.DiskIOPS,
- VApp: config.VApp.Value,
- Network: config.Network.Value,
- IPAllocationMode: config.IPAllocationMode,
+ CPUs: int(config.CPUs),
+ CPUCores: int(config.CPUCores),
+ MemoryMB: int(config.MemoryMB),
+ DiskSizeGB: config.DiskSizeGB,
+ Template: config.Template.Value,
+ Catalog: config.Catalog.Value,
+ DiskIOPS: config.DiskIOPS,
+ VApp: config.VApp.Value,
+ Network: network,
+ AdditionalNetworks: networks,
+ IPAllocationMode: config.IPAllocationMode,
}
if config.StorageProfile != nil {
diff --git a/modules/api/pkg/resources/machine/common.go b/modules/api/pkg/resources/machine/common.go
index 7cda52e196..9466d73a51 100644
--- a/modules/api/pkg/resources/machine/common.go
+++ b/modules/api/pkg/resources/machine/common.go
@@ -355,15 +355,30 @@ func GetVMwareCloudDirectorProviderConfig(c *kubermaticv1.Cluster, nodeSpec apiv
config.PlacementPolicy = nodeSpec.Cloud.VMwareCloudDirector.PlacementPolicy
}
+ var networks []providerconfig.ConfigVarString
+
switch {
case nodeSpec.Cloud.VMwareCloudDirector.Network != "":
- config.Network = providerconfig.ConfigVarString{Value: nodeSpec.Cloud.VMwareCloudDirector.Network}
+ networks = append(networks, providerconfig.ConfigVarString{Value: nodeSpec.Cloud.VMwareCloudDirector.Network})
case c.Spec.Cloud.VMwareCloudDirector.OVDCNetwork != "":
- config.Network = providerconfig.ConfigVarString{Value: c.Spec.Cloud.VMwareCloudDirector.OVDCNetwork}
+ networks = append(networks, providerconfig.ConfigVarString{Value: c.Spec.Cloud.VMwareCloudDirector.OVDCNetwork})
case len(c.Spec.Cloud.VMwareCloudDirector.OVDCNetworks) > 0:
- config.Network = providerconfig.ConfigVarString{Value: c.Spec.Cloud.VMwareCloudDirector.OVDCNetworks[0]}
+ networks = append(networks, providerconfig.ConfigVarString{Value: c.Spec.Cloud.VMwareCloudDirector.OVDCNetworks[0]})
}
+networkLoop:
+ for _, network := range nodeSpec.Cloud.VMwareCloudDirector.AdditionalNetworks {
+ // Check if network is already registered in the slice
+ for _, registered := range networks {
+ if registered.Value == network {
+ continue networkLoop
+ }
+ }
+ networks = append(networks, providerconfig.ConfigVarString{Value: network})
+ }
+
+ config.Networks = networks
+
return config, nil
}
diff --git a/modules/api/pkg/test/e2e/utils/apiclient/models/v_mware_cloud_director_node_spec.go b/modules/api/pkg/test/e2e/utils/apiclient/models/v_mware_cloud_director_node_spec.go
index 3a4f3a9868..d66a909a8e 100644
--- a/modules/api/pkg/test/e2e/utils/apiclient/models/v_mware_cloud_director_node_spec.go
+++ b/modules/api/pkg/test/e2e/utils/apiclient/models/v_mware_cloud_director_node_spec.go
@@ -18,6 +18,9 @@ import (
// swagger:model VMwareCloudDirectorNodeSpec
type VMwareCloudDirectorNodeSpec struct {
+ // additional networks
+ AdditionalNetworks []string `json:"additionalNetworks"`
+
// CPU cores
CPUCores int64 `json:"cpuCores,omitempty"`
diff --git a/modules/web/src/app/node-data/basic/provider/vmware-cloud-director/component.ts b/modules/web/src/app/node-data/basic/provider/vmware-cloud-director/component.ts
index 9ec4b8f702..631e6511f2 100644
--- a/modules/web/src/app/node-data/basic/provider/vmware-cloud-director/component.ts
+++ b/modules/web/src/app/node-data/basic/provider/vmware-cloud-director/component.ts
@@ -61,6 +61,7 @@ enum Controls {
SizingPolicy = 'sizingPolicy',
Template = 'template',
Network = 'network',
+ AdditionalNetworks = 'additionalNetworks',
}
enum StorageProfileState {
@@ -201,7 +202,8 @@ export class VMwareCloudDirectorBasicNodeDataComponent
this.form.get(Controls.DiskSizeGB).valueChanges,
this.form.get(Controls.DiskIOPs).valueChanges,
this.form.get(Controls.IPAllocationMode).valueChanges,
- this.form.get(Controls.Network).valueChanges
+ this.form.get(Controls.Network).valueChanges,
+ this.form.get(Controls.AdditionalNetworks).valueChanges
)
.pipe(takeUntil(this._unsubscribe))
.subscribe(_ => (this._nodeDataService.nodeData = this._getNodeData()));
@@ -217,7 +219,8 @@ export class VMwareCloudDirectorBasicNodeDataComponent
this.form.get(Controls.Catalog).valueChanges,
this.form.get(Controls.PlacementPolicy).valueChanges,
this.form.get(Controls.SizingPolicy).valueChanges,
- this.form.get(Controls.Network).valueChanges
+ this.form.get(Controls.Network).valueChanges,
+ this.form.get(Controls.AdditionalNetworks).valueChanges
)
.pipe(filter(_ => this.isEnterpriseEdition))
.pipe(takeUntil(this._unsubscribe))
@@ -339,10 +342,21 @@ export class VMwareCloudDirectorBasicNodeDataComponent
}
onNetworkChanged(network: string): void {
+ const additionalNetworksControl = this.form.get(Controls.AdditionalNetworks);
+
+ if (additionalNetworksControl.value && additionalNetworksControl.value.includes(network)) {
+ additionalNetworksControl.setValue(additionalNetworksControl.value.filter(n => n !== network));
+ }
+
this._nodeDataService.nodeData.spec.cloud.vmwareclouddirector.network = network;
this._nodeDataService.nodeDataChanges.next(this._nodeDataService.nodeData);
}
+ onAdditionalNetworkChanged(networks: string[]): void {
+ this._nodeDataService.nodeData.spec.cloud.vmwareclouddirector.additionalNetworks = networks;
+ this._nodeDataService.nodeDataChanges.next(this._nodeDataService.nodeData);
+ }
+
onTemplateChanged(template: string): void {
this.selectedTemplate = template;
this._nodeDataService.nodeData.spec.cloud.vmwareclouddirector.template = template;
@@ -351,6 +365,10 @@ export class VMwareCloudDirectorBasicNodeDataComponent
updateSelectedNetwork(): void {
const networkControl = this.form.get(Controls.Network);
+ const additionalNetworksControl = this.form.get(Controls.AdditionalNetworks);
+
+ additionalNetworksControl.setValue(additionalNetworksControl.value.filter(n => this.networks.includes(n)));
+
if (networkControl.value && this.networks.includes(networkControl.value)) {
return;
}
@@ -381,9 +399,8 @@ export class VMwareCloudDirectorBasicNodeDataComponent
[Controls.Catalog]: this._builder.control(values ? values.catalog : defaults.catalog, [Validators.required]),
[Controls.PlacementPolicy]: this._builder.control(values ? values.placementPolicy : defaults.placementPolicy),
[Controls.SizingPolicy]: this._builder.control(values ? values.sizingPolicy : defaults.sizingPolicy),
- [Controls.Network]: this._builder.control(values && values.network ? values.network : this.networks[0], [
- Validators.required,
- ]),
+ [Controls.Network]: this._builder.control(values?.network ?? this.networks[0], Validators.required),
+ [Controls.AdditionalNetworks]: this._builder.control(values?.additionalNetworks ?? defaults.additionalNetworks),
});
}
@@ -573,6 +590,7 @@ export class VMwareCloudDirectorBasicNodeDataComponent
diskIOPS: this.form.get(Controls.DiskIOPs).value,
ipAllocationMode: this.form.get(Controls.IPAllocationMode).value,
network: this.form.get(Controls.Network).value,
+ additionalNetworks: this.form.get(Controls.AdditionalNetworks).value,
} as VMwareCloudDirectorNodeSpec,
} as NodeCloudSpec,
} as NodeSpec,
@@ -595,6 +613,7 @@ export class VMwareCloudDirectorBasicNodeDataComponent
[Controls.PlacementPolicy]: this.form.get(Controls.PlacementPolicy).value?.[ComboboxControls.Select],
[Controls.SizingPolicy]: this.form.get(Controls.SizingPolicy).value?.[ComboboxControls.Select],
[Controls.Network]: this.form.get(Controls.Network).value,
+ [Controls.AdditionalNetworks]: this.form.get(Controls.AdditionalNetworks).value,
} as VMwareCloudDirectorNodeSpec,
};
diff --git a/modules/web/src/app/node-data/basic/provider/vmware-cloud-director/template.html b/modules/web/src/app/node-data/basic/provider/vmware-cloud-director/template.html
index 68fe0c4302..6381487e2b 100644
--- a/modules/web/src/app/node-data/basic/provider/vmware-cloud-director/template.html
+++ b/modules/web/src/app/node-data/basic/provider/vmware-cloud-director/template.html
@@ -98,6 +98,20 @@
+
+ 1">
+ Additional Networks
+
+ {{network}}
+
+
+