Sync public tree to d0af060
This commit is contained in:
parent
8c11db913e
commit
a77e138a24
6 changed files with 185 additions and 11 deletions
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"kind": "disasmer-filtered-public-tree",
|
"kind": "disasmer-filtered-public-tree",
|
||||||
"source_commit": "7aeb51ff791bd445afc2d51757239b6e306db015",
|
"source_commit": "d0af06088a23d83911b49a6b05073b892f70385b",
|
||||||
"release_name": "dryrun-7aeb51ff791b",
|
"release_name": "dryrun-d0af06088a23",
|
||||||
"filtered_out": [
|
"filtered_out": [
|
||||||
"private/**",
|
"private/**",
|
||||||
"experiments/**",
|
"experiments/**",
|
||||||
|
|
|
||||||
|
|
@ -587,6 +587,7 @@ pub enum CoordinatorResponse {
|
||||||
actor: WorkflowActor,
|
actor: WorkflowActor,
|
||||||
placement: Placement,
|
placement: Placement,
|
||||||
assignment: TaskAssignment,
|
assignment: TaskAssignment,
|
||||||
|
charged_spawns: u64,
|
||||||
},
|
},
|
||||||
TaskAssignment {
|
TaskAssignment {
|
||||||
assignment: Option<TaskAssignment>,
|
assignment: Option<TaskAssignment>,
|
||||||
|
|
@ -607,6 +608,7 @@ pub enum CoordinatorResponse {
|
||||||
process: ProcessId,
|
process: ProcessId,
|
||||||
epoch: u64,
|
epoch: u64,
|
||||||
actor: WorkflowActor,
|
actor: WorkflowActor,
|
||||||
|
charged_spawns: u64,
|
||||||
},
|
},
|
||||||
NodeReconnected {
|
NodeReconnected {
|
||||||
node: NodeId,
|
node: NodeId,
|
||||||
|
|
@ -761,6 +763,8 @@ pub struct CoordinatorService {
|
||||||
download_meter: ResourceMeter,
|
download_meter: ResourceMeter,
|
||||||
debug_limits: ResourceLimits,
|
debug_limits: ResourceLimits,
|
||||||
debug_meter: ResourceMeter,
|
debug_meter: ResourceMeter,
|
||||||
|
workflow_limits: ResourceLimits,
|
||||||
|
workflow_meter: ResourceMeter,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CoordinatorService {
|
impl CoordinatorService {
|
||||||
|
|
@ -790,6 +794,8 @@ impl CoordinatorService {
|
||||||
download_meter: ResourceMeter::default(),
|
download_meter: ResourceMeter::default(),
|
||||||
debug_limits: ResourceLimits::community_tier_defaults(),
|
debug_limits: ResourceLimits::community_tier_defaults(),
|
||||||
debug_meter: ResourceMeter::default(),
|
debug_meter: ResourceMeter::default(),
|
||||||
|
workflow_limits: ResourceLimits::community_tier_defaults(),
|
||||||
|
workflow_meter: ResourceMeter::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1356,6 +1362,8 @@ impl CoordinatorService {
|
||||||
)
|
)
|
||||||
.into());
|
.into());
|
||||||
}
|
}
|
||||||
|
self.workflow_meter
|
||||||
|
.can_charge(&self.workflow_limits, LimitKind::Spawn, 1)?;
|
||||||
let request = PlacementRequest {
|
let request = PlacementRequest {
|
||||||
tenant: tenant.clone(),
|
tenant: tenant.clone(),
|
||||||
project: project.clone(),
|
project: project.clone(),
|
||||||
|
|
@ -1374,6 +1382,9 @@ impl CoordinatorService {
|
||||||
};
|
};
|
||||||
let nodes = self.node_descriptors.values().cloned().collect::<Vec<_>>();
|
let nodes = self.node_descriptors.values().cloned().collect::<Vec<_>>();
|
||||||
let placement = DefaultScheduler.place(&nodes, &request)?;
|
let placement = DefaultScheduler.place(&nodes, &request)?;
|
||||||
|
self.workflow_meter
|
||||||
|
.charge(&self.workflow_limits, LimitKind::Spawn, 1)?;
|
||||||
|
let charged_spawns = self.workflow_meter.used(&LimitKind::Spawn);
|
||||||
let assignment = TaskAssignment {
|
let assignment = TaskAssignment {
|
||||||
tenant: tenant.clone(),
|
tenant: tenant.clone(),
|
||||||
project: project.clone(),
|
project: project.clone(),
|
||||||
|
|
@ -1399,6 +1410,7 @@ impl CoordinatorService {
|
||||||
actor,
|
actor,
|
||||||
placement,
|
placement,
|
||||||
assignment,
|
assignment,
|
||||||
|
charged_spawns,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
CoordinatorRequest::PollTaskAssignment {
|
CoordinatorRequest::PollTaskAssignment {
|
||||||
|
|
@ -1562,6 +1574,8 @@ impl CoordinatorService {
|
||||||
.into());
|
.into());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
self.workflow_meter
|
||||||
|
.charge(&self.workflow_limits, LimitKind::Spawn, 1)?;
|
||||||
self.process_cancellations
|
self.process_cancellations
|
||||||
.remove(&process_control_key(&tenant, &project, &process));
|
.remove(&process_control_key(&tenant, &project, &process));
|
||||||
self.task_cancellations.retain(
|
self.task_cancellations.retain(
|
||||||
|
|
@ -1598,6 +1612,7 @@ impl CoordinatorService {
|
||||||
process,
|
process,
|
||||||
epoch: self.coordinator.coordinator_epoch(),
|
epoch: self.coordinator.coordinator_epoch(),
|
||||||
actor,
|
actor,
|
||||||
|
charged_spawns: self.workflow_meter.used(&LimitKind::Spawn),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
CoordinatorRequest::ReconnectNode {
|
CoordinatorRequest::ReconnectNode {
|
||||||
|
|
@ -3411,6 +3426,144 @@ mod tests {
|
||||||
assert!(policy_error.to_string().contains("policy denied placement"));
|
assert!(policy_error.to_string().contains("policy denied placement"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn service_checks_spawn_quota_before_process_or_task_work_starts() {
|
||||||
|
let mut service = CoordinatorService::new(7);
|
||||||
|
service.workflow_limits = ResourceLimits {
|
||||||
|
limits: BTreeMap::from([(LimitKind::Spawn, 2)]),
|
||||||
|
};
|
||||||
|
|
||||||
|
service
|
||||||
|
.handle_request(CoordinatorRequest::AttachNode {
|
||||||
|
tenant: "tenant".to_owned(),
|
||||||
|
project: "project".to_owned(),
|
||||||
|
node: "worker-linux".to_owned(),
|
||||||
|
public_key: "worker-linux-public-key".to_owned(),
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
service
|
||||||
|
.handle_request(CoordinatorRequest::ReportNodeCapabilities {
|
||||||
|
tenant: "tenant".to_owned(),
|
||||||
|
project: "project".to_owned(),
|
||||||
|
node: "worker-linux".to_owned(),
|
||||||
|
capabilities: linux_capabilities(),
|
||||||
|
cached_environment_digests: Vec::new(),
|
||||||
|
dependency_cache_digests: Vec::new(),
|
||||||
|
source_snapshots: Vec::new(),
|
||||||
|
artifact_locations: Vec::new(),
|
||||||
|
direct_connectivity: true,
|
||||||
|
online: true,
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let CoordinatorResponse::ProcessStarted { charged_spawns, .. } = service
|
||||||
|
.handle_request(CoordinatorRequest::StartProcess {
|
||||||
|
tenant: "tenant".to_owned(),
|
||||||
|
project: "project".to_owned(),
|
||||||
|
actor_user: None,
|
||||||
|
actor_agent: None,
|
||||||
|
agent_public_key_fingerprint: None,
|
||||||
|
process: "vp-quota".to_owned(),
|
||||||
|
restart: false,
|
||||||
|
})
|
||||||
|
.unwrap()
|
||||||
|
else {
|
||||||
|
panic!("expected process start within spawn quota");
|
||||||
|
};
|
||||||
|
assert_eq!(charged_spawns, 1);
|
||||||
|
|
||||||
|
let CoordinatorResponse::TaskLaunched { charged_spawns, .. } = service
|
||||||
|
.handle_request(CoordinatorRequest::LaunchTask {
|
||||||
|
tenant: "tenant".to_owned(),
|
||||||
|
project: "project".to_owned(),
|
||||||
|
actor_user: Some("user".to_owned()),
|
||||||
|
actor_agent: None,
|
||||||
|
agent_public_key_fingerprint: None,
|
||||||
|
process: "vp-quota".to_owned(),
|
||||||
|
task: "compile-linux".to_owned(),
|
||||||
|
environment: None,
|
||||||
|
environment_digest: None,
|
||||||
|
required_capabilities: vec![Capability::Command],
|
||||||
|
dependency_cache: None,
|
||||||
|
source_snapshot: None,
|
||||||
|
required_artifacts: Vec::new(),
|
||||||
|
quota_available: true,
|
||||||
|
policy_allowed: true,
|
||||||
|
command: "cargo".to_owned(),
|
||||||
|
command_args: vec!["test".to_owned()],
|
||||||
|
artifact_path: "/vfs/artifacts/dap-output.txt".to_owned(),
|
||||||
|
})
|
||||||
|
.unwrap()
|
||||||
|
else {
|
||||||
|
panic!("expected task launch within spawn quota");
|
||||||
|
};
|
||||||
|
assert_eq!(charged_spawns, 2);
|
||||||
|
|
||||||
|
let denied_task = service
|
||||||
|
.handle_request(CoordinatorRequest::LaunchTask {
|
||||||
|
tenant: "tenant".to_owned(),
|
||||||
|
project: "project".to_owned(),
|
||||||
|
actor_user: Some("user".to_owned()),
|
||||||
|
actor_agent: None,
|
||||||
|
agent_public_key_fingerprint: None,
|
||||||
|
process: "vp-quota".to_owned(),
|
||||||
|
task: "compile-linux-denied".to_owned(),
|
||||||
|
environment: None,
|
||||||
|
environment_digest: None,
|
||||||
|
required_capabilities: vec![Capability::Command],
|
||||||
|
dependency_cache: None,
|
||||||
|
source_snapshot: None,
|
||||||
|
required_artifacts: Vec::new(),
|
||||||
|
quota_available: true,
|
||||||
|
policy_allowed: true,
|
||||||
|
command: "cargo".to_owned(),
|
||||||
|
command_args: vec!["test".to_owned()],
|
||||||
|
artifact_path: "/vfs/artifacts/denied.txt".to_owned(),
|
||||||
|
})
|
||||||
|
.unwrap_err();
|
||||||
|
assert!(denied_task.to_string().contains("Spawn"));
|
||||||
|
assert_eq!(service.workflow_meter.used(&LimitKind::Spawn), 2);
|
||||||
|
|
||||||
|
let denied_task_key = task_control_key(
|
||||||
|
&TenantId::from("tenant"),
|
||||||
|
&ProjectId::from("project"),
|
||||||
|
&ProcessId::from("vp-quota"),
|
||||||
|
&NodeId::from("worker-linux"),
|
||||||
|
&TaskId::from("compile-linux-denied"),
|
||||||
|
);
|
||||||
|
assert!(!service.active_tasks.contains(&denied_task_key));
|
||||||
|
assert!(!service.task_placements.contains_key(&denied_task_key));
|
||||||
|
assert_eq!(
|
||||||
|
service
|
||||||
|
.task_assignments
|
||||||
|
.get(&(
|
||||||
|
TenantId::from("tenant"),
|
||||||
|
ProjectId::from("project"),
|
||||||
|
NodeId::from("worker-linux"),
|
||||||
|
))
|
||||||
|
.map(VecDeque::len),
|
||||||
|
Some(1)
|
||||||
|
);
|
||||||
|
|
||||||
|
let denied_process = service
|
||||||
|
.handle_request(CoordinatorRequest::StartProcess {
|
||||||
|
tenant: "tenant".to_owned(),
|
||||||
|
project: "other-project".to_owned(),
|
||||||
|
actor_user: None,
|
||||||
|
actor_agent: None,
|
||||||
|
agent_public_key_fingerprint: None,
|
||||||
|
process: "vp-denied".to_owned(),
|
||||||
|
restart: false,
|
||||||
|
})
|
||||||
|
.unwrap_err();
|
||||||
|
assert!(denied_process.to_string().contains("Spawn"));
|
||||||
|
assert!(service
|
||||||
|
.coordinator
|
||||||
|
.active_process(&ProcessId::from("vp-denied"))
|
||||||
|
.is_none());
|
||||||
|
assert_eq!(service.workflow_meter.used(&LimitKind::Spawn), 2);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn service_attaches_node_starts_process_and_records_scoped_task_event() {
|
fn service_attaches_node_starts_process_and_records_scoped_task_event() {
|
||||||
let mut service = CoordinatorService::new(7);
|
let mut service = CoordinatorService::new(7);
|
||||||
|
|
@ -3449,7 +3602,8 @@ mod tests {
|
||||||
public_key_fingerprint: None,
|
public_key_fingerprint: None,
|
||||||
authenticated_without_browser: false,
|
authenticated_without_browser: false,
|
||||||
scopes: vec!["project:read".to_owned(), "project:run".to_owned()],
|
scopes: vec!["project:read".to_owned(), "project:run".to_owned()],
|
||||||
}
|
},
|
||||||
|
charged_spawns: 1,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -4296,7 +4450,8 @@ mod tests {
|
||||||
public_key_fingerprint: None,
|
public_key_fingerprint: None,
|
||||||
authenticated_without_browser: false,
|
authenticated_without_browser: false,
|
||||||
scopes: vec!["project:read".to_owned(), "project:run".to_owned()],
|
scopes: vec!["project:read".to_owned(), "project:run".to_owned()],
|
||||||
}
|
},
|
||||||
|
charged_spawns: 2,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -26,16 +26,17 @@ node scripts/resource-metering-contract-smoke.js
|
||||||
node scripts/hostile-input-contract-smoke.js
|
node scripts/hostile-input-contract-smoke.js
|
||||||
node scripts/tenant-isolation-contract-smoke.js
|
node scripts/tenant-isolation-contract-smoke.js
|
||||||
node scripts/public-story-contract-smoke.js
|
node scripts/public-story-contract-smoke.js
|
||||||
|
|
||||||
|
if [[ "${DISASMER_CLI_FIRST_PREPARE_PUBLIC_RELEASE:-1}" == "1" ]]; then
|
||||||
|
node scripts/prepare-public-release-dryrun.js
|
||||||
|
fi
|
||||||
|
|
||||||
node scripts/public-release-dryrun-contract-smoke.js
|
node scripts/public-release-dryrun-contract-smoke.js
|
||||||
node scripts/public-browser-login-contract-smoke.js
|
node scripts/public-browser-login-contract-smoke.js
|
||||||
node scripts/self-hosted-coordinator-smoke.js
|
node scripts/self-hosted-coordinator-smoke.js
|
||||||
node scripts/public-local-demo-matrix-smoke.js
|
node scripts/public-local-demo-matrix-smoke.js
|
||||||
scripts/release-source-scan.sh
|
scripts/release-source-scan.sh
|
||||||
|
|
||||||
if [[ "${DISASMER_CLI_FIRST_PREPARE_PUBLIC_RELEASE:-1}" == "1" ]]; then
|
|
||||||
node scripts/prepare-public-release-dryrun.js
|
|
||||||
fi
|
|
||||||
|
|
||||||
cargo fmt --all --check
|
cargo fmt --all --check
|
||||||
cargo test --workspace
|
cargo test --workspace
|
||||||
cargo build --workspace --bins
|
cargo build --workspace --bins
|
||||||
|
|
|
||||||
|
|
@ -165,6 +165,11 @@ expect(
|
||||||
"quota resource-category criteria",
|
"quota resource-category criteria",
|
||||||
/Hitting a quota produces a clear error[\s\S]*resource category[\s\S]*private abuse heuristics[\s\S]*quota machine errors now extract the resource category/
|
/Hitting a quota produces a clear error[\s\S]*resource category[\s\S]*private abuse heuristics[\s\S]*quota machine errors now extract the resource category/
|
||||||
);
|
);
|
||||||
|
expect(
|
||||||
|
criteria,
|
||||||
|
"quota before work criteria",
|
||||||
|
/Quotas are checked before expensive work starts[\s\S]*workflow spawns now charge `Spawn` before coordinator-side process\/task state is created or queued[\s\S]*debug, artifact-download, and rendezvous metering/
|
||||||
|
);
|
||||||
expect(
|
expect(
|
||||||
criteria,
|
criteria,
|
||||||
"community tier CLI wording criteria",
|
"community tier CLI wording criteria",
|
||||||
|
|
@ -403,6 +408,16 @@ expect(
|
||||||
"coordinator task events retain placement reasons",
|
"coordinator task events retain placement reasons",
|
||||||
/pub struct TaskCompletionEvent[\s\S]*placement: Option<Placement>[\s\S]*task_placements[\s\S]*event\.placement = self\.task_placements\.remove/
|
/pub struct TaskCompletionEvent[\s\S]*placement: Option<Placement>[\s\S]*task_placements[\s\S]*event\.placement = self\.task_placements\.remove/
|
||||||
);
|
);
|
||||||
|
expect(
|
||||||
|
coordinator,
|
||||||
|
"coordinator workflow spawn metering",
|
||||||
|
/workflow_limits: ResourceLimits[\s\S]*workflow_meter: ResourceMeter[\s\S]*can_charge\(&self\.workflow_limits, LimitKind::Spawn, 1\)[\s\S]*charge\(&self\.workflow_limits, LimitKind::Spawn, 1\)[\s\S]*charged_spawns/
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
coordinator,
|
||||||
|
"coordinator spawn quota before work coverage",
|
||||||
|
/fn service_checks_spawn_quota_before_process_or_task_work_starts\(\)[\s\S]*workflow_limits = ResourceLimits[\s\S]*LimitKind::Spawn[\s\S]*compile-linux-denied[\s\S]*active_tasks\.contains[\s\S]*active_process\(&ProcessId::from\("vp-denied"\)\)/
|
||||||
|
);
|
||||||
expect(
|
expect(
|
||||||
coordinator,
|
coordinator,
|
||||||
"public debug operation audit event",
|
"public debug operation audit event",
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,10 @@ const path = require("path");
|
||||||
const repo = path.resolve(__dirname, "..");
|
const repo = path.resolve(__dirname, "..");
|
||||||
const temp = fs.mkdtempSync(path.join(os.tmpdir(), "disasmer-cli-install-"));
|
const temp = fs.mkdtempSync(path.join(os.tmpdir(), "disasmer-cli-install-"));
|
||||||
const installRoot = path.join(temp, "install");
|
const installRoot = path.join(temp, "install");
|
||||||
const targetDir = path.join(temp, "target");
|
const targetDir =
|
||||||
|
process.env.DISASMER_CLI_INSTALL_CARGO_TARGET_DIR ||
|
||||||
|
process.env.CARGO_TARGET_DIR ||
|
||||||
|
path.join(repo, "target");
|
||||||
const project = path.join(repo, "examples/launch-build-demo");
|
const project = path.join(repo, "examples/launch-build-demo");
|
||||||
const binName = process.platform === "win32" ? "disasmer.exe" : "disasmer";
|
const binName = process.platform === "win32" ? "disasmer.exe" : "disasmer";
|
||||||
const installedBin = path.join(installRoot, "bin", binName);
|
const installedBin = path.join(installRoot, "bin", binName);
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ function criterionLines() {
|
||||||
expect("header", /^# Disasmer MVP Website Acceptance Criteria/m);
|
expect("header", /^# Disasmer MVP Website Acceptance Criteria/m);
|
||||||
expect(
|
expect(
|
||||||
"acceptance-style status",
|
"acceptance-style status",
|
||||||
/\*\*Status:\*\* hosted website addendum to `acceptance_criteria\.md`, `acceptance_criteria_phase2\.md`, and `cli_acceptance_criteria\.md`/
|
/\*\*Status:\*\* (?:private )?hosted website addendum to `acceptance_criteria\.md`, `acceptance_criteria_phase2\.md`, and `cli_acceptance_criteria\.md`/
|
||||||
);
|
);
|
||||||
expect(
|
expect(
|
||||||
"hosted signup exception",
|
"hosted signup exception",
|
||||||
|
|
@ -50,7 +50,7 @@ expect(
|
||||||
);
|
);
|
||||||
expect(
|
expect(
|
||||||
"future hosted business scope",
|
"future hosted business scope",
|
||||||
/Billing is not part of the MVP\. Paid checkout, paid-plan management, upgrade flows, hosted support tooling, a full hosted admin console, broad moderation workflows/
|
/Billing is not part of the MVP[\s\S]*Paid checkout, paid-plan management, upgrade flows, hosted support tooling, a full hosted admin console, broad moderation workflows/
|
||||||
);
|
);
|
||||||
expect(
|
expect(
|
||||||
"billing is not MVP website work",
|
"billing is not MVP website work",
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue