Skip to content

Commit 9d59c87

Browse files
committed
Add support for network data in Config Drive
1 parent de683a5 commit 9d59c87

File tree

5 files changed

+168
-18
lines changed

5 files changed

+168
-18
lines changed

engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NetworkOrchestrator.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1798,6 +1798,7 @@ protected boolean prepareElement(final NetworkElement element, final Network net
17981798
final ReservationContext context) throws InsufficientCapacityException, ConcurrentOperationException, ResourceUnavailableException {
17991799
element.prepare(network, profile, vmProfile, dest, context);
18001800
if (vmProfile.getType() == Type.User && element.getProvider() != null) {
1801+
boolean buildConfigDrive = false;
18011802
if (_networkModel.areServicesSupportedInNetwork(network.getId(), Service.Dhcp)
18021803
&& _networkModel.isProviderSupportServiceInNetwork(network.getId(), Service.Dhcp, element.getProvider()) && element instanceof DhcpServiceProvider) {
18031804
final DhcpServiceProvider sp = (DhcpServiceProvider) element;
@@ -1809,6 +1810,7 @@ protected boolean prepareElement(final NetworkElement element, final Network net
18091810
if (!sp.addDhcpEntry(network, profile, vmProfile, dest, context)) {
18101811
return false;
18111812
}
1813+
buildConfigDrive = true;
18121814
}
18131815
if (_networkModel.areServicesSupportedInNetwork(network.getId(), Service.Dns)
18141816
&& _networkModel.isProviderSupportServiceInNetwork(network.getId(), Service.Dns, element.getProvider()) && element instanceof DnsServiceProvider) {
@@ -1821,13 +1823,19 @@ protected boolean prepareElement(final NetworkElement element, final Network net
18211823
if (!sp.addDnsEntry(network, profile, vmProfile, dest, context)) {
18221824
return false;
18231825
}
1826+
buildConfigDrive = true;
18241827
}
18251828
if (_networkModel.areServicesSupportedInNetwork(network.getId(), Service.UserData)
18261829
&& _networkModel.isProviderSupportServiceInNetwork(network.getId(), Service.UserData, element.getProvider()) && element instanceof UserDataServiceProvider) {
18271830
final UserDataServiceProvider sp = (UserDataServiceProvider) element;
18281831
if (!sp.addPasswordAndUserdata(network, profile, vmProfile, dest, context)) {
18291832
return false;
18301833
}
1834+
buildConfigDrive = true;
1835+
}
1836+
if (buildConfigDrive && element instanceof ConfigDriveNetworkElement) {
1837+
final ConfigDriveNetworkElement sp = (ConfigDriveNetworkElement) element;
1838+
return sp.createConfigDriveIso(profile, vmProfile, dest, null);
18311839
}
18321840
}
18331841
return true;

engine/storage/configdrive/src/main/java/org/apache/cloudstack/storage/configdrive/ConfigDriveBuilder.java

Lines changed: 91 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@
3333
import java.util.Map;
3434
import java.util.Set;
3535

36+
import com.cloud.vm.NicProfile;
37+
import com.googlecode.ipv6.IPv6Network;
3638
import org.apache.commons.codec.binary.Base64;
3739
import org.apache.commons.collections.MapUtils;
3840
import org.apache.commons.io.FileUtils;
@@ -108,9 +110,9 @@ public static File base64StringToFile(String encodedIsoData, String folder, Stri
108110
* 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.
109111
* If vmData is null, we throw a {@link CloudRuntimeException}. Moreover, {@link IOException} are captured and re-thrown as {@link CloudRuntimeException}.
110112
*/
111-
public static String buildConfigDrive(List<String[]> vmData, String isoFileName, String driveLabel, Map<String, String> customUserdataParams) {
112-
if (vmData == null) {
113-
throw new CloudRuntimeException("No VM metadata provided");
113+
public static String buildConfigDrive(NicProfile nic, List<String[]> vmData, String isoFileName, String driveLabel, Map<String, String> customUserdataParams) {
114+
if (vmData == null && nic == null) {
115+
throw new CloudRuntimeException("No VM metadata and nic profile provided");
114116
}
115117

116118
Path tempDir = null;
@@ -122,6 +124,7 @@ public static String buildConfigDrive(List<String[]> vmData, String isoFileName,
122124
File openStackFolder = new File(tempDirName + ConfigDrive.openStackConfigDriveName);
123125

124126
writeVendorAndNetworkEmptyJsonFile(openStackFolder);
127+
writeNetworkData(nic, tempDirName, openStackFolder);
125128
writeVmMetadata(vmData, tempDirName, openStackFolder, customUserdataParams);
126129

127130
linkUserData(tempDirName);
@@ -211,6 +214,14 @@ static void writeVmMetadata(List<String[]> vmData, String tempDirName, File open
211214
writeFile(openStackFolder, "meta_data.json", metaData.toString());
212215
}
213216

217+
/**
218+
* First we generate a JSON object using {@link #createJsonObjectWithNic(NicProfile)}, then we write it to a file called "network_data.json".
219+
*/
220+
static void writeNetworkData(NicProfile nic, String tempDirName, File openStackFolder) {
221+
JsonObject networkData = createJsonObjectWithNic(nic);
222+
writeFile(openStackFolder, "network_data.json", networkData.toString());
223+
}
224+
214225
/**
215226
* Writes the following empty JSON files:
216227
* <ul>
@@ -250,6 +261,83 @@ static JsonObject createJsonObjectWithVmData(List<String[]> vmData, String tempD
250261
return metaData;
251262
}
252263

264+
/**
265+
* Creates the {@link JsonObject} with NIC's metadata. We expect the JSONObject to have the following entries:
266+
*/
267+
static JsonObject createJsonObjectWithNic(NicProfile nic) {
268+
// TODO: Check if we actually need to read the existing file and upsert the data to support multiple networks
269+
JsonObject networkData = new JsonObject();
270+
JsonArray links = new JsonArray();
271+
JsonArray networks = new JsonArray();
272+
JsonArray services = new JsonArray();
273+
274+
if (StringUtils.isNotBlank(nic.getMacAddress())) {
275+
JsonObject link = new JsonObject();
276+
link.addProperty("ethernet_mac_address", nic.getMacAddress());
277+
link.addProperty("id", "eth0");
278+
link.addProperty("mtu", 1500);
279+
link.addProperty("type", "phy");
280+
links.add(link);
281+
}
282+
283+
if (StringUtils.isNotBlank(nic.getIPv4Address())) {
284+
JsonObject ipv4Network = new JsonObject();
285+
ipv4Network.addProperty("id", nic.getIPv4Address());
286+
ipv4Network.addProperty("ip_address", nic.getIPv4Address());
287+
ipv4Network.addProperty("link", "eth0");
288+
ipv4Network.addProperty("netmask", nic.getIPv4Netmask());
289+
ipv4Network.addProperty("network_id", nic.getNetworkId());
290+
ipv4Network.addProperty("type", "ipv4");
291+
292+
JsonObject ipv4Route = new JsonObject();
293+
ipv4Route.addProperty("gateway", nic.getIPv4Gateway());
294+
ipv4Route.addProperty("netmask", "0.0.0.0");
295+
ipv4Route.addProperty("network", "0.0.0.0");
296+
networks.add(ipv4Network);
297+
}
298+
299+
if (StringUtils.isNotBlank(nic.getIPv6Address())) {
300+
JsonObject ipv6Network = new JsonObject();
301+
ipv6Network.addProperty("id", nic.getIPv6Address());
302+
ipv6Network.addProperty("ip_address", nic.getIPv6Address());
303+
ipv6Network.addProperty("link", "eth0");
304+
ipv6Network.addProperty("netmask", IPv6Network.fromString(nic.getIPv6Cidr()).getNetmask().toString());
305+
ipv6Network.addProperty("network_id", nic.getNetworkId());
306+
ipv6Network.addProperty("type", "ipv6");
307+
308+
JsonObject ipv6Route = new JsonObject();
309+
ipv6Route.addProperty("gateway", nic.getIPv6Gateway());
310+
ipv6Route.addProperty("netmask", "0");
311+
ipv6Route.addProperty("network", "::");
312+
networks.add(ipv6Network);
313+
}
314+
315+
if (StringUtils.isNotBlank(nic.getIPv4Dns1())) {
316+
services.add(getDnsServiceObject(nic.getIPv4Dns1()));
317+
}
318+
319+
if (StringUtils.isNotBlank(nic.getIPv4Dns2())) {
320+
services.add(getDnsServiceObject(nic.getIPv4Dns2()));
321+
}
322+
323+
if (StringUtils.isNotBlank(nic.getIPv6Dns1())) {
324+
services.add(getDnsServiceObject(nic.getIPv6Dns1()));
325+
}
326+
327+
if (StringUtils.isNotBlank(nic.getIPv6Dns2())) {
328+
services.add(getDnsServiceObject(nic.getIPv6Dns2()));
329+
}
330+
331+
return networkData;
332+
}
333+
334+
private static JsonObject getDnsServiceObject(String dnsAddress) {
335+
JsonObject dnsService = new JsonObject();
336+
dnsService.addProperty("address", dnsAddress);
337+
dnsService.addProperty("type", "dns");
338+
return dnsService;
339+
}
340+
253341
static void createFileInTempDirAnAppendOpenStackMetadataToJsonObject(String tempDirName, JsonObject metaData, String dataType, String fileName, String content, Map<String, String> customUserdataParams) {
254342
if (StringUtils.isBlank(dataType)) {
255343
return;

engine/storage/configdrive/src/test/java/org/apache/cloudstack/storage/configdrive/ConfigDriveBuilderTest.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -113,15 +113,15 @@ public void base64StringToFileTest() throws Exception {
113113

114114
@Test(expected = CloudRuntimeException.class)
115115
public void buildConfigDriveTestNoVmData() {
116-
ConfigDriveBuilder.buildConfigDrive(null, "teste", "C:", null);
116+
ConfigDriveBuilder.buildConfigDrive(null, null, "teste", "C:", null);
117117
}
118118

119119
@Test(expected = CloudRuntimeException.class)
120120
public void buildConfigDriveTestIoException() {
121121
try (MockedStatic<ConfigDriveBuilder> configDriveBuilderMocked = Mockito.mockStatic(ConfigDriveBuilder.class)) {
122122
configDriveBuilderMocked.when(() -> ConfigDriveBuilder.writeVendorAndNetworkEmptyJsonFile(nullable(File.class))).thenThrow(CloudRuntimeException.class);
123-
Mockito.when(ConfigDriveBuilder.buildConfigDrive(new ArrayList<>(), "teste", "C:", null)).thenCallRealMethod();
124-
ConfigDriveBuilder.buildConfigDrive(new ArrayList<>(), "teste", "C:", null);
123+
Mockito.when(ConfigDriveBuilder.buildConfigDrive(null, new ArrayList<>(), "teste", "C:", null)).thenCallRealMethod();
124+
ConfigDriveBuilder.buildConfigDrive(null, new ArrayList<>(), "teste", "C:", null);
125125
}
126126
}
127127

@@ -137,9 +137,9 @@ public void buildConfigDriveTest() {
137137

138138
configDriveBuilderMocked.when(() -> ConfigDriveBuilder.generateAndRetrieveIsoAsBase64Iso(Mockito.anyString(), Mockito.anyString(), Mockito.anyString())).thenAnswer(invocation -> "mockIsoDataBase64");
139139
//force execution of real method
140-
Mockito.when(ConfigDriveBuilder.buildConfigDrive(new ArrayList<>(), "teste", "C:", null)).thenCallRealMethod();
140+
Mockito.when(ConfigDriveBuilder.buildConfigDrive(null, new ArrayList<>(), "teste", "C:", null)).thenCallRealMethod();
141141

142-
String returnedIsoData = ConfigDriveBuilder.buildConfigDrive(new ArrayList<>(), "teste", "C:", null);
142+
String returnedIsoData = ConfigDriveBuilder.buildConfigDrive(null, new ArrayList<>(), "teste", "C:", null);
143143

144144
Assert.assertEquals("mockIsoDataBase64", returnedIsoData);
145145

server/src/main/java/com/cloud/network/element/ConfigDriveNetworkElement.java

Lines changed: 63 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,8 @@
9090
import com.cloud.vm.dao.UserVmDao;
9191
import com.cloud.vm.dao.UserVmDetailsDao;
9292

93-
public class ConfigDriveNetworkElement extends AdapterBase implements NetworkElement, UserDataServiceProvider,
93+
public class ConfigDriveNetworkElement extends AdapterBase implements NetworkElement,
94+
UserDataServiceProvider, DhcpServiceProvider, DnsServiceProvider,
9495
StateListener<VirtualMachine.State, VirtualMachine.Event, VirtualMachine>, NetworkMigrationResponder {
9596

9697
private static final Map<Service, Map<Capability, String>> capabilities = setCapabilities();
@@ -197,6 +198,8 @@ public Map<Service, Map<Capability, String>> getCapabilities() {
197198
private static Map<Service, Map<Capability, String>> setCapabilities() {
198199
Map<Service, Map<Capability, String>> capabilities = new HashMap<>();
199200
capabilities.put(Service.UserData, null);
201+
capabilities.put(Service.Dhcp, null);
202+
capabilities.put(Service.Dns, null);
200203
return capabilities;
201204
}
202205

@@ -225,7 +228,7 @@ public boolean addPasswordAndUserdata(Network network, NicProfile nic, VirtualMa
225228
throws ConcurrentOperationException, InsufficientCapacityException, ResourceUnavailableException {
226229
return (canHandle(network.getTrafficType())
227230
&& configureConfigDriveData(profile, nic, dest))
228-
&& createConfigDriveIso(profile, dest, null);
231+
&& createConfigDriveIso(nic, profile, dest, null);
229232
}
230233

231234
@Override
@@ -342,10 +345,13 @@ public boolean prepareMigration(NicProfile nic, Network network, VirtualMachineP
342345
configureConfigDriveData(vm, nic, dest);
343346

344347
// Create the config drive on dest host cache
345-
createConfigDriveIsoOnHostCache(vm, dest.getHost().getId());
348+
createConfigDriveIsoOnHostCache(nic, vm, dest.getHost().getId());
346349
} else {
347350
vm.setConfigDriveLocation(getConfigDriveLocation(vm.getId()));
348-
addPasswordAndUserdata(network, nic, vm, dest, context);
351+
boolean result = addPasswordAndUserdata(network, nic, vm, dest, context);
352+
if (result) {
353+
createConfigDriveIso(nic, vm, dest, null);
354+
}
349355
}
350356
} catch (InsufficientCapacityException | ResourceUnavailableException e) {
351357
logger.error("Failed to add config disk drive due to: ", e);
@@ -398,7 +404,7 @@ private void recreateConfigDriveIso(NicProfile nic, Network network, VirtualMach
398404
vm.getUuid(), nic.getMacAddress(), userVm.getDetail("SSH.PublicKey"), (String) vm.getParameter(VirtualMachineProfile.Param.VmPassword), isWindows, VirtualMachineManager.getHypervisorHostname(dest.getHost() != null ? dest.getHost().getName() : ""));
399405
vm.setVmData(vmData);
400406
vm.setConfigDriveLabel(VirtualMachineManager.VmConfigDriveLabel.value());
401-
createConfigDriveIso(vm, dest, diskToUse);
407+
createConfigDriveIso(nic, vm, dest, diskToUse);
402408
}
403409
}
404410
}
@@ -528,7 +534,7 @@ private boolean isConfigDriveIsoOnHostCache(long vmId) {
528534
return false;
529535
}
530536

531-
private boolean createConfigDriveIsoOnHostCache(VirtualMachineProfile profile, Long hostId) throws ResourceUnavailableException {
537+
private boolean createConfigDriveIsoOnHostCache(NicProfile nic, VirtualMachineProfile profile, Long hostId) throws ResourceUnavailableException {
532538
if (hostId == null) {
533539
throw new ResourceUnavailableException("Config drive iso creation failed, dest host not available",
534540
ConfigDriveNetworkElement.class, 0L);
@@ -540,7 +546,7 @@ private boolean createConfigDriveIsoOnHostCache(VirtualMachineProfile profile, L
540546

541547
final String isoFileName = ConfigDrive.configIsoFileName(profile.getInstanceName());
542548
final String isoPath = ConfigDrive.createConfigDrivePath(profile.getInstanceName());
543-
final String isoData = ConfigDriveBuilder.buildConfigDrive(profile.getVmData(), isoFileName, profile.getConfigDriveLabel(), customUserdataParamMap);
549+
final String isoData = ConfigDriveBuilder.buildConfigDrive(nic, profile.getVmData(), isoFileName, profile.getConfigDriveLabel(), customUserdataParamMap);
544550
final HandleConfigDriveIsoCommand configDriveIsoCommand = new HandleConfigDriveIsoCommand(isoPath, isoData, null, false, true, true);
545551

546552
final HandleConfigDriveIsoAnswer answer = (HandleConfigDriveIsoAnswer) agentManager.easySend(hostId, configDriveIsoCommand);
@@ -590,7 +596,7 @@ private boolean deleteConfigDriveIsoOnHostCache(final VirtualMachine vm, final L
590596
return true;
591597
}
592598

593-
private boolean createConfigDriveIso(VirtualMachineProfile profile, DeployDestination dest, DiskTO disk) throws ResourceUnavailableException {
599+
public boolean createConfigDriveIso(NicProfile nic, VirtualMachineProfile profile, DeployDestination dest, DiskTO disk) throws ResourceUnavailableException {
594600
DataStore dataStore = getDatastoreForConfigDriveIso(disk, profile, dest);
595601

596602
final Long agentId = findAgentId(profile, dest, dataStore);
@@ -605,7 +611,7 @@ private boolean createConfigDriveIso(VirtualMachineProfile profile, DeployDestin
605611

606612
final String isoFileName = ConfigDrive.configIsoFileName(profile.getInstanceName());
607613
final String isoPath = ConfigDrive.createConfigDrivePath(profile.getInstanceName());
608-
final String isoData = ConfigDriveBuilder.buildConfigDrive(profile.getVmData(), isoFileName, profile.getConfigDriveLabel(), customUserdataParamMap);
614+
final String isoData = ConfigDriveBuilder.buildConfigDrive(nic, profile.getVmData(), isoFileName, profile.getConfigDriveLabel(), customUserdataParamMap);
609615
boolean useHostCacheOnUnsupportedPool = VirtualMachineManager.VmConfigDriveUseHostCacheOnUnsupportedPool.valueIn(dest.getDataCenter().getId());
610616
boolean preferHostCache = VirtualMachineManager.VmConfigDriveForceHostCacheUse.valueIn(dest.getDataCenter().getId());
611617
final HandleConfigDriveIsoCommand configDriveIsoCommand = new HandleConfigDriveIsoCommand(isoPath, isoData, dataStore.getTO(), useHostCacheOnUnsupportedPool, preferHostCache, true);
@@ -758,4 +764,52 @@ private boolean configureConfigDriveData(final VirtualMachineProfile profile, fi
758764
return true;
759765
}
760766

767+
@Override
768+
public boolean addDhcpEntry(Network network, NicProfile nic, VirtualMachineProfile vm, DeployDestination dest,
769+
ReservationContext context) throws ConcurrentOperationException, InsufficientCapacityException, ResourceUnavailableException {
770+
// Update nic profile with required information.
771+
// Add network checks
772+
return true;
773+
}
774+
775+
@Override
776+
public boolean configDhcpSupportForSubnet(Network network, NicProfile nic, VirtualMachineProfile vm,
777+
DeployDestination dest,
778+
ReservationContext context) throws ConcurrentOperationException, InsufficientCapacityException, ResourceUnavailableException {
779+
return false;
780+
}
781+
782+
@Override
783+
public boolean removeDhcpSupportForSubnet(Network network) throws ResourceUnavailableException {
784+
return true;
785+
}
786+
787+
@Override
788+
public boolean setExtraDhcpOptions(Network network, long nicId, Map<Integer, String> dhcpOptions) {
789+
return false;
790+
}
791+
792+
@Override
793+
public boolean removeDhcpEntry(Network network, NicProfile nic,
794+
VirtualMachineProfile vmProfile) throws ResourceUnavailableException {
795+
return true;
796+
}
797+
798+
@Override
799+
public boolean addDnsEntry(Network network, NicProfile nic, VirtualMachineProfile vm, DeployDestination dest,
800+
ReservationContext context) throws ConcurrentOperationException, InsufficientCapacityException, ResourceUnavailableException {
801+
return true;
802+
}
803+
804+
@Override
805+
public boolean configDnsSupportForSubnet(Network network, NicProfile nic, VirtualMachineProfile vm,
806+
DeployDestination dest,
807+
ReservationContext context) throws ConcurrentOperationException, InsufficientCapacityException, ResourceUnavailableException {
808+
return true;
809+
}
810+
811+
@Override
812+
public boolean removeDnsSupportForSubnet(Network network) throws ResourceUnavailableException {
813+
return true;
814+
}
761815
}

server/src/test/java/com/cloud/network/element/ConfigDriveNetworkElementTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -264,7 +264,7 @@ public void testAddPasswordAndUserData() throws Exception {
264264
try (MockedStatic<ConfigDriveBuilder> ignored1 = Mockito.mockStatic(ConfigDriveBuilder.class); MockedStatic<CallContext> ignored2 = Mockito.mockStatic(CallContext.class)) {
265265
Mockito.when(CallContext.current()).thenReturn(callContextMock);
266266
Mockito.doReturn(Mockito.mock(Account.class)).when(callContextMock).getCallingAccount();
267-
Mockito.when(ConfigDriveBuilder.buildConfigDrive(Mockito.anyList(), Mockito.anyString(), Mockito.anyString(), Mockito.anyMap())).thenReturn("content");
267+
Mockito.when(ConfigDriveBuilder.buildConfigDrive(Mockito.any(NicProfile.class), Mockito.anyList(), Mockito.anyString(), Mockito.anyString(), Mockito.anyMap())).thenReturn("content");
268268

269269
final HandleConfigDriveIsoAnswer answer = mock(HandleConfigDriveIsoAnswer.class);
270270
final UserVmDetailVO userVmDetailVO = mock(UserVmDetailVO.class);

0 commit comments

Comments
 (0)