This is an automated email from the ASF dual-hosted git repository. vishesh pushed a commit to branch configdrive-network-data in repository https://gitbox.apache.org/repos/asf/cloudstack.git
commit 227be3dabf6aa5b7f35a62570cf4c1352f1429d2 Author: Vishesh <vishes...@gmail.com> AuthorDate: Wed Jul 3 13:10:17 2024 +0530 Add support for network data in Config Drive --- .../engine/orchestration/NetworkOrchestrator.java | 8 ++ .../storage/configdrive/ConfigDriveBuilder.java | 94 +++++++++++++++++++++- .../network/element/ConfigDriveNetworkElement.java | 72 ++++++++++++++--- 3 files changed, 162 insertions(+), 12 deletions(-) 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 ea34f62ecd5..fccb2797275 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 @@ -1798,6 +1798,7 @@ public class NetworkOrchestrator extends ManagerBase implements NetworkOrchestra final ReservationContext context) throws InsufficientCapacityException, ConcurrentOperationException, ResourceUnavailableException { element.prepare(network, profile, vmProfile, dest, context); if (vmProfile.getType() == Type.User && element.getProvider() != null) { + boolean buildConfigDrive = false; if (_networkModel.areServicesSupportedInNetwork(network.getId(), Service.Dhcp) && _networkModel.isProviderSupportServiceInNetwork(network.getId(), Service.Dhcp, element.getProvider()) && element instanceof DhcpServiceProvider) { final DhcpServiceProvider sp = (DhcpServiceProvider) element; @@ -1809,6 +1810,7 @@ public class NetworkOrchestrator extends ManagerBase implements NetworkOrchestra if (!sp.addDhcpEntry(network, profile, vmProfile, dest, context)) { return false; } + buildConfigDrive = true; } if (_networkModel.areServicesSupportedInNetwork(network.getId(), Service.Dns) && _networkModel.isProviderSupportServiceInNetwork(network.getId(), Service.Dns, element.getProvider()) && element instanceof DnsServiceProvider) { @@ -1821,6 +1823,7 @@ public class NetworkOrchestrator extends ManagerBase implements NetworkOrchestra if (!sp.addDnsEntry(network, profile, vmProfile, dest, context)) { return false; } + buildConfigDrive = true; } if (_networkModel.areServicesSupportedInNetwork(network.getId(), Service.UserData) && _networkModel.isProviderSupportServiceInNetwork(network.getId(), Service.UserData, element.getProvider()) && element instanceof UserDataServiceProvider) { @@ -1828,6 +1831,11 @@ public class NetworkOrchestrator extends ManagerBase implements NetworkOrchestra if (!sp.addPasswordAndUserdata(network, profile, vmProfile, dest, context)) { return false; } + buildConfigDrive = true; + } + if (buildConfigDrive && element instanceof ConfigDriveNetworkElement) { + final ConfigDriveNetworkElement sp = (ConfigDriveNetworkElement) element; + return sp.createConfigDriveIso(profile, vmProfile, dest, null); } } return true; 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..14969c18078 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 @@ -33,6 +33,8 @@ import java.util.List; import java.util.Map; import java.util.Set; +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; @@ -108,9 +110,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(NicProfile nic, List<String[]> vmData, String isoFileName, String driveLabel, Map<String, String> customUserdataParams) { + if (vmData == null && nic == null) { + throw new CloudRuntimeException("No VM metadata and nic profile provided"); } Path tempDir = null; @@ -122,6 +124,7 @@ public class ConfigDriveBuilder { File openStackFolder = new File(tempDirName + ConfigDrive.openStackConfigDriveName); writeVendorAndNetworkEmptyJsonFile(openStackFolder); + writeNetworkData(nic, tempDirName, openStackFolder); writeVmMetadata(vmData, tempDirName, openStackFolder, customUserdataParams); linkUserData(tempDirName); @@ -211,6 +214,14 @@ public class ConfigDriveBuilder { writeFile(openStackFolder, "meta_data.json", metaData.toString()); } + /** + * First we generate a JSON object using {@link #createJsonObjectWithNic(NicProfile)}, then we write it to a file called "network_data.json". + */ + static void writeNetworkData(NicProfile nic, String tempDirName, File openStackFolder) { + JsonObject networkData = createJsonObjectWithNic(nic); + writeFile(openStackFolder, "network_data.json", networkData.toString()); + } + /** * Writes the following empty JSON files: * <ul> @@ -250,6 +261,83 @@ public class ConfigDriveBuilder { return metaData; } + /** + * Creates the {@link JsonObject} with NIC's metadata. We expect the JSONObject to have the following entries: + */ + static JsonObject createJsonObjectWithNic(NicProfile nic) { + // TODO: Check if we actually need to read the existing file and upsert the data to support multiple networks + JsonObject networkData = new JsonObject(); + JsonArray links = new JsonArray(); + JsonArray networks = new JsonArray(); + JsonArray services = new JsonArray(); + + if (StringUtils.isNotBlank(nic.getMacAddress())) { + JsonObject link = new JsonObject(); + link.addProperty("ethernet_mac_address", nic.getMacAddress()); + link.addProperty("id", "eth0"); + link.addProperty("mtu", 1500); + link.addProperty("type", "phy"); + links.add(link); + } + + if (StringUtils.isNotBlank(nic.getIPv4Address())) { + JsonObject ipv4Network = new JsonObject(); + ipv4Network.addProperty("id", nic.getIPv4Address()); + ipv4Network.addProperty("ip_address", nic.getIPv4Address()); + ipv4Network.addProperty("link", "eth0"); + ipv4Network.addProperty("netmask", nic.getIPv4Netmask()); + ipv4Network.addProperty("network_id", nic.getNetworkId()); + ipv4Network.addProperty("type", "ipv4"); + + JsonObject ipv4Route = new JsonObject(); + ipv4Route.addProperty("gateway", nic.getIPv4Gateway()); + ipv4Route.addProperty("netmask", "0.0.0.0"); + ipv4Route.addProperty("network", "0.0.0.0"); + networks.add(ipv4Network); + } + + if (StringUtils.isNotBlank(nic.getIPv6Address())) { + JsonObject ipv6Network = new JsonObject(); + ipv6Network.addProperty("id", nic.getIPv6Address()); + ipv6Network.addProperty("ip_address", nic.getIPv6Address()); + ipv6Network.addProperty("link", "eth0"); + ipv6Network.addProperty("netmask", IPv6Network.fromString(nic.getIPv6Cidr()).getNetmask().toString()); + ipv6Network.addProperty("network_id", nic.getNetworkId()); + ipv6Network.addProperty("type", "ipv6"); + + JsonObject ipv6Route = new JsonObject(); + ipv6Route.addProperty("gateway", nic.getIPv6Gateway()); + ipv6Route.addProperty("netmask", "0"); + ipv6Route.addProperty("network", "::"); + networks.add(ipv6Network); + } + + 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 networkData; + } + + 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/server/src/main/java/com/cloud/network/element/ConfigDriveNetworkElement.java b/server/src/main/java/com/cloud/network/element/ConfigDriveNetworkElement.java index a9fa3e95275..82d51d04160 100644 --- a/server/src/main/java/com/cloud/network/element/ConfigDriveNetworkElement.java +++ b/server/src/main/java/com/cloud/network/element/ConfigDriveNetworkElement.java @@ -90,7 +90,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(); @@ -197,6 +198,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, null); + capabilities.put(Service.Dns, null); return capabilities; } @@ -225,7 +228,7 @@ public class ConfigDriveNetworkElement extends AdapterBase implements NetworkEle throws ConcurrentOperationException, InsufficientCapacityException, ResourceUnavailableException { return (canHandle(network.getTrafficType()) && configureConfigDriveData(profile, nic, dest)) - && createConfigDriveIso(profile, dest, null); + && createConfigDriveIso(nic, profile, dest, null); } @Override @@ -342,10 +345,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 +404,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 +534,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 +546,7 @@ 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); + final String isoData = ConfigDriveBuilder.buildConfigDrive(nic, profile.getVmData(), isoFileName, profile.getConfigDriveLabel(), customUserdataParamMap); final HandleConfigDriveIsoCommand configDriveIsoCommand = new HandleConfigDriveIsoCommand(isoPath, isoData, null, false, true, true); final HandleConfigDriveIsoAnswer answer = (HandleConfigDriveIsoAnswer) agentManager.easySend(hostId, configDriveIsoCommand); @@ -590,7 +596,7 @@ public class ConfigDriveNetworkElement extends AdapterBase implements NetworkEle return true; } - private boolean createConfigDriveIso(VirtualMachineProfile profile, DeployDestination dest, DiskTO disk) throws ResourceUnavailableException { + 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 +611,7 @@ 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); + final String isoData = ConfigDriveBuilder.buildConfigDrive(nic, profile.getVmData(), isoFileName, profile.getConfigDriveLabel(), customUserdataParamMap); 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 +764,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; + } }