Skip to content

Commit 7f26e6d

Browse files
vishesh92dhslove
authored andcommitted
Add support for network data in Config Drive (apache#9329)
1 parent 4d477e2 commit 7f26e6d

File tree

12 files changed

+1025
-54
lines changed

12 files changed

+1025
-54
lines changed

engine/api/src/main/java/org/apache/cloudstack/engine/orchestration/service/NetworkOrchestrationService.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import java.util.Map;
2222

2323
import com.cloud.dc.DataCenter;
24+
import com.cloud.hypervisor.Hypervisor;
2425
import org.apache.cloudstack.acl.ControlledEntity.ACLType;
2526
import org.apache.cloudstack.framework.config.ConfigKey;
2627
import org.apache.cloudstack.framework.config.ConfigKey.Scope;
@@ -144,6 +145,8 @@ void prepare(VirtualMachineProfile profile, DeployDestination dest, ReservationC
144145

145146
List<NicProfile> getNicProfiles(VirtualMachine vm);
146147

148+
List<NicProfile> getNicProfiles(Long vmId, Hypervisor.HypervisorType hypervisorType);
149+
147150
Map<String, String> getSystemVMAccessDetails(VirtualMachine vm);
148151

149152
Pair<? extends NetworkGuru, ? extends Network> implementNetwork(long networkId, DeployDestination dest, ReservationContext context)

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

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1838,6 +1838,19 @@ protected boolean prepareElement(final NetworkElement element, final Network net
18381838
return false;
18391839
}
18401840
}
1841+
if (element instanceof ConfigDriveNetworkElement && ((
1842+
_networkModel.areServicesSupportedInNetwork(network.getId(), Service.Dhcp) &&
1843+
_networkModel.isProviderSupportServiceInNetwork(network.getId(), Service.Dhcp, element.getProvider())
1844+
) || (
1845+
_networkModel.areServicesSupportedInNetwork(network.getId(), Service.Dns) &&
1846+
_networkModel.isProviderSupportServiceInNetwork(network.getId(), Service.Dns, element.getProvider())
1847+
) || (
1848+
_networkModel.areServicesSupportedInNetwork(network.getId(), Service.UserData) &&
1849+
_networkModel.isProviderSupportServiceInNetwork(network.getId(), Service.UserData, element.getProvider())
1850+
))) {
1851+
final ConfigDriveNetworkElement sp = (ConfigDriveNetworkElement) element;
1852+
return sp.createConfigDriveIso(profile, vmProfile, dest, null);
1853+
}
18411854
}
18421855
return true;
18431856
}
@@ -4445,25 +4458,30 @@ private boolean getNicProfileDefaultNic(NicProfile nicProfile) {
44454458
}
44464459

44474460
@Override
4448-
public List<NicProfile> getNicProfiles(final VirtualMachine vm) {
4449-
final List<NicVO> nics = _nicDao.listByVmId(vm.getId());
4461+
public List<NicProfile> getNicProfiles(final Long vmId, HypervisorType hypervisorType) {
4462+
final List<NicVO> nics = _nicDao.listByVmId(vmId);
44504463
final List<NicProfile> profiles = new ArrayList<NicProfile>();
44514464

44524465
if (nics != null) {
44534466
for (final Nic nic : nics) {
44544467
final NetworkVO network = _networksDao.findById(nic.getNetworkId());
4455-
final Integer networkRate = _networkModel.getNetworkRate(network.getId(), vm.getId());
4468+
final Integer networkRate = _networkModel.getNetworkRate(network.getId(), vmId);
44564469

44574470
final NetworkGuru guru = AdapterBase.getAdapterByName(networkGurus, network.getGuruName());
44584471
final NicProfile profile = new NicProfile(nic, network, nic.getBroadcastUri(), nic.getIsolationUri(), networkRate,
4459-
_networkModel.isSecurityGroupSupportedInNetwork(network), _networkModel.getNetworkTag(vm.getHypervisorType(), network));
4472+
_networkModel.isSecurityGroupSupportedInNetwork(network), _networkModel.getNetworkTag(hypervisorType, network));
44604473
guru.updateNicProfile(profile, network);
44614474
profiles.add(profile);
44624475
}
44634476
}
44644477
return profiles;
44654478
}
44664479

4480+
@Override
4481+
public List<NicProfile> getNicProfiles(final VirtualMachine vm) {
4482+
return getNicProfiles(vm.getId(), vm.getHypervisorType());
4483+
}
4484+
44674485
@Override
44684486
public Map<String, String> getSystemVMAccessDetails(final VirtualMachine vm) {
44694487
final Map<String, String> accessDetails = new HashMap<>();

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

Lines changed: 162 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@
2222
import static com.cloud.network.NetworkModel.CONFIGDATA_FILE;
2323
import static com.cloud.network.NetworkModel.PASSWORD_FILE;
2424
import static com.cloud.network.NetworkModel.USERDATA_FILE;
25+
import static com.cloud.network.NetworkService.DEFAULT_MTU;
26+
import static org.apache.cloudstack.storage.configdrive.ConfigDriveUtils.mergeJsonArraysAndUpdateObject;
2527

2628
import java.io.File;
2729
import java.io.IOException;
@@ -33,6 +35,9 @@
3335
import java.util.Map;
3436
import java.util.Set;
3537

38+
import com.cloud.network.Network;
39+
import com.cloud.vm.NicProfile;
40+
import com.googlecode.ipv6.IPv6Network;
3641
import org.apache.commons.codec.binary.Base64;
3742
import org.apache.commons.collections.MapUtils;
3843
import org.apache.commons.io.FileUtils;
@@ -81,7 +86,7 @@ static void writeFile(File folder, String file, String content) {
8186

8287
/**
8388
* Read the content of a {@link File} and convert it to a String in base 64.
84-
* We expect the content of the file to be encoded using {@link StandardCharsets#US_ASC}
89+
* We expect the content of the file to be encoded using {@link StandardCharsets#US_ASCII}
8590
*/
8691
public static String fileToBase64String(File isoFile) throws IOException {
8792
byte[] encoded = Base64.encodeBase64(FileUtils.readFileToByteArray(isoFile));
@@ -108,9 +113,9 @@ public static File base64StringToFile(String encodedIsoData, String folder, Stri
108113
* 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.
109114
* If vmData is null, we throw a {@link CloudRuntimeException}. Moreover, {@link IOException} are captured and re-thrown as {@link CloudRuntimeException}.
110115
*/
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");
116+
public static String buildConfigDrive(List<NicProfile> nics, List<String[]> vmData, String isoFileName, String driveLabel, Map<String, String> customUserdataParams, Map<Long, List<Network.Service>> supportedServices) {
117+
if (vmData == null && nics == null) {
118+
throw new CloudRuntimeException("No VM metadata and nic profile provided");
114119
}
115120

116121
Path tempDir = null;
@@ -121,10 +126,19 @@ public static String buildConfigDrive(List<String[]> vmData, String isoFileName,
121126

122127
File openStackFolder = new File(tempDirName + ConfigDrive.openStackConfigDriveName);
123128

124-
writeVendorAndNetworkEmptyJsonFile(openStackFolder);
125-
writeVmMetadata(vmData, tempDirName, openStackFolder, customUserdataParams);
126-
127-
linkUserData(tempDirName);
129+
writeVendorEmptyJsonFile(openStackFolder);
130+
writeNetworkData(nics, supportedServices, openStackFolder);
131+
for (NicProfile nic: nics) {
132+
if (supportedServices.get(nic.getId()).contains(Network.Service.UserData)) {
133+
if (vmData == null) {
134+
throw new CloudRuntimeException("No VM metadata provided");
135+
}
136+
writeVmMetadata(vmData, tempDirName, openStackFolder, customUserdataParams);
137+
138+
linkUserData(tempDirName);
139+
break;
140+
}
141+
}
128142

129143
return generateAndRetrieveIsoAsBase64Iso(isoFileName, driveLabel, tempDirName);
130144
} catch (IOException e) {
@@ -212,18 +226,36 @@ static void writeVmMetadata(List<String[]> vmData, String tempDirName, File open
212226
}
213227

214228
/**
215-
* Writes the following empty JSON files:
216-
* <ul>
217-
* <li> vendor_data.json
218-
* <li> network_data.json
219-
* </ul>
229+
* First we generate a JSON object using {@link #getNetworkDataJsonObjectForNic(NicProfile, List)}, then we write it to a file called "network_data.json".
230+
*/
231+
static void writeNetworkData(List<NicProfile> nics, Map<Long, List<Network.Service>> supportedServices, File openStackFolder) {
232+
JsonObject finalNetworkData = new JsonObject();
233+
if (needForGeneratingNetworkData(supportedServices)) {
234+
for (NicProfile nic : nics) {
235+
List<Network.Service> supportedService = supportedServices.get(nic.getId());
236+
JsonObject networkData = getNetworkDataJsonObjectForNic(nic, supportedService);
237+
238+
mergeJsonArraysAndUpdateObject(finalNetworkData, networkData, "links", "id", "type");
239+
mergeJsonArraysAndUpdateObject(finalNetworkData, networkData, "networks", "id", "type");
240+
mergeJsonArraysAndUpdateObject(finalNetworkData, networkData, "services", "address", "type");
241+
}
242+
}
243+
244+
writeFile(openStackFolder, "network_data.json", finalNetworkData.toString());
245+
}
246+
247+
static boolean needForGeneratingNetworkData(Map<Long, List<Network.Service>> supportedServices) {
248+
return supportedServices.values().stream().anyMatch(services -> services.contains(Network.Service.Dhcp) || services.contains(Network.Service.Dns));
249+
}
250+
251+
/**
252+
* Writes an empty JSON file named vendor_data.json in openStackFolder
220253
*
221-
* If the folder does not exist and we cannot create it, we throw a {@link CloudRuntimeException}.
254+
* If the folder does not exist, and we cannot create it, we throw a {@link CloudRuntimeException}.
222255
*/
223-
static void writeVendorAndNetworkEmptyJsonFile(File openStackFolder) {
256+
static void writeVendorEmptyJsonFile(File openStackFolder) {
224257
if (openStackFolder.exists() || openStackFolder.mkdirs()) {
225258
writeFile(openStackFolder, "vendor_data.json", "{}");
226-
writeFile(openStackFolder, "network_data.json", "{}");
227259
} else {
228260
throw new CloudRuntimeException("Failed to create folder " + openStackFolder);
229261
}
@@ -250,6 +282,120 @@ static JsonObject createJsonObjectWithVmData(List<String[]> vmData, String tempD
250282
return metaData;
251283
}
252284

285+
/**
286+
* Creates the {@link JsonObject} using @param nic's metadata. We expect the JSONObject to have the following entries:
287+
* <ul>
288+
* <li> links </li>
289+
* <li> networks </li>
290+
* <li> services </li>
291+
* </ul>
292+
*/
293+
static JsonObject getNetworkDataJsonObjectForNic(NicProfile nic, List<Network.Service> supportedServices) {
294+
JsonObject networkData = new JsonObject();
295+
296+
JsonArray links = getLinksJsonArrayForNic(nic);
297+
JsonArray networks = getNetworksJsonArrayForNic(nic);
298+
if (links.size() > 0) {
299+
networkData.add("links", links);
300+
}
301+
if (networks.size() > 0) {
302+
networkData.add("networks", networks);
303+
}
304+
305+
JsonArray services = getServicesJsonArrayForNic(nic);
306+
if (services.size() > 0) {
307+
networkData.add("services", services);
308+
}
309+
310+
return networkData;
311+
}
312+
313+
static JsonArray getLinksJsonArrayForNic(NicProfile nic) {
314+
JsonArray links = new JsonArray();
315+
if (StringUtils.isNotBlank(nic.getMacAddress())) {
316+
JsonObject link = new JsonObject();
317+
link.addProperty("ethernet_mac_address", nic.getMacAddress());
318+
link.addProperty("id", String.format("eth%d", nic.getDeviceId()));
319+
link.addProperty("mtu", nic.getMtu() != null ? nic.getMtu() : DEFAULT_MTU);
320+
link.addProperty("type", "phy");
321+
links.add(link);
322+
}
323+
return links;
324+
}
325+
326+
static JsonArray getNetworksJsonArrayForNic(NicProfile nic) {
327+
JsonArray networks = new JsonArray();
328+
if (StringUtils.isNotBlank(nic.getIPv4Address())) {
329+
JsonObject ipv4Network = new JsonObject();
330+
ipv4Network.addProperty("id", String.format("eth%d", nic.getDeviceId()));
331+
ipv4Network.addProperty("ip_address", nic.getIPv4Address());
332+
ipv4Network.addProperty("link", String.format("eth%d", nic.getDeviceId()));
333+
ipv4Network.addProperty("netmask", nic.getIPv4Netmask());
334+
ipv4Network.addProperty("network_id", nic.getUuid());
335+
ipv4Network.addProperty("type", "ipv4");
336+
337+
JsonArray ipv4RouteArray = new JsonArray();
338+
JsonObject ipv4Route = new JsonObject();
339+
ipv4Route.addProperty("gateway", nic.getIPv4Gateway());
340+
ipv4Route.addProperty("netmask", "0.0.0.0");
341+
ipv4Route.addProperty("network", "0.0.0.0");
342+
ipv4RouteArray.add(ipv4Route);
343+
344+
ipv4Network.add("routes", ipv4RouteArray);
345+
346+
networks.add(ipv4Network);
347+
}
348+
349+
if (StringUtils.isNotBlank(nic.getIPv6Address())) {
350+
JsonObject ipv6Network = new JsonObject();
351+
ipv6Network.addProperty("id", String.format("eth%d", nic.getDeviceId()));
352+
ipv6Network.addProperty("ip_address", nic.getIPv6Address());
353+
ipv6Network.addProperty("link", String.format("eth%d", nic.getDeviceId()));
354+
ipv6Network.addProperty("netmask", IPv6Network.fromString(nic.getIPv6Cidr()).getNetmask().toString());
355+
ipv6Network.addProperty("network_id", nic.getUuid());
356+
ipv6Network.addProperty("type", "ipv6");
357+
358+
JsonArray ipv6RouteArray = new JsonArray();
359+
JsonObject ipv6Route = new JsonObject();
360+
ipv6Route.addProperty("gateway", nic.getIPv6Gateway());
361+
ipv6Route.addProperty("netmask", "0");
362+
ipv6Route.addProperty("network", "::");
363+
ipv6RouteArray.add(ipv6Route);
364+
365+
ipv6Network.add("routes", ipv6RouteArray);
366+
367+
networks.add(ipv6Network);
368+
}
369+
return networks;
370+
}
371+
372+
static JsonArray getServicesJsonArrayForNic(NicProfile nic) {
373+
JsonArray services = new JsonArray();
374+
if (StringUtils.isNotBlank(nic.getIPv4Dns1())) {
375+
services.add(getDnsServiceObject(nic.getIPv4Dns1()));
376+
}
377+
378+
if (StringUtils.isNotBlank(nic.getIPv4Dns2())) {
379+
services.add(getDnsServiceObject(nic.getIPv4Dns2()));
380+
}
381+
382+
if (StringUtils.isNotBlank(nic.getIPv6Dns1())) {
383+
services.add(getDnsServiceObject(nic.getIPv6Dns1()));
384+
}
385+
386+
if (StringUtils.isNotBlank(nic.getIPv6Dns2())) {
387+
services.add(getDnsServiceObject(nic.getIPv6Dns2()));
388+
}
389+
return services;
390+
}
391+
392+
private static JsonObject getDnsServiceObject(String dnsAddress) {
393+
JsonObject dnsService = new JsonObject();
394+
dnsService.addProperty("address", dnsAddress);
395+
dnsService.addProperty("type", "dns");
396+
return dnsService;
397+
}
398+
253399
static void createFileInTempDirAnAppendOpenStackMetadataToJsonObject(String tempDirName, JsonObject metaData, String dataType, String fileName, String content, Map<String, String> customUserdataParams) {
254400
if (StringUtils.isBlank(dataType)) {
255401
return;
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
// Licensed to the Apache Software Foundation (ASF) under one
2+
// or more contributor license agreements. See the NOTICE file
3+
// distributed with this work for additional information
4+
// regarding copyright ownership. The ASF licenses this file
5+
// to you under the Apache License, Version 2.0 (the
6+
// "License"); you may not use this file except in compliance
7+
// with the License. You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing,
12+
// software distributed under the License is distributed on an
13+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
// KIND, either express or implied. See the License for the
15+
// specific language governing permissions and limitations
16+
// under the License.
17+
18+
package org.apache.cloudstack.storage.configdrive;
19+
20+
import com.google.gson.JsonArray;
21+
import com.google.gson.JsonElement;
22+
import com.google.gson.JsonObject;
23+
24+
import java.util.Arrays;
25+
import java.util.HashSet;
26+
import java.util.Set;
27+
28+
public class ConfigDriveUtils {
29+
30+
static void mergeJsonArraysAndUpdateObject(JsonObject finalObject, JsonObject newObj, String memberName, String... keys) {
31+
JsonArray existingMembers = finalObject.has(memberName) ? finalObject.get(memberName).getAsJsonArray() : new JsonArray();
32+
JsonArray newMembers = newObj.has(memberName) ? newObj.get(memberName).getAsJsonArray() : new JsonArray();
33+
34+
if (existingMembers.size() > 0 || newMembers.size() > 0) {
35+
JsonArray finalMembers = new JsonArray();
36+
Set<String> idSet = new HashSet<>();
37+
for (JsonElement element : existingMembers.getAsJsonArray()) {
38+
JsonObject elementObject = element.getAsJsonObject();
39+
String key = Arrays.stream(keys).map(elementObject::get).map(JsonElement::getAsString).reduce((a, b) -> a + "-" + b).orElse("");
40+
idSet.add(key);
41+
finalMembers.add(element);
42+
}
43+
for (JsonElement element : newMembers.getAsJsonArray()) {
44+
JsonObject elementObject = element.getAsJsonObject();
45+
String key = Arrays.stream(keys).map(elementObject::get).map(JsonElement::getAsString).reduce((a, b) -> a + "-" + b).orElse("");
46+
if (!idSet.contains(key)) {
47+
finalMembers.add(element);
48+
}
49+
}
50+
finalObject.add(memberName, finalMembers);
51+
}
52+
}
53+
54+
}

0 commit comments

Comments
 (0)