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 @@ + + + Additional Networks + + {{network}} + + +