-
Notifications
You must be signed in to change notification settings - Fork 34
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[WIP] Add new D-Bus Updates interface #521
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
//! Updates interface for ushering the update agent to various states. | ||
|
||
use crate::update_agent::{RefreshTick, RefreshTickCommand, UpdateAgent, UpdateAgentState}; | ||
use actix::Addr; | ||
use failure::Error; | ||
use fdo::Error::Failed; | ||
use futures::prelude::*; | ||
use tokio::runtime::Runtime; | ||
use zbus::{dbus_interface, fdo}; | ||
|
||
/// Updates interface for checking for and finalizing updates. | ||
pub(crate) struct Updates { | ||
pub(crate) agent_addr: Addr<UpdateAgent>, | ||
} | ||
|
||
impl Updates { | ||
/// Send msg to the update agent actor and wait for the returned future to resolve. | ||
fn send_msg_to_agent( | ||
&self, | ||
msg: RefreshTick, | ||
) -> Result<Result<UpdateAgentState, Error>, fdo::Error> { | ||
let refresh_time_fut = self.agent_addr.send(msg).map_err(|e| { | ||
let err_msg = format!("failed to get last refresh time from agent actor: {}", e); | ||
log::error!("LastRefreshTime D-Bus method call: {}", err_msg); | ||
Failed(err_msg) | ||
}); | ||
|
||
Runtime::new() | ||
.map_err(|e| { | ||
let err_msg = format!("failed to create runtime to execute future: {}", e); | ||
log::error!("{}", err_msg); | ||
Failed(err_msg) | ||
}) | ||
.and_then(|mut runtime| runtime.block_on(refresh_time_fut)) | ||
} | ||
} | ||
|
||
#[dbus_interface(name = "org.coreos.zincati.Updates")] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd rather keep this under the existing |
||
impl Updates { | ||
/// Check for update immediately. | ||
fn check_update(&self) -> fdo::Result<Vec<String>> { | ||
let msg = RefreshTick { | ||
command: RefreshTickCommand::CheckUpdate, | ||
}; | ||
|
||
self.send_msg_to_agent(msg).and_then(|res| match res { | ||
Ok(state) => match state { | ||
UpdateAgentState::NoNewUpdate => Ok(vec![]), | ||
UpdateAgentState::UpdateAvailable((release, _)) => Ok(vec![release.version]), | ||
_ => { | ||
let err_msg = "update agent reached unexpected state after update check"; | ||
log::error!("CheckUpdate D-Bus method call: {}", err_msg); | ||
Err(Failed(String::from(err_msg))) | ||
} | ||
}, | ||
Err(e) => Err(Failed(format!("{}", e))), | ||
}) | ||
} | ||
|
||
/// Finalize update immediately. | ||
fn finalize_update(&self, force: bool) -> fdo::Result<Vec<String>> { | ||
let msg = RefreshTick { | ||
command: RefreshTickCommand::FinalizeUpdate { force }, | ||
}; | ||
|
||
self.send_msg_to_agent(msg).and_then(|res| match res { | ||
Ok(state) => match state { | ||
UpdateAgentState::UpdateStaged(_) => { | ||
Err(Failed(String::from("update finalization attempt failed"))) | ||
} | ||
UpdateAgentState::UpdateFinalized(release) => Ok(vec![release.version]), | ||
_ => { | ||
let err_msg = | ||
"update agent reached unexpected state after finalization attempt"; | ||
log::error!("FinalizeUpdate D-Bus method call: {}", err_msg); | ||
Err(Failed(String::from(err_msg))) | ||
} | ||
}, | ||
Err(e) => Err(Failed(format!("{}", e))), | ||
}) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -29,7 +29,7 @@ impl Actor for UpdateAgent { | |
} | ||
|
||
// Kick-start the state machine. | ||
Self::tick_now(ctx); | ||
self.tick_now(ctx); | ||
} | ||
} | ||
|
||
|
@@ -48,16 +48,64 @@ impl Handler<LastRefresh> for UpdateAgent { | |
} | ||
} | ||
|
||
pub(crate) struct RefreshTick {} | ||
/// Error thrown when the command that attempted to initiate a refresh tick | ||
/// is not permitted to do so in the current state of the update agent. | ||
#[derive(Debug, Fail)] | ||
struct TickPermissionError {} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
|
||
impl std::fmt::Display for TickPermissionError { | ||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | ||
write!(f, "command not permitted in current update agent state") | ||
} | ||
} | ||
|
||
pub enum RefreshTickCommand { | ||
/// Command to check for updates. | ||
CheckUpdate, | ||
/// Command to finalize an update. | ||
FinalizeUpdate { force: bool }, | ||
/// Tick initiated by update agent itself. | ||
SelfTick, | ||
} | ||
|
||
/// A message to trigger an update agent tick. | ||
pub struct RefreshTick { | ||
/// Command that initiated the tick. | ||
pub(crate) command: RefreshTickCommand, | ||
} | ||
|
||
impl RefreshTick { | ||
/// Return whether the command in the command field is allowed to initiate | ||
/// a refresh tick. | ||
fn check_state(&self, cur_state: &UpdateAgentState) -> bool { | ||
match self.command { | ||
RefreshTickCommand::CheckUpdate => { | ||
matches!(cur_state, UpdateAgentState::NoNewUpdate) | ||
} | ||
RefreshTickCommand::FinalizeUpdate { force: _ } => { | ||
matches!(cur_state, UpdateAgentState::UpdateAvailable { .. }) | ||
} | ||
// SelfTicks are always allowed. | ||
RefreshTickCommand::SelfTick => true, | ||
} | ||
} | ||
} | ||
|
||
impl Message for RefreshTick { | ||
type Result = Result<(), Error>; | ||
type Result = Result<UpdateAgentState, Error>; | ||
} | ||
|
||
impl Handler<RefreshTick> for UpdateAgent { | ||
type Result = ResponseActFuture<Self, Result<(), Error>>; | ||
type Result = ResponseActFuture<Self, Result<UpdateAgentState, Error>>; | ||
|
||
/// Return the state of the update agent's state machine after msg is handled. | ||
fn handle(&mut self, msg: RefreshTick, ctx: &mut Self::Context) -> Self::Result { | ||
// Make sure that the command that sent the RefreshTick is permitted to be | ||
// called in update agent's current state. | ||
if !msg.check_state(&self.state) { | ||
return Box::pin(actix::fut::err(Error::from(TickPermissionError {}))); | ||
} | ||
|
||
fn handle(&mut self, _msg: RefreshTick, ctx: &mut Self::Context) -> Self::Result { | ||
let tick_timestamp = chrono::Utc::now(); | ||
LAST_REFRESH.set(tick_timestamp.timestamp()); | ||
|
||
|
@@ -75,7 +123,7 @@ impl Handler<RefreshTick> for UpdateAgent { | |
} | ||
UpdateAgentState::UpdateStaged((release, _)) => { | ||
let update = release.clone(); | ||
self.tick_finalize_update(update) | ||
self.tick_finalize_update(update, &msg) | ||
} | ||
UpdateAgentState::UpdateFinalized(release) => { | ||
let update = release.clone(); | ||
|
@@ -91,31 +139,50 @@ impl Handler<RefreshTick> for UpdateAgent { | |
"scheduling next agent refresh in {} seconds", | ||
pause.as_secs() | ||
); | ||
Self::tick_later(ctx, pause); | ||
actor.tick_later(ctx, pause); | ||
} else { | ||
let update_timestamp = chrono::Utc::now(); | ||
actor.state_changed = update_timestamp; | ||
Self::tick_now(ctx); | ||
actor.tick_now(ctx); | ||
} | ||
actix::fut::ready(()) | ||
}); | ||
|
||
// Process state machine refresh ticks sequentially. | ||
ctx.wait(update_machine); | ||
|
||
Box::pin(actix::fut::ok(())) | ||
Box::pin(actix::fut::ok(self.state.clone())) | ||
} | ||
} | ||
|
||
impl UpdateAgent { | ||
/// Cancel the scheduled refresh tick whose handle is stored in the update agent's | ||
/// `tick_later_handle` field, if any. | ||
fn cancel_scheduled_ticks(&mut self, ctx: &mut Context<Self>) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. When transitioning "naturally" (no messages received from the D-Bus actor) from a "steady state" (e.g. |
||
if let Some(handle) = self.tick_later_handle { | ||
ctx.cancel_future(handle); | ||
self.tick_later_handle = None; | ||
} | ||
} | ||
|
||
/// Schedule an immediate refresh of the state machine. | ||
pub fn tick_now(ctx: &mut Context<Self>) { | ||
ctx.notify(RefreshTick {}) | ||
pub fn tick_now(&mut self, ctx: &mut Context<Self>) { | ||
// Cancel scheduled ticks, if any. | ||
self.cancel_scheduled_ticks(ctx); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think here it is possible to have a race where a self tick msg and a command msg is sent at the same time, and the command msg arrives first. |
||
ctx.notify(RefreshTick { | ||
command: RefreshTickCommand::SelfTick, | ||
}) | ||
} | ||
|
||
/// Schedule a delayed refresh of the state machine. | ||
pub fn tick_later(ctx: &mut Context<Self>, after: std::time::Duration) -> actix::SpawnHandle { | ||
ctx.notify_later(RefreshTick {}, after) | ||
pub fn tick_later(&mut self, ctx: &mut Context<Self>, after: std::time::Duration) { | ||
let handle = ctx.notify_later( | ||
RefreshTick { | ||
command: RefreshTickCommand::SelfTick, | ||
}, | ||
after, | ||
); | ||
self.tick_later_handle = Some(handle); | ||
} | ||
|
||
/// Pausing interval between state-machine refresh cycles. | ||
|
@@ -322,12 +389,23 @@ impl UpdateAgent { | |
fn tick_finalize_update( | ||
&mut self, | ||
release: Release, | ||
msg: &RefreshTick, | ||
) -> ResponseActFuture<Self, Result<(), ()>> { | ||
trace!("trying to finalize an update"); | ||
|
||
let strategy_can_finalize = self.strategy.can_finalize(); | ||
let state_change = actix::fut::wrap_future::<_, Self>(strategy_can_finalize) | ||
.then(|strategy_can_finalize, actor, _ctx| { | ||
let mut strategy_can_finalize = false; | ||
let mut usersessions_can_finalize = false; | ||
if let RefreshTickCommand::FinalizeUpdate { force } = msg.command { | ||
strategy_can_finalize = force; | ||
// If msg's associated command is FinalizeUpdate, ignore logged in sessions | ||
// and allow finalizations even when active user sessions are present. | ||
usersessions_can_finalize = true; | ||
} | ||
|
||
let strategy_can_finalize_fut = self.strategy.can_finalize(); | ||
let state_change = actix::fut::wrap_future::<_, Self>(strategy_can_finalize_fut) | ||
.then(move |can_finalize, actor, _ctx| { | ||
strategy_can_finalize = strategy_can_finalize || can_finalize; | ||
if !strategy_can_finalize { | ||
update_unit_status(&format!( | ||
"update staged: {}; reboot pending due to update strategy", | ||
|
@@ -338,7 +416,8 @@ impl UpdateAgent { | |
actor.state.update_staged(release); | ||
Box::pin(actix::fut::err(())) | ||
} else { | ||
let usersessions_can_finalize = actor.state.usersessions_can_finalize(); | ||
usersessions_can_finalize = | ||
usersessions_can_finalize || actor.state.usersessions_can_finalize(); | ||
if !usersessions_can_finalize { | ||
update_unit_status(&format!( | ||
"update staged: {}; reboot delayed due to active user sessions", | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's do #519 instead of going this way.