diff --git a/ui/src/config/section/network.js b/ui/src/config/section/network.js index 37cdd0c8b98a..bc45f4c8eacf 100644 --- a/ui/src/config/section/network.js +++ b/ui/src/config/section/network.js @@ -1139,6 +1139,11 @@ export default { args: ['name', 'description', 'sourceipaddress', 'sourceport', 'instanceport', 'algorithm', 'networkid', 'sourceipaddressnetworkid', 'scheme'], mapping: { algorithm: { + // TODO: derive from the selected network's offering capabilities + // (service Lb, SupportedLBAlgorithms) - mirrors what LoadBalancing.vue + // does for public LB. Doing it here requires AutogenView to support + // an async/function-valued `options`, which is a framework change + // outside the scope of this UI patch. options: ['source', 'roundrobin', 'leastconn'] }, scheme: { diff --git a/ui/src/views/compute/AutoScaleLoadBalancing.vue b/ui/src/views/compute/AutoScaleLoadBalancing.vue index 6c04ce1c2504..deebd93f26ef 100644 --- a/ui/src/views/compute/AutoScaleLoadBalancing.vue +++ b/ui/src/views/compute/AutoScaleLoadBalancing.vue @@ -267,9 +267,7 @@ :filterOption="(input, option) => { return option.value.toLowerCase().indexOf(input.toLowerCase()) >= 0 }" > - {{ $t('label.lb.algorithm.roundrobin') }} - {{ $t('label.lb.algorithm.leastconn') }} - {{ $t('label.lb.algorithm.source') }} + {{ $t('label.lb.algorithm.' + algo) }}
@@ -338,6 +336,10 @@ export default { algorithm: '', protocol: '' }, + // Default fallback list. Replaced by fetchLbCapabilitiesForRule() with whatever the + // rule's tier network advertises via service.Lb.capability.SupportedLbAlgorithms, + // matching the pattern in LoadBalancing.vue and VpcTiersTab.vue. + supportedAlgorithms: ['roundrobin', 'leastconn', 'source'], vms: [], nics: [], totalCount: 0, @@ -836,6 +838,36 @@ export default { this.editRuleDetails.name = this.selectedRule.name this.editRuleDetails.algorithm = this.selectedRule.algorithm this.editRuleDetails.protocol = this.selectedRule.protocol + this.fetchLbCapabilitiesForRule(this.selectedRule) + }, + /** + * Loads SupportedLbAlgorithms from the LB rule's tier network. Same approach as + * LoadBalancing.vue's fetchLbCapabilities() and VpcTiersTab.vue's + * fetchLbCapabilitiesForNetwork() — capability is read from the live network + * (listNetworks) rather than the offering, since the offering response does not + * carry the per-provider Lb capability map. + */ + fetchLbCapabilitiesForRule (rule) { + const networkId = rule?.networkid + if (!networkId) { + return + } + getAPI('listNetworks', { id: networkId, listall: true }).then(json => { + const network = json?.listnetworksresponse?.network?.[0] + const lbService = network?.service?.find(s => s.name === 'Lb') + const algoCap = lbService?.capability?.find(c => c.name === 'SupportedLbAlgorithms') + if (algoCap && algoCap.value) { + const algos = algoCap.value.split(',').map(s => s.trim()).filter(s => s) + if (algos.length > 0) { + this.supportedAlgorithms = algos + if (!algos.includes(this.editRuleDetails.algorithm)) { + this.editRuleDetails.algorithm = algos[0] + } + } + } + }).catch(error => { + console.warn('Failed to load AutoScale LB algorithm capabilities; using defaults', error) + }) }, handleSubmitEditForm () { if (this.editRuleModalLoading) return diff --git a/ui/src/views/network/LoadBalancing.vue b/ui/src/views/network/LoadBalancing.vue index 0b9ed7684a89..95b982d3cd47 100644 --- a/ui/src/views/network/LoadBalancing.vue +++ b/ui/src/views/network/LoadBalancing.vue @@ -49,9 +49,7 @@ :filterOption="(input, option) => { return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0 }" > - {{ $t('label.lb.algorithm.roundrobin') }} - {{ $t('label.lb.algorithm.leastconn') }} - {{ $t('label.lb.algorithm.source') }} + {{ $t('label.lb.algorithm.' + algo) }}
@@ -436,9 +434,7 @@ :filterOption="(input, option) => { return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0 }" > - {{ $t('label.lb.algorithm.roundrobin') }} - {{ $t('label.lb.algorithm.leastconn') }} - {{ $t('label.lb.algorithm.source') }} + {{ $t('label.lb.algorithm.' + algo) }}
@@ -872,6 +868,10 @@ export default { cidrlist: '' }, lbProvider: null, + // Populated from the network's offering capabilities (service Lb, + // SupportedLBAlgorithms). Defaults to the union we historically hard-coded so + // legacy networks/offerings without an explicit cap keep working. + supportedAlgorithms: ['roundrobin', 'leastconn', 'source'], addVmModalVisible: false, addVmModalLoading: false, addVmModalNicLoading: false, @@ -1089,6 +1089,69 @@ export default { this.fetchListTiers() this.fetchLBRules() this.fetchZone() + this.fetchLbCapabilities() + }, + /** + * Loads the SupportedLbAlgorithms capability from the network's Lb service. + * The capability lives at network.service[name=Lb].capability[name=SupportedLbAlgorithms] + * with a CSV value like "roundrobin,source". Falls back to the legacy + * static list when the lookup fails so providers that don't declare it + * keep their pre-existing behaviour. + * + * Note: listNetworks is the right endpoint here (not listNetworkOfferings): + * the offering response hard-codes only SupportedLBIsolation/ElasticLb/ + * InlineMode/VmAutoScaling for the Lb service, while the network response + * walks the live provider's getCapabilities() and exposes everything we + * declared. The capability name on the wire is "SupportedLbAlgorithms" + * (note the lowercase 'b' in 'Lb') - that's how Capability.SupportedLBAlgorithms.getName() + * serialises. + */ + fetchLbCapabilities () { + const networkId = this.resource?.associatednetworkid || this.resource?.networkid + if (networkId) { + this.fetchLbCapabilitiesByNetworkId(networkId) + return + } + // VPC public IP that has not been associated to a specific tier yet (no rule yet). + // Pick any Lb-enabled tier of the VPC and read the capability from there - every tier + // of the same VPC uses the same Lb provider, so the answer is provider-uniform. + if (this.resource?.vpcid) { + getAPI('listNetworks', { vpcid: this.resource.vpcid, supportedservices: 'Lb', listall: true }).then(json => { + const tier = json?.listnetworksresponse?.network?.[0] + if (tier?.id) { + this.applyLbAlgorithmCapability(tier) + } + }).catch(error => { + console.warn('Failed to enumerate VPC tiers for LB capabilities; using defaults', error) + }) + } + }, + fetchLbCapabilitiesByNetworkId (networkId) { + getAPI('listNetworks', { id: networkId, listall: true }).then(json => { + const network = json?.listnetworksresponse?.network?.[0] + if (network) { + this.applyLbAlgorithmCapability(network) + } + }).catch(error => { + // Swallow - we just keep the default list. Don't pop a notification + // because this is non-fatal background data. + console.warn('Failed to load LB algorithm capabilities; using defaults', error) + }) + }, + applyLbAlgorithmCapability (network) { + const lbService = network?.service?.find(s => s.name === 'Lb') + const algoCap = lbService?.capability?.find(c => c.name === 'SupportedLbAlgorithms') + if (algoCap && algoCap.value) { + const algos = algoCap.value.split(',').map(s => s.trim()).filter(s => s) + if (algos.length > 0) { + this.supportedAlgorithms = algos + // If the default algorithm is not in the supported set, switch to the first one + // so the dropdown shows a valid selection from the start. + if (!algos.includes(this.newRule.algorithm)) { + this.newRule.algorithm = algos[0] + } + } + } }, fetchVpc () { if (!this.resource.vpcid) { diff --git a/ui/src/views/network/VpcTiersTab.vue b/ui/src/views/network/VpcTiersTab.vue index 4a689f13c34d..bf21c3ec8227 100644 --- a/ui/src/views/network/VpcTiersTab.vue +++ b/ui/src/views/network/VpcTiersTab.vue @@ -347,8 +347,8 @@ :filterOption="(input, option) => { return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0 }" > - - {{ key }} + + {{ $t('label.lb.algorithm.' + algo) }} @@ -409,11 +409,13 @@ export default { errorPrivateMtu: '', gatewayPlaceholder: '', netmaskPlaceholder: '', - algorithms: { - Source: 'source', - 'Round-robin': 'roundrobin', - 'Least connections': 'leastconn' - }, + // Default fallback algorithm list for the Internal LB modal. Replaced at runtime by + // fetchLbCapabilitiesForNetwork() with whatever the selected tier's Lb provider + // declares via service.Lb.capability.SupportedLbAlgorithms — same approach used by + // LoadBalancing.vue for the public LB form, so providers like OVN that only support + // {roundrobin, source} expose exactly that subset, while providers that declare the + // full set keep their previous options. + supportedAlgorithms: ['source', 'roundrobin', 'leastconn'], internalLbCols: [ { key: 'name', @@ -728,13 +730,45 @@ export default { this.initForm() this.showAddInternalLB = true this.networkid = id - this.form.algorithm = 'Source' + // Reset to the static default first so the dropdown is never empty while the async + // capability lookup runs; fetchLbCapabilitiesForNetwork() narrows it down once the + // listNetworks response arrives. + this.supportedAlgorithms = ['source', 'roundrobin', 'leastconn'] + this.form.algorithm = this.supportedAlgorithms[0] this.rules = { name: [{ required: true, message: this.$t('message.error.internallb.name') }], sourcePort: [{ required: true, message: this.$t('message.error.internallb.source.port') }], instancePort: [{ required: true, message: this.$t('message.error.internallb.instance.port') }], algorithm: [{ required: true, message: this.$t('label.required') }] } + this.fetchLbCapabilitiesForNetwork(id) + }, + /** + * Loads SupportedLbAlgorithms from the tier network's Lb service. Mirrors the + * fetchLbCapabilities() helper in LoadBalancing.vue so the public-LB form and the + * Internal-LB modal converge on the same data-driven dropdown — provider-agnostic and + * future-proof. Falls back silently to the static default when the lookup fails. + */ + fetchLbCapabilitiesForNetwork (networkId) { + if (!networkId) { + return + } + getAPI('listNetworks', { id: networkId, listall: true }).then(json => { + const network = json?.listnetworksresponse?.network?.[0] + const lbService = network?.service?.find(s => s.name === 'Lb') + const algoCap = lbService?.capability?.find(c => c.name === 'SupportedLbAlgorithms') + if (algoCap && algoCap.value) { + const algos = algoCap.value.split(',').map(s => s.trim()).filter(s => s) + if (algos.length > 0) { + this.supportedAlgorithms = algos + if (!algos.includes(this.form.algorithm)) { + this.form.algorithm = algos[0] + } + } + } + }).catch(error => { + console.warn('Failed to load Internal LB algorithm capabilities; using defaults', error) + }) }, handleAddNetworkSubmit () { if (this.modalLoading) return