Skip to content

Commit e80374d

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

File tree

11 files changed

+154
-5
lines changed

11 files changed

+154
-5
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/init.sh

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ set -x
2020
PATH="/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin"
2121
CMDLINE=/var/cache/cloud/cmdline
2222

23+
. /lib/lsb/init-functions
24+
2325
log_it() {
2426
echo "$(date) $@" >> /var/log/cloud.log
2527
log_action_msg "$@"

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

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818
#
1919
# This scripts before ssh.service but after cloud-early-config
2020

21+
. /lib/lsb/init-functions
22+
2123
log_it() {
2224
echo "$(date) $@" >> /var/log/cloud.log
2325
log_action_msg "$@"
@@ -47,6 +49,100 @@ fi
4749

4850
CMDLINE=/var/cache/cloud/cmdline
4951
TYPE=$(grep -Po 'type=\K[a-zA-Z]*' $CMDLINE)
52+
53+
# Execute cloud-init if user data is present
54+
run_cloud_init() {
55+
if [ ! -f "$CMDLINE" ]; then
56+
log_it "No cmdline file found, skipping cloud-init execution"
57+
return 0
58+
fi
59+
60+
local encoded_userdata=$(grep -Po 'userdata=\K[^[:space:]]*' "$CMDLINE" || true)
61+
if [ -z "$encoded_userdata" ]; then
62+
log_it "No user data found in cmdline, skipping cloud-init execution"
63+
return 0
64+
fi
65+
66+
log_it "User data detected, setting up and running cloud-init"
67+
68+
# Update cloud-init config to use NoCloud datasource
69+
cat <<EOF > /etc/cloud/cloud.cfg.d/cloudstack.cfg
70+
#cloud-config
71+
datasource_list: ['NoCloud']
72+
network:
73+
config: disabled
74+
manage_etc_hosts: false
75+
manage_resolv_conf: false
76+
users: []
77+
disable_root: false
78+
ssh_pwauth: false
79+
cloud_init_modules:
80+
- migrator
81+
- seed_random
82+
- bootcmd
83+
- write-files
84+
- growpart
85+
- resizefs
86+
- disk_setup
87+
- mounts
88+
- rsyslog
89+
cloud_config_modules:
90+
- locale
91+
- timezone
92+
- runcmd
93+
cloud_final_modules:
94+
- scripts-per-once
95+
- scripts-per-boot
96+
- scripts-per-instance
97+
- scripts-user
98+
- final-message
99+
- power-state-change
100+
EOF
101+
102+
# Set up user data files (reuse the function from init.sh)
103+
mkdir -p /var/lib/cloud/seed/nocloud
104+
105+
# Decode and potentially decompress user data
106+
local decoded_userdata
107+
decoded_userdata=$(echo "$encoded_userdata" | base64 -d 2>/dev/null)
108+
if [ $? -ne 0 ] || [ -z "$decoded_userdata" ]; then
109+
log_it "ERROR: Failed to decode base64 user data"
110+
return 1
111+
fi
112+
113+
# Write user data
114+
echo "$decoded_userdata" > /var/lib/cloud/seed/nocloud/user-data
115+
chmod 600 /var/lib/cloud/seed/nocloud/user-data
116+
117+
# Create meta-data
118+
local instance_name=$(grep -Po 'name=\K[^[:space:]]*' "$CMDLINE" || hostname)
119+
cat > /var/lib/cloud/seed/nocloud/meta-data << EOF
120+
instance-id: $instance_name
121+
local-hostname: $instance_name
122+
EOF
123+
chmod 644 /var/lib/cloud/seed/nocloud/meta-data
124+
125+
log_it "User data files created, executing cloud-init..."
126+
127+
# Clean any previous cloud-init state
128+
cloud-init clean --logs
129+
130+
# Run cloud-init stages manually
131+
cloud-init init --local && \
132+
cloud-init init && \
133+
cloud-init modules --mode=config && \
134+
cloud-init modules --mode=final
135+
136+
local cloud_init_result=$?
137+
if [ $cloud_init_result -eq 0 ]; then
138+
log_it "Cloud-init executed successfully"
139+
else
140+
log_it "ERROR: Cloud-init execution failed with exit code: $cloud_init_result"
141+
fi
142+
143+
return $cloud_init_result
144+
}
145+
50146
if [ "$TYPE" == "router" ] || [ "$TYPE" == "vpcrouter" ] || [ "$TYPE" == "dhcpsrvr" ]
51147
then
52148
if [ -x /opt/cloud/bin/update_config.py ]
@@ -71,4 +167,6 @@ do
71167
systemctl disable --now --no-block $svc
72168
done
73169

170+
run_cloud_init
171+
74172
date > /var/cache/cloud/boot_up_done

0 commit comments

Comments
 (0)