Skip to content

Commit

Permalink
rework working directory setup
Browse files Browse the repository at this point in the history
CMK-17855
  • Loading branch information
SoloJacobs committed Jun 25, 2024
1 parent d1d3f33 commit 4bfb24d
Show file tree
Hide file tree
Showing 6 changed files with 233 additions and 130 deletions.
3 changes: 2 additions & 1 deletion src/bin/scheduler/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,11 +62,12 @@ fn build_environment(
build_stage_reporter.update(id, EnvironmentBuildStage::Complete(outcome.clone()))?;
return Ok(outcome);
};
let base_path = &working_directory.join(session.id()).join(id);
info!("Building environment for plan {id}");
let run_spec = RunSpec {
id: &format!("robotmk_env_building_{id}"),
command_spec: &build_instructions.command_spec,
base_path: &working_directory.join(id),
base_path,
timeout: build_instructions.timeout,
cancellation_token,
};
Expand Down
183 changes: 134 additions & 49 deletions src/bin/scheduler/setup/general.rs
Original file line number Diff line number Diff line change
@@ -1,82 +1,167 @@
use super::{failed_plan_ids_human_readable, grant_permissions_to_all_plan_users};
use super::rcc::rcc_setup_working_directory;
use super::{grant_full_access, plans_by_sessions};
use crate::build::environment_building_working_directory;
use crate::internal_config::{sort_plans_by_grouping, GlobalConfig, Plan};
use anyhow::{Context, Result as AnyhowResult};

use anyhow::{anyhow, Context, Result as AnyhowResult};
use camino::{Utf8Path, Utf8PathBuf};
use log::{debug, error};
use log::info;
use robotmk::environment::Environment;
use robotmk::results::{plan_results_directory, GeneralSetupFailures};
use robotmk::section::WriteSection;
use robotmk::session::Session;
use std::collections::{HashMap, HashSet};
use std::fs::{create_dir_all, remove_dir_all, remove_file};

pub fn setup(global_config: &GlobalConfig, plans: Vec<Plan>) -> AnyhowResult<Vec<Plan>> {
setup_working_directories(&global_config.working_directory, &plans)?;
if global_config.working_directory.exists() {
remove_dir_all(&global_config.working_directory)
.context("Failed to remove working directory")?;
}
create_dir_all(&global_config.working_directory)
.context("Failed to create working directory")?;
setup_results_directories(global_config, &plans)?;

let mut surviving_plans: Vec<Plan>;
let mut general_setup_failures = GeneralSetupFailures::default();
(
surviving_plans,
general_setup_failures.working_directory_permissions,
) = adjust_working_directory_permissions(global_config, plans);
let mut surviving_plans: Vec<Plan> = setup_working_directories(global_config, plans)?;
sort_plans_by_grouping(&mut surviving_plans);
Ok(surviving_plans)
}

general_setup_failures.write(
fn setup_working_directories(
global_config: &GlobalConfig,
plans: Vec<Plan>,
) -> AnyhowResult<Vec<Plan>> {
let (surviving_plans, plan_failures) = setup_plans_working_directory(plans);
let (surviving_plans, rcc_failures) =
setup_rcc_working_directories(&global_config.working_directory, surviving_plans);
GeneralSetupFailures {
working_directory_permissions: plan_failures
.into_iter()
.chain(rcc_failures.into_iter())
.collect(),
}
.write(
global_config
.results_directory
.join("general_setup_failures.json"),
&global_config.results_directory_locker,
)?;

sort_plans_by_grouping(&mut surviving_plans);
Ok(surviving_plans)
}

fn setup_working_directories(working_directory: &Utf8Path, plans: &[Plan]) -> AnyhowResult<()> {
if working_directory.exists() {
remove_dir_all(working_directory).context("Failed to remove working directory")?;
}
create_dir_all(working_directory).context("Failed to create working directory")?;
for plan in plans {
create_dir_all(&plan.working_directory).context(format!(
"Failed to create working directory {} of plan {}",
plan.working_directory, plan.id
))?;
fn setup_plans_working_directory(plans: Vec<Plan>) -> (Vec<Plan>, HashMap<String, String>) {
let mut surviving_plans = Vec::new();
let mut failures = HashMap::new();
for plan in plans.into_iter() {
if let Err(e) = create_dir_all(&plan.working_directory) {
let error = anyhow!(e).context(format!(
"Failed to create working directory {} of plan {}",
plan.working_directory, plan.id
));
info!("{error:#}");
failures.insert(plan.id.clone(), format!("{error:#}"));
continue;
}
if let Session::User(user_session) = &plan.session {
info!(
"Granting full access for {} to user `{}`.",
&plan.working_directory, &user_session.user_name
);
if let Err(e) = grant_full_access(&user_session.user_name, &plan.working_directory) {
let error = anyhow!(e).context(format!(
"Failed to set permissions for working directory {} of plan {}",
plan.working_directory, plan.id
));
info!("{error:#}");
failures.insert(plan.id.clone(), format!("{error:#}"));
continue;
};
}
surviving_plans.push(plan);
}
create_dir_all(environment_building_working_directory(working_directory))
.context("Failed to create environment building working directory")
(surviving_plans, failures)
}

fn setup_results_directories(global_config: &GlobalConfig, plans: &[Plan]) -> AnyhowResult<()> {
create_dir_all(&global_config.results_directory)
.context("Failed to create results directory")?;
create_dir_all(plan_results_directory(&global_config.results_directory))
.context("Failed to create plan results directory")?;
clean_up_results_directory(global_config, plans).context("Failed to clean up results directory")
}

fn adjust_working_directory_permissions(
global_config: &GlobalConfig,
fn setup_rcc_working_directories(
working_directory: &Utf8Path,
plans: Vec<Plan>,
) -> (Vec<Plan>, HashMap<String, String>) {
debug!(
"Granting all plan users full access to {}",
global_config.working_directory
let (rcc_plans, system_plans): (Vec<Plan>, Vec<Plan>) = plans
.into_iter()
.partition(|plan| matches!(plan.environment, Environment::Rcc(_)));
let (surviving_plans, environment_failures) = setup_with_one_directory_per_user(
&environment_building_working_directory(working_directory),
rcc_plans,
);
let (surviving_plans, failures_by_plan_id) = grant_permissions_to_all_plan_users(
&global_config.working_directory,
plans,
"(OI)(CI)F",
&["/T"],
let (mut surviving_plans, rcc_setup_failures) = setup_with_one_directory_per_user(
&rcc_setup_working_directory(working_directory),
surviving_plans,
);
surviving_plans.extend(system_plans);
(
surviving_plans,
environment_failures
.into_iter()
.chain(rcc_setup_failures)
.collect(),
)
}

if !failures_by_plan_id.is_empty() {
error!(
"Dropping the following plans due to failure to adjust working directory permissions: {}",
failed_plan_ids_human_readable(failures_by_plan_id.keys())
);
fn setup_with_one_directory_per_user(
target: &Utf8Path,
plans: Vec<Plan>,
) -> (Vec<Plan>, HashMap<String, String>) {
let mut surviving_plans = Vec::new();
let mut failures = HashMap::new();
if let Err(e) = create_dir_all(target) {
let error = anyhow!(e).context(format!("Failed to create directory {target}",));
info!("{error:#}");
for plan in plans {
failures.insert(plan.id.clone(), format!("{error:#}"));
}
return (surviving_plans, failures);
}
for (session, plans_in_session) in plans_by_sessions(plans) {
let user_target = &target.join(&session.id());
if let Err(e) = create_dir_all(user_target) {
let error = anyhow!(e).context(format!(
"Failed to create directory {} for session {}",
user_target, &session
));
info!("{error:#}");
for plan in plans_in_session {
failures.insert(plan.id.clone(), format!("{error:#}"));
}
continue;
}
if let Session::User(user_session) = &session {
info!(
"Granting full access for {} to user `{}`.",
user_target, &user_session.user_name
);
if let Err(e) = grant_full_access(&user_session.user_name, user_target) {
let error = anyhow!(e).context(format!(
"Failed to grant full access for {} to user `{}`.",
user_target, &user_session.user_name
));
info!("{error:#}");
for plan in plans_in_session {
failures.insert(plan.id.clone(), format!("{error:#}"));
}
continue;
};
}
surviving_plans.extend(plans_in_session);
}
(surviving_plans, failures)
}

(surviving_plans, failures_by_plan_id)
fn setup_results_directories(global_config: &GlobalConfig, plans: &[Plan]) -> AnyhowResult<()> {
create_dir_all(&global_config.results_directory)
.context("Failed to create results directory")?;
create_dir_all(plan_results_directory(&global_config.results_directory))
.context("Failed to create plan results directory")?;
clean_up_results_directory(global_config, plans).context("Failed to clean up results directory")
}

fn clean_up_results_directory(global_config: &GlobalConfig, plans: &[Plan]) -> AnyhowResult<()> {
Expand Down
13 changes: 13 additions & 0 deletions src/bin/scheduler/setup/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,19 @@ use icacls::run_icacls_command;
use robotmk::session::Session;
use std::collections::HashMap;

fn grant_full_access(user: &str, target_path: &Utf8Path) -> anyhow::Result<()> {
let arguments = [
target_path.as_ref(),
"/grant",
&format!("{user}:(OI)(CI)F"),
"/T",
];
run_icacls_command(arguments).map_err(|e| {
let message = format!("Adjusting permissions of {target_path} for user `{user}` failed");
e.context(message)
})
}

fn plans_by_sessions(plans: Vec<Plan>) -> HashMap<Session, Vec<Plan>> {
let mut plans_by_session = HashMap::new();
for plan in plans {
Expand Down
56 changes: 16 additions & 40 deletions src/bin/scheduler/setup/rcc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ use robotmk::{
section::WriteSection,
};
use std::collections::HashMap;
use std::fs::{create_dir_all, remove_dir_all};
use std::vec;

pub fn setup(global_config: &GlobalConfig, plans: Vec<Plan>) -> AnyhowResult<Vec<Plan>> {
Expand All @@ -30,10 +29,6 @@ pub fn setup(global_config: &GlobalConfig, plans: Vec<Plan>) -> AnyhowResult<Vec
return Ok(system_plans);
}

clear_rcc_setup_working_directory(&rcc_setup_working_directory(
&global_config.working_directory,
))?;

let mut rcc_setup_failures = RCCSetupFailures::default();
let surviving_rcc_plans = adjust_rcc_file_permissions(
&global_config.rcc_config,
Expand All @@ -57,18 +52,7 @@ pub fn setup(global_config: &GlobalConfig, plans: Vec<Plan>) -> AnyhowResult<Vec
Ok(surviving_plans)
}

fn clear_rcc_setup_working_directory(working_directory: &Utf8Path) -> AnyhowResult<()> {
if working_directory.exists() {
remove_dir_all(working_directory).context(format!(
"Failed to remove working directory for RCC setup: {working_directory}"
))?;
}
create_dir_all(working_directory).context(format!(
"Failed to create working directory for RCC setup: {working_directory}"
))
}

fn rcc_setup_working_directory(working_directory: &Utf8Path) -> Utf8PathBuf {
pub fn rcc_setup_working_directory(working_directory: &Utf8Path) -> Utf8PathBuf {
working_directory.join("rcc_setup")
}

Expand Down Expand Up @@ -274,18 +258,13 @@ fn holotree_disable_sharing(

for (session, plans) in plans_by_sessions(plans) {
debug!("Running {} for `{}`", command_spec, &session);
let session_id = &format!(
"holotree_disabling_sharing_{}",
match &session {
Session::Current(_) => "current_user".into(),
Session::User(user_session) => format!("user_{}", user_session.user_name),
}
);
let name = "holotree_disabling_sharing";
let run_spec = &RunSpec {
id: &format!("robotmk_{session_id}"),
id: &format!("robotmk_{name}_{}", session.id()),
command_spec: &command_spec,
base_path: &rcc_setup_working_directory(&global_config.working_directory)
.join(session_id),
.join(session.id())
.join(name),
timeout: 120,
cancellation_token: &global_config.cancellation_token,
};
Expand Down Expand Up @@ -344,13 +323,17 @@ fn run_command_spec_once_in_current_session(
command_spec: &CommandSpec,
id: &str,
) -> Result<(Vec<Plan>, HashMap<String, String>), Cancelled> {
let session = Session::Current(CurrentSession {});
let base_path = &rcc_setup_working_directory(&global_config.working_directory)
.join(session.id())
.join(id);
Ok(
match execute_run_spec_in_session(
&Session::Current(CurrentSession {}),
&session,
&RunSpec {
id: &format!("robotmk_{id}"),
command_spec,
base_path: &rcc_setup_working_directory(&global_config.working_directory).join(id),
base_path,
timeout: 120,
cancellation_token: &global_config.cancellation_token,
},
Expand All @@ -374,23 +357,16 @@ fn run_command_spec_per_session(
let mut failed_plans: HashMap<String, String> = HashMap::new();

for (session, plans) in plans_by_sessions(plans) {
let session_id = format!(
"{}_{}",
id,
match &session {
Session::Current(_) => "current_user".into(),
Session::User(user_session) => format!("user_{}", user_session.user_name),
}
);

let base_path = &rcc_setup_working_directory(&global_config.working_directory)
.join(session.id())
.join(id);
debug!("Running {} for `{}`", command_spec, &session);
match execute_run_spec_in_session(
&session,
&RunSpec {
id: &format!("robotmk_{session_id}"),
id: &format!("robotmk_{id}"),
command_spec,
base_path: &rcc_setup_working_directory(&global_config.working_directory)
.join(session_id),
base_path,
timeout: 120,
cancellation_token: &global_config.cancellation_token,
},
Expand Down
15 changes: 15 additions & 0 deletions src/session.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,13 @@ impl Session {
Self::User(user_session) => user_session.run(spec),
}
}

pub fn id(&self) -> String {
match self {
Self::Current(session) => session.id(),
Self::User(session) => session.id(),
}
}
}

impl Display for Session {
Expand Down Expand Up @@ -92,6 +99,10 @@ impl CurrentSession {
Outcome::Cancel => Ok(Outcome::Cancel),
}
}

pub fn id(&self) -> String {
"current_user".into()
}
}

impl UserSession {
Expand All @@ -105,6 +116,10 @@ impl UserSession {
cancellation_token: spec.cancellation_token,
})
}

pub fn id(&self) -> String {
format!("user_{}", self.user_name)
}
}

#[cfg(test)]
Expand Down
Loading

0 comments on commit 4bfb24d

Please sign in to comment.