This is an automated email from the ASF dual-hosted git repository.

wido pushed a commit to branch direct-routed-network
in repository https://gitbox.apache.org/repos/asf/cloudstack.git


The following commit(s) were added to refs/heads/direct-routed-network by this 
push:
     new 34a103e91a0 Direct Routed networks: let the UI pass an operator-chosen 
routed id
34a103e91a0 is described below

commit 34a103e91a0e10147fd4decc6edd859159a7f9b1
Author: Wido den Hollander <[email protected]>
AuthorDate: Sat Sep 5 19:18:48 2026 +0000

    Direct Routed networks: let the UI pass an operator-chosen routed id
    
    CreateL3NetworkForm only supported offerings that allocate the routed id
    automatically; an operator-chosen id (the API's vlan parameter, like a
    Shared network's VLAN tag) could be given through the API alone. Add a
    'Routed ID' field to the form, shown to root admins when the selected
    L3 offering carries specifyVlan - where the API requires the id - and
    hidden otherwise, since the API then rejects the parameter and
    allocates the id from the ROUTED physical network's range.
    
    The field's description explains what the id is: a label naming the
    per-network bridge (brdr-<id>) on every KVM host, unique per zone,
    nothing on the wire, and useful to choose in advance so host routing
    policy can exist before the network does.
---
 ui/public/locales/en.json                    |  3 +++
 ui/src/views/network/CreateL3NetworkForm.vue | 25 +++++++++++++++++++++++--
 2 files changed, 26 insertions(+), 2 deletions(-)

diff --git a/ui/public/locales/en.json b/ui/public/locales/en.json
index 78cf90618d8..e1715fcc7db 100644
--- a/ui/public/locales/en.json
+++ b/ui/public/locales/en.json
@@ -2304,6 +2304,7 @@
 "label.rootdisksize": "Root disk size (GB)",
 "label.routenexthop": "Route next hop",
 "label.routenexthoptype": "Route next hop type",
+"label.routedid": "Routed ID",
 "label.routeprefix": "Route prefix",
 "label.router.health.check.last.updated": "Last updated",
 "label.router.health.check.name": "Check name",
@@ -3691,6 +3692,7 @@
 "message.error.required.input": "Please enter input",
 "message.error.reset.config": "Unable to reset config to default value",
 "message.error.retrieve.kubeconfig": "Unable to retrieve Kubernetes Cluster 
config",
+"message.error.routedid": "Please enter the Routed ID for this network; the 
selected network offering requires the operator to choose it",
 "message.error.routing.policy.term": "Community need to have the following 
format number:number",
 "message.error.s3nfs.path": "Please enter S3 NFS Path",
 "message.error.s3nfs.server": "Please enter S3 NFS Server",
@@ -3952,6 +3954,7 @@
 "message.releasing.dedicated.zone": "Releasing dedicated Zone...",
 "message.remote.access.vpn.iprange.description": "The range of IP addresses to 
allocate to VPN clients. The first IP in the range will be taken by the VPN 
server. (Optional)",
 "message.remove.annotation": "Are you sure you want to delete the comment?",
+"message.routedid.description": "Numeric ID for this Direct Routed (L3) 
network, chosen by the operator. The ID names the network's bridge on every KVM 
host (brdr-<ID>) and must be unique within the zone; it is only a label, 
nothing appears on the wire. Choosing it yourself makes the bridge name 
plannable, so host routing policy can be prepared before the network exists. 
Network offerings without 'specify VLAN' allocate the ID automatically from the 
ROUTED physical network's range instead.",
 "message.remove.egress.rule.failed": "Removing egress rule failed",
 "message.remove.egress.rule.processing": "Deleting egress rule...",
 "message.remove.failed": "Removing failed",
diff --git a/ui/src/views/network/CreateL3NetworkForm.vue 
b/ui/src/views/network/CreateL3NetworkForm.vue
index b6a04e80297..7e2fd2a76a9 100644
--- a/ui/src/views/network/CreateL3NetworkForm.vue
+++ b/ui/src/views/network/CreateL3NetworkForm.vue
@@ -92,6 +92,17 @@
               </a-select-option>
             </a-select>
           </a-form-item>
+          <a-form-item
+            v-if="!isObjectEmpty(selectedNetworkOffering) && 
selectedNetworkOffering.specifyvlan && isAdmin()"
+            name="routedid"
+            ref="routedid">
+            <template #label>
+              <tooltip-label :title="$t('label.routedid')" 
:tooltip="$t('message.routedid.description')"/>
+            </template>
+            <a-input
+              v-model:value="form.routedid"
+              :placeholder="$t('message.routedid.description')"/>
+          </a-form-item>
           <a-row :gutter="12">
             <a-col :md="12" :lg="12">
               <a-form-item name="gateway" ref="gateway">
@@ -223,7 +234,7 @@
 <script>
 import { ref, reactive, toRaw } from 'vue'
 import { getAPI, postAPI } from '@/api'
-import { isAdminOrDomainAdmin } from '@/role'
+import { isAdmin, isAdminOrDomainAdmin } from '@/role'
 import { mixinForm } from '@/utils/mixin'
 import ResourceIcon from '@/components/view/ResourceIcon'
 import TooltipLabel from '@/components/widgets/TooltipLabel'
@@ -275,6 +286,7 @@ export default {
     this.fetchData()
   },
   methods: {
+    isAdmin,
     isAdminOrDomainAdmin,
     initForm () {
       this.formRef = ref()
@@ -282,7 +294,10 @@ export default {
       this.rules = reactive({
         name: [{ required: true, message: this.$t('message.error.name') }],
         zoneid: [{ required: true, message: this.$t('message.error.select') }],
-        networkofferingid: [{ required: true, message: 
this.$t('message.error.select') }]
+        networkofferingid: [{ required: true, message: 
this.$t('message.error.select') }],
+        // Only rendered (and thus validated) for offerings with specifyVlan, 
where the API
+        // requires the operator to pick the routed id.
+        routedid: [{ required: true, message: 
this.$t('message.error.routedid') }]
       })
     },
     fetchData () {
@@ -356,6 +371,12 @@ export default {
             params[key] = values[key]
           }
         }
+        // The operator-chosen routed id travels in the API's vlan parameter, 
like the VLAN
+        // tag of a Shared network. Only valid with a specifyVlan network 
offering; otherwise
+        // CloudStack allocates the id from the ROUTED physical network's 
range.
+        if (this.selectedNetworkOffering.specifyvlan && values.routedid) {
+          params.vlan = values.routedid
+        }
         if (this.owner.account) {
           params.account = this.owner.account
           params.domainid = this.owner.domainid

Reply via email to