Skip to content

Commit a8dba3f

Browse files
authored
Merge pull request #268 from Dstack-TEE/vmm-init-stopped
vmm-cli: Add --stopped and --user-config
2 parents c49be63 + 1c18d24 commit a8dba3f

File tree

6 files changed

+63
-3
lines changed

6 files changed

+63
-3
lines changed

docs/vmm-cli-user-guide.md

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -250,8 +250,10 @@ Deploy your application with the compose file:
250250
- `--gpu`: GPU assignments
251251
- `--ppcie`: Enable PPCIE mode (attach ALL available GPUs and NVSwitches)
252252
- `--env-file`: Environment variables file
253+
- `--user-config`: Path to user config file (will be placed at `/dstack/.user-config` in the CVM)
253254
- `--kms-url`: KMS server URL
254255
- `--gateway-url`: Gateway server URL
256+
- `--stopped`: Create VM in stopped state (requires dstack-vmm >= 0.5.4)
255257

256258
#### Port Mapping
257259

@@ -390,6 +392,40 @@ export DSTACK_VMM_URL=http://ml-cluster:8080
390392
--env-file ./ml-secrets.env
391393
```
392394

395+
##### VM with User Configuration and Stopped State
396+
397+
```bash
398+
# Create a user configuration file
399+
cat > user-config.json << EOF
400+
{
401+
"timezone": "UTC",
402+
"locale": "en_US.UTF-8",
403+
"custom_settings": {
404+
"debug_mode": false,
405+
"log_level": "INFO"
406+
}
407+
}
408+
EOF
409+
410+
# Deploy VM in stopped state with user config
411+
./vmm-cli.py deploy \
412+
--name "configured-vm" \
413+
--image "dstack-0.5.4" \
414+
--compose ./app-compose.json \
415+
--vcpu 4 \
416+
--memory 8G \
417+
--disk 100G \
418+
--user-config ./user-config.json \
419+
--stopped
420+
421+
# The VM is created but not started - start it manually when ready
422+
./vmm-cli.py start configured-vm
423+
```
424+
425+
**Note:** The `--stopped` flag is useful for:
426+
- Pre-staging VMs for later use
427+
- Preparing VMs with specific configurations before startup
428+
393429
### Environment Variable Encryption
394430

395431
The VMM CLI automatically encrypts sensitive environment variables before sending them to the server.

vmm/rpc/proto/vmm_rpc.proto

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,8 @@ message VmConfiguration {
7777
repeated string kms_urls = 14;
7878
// Gateway URLs
7979
repeated string gateway_urls = 15;
80+
// The VM is stopped
81+
bool stopped = 16;
8082
}
8183

8284
message GpuConfig {

vmm/src/app/qemu.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ impl VmInfo {
113113
.as_ref()
114114
.map(|c| c.gateway_urls.clone())
115115
.unwrap_or_default();
116+
let stopped = !workdir.started().unwrap_or(false);
116117

117118
Some(pb::VmConfiguration {
118119
name: self.manifest.name.clone(),
@@ -153,6 +154,7 @@ impl VmInfo {
153154
}),
154155
kms_urls,
155156
gateway_urls,
157+
stopped,
156158
})
157159
},
158160
app_url: self

vmm/src/main_service.rs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,7 @@ impl VmmRpc for RpcHandler {
185185
.put_manifest(&manifest)
186186
.context("Failed to write manifest")?;
187187
let work_dir = self.prepare_work_dir(&id, &request, &app_id)?;
188-
if let Err(err) = vm_work_dir.set_started(true) {
188+
if let Err(err) = vm_work_dir.set_started(!request.stopped) {
189189
warn!("Failed to set started: {}", err);
190190
}
191191

@@ -195,7 +195,13 @@ impl VmmRpc for RpcHandler {
195195
.await
196196
.context("Failed to load VM");
197197
let result = match result {
198-
Ok(()) => self.app.start_vm(&id).await,
198+
Ok(()) => {
199+
if !request.stopped {
200+
self.app.start_vm(&id).await
201+
} else {
202+
Ok(())
203+
}
204+
}
199205
Err(err) => Err(err),
200206
};
201207
if let Err(err) = result {

vmm/src/tests/test-deployment.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
# Test script for vmm-cli.py deployment functionality
44
# Tests the complete compose + deploy workflow with local VMM instance
55

6-
set -e # Exit on any error
6+
set -e # Exit on any error
77

88
# Colors for output
99
RED='\033[0;31m'

vmm/src/vmm-cli.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -492,6 +492,14 @@ def create_vm(self, args) -> None:
492492

493493
envs = parse_env_file(args.env_file)
494494

495+
# Read user config file if provided
496+
user_config = ""
497+
if args.user_config:
498+
if not os.path.exists(args.user_config):
499+
raise Exception(f"User config file not found: {args.user_config}")
500+
with open(args.user_config, 'r') as f:
501+
user_config = f.read()
502+
495503
# Create VM request
496504
params = {
497505
"name": args.name,
@@ -501,9 +509,11 @@ def create_vm(self, args) -> None:
501509
"memory": args.memory,
502510
"disk_size": args.disk,
503511
"app_id": args.app_id,
512+
"user_config": user_config,
504513
"ports": [parse_port_mapping(port) for port in args.port or []],
505514
"hugepages": args.hugepages,
506515
"pin_numa": args.pin_numa,
516+
"stopped": args.stopped,
507517
}
508518

509519
if args.ppcie:
@@ -884,6 +894,8 @@ def main():
884894
'--disk', type=parse_disk_size, default=20, help='Disk size (e.g. 1G, 100M)')
885895
deploy_parser.add_argument(
886896
'--env-file', help='File with environment variables to encrypt', default=None)
897+
deploy_parser.add_argument(
898+
'--user-config', help='Path to user config file', default=None)
887899
deploy_parser.add_argument('--app-id', help='Application ID', default=None)
888900
deploy_parser.add_argument('--port', action='append', type=str,
889901
help='Port mapping in format: protocol[:address]:from:to')
@@ -899,6 +911,8 @@ def main():
899911
help='KMS URL')
900912
deploy_parser.add_argument('--gateway-url', action='append', type=str,
901913
help='Gateway URL')
914+
deploy_parser.add_argument('--stopped', action='store_true',
915+
help='Create VM in stopped state (requires dstack-vmm >= 0.5.4)')
902916

903917
# Images command
904918
_images_parser = subparsers.add_parser(

0 commit comments

Comments
 (0)