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

dahn pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/cloudstack.git


The following commit(s) were added to refs/heads/main by this push:
     new bc286656796 Add support for network data in Config Drive (#9329)
bc286656796 is described below

commit bc2866567968f187ac1c2ea1293f5c50df00f917
Author: Vishesh <vishes...@gmail.com>
AuthorDate: Mon Aug 26 14:23:42 2024 +0530

    Add support for network data in Config Drive (#9329)
---
 .../service/NetworkOrchestrationService.java       |   3 +
 .../engine/orchestration/NetworkOrchestrator.java  |  26 +-
 .../storage/configdrive/ConfigDriveBuilder.java    | 178 ++++++++++--
 .../storage/configdrive/ConfigDriveUtils.java      |  54 ++++
 .../configdrive/ConfigDriveBuilderTest.java        | 190 ++++++++++--
 .../storage/configdrive/ConfigDriveUtilsTest.java  | 108 +++++++
 .../java/com/cloud/network/NetworkModelImpl.java   |  13 +-
 .../network/element/ConfigDriveNetworkElement.java | 102 ++++++-
 .../element/ConfigDriveNetworkElementTest.java     |  29 +-
 .../java/com/cloud/vpc/MockNetworkManagerImpl.java |   6 +
 test/integration/smoke/test_network.py             | 320 ++++++++++++++++++++-
 tools/marvin/marvin/config/test_data.py            |  50 ++++
 12 files changed, 1025 insertions(+), 54 deletions(-)

diff --git 
a/engine/api/src/main/java/org/apache/cloudstack/engine/orchestration/service/NetworkOrchestrationService.java
 
b/engine/api/src/main/java/org/apache/cloudstack/engine/orchestration/service/NetworkOrchestrationService.java
index 41bd74f1192..84098bbc654 100644
--- 
a/engine/api/src/main/java/org/apache/cloudstack/engine/orchestration/service/NetworkOrchestrationService.java
+++ 
b/engine/api/src/main/java/org/apache/cloudstack/engine/orchestration/service/NetworkOrchestrationService.java
@@ -21,6 +21,7 @@ import java.util.List;
 import java.util.Map;
 
 import com.cloud.dc.DataCenter;
+import com.cloud.hypervisor.Hypervisor;
 import org.apache.cloudstack.acl.ControlledEntity.ACLType;
 import org.apache.cloudstack.framework.config.ConfigKey;
 import org.apache.cloudstack.framework.config.ConfigKey.Scope;
@@ -144,6 +145,8 @@ public interface NetworkOrchestrationService {
 
     List<NicProfile> getNicProfiles(VirtualMachine vm);
 
+    List<NicProfile> getNicProfiles(Long vmId, Hypervisor.HypervisorType 
hypervisorType);
+
     Map<String, String> getSystemVMAccessDetails(VirtualMachine vm);
 
     Pair<? extends NetworkGuru, ? extends Network> implementNetwork(long 
networkId, DeployDestination dest, ReservationContext context)
diff --git 
a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NetworkOrchestrator.java
 
b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NetworkOrchestrator.java
index 5c01bb4f288..ce4c6bab94a 100644
--- 
a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NetworkOrchestrator.java
+++ 
b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NetworkOrchestrator.java
@@ -1835,6 +1835,19 @@ public class NetworkOrchestrator extends ManagerBase 
implements NetworkOrchestra
                     return false;
                 }
             }
+            if (element instanceof ConfigDriveNetworkElement && ((
+                    
_networkModel.areServicesSupportedInNetwork(network.getId(), Service.Dhcp) &&
+                            
_networkModel.isProviderSupportServiceInNetwork(network.getId(), Service.Dhcp, 
element.getProvider())
+            ) || (
+                    
_networkModel.areServicesSupportedInNetwork(network.getId(), Service.Dns) &&
+                            
_networkModel.isProviderSupportServiceInNetwork(network.getId(), Service.Dns, 
element.getProvider())
+            ) || (
+                    
_networkModel.areServicesSupportedInNetwork(network.getId(), Service.UserData) 
&&
+                            
_networkModel.isProviderSupportServiceInNetwork(network.getId(), 
Service.UserData, element.getProvider())
+            ))) {
+                final ConfigDriveNetworkElement sp = 
(ConfigDriveNetworkElement) element;
+                return sp.createConfigDriveIso(profile, vmProfile, dest, null);
+            }
         }
         return true;
     }
@@ -4443,18 +4456,18 @@ public class NetworkOrchestrator extends ManagerBase 
implements NetworkOrchestra
     }
 
     @Override
-    public List<NicProfile> getNicProfiles(final VirtualMachine vm) {
-        final List<NicVO> nics = _nicDao.listByVmId(vm.getId());
+    public List<NicProfile> getNicProfiles(final Long vmId, HypervisorType 
hypervisorType) {
+        final List<NicVO> nics = _nicDao.listByVmId(vmId);
         final List<NicProfile> profiles = new ArrayList<NicProfile>();
 
         if (nics != null) {
             for (final Nic nic : nics) {
                 final NetworkVO network = 
_networksDao.findById(nic.getNetworkId());
-                final Integer networkRate = 
_networkModel.getNetworkRate(network.getId(), vm.getId());
+                final Integer networkRate = 
_networkModel.getNetworkRate(network.getId(), vmId);
 
                 final NetworkGuru guru = 
AdapterBase.getAdapterByName(networkGurus, network.getGuruName());
                 final NicProfile profile = new NicProfile(nic, network, 
nic.getBroadcastUri(), nic.getIsolationUri(), networkRate,
-                        
_networkModel.isSecurityGroupSupportedInNetwork(network), 
_networkModel.getNetworkTag(vm.getHypervisorType(), network));
+                        
_networkModel.isSecurityGroupSupportedInNetwork(network), 
_networkModel.getNetworkTag(hypervisorType, network));
                 guru.updateNicProfile(profile, network);
                 profiles.add(profile);
             }
@@ -4462,6 +4475,11 @@ public class NetworkOrchestrator extends ManagerBase 
implements NetworkOrchestra
         return profiles;
     }
 
+    @Override
+    public List<NicProfile> getNicProfiles(final VirtualMachine vm) {
+        return getNicProfiles(vm.getId(), vm.getHypervisorType());
+    }
+
     @Override
     public Map<String, String> getSystemVMAccessDetails(final VirtualMachine 
vm) {
         final Map<String, String> accessDetails = new HashMap<>();
diff --git 
a/engine/storage/configdrive/src/main/java/org/apache/cloudstack/storage/configdrive/ConfigDriveBuilder.java
 
b/engine/storage/configdrive/src/main/java/org/apache/cloudstack/storage/configdrive/ConfigDriveBuilder.java
index e1d51120efa..58cc341a87b 100644
--- 
a/engine/storage/configdrive/src/main/java/org/apache/cloudstack/storage/configdrive/ConfigDriveBuilder.java
+++ 
b/engine/storage/configdrive/src/main/java/org/apache/cloudstack/storage/configdrive/ConfigDriveBuilder.java
@@ -22,6 +22,8 @@ import static com.cloud.network.NetworkModel.CONFIGDATA_DIR;
 import static com.cloud.network.NetworkModel.CONFIGDATA_FILE;
 import static com.cloud.network.NetworkModel.PASSWORD_FILE;
 import static com.cloud.network.NetworkModel.USERDATA_FILE;
+import static com.cloud.network.NetworkService.DEFAULT_MTU;
+import static 
org.apache.cloudstack.storage.configdrive.ConfigDriveUtils.mergeJsonArraysAndUpdateObject;
 
 import java.io.File;
 import java.io.IOException;
@@ -33,6 +35,9 @@ import java.util.List;
 import java.util.Map;
 import java.util.Set;
 
+import com.cloud.network.Network;
+import com.cloud.vm.NicProfile;
+import com.googlecode.ipv6.IPv6Network;
 import org.apache.commons.codec.binary.Base64;
 import org.apache.commons.collections.MapUtils;
 import org.apache.commons.io.FileUtils;
@@ -81,7 +86,7 @@ public class ConfigDriveBuilder {
 
     /**
      *  Read the content of a {@link File} and convert it to a String in base 
64.
-     *  We expect the content of the file to be encoded using {@link 
StandardCharsets#US_ASC}
+     *  We expect the content of the file to be encoded using {@link 
StandardCharsets#US_ASCII}
      */
     public static String fileToBase64String(File isoFile) throws IOException {
         byte[] encoded = 
Base64.encodeBase64(FileUtils.readFileToByteArray(isoFile));
@@ -108,9 +113,9 @@ public class ConfigDriveBuilder {
      *  This method will build the metadata files required by OpenStack 
driver. Then, an ISO is going to be generated and returned as a String in base 
64.
      *  If vmData is null, we throw a {@link CloudRuntimeException}. Moreover, 
{@link IOException} are captured and re-thrown as {@link CloudRuntimeException}.
      */
-    public static String buildConfigDrive(List<String[]> vmData, String 
isoFileName, String driveLabel, Map<String, String> customUserdataParams) {
-        if (vmData == null) {
-            throw new CloudRuntimeException("No VM metadata provided");
+    public static String buildConfigDrive(List<NicProfile> nics, 
List<String[]> vmData, String isoFileName, String driveLabel, Map<String, 
String> customUserdataParams, Map<Long, List<Network.Service>> 
supportedServices) {
+        if (vmData == null && nics == null) {
+            throw new CloudRuntimeException("No VM metadata and nic profile 
provided");
         }
 
         Path tempDir = null;
@@ -121,10 +126,19 @@ public class ConfigDriveBuilder {
 
             File openStackFolder = new File(tempDirName + 
ConfigDrive.openStackConfigDriveName);
 
-            writeVendorAndNetworkEmptyJsonFile(openStackFolder);
-            writeVmMetadata(vmData, tempDirName, openStackFolder, 
customUserdataParams);
-
-            linkUserData(tempDirName);
+            writeVendorEmptyJsonFile(openStackFolder);
+            writeNetworkData(nics, supportedServices, openStackFolder);
+            for (NicProfile nic: nics) {
+                if 
(supportedServices.get(nic.getId()).contains(Network.Service.UserData)) {
+                    if (vmData == null) {
+                        throw new CloudRuntimeException("No VM metadata 
provided");
+                    }
+                    writeVmMetadata(vmData, tempDirName, openStackFolder, 
customUserdataParams);
+
+                    linkUserData(tempDirName);
+                    break;
+                }
+            }
 
             return generateAndRetrieveIsoAsBase64Iso(isoFileName, driveLabel, 
tempDirName);
         } catch (IOException e) {
@@ -212,18 +226,36 @@ public class ConfigDriveBuilder {
     }
 
     /**
-     *  Writes the following empty JSON files:
-     *  <ul>
-     *      <li> vendor_data.json
-     *      <li> network_data.json
-     *  </ul>
+     * First we generate a JSON object using {@link 
#getNetworkDataJsonObjectForNic(NicProfile, List)}, then we write it to a file 
called "network_data.json".
+     */
+    static void writeNetworkData(List<NicProfile> nics, Map<Long, 
List<Network.Service>> supportedServices, File openStackFolder) {
+        JsonObject finalNetworkData = new JsonObject();
+        if (needForGeneratingNetworkData(supportedServices)) {
+            for (NicProfile nic : nics) {
+                List<Network.Service> supportedService = 
supportedServices.get(nic.getId());
+                JsonObject networkData = getNetworkDataJsonObjectForNic(nic, 
supportedService);
+
+                mergeJsonArraysAndUpdateObject(finalNetworkData, networkData, 
"links", "id", "type");
+                mergeJsonArraysAndUpdateObject(finalNetworkData, networkData, 
"networks", "id", "type");
+                mergeJsonArraysAndUpdateObject(finalNetworkData, networkData, 
"services", "address", "type");
+            }
+        }
+
+        writeFile(openStackFolder, "network_data.json", 
finalNetworkData.toString());
+    }
+
+    static boolean needForGeneratingNetworkData(Map<Long, 
List<Network.Service>> supportedServices) {
+        return supportedServices.values().stream().anyMatch(services -> 
services.contains(Network.Service.Dhcp) || 
services.contains(Network.Service.Dns));
+    }
+
+    /**
+     *  Writes an empty JSON file named vendor_data.json in openStackFolder
      *
-     *  If the folder does not exist and we cannot create it, we throw a 
{@link CloudRuntimeException}.
+     *  If the folder does not exist, and we cannot create it, we throw a 
{@link CloudRuntimeException}.
      */
-    static void writeVendorAndNetworkEmptyJsonFile(File openStackFolder) {
+    static void writeVendorEmptyJsonFile(File openStackFolder) {
         if (openStackFolder.exists() || openStackFolder.mkdirs()) {
             writeFile(openStackFolder, "vendor_data.json", "{}");
-            writeFile(openStackFolder, "network_data.json", "{}");
         } else {
             throw new CloudRuntimeException("Failed to create folder " + 
openStackFolder);
         }
@@ -250,6 +282,120 @@ public class ConfigDriveBuilder {
         return metaData;
     }
 
+    /**
+     * Creates the {@link JsonObject} using @param nic's metadata. We expect 
the JSONObject to have the following entries:
+     * <ul>
+     *     <li> links </li>
+     *     <li> networks </li>
+     *     <li> services </li>
+     * </ul>
+     */
+    static JsonObject getNetworkDataJsonObjectForNic(NicProfile nic, 
List<Network.Service> supportedServices) {
+        JsonObject networkData = new JsonObject();
+
+        JsonArray links = getLinksJsonArrayForNic(nic);
+        JsonArray networks = getNetworksJsonArrayForNic(nic);
+        if (links.size() > 0) {
+            networkData.add("links", links);
+        }
+        if (networks.size() > 0) {
+            networkData.add("networks", networks);
+        }
+
+        JsonArray services = getServicesJsonArrayForNic(nic);
+        if (services.size() > 0) {
+            networkData.add("services", services);
+        }
+
+        return networkData;
+    }
+
+    static JsonArray getLinksJsonArrayForNic(NicProfile nic) {
+        JsonArray links = new JsonArray();
+        if (StringUtils.isNotBlank(nic.getMacAddress())) {
+            JsonObject link = new JsonObject();
+            link.addProperty("ethernet_mac_address", nic.getMacAddress());
+            link.addProperty("id", String.format("eth%d", nic.getDeviceId()));
+            link.addProperty("mtu", nic.getMtu() != null ? nic.getMtu() : 
DEFAULT_MTU);
+            link.addProperty("type", "phy");
+            links.add(link);
+        }
+        return links;
+    }
+
+    static JsonArray getNetworksJsonArrayForNic(NicProfile nic) {
+        JsonArray networks = new JsonArray();
+        if (StringUtils.isNotBlank(nic.getIPv4Address())) {
+            JsonObject ipv4Network = new JsonObject();
+            ipv4Network.addProperty("id", String.format("eth%d", 
nic.getDeviceId()));
+            ipv4Network.addProperty("ip_address", nic.getIPv4Address());
+            ipv4Network.addProperty("link", String.format("eth%d", 
nic.getDeviceId()));
+            ipv4Network.addProperty("netmask", nic.getIPv4Netmask());
+            ipv4Network.addProperty("network_id", nic.getUuid());
+            ipv4Network.addProperty("type", "ipv4");
+
+            JsonArray ipv4RouteArray = new JsonArray();
+            JsonObject ipv4Route = new JsonObject();
+            ipv4Route.addProperty("gateway", nic.getIPv4Gateway());
+            ipv4Route.addProperty("netmask", "0.0.0.0");
+            ipv4Route.addProperty("network", "0.0.0.0");
+            ipv4RouteArray.add(ipv4Route);
+
+            ipv4Network.add("routes", ipv4RouteArray);
+
+            networks.add(ipv4Network);
+        }
+
+        if (StringUtils.isNotBlank(nic.getIPv6Address())) {
+            JsonObject ipv6Network = new JsonObject();
+            ipv6Network.addProperty("id", String.format("eth%d", 
nic.getDeviceId()));
+            ipv6Network.addProperty("ip_address", nic.getIPv6Address());
+            ipv6Network.addProperty("link", String.format("eth%d", 
nic.getDeviceId()));
+            ipv6Network.addProperty("netmask", 
IPv6Network.fromString(nic.getIPv6Cidr()).getNetmask().toString());
+            ipv6Network.addProperty("network_id", nic.getUuid());
+            ipv6Network.addProperty("type", "ipv6");
+
+            JsonArray ipv6RouteArray = new JsonArray();
+            JsonObject ipv6Route = new JsonObject();
+            ipv6Route.addProperty("gateway", nic.getIPv6Gateway());
+            ipv6Route.addProperty("netmask", "0");
+            ipv6Route.addProperty("network", "::");
+            ipv6RouteArray.add(ipv6Route);
+
+            ipv6Network.add("routes", ipv6RouteArray);
+
+            networks.add(ipv6Network);
+        }
+        return networks;
+    }
+
+    static JsonArray getServicesJsonArrayForNic(NicProfile nic) {
+        JsonArray services = new JsonArray();
+        if (StringUtils.isNotBlank(nic.getIPv4Dns1())) {
+            services.add(getDnsServiceObject(nic.getIPv4Dns1()));
+        }
+
+        if (StringUtils.isNotBlank(nic.getIPv4Dns2())) {
+            services.add(getDnsServiceObject(nic.getIPv4Dns2()));
+        }
+
+        if (StringUtils.isNotBlank(nic.getIPv6Dns1())) {
+            services.add(getDnsServiceObject(nic.getIPv6Dns1()));
+        }
+
+        if (StringUtils.isNotBlank(nic.getIPv6Dns2())) {
+            services.add(getDnsServiceObject(nic.getIPv6Dns2()));
+        }
+        return services;
+    }
+
+    private static JsonObject getDnsServiceObject(String dnsAddress) {
+        JsonObject dnsService = new JsonObject();
+        dnsService.addProperty("address", dnsAddress);
+        dnsService.addProperty("type", "dns");
+        return dnsService;
+    }
+
     static void 
createFileInTempDirAnAppendOpenStackMetadataToJsonObject(String tempDirName, 
JsonObject metaData, String dataType, String fileName, String content, 
Map<String, String> customUserdataParams) {
         if (StringUtils.isBlank(dataType)) {
             return;
diff --git 
a/engine/storage/configdrive/src/main/java/org/apache/cloudstack/storage/configdrive/ConfigDriveUtils.java
 
b/engine/storage/configdrive/src/main/java/org/apache/cloudstack/storage/configdrive/ConfigDriveUtils.java
new file mode 100644
index 00000000000..8847497f193
--- /dev/null
+++ 
b/engine/storage/configdrive/src/main/java/org/apache/cloudstack/storage/configdrive/ConfigDriveUtils.java
@@ -0,0 +1,54 @@
+// 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.storage.configdrive;
+
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+public class ConfigDriveUtils {
+
+    static void mergeJsonArraysAndUpdateObject(JsonObject finalObject, 
JsonObject newObj, String memberName, String... keys) {
+        JsonArray existingMembers = finalObject.has(memberName) ? 
finalObject.get(memberName).getAsJsonArray() : new JsonArray();
+        JsonArray newMembers = newObj.has(memberName) ? 
newObj.get(memberName).getAsJsonArray() : new JsonArray();
+
+        if (existingMembers.size() > 0 || newMembers.size() > 0) {
+            JsonArray finalMembers = new JsonArray();
+            Set<String> idSet = new HashSet<>();
+            for (JsonElement element : existingMembers.getAsJsonArray()) {
+                JsonObject elementObject = element.getAsJsonObject();
+                String key = 
Arrays.stream(keys).map(elementObject::get).map(JsonElement::getAsString).reduce((a,
 b) -> a + "-" + b).orElse("");
+                idSet.add(key);
+                finalMembers.add(element);
+            }
+            for (JsonElement element : newMembers.getAsJsonArray()) {
+                JsonObject elementObject = element.getAsJsonObject();
+                String key = 
Arrays.stream(keys).map(elementObject::get).map(JsonElement::getAsString).reduce((a,
 b) -> a + "-" + b).orElse("");
+                if (!idSet.contains(key)) {
+                    finalMembers.add(element);
+                }
+            }
+            finalObject.add(memberName, finalMembers);
+        }
+    }
+
+}
diff --git 
a/engine/storage/configdrive/src/test/java/org/apache/cloudstack/storage/configdrive/ConfigDriveBuilderTest.java
 
b/engine/storage/configdrive/src/test/java/org/apache/cloudstack/storage/configdrive/ConfigDriveBuilderTest.java
index eff881065c2..3effdb5ba21 100644
--- 
a/engine/storage/configdrive/src/test/java/org/apache/cloudstack/storage/configdrive/ConfigDriveBuilderTest.java
+++ 
b/engine/storage/configdrive/src/test/java/org/apache/cloudstack/storage/configdrive/ConfigDriveBuilderTest.java
@@ -20,6 +20,7 @@ package org.apache.cloudstack.storage.configdrive;
 import static org.mockito.ArgumentMatchers.anyMap;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.nullable;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.times;
 
 import java.io.File;
@@ -27,14 +28,21 @@ import java.io.IOException;
 import java.nio.charset.Charset;
 import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 
+import com.cloud.network.Network;
+import com.cloud.vm.NicProfile;
+import com.google.gson.JsonParser;
 import org.apache.commons.io.FileUtils;
 import org.apache.commons.lang3.StringUtils;
 import org.junit.Assert;
+import org.junit.BeforeClass;
 import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
 import org.junit.runner.RunWith;
 import org.mockito.InOrder;
 import org.mockito.MockedConstruction;
@@ -49,6 +57,13 @@ import com.google.gson.JsonObject;
 @RunWith(MockitoJUnitRunner.class)
 public class ConfigDriveBuilderTest {
 
+    private static Map<Long, List<Network.Service>> supportedServices;
+
+    @BeforeClass
+    public static void beforeClass() throws Exception {
+        supportedServices = Map.of(1L, List.of(Network.Service.UserData, 
Network.Service.Dhcp, Network.Service.Dns));
+    }
+
     @Test
     public void writeFileTest() {
         try (MockedStatic<FileUtils> fileUtilsMocked = 
Mockito.mockStatic(FileUtils.class)) {
@@ -112,16 +127,16 @@ public class ConfigDriveBuilderTest {
     }
 
     @Test(expected = CloudRuntimeException.class)
-    public void buildConfigDriveTestNoVmData() {
-        ConfigDriveBuilder.buildConfigDrive(null, "teste", "C:", null);
+    public void buildConfigDriveTestNoVmDataAndNic() {
+        ConfigDriveBuilder.buildConfigDrive(null, null, "teste", "C:", null, 
null);
     }
 
     @Test(expected = CloudRuntimeException.class)
     public void buildConfigDriveTestIoException() {
         try (MockedStatic<ConfigDriveBuilder> configDriveBuilderMocked = 
Mockito.mockStatic(ConfigDriveBuilder.class)) {
-            configDriveBuilderMocked.when(() -> 
ConfigDriveBuilder.writeVendorAndNetworkEmptyJsonFile(nullable(File.class))).thenThrow(CloudRuntimeException.class);
-            Mockito.when(ConfigDriveBuilder.buildConfigDrive(new 
ArrayList<>(), "teste", "C:", null)).thenCallRealMethod();
-            ConfigDriveBuilder.buildConfigDrive(new ArrayList<>(), "teste", 
"C:", null);
+            configDriveBuilderMocked.when(() -> 
ConfigDriveBuilder.writeVendorEmptyJsonFile(nullable(File.class))).thenThrow(CloudRuntimeException.class);
+            Mockito.when(ConfigDriveBuilder.buildConfigDrive(null, new 
ArrayList<>(), "teste", "C:", null, supportedServices)).thenCallRealMethod();
+            ConfigDriveBuilder.buildConfigDrive(null, new ArrayList<>(), 
"teste", "C:", null, supportedServices);
         }
     }
 
@@ -129,22 +144,26 @@ public class ConfigDriveBuilderTest {
     public void buildConfigDriveTest() {
         try (MockedStatic<ConfigDriveBuilder> configDriveBuilderMocked = 
Mockito.mockStatic(ConfigDriveBuilder.class)) {
 
-            configDriveBuilderMocked.when(() -> 
ConfigDriveBuilder.writeVendorAndNetworkEmptyJsonFile(Mockito.any(File.class))).then(invocationOnMock
 -> null);
+            configDriveBuilderMocked.when(() -> 
ConfigDriveBuilder.writeVendorEmptyJsonFile(Mockito.any(File.class))).then(invocationOnMock
 -> null);
 
             configDriveBuilderMocked.when(() -> 
ConfigDriveBuilder.writeVmMetadata(Mockito.anyList(), Mockito.anyString(), 
Mockito.any(File.class), anyMap())).then(invocationOnMock -> null);
 
             configDriveBuilderMocked.when(() -> 
ConfigDriveBuilder.linkUserData((Mockito.anyString()))).then(invocationOnMock 
-> null);
 
             configDriveBuilderMocked.when(() -> 
ConfigDriveBuilder.generateAndRetrieveIsoAsBase64Iso(Mockito.anyString(), 
Mockito.anyString(), Mockito.anyString())).thenAnswer(invocation -> 
"mockIsoDataBase64");
+
+            NicProfile mockedNicProfile = Mockito.mock(NicProfile.class);
+            Mockito.when(mockedNicProfile.getId()).thenReturn(1L);
+
             //force execution of real method
-            Mockito.when(ConfigDriveBuilder.buildConfigDrive(new 
ArrayList<>(), "teste", "C:", null)).thenCallRealMethod();
+            
Mockito.when(ConfigDriveBuilder.buildConfigDrive(List.of(mockedNicProfile), new 
ArrayList<>(), "teste", "C:", null, supportedServices)).thenCallRealMethod();
 
-            String returnedIsoData = ConfigDriveBuilder.buildConfigDrive(new 
ArrayList<>(), "teste", "C:", null);
+            String returnedIsoData = 
ConfigDriveBuilder.buildConfigDrive(List.of(mockedNicProfile), new 
ArrayList<>(), "teste", "C:", null, supportedServices);
 
             Assert.assertEquals("mockIsoDataBase64", returnedIsoData);
 
             configDriveBuilderMocked.verify(() -> {
-                
ConfigDriveBuilder.writeVendorAndNetworkEmptyJsonFile(Mockito.any(File.class));
+                
ConfigDriveBuilder.writeVendorEmptyJsonFile(Mockito.any(File.class));
                 ConfigDriveBuilder.writeVmMetadata(Mockito.anyList(), 
Mockito.anyString(), Mockito.any(File.class), anyMap());
                 ConfigDriveBuilder.linkUserData(Mockito.anyString());
                 
ConfigDriveBuilder.generateAndRetrieveIsoAsBase64Iso(Mockito.anyString(), 
Mockito.anyString(), Mockito.anyString());
@@ -153,23 +172,23 @@ public class ConfigDriveBuilderTest {
     }
 
     @Test(expected = CloudRuntimeException.class)
-    public void 
writeVendorAndNetworkEmptyJsonFileTestCannotCreateOpenStackFolder() {
+    public void writeVendorEmptyJsonFileTestCannotCreateOpenStackFolder() {
         File folderFileMock = Mockito.mock(File.class);
         Mockito.doReturn(false).when(folderFileMock).mkdirs();
 
-        ConfigDriveBuilder.writeVendorAndNetworkEmptyJsonFile(folderFileMock);
+        ConfigDriveBuilder.writeVendorEmptyJsonFile(folderFileMock);
     }
 
     @Test(expected = CloudRuntimeException.class)
-    public void writeVendorAndNetworkEmptyJsonFileTest() {
+    public void writeVendorEmptyJsonFileTest() {
         File folderFileMock = Mockito.mock(File.class);
         Mockito.doReturn(false).when(folderFileMock).mkdirs();
 
-        ConfigDriveBuilder.writeVendorAndNetworkEmptyJsonFile(folderFileMock);
+        ConfigDriveBuilder.writeVendorEmptyJsonFile(folderFileMock);
     }
 
     @Test
-    public void writeVendorAndNetworkEmptyJsonFileTestCreatingFolder() {
+    public void writeVendorEmptyJsonFileTestCreatingFolder() {
         try (MockedStatic<ConfigDriveBuilder> configDriveBuilderMocked = 
Mockito.mockStatic(ConfigDriveBuilder.class)) {
 
             File folderFileMock = Mockito.mock(File.class);
@@ -177,9 +196,9 @@ public class ConfigDriveBuilderTest {
             Mockito.doReturn(true).when(folderFileMock).mkdirs();
 
             //force execution of real method
-            configDriveBuilderMocked.when(() -> 
ConfigDriveBuilder.writeVendorAndNetworkEmptyJsonFile(folderFileMock)).thenCallRealMethod();
+            configDriveBuilderMocked.when(() -> 
ConfigDriveBuilder.writeVendorEmptyJsonFile(folderFileMock)).thenCallRealMethod();
 
-            
ConfigDriveBuilder.writeVendorAndNetworkEmptyJsonFile(folderFileMock);
+            ConfigDriveBuilder.writeVendorEmptyJsonFile(folderFileMock);
 
             Mockito.verify(folderFileMock).exists();
             Mockito.verify(folderFileMock).mkdirs();
@@ -501,4 +520,143 @@ public class ConfigDriveBuilderTest {
             Mockito.verify(mkIsoProgramInMacOsFileMock, 
Mockito.times(1)).getCanonicalPath();
         }
     }
+
+    @Test
+    public void testWriteNetworkData() throws Exception {
+        // Setup
+        NicProfile nicp = mock(NicProfile.class);
+        Mockito.when(nicp.getId()).thenReturn(1L);
+
+        Mockito.when(nicp.getMacAddress()).thenReturn("00:00:00:00:00:00");
+        Mockito.when(nicp.getMtu()).thenReturn(2000);
+
+        Mockito.when(nicp.getIPv4Address()).thenReturn("172.31.0.10");
+        Mockito.when(nicp.getDeviceId()).thenReturn(1);
+        Mockito.when(nicp.getIPv4Netmask()).thenReturn("255.255.255.0");
+        Mockito.when(nicp.getUuid()).thenReturn("NETWORK UUID");
+        Mockito.when(nicp.getIPv4Gateway()).thenReturn("172.31.0.1");
+
+
+        
Mockito.when(nicp.getIPv6Address()).thenReturn("2001:db8:0:1234:0:567:8:1");
+        
Mockito.when(nicp.getIPv6Cidr()).thenReturn("2001:db8:0:1234:0:567:8:1/64");
+        
Mockito.when(nicp.getIPv6Gateway()).thenReturn("2001:db8:0:1234:0:567:8::1");
+
+        Mockito.when(nicp.getIPv4Dns1()).thenReturn("8.8.8.8");
+        Mockito.when(nicp.getIPv4Dns2()).thenReturn("1.1.1.1");
+        Mockito.when(nicp.getIPv6Dns1()).thenReturn("2001:4860:4860::8888");
+        Mockito.when(nicp.getIPv6Dns2()).thenReturn("2001:4860:4860::8844");
+
+
+        List<Network.Service> services1 = Arrays.asList(Network.Service.Dhcp, 
Network.Service.Dns);
+
+        Map<Long, List<Network.Service>> supportedServices = new HashMap<>();
+        supportedServices.put(1L, services1);
+
+        TemporaryFolder folder = new TemporaryFolder();
+        folder.create();
+        File openStackFolder = folder.newFolder("openStack");
+
+        // Expected JSON structure
+        String expectedJson = "{" +
+                "  \"links\": [" +
+                "    {" +
+                "      \"ethernet_mac_address\": \"00:00:00:00:00:00\"," +
+                "      \"id\": \"eth1\"," +
+                "      \"mtu\": 2000," +
+                "      \"type\": \"phy\"" +
+                "    }" +
+                "  ]," +
+                "  \"networks\": [" +
+                "    {" +
+                "      \"id\": \"eth1\"," +
+                "      \"ip_address\": \"172.31.0.10\"," +
+                "      \"link\": \"eth1\"," +
+                "      \"netmask\": \"255.255.255.0\"," +
+                "      \"network_id\": \"NETWORK UUID\"," +
+                "      \"type\": \"ipv4\"," +
+                "      \"routes\": [" +
+                "        {" +
+                "          \"gateway\": \"172.31.0.1\"," +
+                "          \"netmask\": \"0.0.0.0\"," +
+                "          \"network\": \"0.0.0.0\"" +
+                "        }" +
+                "      ]" +
+                "    }," +
+                "    {" +
+                "      \"id\": \"eth1\"," +
+                "      \"ip_address\": \"2001:db8:0:1234:0:567:8:1\"," +
+                "      \"link\": \"eth1\"," +
+                "      \"netmask\": \"64\"," +
+                "      \"network_id\": \"NETWORK UUID\"," +
+                "      \"type\": \"ipv6\"," +
+                "      \"routes\": [" +
+                "        {" +
+                "          \"gateway\": \"2001:db8:0:1234:0:567:8::1\"," +
+                "          \"netmask\": \"0\"," +
+                "          \"network\": \"::\"" +
+                "        }" +
+                "      ]" +
+                "    }" +
+                "  ]," +
+                "  \"services\": [" +
+                "    {" +
+                "      \"address\": \"8.8.8.8\"," +
+                "      \"type\": \"dns\"" +
+                "    }," +
+                "    {" +
+                "      \"address\": \"1.1.1.1\"," +
+                "      \"type\": \"dns\"" +
+                "    }," +
+                "    {" +
+                "      \"address\": \"2001:4860:4860::8888\"," +
+                "      \"type\": \"dns\"" +
+                "    }," +
+                "    {" +
+                "      \"address\": \"2001:4860:4860::8844\"," +
+                "      \"type\": \"dns\"" +
+                "    }" +
+                "  ]" +
+                "}";
+
+        // Action
+        ConfigDriveBuilder.writeNetworkData(Arrays.asList(nicp), 
supportedServices, openStackFolder);
+
+        // Verify
+        File networkDataFile = new File(openStackFolder, "network_data.json");
+        String content = FileUtils.readFileToString(networkDataFile, 
StandardCharsets.UTF_8);
+        JsonObject actualJson = new 
JsonParser().parse(content).getAsJsonObject();
+        JsonObject expectedJsonObject = new 
JsonParser().parse(expectedJson).getAsJsonObject();
+
+        Assert.assertEquals(expectedJsonObject, actualJson);
+        folder.delete();
+    }
+
+    @Test
+    public void testWriteNetworkDataEmptyJson() throws Exception {
+        // Setup
+        NicProfile nicp = mock(NicProfile.class);
+        List<Network.Service> services1 = Collections.emptyList();
+
+        Map<Long, List<Network.Service>> supportedServices = new HashMap<>();
+        supportedServices.put(1L, services1);
+
+        TemporaryFolder folder = new TemporaryFolder();
+        folder.create();
+        File openStackFolder = folder.newFolder("openStack");
+
+        // Expected JSON structure
+        String expectedJson = "{}";
+
+        // Action
+        ConfigDriveBuilder.writeNetworkData(Arrays.asList(nicp), 
supportedServices, openStackFolder);
+
+        // Verify
+        File networkDataFile = new File(openStackFolder, "network_data.json");
+        String content = FileUtils.readFileToString(networkDataFile, 
StandardCharsets.UTF_8);
+        JsonObject actualJson = new 
JsonParser().parse(content).getAsJsonObject();
+        JsonObject expectedJsonObject = new 
JsonParser().parse(expectedJson).getAsJsonObject();
+
+        Assert.assertEquals(expectedJsonObject, actualJson);
+        folder.delete();
+    }
 }
diff --git 
a/engine/storage/configdrive/src/test/java/org/apache/cloudstack/storage/configdrive/ConfigDriveUtilsTest.java
 
b/engine/storage/configdrive/src/test/java/org/apache/cloudstack/storage/configdrive/ConfigDriveUtilsTest.java
new file mode 100644
index 00000000000..6e935b951da
--- /dev/null
+++ 
b/engine/storage/configdrive/src/test/java/org/apache/cloudstack/storage/configdrive/ConfigDriveUtilsTest.java
@@ -0,0 +1,108 @@
+// 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.storage.configdrive;
+
+import static 
org.apache.cloudstack.storage.configdrive.ConfigDriveUtils.mergeJsonArraysAndUpdateObject;
+
+import com.google.gson.JsonArray;
+import com.google.gson.JsonParser;
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.junit.MockitoJUnitRunner;
+
+import com.google.gson.JsonObject;
+
+@RunWith(MockitoJUnitRunner.class)
+public class ConfigDriveUtilsTest {
+
+    @Test
+    public void testMergeJsonArraysAndUpdateObjectWithEmptyObjects() {
+        JsonObject finalObject = new JsonObject();
+        JsonObject newObj = new JsonObject();
+        mergeJsonArraysAndUpdateObject(finalObject, newObj, "links", "id", 
"type");
+        Assert.assertEquals("{}", finalObject.toString());
+    }
+
+    @Test
+    public void testMergeJsonArraysAndUpdateObjectWithNewMembersAdded() {
+        JsonObject finalObject = new JsonObject();
+
+        JsonObject newObj = new JsonObject();
+        JsonArray newMembers = new JsonArray();
+        JsonObject newMember = new JsonObject();
+        newMember.addProperty("id", "eth0");
+        newMember.addProperty("type", "phy");
+        newMembers.add(newMember);
+        newObj.add("links", newMembers);
+
+        mergeJsonArraysAndUpdateObject(finalObject, newObj, "links", "id", 
"type");
+        Assert.assertEquals(1, finalObject.getAsJsonArray("links").size());
+        JsonObject expectedObj = new JsonParser().parse("{'links': [{'id': 
'eth0', 'type': 'phy'}]}").getAsJsonObject();
+        Assert.assertEquals(expectedObj, finalObject);
+    }
+
+    @Test
+    public void 
testMergeJsonArraysAndUpdateObjectWithDuplicateMembersIgnored() {
+        JsonObject finalObject = new JsonObject();
+        JsonArray existingMembers = new JsonArray();
+        JsonObject existingMember = new JsonObject();
+        existingMember.addProperty("id", "eth0");
+        existingMember.addProperty("type", "phy");
+        existingMembers.add(existingMember);
+        finalObject.add("links", existingMembers);
+
+        JsonObject newObj = new JsonObject();
+        newObj.add("links", existingMembers); // same as existingMembers for 
duplication
+
+        mergeJsonArraysAndUpdateObject(finalObject, newObj, "links", "id", 
"type");
+        Assert.assertEquals(1, finalObject.getAsJsonArray("links").size());
+        JsonObject expectedObj = new JsonParser().parse("{'links': [{'id': 
'eth0', 'type': 'phy'}]}").getAsJsonObject();
+        Assert.assertEquals(expectedObj, finalObject);
+    }
+
+    @Test
+    public void testMergeJsonArraysAndUpdateObjectWithDifferentMembers() {
+        JsonObject finalObject = new JsonObject();
+
+        JsonArray newMembers = new JsonArray();
+        JsonObject newMember = new JsonObject();
+        newMember.addProperty("id", "eth0");
+        newMember.addProperty("type", "phy");
+        newMembers.add(newMember);
+        finalObject.add("links", newMembers);
+
+        JsonObject newObj = new JsonObject();
+        newMembers = new JsonArray();
+        newMember = new JsonObject();
+        newMember.addProperty("id", "eth1");
+        newMember.addProperty("type", "phy");
+        newMembers.add(newMember);
+        newObj.add("links", newMembers);
+
+        mergeJsonArraysAndUpdateObject(finalObject, newObj, "links", "id", 
"type");
+        Assert.assertEquals(2, finalObject.getAsJsonArray("links").size());
+        JsonObject expectedObj = new JsonParser().parse("{'links': [{'id': 
'eth0', 'type': 'phy'}, {'id': 'eth1', 'type': 'phy'}]}").getAsJsonObject();
+        Assert.assertEquals(expectedObj, finalObject);
+    }
+
+    @Test(expected = NullPointerException.class)
+    public void testMergeJsonArraysAndUpdateObjectWithNullObjects() {
+        mergeJsonArraysAndUpdateObject(null, null, "services", "id", "type");
+    }
+}
diff --git a/server/src/main/java/com/cloud/network/NetworkModelImpl.java 
b/server/src/main/java/com/cloud/network/NetworkModelImpl.java
index aadce946193..47c29f63717 100644
--- a/server/src/main/java/com/cloud/network/NetworkModelImpl.java
+++ b/server/src/main/java/com/cloud/network/NetworkModelImpl.java
@@ -2174,7 +2174,6 @@ public class NetworkModelImpl extends ManagerBase 
implements NetworkModel, Confi
         NetworkVO network = _networksDao.findById(networkId);
         Integer networkRate = getNetworkRate(network.getId(), vm.getId());
 
-//        NetworkGuru guru = _networkGurus.get(network.getGuruName());
         NicProfile profile =
             new NicProfile(nic, network, nic.getBroadcastUri(), 
nic.getIsolationUri(), networkRate, isSecurityGroupSupportedInNetwork(network), 
getNetworkTag(
                 vm.getHypervisorType(), network));
@@ -2184,7 +2183,17 @@ public class NetworkModelImpl extends ManagerBase 
implements NetworkModel, Confi
         if (network.getTrafficType() == TrafficType.Guest && 
network.getPrivateMtu() != null) {
             profile.setMtu(network.getPrivateMtu());
         }
-//        guru.updateNicProfile(profile, network);
+
+        DataCenter dc = _dcDao.findById(network.getDataCenterId());
+
+        Pair<String, String> ip4Dns = getNetworkIp4Dns(network, dc);
+        profile.setIPv4Dns1(ip4Dns.first());
+        profile.setIPv4Dns2(ip4Dns.second());
+
+        Pair<String, String> ip6Dns = getNetworkIp6Dns(network, dc);
+        profile.setIPv6Dns1(ip6Dns.first());
+        profile.setIPv6Dns2(ip6Dns.second());
+
         return profile;
     }
 
diff --git 
a/server/src/main/java/com/cloud/network/element/ConfigDriveNetworkElement.java 
b/server/src/main/java/com/cloud/network/element/ConfigDriveNetworkElement.java
index a9fa3e95275..3449f1f5d00 100644
--- 
a/server/src/main/java/com/cloud/network/element/ConfigDriveNetworkElement.java
+++ 
b/server/src/main/java/com/cloud/network/element/ConfigDriveNetworkElement.java
@@ -16,6 +16,7 @@
 // under the License.
 package com.cloud.network.element;
 
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashMap;
 import java.util.List;
@@ -24,6 +25,7 @@ import java.util.Set;
 
 import javax.inject.Inject;
 
+import 
org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService;
 import org.apache.cloudstack.engine.subsystem.api.storage.DataStore;
 import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreManager;
 import org.apache.cloudstack.engine.subsystem.api.storage.EndPoint;
@@ -90,7 +92,8 @@ import com.cloud.vm.VmDetailConstants;
 import com.cloud.vm.dao.UserVmDao;
 import com.cloud.vm.dao.UserVmDetailsDao;
 
-public class ConfigDriveNetworkElement extends AdapterBase implements 
NetworkElement, UserDataServiceProvider,
+public class ConfigDriveNetworkElement extends AdapterBase implements 
NetworkElement,
+        UserDataServiceProvider, DhcpServiceProvider, DnsServiceProvider,
         StateListener<VirtualMachine.State, VirtualMachine.Event, 
VirtualMachine>, NetworkMigrationResponder {
 
     private static final Map<Service, Map<Capability, String>> capabilities = 
setCapabilities();
@@ -110,6 +113,8 @@ public class ConfigDriveNetworkElement extends AdapterBase 
implements NetworkEle
     @Inject
     NetworkModel _networkModel;
     @Inject
+    NetworkOrchestrationService _networkOrchestrationService;
+    @Inject
     GuestOSCategoryDao _guestOSCategoryDao;
     @Inject
     GuestOSDao _guestOSDao;
@@ -197,6 +202,8 @@ public class ConfigDriveNetworkElement extends AdapterBase 
implements NetworkEle
     private static Map<Service, Map<Capability, String>> setCapabilities() {
         Map<Service, Map<Capability, String>> capabilities = new HashMap<>();
         capabilities.put(Service.UserData, null);
+        capabilities.put(Service.Dhcp, new HashMap<>());
+        capabilities.put(Service.Dns, new HashMap<>());
         return capabilities;
     }
 
@@ -224,8 +231,7 @@ public class ConfigDriveNetworkElement extends AdapterBase 
implements NetworkEle
     public boolean addPasswordAndUserdata(Network network, NicProfile nic, 
VirtualMachineProfile profile, DeployDestination dest, ReservationContext 
context)
             throws ConcurrentOperationException, 
InsufficientCapacityException, ResourceUnavailableException {
         return (canHandle(network.getTrafficType())
-                && configureConfigDriveData(profile, nic, dest))
-                && createConfigDriveIso(profile, dest, null);
+                && configureConfigDriveData(profile, nic, dest));
     }
 
     @Override
@@ -342,10 +348,13 @@ public class ConfigDriveNetworkElement extends 
AdapterBase implements NetworkEle
                     configureConfigDriveData(vm, nic, dest);
 
                     // Create the config drive on dest host cache
-                    createConfigDriveIsoOnHostCache(vm, 
dest.getHost().getId());
+                    createConfigDriveIsoOnHostCache(nic, vm, 
dest.getHost().getId());
                 } else {
                     
vm.setConfigDriveLocation(getConfigDriveLocation(vm.getId()));
-                    addPasswordAndUserdata(network, nic, vm, dest, context);
+                    boolean result = addPasswordAndUserdata(network, nic, vm, 
dest, context);
+                    if (result) {
+                        createConfigDriveIso(nic, vm, dest, null);
+                    }
                 }
             } catch (InsufficientCapacityException | 
ResourceUnavailableException e) {
                 logger.error("Failed to add config disk drive due to: ", e);
@@ -398,7 +407,7 @@ public class ConfigDriveNetworkElement extends AdapterBase 
implements NetworkEle
                         vm.getUuid(), nic.getMacAddress(), 
userVm.getDetail("SSH.PublicKey"), (String) 
vm.getParameter(VirtualMachineProfile.Param.VmPassword), isWindows, 
VirtualMachineManager.getHypervisorHostname(dest.getHost() != null ? 
dest.getHost().getName() : ""));
                 vm.setVmData(vmData);
                 
vm.setConfigDriveLabel(VirtualMachineManager.VmConfigDriveLabel.value());
-                createConfigDriveIso(vm, dest, diskToUse);
+                createConfigDriveIso(nic, vm, dest, diskToUse);
             }
         }
     }
@@ -528,7 +537,7 @@ public class ConfigDriveNetworkElement extends AdapterBase 
implements NetworkEle
         return false;
     }
 
-    private boolean createConfigDriveIsoOnHostCache(VirtualMachineProfile 
profile, Long hostId) throws ResourceUnavailableException {
+    private boolean createConfigDriveIsoOnHostCache(NicProfile nic, 
VirtualMachineProfile profile, Long hostId) throws ResourceUnavailableException 
{
         if (hostId == null) {
             throw new ResourceUnavailableException("Config drive iso creation 
failed, dest host not available",
                     ConfigDriveNetworkElement.class, 0L);
@@ -540,7 +549,9 @@ public class ConfigDriveNetworkElement extends AdapterBase 
implements NetworkEle
 
         final String isoFileName = 
ConfigDrive.configIsoFileName(profile.getInstanceName());
         final String isoPath = 
ConfigDrive.createConfigDrivePath(profile.getInstanceName());
-        final String isoData = 
ConfigDriveBuilder.buildConfigDrive(profile.getVmData(), isoFileName, 
profile.getConfigDriveLabel(), customUserdataParamMap);
+        List<NicProfile> nicProfiles = 
_networkOrchestrationService.getNicProfiles(nic.getVirtualMachineId(), 
profile.getHypervisorType());
+        final Map<Long, List<Service>> supportedServices = 
getSupportedServicesByElementForNetwork(nicProfiles);
+        final String isoData = 
ConfigDriveBuilder.buildConfigDrive(nicProfiles, profile.getVmData(), 
isoFileName, profile.getConfigDriveLabel(), customUserdataParamMap, 
supportedServices);
         final HandleConfigDriveIsoCommand configDriveIsoCommand = new 
HandleConfigDriveIsoCommand(isoPath, isoData, null, false, true, true);
 
         final HandleConfigDriveIsoAnswer answer = (HandleConfigDriveIsoAnswer) 
agentManager.easySend(hostId, configDriveIsoCommand);
@@ -590,7 +601,27 @@ public class ConfigDriveNetworkElement extends AdapterBase 
implements NetworkEle
         return true;
     }
 
-    private boolean createConfigDriveIso(VirtualMachineProfile profile, 
DeployDestination dest, DiskTO disk) throws ResourceUnavailableException {
+    private Map<Long, List<Network.Service>> 
getSupportedServicesByElementForNetwork(List<NicProfile> nics) {
+
+        Map<Long, List<Network.Service>> supportedServices = new HashMap<>();
+        for (NicProfile nic: nics) {
+            ArrayList<Network.Service> serviceList = new ArrayList<>();
+            if 
(_networkModel.isProviderSupportServiceInNetwork(nic.getNetworkId(), 
Service.Dns, getProvider())) {
+                serviceList.add(Service.Dns);
+            }
+            if 
(_networkModel.isProviderSupportServiceInNetwork(nic.getNetworkId(), 
Service.UserData, getProvider())) {
+                serviceList.add(Service.UserData);
+            }
+            if 
(_networkModel.isProviderSupportServiceInNetwork(nic.getNetworkId(), 
Service.Dhcp, getProvider())) {
+                serviceList.add(Service.Dhcp);
+            }
+            supportedServices.put(nic.getId(), serviceList);
+        }
+
+        return supportedServices;
+    }
+
+    public boolean createConfigDriveIso(NicProfile nic, VirtualMachineProfile 
profile, DeployDestination dest, DiskTO disk) throws 
ResourceUnavailableException {
         DataStore dataStore = getDatastoreForConfigDriveIso(disk, profile, 
dest);
 
         final Long agentId = findAgentId(profile, dest, dataStore);
@@ -605,7 +636,10 @@ public class ConfigDriveNetworkElement extends AdapterBase 
implements NetworkEle
 
         final String isoFileName = 
ConfigDrive.configIsoFileName(profile.getInstanceName());
         final String isoPath = 
ConfigDrive.createConfigDrivePath(profile.getInstanceName());
-        final String isoData = 
ConfigDriveBuilder.buildConfigDrive(profile.getVmData(), isoFileName, 
profile.getConfigDriveLabel(), customUserdataParamMap);
+        List<NicProfile> nicProfiles = 
_networkOrchestrationService.getNicProfiles(nic.getVirtualMachineId(), 
profile.getHypervisorType());
+        final Map<Long, List<Service>> supportedServices = 
getSupportedServicesByElementForNetwork(nicProfiles);
+        final String isoData = ConfigDriveBuilder.buildConfigDrive(
+                nicProfiles, profile.getVmData(), isoFileName, 
profile.getConfigDriveLabel(), customUserdataParamMap, supportedServices);
         boolean useHostCacheOnUnsupportedPool = 
VirtualMachineManager.VmConfigDriveUseHostCacheOnUnsupportedPool.valueIn(dest.getDataCenter().getId());
         boolean preferHostCache = 
VirtualMachineManager.VmConfigDriveForceHostCacheUse.valueIn(dest.getDataCenter().getId());
         final HandleConfigDriveIsoCommand configDriveIsoCommand = new 
HandleConfigDriveIsoCommand(isoPath, isoData, dataStore.getTO(), 
useHostCacheOnUnsupportedPool, preferHostCache, true);
@@ -758,4 +792,52 @@ public class ConfigDriveNetworkElement extends AdapterBase 
implements NetworkEle
         return true;
     }
 
+    @Override
+    public boolean addDhcpEntry(Network network, NicProfile nic, 
VirtualMachineProfile vm, DeployDestination dest,
+            ReservationContext context) throws ConcurrentOperationException, 
InsufficientCapacityException, ResourceUnavailableException {
+        // Update nic profile with required information.
+        // Add network checks
+        return true;
+    }
+
+    @Override
+    public boolean configDhcpSupportForSubnet(Network network, NicProfile nic, 
VirtualMachineProfile vm,
+            DeployDestination dest,
+            ReservationContext context) throws ConcurrentOperationException, 
InsufficientCapacityException, ResourceUnavailableException {
+        return false;
+    }
+
+    @Override
+    public boolean removeDhcpSupportForSubnet(Network network) throws 
ResourceUnavailableException {
+        return true;
+    }
+
+    @Override
+    public boolean setExtraDhcpOptions(Network network, long nicId, 
Map<Integer, String> dhcpOptions) {
+        return false;
+    }
+
+    @Override
+    public boolean removeDhcpEntry(Network network, NicProfile nic,
+            VirtualMachineProfile vmProfile) throws 
ResourceUnavailableException {
+        return true;
+    }
+
+    @Override
+    public boolean addDnsEntry(Network network, NicProfile nic, 
VirtualMachineProfile vm, DeployDestination dest,
+            ReservationContext context) throws ConcurrentOperationException, 
InsufficientCapacityException, ResourceUnavailableException {
+        return true;
+    }
+
+    @Override
+    public boolean configDnsSupportForSubnet(Network network, NicProfile nic, 
VirtualMachineProfile vm,
+            DeployDestination dest,
+            ReservationContext context) throws ConcurrentOperationException, 
InsufficientCapacityException, ResourceUnavailableException {
+        return true;
+    }
+
+    @Override
+    public boolean removeDnsSupportForSubnet(Network network) throws 
ResourceUnavailableException {
+        return true;
+    }
 }
diff --git 
a/server/src/test/java/com/cloud/network/element/ConfigDriveNetworkElementTest.java
 
b/server/src/test/java/com/cloud/network/element/ConfigDriveNetworkElementTest.java
index d83120d75f3..8c8dc33d7ec 100644
--- 
a/server/src/test/java/com/cloud/network/element/ConfigDriveNetworkElementTest.java
+++ 
b/server/src/test/java/com/cloud/network/element/ConfigDriveNetworkElementTest.java
@@ -61,6 +61,7 @@ import com.cloud.vm.dao.UserVmDetailsDao;
 import com.cloud.vm.dao.VMInstanceDao;
 import com.google.common.collect.Maps;
 import org.apache.cloudstack.context.CallContext;
+import 
org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService;
 import org.apache.cloudstack.engine.subsystem.api.storage.DataStore;
 import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreManager;
 import org.apache.cloudstack.engine.subsystem.api.storage.EndPoint;
@@ -83,6 +84,7 @@ import org.mockito.junit.MockitoJUnitRunner;
 
 import java.lang.reflect.Field;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 
@@ -148,6 +150,7 @@ public class ConfigDriveNetworkElementTest {
     @Mock private AgentManager agentManager;
     @Mock private CallContext callContextMock;
     @Mock private DomainVO domainVO;
+    @Mock private NetworkOrchestrationService _networkOrchestrationService;
 
     @Spy @InjectMocks
     private ConfigDriveNetworkElement _configDrivesNetworkElement = new 
ConfigDriveNetworkElement();
@@ -264,13 +267,9 @@ public class ConfigDriveNetworkElementTest {
         try (MockedStatic<ConfigDriveBuilder> ignored1 = 
Mockito.mockStatic(ConfigDriveBuilder.class); MockedStatic<CallContext> 
ignored2 = Mockito.mockStatic(CallContext.class)) {
             Mockito.when(CallContext.current()).thenReturn(callContextMock);
             
Mockito.doReturn(Mockito.mock(Account.class)).when(callContextMock).getCallingAccount();
-            
Mockito.when(ConfigDriveBuilder.buildConfigDrive(Mockito.anyList(), 
Mockito.anyString(), Mockito.anyString(), 
Mockito.anyMap())).thenReturn("content");
 
             final HandleConfigDriveIsoAnswer answer = 
mock(HandleConfigDriveIsoAnswer.class);
             final UserVmDetailVO userVmDetailVO = mock(UserVmDetailVO.class);
-            when(agentManager.easySend(Mockito.anyLong(), 
Mockito.any(HandleConfigDriveIsoCommand.class))).thenReturn(answer);
-            when(answer.getResult()).thenReturn(true);
-            
when(answer.getConfigDriveLocation()).thenReturn(NetworkElement.Location.PRIMARY);
             
when(network.getTrafficType()).thenReturn(Networks.TrafficType.Guest);
             when(virtualMachine.getUuid()).thenReturn("vm-uuid");
             when(userVmDetailVO.getValue()).thenReturn(PUBLIC_KEY);
@@ -288,6 +287,28 @@ public class ConfigDriveNetworkElementTest {
             profile.setConfigDriveLabel("testlabel");
             assertTrue(_configDrivesNetworkElement.addPasswordAndUserdata(
                     network, nicp, profile, deployDestination, null));
+        }
+    }
+
+    @Test
+    public void testCreateConfigDriveIso() throws Exception {
+        try (MockedStatic<ConfigDriveBuilder> ignored1 = 
Mockito.mockStatic(ConfigDriveBuilder.class); MockedStatic<CallContext> 
ignored2 = Mockito.mockStatic(CallContext.class)) {
+            Mockito.when(CallContext.current()).thenReturn(callContextMock);
+            
Mockito.when(ConfigDriveBuilder.buildConfigDrive(Mockito.anyList(), 
Mockito.anyList(), Mockito.anyString(), Mockito.anyString(), Mockito.anyMap(), 
Mockito.anyMap())).thenReturn("content");
+
+            final HandleConfigDriveIsoAnswer answer = 
mock(HandleConfigDriveIsoAnswer.class);
+            when(agentManager.easySend(Mockito.anyLong(), 
Mockito.any(HandleConfigDriveIsoCommand.class))).thenReturn(answer);
+            when(answer.getResult()).thenReturn(true);
+            
when(answer.getConfigDriveLocation()).thenReturn(NetworkElement.Location.PRIMARY);
+            when(virtualMachine.getUuid()).thenReturn("vm-uuid");
+
+            Map<VirtualMachineProfile.Param, Object> parms = Maps.newHashMap();
+            parms.put(VirtualMachineProfile.Param.VmPassword, PASSWORD);
+            parms.put(VirtualMachineProfile.Param.VmSshPubKey, PUBLIC_KEY);
+            VirtualMachineProfile profile = new 
VirtualMachineProfileImpl(virtualMachine, null, serviceOfferingVO, null, parms);
+            profile.setConfigDriveLabel("testlabel");
+            profile.setVmData(Collections.emptyList());
+            assertTrue(_configDrivesNetworkElement.createConfigDriveIso(nicp, 
profile, deployDestination, null));
 
             ArgumentCaptor<HandleConfigDriveIsoCommand> commandCaptor = 
ArgumentCaptor.forClass(HandleConfigDriveIsoCommand.class);
             verify(agentManager, times(1)).easySend(Mockito.anyLong(), 
commandCaptor.capture());
diff --git a/server/src/test/java/com/cloud/vpc/MockNetworkManagerImpl.java 
b/server/src/test/java/com/cloud/vpc/MockNetworkManagerImpl.java
index 8355648ad1d..68ad250a95e 100644
--- a/server/src/test/java/com/cloud/vpc/MockNetworkManagerImpl.java
+++ b/server/src/test/java/com/cloud/vpc/MockNetworkManagerImpl.java
@@ -25,6 +25,7 @@ import javax.inject.Inject;
 import javax.naming.ConfigurationException;
 
 import com.cloud.dc.DataCenter;
+import com.cloud.hypervisor.Hypervisor;
 import com.cloud.network.PublicIpQuarantine;
 import com.cloud.network.VirtualRouterProvider;
 import com.cloud.utils.fsm.NoTransitionException;
@@ -640,6 +641,11 @@ public class MockNetworkManagerImpl extends ManagerBase 
implements NetworkOrches
         return null;
     }
 
+    @Override
+    public List<NicProfile> getNicProfiles(Long vmId, 
Hypervisor.HypervisorType hypervisorType) {
+        return List.of();
+    }
+
     @Override
     public Map<String, String> getSystemVMAccessDetails(VirtualMachine vm) {
         return null;
diff --git a/test/integration/smoke/test_network.py 
b/test/integration/smoke/test_network.py
index 8f3f4f533dd..b3e7fd3e42f 100644
--- a/test/integration/smoke/test_network.py
+++ b/test/integration/smoke/test_network.py
@@ -17,6 +17,8 @@
 # under the License.
 """ BVT tests for Network Life Cycle
 """
+import json
+
 # Import Local Modules
 from marvin.codes import (FAILED, STATIC_NAT_RULE, LB_RULE,
                           NAT_RULE, PASS)
@@ -24,7 +26,7 @@ from marvin.cloudstackTestCase import cloudstackTestCase
 from marvin.cloudstackException import CloudstackAPIException
 from marvin.cloudstackAPI import rebootRouter
 from marvin.sshClient import SshClient
-from marvin.lib.utils import cleanup_resources, get_process_status, 
get_host_credentials
+from marvin.lib.utils import cleanup_resources, get_process_status, 
get_host_credentials, random_gen
 from marvin.lib.base import (Account,
                              VirtualMachine,
                              ServiceOffering,
@@ -37,7 +39,9 @@ from marvin.lib.base import (Account,
                              LoadBalancerRule,
                              Router,
                              NIC,
-                             Cluster)
+                             Template,
+                             Cluster,
+                             SSHKeyPair)
 from marvin.lib.common import (get_domain,
                                get_free_vlan,
                                get_zone,
@@ -58,9 +62,11 @@ from marvin.lib.decoratorGenerators import skipTestIf
 from ddt import ddt, data
 import unittest
 # Import System modules
+import os
 import time
 import logging
 import random
+import tempfile
 
 _multiprocess_shared_ = True
 
@@ -2113,3 +2119,313 @@ class TestSharedNetwork(cloudstackTestCase):
             0,
             "Failed to find the placeholder IP"
         )
+
+
+class TestSharedNetworkWithConfigDrive(cloudstackTestCase):
+
+    @classmethod
+    def setUpClass(cls):
+        cls.testClient = super(TestSharedNetworkWithConfigDrive, 
cls).getClsTestClient()
+        cls.apiclient = cls.testClient.getApiClient()
+
+        cls.services = cls.testClient.getParsedTestDataConfig()
+        # Get Zone, Domain and templates
+        cls.domain = get_domain(cls.apiclient)
+        cls.zone = get_zone(cls.apiclient, cls.testClient.getZoneForTests())
+        cls.hv = cls.testClient.getHypervisorInfo()
+
+        if cls.hv.lower() == 'simulator':
+            cls.skip = True
+            return
+        else:
+            cls.skip = False
+
+        cls._cleanup = []
+
+        template = Template.register(
+            cls.apiclient,
+            cls.services["test_templates_cloud_init"][cls.hv.lower()],
+            zoneid=cls.zone.id,
+            hypervisor=cls.hv,
+        )
+        template.download(cls.apiclient)
+        cls._cleanup.append(template)
+
+        cls.services["virtual_machine"]["zoneid"] = cls.zone.id
+        cls.services["virtual_machine"]["template"] = template.id
+        cls.services["virtual_machine"]["username"] = "ubuntu"
+        # Create Network Offering
+        cls.services["shared_network_offering_configdrive"]["specifyVlan"] = 
"True"
+        cls.services["shared_network_offering_configdrive"]["specifyIpRanges"] 
= "True"
+        cls.shared_network_offering = NetworkOffering.create(cls.apiclient,
+                                                             
cls.services["shared_network_offering_configdrive"],
+                                                             conservemode=True)
+
+        cls.isolated_network_offering = NetworkOffering.create(
+            cls.apiclient,
+            cls.services["isolated_network_offering"],
+            conservemode=True
+        )
+
+        # Update network offering state from disabled to enabled.
+        NetworkOffering.update(
+            cls.isolated_network_offering,
+            cls.apiclient,
+            id=cls.isolated_network_offering.id,
+            state="enabled"
+        )
+
+        # Update network offering state from disabled to enabled.
+        NetworkOffering.update(cls.shared_network_offering, cls.apiclient, 
state="enabled")
+
+        cls.service_offering = ServiceOffering.create(cls.apiclient, 
cls.services["service_offering"])
+        physical_network, vlan = get_free_vlan(cls.apiclient, cls.zone.id)
+        # create network using the shared network offering created
+
+        cls.services["shared_network"]["acltype"] = "domain"
+        cls.services["shared_network"]["vlan"] = vlan
+        cls.services["shared_network"]["networkofferingid"] = 
cls.shared_network_offering.id
+        cls.services["shared_network"]["physicalnetworkid"] = 
physical_network.id
+
+        cls.setSharedNetworkParams("shared_network")
+        cls.shared_network = Network.create(cls.apiclient,
+                                            cls.services["shared_network"],
+                                            
networkofferingid=cls.shared_network_offering.id,
+                                            zoneid=cls.zone.id)
+
+        cls.isolated_network = Network.create(
+            cls.apiclient,
+            cls.services["isolated_network"],
+            networkofferingid=cls.isolated_network_offering.id,
+            zoneid=cls.zone.id
+        )
+
+        cls._cleanup.extend([
+            cls.service_offering,
+            cls.shared_network,
+            cls.shared_network_offering,
+            cls.isolated_network,
+            cls.isolated_network_offering,
+        ])
+        cls.tmp_files = []
+        cls.keypair = cls.generate_ssh_keys()
+        return
+
+    @classmethod
+    def generate_ssh_keys(cls):
+        """Generates ssh key pair
+
+        Writes the private key into a temp file and returns the file name
+
+        :returns: generated keypair
+        :rtype: MySSHKeyPair
+        """
+        cls.keypair = SSHKeyPair.create(
+            cls.apiclient,
+            name=random_gen() + ".pem")
+
+        cls._cleanup.append(SSHKeyPair(cls.keypair.__dict__, None))
+        cls.debug("Created keypair with name: %s" % cls.keypair.name)
+        cls.debug("Writing the private key to local file")
+        pkfile = tempfile.gettempdir() + os.sep + cls.keypair.name
+        cls.keypair.private_key_file = pkfile
+        cls.tmp_files.append(pkfile)
+        cls.debug("File path: %s" % pkfile)
+        with open(pkfile, "w+") as f:
+            f.write(cls.keypair.privatekey)
+        os.chmod(pkfile, 0o400)
+
+        return cls.keypair
+
+    def setUp(self):
+        self.apiclient = self.testClient.getApiClient()
+        self.dbclient = self.testClient.getDbConnection()
+        if self.skip:
+            self.skipTest("Hypervisor is simulator - skipping Test..")
+        self.cleanup = []
+
+    @classmethod
+    def tearDownClass(cls):
+        try:
+            # Cleanup resources used
+            cleanup_resources(cls.apiclient, cls._cleanup)
+            for tmp_file in cls.tmp_files:
+                os.remove(tmp_file)
+        except Exception as e:
+            raise Exception("Warning: Exception during cleanup : %s" % e)
+        return
+
+    def tearDown(self):
+        cleanup_resources(self.apiclient, self.cleanup)
+        return
+
+    @classmethod
+    def setSharedNetworkParams(cls, network, range=20):
+
+        # @range: range decides the endip. Pass the range as "x" if you want 
the difference between the startip
+        # and endip as "x"
+        # Set the subnet number of shared networks randomly prior to execution
+        # of each test case to avoid overlapping of ip addresses
+        shared_network_subnet_number = random.randrange(1, 254)
+        cls.services[network]["gateway"] = "172.16." + 
str(shared_network_subnet_number) + ".1"
+        cls.services[network]["startip"] = "172.16." + 
str(shared_network_subnet_number) + ".2"
+        cls.services[network]["endip"] = "172.16." + 
str(shared_network_subnet_number) + "." + str(range + 1)
+        cls.services[network]["netmask"] = "255.255.255.0"
+        logger.debug("Executing command '%s'" % cls.services[network])
+
+    def _mount_config_drive(self, ssh):
+        """
+        This method is to verify whether configdrive iso
+        is attached to vm or not
+        Returns mount path if config drive is attached else None
+        """
+        mountdir = "/root/iso"
+        cmd = "sudo blkid -t LABEL='config-2' " \
+              "/dev/sr? /dev/hd? /dev/sd? /dev/xvd? -o device"
+        tmp_cmd = [
+            'sudo bash -c "if [ ! -d {0} ]; then mkdir {0}; 
fi"'.format(mountdir),
+            "sudo umount %s" % mountdir]
+        self.debug("Unmounting drive from %s" % mountdir)
+        for tcmd in tmp_cmd:
+            ssh.execute(tcmd)
+
+        self.debug("Trying to find ConfigDrive device")
+        configDrive = ssh.execute(cmd)
+        if not configDrive:
+            self.warn("ConfigDrive is not attached")
+            return None
+
+        res = ssh.execute("sudo mount {} {}".format(str(configDrive[0]), 
mountdir))
+        if str(res).lower().find("read-only") > -1:
+            self.debug("ConfigDrive iso is mounted at location %s" % mountdir)
+            return mountdir
+        else:
+            return None
+
+    def _umount_config_drive(self, ssh, mount_path):
+        """unmount config drive inside guest vm
+
+        :param ssh: SSH connection to the VM
+        :type ssh: marvin.sshClient.SshClient
+        :type mount_path: str
+        """
+        ssh.execute("sudo umount -d %s" % mount_path)
+        # Give the VM time to unlock the iso device
+        time.sleep(0.5)
+        # Verify umount
+        result = ssh.execute("sudo ls %s" % mount_path)
+        self.assertTrue(len(result) == 0,
+                        "After umount directory should be empty "
+                        "but contains: %s" % result)
+
+    def _get_config_drive_data(self, ssh, file, name, fail_on_missing=True):
+        """Fetches the content of a file file on the config drive
+
+        :param ssh: SSH connection to the VM
+        :param file: path to the file to fetch
+        :param name: description of the file
+        :param fail_on_missing:
+                 whether the test should fail if the file is missing
+        :type ssh: marvin.sshClient.SshClient
+        :type file: str
+        :type name: str
+        :type fail_on_missing: bool
+        :returns: the content of the file
+        :rtype: str
+        """
+        cmd = "sudo cat %s" % file
+        res = ssh.execute(cmd)
+        content = '\n'.join(res)
+
+        if fail_on_missing and "No such file or directory" in content:
+            self.debug("{} is not found".format(name))
+            self.fail("{} is not found".format(name))
+
+        return content
+
+    def _get_ip_address_output(self, ssh):
+        cmd = "ip address"
+        res = ssh.execute(cmd)
+        return '\n'.join(res)
+
+    @attr(tags=["advanced", "shared"], required_hardware="true")
+    def test_01_deployVMInSharedNetwork(self):
+        try:
+            self.virtual_machine = VirtualMachine.create(self.apiclient, 
self.services["virtual_machine"],
+                                                         
networkids=[self.shared_network.id, self.isolated_network.id],
+                                                         
serviceofferingid=self.service_offering.id,
+                                                         
keypair=self.keypair.name
+                                                         )
+            self.cleanup.append(self.virtual_machine)
+        except Exception as e:
+            self.fail("Exception while deploying virtual machine: %s" % e)
+
+        public_ips = list_publicIP(
+            self.apiclient,
+            associatednetworkid=self.isolated_network.id
+        )
+        public_ip = public_ips[0]
+        FireWallRule.create(
+            self.apiclient,
+            ipaddressid=public_ip.id,
+            protocol=self.services["natrule"]["protocol"],
+            cidrlist=['0.0.0.0/0'],
+            startport=self.services["natrule"]["publicport"],
+            endport=self.services["natrule"]["publicport"]
+        )
+
+        nat_rule = NATRule.create(
+            self.apiclient,
+            self.virtual_machine,
+            self.services["natrule"],
+            public_ip.id
+        )
+
+        private_key_file_location = self.keypair.private_key_file if 
self.keypair else None
+        ssh = self.virtual_machine.get_ssh_client(ipaddress=nat_rule.ipaddress,
+                                                  
keyPairFileLocation=private_key_file_location, retries=5)
+
+        mount_path = self._mount_config_drive(ssh)
+
+        network_data_content = self._get_config_drive_data(ssh, mount_path + 
"/openstack/latest/network_data.json",
+                                                           "network_data")
+
+        network_data = json.loads(network_data_content)
+
+        self._umount_config_drive(ssh, mount_path)
+
+        ip_address_output = self._get_ip_address_output(ssh)
+
+        self.assertTrue('links' in network_data, "network_data.json doesn't 
contain links")
+        self.assertTrue('networks' in network_data, "network_data.json doesn't 
contain networks")
+        self.assertTrue('services' in network_data, "network_data.json doesn't 
contain services")
+
+        for x in ['links', 'networks', 'services']:
+            self.assertTrue(x in network_data, "network_data.json doesn't 
contain " + x)
+            self.assertEqual(len(network_data[x]), 2, "network_data.json 
doesn't contain 2 " + x)
+
+        self.assertIn(network_data['links'][0]['ethernet_mac_address'],
+                      [self.virtual_machine.nic[0].macaddress, 
self.virtual_machine.nic[1].macaddress],
+                      "macaddress doesn't match")
+        self.assertIn(network_data['links'][1]['ethernet_mac_address'],
+                      [self.virtual_machine.nic[0].macaddress, 
self.virtual_machine.nic[1].macaddress],
+                      "macaddress doesn't match")
+
+        self.assertIn(network_data['networks'][0]['ip_address'],
+                      [self.virtual_machine.nic[0].ipaddress, 
self.virtual_machine.nic[1].ipaddress],
+                      "ip address doesn't match")
+        self.assertIn(network_data['networks'][1]['ip_address'],
+                      [self.virtual_machine.nic[0].ipaddress, 
self.virtual_machine.nic[1].ipaddress],
+                      "ip address doesn't match")
+        self.assertIn(network_data['networks'][0]['netmask'],
+                      [self.virtual_machine.nic[0].netmask, 
self.virtual_machine.nic[1].netmask],
+                      "netmask doesn't match")
+        self.assertIn(network_data['networks'][1]['netmask'],
+                      [self.virtual_machine.nic[0].netmask, 
self.virtual_machine.nic[1].netmask],
+                      "netmask doesn't match")
+
+        self.assertEqual(network_data['services'][0]['type'], 'dns', 
"network_data.json doesn't contain dns service")
+        self.assertEqual(network_data['services'][1]['type'], 'dns', 
"network_data.json doesn't contain dns service")
+
+        self.assertTrue(self.virtual_machine.nic[0].ipaddress in 
ip_address_output, "ip address doesn't match")
+        self.assertTrue(self.virtual_machine.nic[1].ipaddress in 
ip_address_output, "ip address doesn't match")
diff --git a/tools/marvin/marvin/config/test_data.py 
b/tools/marvin/marvin/config/test_data.py
index e96dba1c4d5..3485eeb8b18 100644
--- a/tools/marvin/marvin/config/test_data.py
+++ b/tools/marvin/marvin/config/test_data.py
@@ -450,6 +450,21 @@ test_data = {
             "UserData": "VirtualRouter"
         }
     },
+    "shared_network_offering_configdrive": {
+        "name": "MySharedOfferingWithConfigDrive-shared",
+        "displaytext": "MySharedOfferingWithConfigDrive",
+        "guestiptype": "Shared",
+        "supportedservices": "Dhcp,Dns,UserData",
+        "specifyVlan": "False",
+        "specifyIpRanges": "False",
+        "traffictype": "GUEST",
+        "tags": "native",
+        "serviceProviderList": {
+            "Dhcp": "ConfigDrive",
+            "Dns": "ConfigDrive",
+            "UserData": "ConfigDrive"
+        }
+    },
     "shared_network_offering_all_services": {
         "name": "shared network offering with services enabled",
         "displaytext": "Shared network offering",
@@ -1047,6 +1062,41 @@ test_data = {
             "isextractable": "True"
         },
     },
+    "test_templates_cloud_init": {
+        "kvm": {
+            "name": "ubuntu 22.04 kvm",
+            "displaytext": "ubuntu 22.04 kvm",
+            "format": "raw",
+            "hypervisor": "kvm",
+            "ostype": "Other Linux (64-bit)",
+            "url": 
"https://cloud-images.ubuntu.com/releases/22.04/release/ubuntu-22.04-server-cloudimg-amd64.img";,
+            "requireshvm": "True",
+            "ispublic": "True",
+            "isextractable": "False"
+        },
+        "xenserver": {
+            "name": "ubuntu 22.04 xen",
+            "displaytext": "ubuntu 22.04 xen",
+            "format": "vhd",
+            "hypervisor": "xenserver",
+            "ostype": "Other Linux (64-bit)",
+            "url": 
"https://cloud-images.ubuntu.com/releases/22.04/release/ubuntu-22.04-server-cloudimg-amd64-azure.vhd.tar.gz";,
+            "requireshvm": "True",
+            "ispublic": "True",
+            "isextractable": "True"
+        },
+        "vmware": {
+            "name": "ubuntu 22.04 vmware",
+            "displaytext": "ubuntu 22.04 vmware",
+            "format": "ova",
+            "hypervisor": "vmware",
+            "ostype": "Other Linux (64-bit)",
+            "url": 
"https://cloud-images.ubuntu.com/releases/22.04/release/ubuntu-22.04-server-cloudimg-amd64.ova";,
+            "requireshvm": "True",
+            "ispublic": "True",
+            "deployasis": "True"
+        },
+    },
     "test_ovf_templates": [
         {
             "name": "test-ovf",

Reply via email to