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 <[email protected]>
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",