Auto-updater improvements (#8078)

* updater: refactor updater flow into state machine

* updater: delay update randomly within max range

* updater: configurable update delay

* updater: split polling and updater state machine step

* updater: drop state to avoid deadlocking

* updater: fix fetch backoff

* updater: fix overflow in update delay calculation

* updater: configurable update check frequency

* updater: fix update policy frequency comparison

* updater: use lazy_static for platform and platform_id_hash

* updater: refactor operations contract calls into OperationsClient

* updater: make updater generic over operations and fetch client

* updater: fix compilation

* updater: add testing infrastructure and minimal test

* updater: fix minor grumbles

* updater: add test for successful updater flow

* updater: add test for update delay

* updater: add test for update check frequency

* updater: mock time and rng for deterministic tests

* updater: test backoff on failure

* updater: add test for backoff short-circuit on new release

* updater: refactor to increase readability

* updater: cap maximum backoff to one month

* updater: add test for detecting already downloaded update

* updater: add test for updater disable on fatal errors

* updater: add test for pending outdated fetch

* updater: test auto install of updates

* updater: add test for capability updates

* updater: fix capability update

* updater: use ethabi to create event topic filter

* updater: decrease maximum backoff to 1 day

* updater: cap maximum update delay with upcoming fork block number

* updater: receive state mutex guard in updater_step

* updater: overload execute_upgrade to take state mutex guard

* updater: remove unnecessary clone of latest operations info

* updater: remove latest operations info clone when triggering fetch
This commit is contained in:
André Silva 2018-04-03 15:49:23 +01:00 committed by Rando
parent 5e7d42e4a4
commit dcaff6f4c8
10 changed files with 1131 additions and 227 deletions

5
Cargo.lock generated
View File

@ -2229,13 +2229,18 @@ dependencies = [
"ethcore-bytes 0.1.0", "ethcore-bytes 0.1.0",
"ethereum-types 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "ethereum-types 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"ethsync 1.11.0", "ethsync 1.11.0",
"keccak-hash 0.1.0",
"lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
"matches 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
"parity-hash-fetch 1.11.0", "parity-hash-fetch 1.11.0",
"parity-version 1.11.0", "parity-version 1.11.0",
"parking_lot 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)", "parking_lot 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)",
"path 0.1.0", "path 0.1.0",
"rand 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
"semver 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", "semver 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
"target_info 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "target_info 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"tempdir 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
[[package]] [[package]]

View File

@ -16,7 +16,7 @@
//! Test client. //! Test client.
use std::sync::atomic::{AtomicUsize, Ordering as AtomicOrder}; use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering as AtomicOrder};
use std::sync::Arc; use std::sync::Arc;
use std::collections::{HashMap, BTreeMap}; use std::collections::{HashMap, BTreeMap};
use std::mem; use std::mem;
@ -114,6 +114,8 @@ pub struct TestBlockChainClient {
pub traces: RwLock<Option<Vec<LocalizedTrace>>>, pub traces: RwLock<Option<Vec<LocalizedTrace>>>,
/// Pruning history size to report. /// Pruning history size to report.
pub history: RwLock<Option<u64>>, pub history: RwLock<Option<u64>>,
/// Is disabled
pub disabled: AtomicBool,
} }
/// Used for generating test client blocks. /// Used for generating test client blocks.
@ -180,6 +182,7 @@ impl TestBlockChainClient {
first_block: RwLock::new(None), first_block: RwLock::new(None),
traces: RwLock::new(None), traces: RwLock::new(None),
history: RwLock::new(None), history: RwLock::new(None),
disabled: AtomicBool::new(false),
}; };
// insert genesis hash. // insert genesis hash.
@ -356,6 +359,11 @@ impl TestBlockChainClient {
pub fn set_history(&self, h: Option<u64>) { pub fn set_history(&self, h: Option<u64>) {
*self.history.write() = h; *self.history.write() = h;
} }
/// Returns true if the client has been disabled.
pub fn is_disabled(&self) -> bool {
self.disabled.load(AtomicOrder::Relaxed)
}
} }
pub fn get_temp_state_db() -> StateDB { pub fn get_temp_state_db() -> StateDB {
@ -679,8 +687,14 @@ impl BlockChainClient for TestBlockChainClient {
unimplemented!(); unimplemented!();
} }
fn block_number(&self, _id: BlockId) -> Option<BlockNumber> { fn block_number(&self, id: BlockId) -> Option<BlockNumber> {
unimplemented!() match id {
BlockId::Number(number) => Some(number),
BlockId::Earliest => Some(0),
BlockId::Latest => Some(self.chain_info().best_block_number),
BlockId::Hash(ref h) =>
self.numbers.read().iter().find(|&(_, hash)| hash == h).map(|e| *e.0 as u64)
}
} }
fn block_body(&self, id: BlockId) -> Option<encoded::Body> { fn block_body(&self, id: BlockId) -> Option<encoded::Body> {
@ -827,7 +841,7 @@ impl BlockChainClient for TestBlockChainClient {
fn set_spec_name(&self, _: String) { unimplemented!(); } fn set_spec_name(&self, _: String) { unimplemented!(); }
fn disable(&self) { unimplemented!(); } fn disable(&self) { self.disabled.store(true, AtomicOrder::Relaxed); }
fn pruning_info(&self) -> PruningInfo { fn pruning_info(&self) -> PruningInfo {
let best_num = self.chain_info().best_block_number; let best_num = self.chain_info().best_block_number;

View File

@ -280,6 +280,14 @@ usage! {
"--auto-update=[SET]", "--auto-update=[SET]",
"Set a releases set to automatically update and install. SET can be one of: all - All updates in the our release track; critical - Only consensus/security updates; none - No updates will be auto-installed.", "Set a releases set to automatically update and install. SET can be one of: all - All updates in the our release track; critical - Only consensus/security updates; none - No updates will be auto-installed.",
ARG arg_auto_update_delay: (u16) = 100u16, or |c: &Config| c.parity.as_ref()?.auto_update_delay.clone(),
"--auto-update-delay=[NUM]",
"Specify the maximum number of blocks used for randomly delaying updates.",
ARG arg_auto_update_check_frequency: (u16) = 20u16, or |c: &Config| c.parity.as_ref()?.auto_update_check_frequency.clone(),
"--auto-update-check-frequency=[NUM]",
"Specify the number of blocks between each auto-update check.",
ARG arg_release_track: (String) = "current", or |c: &Config| c.parity.as_ref()?.release_track.clone(), ARG arg_release_track: (String) = "current", or |c: &Config| c.parity.as_ref()?.release_track.clone(),
"--release-track=[TRACK]", "--release-track=[TRACK]",
"Set which release track we should use for updates. TRACK can be one of: stable - Stable releases; beta - Beta releases; nightly - Nightly releases (unstable); testing - Testing releases (do not use); current - Whatever track this executable was released on.", "Set which release track we should use for updates. TRACK can be one of: stable - Stable releases; beta - Beta releases; nightly - Nightly releases (unstable); testing - Testing releases (do not use); current - Whatever track this executable was released on.",
@ -1012,6 +1020,8 @@ struct Operating {
mode_timeout: Option<u64>, mode_timeout: Option<u64>,
mode_alarm: Option<u64>, mode_alarm: Option<u64>,
auto_update: Option<String>, auto_update: Option<String>,
auto_update_delay: Option<u16>,
auto_update_check_frequency: Option<u16>,
release_track: Option<String>, release_track: Option<String>,
public_node: Option<bool>, public_node: Option<bool>,
no_download: Option<bool>, no_download: Option<bool>,
@ -1454,6 +1464,8 @@ mod tests {
arg_mode_timeout: 300u64, arg_mode_timeout: 300u64,
arg_mode_alarm: 3600u64, arg_mode_alarm: 3600u64,
arg_auto_update: "none".into(), arg_auto_update: "none".into(),
arg_auto_update_delay: 200u16,
arg_auto_update_check_frequency: 50u16,
arg_release_track: "current".into(), arg_release_track: "current".into(),
flag_public_node: false, flag_public_node: false,
flag_no_download: false, flag_no_download: false,
@ -1711,6 +1723,8 @@ mod tests {
mode_timeout: Some(15u64), mode_timeout: Some(15u64),
mode_alarm: Some(10u64), mode_alarm: Some(10u64),
auto_update: None, auto_update: None,
auto_update_delay: None,
auto_update_check_frequency: None,
release_track: None, release_track: None,
public_node: None, public_node: None,
no_download: None, no_download: None,

View File

@ -3,6 +3,8 @@ mode = "last"
mode_timeout = 300 mode_timeout = 300
mode_alarm = 3600 mode_alarm = 3600
auto_update = "none" auto_update = "none"
auto_update_delay = 200
auto_update_check_frequency = 50
release_track = "current" release_track = "current"
public_node = false public_node = false
no_download = false no_download = false

View File

@ -961,6 +961,8 @@ impl Configuration {
}, },
path: default_hypervisor_path(), path: default_hypervisor_path(),
max_size: 128 * 1024 * 1024, max_size: 128 * 1024 * 1024,
max_delay: self.args.arg_auto_update_delay as u64,
frequency: self.args.arg_auto_update_check_frequency as u64,
}) })
} }
@ -1426,6 +1428,8 @@ mod tests {
track: ReleaseTrack::Unknown, track: ReleaseTrack::Unknown,
path: default_hypervisor_path(), path: default_hypervisor_path(),
max_size: 128 * 1024 * 1024, max_size: 128 * 1024 * 1024,
max_delay: 100,
frequency: 20,
}, },
mode: Default::default(), mode: Default::default(),
tracing: Default::default(), tracing: Default::default(),
@ -1491,8 +1495,8 @@ mod tests {
fn should_parse_updater_options() { fn should_parse_updater_options() {
// when // when
let conf0 = parse(&["parity", "--release-track=testing"]); let conf0 = parse(&["parity", "--release-track=testing"]);
let conf1 = parse(&["parity", "--auto-update", "all", "--no-consensus"]); let conf1 = parse(&["parity", "--auto-update", "all", "--no-consensus", "--auto-update-delay", "300"]);
let conf2 = parse(&["parity", "--no-download", "--auto-update=all", "--release-track=beta"]); let conf2 = parse(&["parity", "--no-download", "--auto-update=all", "--release-track=beta", "--auto-update-delay=300", "--auto-update-check-frequency=100"]);
let conf3 = parse(&["parity", "--auto-update=xxx"]); let conf3 = parse(&["parity", "--auto-update=xxx"]);
// then // then
@ -1503,6 +1507,8 @@ mod tests {
track: ReleaseTrack::Testing, track: ReleaseTrack::Testing,
path: default_hypervisor_path(), path: default_hypervisor_path(),
max_size: 128 * 1024 * 1024, max_size: 128 * 1024 * 1024,
max_delay: 100,
frequency: 20,
}); });
assert_eq!(conf1.update_policy().unwrap(), UpdatePolicy { assert_eq!(conf1.update_policy().unwrap(), UpdatePolicy {
enable_downloading: true, enable_downloading: true,
@ -1511,6 +1517,8 @@ mod tests {
track: ReleaseTrack::Unknown, track: ReleaseTrack::Unknown,
path: default_hypervisor_path(), path: default_hypervisor_path(),
max_size: 128 * 1024 * 1024, max_size: 128 * 1024 * 1024,
max_delay: 300,
frequency: 20,
}); });
assert_eq!(conf2.update_policy().unwrap(), UpdatePolicy { assert_eq!(conf2.update_policy().unwrap(), UpdatePolicy {
enable_downloading: false, enable_downloading: false,
@ -1519,6 +1527,8 @@ mod tests {
track: ReleaseTrack::Beta, track: ReleaseTrack::Beta,
path: default_hypervisor_path(), path: default_hypervisor_path(),
max_size: 128 * 1024 * 1024, max_size: 128 * 1024 * 1024,
max_delay: 300,
frequency: 100,
}); });
assert!(conf3.update_policy().is_err()); assert!(conf3.update_policy().is_err());
} }

View File

@ -6,6 +6,8 @@ license = "GPL-3.0"
authors = ["Parity Technologies <admin@parity.io>"] authors = ["Parity Technologies <admin@parity.io>"]
[dependencies] [dependencies]
keccak-hash = { path = "../util/hash" }
lazy_static = "1.0"
log = "0.3" log = "0.3"
ethabi = "5.1" ethabi = "5.1"
ethabi-derive = "5.0" ethabi-derive = "5.0"
@ -20,3 +22,8 @@ parking_lot = "0.5"
parity-hash-fetch = { path = "../hash-fetch" } parity-hash-fetch = { path = "../hash-fetch" }
parity-version = { path = "../util/version" } parity-version = { path = "../util/version" }
path = { path = "../util/path" } path = { path = "../util/path" }
rand = "0.4"
[dev-dependencies]
tempdir = "0.3"
matches = "0.1"

View File

@ -556,5 +556,42 @@
], ],
"payable": false, "payable": false,
"type": "function" "type": "function"
},
{
"name": "ReleaseAdded",
"type": "event",
"anonymous": false,
"inputs": [
{
"indexed": true,
"name": "client",
"type": "bytes32"
},
{
"indexed": true,
"name": "forkBlock",
"type": "uint32"
},
{
"indexed": false,
"name": "release",
"type": "bytes32"
},
{
"indexed": false,
"name": "track",
"type": "uint8"
},
{
"indexed": false,
"name": "semver",
"type": "uint24"
},
{
"indexed": true,
"name": "critical",
"type": "bool"
}
]
} }
] ]

View File

@ -21,10 +21,12 @@ extern crate ethcore;
extern crate ethcore_bytes as bytes; extern crate ethcore_bytes as bytes;
extern crate ethereum_types; extern crate ethereum_types;
extern crate ethsync; extern crate ethsync;
extern crate keccak_hash as hash;
extern crate parity_hash_fetch as hash_fetch; extern crate parity_hash_fetch as hash_fetch;
extern crate parity_version as version; extern crate parity_version as version;
extern crate parking_lot; extern crate parking_lot;
extern crate path; extern crate path;
extern crate rand;
extern crate semver; extern crate semver;
extern crate target_info; extern crate target_info;
@ -33,8 +35,17 @@ extern crate ethabi_contract;
#[macro_use] #[macro_use]
extern crate ethabi_derive; extern crate ethabi_derive;
#[macro_use] #[macro_use]
extern crate lazy_static;
#[macro_use]
extern crate log; extern crate log;
#[cfg(test)]
extern crate tempdir;
#[cfg(test)]
#[macro_use]
extern crate matches;
mod updater; mod updater;
mod types; mod types;
mod service; mod service;

File diff suppressed because it is too large Load Diff

View File

@ -232,9 +232,9 @@ pub fn default_local_path() -> String {
} }
/// Default hypervisor path /// Default hypervisor path
pub fn default_hypervisor_path() -> String { pub fn default_hypervisor_path() -> PathBuf {
let app_info = AppInfo { name: PRODUCT_HYPERVISOR, author: AUTHOR }; let app_info = AppInfo { name: PRODUCT_HYPERVISOR, author: AUTHOR };
get_app_root(AppDataType::UserData, &app_info).map(|p| p.to_string_lossy().into_owned()).unwrap_or_else(|_| "$HOME/.parity-hypervisor".to_owned()) get_app_root(AppDataType::UserData, &app_info).unwrap_or_else(|_| "$HOME/.parity-hypervisor".into())
} }
/// Get home directory. /// Get home directory.