diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/vnf/AttachVnfTemplateCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/vnf/AttachVnfTemplateCmd.java
new file mode 100644
index 000000000000..8cb389ca6b8b
--- /dev/null
+++ b/api/src/main/java/org/apache/cloudstack/api/command/user/vnf/AttachVnfTemplateCmd.java
@@ -0,0 +1,38 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.cloudstack.api.command.user.vnf;
+
+import org.apache.cloudstack.api.*;
+import org.apache.cloudstack.api.response.*;
+import javax.inject.Inject;
+
+@APICommand(name="attachVnfTemplate", responseObject=SuccessResponse.class,
+ description="Bind an existing VM as the VNF for the network")
+public class AttachVnfTemplateCmd extends BaseAsyncCmd {
+ @Parameter(name="networkid", type=CommandType.UUID, entityType=NetworkResponse.class, required=true) private Long networkId;
+ @Parameter(name="vmid", type=CommandType.UUID, entityType=UserVmResponse.class, required=true) private Long vmId;
+
+ @Inject private org.apache.cloudstack.vnf.VnfNetworkService vnfSvc;
+ @Override public void execute() {
+ vnfSvc.attachVnfVm(networkId, vmId, getEntityOwnerId());
+ setResponseObject(new SuccessResponse(getCommandName()));
+ }
+ @Override public String getCommandName(){return "attachvnftemplateresponse";}
+ @Override public long getEntityOwnerId(){return CallContext.current().getCallingAccountId();}
+}
diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/vnf/CreateVnfNetworkCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/vnf/CreateVnfNetworkCmd.java
new file mode 100644
index 000000000000..1a779d9020c3
--- /dev/null
+++ b/api/src/main/java/org/apache/cloudstack/api/command/user/vnf/CreateVnfNetworkCmd.java
@@ -0,0 +1,49 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.cloudstack.api.command.user.vnf;
+
+import org.apache.cloudstack.api.*;
+import org.apache.cloudstack.api.response.*;
+import javax.inject.Inject;
+
+@APICommand(name = "createVnfNetwork",
+ description = "Create a VNF network (deploy VR broker + VNF VM)",
+ responseObject = CreateNetworkResponse.class)
+public class CreateVnfNetworkCmd extends BaseAsyncCreateCmd {
+ @Parameter(name="name", type=CommandType.STRING, required=true) private String name;
+ @Parameter(name="displaytext", type=CommandType.STRING) private String displayText;
+ @Parameter(name="zoneid", type=CommandType.UUID, entityType=ZoneResponse.class, required=true) private Long zoneId;
+ @Parameter(name="vnftemplateid", type=CommandType.UUID, entityType=TemplateResponse.class, required=true) private Long vnfTemplateId;
+ @Parameter(name="servicehelpers", type=CommandType.STRING) private String serviceHelpers;
+ @Parameter(name="dictionaryyaml", type=CommandType.STRING) private String dictionaryYaml;
+
+ @Inject private org.apache.cloudstack.vnf.VnfNetworkService vnfSvc;
+
+ @Override public void execute() {
+ CreateNetworkResponse resp = vnfSvc.createVnfNetwork(this);
+ setResponseObject(resp); resp.setResponseName(getCommandName());
+ }
+ @Override public String getCommandName() { return "createvnfnetworkresponse"; }
+ @Override public long getEntityOwnerId() { return CallContext.current().getCallingAccountId(); }
+
+ // getters...
+ public String getName(){return name;} public String getDisplayText(){return displayText;}
+ public Long getZoneId(){return zoneId;} public Long getVnfTemplateId(){return vnfTemplateId;}
+ public String getServiceHelpers(){return serviceHelpers;} public String getDictionaryYaml(){return dictionaryYaml;}
+}
diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/vnf/GetVnfNetworkStatusCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/vnf/GetVnfNetworkStatusCmd.java
new file mode 100644
index 000000000000..9a5537b5708e
--- /dev/null
+++ b/api/src/main/java/org/apache/cloudstack/api/command/user/vnf/GetVnfNetworkStatusCmd.java
@@ -0,0 +1,35 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.cloudstack.api.command.user.vnf;
+
+import org.apache.cloudstack.api.*;
+import org.apache.cloudstack.api.response.*;
+import javax.inject.Inject;
+
+@APICommand(name="getVnfNetworkStatus",
+ description="Return broker/VNF/dictionary status for a VNF network",
+ responseObject=org.apache.cloudstack.api.response.SuccessResponse.class)
+public class GetVnfNetworkStatusCmd extends BaseCmd {
+ @Parameter(name="networkid", type=CommandType.UUID, entityType=NetworkResponse.class, required=true) private Long networkId;
+ @Inject private org.apache.cloudstack.vnf.VnfNetworkService vnfSvc;
+ @Override public void execute() {
+ setResponseObject(vnfSvc.getStatus(networkId));
+ }
+ @Override public String getCommandName(){ return "getvnfnetworkstatusresponse"; }
+}
diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/vnf/UploadVnfDictionaryCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/vnf/UploadVnfDictionaryCmd.java
new file mode 100644
index 000000000000..34370d41044f
--- /dev/null
+++ b/api/src/main/java/org/apache/cloudstack/api/command/user/vnf/UploadVnfDictionaryCmd.java
@@ -0,0 +1,40 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.cloudstack.api.command.user.vnf;
+
+import org.apache.cloudstack.api.*;
+import org.apache.cloudstack.api.response.*;
+import javax.inject.Inject;
+
+@APICommand(name="uploadVnfDictionary", responseObject=SuccessResponse.class,
+ description="Upload/replace a dictionary YAML for a VNF network")
+public class UploadVnfDictionaryCmd extends BaseAsyncCmd {
+ @Parameter(name="networkid", type=CommandType.UUID, entityType=NetworkResponse.class, required=true) private Long networkId;
+ @Parameter(name="name", type=CommandType.STRING) private String name;
+ @Parameter(name="yaml", type=CommandType.STRING, required=true) private String yaml;
+
+ @Inject private org.apache.cloudstack.vnf.VnfNetworkService vnfSvc;
+
+ @Override public void execute() {
+ vnfSvc.uploadDictionary(networkId, name, yaml, getEntityOwnerId());
+ setResponseObject(new SuccessResponse(getCommandName()));
+ }
+ @Override public String getCommandName() { return "uploadvnfdictionaryresponse"; }
+ @Override public long getEntityOwnerId() { return CallContext.current().getCallingAccountId(); }
+}
diff --git a/engine/schema/src/main/resources/META-INF/db/schema-42100to42200/42110-vnf-network.xml b/engine/schema/src/main/resources/META-INF/db/schema-42100to42200/42110-vnf-network.xml
new file mode 100644
index 000000000000..2f25b3f0f4f9
--- /dev/null
+++ b/engine/schema/src/main/resources/META-INF/db/schema-42100to42200/42110-vnf-network.xml
@@ -0,0 +1,77 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/engine/schema/src/main/resources/META-INF/db/schema-42100to42200/upgrade.xml b/engine/schema/src/main/resources/META-INF/db/schema-42100to42200/upgrade.xml
new file mode 100644
index 000000000000..6950977fb4ed
--- /dev/null
+++ b/engine/schema/src/main/resources/META-INF/db/schema-42100to42200/upgrade.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
diff --git a/server/src/main/java/org/apache/cloudstack/vnf/VnfNetworkService.java b/server/src/main/java/org/apache/cloudstack/vnf/VnfNetworkService.java
new file mode 100644
index 000000000000..850f462453a1
--- /dev/null
+++ b/server/src/main/java/org/apache/cloudstack/vnf/VnfNetworkService.java
@@ -0,0 +1,29 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.cloudstack.vnf;
+
+import org.apache.cloudstack.api.command.user.vnf.*;
+import org.apache.cloudstack.api.response.CreateNetworkResponse;
+
+public interface VnfNetworkService {
+ CreateNetworkResponse createVnfNetwork(CreateVnfNetworkCmd cmd);
+ void uploadDictionary(long networkId, String name, String yaml, long ownerId);
+ void attachVnfVm(long networkId, long vmId, long ownerId);
+ org.apache.cloudstack.api.response.SuccessResponse getStatus(long networkId);
+}
diff --git a/server/src/main/java/org/apache/cloudstack/vnf/VnfNetworkServiceImpl.java b/server/src/main/java/org/apache/cloudstack/vnf/VnfNetworkServiceImpl.java
new file mode 100644
index 000000000000..45f96baa170c
--- /dev/null
+++ b/server/src/main/java/org/apache/cloudstack/vnf/VnfNetworkServiceImpl.java
@@ -0,0 +1,58 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.cloudstack.vnf;
+
+import com.cloud.network.Network;
+import com.cloud.user.Account;
+import org.apache.cloudstack.api.response.CreateNetworkResponse;
+import javax.inject.Inject;
+import javax.ejb.Local;
+
+@Local(value=VnfNetworkService.class)
+public class VnfNetworkServiceImpl implements VnfNetworkService {
+
+ @Inject private dao.VnfDictionaryDao dictDao;
+ @Inject private dao.VnfNetworkBindingDao bindingDao;
+ @Inject private dao.VnfRuleMapDao ruleDao;
+ @Inject private VnfTemplateRenderer renderer;
+ @Inject private VnfTransport transport;
+
+ @Override
+ public CreateNetworkResponse createVnfNetwork(org.apache.cloudstack.api.command.user.vnf.CreateVnfNetworkCmd cmd) {
+ // TODO: create network record (like Isolated), deploy VR broker + VNF VM from template,
+ // persist binding + optional dictionary, return response with IDs
+ return new CreateNetworkResponse();
+ }
+
+ @Override
+ public void uploadDictionary(long networkId, String name, String yaml, long ownerId) {
+ // TODO: validate YAML placeholders; persist versioned dictionary
+ }
+
+ @Override
+ public void attachVnfVm(long networkId, long vmId, long ownerId) {
+ // TODO: bind a VM as the VNF; update egress allowlist for broker
+ }
+
+ @Override
+ public org.apache.cloudstack.api.response.SuccessResponse getStatus(long networkId) {
+ // TODO: query broker health + VNF reachability + dict version
+ return new org.apache.cloudstack.api.response.SuccessResponse("getvnfnetworkstatus");
+ }
+}
diff --git a/server/src/main/java/org/apache/cloudstack/vnf/VnfTemplateRenderer.java b/server/src/main/java/org/apache/cloudstack/vnf/VnfTemplateRenderer.java
new file mode 100644
index 000000000000..dd0e4e6c96bc
--- /dev/null
+++ b/server/src/main/java/org/apache/cloudstack/vnf/VnfTemplateRenderer.java
@@ -0,0 +1,34 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.cloudstack.vnf;
+
+import java.util.Map;
+
+public class VnfTemplateRenderer {
+ public RenderedRequest render(Dictionary dict, String key, Map inputs, Map injectedHeaders) {
+ // TODO: SnakeYAML load -> map; replace ${...}; build method/path/body/headers; return RenderedRequest
+ return new RenderedRequest("POST", "/api/v2/firewall/rule", Map.of(), injectedHeaders);
+ }
+
+ public static class Dictionary { public Map root; }
+ public static class RenderedRequest {
+ public final String method, path; public final Object body; public final Map headers;
+ public RenderedRequest(String m, String p, Object b, Map h){ method=m; path=p; body=b; headers=h; }
+ }
+}
diff --git a/server/src/main/java/org/apache/cloudstack/vnf/VnfTransport.java b/server/src/main/java/org/apache/cloudstack/vnf/VnfTransport.java
new file mode 100644
index 000000000000..49c428223cb2
--- /dev/null
+++ b/server/src/main/java/org/apache/cloudstack/vnf/VnfTransport.java
@@ -0,0 +1,30 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.cloudstack.vnf;
+
+public class VnfTransport {
+ public static class VnfResponse { public final int status; public final String body;
+ public VnfResponse(int s, String b){ status=s; body=b; } }
+
+ public VnfResponse forward(long networkId, String vnfIp, int port,
+ VnfTemplateRenderer.RenderedRequest req, String idemKey) {
+ // TODO: POST https://:8443/v1/forward with mTLS + JWT; return status/body
+ return new VnfResponse(200, "{}");
+ }
+}