Add support for network data in Config Drive (#9329)

This commit is contained in:
Vishesh 2024-08-26 14:23:42 +05:30 committed by GitHub
parent c9f1c5790d
commit bc28665679
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 1026 additions and 55 deletions

View File

@ -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)

View File

@ -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<>();

View File

@ -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);
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);
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>
*
* If the folder does not exist and we cannot create it, we throw a {@link CloudRuntimeException}.
* First we generate a JSON object using {@link #getNetworkDataJsonObjectForNic(NicProfile, List)}, then we write it to a file called "network_data.json".
*/
static void writeVendorAndNetworkEmptyJsonFile(File openStackFolder) {
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}.
*/
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;

View File

@ -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);
}
}
}

View File

@ -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");
//force execution of real method
Mockito.when(ConfigDriveBuilder.buildConfigDrive(new ArrayList<>(), "teste", "C:", null)).thenCallRealMethod();
String returnedIsoData = ConfigDriveBuilder.buildConfigDrive(new ArrayList<>(), "teste", "C:", null);
NicProfile mockedNicProfile = Mockito.mock(NicProfile.class);
Mockito.when(mockedNicProfile.getId()).thenReturn(1L);
//force execution of real method
Mockito.when(ConfigDriveBuilder.buildConfigDrive(List.of(mockedNicProfile), new ArrayList<>(), "teste", "C:", null, supportedServices)).thenCallRealMethod();
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();
}
}

View File

@ -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");
}
}

View File

@ -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;
}

View File

@ -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;
}
}

View File

@ -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());

View File

@ -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;

View File

@ -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")

View File

@ -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",