| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256 |
- use super::*;
- use crate::config::{
- Cluster, DeployTarget, DistributedBackend, ExtraMount, NcclConfig, Node, Recipe, RecipeArgs,
- VenvSetup,
- };
- use std::collections::HashMap;
- fn tcls() -> Cluster {
- Cluster {
- name: "t".into(),
- master_ib_ip: Some("1".into()),
- nodes: vec![Node {
- name: "n0".into(),
- ssh_ip: "10.0.0.1".into(),
- ib_ip: Some("1".into()),
- rank: 0,
- host_key: None,
- entity: None,
- }],
- extra_entities: vec![],
- }
- }
- fn tnccl() -> NcclConfig {
- NcclConfig {
- ib_hca: "r".into(),
- socket_ifname: "e".into(),
- ib_gid_index: Some(3),
- ib_disable: 0,
- gloo_socket_ifname: None,
- ucx_net_devices: None,
- nccl_cross_nic: None,
- nccl_proto: None,
- nccl_p2p_level: None,
- }
- }
- fn minimal_config() -> Config {
- Config {
- ssh_user: "u".into(),
- ssh_key_path: "/tmp/key".into(),
- clusters: vec![],
- nccl: HashMap::new(),
- hf_cache_dir: None,
- init_port: 25000,
- before_serve: HashMap::new(),
- exec_aliases: HashMap::new(),
- home_assistant: None,
- global_mods_dir: None,
- drop_caches: true,
- mount_aliases: HashMap::from([("models".to_string(), "/mnt/nas/models".to_string())]),
- recipe_sources: Vec::new(),
- docker_image_sources: Vec::new(),
- cluster_sources: Vec::new(),
- }
- }
- fn run_with(recipe: &Recipe) -> String {
- let resolved = resolve_extra_mounts(&minimal_config(), recipe).unwrap();
- let ctx = DockerCommandCtx {
- cluster: tcls(),
- nccl: Some(tnccl()),
- recipe: recipe.clone(),
- recipe_name: "r".to_string(),
- pp_size: 2,
- tp_size: 1,
- nnodes: 2,
- cache_dir: "/home/user/.cache/huggingface".to_string(),
- keep: false,
- init_port: 25000,
- mods_remote_path: String::new(),
- extra_mounts: resolved.static_mounts,
- };
- build_docker_command(&ctx, &tcls().nodes[0], true).join(" ")
- }
- fn base_recipe() -> Recipe {
- Recipe {
- engine: "vllm".into(),
- name: "T".into(),
- docker_image: "img".into(),
- model: "mdl".into(),
- port: 8000,
- required_nodes: Some(2),
- pipeline_parallel_size: Some(2),
- tensor_parallel_size: 1,
- args: RecipeArgs::List(vec!["--x".into(), "4096".into()]),
- env: HashMap::new(),
- cluster: None,
- entrypoint: vec![],
- distributed_backend: Some(DistributedBackend::Mp),
- before_serve: vec![],
- after_stop: vec![],
- solo: None,
- deploy: DeployTarget::Docker,
- venv: None,
- venv_setup: VenvSetup::default(),
- venv_setup_script: None,
- mods: Default::default(),
- shm_size: String::new(),
- sync_image: true,
- docker_image_dir: None,
- extra_mounts: vec![],
- build: Default::default(),
- composed_model: None,
- }
- }
- #[test]
- fn extra_mounts_are_mounted() {
- let mut r = base_recipe();
- r.extra_mounts.push(ExtraMount {
- alias: Some("models".into()),
- host: Some("~/.cache/models".into()),
- repo: None,
- subfolder: None,
- container: "/models".into(),
- read_only: true,
- env: Some("MODEL_DIR".into()),
- });
- let cmd = run_with(&r);
- // alias present in minimal_config() → alias host wins. Host/container
- // and env are now shell-quoted so a crafted value can't inject.
- assert!(cmd.contains("-v '/mnt/nas/models':'/models':ro"));
- assert!(cmd.contains("-v '/home/user/.cache/huggingface':/root/.cache/huggingface"));
- assert!(!cmd.contains("-v /mnt/nas/models:/models:ro")); // unquoted form gone
- // env injected pointing at the container path, before recipe env
- assert!(cmd.contains("-e 'MODEL_DIR=/models'"));
- }
- #[test]
- fn extra_mounts_fallback_to_host_when_alias_absent() {
- let mut r = base_recipe();
- r.extra_mounts.push(ExtraMount {
- alias: Some("no_such_alias".into()),
- host: Some("~/.cache/jit".into()),
- repo: None,
- subfolder: None,
- container: "/root/.cache/jit".into(),
- read_only: false,
- env: None,
- });
- let cmd = run_with(&r);
- // alias not in config → host default used with ~ expansion (ssh_user=u)
- assert!(cmd.contains("-v '/home/u/.cache/jit':'/root/.cache/jit'"));
- assert!(!cmd.contains(":ro"));
- }
- #[test]
- fn malicious_env_value_cannot_inject() {
- // A crafted `[env]` value with shell metachars must stay a single
- // quoted argument — never break out of the remote `docker run`.
- let mut r = base_recipe();
- r.env
- .insert("EVIL".to_string(), "x; touch /tmp/pwned".to_string());
- let cmd = run_with(&r);
- assert!(cmd.contains("-e 'EVIL=x; touch /tmp/pwned'"));
- // No raw `; touch` escape: the whole K=V is inside quotes.
- assert!(!cmd.contains("-e EVIL=x;"));
- }
- #[test]
- fn malicious_mount_container_cannot_inject() {
- let mut r = base_recipe();
- r.extra_mounts.push(ExtraMount {
- alias: None,
- host: Some("/safe".into()),
- repo: None,
- subfolder: None,
- container: "/x; touch /tmp/pwned".into(),
- read_only: false,
- env: None,
- });
- let cmd = run_with(&r);
- // Container path is quoted; the metacharacter is inert inside quotes.
- assert!(cmd.contains("'/x; touch /tmp/pwned'"));
- // Never appear as the unquoted concatenation `-v /safe:/x; touch`.
- assert!(!cmd.contains("-v /safe:/x; touch"));
- }
- #[test]
- fn model_path_ref_is_partitioned_not_mounted() {
- // repo-based mounts are path references, NOT bind-mounts. They are
- // partitioned into path_refs and resolved per-node via substitution.
- let mut r = base_recipe();
- r.extra_mounts.push(ExtraMount {
- alias: None,
- host: None,
- repo: Some("aidendle94/GLM-5.2-Hybrid-FP8-MXPF4".into()),
- subfolder: Some("mtp-draft".into()),
- container: "mtp_draft".into(),
- read_only: true,
- env: None,
- });
- let resolved = resolve_extra_mounts(&minimal_config(), &r).unwrap();
- assert_eq!(resolved.path_refs.len(), 1);
- assert_eq!(resolved.static_mounts.len(), 0);
- let pr = &resolved.path_refs[0];
- assert_eq!(pr.repo, "aidendle94/GLM-5.2-Hybrid-FP8-MXPF4");
- assert_eq!(pr.subfolder.as_deref(), Some("mtp-draft"));
- assert_eq!(pr.name, "mtp_draft");
- // No -v flag for path refs in build_docker_command (substitution only)
- let cmd = run_with(&r);
- assert!(!cmd.contains("-v /models/mtp-draft"));
- }
- #[test]
- fn substitute_path_refs_replaces_placeholders() {
- let mut r = base_recipe();
- r.args = RecipeArgs::List(vec![
- "--speculative-config".into(),
- "{\"model\":\"{{mtp_draft}}\"}".into(),
- ]);
- let path_refs = vec![(
- "mtp_draft".to_string(),
- "/root/.cache/huggingface/hub/models--aidendle94--GLM-5.2-Hybrid-FP8-MXPF4/snapshots/bb0d0858/mtp-draft".to_string(),
- )];
- let substituted = substitute_path_refs(&r, &path_refs);
- if let RecipeArgs::List(args) = &substituted.args {
- assert_eq!(
- args[1],
- "{\"model\":\"/root/.cache/huggingface/hub/models--aidendle94--GLM-5.2-Hybrid-FP8-MXPF4/snapshots/bb0d0858/mtp-draft\"}"
- );
- } else {
- panic!("expected list args");
- }
- }
- #[test]
- fn substitute_path_refs_in_env() {
- let mut r = base_recipe();
- r.env
- .insert("MTP_PATH".to_string(), "{{mtp_draft}}".to_string());
- let path_refs = vec![(
- "mtp_draft".to_string(),
- "/root/.cache/huggingface/hub/models--aidendle94--GLM-5.2-Hybrid-FP8-MXPF4/snapshots/bb0d0858/mtp-draft".to_string(),
- )];
- let substituted = substitute_path_refs(&r, &path_refs);
- assert_eq!(
- substituted.env["MTP_PATH"],
- "/root/.cache/huggingface/hub/models--aidendle94--GLM-5.2-Hybrid-FP8-MXPF4/snapshots/bb0d0858/mtp-draft"
- );
- }
- #[test]
- fn clear_paths_dedups_and_sorts() {
- let m = |host: &str| ResolvedMount {
- host: host.into(),
- container: "/c".into(),
- read_only: false,
- env: None,
- };
- let mounts = vec![m("/b/.cache/jit"), m("/a/.cache/jit"), m("/b/.cache/jit")];
- assert_eq!(clear_paths(&mounts), vec!["/a/.cache/jit", "/b/.cache/jit"]);
- assert!(clear_paths(&[]).is_empty());
- }
|