Skip to content

Commit f5eeca0

Browse files
committed
Add support for providing userdata to system VMs
1 parent e7015cb commit f5eeca0

File tree

10 files changed

+148
-11
lines changed

10 files changed

+148
-11
lines changed

engine/api/src/main/java/com/cloud/vm/VirtualMachineManager.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,9 @@ public interface VirtualMachineManager extends Manager {
106106
ConfigKey<Boolean> VmSyncPowerStateTransitioning = new ConfigKey<>("Advanced", Boolean.class, "vm.sync.power.state.transitioning", "true",
107107
"Whether to sync power states of the transitioning and stalled VMs while processing VM power reports.", false);
108108

109+
ConfigKey<Boolean> SystemVmEnableUserData = new ConfigKey<>(Boolean.class, "systemvm.userdata.enabled", "Advanced", "false",
110+
"Enable user data for system VMs. When enabled, the CPVM, SSVM, and Router system VMs will use the values from the global settings consoleproxy.userdata, secstorage.userdata, and router.userdata, respectively, to provide cloud-init user data to the VM.",
111+
true, ConfigKey.Scope.Zone, null);
109112

110113
interface Topics {
111114
String VM_POWER_STATE = "vm.powerstate";

engine/orchestration/src/main/java/com/cloud/vm/VirtualMachineManagerImpl.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5121,7 +5121,7 @@ public ConfigKey<?>[] getConfigKeys() {
51215121
VmConfigDriveLabel, VmConfigDriveOnPrimaryPool, VmConfigDriveForceHostCacheUse, VmConfigDriveUseHostCacheOnUnsupportedPool,
51225122
HaVmRestartHostUp, ResourceCountRunningVMsonly, AllowExposeHypervisorHostname, AllowExposeHypervisorHostnameAccountLevel, SystemVmRootDiskSize,
51235123
AllowExposeDomainInMetadata, MetadataCustomCloudName, VmMetadataManufacturer, VmMetadataProductName,
5124-
VmSyncPowerStateTransitioning
5124+
VmSyncPowerStateTransitioning, SystemVmEnableUserData
51255125
};
51265126
}
51275127

server/src/main/java/com/cloud/consoleproxy/ConsoleProxyManager.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,10 @@ public interface ConsoleProxyManager extends Manager, ConsoleProxyService {
9393
ConfigKey<String> ConsoleProxyManagementLastState = new ConfigKey<String>(ConfigKey.CATEGORY_ADVANCED, String.class, "consoleproxy.management.state.last", com.cloud.consoleproxy.ConsoleProxyManagementState.Auto.toString(),
9494
"last console proxy service management state", false, ConfigKey.Kind.Select, consoleProxyManagementStates);
9595

96+
ConfigKey<String> ConsoleProxyUserData = new ConfigKey<>(String.class, "consoleproxy.userdata", "Advanced", "",
97+
"Default user data for console proxy VMs. This works only when systemvm.userdata.enabled is set to true.",
98+
true, ConfigKey.Scope.Zone, null);
99+
96100
void setManagementState(ConsoleProxyManagementState state);
97101

98102
ConsoleProxyManagementState getManagementState();

server/src/main/java/com/cloud/consoleproxy/ConsoleProxyManagerImpl.java

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import java.nio.charset.Charset;
2020
import java.util.ArrayList;
2121
import java.util.Arrays;
22+
import java.util.Base64;
2223
import java.util.Date;
2324
import java.util.HashMap;
2425
import java.util.HashSet;
@@ -152,6 +153,8 @@
152153
import com.google.gson.GsonBuilder;
153154
import com.google.gson.JsonParseException;
154155

156+
import static com.cloud.vm.VirtualMachineManager.SystemVmEnableUserData;
157+
155158
/**
156159
* Class to manage console proxys. <br><br>
157160
* Possible console proxy state transition cases:<br>
@@ -1265,6 +1268,15 @@ public boolean finalizeVirtualMachineProfile(VirtualMachineProfile profile, Depl
12651268
buf.append(" vncport=").append(getVncPort(datacenterId));
12661269
}
12671270
buf.append(" keystore_password=").append(VirtualMachineGuru.getEncodedString(PasswordGenerator.generateRandomPassword(16)));
1271+
1272+
if (SystemVmEnableUserData.valueIn(dc.getId())) {
1273+
String userData = ConsoleProxyUserData.valueIn(dc.getId());
1274+
if (userData != null && !userData.trim().isEmpty()) {
1275+
String encodedUserData = Base64.getEncoder().encodeToString(userData.getBytes());
1276+
buf.append(" userdata=").append(encodedUserData);
1277+
}
1278+
}
1279+
12681280
String bootArgs = buf.toString();
12691281
if (logger.isDebugEnabled()) {
12701282
logger.debug("Boot Args for " + profile + ": " + bootArgs);
@@ -1572,7 +1584,7 @@ public String getConfigComponentName() {
15721584
public ConfigKey<?>[] getConfigKeys() {
15731585
return new ConfigKey<?>[] { ConsoleProxySslEnabled, NoVncConsoleDefault, NoVncConsoleSourceIpCheckEnabled, ConsoleProxyServiceOffering,
15741586
ConsoleProxyCapacityStandby, ConsoleProxyCapacityScanInterval, ConsoleProxyCmdPort, ConsoleProxyRestart, ConsoleProxyUrlDomain, ConsoleProxySessionMax, ConsoleProxySessionTimeout, ConsoleProxyDisableRpFilter, ConsoleProxyLaunchMax,
1575-
ConsoleProxyManagementLastState, ConsoleProxyServiceManagementState };
1587+
ConsoleProxyManagementLastState, ConsoleProxyServiceManagementState, ConsoleProxyUserData };
15761588
}
15771589

15781590
protected ConsoleProxyStatus parseJsonToConsoleProxyStatus(String json) throws JsonParseException {

server/src/main/java/com/cloud/network/router/VirtualNetworkApplianceManager.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,10 @@ public interface VirtualNetworkApplianceManager extends Manager, VirtualNetworkA
6464
ConfigKey<String> RouterTemplateOvm3 = new ConfigKey<>(String.class, RouterTemplateOvm3CK, "Advanced", "SystemVM Template (Ovm3)",
6565
"Name of the default router template on Ovm3.", true, ConfigKey.Scope.Zone, null);
6666

67+
ConfigKey<String> RouterUserData = new ConfigKey<>(String.class, "router.userdata", "Advanced", "",
68+
"Default user data for virtual router. This works only when systemvm.userdata.enabled is set to true.",
69+
true, ConfigKey.Scope.Zone, null);
70+
6771
ConfigKey<Boolean> SetServiceMonitor = new ConfigKey<>(Boolean.class, SetServiceMonitorCK, "Advanced", "true",
6872
"service monitoring in router enable/disable option, default true", true, ConfigKey.Scope.Zone, null);
6973

server/src/main/java/com/cloud/network/router/VirtualNetworkApplianceManagerImpl.java

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
package com.cloud.network.router;
1919

2020
import static com.cloud.utils.NumbersUtil.toHumanReadableSize;
21+
import static com.cloud.vm.VirtualMachineManager.SystemVmEnableUserData;
2122

2223
import java.lang.reflect.Type;
2324
import java.math.BigInteger;
@@ -28,6 +29,7 @@
2829
import java.text.SimpleDateFormat;
2930
import java.util.ArrayList;
3031
import java.util.Arrays;
32+
import java.util.Base64;
3133
import java.util.Calendar;
3234
import java.util.Collections;
3335
import java.util.Date;
@@ -2149,6 +2151,14 @@ public boolean finalizeVirtualMachineProfile(final VirtualMachineProfile profile
21492151
" on the virtual router.", RouterLogrotateFrequency.key(), routerLogrotateFrequency, dc.getUuid()));
21502152
buf.append(String.format(" logrotatefrequency=%s", routerLogrotateFrequency));
21512153

2154+
if (SystemVmEnableUserData.valueIn(router.getDataCenterId())) {
2155+
String userData = RouterUserData.valueIn(router.getDataCenterId());
2156+
if (userData != null && !userData.trim().isEmpty()) {
2157+
String encodedUserData = Base64.getEncoder().encodeToString(userData.getBytes());
2158+
buf.append(" userdata=").append(encodedUserData);
2159+
}
2160+
}
2161+
21522162
if (logger.isDebugEnabled()) {
21532163
logger.debug("Boot Args for " + profile + ": " + buf.toString());
21542164
}
@@ -3429,7 +3439,8 @@ public ConfigKey<?>[] getConfigKeys() {
34293439
RouterHealthChecksMaxMemoryUsageThreshold,
34303440
ExposeDnsAndBootpServer,
34313441
RouterLogrotateFrequency,
3432-
RemoveControlIpOnStop
3442+
RemoveControlIpOnStop,
3443+
RouterUserData
34333444
};
34343445
}
34353446

server/src/main/java/com/cloud/storage/secondary/SecondaryStorageVmManager.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,10 @@ public interface SecondaryStorageVmManager extends Manager {
4444
"The time interval(in millisecond) to scan whether or not system needs more SSVM to ensure minimal standby capacity",
4545
false);
4646

47+
ConfigKey<String> SecondaryStorageUserData = new ConfigKey<>(String.class, "secstorage.userdata", "Advanced", "",
48+
"Default user data for secondary storage VMs. This works only when systemvm.userdata.enabled is set to true.", true, ConfigKey.Scope.Zone, null);
49+
50+
4751
public static final int DEFAULT_SS_VM_RAMSIZE = 512; // 512M
4852
public static final int DEFAULT_SS_VM_CPUMHZ = 500; // 500 MHz
4953
public static final int DEFAULT_SS_VM_MTUSIZE = 1500;

services/secondary-storage/controller/src/main/java/org/apache/cloudstack/secondarystorage/SecondaryStorageManagerImpl.java

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,13 @@
1717
package org.apache.cloudstack.secondarystorage;
1818

1919
import static com.cloud.configuration.Config.SecStorageAllowedInternalDownloadSites;
20+
import static com.cloud.vm.VirtualMachineManager.SystemVmEnableUserData;
2021

2122
import java.net.URI;
2223
import java.net.URISyntaxException;
2324
import java.util.ArrayList;
2425
import java.util.Arrays;
26+
import java.util.Base64;
2527
import java.util.Collections;
2628
import java.util.Date;
2729
import java.util.HashMap;
@@ -1227,6 +1229,15 @@ public boolean finalizeVirtualMachineProfile(VirtualMachineProfile profile, Depl
12271229
String nfsVersion = imageStoreDetailsUtil != null ? imageStoreDetailsUtil.getNfsVersion(secStores.get(0).getId()) : null;
12281230
buf.append(" nfsVersion=").append(nfsVersion);
12291231
buf.append(" keystore_password=").append(VirtualMachineGuru.getEncodedString(PasswordGenerator.generateRandomPassword(16)));
1232+
1233+
if (SystemVmEnableUserData.valueIn(dc.getId())) {
1234+
String userData = SecondaryStorageUserData.valueIn(dc.getId());
1235+
if (userData != null && !userData.trim().isEmpty()) {
1236+
String encodedUserData = Base64.getEncoder().encodeToString(userData.getBytes());
1237+
buf.append(" userdata=").append(encodedUserData);
1238+
}
1239+
}
1240+
12301241
String bootArgs = buf.toString();
12311242
if (logger.isDebugEnabled()) {
12321243
logger.debug(String.format("Boot args for machine profile [%s]: [%s].", profile.toString(), bootArgs));
@@ -1529,7 +1540,7 @@ public String getConfigComponentName() {
15291540

15301541
@Override
15311542
public ConfigKey<?>[] getConfigKeys() {
1532-
return new ConfigKey<?>[] {NTPServerConfig, MaxNumberOfSsvmsForMigration, SecondaryStorageCapacityScanInterval};
1543+
return new ConfigKey<?>[] {NTPServerConfig, MaxNumberOfSsvmsForMigration, SecondaryStorageCapacityScanInterval, SecondaryStorageUserData};
15331544
}
15341545

15351546
}

systemvm/debian/opt/cloud/bin/setup/postinit.sh

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,66 @@ fi
4747

4848
CMDLINE=/var/cache/cloud/cmdline
4949
TYPE=$(grep -Po 'type=\K[a-zA-Z]*' $CMDLINE)
50+
51+
# Execute cloud-init manually if user data is present
52+
run_cloud_init() {
53+
if [ ! -f "$CMDLINE" ]; then
54+
log_it "No cmdline file found, skipping cloud-init execution"
55+
return 0
56+
fi
57+
58+
local encoded_userdata=$(grep -Po 'userdata=\K[^[:space:]]*' "$CMDLINE" || true)
59+
if [ -z "$encoded_userdata" ]; then
60+
log_it "No user data found in cmdline, skipping cloud-init execution"
61+
return 0
62+
fi
63+
64+
log_it "User data detected, setting up and running cloud-init manually"
65+
66+
# Set up user data files (reuse the function from init.sh)
67+
mkdir -p /var/lib/cloud/seed/nocloud
68+
69+
# Decode and potentially decompress user data
70+
local decoded_userdata
71+
decoded_userdata=$(echo "$encoded_userdata" | base64 -d 2>/dev/null)
72+
if [ $? -ne 0 ] || [ -z "$decoded_userdata" ]; then
73+
log_it "ERROR: Failed to decode base64 user data"
74+
return 1
75+
fi
76+
77+
# Write user data
78+
echo "$decoded_userdata" > /var/lib/cloud/seed/nocloud/user-data
79+
chmod 600 /var/lib/cloud/seed/nocloud/user-data
80+
81+
# Create meta-data
82+
local instance_name=$(grep -Po 'name=\K[^[:space:]]*' "$CMDLINE" || hostname)
83+
cat > /var/lib/cloud/seed/nocloud/meta-data << EOF
84+
instance-id: $instance_name
85+
local-hostname: $instance_name
86+
EOF
87+
chmod 644 /var/lib/cloud/seed/nocloud/meta-data
88+
89+
log_it "User data files created, executing cloud-init..."
90+
91+
# Clean any previous cloud-init state
92+
cloud-init clean --logs
93+
94+
# Run cloud-init stages manually
95+
cloud-init init --local && \
96+
cloud-init init && \
97+
cloud-init modules --mode=config && \
98+
cloud-init modules --mode=final
99+
100+
local cloud_init_result=$?
101+
if [ $cloud_init_result -eq 0 ]; then
102+
log_it "Cloud-init executed successfully"
103+
else
104+
log_it "ERROR: Cloud-init execution failed with exit code: $cloud_init_result"
105+
fi
106+
107+
return $cloud_init_result
108+
}
109+
50110
if [ "$TYPE" == "router" ] || [ "$TYPE" == "vpcrouter" ] || [ "$TYPE" == "dhcpsrvr" ]
51111
then
52112
if [ -x /opt/cloud/bin/update_config.py ]
@@ -71,4 +131,6 @@ do
71131
systemctl disable --now --no-block $svc
72132
done
73133

134+
run_cloud_init
135+
74136
date > /var/cache/cloud/boot_up_done

tools/appliance/systemvmtemplate/scripts/configure_systemvm_services.sh

Lines changed: 33 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -132,15 +132,41 @@ function configure_services() {
132132
# Disable container services
133133
systemctl disable containerd
134134

135-
# Disable cloud init by default
136-
cat <<EOF > /etc/cloud/cloud.cfg.d/cloudstack.cfg
137-
datasource_list: ['CloudStack']
138-
datasource:
139-
CloudStack:
140-
max_wait: 120
141-
timeout: 50
135+
136+
cat <<EOF > /etc/cloud/cloud.cfg.d/cloudstack.cfg
137+
#cloud-config
138+
datasource_list: ['NoCloud']
139+
network:
140+
config: disabled
141+
manage_etc_hosts: false
142+
manage_resolv_conf: false
143+
users: []
144+
disable_root: false
145+
ssh_pwauth: false
146+
cloud_init_modules:
147+
- migrator
148+
- seed_random
149+
- bootcmd
150+
- write-files
151+
- growpart
152+
- resizefs
153+
- disk_setup
154+
- mounts
155+
- rsyslog
156+
cloud_config_modules:
157+
- locale
158+
- timezone
159+
- runcmd
160+
cloud_final_modules:
161+
- scripts-per-once
162+
- scripts-per-boot
163+
- scripts-per-instance
164+
- scripts-user
165+
- final-message
166+
- power-state-change
142167
EOF
143168

169+
# Disable cloud-init services since we run it manually in postinit.sh if user data is available
144170
touch /etc/cloud/cloud-init.disabled
145171
systemctl stop cloud-init
146172
systemctl disable cloud-init

0 commit comments

Comments
 (0)