Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions ui/public/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -4073,6 +4073,7 @@
"message.setting.update.delay": "The new value will take effect within 30 seconds.",
"message.setup.physical.network.during.zone.creation": "When adding a Zone, you need to set up one or more physical networks. Each physical network can carry one or more types of traffic, with certain restrictions on how they may be combined. Add or remove one or more traffic types onto each physical network.",
"message.setup.physical.network.during.zone.creation.basic": "When adding a basic Zone, you can set up one physical Network, which corresponds to a NIC on the hypervisor. The Network carries several types of traffic.<br/><br/>You may also <strong>add</strong> other traffic types onto the physical Network.",
"message.network.offering.unavailable": "No enabled network offering is available for this network type. Please contact an administrator or enable a suitable network offering.",
"message.shared.network.offering.warning": "Domain admins and regular Users can only create shared Networks from Network offering with the setting specifyvlan=false. Please contact an administrator to create a Network offering if this list is empty.",
"message.shared.network.unsupported.for.nsx": "Shared networks aren't supported for NSX enabled Zones",
"message.shutdown.triggered": "A shutdown has been triggered. CloudStack will not accept new jobs",
Expand Down
1 change: 1 addition & 0 deletions ui/public/locales/fr_FR.json
Original file line number Diff line number Diff line change
Expand Up @@ -1423,6 +1423,7 @@
"message.migrate.systemvm.confirm": "Confirmer la migration de la VM syst\u00e8me vers :",
"message.migrate.volume": "Confirmer la migration du volume vers un autre stockage primaire.",
"message.network.addvm.desc": "Veuillez sp\u00e9cifier le r\u00e9seau que vous souhaitez ajouter \u00e0 cette VM. Une nouvelle interface NIC sera ajout\u00e9e pour ce r\u00e9seau.",
"message.network.offering.unavailable": "Aucune offre de r\u00e9seau activ\u00e9e n'est disponible pour ce type de r\u00e9seau. Veuillez contacter un administrateur ou activer une offre de r\u00e9seau adapt\u00e9e.",
"message.network.removenic": "Veuillez confirmer que vous souhaitez supprimer cette carte NIC, ce qui supprimera \u00e9galement le r\u00e9seau associ\u00e9 sur la machine virtuelle.",
"message.network.updateip": "Confirmer que vous souhaitez changer l'adresse IP pour cette NIC sur la VM.",
"message.please.confirm.remove.ssh.key.pair": "Confirmer la suppression de cette bi-cl\u00e9 SSH",
Expand Down
24 changes: 19 additions & 5 deletions ui/src/views/network/CreateIsolatedNetworkForm.vue
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,9 @@
{{ opt.displaytext || opt.name || opt.description }}
</a-select-option>
</a-select>
<a-alert type="warning" v-if="networkOfferingWarning">
<template #message>{{ $t('message.network.offering.unavailable') }}</template>
</a-alert>
<a-alert type="warning" v-if="!this.hasVPC">
<template #message>
<span v-html="$t('message.warn.vpc.offerings')"/>
Expand Down Expand Up @@ -371,6 +374,7 @@ export default {
selectedZone: {},
networkOfferings: [],
networkOfferingLoading: false,
networkOfferingWarning: false,
selectedNetworkOffering: {},
vpcs: [],
vpcLoading: false,
Expand Down Expand Up @@ -540,13 +544,16 @@ export default {
this.networkOfferingLoading = true
if ('listVPCs' in this.$store.getters.apis) {
getAPI('listVPCs', params).then(json => {
const listVPCs = json.listvpcsresponse.vpc
const listVPCs = json?.listvpcsresponse?.vpc
this.hasVPC = this.arrayHasItems(listVPCs)
if (!this.hasVPC) {
this.fetchNetworkOfferingData(false)
} else {
this.fetchNetworkOfferingData()
}
}).catch(() => {
this.hasVPC = false
this.fetchNetworkOfferingData(false)
})
} else {
this.fetchNetworkOfferingData(false)
Expand All @@ -555,6 +562,7 @@ export default {
},
fetchNetworkOfferingData (forVpc) {
this.networkOfferingLoading = true
this.networkOfferingWarning = false
const params = {
zoneid: this.selectedZone.id,
guestiptype: 'Isolated',
Expand All @@ -566,24 +574,30 @@ export default {
if (!isAdmin()) { // normal user is not aware of the VLANs in the system, so normal user is not allowed to create network with network offerings whose specifyvlan = true
params.specifyvlan = false
}
if (forVpc !== null) {
if (forVpc !== undefined && forVpc !== null) {
params.forvpc = forVpc
}
this.networkOfferings = []
this.selectedNetworkOffering = {}
getAPI('listNetworkOfferings', params).then(json => {
this.networkOfferings = json.listnetworkofferingsresponse.networkoffering || []
this.networkOfferings = this.networkOfferings.filter(offering => offering.fornsx === this.selectedZone.isnsxenabled)
if (!this.selectedZone.routedmodeenabled) {
const offerings = json?.listnetworkofferingsresponse?.networkoffering || []

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks like #13503 already fixed the crash on the isolated tab for 4.22 and main. does it still show request failed on main without this change?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @Damans227 for the review — I was not aware of #13503 when I opened #14377 and this PR.
Our report is on CloudStack 4.22.1.0. #13503 is targeted at the 4.22.2 milestone, so it was not something we had on our installed 4.22.1.0 packages when we hit the bug.

Isolated repro on 4.22.1.0

  1. Add Network → Isolated (all Isolated network offerings disabled)
  2. Toast: Request failed. (no HTTP status)
  3. Network tab: listNetworkOfferings → HTTP 200
  4. Response:
    { "listnetworkofferingsresponse": {} }
  5. Console:
    TypeError: can't access property "filter", t.networkOfferings is undefined
    at fetchNetworkOfferingData (CreateIsolatedNetworkForm.vue:555)

Root cause
Same as #13485 / #13503: empty listnetworkofferingsresponse -> networkOfferings stays undefined -> TypeError on .filter -> UI shows "Request failed."

The difference is only the trigger: in our case it is disabling Isolated offerings, not physical-network tags.

Beyond the same || [] guard as #13503, this PR also:

  • shows a clear warning on Isolated when no offerings are available (instead of only failing silently / with a toast)
  • hardens L2 the same way (|| [] / safe parse) — UI: Prevent failure when loading network offerings when physical network has a tag #13503 only touched Isolated
  • adds a catch on listVPCs so a VPC lookup failure does not break the offerings path
  • documents that the empty-response crash is also triggered by disabled offerings, not only physical-network tags

I’m happy to slim this PR to the L2 / warning / listVPCs parts, or close whatever is redundant

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks, that explains it. can we slim this to the isolated warning and the l2 change? the empty list crash itself is already fixed on 4.22 and main

this.networkOfferings = Array.isArray(offerings) ? offerings : []
this.networkOfferings = this.networkOfferings.filter(offering => offering.fornsx === this.selectedZone?.isnsxenabled)
if (!this.selectedZone?.routedmodeenabled) {
this.networkOfferings = this.networkOfferings.filter(offering => offering.networkmode !== 'ROUTED')
}
}).catch(error => {
this.networkOfferings = []
this.$notifyError(error)
}).finally(() => {
this.networkOfferingLoading = false
if (this.arrayHasItems(this.networkOfferings)) {
this.form.networkofferingid = 0
this.handleNetworkOfferingChange(this.networkOfferings[0])
this.networkOfferingWarning = false
} else {
this.form.networkofferingid = null
this.networkOfferingWarning = true
}
})
},
Expand Down
11 changes: 8 additions & 3 deletions ui/src/views/network/CreateL2NetworkForm.vue
Original file line number Diff line number Diff line change
Expand Up @@ -321,13 +321,15 @@ export default {
this.networkOfferingLoading = true
if ('listVPCs' in this.$store.getters.apis) {
getAPI('listVPCs', params).then(json => {
const listVPCs = json.listvpcsresponse.vpc
const listVPCs = json?.listvpcsresponse?.vpc
var vpcAvailable = this.arrayHasItems(listVPCs)
if (vpcAvailable === false) {
this.fetchNetworkOfferingData(false)
} else {
this.fetchNetworkOfferingData()
}
}).catch(() => {
this.fetchNetworkOfferingData(false)
})
} else {
this.fetchNetworkOfferingData(false)
Expand All @@ -347,12 +349,15 @@ export default {
if (!isAdmin()) { // normal user is not aware of the VLANs in the system, so normal user is not allowed to create network with network offerings whose specifyvlan = true
params.specifyvlan = false
}
if (forVpc !== null) {
if (forVpc !== undefined && forVpc !== null) {
params.forvpc = forVpc
}
this.networkOfferings = []
getAPI('listNetworkOfferings', params).then(json => {
this.networkOfferings = json.listnetworkofferingsresponse.networkoffering
const offerings = json?.listnetworkofferingsresponse?.networkoffering || []
this.networkOfferings = Array.isArray(offerings) ? offerings : []
}).catch(error => {
this.networkOfferings = []
this.$notifyError(error)
}).finally(() => {
this.networkOfferingLoading = false
Expand Down
4 changes: 3 additions & 1 deletion ui/src/views/network/CreateSharedNetworkForm.vue
Original file line number Diff line number Diff line change
Expand Up @@ -800,8 +800,10 @@ export default {
this.handleNetworkOfferingChange(null)
this.networkOfferings = []
getAPI('listNetworkOfferings', params).then(json => {
this.networkOfferings = json.listnetworkofferingsresponse.networkoffering || []
const offerings = json?.listnetworkofferingsresponse?.networkoffering || []

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the shared tab already handles an empty list here in 4.22.1.0. which request fails on that tab in the browser network tab?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

None. On Shared, listNetworkOfferings also returns HTTP 200 with an empty
listnetworkofferingsresponse. No request fails in the Network tab; the
"Request failed" toast comes from the client-side TypeError path, same as Isolated

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in 4.22.1.0 that line already falls back to an empty list and shows the shared warning. can you share the console error from the shared tab? it might be the isolated tab erroring when the dialog opens

this.networkOfferings = Array.isArray(offerings) ? offerings : []
}).catch(error => {
this.networkOfferings = []
this.$notifyError(error)
}).finally(() => {
this.networkOfferingLoading = false
Expand Down