Add runtime observation and systemd restart adapter
This commit is contained in:
@@ -0,0 +1,161 @@
|
||||
use std::fs;
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
use std::path::Path;
|
||||
|
||||
use wireguard_admin::app::{Application, RestartChoice, RestartStage};
|
||||
use wireguard_admin::config::{ResolvedSettings, SettingsFile};
|
||||
use wireguard_admin::domain::{
|
||||
Client, ClientAddresses, ClientStates, RuntimeObservation, Snapshot,
|
||||
};
|
||||
use wireguard_admin::persistence::SavedStore;
|
||||
use wireguard_admin::vault::WireGuardPrivateKey;
|
||||
|
||||
fn settings(service: &str, timeout: u64) -> String {
|
||||
format!(
|
||||
r#"
|
||||
interface_name = "wg0"
|
||||
server_config_path = "server.conf"
|
||||
client_source_directory = "clients"
|
||||
server_endpoint = "vpn.example.test:51820"
|
||||
ipv4_allocation_range = "10.20.0.0/24"
|
||||
ipv6_allocation_range = "fd20::/64"
|
||||
dns_addresses = ["10.20.0.1"]
|
||||
systemd_unit = "{service}"
|
||||
restart_timeout_seconds = {timeout}
|
||||
"#
|
||||
)
|
||||
}
|
||||
|
||||
struct Fixture {
|
||||
_root: tempfile::TempDir,
|
||||
store: SavedStore,
|
||||
initial_settings: String,
|
||||
}
|
||||
|
||||
fn fixture(service: &str, timeout: u64) -> Fixture {
|
||||
let root = tempfile::tempdir().expect("temporary saved store");
|
||||
let initial_settings = settings(service, timeout);
|
||||
let settings = SettingsFile::parse_toml(&initial_settings)
|
||||
.expect("synthetic settings parse")
|
||||
.validate()
|
||||
.expect("synthetic settings validate")
|
||||
.resolve_paths(root.path());
|
||||
fs::create_dir_all(&settings.client_source_directory).expect("client directory");
|
||||
fs::create_dir_all(&settings.data_directory).expect("data directory");
|
||||
fs::create_dir_all(&settings.archives_directory).expect("archive directory");
|
||||
fs::write(
|
||||
ResolvedSettings::settings_path(root.path()),
|
||||
initial_settings.as_bytes(),
|
||||
)
|
||||
.expect("install synthetic settings");
|
||||
fs::write(
|
||||
&settings.server_config_path,
|
||||
b"[Interface]\nPrivateKey = synthetic-server-key\nAddress = 10.20.0.1/24\n",
|
||||
)
|
||||
.expect("install synthetic server configuration");
|
||||
fs::set_permissions(
|
||||
&settings.server_config_path,
|
||||
fs::Permissions::from_mode(0o600),
|
||||
)
|
||||
.expect("restrict synthetic server configuration");
|
||||
let store = SavedStore::new(root.path(), settings.clone()).expect("saved store mapping");
|
||||
Fixture {
|
||||
_root: root,
|
||||
store,
|
||||
initial_settings,
|
||||
}
|
||||
}
|
||||
|
||||
fn open(fixture: &Fixture) -> Application {
|
||||
let states = ClientStates::new(Snapshot::default(), None, RuntimeObservation::Unknown)
|
||||
.expect("empty saved states");
|
||||
Application::open(fixture.store.clone(), states).expect("open synthetic application")
|
||||
}
|
||||
|
||||
fn archive_name(path: &Path) -> &str {
|
||||
path.file_name()
|
||||
.and_then(|name| name.to_str())
|
||||
.expect("archive filename")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn public_restart_preview_and_cancel_preserve_draft_and_saved_files() {
|
||||
let fixture = fixture("wg-quick@wg0.service", 30);
|
||||
let mut app = open(&fixture);
|
||||
let original_server_config = app.saved_bytes().server_config.clone();
|
||||
let secret = WireGuardPrivateKey::generate().expect("synthetic client key");
|
||||
let client = Client::new(
|
||||
secret.public_key(),
|
||||
"draft client",
|
||||
ClientAddresses {
|
||||
ipv4: "10.20.0.2".parse().expect("synthetic IPv4"),
|
||||
ipv6: Some("fd20::2".parse().expect("synthetic IPv6")),
|
||||
},
|
||||
)
|
||||
.expect("synthetic client");
|
||||
app.add_client(client.clone(), None)
|
||||
.expect("stage client draft");
|
||||
|
||||
let preview = app.preview_restart().expect("preview saved generation");
|
||||
assert!(preview.has_draft());
|
||||
assert_eq!(preview.interface(), "wg0");
|
||||
assert_eq!(preview.systemd_unit(), "wg-quick@wg0.service");
|
||||
assert!(app.list_backups().expect("list archives").is_empty());
|
||||
|
||||
// The public cancellation path returns before either real host adapter can run.
|
||||
let report = app.restart(preview, RestartChoice::Cancel);
|
||||
|
||||
assert_eq!(report.stage, RestartStage::Cancelled);
|
||||
assert!(!report.restart_submitted);
|
||||
assert_eq!(app.saved_bytes().server_config, original_server_config);
|
||||
assert!(!app.document().render().is_empty());
|
||||
assert_eq!(
|
||||
app.states()
|
||||
.draft()
|
||||
.get(&client.identity)
|
||||
.map(|saved| saved.alias.as_str()),
|
||||
Some("draft client")
|
||||
);
|
||||
assert!(
|
||||
app.list_backups()
|
||||
.expect("no cancellation backup")
|
||||
.is_empty()
|
||||
);
|
||||
assert_eq!(app.states().applied(), &RuntimeObservation::Unknown);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn restore_changes_the_settings_used_by_the_next_restart_preview() {
|
||||
let fixture = fixture("wg-quick@wg0.service", 30);
|
||||
let app = open(&fixture);
|
||||
let archive = app
|
||||
.create_manual_backup()
|
||||
.expect("archive original saved settings");
|
||||
|
||||
let replacement = settings("wg-quick@wg9.service", 90);
|
||||
fs::write(
|
||||
ResolvedSettings::settings_path(fixture._root.path()),
|
||||
replacement.as_bytes(),
|
||||
)
|
||||
.expect("install a second synthetic saved settings generation");
|
||||
drop(app);
|
||||
|
||||
let mut app = open(&fixture);
|
||||
let preview = app
|
||||
.preview_restore(archive_name(&archive))
|
||||
.expect("preview archived saved settings");
|
||||
app.confirm_restore(preview)
|
||||
.expect("restore archived saved settings");
|
||||
|
||||
assert_eq!(
|
||||
app.saved_bytes().settings,
|
||||
fixture.initial_settings.as_bytes()
|
||||
);
|
||||
assert_eq!(app.states().applied(), &RuntimeObservation::Unknown);
|
||||
let restart = app
|
||||
.preview_restart()
|
||||
.expect("preview restored generation without invoking adapters");
|
||||
assert_eq!(restart.interface(), "wg0");
|
||||
assert_eq!(restart.systemd_unit(), "wg-quick@wg0.service");
|
||||
assert!(!restart.has_draft());
|
||||
}
|
||||
Reference in New Issue
Block a user