1use crate::{
10 app_set::{AppSet, AppSetExt as _},
11 async_generator,
12 common::{App, CheckOptions, CheckTiming},
13 configuration::Config,
14 cup_ecdsa::{CupDecorationError, CupVerificationError, Cupv2Handler, RequestMetadata},
15 http_request::{self, HttpRequest},
16 installer::{AppInstallResult, Installer, Plan},
17 metrics::{ClockType, Metrics, MetricsReporter, UpdateCheckFailureReason},
18 policy::{CheckDecision, PolicyEngine, UpdateDecision},
19 protocol::{
20 self,
21 request::{Event, EventErrorCode, EventResult, EventType, GUID, InstallSource},
22 response::{OmahaStatus, Response, UpdateCheck, parse_json_response},
23 },
24 request_builder::{self, RequestBuilder, RequestParams},
25 storage::{Storage, StorageExt},
26 time::{ComplexTime, PartialComplexTime, TimeSource, Timer},
27};
28
29use anyhow::anyhow;
30use futures::{
31 channel::{mpsc, oneshot},
32 future::{self, BoxFuture, Fuse},
33 lock::Mutex,
34 prelude::*,
35 select,
36};
37use http::{Response as HttpResponse, response::Parts};
38use log::{error, info, warn};
39use p256::ecdsa::DerSignature;
40use std::{
41 cmp::min,
42 collections::HashMap,
43 convert::TryInto,
44 rc::Rc,
45 str::Utf8Error,
46 time::{Duration, Instant, SystemTime},
47};
48use thiserror::Error;
49
50pub mod update_check;
51
52mod builder;
53pub use builder::StateMachineBuilder;
54
55mod observer;
56use observer::StateMachineProgressObserver;
57pub use observer::{InstallProgress, StateMachineEvent};
58
59const INSTALL_PLAN_ID: &str = "install_plan_id";
60const UPDATE_FIRST_SEEN_TIME: &str = "update_first_seen_time";
61const UPDATE_FINISH_TIME: &str = "update_finish_time";
62const TARGET_VERSION: &str = "target_version";
63const CONSECUTIVE_FAILED_INSTALL_ATTEMPTS: &str = "consecutive_failed_install_attempts";
64const CHECK_REBOOT_ALLOWED_INTERVAL: Duration = Duration::from_secs(30 * 60);
66const X_RETRY_AFTER: &str = "X-Retry-After";
68const MAX_OMAHA_REQUEST_ATTEMPTS: u64 = 3;
70
71#[derive(Debug)]
74pub struct StateMachine<PE, HR, IN, TM, MR, ST, AS, CH>
75where
76 PE: PolicyEngine,
77 HR: HttpRequest,
78 IN: Installer,
79 TM: Timer,
80 MR: MetricsReporter,
81 ST: Storage,
82 AS: AppSet,
83{
84 config: Config,
86
87 policy_engine: PE,
88
89 http: HR,
90
91 installer: IN,
92
93 timer: TM,
94
95 time_source: PE::TimeSource,
96
97 metrics_reporter: MR,
98
99 storage_ref: Rc<Mutex<ST>>,
100
101 context: update_check::Context,
103
104 app_set: Rc<Mutex<AS>>,
107
108 cup_handler: Option<CH>,
109}
110
111#[derive(Copy, Clone, Debug, Eq, PartialEq)]
112pub enum State {
113 Idle,
114 CheckingForUpdates(InstallSource),
115 ErrorCheckingForUpdate,
116 NoUpdateAvailable,
117 InstallationDeferredByPolicy,
118 InstallingUpdate,
119 WaitingForReboot,
120 InstallationError,
121}
122
123#[derive(Error, Debug)]
126pub enum OmahaRequestError {
127 #[error("Unexpected JSON error constructing update check")]
128 Json(#[from] serde_json::Error),
129
130 #[error("Error building update check HTTP request")]
131 HttpBuilder(#[from] http::Error),
132
133 #[error("Error decorating outgoing request with CUPv2 parameters")]
134 CupDecoration(#[from] CupDecorationError),
135
136 #[error("Error validating incoming response with CUPv2 protocol")]
137 CupValidation(#[from] CupVerificationError),
138
139 #[error("HTTP transport error performing update check")]
141 HttpTransport(#[from] http_request::Error),
142
143 #[error("HTTP error performing update check: {0}")]
144 HttpStatus(hyper::StatusCode),
145}
146
147impl From<request_builder::Error> for OmahaRequestError {
148 fn from(err: request_builder::Error) -> Self {
149 match err {
150 request_builder::Error::Json(e) => OmahaRequestError::Json(e),
151 request_builder::Error::Http(e) => OmahaRequestError::HttpBuilder(e),
152 request_builder::Error::Cup(e) => OmahaRequestError::CupDecoration(e),
153 }
154 }
155}
156
157impl From<http::StatusCode> for OmahaRequestError {
158 fn from(sc: http::StatusCode) -> Self {
159 OmahaRequestError::HttpStatus(sc)
160 }
161}
162
163#[derive(Error, Debug)]
166pub enum ResponseParseError {
167 #[error("Response was not valid UTF-8")]
168 Utf8(#[from] Utf8Error),
169
170 #[error("Unexpected JSON error parsing update check response")]
171 Json(#[from] serde_json::Error),
172}
173
174#[derive(Error, Debug)]
175pub enum UpdateCheckError {
176 #[error("Error checking with Omaha")]
177 OmahaRequest(#[from] OmahaRequestError),
178
179 #[error("Error parsing Omaha response")]
180 ResponseParser(#[from] ResponseParseError),
181
182 #[error("Unable to create an install plan")]
183 InstallPlan(#[source] anyhow::Error),
184}
185
186#[derive(Clone)]
188pub struct ControlHandle(mpsc::Sender<ControlRequest>);
189
190#[derive(Debug, Clone, Error, PartialEq, Eq)]
192#[error("state machine dropped before all its control handles")]
193pub struct StateMachineGone;
194
195impl From<mpsc::SendError> for StateMachineGone {
196 fn from(_: mpsc::SendError) -> Self {
197 StateMachineGone
198 }
199}
200
201impl From<oneshot::Canceled> for StateMachineGone {
202 fn from(_: oneshot::Canceled) -> Self {
203 StateMachineGone
204 }
205}
206
207enum ControlRequest {
208 StartUpdateCheck { options: CheckOptions, responder: oneshot::Sender<StartUpdateCheckResponse> },
209}
210
211#[derive(Debug, Clone, PartialEq, Eq)]
213pub enum StartUpdateCheckResponse {
214 Started,
216
217 AlreadyRunning,
220
221 Throttled,
223}
224
225impl ControlHandle {
226 pub async fn start_update_check(
229 &mut self,
230 options: CheckOptions,
231 ) -> Result<StartUpdateCheckResponse, StateMachineGone> {
232 let (responder, receive_response) = oneshot::channel();
233 self.0.send(ControlRequest::StartUpdateCheck { options, responder }).await?;
234 Ok(receive_response.await?)
235 }
236}
237
238#[derive(Debug)]
239enum RebootAfterUpdate<T> {
240 Needed(T),
241 NotNeeded,
242}
243
244impl<PE, HR, IN, TM, MR, ST, AS, IR, PL, CH> StateMachine<PE, HR, IN, TM, MR, ST, AS, CH>
245where
246 PE: PolicyEngine<InstallResult = IR, InstallPlan = PL>,
247 HR: HttpRequest,
248 IN: Installer<InstallResult = IR, InstallPlan = PL>,
249 TM: Timer,
250 MR: MetricsReporter,
251 ST: Storage,
252 AS: AppSet,
253 CH: Cupv2Handler,
254 IR: 'static + Send,
255 PL: Plan,
256{
257 async fn update_next_update_time(
259 &mut self,
260 co: &mut async_generator::Yield<StateMachineEvent>,
261 ) -> CheckTiming {
262 let apps = self.app_set.lock().await.get_apps();
263 let timing = self
264 .policy_engine
265 .compute_next_update_time(&apps, &self.context.schedule, &self.context.state)
266 .await;
267 self.context.schedule.next_update_time = Some(timing);
268
269 co.yield_(StateMachineEvent::ScheduleChange(self.context.schedule)).await;
270 info!("Calculated check timing: {}", timing);
271 timing
272 }
273
274 async fn make_wait_to_next_check(
276 &mut self,
277 check_timing: CheckTiming,
278 ) -> Fuse<BoxFuture<'static, ()>> {
279 if let Some(minimum_wait) = check_timing.minimum_wait {
280 future::join(
284 self.timer.wait_for(minimum_wait),
285 self.timer.wait_until(check_timing.time),
286 )
287 .map(|_| ())
288 .boxed()
289 .fuse()
290 } else {
291 self.timer.wait_until(check_timing.time).fuse()
294 }
295 }
296
297 async fn run(
298 mut self,
299 mut control: mpsc::Receiver<ControlRequest>,
300 mut co: async_generator::Yield<StateMachineEvent>,
301 ) {
302 {
303 let app_set = self.app_set.lock().await;
304 if !app_set.all_valid() {
305 error!("App set not valid, not starting state machine: {:#?}", app_set.get_apps());
306 return;
307 }
308 }
309
310 let state_machine_start_monotonic_time = self.time_source.now_in_monotonic();
311
312 let mut should_report_waited_for_reboot_duration = false;
313
314 let update_finish_time = {
315 let storage = self.storage_ref.lock().await;
316 let update_finish_time = storage.get_time(UPDATE_FINISH_TIME).await;
317 if update_finish_time.is_some()
318 && let Some(target_version) = storage.get_string(TARGET_VERSION).await
319 && target_version == self.config.os.version
320 {
321 should_report_waited_for_reboot_duration = true;
322 }
323 update_finish_time
324 };
325
326 loop {
327 info!("Initial context: {:?}", self.context);
328
329 if should_report_waited_for_reboot_duration {
330 match self.report_waited_for_reboot_duration(
331 update_finish_time.unwrap(),
332 state_machine_start_monotonic_time,
333 self.time_source.now(),
334 ) {
335 Ok(()) => {
336 should_report_waited_for_reboot_duration = false;
338
339 let mut storage = self.storage_ref.lock().await;
340 storage.remove_or_log(UPDATE_FINISH_TIME).await;
341 storage.remove_or_log(TARGET_VERSION).await;
342 storage.commit_or_log().await;
343 }
344 Err(e) => {
345 warn!("Couldn't report wait for reboot duration: {:#}, will try again", e);
346 }
347 }
348 }
349
350 let (mut options, responder) = {
351 let check_timing = self.update_next_update_time(&mut co).await;
352 let mut wait_to_next_check = self.make_wait_to_next_check(check_timing).await;
353
354 select! {
357 () = wait_to_next_check => (CheckOptions::default(), None),
358 ControlRequest::StartUpdateCheck{options, responder} = control.select_next_some() => {
359 (options, Some(responder))
360 }
361 }
362 };
363
364 let reboot_after_update = {
365 let apps = self.app_set.lock().await.get_apps();
366 info!("Checking to see if an update check is allowed at this time for {:?}", apps);
367 let decision = self
368 .policy_engine
369 .update_check_allowed(
370 &apps,
371 &self.context.schedule,
372 &self.context.state,
373 &options,
374 )
375 .await;
376
377 info!("The update check decision is: {:?}", decision);
378
379 let request_params = match decision {
380 CheckDecision::Ok(rp) | CheckDecision::OkUpdateDeferred(rp) => rp,
382
383 CheckDecision::TooSoon
385 | CheckDecision::ThrottledByPolicy
386 | CheckDecision::DeniedByPolicy => {
387 info!("The update check is not allowed at this time.");
388 if let Some(responder) = responder {
389 let _ = responder.send(StartUpdateCheckResponse::Throttled);
390 }
391 continue;
392 }
393 };
394 if let Some(responder) = responder {
395 let _ = responder.send(StartUpdateCheckResponse::Started);
396 }
397
398 let update_check = self.start_update_check(request_params, &mut co).fuse();
400 futures::pin_mut!(update_check);
401
402 loop {
405 select! {
406 update_check_result = update_check => break update_check_result,
407 ControlRequest::StartUpdateCheck{
408 options: new_options,
409 responder
410 } = control.select_next_some() => {
411 if new_options.source == InstallSource::OnDemand {
412 info!("Got on demand update check request, ensuring ongoing check is on demand");
413 options.source = InstallSource::OnDemand;
415 }
416
417 let _ = responder.send(StartUpdateCheckResponse::AlreadyRunning);
418 }
419 }
420 }
421 };
422
423 if let RebootAfterUpdate::Needed(install_result) = reboot_after_update {
424 Self::yield_state(State::WaitingForReboot, &mut co).await;
425 self.wait_for_reboot(options, &mut control, install_result, &mut co).await;
426 }
427
428 Self::yield_state(State::Idle, &mut co).await;
429 }
430 }
431
432 async fn wait_for_reboot(
433 &mut self,
434 mut options: CheckOptions,
435 control: &mut mpsc::Receiver<ControlRequest>,
436 install_result: IN::InstallResult,
437 co: &mut async_generator::Yield<StateMachineEvent>,
438 ) {
439 if !self.policy_engine.reboot_allowed(&options, &install_result).await {
440 let wait_to_see_if_reboot_allowed =
441 self.timer.wait_for(CHECK_REBOOT_ALLOWED_INTERVAL).fuse();
442 futures::pin_mut!(wait_to_see_if_reboot_allowed);
443
444 let check_timing = self.update_next_update_time(co).await;
445 let wait_to_next_ping = self.make_wait_to_next_check(check_timing).await;
446 futures::pin_mut!(wait_to_next_ping);
447
448 loop {
449 select! {
453 () = wait_to_see_if_reboot_allowed => {
454 if self.policy_engine.reboot_allowed(&options, &install_result).await {
455 break;
456 }
457 info!("Reboot not allowed at the moment, will try again in 30 minutes...");
458 wait_to_see_if_reboot_allowed.set(
459 self.timer.wait_for(CHECK_REBOOT_ALLOWED_INTERVAL).fuse()
460 );
461 },
462 () = wait_to_next_ping => {
463 self.ping_omaha(co).await;
464 let check_timing = self.update_next_update_time(co).await;
465 wait_to_next_ping.set(self.make_wait_to_next_check(check_timing).await);
466 },
467 ControlRequest::StartUpdateCheck{
468 options: new_options,
469 responder
470 } = control.select_next_some() => {
471 let _ = responder.send(StartUpdateCheckResponse::AlreadyRunning);
472 if new_options.source == InstallSource::OnDemand {
473 info!("Waiting for reboot, but ensuring that InstallSource is OnDemand");
474 options.source = InstallSource::OnDemand;
475
476 if self.policy_engine.reboot_allowed(&options, &install_result).await {
477 info!("Upgraded update check request to on demand, policy allowed reboot");
478 break;
479 }
480 };
481 }
482 }
483 }
484 }
485 info!("Rebooting the system at the end of a successful update");
486 if let Err(e) = self.installer.perform_reboot().await {
487 error!("Unable to reboot the system: {}", e);
488 }
489 }
490
491 fn report_waited_for_reboot_duration(
496 &mut self,
497 update_finish_time: SystemTime,
498 state_machine_start_monotonic_time: Instant,
499 now: ComplexTime,
500 ) -> Result<(), anyhow::Error> {
501 let update_finish_time_to_now =
504 now.wall_duration_since(update_finish_time).map_err(|e| {
505 anyhow!(
506 "Update finish time later than now, can't report waited for reboot duration,
507 update finish time: {:?}, now: {:?}, error: {:?}",
508 update_finish_time,
509 now,
510 e,
511 )
512 })?;
513
514 let state_machine_start_to_now = now
523 .mono
524 .checked_duration_since(state_machine_start_monotonic_time)
525 .ok_or_else(|| {
526 error!("Monotonic time appears to have gone backwards");
527 anyhow!(
528 "State machine start later than now, can't report waited for reboot duration. \
529 State machine start: {:?}, now: {:?}",
530 state_machine_start_monotonic_time,
531 now.mono,
532 )
533 })?;
534
535 let waited_for_reboot_duration =
536 update_finish_time_to_now.checked_sub(state_machine_start_to_now).ok_or_else(|| {
537 anyhow!(
538 "Can't report waiting for reboot duration, update finish time to now smaller \
539 than state machine start to now. Update finish time to now: {:?}, state \
540 machine start to now: {:?}",
541 update_finish_time_to_now,
542 state_machine_start_to_now,
543 )
544 })?;
545
546 info!("Waited {} seconds for reboot.", waited_for_reboot_duration.as_secs());
547 self.report_metrics(Metrics::WaitedForRebootDuration(waited_for_reboot_duration));
548 Ok(())
549 }
550
551 async fn report_check_interval(&mut self, install_source: InstallSource) {
554 let now = self.time_source.now();
555
556 match self.context.schedule.last_update_check_time {
557 Some(PartialComplexTime::Wall(t)) => match now.wall_duration_since(t) {
560 Ok(interval) => self.report_metrics(Metrics::UpdateCheckInterval {
561 interval,
562 clock: ClockType::Wall,
563 install_source,
564 }),
565 Err(e) => warn!("Last check time is in the future: {}", e),
566 },
567
568 Some(PartialComplexTime::Complex(t)) => match now.mono.checked_duration_since(t.mono) {
573 Some(interval) => self.report_metrics(Metrics::UpdateCheckInterval {
574 interval,
575 clock: ClockType::Monotonic,
576 install_source,
577 }),
578 None => error!("Monotonic time in the past"),
579 },
580
581 _ => {}
587 }
588
589 self.context.schedule.last_update_check_time = now.into();
590 }
591
592 async fn start_update_check(
596 &mut self,
597 request_params: RequestParams,
598 co: &mut async_generator::Yield<StateMachineEvent>,
599 ) -> RebootAfterUpdate<IN::InstallResult> {
600 let apps = self.app_set.lock().await.get_apps();
601 let result = self.perform_update_check(request_params, apps, co).await;
602
603 let (result, reboot_after_update) = match result {
604 Ok((result, reboot_after_update)) => {
605 info!("Update check result: {:?}", result);
606 self.context.schedule.last_update_time = Some(self.time_source.now().into());
608
609 let install_success =
611 result.app_responses.iter().fold(None, |result, app| {
612 match (result, &app.result) {
613 (_, update_check::Action::InstallPlanExecutionError) => Some(false),
614 (None, update_check::Action::Updated) => Some(true),
615 (result, _) => result,
616 }
617 });
618
619 self.report_attempts_to_successful_check(true).await;
622
623 self.app_set.lock().await.update_from_omaha(&result.app_responses);
624
625 if let Some(success) = install_success {
628 self.report_attempts_to_successful_install(success).await;
629 }
630
631 (Ok(result), reboot_after_update)
632 }
634 Err(error) => {
635 error!("Update check failed: {:?}", error);
636
637 let failure_reason = match &error {
638 UpdateCheckError::ResponseParser(_) | UpdateCheckError::InstallPlan(_) => {
639 self.context.schedule.last_update_time =
641 Some(self.time_source.now().into());
642
643 UpdateCheckFailureReason::Omaha
644 }
645 UpdateCheckError::OmahaRequest(request_error) => match request_error {
646 OmahaRequestError::Json(_)
647 | OmahaRequestError::HttpBuilder(_)
648 | OmahaRequestError::CupDecoration(_)
649 | OmahaRequestError::CupValidation(_) => UpdateCheckFailureReason::Internal,
650 OmahaRequestError::HttpTransport(_) | OmahaRequestError::HttpStatus(_) => {
651 UpdateCheckFailureReason::Network
652 }
653 },
654 };
655 self.report_metrics(Metrics::UpdateCheckFailureReason(failure_reason));
656
657 self.report_attempts_to_successful_check(false).await;
658 (Err(error), RebootAfterUpdate::NotNeeded)
659 }
660 };
661
662 co.yield_(StateMachineEvent::ScheduleChange(self.context.schedule)).await;
663 co.yield_(StateMachineEvent::ProtocolStateChange(self.context.state.clone())).await;
664 co.yield_(StateMachineEvent::UpdateCheckResult(result)).await;
665
666 self.persist_data().await;
667
668 reboot_after_update
669 }
670
671 async fn report_attempts_to_successful_check(&mut self, success: bool) {
674 let attempts = self.context.state.consecutive_failed_update_checks + 1;
675 if success {
676 self.context.state.consecutive_failed_update_checks = 0;
677 self.report_metrics(Metrics::AttemptsToSuccessfulCheck(attempts as u64));
678 } else {
679 self.context.state.consecutive_failed_update_checks = attempts;
680 }
681 }
682
683 async fn report_attempts_to_successful_install(&mut self, success: bool) {
686 let storage_ref = self.storage_ref.clone();
687 let mut storage = storage_ref.lock().await;
688 let attempts = storage.get_int(CONSECUTIVE_FAILED_INSTALL_ATTEMPTS).await.unwrap_or(0) + 1;
689
690 self.report_metrics(Metrics::AttemptsToSuccessfulInstall {
691 count: attempts as u64,
692 successful: success,
693 });
694
695 if success {
696 storage.remove_or_log(CONSECUTIVE_FAILED_INSTALL_ATTEMPTS).await;
697 } else if let Err(e) = storage.set_int(CONSECUTIVE_FAILED_INSTALL_ATTEMPTS, attempts).await
698 {
699 error!("Unable to persist {}: {}", CONSECUTIVE_FAILED_INSTALL_ATTEMPTS, e);
700 }
701 }
702
703 async fn persist_data(&self) {
705 let mut storage = self.storage_ref.lock().await;
706 self.context.persist(&mut *storage).await;
707 self.app_set.lock().await.persist(&mut *storage).await;
708
709 storage.commit_or_log().await;
710 }
711
712 async fn perform_update_check(
715 &mut self,
716 request_params: RequestParams,
717 apps: Vec<App>,
718 co: &mut async_generator::Yield<StateMachineEvent>,
719 ) -> Result<(update_check::Response, RebootAfterUpdate<IN::InstallResult>), UpdateCheckError>
720 {
721 Self::yield_state(State::CheckingForUpdates(request_params.source), co).await;
722
723 self.report_check_interval(request_params.source).await;
724
725 let config = self.config.clone();
727 let mut request_builder = RequestBuilder::new(&config, &request_params);
728 for app in &apps {
729 request_builder = request_builder.add_update_check(app).add_ping(app);
730 }
731 let session_id = GUID::new();
732 request_builder = request_builder.session_id(session_id.clone());
733
734 let mut omaha_request_attempt = 1;
735
736 let loop_result = loop {
739 let omaha_check_start_time = self.time_source.now_in_monotonic();
741 request_builder = request_builder.request_id(GUID::new());
742 let result = self.do_omaha_request_and_update_context(&request_builder, co).await;
743
744 {
746 let now = self.time_source.now_in_monotonic();
749 let duration = now.checked_duration_since(omaha_check_start_time);
750
751 if let Some(response_time) = duration {
752 self.report_metrics(Metrics::UpdateCheckResponseTime {
753 response_time,
754 successful: result.is_ok(),
755 });
756 } else {
757 error!(
759 "now: {:?}, is before omaha_check_start_time: {:?}",
760 now, omaha_check_start_time
761 );
762 }
763 }
764
765 match result {
766 Ok(res) => {
767 break Ok(res);
768 }
769 Err(OmahaRequestError::Json(e)) => {
770 error!("Unable to construct request body! {:?}", e);
771 Self::yield_state(State::ErrorCheckingForUpdate, co).await;
772 break Err(UpdateCheckError::OmahaRequest(e.into()));
773 }
774 Err(OmahaRequestError::HttpBuilder(e)) => {
775 error!("Unable to construct HTTP request! {:?}", e);
776 Self::yield_state(State::ErrorCheckingForUpdate, co).await;
777 break Err(UpdateCheckError::OmahaRequest(e.into()));
778 }
779 Err(OmahaRequestError::CupDecoration(e)) => {
780 error!("Unable to decorate HTTP request with CUPv2 parameters! {:?}", e);
781 Self::yield_state(State::ErrorCheckingForUpdate, co).await;
782 break Err(UpdateCheckError::OmahaRequest(e.into()));
783 }
784 Err(OmahaRequestError::CupValidation(e)) => {
785 error!("Unable to validate HTTP response with CUPv2 parameters! {:?}", e);
786 Self::yield_state(State::ErrorCheckingForUpdate, co).await;
787 break Err(UpdateCheckError::OmahaRequest(e.into()));
788 }
789 Err(OmahaRequestError::HttpTransport(e)) => {
790 warn!("Unable to contact Omaha: {:?}", e);
791 if omaha_request_attempt >= MAX_OMAHA_REQUEST_ATTEMPTS
794 || e.is_user()
795 || self.context.state.server_dictated_poll_interval.is_some()
796 {
797 Self::yield_state(State::ErrorCheckingForUpdate, co).await;
798 break Err(UpdateCheckError::OmahaRequest(e.into()));
799 }
800 }
801 Err(OmahaRequestError::HttpStatus(e)) => {
802 warn!("Unable to contact Omaha: {:?}", e);
803 if omaha_request_attempt >= MAX_OMAHA_REQUEST_ATTEMPTS
804 || self.context.state.server_dictated_poll_interval.is_some()
805 {
806 Self::yield_state(State::ErrorCheckingForUpdate, co).await;
807 break Err(UpdateCheckError::OmahaRequest(e.into()));
808 }
809 }
810 }
811
812 let backoff_time_secs = 1 << (omaha_request_attempt - 1);
815 let backoff_time = randomize(backoff_time_secs * 1000, 1000);
816 info!("Waiting {} ms before retrying...", backoff_time);
817 self.timer.wait_for(Duration::from_millis(backoff_time)).await;
818
819 omaha_request_attempt += 1;
820 };
821
822 self.report_metrics(Metrics::RequestsPerCheck {
823 count: omaha_request_attempt,
824 successful: loop_result.is_ok(),
825 });
826
827 let (_parts, data, request_metadata, signature) = loop_result?;
828
829 let response = match Self::parse_omaha_response(&data) {
830 Ok(res) => res,
831 Err(err) => {
832 warn!("Unable to parse Omaha response: {:?}", err);
833 Self::yield_state(State::ErrorCheckingForUpdate, co).await;
834 self.report_omaha_event_and_update_context(
835 &request_params,
836 Event::error(EventErrorCode::ParseResponse),
837 &apps,
838 &session_id,
839 &apps.iter().map(|app| (app.id.clone(), None)).collect(),
840 None,
841 co,
842 )
843 .await;
844 return Err(UpdateCheckError::ResponseParser(err));
845 }
846 };
847
848 info!("result: {:?}", response);
849
850 co.yield_(StateMachineEvent::OmahaServerResponse(response.clone())).await;
851
852 let statuses = Self::get_app_update_statuses(&response);
853 for (app_id, status) in &statuses {
854 info!("Omaha update check status: {} => {:?}", app_id, status);
856 }
857
858 let apps_with_update: Vec<_> = response
859 .apps
860 .iter()
861 .filter(|app| {
862 matches!(app.update_check, Some(UpdateCheck { status: OmahaStatus::Ok, .. }))
863 })
864 .collect();
865
866 if apps_with_update.is_empty() {
867 Self::yield_state(State::NoUpdateAvailable, co).await;
870 Self::make_not_updated_result(response, update_check::Action::NoUpdate)
871 } else {
872 info!(
873 "At least one app has an update, proceeding to build and process an Install Plan"
874 );
875 let next_versions: HashMap<String, Option<String>> = apps_with_update
879 .iter()
880 .map(|app| (app.id.clone(), app.get_manifest_version()))
881 .collect();
882 let install_plan = match self
883 .installer
884 .try_create_install_plan(
885 &request_params,
886 request_metadata.as_ref(),
887 &response,
888 data,
889 signature.map(|s| s.as_bytes().to_vec()),
890 )
891 .await
892 {
893 Ok(plan) => plan,
894 Err(e) => {
895 error!("Unable to construct install plan! {}", e);
896 Self::yield_state(State::InstallingUpdate, co).await;
897 Self::yield_state(State::InstallationError, co).await;
898 self.report_omaha_event_and_update_context(
899 &request_params,
900 Event::error(EventErrorCode::ConstructInstallPlan),
901 &apps,
902 &session_id,
903 &next_versions,
904 None,
905 co,
906 )
907 .await;
908 return Err(UpdateCheckError::InstallPlan(e.into()));
909 }
910 };
911
912 info!("Validating Install Plan with Policy");
913 let install_plan_decision = self.policy_engine.update_can_start(&install_plan).await;
914 match install_plan_decision {
915 UpdateDecision::Ok => {
916 info!("Proceeding with install plan.");
917 }
918 UpdateDecision::DeferredByPolicy => {
919 info!("Install plan was deferred by Policy.");
920 let event = Event {
923 event_type: EventType::UpdateComplete,
924 event_result: EventResult::UpdateDeferred,
925 ..Event::default()
926 };
927 self.report_omaha_event_and_update_context(
928 &request_params,
929 event,
930 &apps,
931 &session_id,
932 &next_versions,
933 None,
934 co,
935 )
936 .await;
937
938 Self::yield_state(State::InstallationDeferredByPolicy, co).await;
939
940 return Self::make_not_updated_result(
941 response,
942 update_check::Action::DeferredByPolicy,
943 );
944 }
945 UpdateDecision::DeniedByPolicy => {
946 warn!("Install plan was denied by Policy, see Policy logs for reasoning");
947 self.report_omaha_event_and_update_context(
948 &request_params,
949 Event::error(EventErrorCode::DeniedByPolicy),
950 &apps,
951 &session_id,
952 &next_versions,
953 None,
954 co,
955 )
956 .await;
957
958 return Self::make_not_updated_result(
959 response,
960 update_check::Action::DeniedByPolicy,
961 );
962 }
963 }
964
965 Self::yield_state(State::InstallingUpdate, co).await;
966 self.report_omaha_event_and_update_context(
967 &request_params,
968 Event::success(EventType::UpdateDownloadStarted),
969 &apps,
970 &session_id,
971 &next_versions,
972 None,
973 co,
974 )
975 .await;
976
977 let install_plan_id = install_plan.id();
978 let update_start_time = self.time_source.now_in_walltime();
979 let update_first_seen_time =
980 self.record_update_first_seen_time(&install_plan_id, update_start_time).await;
981
982 let (send, mut recv) = mpsc::channel(0);
983 let observer = StateMachineProgressObserver(send);
984 let perform_install = async {
985 let result = self.installer.perform_install(&install_plan, Some(&observer)).await;
986 drop(observer);
988 result
989 };
990 let yield_progress = async {
991 while let Some(progress) = recv.next().await {
992 co.yield_(StateMachineEvent::InstallProgressChange(progress)).await;
993 }
994 };
995
996 let ((install_result, mut app_install_results), ()) =
997 future::join(perform_install, yield_progress).await;
998 let no_apps_failed = app_install_results.iter().all(|result| {
999 matches!(result, AppInstallResult::Installed | AppInstallResult::Deferred)
1000 });
1001 let update_finish_time = self.time_source.now_in_walltime();
1002 let install_duration = match update_finish_time.duration_since(update_start_time) {
1003 Ok(duration) => {
1004 let metrics = if no_apps_failed {
1005 Metrics::SuccessfulUpdateDuration(duration)
1006 } else {
1007 Metrics::FailedUpdateDuration(duration)
1008 };
1009 self.report_metrics(metrics);
1010 Some(duration)
1011 }
1012 Err(e) => {
1013 warn!("Update start time is in the future: {}", e);
1014 None
1015 }
1016 };
1017
1018 let config = self.config.clone();
1019 let mut request_builder = RequestBuilder::new(&config, &request_params);
1020 let mut events = vec![];
1021 let mut installed_apps = vec![];
1022 for (response_app, app_install_result) in
1023 apps_with_update.iter().zip(&app_install_results)
1024 {
1025 match apps.iter().find(|app| app.id == response_app.id) {
1026 Some(app) => {
1027 let event = match app_install_result {
1028 AppInstallResult::Installed => {
1029 installed_apps.push(app);
1030 Event::success(EventType::UpdateDownloadFinished)
1031 }
1032 AppInstallResult::Deferred => Event {
1033 event_type: EventType::UpdateComplete,
1034 event_result: EventResult::UpdateDeferred,
1035 ..Event::default()
1036 },
1037 AppInstallResult::Failed(_) => {
1038 Event::error(EventErrorCode::Installation)
1039 }
1040 };
1041 let event = Event {
1042 previous_version: Some(app.version.to_string()),
1043 next_version: response_app.get_manifest_version(),
1044 download_time_ms: install_duration
1045 .and_then(|d| d.as_millis().try_into().ok()),
1046 ..event
1047 };
1048 request_builder = request_builder.add_event(app, event.clone());
1049 events.push(event);
1050 }
1051 None => {
1052 error!("unknown app id in omaha response: {:?}", response_app.id);
1053 }
1054 }
1055 }
1056 request_builder =
1057 request_builder.session_id(session_id.clone()).request_id(GUID::new());
1058 if let Err(e) = self.do_omaha_request_and_update_context(&request_builder, co).await {
1059 for event in events {
1060 self.report_metrics(Metrics::OmahaEventLost(event));
1061 }
1062 warn!("Unable to report event to Omaha: {:?}", e);
1063 }
1064
1065 if !installed_apps.is_empty() {
1069 self.report_omaha_event_and_update_context(
1070 &request_params,
1071 Event::success(EventType::UpdateComplete),
1072 installed_apps,
1073 &session_id,
1074 &next_versions,
1075 install_duration,
1076 co,
1077 )
1078 .await;
1079 }
1080
1081 let mut errors = vec![];
1082 let daystart = response.daystart;
1083 let app_responses = response
1084 .apps
1085 .into_iter()
1086 .map(|app| update_check::AppResponse {
1087 app_id: app.id,
1088 cohort: app.cohort,
1089 user_counting: daystart.clone().into(),
1090 result: match app.update_check {
1091 Some(UpdateCheck { status: OmahaStatus::Ok, .. }) => {
1092 match app_install_results.remove(0) {
1093 AppInstallResult::Installed => update_check::Action::Updated,
1094 AppInstallResult::Deferred => {
1095 update_check::Action::DeferredByPolicy
1096 }
1097 AppInstallResult::Failed(e) => {
1098 errors.push(e);
1099 update_check::Action::InstallPlanExecutionError
1100 }
1101 }
1102 }
1103 _ => update_check::Action::NoUpdate,
1104 },
1105 })
1106 .collect();
1107
1108 if !errors.is_empty() {
1109 for e in errors {
1110 co.yield_(StateMachineEvent::InstallerError(Some(Box::new(e)))).await;
1111 }
1112 Self::yield_state(State::InstallationError, co).await;
1113
1114 return Ok((
1115 update_check::Response { app_responses },
1116 RebootAfterUpdate::NotNeeded,
1117 ));
1118 }
1119
1120 match update_finish_time.duration_since(update_first_seen_time) {
1121 Ok(duration) => {
1122 self.report_metrics(Metrics::SuccessfulUpdateFromFirstSeen(duration))
1123 }
1124 Err(e) => warn!("Update first seen time is in the future: {}", e),
1125 }
1126 {
1127 let mut storage = self.storage_ref.lock().await;
1128 if let Err(e) = storage.set_time(UPDATE_FINISH_TIME, update_finish_time).await {
1129 error!("Unable to persist {}: {}", UPDATE_FINISH_TIME, e);
1130 }
1131 let app_set = self.app_set.lock().await;
1132 let system_app_id = app_set.get_system_app_id();
1133 if let Some(next_version) = next_versions.get(system_app_id) {
1135 let target_version = next_version.as_deref().unwrap_or_else(|| {
1136 error!("Target version string not found in Omaha response.");
1137 "UNKNOWN"
1138 });
1139 if let Err(e) = storage.set_string(TARGET_VERSION, target_version).await {
1140 error!("Unable to persist {}: {}", TARGET_VERSION, e);
1141 }
1142 }
1143 storage.commit_or_log().await;
1144 }
1145
1146 let reboot_after_update = if self.policy_engine.reboot_needed(&install_plan).await {
1147 RebootAfterUpdate::Needed(install_result)
1148 } else {
1149 RebootAfterUpdate::NotNeeded
1150 };
1151
1152 Ok((update_check::Response { app_responses }, reboot_after_update))
1153 }
1154 }
1155
1156 #[allow(clippy::too_many_arguments)]
1159 async fn report_omaha_event_and_update_context<'a>(
1160 &'a mut self,
1161 request_params: &'a RequestParams,
1162 event: Event,
1163 apps: impl IntoIterator<Item = &App>,
1164 session_id: &GUID,
1165 next_versions: &HashMap<String, Option<String>>,
1166 install_duration: Option<Duration>,
1167 co: &mut async_generator::Yield<StateMachineEvent>,
1168 ) {
1169 let config = self.config.clone();
1170 let mut request_builder = RequestBuilder::new(&config, request_params);
1171 for app in apps {
1172 if let Some(next_version) = next_versions.get(&app.id) {
1174 let event = Event {
1175 previous_version: Some(app.version.to_string()),
1176 next_version: next_version.clone(),
1177 download_time_ms: install_duration.and_then(|d| d.as_millis().try_into().ok()),
1178 ..event.clone()
1179 };
1180 request_builder = request_builder.add_event(app, event);
1181 }
1182 }
1183 request_builder = request_builder.session_id(session_id.clone()).request_id(GUID::new());
1184 if let Err(e) = self.do_omaha_request_and_update_context(&request_builder, co).await {
1185 self.report_metrics(Metrics::OmahaEventLost(event));
1186 warn!("Unable to report event to Omaha: {:?}", e);
1187 }
1188 }
1189
1190 async fn ping_omaha(&mut self, co: &mut async_generator::Yield<StateMachineEvent>) {
1192 let apps = self.app_set.lock().await.get_apps();
1193 let request_params = RequestParams {
1194 source: InstallSource::ScheduledTask,
1195 use_configured_proxies: true,
1196 disable_updates: false,
1197 offer_update_if_same_version: false,
1198 };
1199 let config = self.config.clone();
1200 let mut request_builder = RequestBuilder::new(&config, &request_params);
1201 for app in &apps {
1202 request_builder = request_builder.add_ping(app);
1203 }
1204 request_builder = request_builder.session_id(GUID::new()).request_id(GUID::new());
1205
1206 let (_parts, data, _request_metadata, _signature) =
1207 match self.do_omaha_request_and_update_context(&request_builder, co).await {
1208 Ok(res) => res,
1209 Err(e) => {
1210 error!("Ping Omaha failed: {:#}", anyhow!(e));
1211 self.context.state.consecutive_failed_update_checks += 1;
1212 self.persist_data().await;
1213 return;
1214 }
1215 };
1216
1217 let response = match Self::parse_omaha_response(&data) {
1218 Ok(res) => res,
1219 Err(e) => {
1220 error!("Unable to parse Omaha response: {:#}", anyhow!(e));
1221 self.context.state.consecutive_failed_update_checks += 1;
1222 self.persist_data().await;
1223 return;
1224 }
1225 };
1226
1227 self.context.state.consecutive_failed_update_checks = 0;
1228
1229 self.context.schedule.last_update_time = Some(self.time_source.now().into());
1232 co.yield_(StateMachineEvent::ScheduleChange(self.context.schedule)).await;
1233
1234 let app_responses = Self::make_app_responses(response, update_check::Action::NoUpdate);
1235 self.app_set.lock().await.update_from_omaha(&app_responses);
1236
1237 self.persist_data().await;
1238 }
1239
1240 async fn do_omaha_request_and_update_context<'a>(
1253 &'a mut self,
1254 builder: &RequestBuilder<'a>,
1255 co: &mut async_generator::Yield<StateMachineEvent>,
1256 ) -> Result<(Parts, Vec<u8>, Option<RequestMetadata>, Option<DerSignature>), OmahaRequestError>
1257 {
1258 let (request, request_metadata) = builder.build(self.cup_handler.as_ref())?;
1259 let response = Self::make_request(&mut self.http, request).await?;
1260
1261 let signature: Option<DerSignature> = if let (Some(handler), Some(metadata)) =
1262 (self.cup_handler.as_ref(), &request_metadata)
1263 {
1264 let signature = handler
1265 .verify_response(metadata, &response, metadata.public_key_id)
1266 .map_err(|e| {
1267 error!("Could not verify response: {:?}", e);
1268 e
1269 })?;
1270 Some(signature)
1271 } else {
1272 None
1273 };
1274
1275 let (parts, body) = response.into_parts();
1276
1277 let server_dictated_poll_interval = parts.headers.get(X_RETRY_AFTER).and_then(|header| {
1279 match header
1280 .to_str()
1281 .map_err(|e| anyhow!(e))
1282 .and_then(|s| s.parse::<u64>().map_err(|e| anyhow!(e)))
1283 {
1284 Ok(seconds) => {
1285 Some(Duration::from_secs(min(seconds, 86400)))
1288 }
1289 Err(e) => {
1290 error!("Unable to parse {} header: {:#}", X_RETRY_AFTER, e);
1291 None
1292 }
1293 }
1294 });
1295 if self.context.state.server_dictated_poll_interval != server_dictated_poll_interval {
1296 self.context.state.server_dictated_poll_interval = server_dictated_poll_interval;
1297 co.yield_(StateMachineEvent::ProtocolStateChange(self.context.state.clone())).await;
1298 let mut storage = self.storage_ref.lock().await;
1299 self.context.persist(&mut *storage).await;
1300 storage.commit_or_log().await;
1301 }
1302 if !parts.status.is_success() {
1303 Err(OmahaRequestError::HttpStatus(parts.status))
1305 } else {
1306 info!("Omaha HTTP response: {}", parts.status);
1308 Ok((parts, body, request_metadata, signature))
1309 }
1310 }
1311
1312 async fn make_request(
1318 http_client: &mut HR,
1319 request: http::Request<http_request::Body>,
1320 ) -> Result<HttpResponse<Vec<u8>>, http_request::Error> {
1321 info!("Making http request to: {}", request.uri());
1322 http_client.request(request).await.map_err(|err| {
1323 warn!("Unable to perform request: {}", err);
1324 err
1325 })
1326 }
1327
1328 fn parse_omaha_response(data: &[u8]) -> Result<Response, ResponseParseError> {
1332 parse_json_response(data).map_err(ResponseParseError::Json)
1333 }
1334
1335 fn get_app_update_statuses(response: &Response) -> Vec<(&str, &OmahaStatus)> {
1338 response
1339 .apps
1340 .iter()
1341 .filter_map(|app| app.update_check.as_ref().map(|u| (app.id.as_str(), &u.status)))
1342 .collect()
1343 }
1344
1345 fn make_app_responses(
1351 response: protocol::response::Response,
1352 action: update_check::Action,
1353 ) -> Vec<update_check::AppResponse> {
1354 let daystart = response.daystart;
1355 response
1356 .apps
1357 .into_iter()
1358 .map(|app| update_check::AppResponse {
1359 app_id: app.id,
1360 cohort: app.cohort,
1361 user_counting: daystart.clone().into(),
1362 result: action.clone(),
1363 })
1364 .collect()
1365 }
1366
1367 fn make_not_updated_result(
1369 response: protocol::response::Response,
1370 action: update_check::Action,
1371 ) -> Result<(update_check::Response, RebootAfterUpdate<IN::InstallResult>), UpdateCheckError>
1372 {
1373 Ok((
1374 update_check::Response { app_responses: Self::make_app_responses(response, action) },
1375 RebootAfterUpdate::NotNeeded,
1376 ))
1377 }
1378
1379 async fn yield_state(state: State, co: &mut async_generator::Yield<StateMachineEvent>) {
1381 co.yield_(StateMachineEvent::StateChange(state)).await;
1382 }
1383
1384 fn report_metrics(&mut self, metrics: Metrics) {
1385 if let Err(err) = self.metrics_reporter.report_metrics(metrics) {
1386 warn!("Unable to report metrics: {:?}", err);
1387 }
1388 }
1389
1390 async fn record_update_first_seen_time(
1391 &mut self,
1392 install_plan_id: &str,
1393 now: SystemTime,
1394 ) -> SystemTime {
1395 let mut storage = self.storage_ref.lock().await;
1396 let previous_id = storage.get_string(INSTALL_PLAN_ID).await;
1397 if let Some(previous_id) = previous_id
1398 && previous_id == install_plan_id
1399 {
1400 return storage.get_time(UPDATE_FIRST_SEEN_TIME).await.unwrap_or(now);
1401 }
1402 if let Err(e) = storage.set_string(INSTALL_PLAN_ID, install_plan_id).await {
1404 error!("Unable to persist {}: {}", INSTALL_PLAN_ID, e);
1405 return now;
1406 }
1407 if let Err(e) = storage.set_time(UPDATE_FIRST_SEEN_TIME, now).await {
1408 error!("Unable to persist {}: {}", UPDATE_FIRST_SEEN_TIME, e);
1409 let _ = storage.remove(INSTALL_PLAN_ID).await;
1410 return now;
1411 }
1412 storage.commit_or_log().await;
1413 now
1414 }
1415}
1416
1417fn randomize(n: u64, range: u64) -> u64 {
1419 n - range / 2 + rand::random::<u64>() % range
1420}
1421
1422#[cfg(test)]
1423impl<PE, HR, IN, TM, MR, ST, AS, IR, PL, CH> StateMachine<PE, HR, IN, TM, MR, ST, AS, CH>
1424where
1425 PE: PolicyEngine<InstallResult = IR, InstallPlan = PL>,
1426 HR: HttpRequest,
1427 IN: Installer<InstallResult = IR, InstallPlan = PL>,
1428 TM: Timer,
1429 MR: MetricsReporter,
1430 ST: Storage,
1431 AS: AppSet,
1432 CH: Cupv2Handler,
1433 IR: 'static + Send,
1434 PL: Plan,
1435{
1436 async fn oneshot(
1438 &mut self,
1439 request_params: RequestParams,
1440 ) -> Result<(update_check::Response, RebootAfterUpdate<IN::InstallResult>), UpdateCheckError>
1441 {
1442 let apps = self.app_set.lock().await.get_apps();
1443
1444 async_generator::generate(move |mut co| async move {
1445 self.perform_update_check(request_params, apps, &mut co).await
1446 })
1447 .into_complete()
1448 .await
1449 }
1450
1451 async fn run_once(&mut self) {
1453 let request_params = RequestParams::default();
1454
1455 async_generator::generate(move |mut co| async move {
1456 self.start_update_check(request_params, &mut co).await;
1457 })
1458 .map(|_| ())
1459 .collect::<()>()
1460 .await;
1461 }
1462}
1463
1464#[cfg(test)]
1465mod tests {
1466 use super::update_check::{
1467 Action, CONSECUTIVE_FAILED_UPDATE_CHECKS, LAST_UPDATE_TIME, SERVER_DICTATED_POLL_INTERVAL,
1468 };
1469 use super::*;
1470 use crate::{
1471 app_set::VecAppSet,
1472 common::{
1473 App, CheckOptions, PersistedApp, ProtocolState, UpdateCheckSchedule, UserCounting,
1474 },
1475 configuration::Updater,
1476 cup_ecdsa::test_support::{MockCupv2Handler, make_cup_handler_for_test},
1477 http_request::mock::MockHttpRequest,
1478 installer::{
1479 ProgressObserver,
1480 stub::{StubInstallErrors, StubInstaller, StubPlan},
1481 },
1482 metrics::MockMetricsReporter,
1483 policy::{MockPolicyEngine, StubPolicyEngine},
1484 protocol::{Cohort, request::OS, response},
1485 storage::MemStorage,
1486 time::{
1487 MockTimeSource, PartialComplexTime,
1488 timers::{BlockingTimer, MockTimer, RequestedWait},
1489 },
1490 version::Version,
1491 };
1492 use assert_matches::assert_matches;
1493 use futures::executor::{LocalPool, block_on};
1494 use futures::future::LocalBoxFuture;
1495 use futures::task::LocalSpawnExt;
1496 use pretty_assertions::assert_eq;
1497 use serde_json::json;
1498 use std::cell::RefCell;
1499 use std::time::Duration;
1500
1501 fn make_test_app_set() -> Rc<Mutex<VecAppSet>> {
1502 Rc::new(Mutex::new(VecAppSet::new(vec![
1503 App::builder()
1504 .id("{00000000-0000-0000-0000-000000000001}")
1505 .version([1, 2, 3, 4])
1506 .cohort(Cohort::new("stable-channel"))
1507 .build(),
1508 ])))
1509 }
1510
1511 fn make_update_available_response() -> HttpResponse<Vec<u8>> {
1512 let response = json!({"response":{
1513 "server": "prod",
1514 "protocol": "3.0",
1515 "app": [{
1516 "appid": "{00000000-0000-0000-0000-000000000001}",
1517 "status": "ok",
1518 "updatecheck": {
1519 "status": "ok"
1520 }
1521 }],
1522 }});
1523 HttpResponse::new(serde_json::to_vec(&response).unwrap())
1524 }
1525
1526 fn make_noupdate_httpresponse() -> Vec<u8> {
1527 serde_json::to_vec(
1528 &(json!({"response":{
1529 "server": "prod",
1530 "protocol": "3.0",
1531 "app": [{
1532 "appid": "{00000000-0000-0000-0000-000000000001}",
1533 "status": "ok",
1534 "updatecheck": {
1535 "status": "noupdate"
1536 }
1537 }]
1538 }})),
1539 )
1540 .unwrap()
1541 }
1542
1543 async fn assert_request<'a>(http: &MockHttpRequest, request_builder: RequestBuilder<'a>) {
1546 let cup_handler = make_cup_handler_for_test();
1547 let (request, _request_metadata) = request_builder.build(Some(&cup_handler)).unwrap();
1548 let body = http_request::to_bytes(request).await.unwrap();
1549 let body_str = String::from_utf8_lossy(&body);
1551 http.assert_body_str(&body_str).await;
1552 }
1553
1554 #[test]
1555 fn run_simple_check_with_noupdate_result() {
1556 block_on(async {
1557 let http = MockHttpRequest::new(HttpResponse::new(make_noupdate_httpresponse()));
1558
1559 StateMachineBuilder::new_stub()
1560 .http(http)
1561 .oneshot(RequestParams::default())
1562 .await
1563 .unwrap();
1564
1565 info!("update check complete!");
1566 });
1567 }
1568
1569 #[test]
1570 fn test_cohort_returned_with_noupdate_result() {
1571 block_on(async {
1572 let response = json!({"response":{
1573 "server": "prod",
1574 "protocol": "3.0",
1575 "app": [{
1576 "appid": "{00000000-0000-0000-0000-000000000001}",
1577 "status": "ok",
1578 "cohort": "1",
1579 "cohortname": "stable-channel",
1580 "updatecheck": {
1581 "status": "noupdate"
1582 }
1583 }]
1584 }});
1585 let response = serde_json::to_vec(&response).unwrap();
1586 let http = MockHttpRequest::new(HttpResponse::new(response));
1587
1588 let (response, reboot_after_update) = StateMachineBuilder::new_stub()
1589 .http(http)
1590 .oneshot(RequestParams::default())
1591 .await
1592 .unwrap();
1593 assert_eq!("{00000000-0000-0000-0000-000000000001}", response.app_responses[0].app_id);
1594 assert_eq!(Some("1".into()), response.app_responses[0].cohort.id);
1595 assert_eq!(Some("stable-channel".into()), response.app_responses[0].cohort.name);
1596 assert_eq!(None, response.app_responses[0].cohort.hint);
1597
1598 assert_matches!(reboot_after_update, RebootAfterUpdate::NotNeeded);
1599 });
1600 }
1601
1602 #[test]
1603 fn test_cohort_returned_with_update_result() {
1604 block_on(async {
1605 let response = json!({"response":{
1606 "server": "prod",
1607 "protocol": "3.0",
1608 "app": [{
1609 "appid": "{00000000-0000-0000-0000-000000000001}",
1610 "status": "ok",
1611 "cohort": "1",
1612 "cohortname": "stable-channel",
1613 "updatecheck": {
1614 "status": "ok"
1615 }
1616 }]
1617 }});
1618 let response = serde_json::to_vec(&response).unwrap();
1619 let http = MockHttpRequest::new(HttpResponse::new(response));
1620
1621 let (response, reboot_after_update) = StateMachineBuilder::new_stub()
1622 .http(http)
1623 .oneshot(RequestParams::default())
1624 .await
1625 .unwrap();
1626 assert_eq!("{00000000-0000-0000-0000-000000000001}", response.app_responses[0].app_id);
1627 assert_eq!(Some("1".into()), response.app_responses[0].cohort.id);
1628 assert_eq!(Some("stable-channel".into()), response.app_responses[0].cohort.name);
1629 assert_eq!(None, response.app_responses[0].cohort.hint);
1630
1631 assert_matches!(reboot_after_update, RebootAfterUpdate::Needed(()));
1632 });
1633 }
1634
1635 #[test]
1636 fn test_report_parse_response_error() {
1637 block_on(async {
1638 let http = MockHttpRequest::new(HttpResponse::new("invalid response".into()));
1639
1640 let mut state_machine = StateMachineBuilder::new_stub().http(http).build().await;
1641
1642 let response = state_machine.oneshot(RequestParams::default()).await;
1643 assert_matches!(response, Err(UpdateCheckError::ResponseParser(_)));
1644
1645 let request_params = RequestParams::default();
1646 let mut request_builder = RequestBuilder::new(&state_machine.config, &request_params);
1647 let event = Event {
1648 previous_version: Some("1.2.3.4".to_string()),
1649 ..Event::error(EventErrorCode::ParseResponse)
1650 };
1651 let apps = state_machine.app_set.lock().await.get_apps();
1652 request_builder = request_builder
1653 .add_event(&apps[0], event)
1654 .session_id(GUID::from_u128(0))
1655 .request_id(GUID::from_u128(2));
1656 assert_request(&state_machine.http, request_builder).await;
1657 });
1658 }
1659
1660 #[test]
1661 fn test_report_construct_install_plan_error() {
1662 block_on(async {
1663 let response = json!({"response":{
1664 "server": "prod",
1665 "protocol": "4.0",
1666 "app": [{
1667 "appid": "{00000000-0000-0000-0000-000000000001}",
1668 "status": "ok",
1669 "updatecheck": {
1670 "status": "ok"
1671 }
1672 }],
1673 }});
1674 let response = serde_json::to_vec(&response).unwrap();
1675 let http = MockHttpRequest::new(HttpResponse::new(response));
1676
1677 let mut state_machine = StateMachineBuilder::new_stub().http(http).build().await;
1678
1679 let response = state_machine.oneshot(RequestParams::default()).await;
1680 assert_matches!(response, Err(UpdateCheckError::InstallPlan(_)));
1681
1682 let request_params = RequestParams::default();
1683 let mut request_builder = RequestBuilder::new(&state_machine.config, &request_params);
1684 let event = Event {
1685 previous_version: Some("1.2.3.4".to_string()),
1686 ..Event::error(EventErrorCode::ConstructInstallPlan)
1687 };
1688 let apps = state_machine.app_set.lock().await.get_apps();
1689 request_builder = request_builder
1690 .add_event(&apps[0], event)
1691 .session_id(GUID::from_u128(0))
1692 .request_id(GUID::from_u128(2));
1693 assert_request(&state_machine.http, request_builder).await;
1694 });
1695 }
1696
1697 #[test]
1698 fn test_report_installation_error() {
1699 block_on(async {
1700 let response = json!({"response":{
1701 "server": "prod",
1702 "protocol": "3.0",
1703 "app": [{
1704 "appid": "{00000000-0000-0000-0000-000000000001}",
1705 "status": "ok",
1706 "updatecheck": {
1707 "status": "ok",
1708 "manifest": {
1709 "version": "5.6.7.8",
1710 "actions": {
1711 "action": [],
1712 },
1713 "packages": {
1714 "package": [],
1715 },
1716 }
1717 }
1718 }],
1719 }});
1720 let response = serde_json::to_vec(&response).unwrap();
1721 let http = MockHttpRequest::new(HttpResponse::new(response));
1722
1723 let mut state_machine = StateMachineBuilder::new_stub()
1724 .http(http)
1725 .installer(StubInstaller { should_fail: true })
1726 .build()
1727 .await;
1728
1729 let (response, reboot_after_update) =
1730 state_machine.oneshot(RequestParams::default()).await.unwrap();
1731 assert_eq!(Action::InstallPlanExecutionError, response.app_responses[0].result);
1732 assert_matches!(reboot_after_update, RebootAfterUpdate::NotNeeded);
1733
1734 let request_params = RequestParams::default();
1735 let mut request_builder = RequestBuilder::new(&state_machine.config, &request_params);
1736 let event = Event {
1737 previous_version: Some("1.2.3.4".to_string()),
1738 next_version: Some("5.6.7.8".to_string()),
1739 download_time_ms: Some(0),
1740 ..Event::error(EventErrorCode::Installation)
1741 };
1742 let apps = state_machine.app_set.lock().await.get_apps();
1743 request_builder = request_builder
1744 .add_event(&apps[0], event)
1745 .session_id(GUID::from_u128(0))
1746 .request_id(GUID::from_u128(3));
1747 assert_request(&state_machine.http, request_builder).await;
1748 });
1749 }
1750
1751 #[test]
1752 fn test_report_installation_error_multi_app() {
1753 block_on(async {
1754 let response = json!({"response":{
1756 "server": "prod",
1757 "protocol": "3.0",
1758 "app": [{
1759 "appid": "appid_3",
1760 "status": "ok",
1761 "updatecheck": {
1762 "status": "ok",
1763 "manifest": {
1764 "version": "5.6.7.8",
1765 "actions": {
1766 "action": [],
1767 },
1768 "packages": {
1769 "package": [],
1770 },
1771 }
1772 }
1773 },{
1774 "appid": "appid_1",
1775 "status": "ok",
1776 "updatecheck": {
1777 "status": "ok",
1778 "manifest": {
1779 "version": "1.2.3.4",
1780 "actions": {
1781 "action": [],
1782 },
1783 "packages": {
1784 "package": [],
1785 },
1786 }
1787 }
1788 },{
1789 "appid": "appid_2",
1790 "status": "ok",
1791 "updatecheck": {
1792 "status": "noupdate",
1793 }
1794 }],
1795 }});
1796 let response = serde_json::to_vec(&response).unwrap();
1797 let mut http = MockHttpRequest::new(HttpResponse::new(response));
1798 http.add_response(HttpResponse::new(vec![]));
1799 let app_set = VecAppSet::new(vec![
1800 App::builder().id("appid_1").version([1, 2, 3, 3]).build(),
1801 App::builder().id("appid_2").version([9, 9, 9, 9]).build(),
1802 App::builder().id("appid_3").version([5, 6, 7, 7]).build(),
1803 ]);
1804 let app_set = Rc::new(Mutex::new(app_set));
1805 let (send_install, mut recv_install) = mpsc::channel(0);
1806
1807 let mut state_machine = StateMachineBuilder::new_stub()
1808 .app_set(Rc::clone(&app_set))
1809 .http(http)
1810 .installer(BlockingInstaller { on_install: send_install, on_reboot: None })
1811 .build()
1812 .await;
1813
1814 let recv_install_fut = async move {
1815 let unblock_install = recv_install.next().await.unwrap();
1816 unblock_install
1817 .send(vec![AppInstallResult::Deferred, AppInstallResult::Installed])
1818 .unwrap();
1819 };
1820
1821 let (oneshot_result, ()) =
1822 future::join(state_machine.oneshot(RequestParams::default()), recv_install_fut)
1823 .await;
1824 let (response, reboot_after_update) = oneshot_result.unwrap();
1825
1826 assert_eq!("appid_3", response.app_responses[0].app_id);
1827 assert_eq!(Action::DeferredByPolicy, response.app_responses[0].result);
1828 assert_eq!("appid_1", response.app_responses[1].app_id);
1829 assert_eq!(Action::Updated, response.app_responses[1].result);
1830 assert_eq!("appid_2", response.app_responses[2].app_id);
1831 assert_eq!(Action::NoUpdate, response.app_responses[2].result);
1832 assert_matches!(reboot_after_update, RebootAfterUpdate::Needed(()));
1833
1834 let request_params = RequestParams::default();
1835 let apps = app_set.lock().await.get_apps();
1836
1837 let mut request_builder = RequestBuilder::new(&state_machine.config, &request_params);
1838 let event = Event {
1839 previous_version: Some("1.2.3.3".to_string()),
1840 next_version: Some("1.2.3.4".to_string()),
1841 download_time_ms: Some(0),
1842 ..Event::success(EventType::UpdateComplete)
1843 };
1844 request_builder = request_builder
1845 .add_event(&apps[0], event)
1846 .session_id(GUID::from_u128(0))
1847 .request_id(GUID::from_u128(4));
1848 assert_request(&state_machine.http, request_builder).await;
1849
1850 let mut request_builder = RequestBuilder::new(&state_machine.config, &request_params);
1851 let event1 = Event {
1852 previous_version: Some("1.2.3.3".to_string()),
1853 next_version: Some("1.2.3.4".to_string()),
1854 download_time_ms: Some(0),
1855 ..Event::success(EventType::UpdateDownloadFinished)
1856 };
1857 let event2 = Event {
1858 previous_version: Some("5.6.7.7".to_string()),
1859 next_version: Some("5.6.7.8".to_string()),
1860 download_time_ms: Some(0),
1861 event_type: EventType::UpdateComplete,
1862 event_result: EventResult::UpdateDeferred,
1863 ..Event::default()
1864 };
1865 request_builder = request_builder
1866 .add_event(&apps[2], event2)
1867 .add_event(&apps[0], event1)
1868 .session_id(GUID::from_u128(0))
1869 .request_id(GUID::from_u128(3));
1870 assert_request(&state_machine.http, request_builder).await;
1871 });
1872 }
1873
1874 #[test]
1877 fn test_observe_installation_error() {
1878 block_on(async {
1879 let http = MockHttpRequest::new(make_update_available_response());
1880
1881 let actual_errors = StateMachineBuilder::new_stub()
1882 .http(http)
1883 .installer(StubInstaller { should_fail: true })
1884 .oneshot_check()
1885 .await
1886 .filter_map(|event| {
1887 future::ready(match event {
1888 StateMachineEvent::InstallerError(Some(e)) => {
1889 Some(*e.downcast::<StubInstallErrors>().unwrap())
1890 }
1891 _ => None,
1892 })
1893 })
1894 .collect::<Vec<StubInstallErrors>>()
1895 .await;
1896
1897 let expected_errors = vec![StubInstallErrors::Failed];
1898 assert_eq!(actual_errors, expected_errors);
1899 });
1900 }
1901
1902 #[test]
1903 fn test_report_deferred_by_policy() {
1904 block_on(async {
1905 let http = MockHttpRequest::new(make_update_available_response());
1906
1907 let policy_engine = MockPolicyEngine {
1908 update_decision: UpdateDecision::DeferredByPolicy,
1909 ..MockPolicyEngine::default()
1910 };
1911 let mut state_machine = StateMachineBuilder::new_stub()
1912 .policy_engine(policy_engine)
1913 .http(http)
1914 .build()
1915 .await;
1916
1917 let (response, reboot_after_update) =
1918 state_machine.oneshot(RequestParams::default()).await.unwrap();
1919 assert_eq!(Action::DeferredByPolicy, response.app_responses[0].result);
1920 assert_matches!(reboot_after_update, RebootAfterUpdate::NotNeeded);
1921
1922 let request_params = RequestParams::default();
1923 let mut request_builder = RequestBuilder::new(&state_machine.config, &request_params);
1924 let event = Event {
1925 event_type: EventType::UpdateComplete,
1926 event_result: EventResult::UpdateDeferred,
1927 previous_version: Some("1.2.3.4".to_string()),
1928 ..Event::default()
1929 };
1930 let apps = state_machine.app_set.lock().await.get_apps();
1931 request_builder = request_builder
1932 .add_event(&apps[0], event)
1933 .session_id(GUID::from_u128(0))
1934 .request_id(GUID::from_u128(2));
1935 assert_request(&state_machine.http, request_builder).await;
1936 });
1937 }
1938
1939 #[test]
1940 fn test_report_denied_by_policy() {
1941 block_on(async {
1942 let response = make_update_available_response();
1943 let http = MockHttpRequest::new(response);
1944 let policy_engine = MockPolicyEngine {
1945 update_decision: UpdateDecision::DeniedByPolicy,
1946 ..MockPolicyEngine::default()
1947 };
1948
1949 let mut state_machine = StateMachineBuilder::new_stub()
1950 .policy_engine(policy_engine)
1951 .http(http)
1952 .build()
1953 .await;
1954
1955 let (response, reboot_after_update) =
1956 state_machine.oneshot(RequestParams::default()).await.unwrap();
1957 assert_eq!(Action::DeniedByPolicy, response.app_responses[0].result);
1958 assert_matches!(reboot_after_update, RebootAfterUpdate::NotNeeded);
1959
1960 let request_params = RequestParams::default();
1961 let mut request_builder = RequestBuilder::new(&state_machine.config, &request_params);
1962 let event = Event {
1963 previous_version: Some("1.2.3.4".to_string()),
1964 ..Event::error(EventErrorCode::DeniedByPolicy)
1965 };
1966 let apps = state_machine.app_set.lock().await.get_apps();
1967 request_builder = request_builder
1968 .add_event(&apps[0], event)
1969 .session_id(GUID::from_u128(0))
1970 .request_id(GUID::from_u128(2));
1971 assert_request(&state_machine.http, request_builder).await;
1972 });
1973 }
1974
1975 #[test]
1976 fn test_wait_timer() {
1977 let mut pool = LocalPool::new();
1978 let mock_time = MockTimeSource::new_from_now();
1979 let next_update_time = mock_time.now() + Duration::from_secs(111);
1980 let (timer, mut timers) = BlockingTimer::new();
1981 let policy_engine = MockPolicyEngine {
1982 check_timing: Some(CheckTiming::builder().time(next_update_time).build()),
1983 time_source: mock_time,
1984 ..MockPolicyEngine::default()
1985 };
1986
1987 let (_ctl, state_machine) = pool.run_until(
1988 StateMachineBuilder::new_stub().policy_engine(policy_engine).timer(timer).start(),
1989 );
1990
1991 pool.spawner().spawn_local(state_machine.map(|_| ()).collect()).unwrap();
1992
1993 let blocked_timer = pool.run_until(timers.next()).unwrap();
1996 assert_eq!(blocked_timer.requested_wait(), RequestedWait::Until(next_update_time.into()));
1997 }
1998
1999 #[test]
2000 fn test_cohort_and_user_counting_updates_are_used_in_subsequent_requests() {
2001 block_on(async {
2002 let response = json!({"response":{
2003 "server": "prod",
2004 "protocol": "3.0",
2005 "daystart": {
2006 "elapsed_days": 1234567,
2007 "elapsed_seconds": 3645
2008 },
2009 "app": [{
2010 "appid": "{00000000-0000-0000-0000-000000000001}",
2011 "status": "ok",
2012 "cohort": "1",
2013 "cohortname": "stable-channel",
2014 "updatecheck": {
2015 "status": "noupdate"
2016 }
2017 }]
2018 }});
2019 let response = serde_json::to_vec(&response).unwrap();
2020 let mut http = MockHttpRequest::new(HttpResponse::new(response.clone()));
2021 http.add_response(HttpResponse::new(response));
2022 let apps = make_test_app_set();
2023
2024 let mut state_machine =
2025 StateMachineBuilder::new_stub().http(http).app_set(apps.clone()).build().await;
2026
2027 state_machine.run_once().await;
2029
2030 let apps = apps.lock().await.get_apps();
2031 assert_eq!(Some("1".to_string()), apps[0].cohort.id);
2032 assert_eq!(None, apps[0].cohort.hint);
2033 assert_eq!(Some("stable-channel".to_string()), apps[0].cohort.name);
2034 assert_eq!(UserCounting::ClientRegulatedByDate(Some(1234567)), apps[0].user_counting);
2035
2036 state_machine.run_once().await;
2038
2039 let request_params = RequestParams::default();
2040 let expected_request_builder =
2041 RequestBuilder::new(&state_machine.config, &request_params)
2042 .add_update_check(&apps[0])
2043 .add_ping(&apps[0])
2044 .session_id(GUID::from_u128(2))
2045 .request_id(GUID::from_u128(3));
2046 assert_request(&state_machine.http, expected_request_builder).await;
2048 });
2049 }
2050
2051 #[test]
2052 fn test_user_counting_returned() {
2053 block_on(async {
2054 let response = json!({"response":{
2055 "server": "prod",
2056 "protocol": "3.0",
2057 "daystart": {
2058 "elapsed_days": 1234567,
2059 "elapsed_seconds": 3645
2060 },
2061 "app": [{
2062 "appid": "{00000000-0000-0000-0000-000000000001}",
2063 "status": "ok",
2064 "cohort": "1",
2065 "cohortname": "stable-channel",
2066 "updatecheck": {
2067 "status": "noupdate"
2068 }
2069 }]
2070 }});
2071 let response = serde_json::to_vec(&response).unwrap();
2072 let http = MockHttpRequest::new(HttpResponse::new(response));
2073
2074 let (response, reboot_after_update) = StateMachineBuilder::new_stub()
2075 .http(http)
2076 .oneshot(RequestParams::default())
2077 .await
2078 .unwrap();
2079
2080 assert_eq!(
2081 UserCounting::ClientRegulatedByDate(Some(1234567)),
2082 response.app_responses[0].user_counting
2083 );
2084 assert_matches!(reboot_after_update, RebootAfterUpdate::NotNeeded);
2085 });
2086 }
2087
2088 #[test]
2089 fn test_observe_state() {
2090 block_on(async {
2091 let actual_states = StateMachineBuilder::new_stub()
2092 .oneshot_check()
2093 .await
2094 .filter_map(|event| {
2095 future::ready(match event {
2096 StateMachineEvent::StateChange(state) => Some(state),
2097 _ => None,
2098 })
2099 })
2100 .collect::<Vec<State>>()
2101 .await;
2102
2103 let expected_states = vec![
2104 State::CheckingForUpdates(InstallSource::ScheduledTask),
2105 State::ErrorCheckingForUpdate,
2106 ];
2107 assert_eq!(actual_states, expected_states);
2108 });
2109 }
2110
2111 #[test]
2112 fn test_observe_schedule() {
2113 block_on(async {
2114 let mock_time = MockTimeSource::new_from_now();
2115 let actual_schedules = StateMachineBuilder::new_stub()
2116 .policy_engine(StubPolicyEngine::new(&mock_time))
2117 .oneshot_check()
2118 .await
2119 .filter_map(|event| {
2120 future::ready(match event {
2121 StateMachineEvent::ScheduleChange(schedule) => Some(schedule),
2122 _ => None,
2123 })
2124 })
2125 .collect::<Vec<UpdateCheckSchedule>>()
2126 .await;
2127
2128 let expected_schedule = UpdateCheckSchedule::builder()
2130 .last_update_time(mock_time.now())
2131 .last_update_check_time(mock_time.now())
2132 .build();
2133
2134 assert_eq!(actual_schedules, vec![expected_schedule]);
2135 });
2136 }
2137
2138 #[test]
2139 fn test_observe_protocol_state() {
2140 block_on(async {
2141 let actual_protocol_states = StateMachineBuilder::new_stub()
2142 .oneshot_check()
2143 .await
2144 .filter_map(|event| {
2145 future::ready(match event {
2146 StateMachineEvent::ProtocolStateChange(state) => Some(state),
2147 _ => None,
2148 })
2149 })
2150 .collect::<Vec<ProtocolState>>()
2151 .await;
2152
2153 let expected_protocol_state =
2154 ProtocolState { consecutive_failed_update_checks: 1, ..ProtocolState::default() };
2155
2156 assert_eq!(actual_protocol_states, vec![expected_protocol_state]);
2157 });
2158 }
2159
2160 #[test]
2161 fn test_observe_omaha_server_response() {
2162 block_on(async {
2163 let response = json!({"response":{
2164 "server": "prod",
2165 "protocol": "3.0",
2166 "app": [{
2167 "appid": "{00000000-0000-0000-0000-000000000001}",
2168 "status": "ok",
2169 "cohort": "1",
2170 "cohortname": "stable-channel",
2171 "updatecheck": {
2172 "status": "noupdate"
2173 }
2174 }]
2175 }});
2176 let response = serde_json::to_vec(&response).unwrap();
2177 let expected_omaha_response = response::parse_json_response(&response).unwrap();
2178 let http = MockHttpRequest::new(HttpResponse::new(response));
2179
2180 let actual_omaha_response = StateMachineBuilder::new_stub()
2181 .http(http)
2182 .oneshot_check()
2183 .await
2184 .filter_map(|event| {
2185 future::ready(match event {
2186 StateMachineEvent::OmahaServerResponse(response) => Some(response),
2187 _ => None,
2188 })
2189 })
2190 .collect::<Vec<response::Response>>()
2191 .await;
2192
2193 assert_eq!(actual_omaha_response, vec![expected_omaha_response]);
2194 });
2195 }
2196
2197 #[test]
2198 fn test_metrics_report_omaha_event_lost() {
2199 block_on(async {
2200 let http = MockHttpRequest::new(HttpResponse::new("invalid response".into()));
2215 let mut metrics_reporter = MockMetricsReporter::new();
2216 let _response = StateMachineBuilder::new_stub()
2217 .http(http)
2218 .metrics_reporter(&mut metrics_reporter)
2219 .oneshot(RequestParams::default())
2220 .await;
2221
2222 #[rustfmt::skip]
2225 assert_matches!(
2226 metrics_reporter.metrics.as_slice(),
2227 [
2228 Metrics::UpdateCheckResponseTime { response_time: _, successful: true },
2229 Metrics::RequestsPerCheck { count: 1, successful: true },
2230 Metrics::OmahaEventLost(Event {
2231 event_type: EventType::UpdateComplete,
2232 event_result: EventResult::Error,
2233 errorcode: Some(EventErrorCode::ParseResponse),
2234 previous_version: None,
2235 next_version: None,
2236 download_time_ms: None,
2237 })
2238 ]
2239 );
2240 });
2241 }
2242
2243 #[test]
2244 fn test_metrics_report_update_check_response_time() {
2245 block_on(async {
2246 let mut metrics_reporter = MockMetricsReporter::new();
2247 let _response = StateMachineBuilder::new_stub()
2248 .metrics_reporter(&mut metrics_reporter)
2249 .oneshot(RequestParams::default())
2250 .await;
2251
2252 #[rustfmt::skip]
2255 assert_matches!(
2256 metrics_reporter.metrics.as_slice(),
2257 [
2258 Metrics::UpdateCheckResponseTime { response_time: _, successful: true },
2259 Metrics::RequestsPerCheck { count: 1, successful: true },
2260 ]
2261 );
2262 });
2263 }
2264
2265 #[test]
2266 fn test_metrics_report_update_check_response_time_on_failure() {
2267 block_on(async {
2268 let mut metrics_reporter = MockMetricsReporter::new();
2269 let mut http = MockHttpRequest::default();
2270
2271 for _ in 0..MAX_OMAHA_REQUEST_ATTEMPTS {
2272 http.add_error(http_request::mock_errors::make_transport_error());
2273 }
2274
2275 http.add_response(hyper::Response::default());
2278
2279 let _response = StateMachineBuilder::new_stub()
2280 .http(http)
2281 .metrics_reporter(&mut metrics_reporter)
2282 .oneshot(RequestParams::default())
2283 .await;
2284
2285 #[rustfmt::skip]
2288 assert_matches!(
2289 metrics_reporter.metrics.as_slice(),
2290 [
2291 Metrics::UpdateCheckResponseTime { response_time: _, successful: false },
2292 Metrics::UpdateCheckResponseTime { response_time: _, successful: false },
2293 Metrics::UpdateCheckResponseTime { response_time: _, successful: false },
2294 Metrics::RequestsPerCheck { count: 3, successful: false },
2295 ]
2296 );
2297 });
2298 }
2299
2300 #[test]
2301 fn test_metrics_report_update_check_response_time_on_failure_followed_by_success() {
2302 block_on(async {
2303 let mut metrics_reporter = MockMetricsReporter::new();
2304 let mut http = MockHttpRequest::default();
2305
2306 for _ in 0..MAX_OMAHA_REQUEST_ATTEMPTS - 1 {
2307 http.add_error(http_request::mock_errors::make_transport_error());
2308 }
2309 http.add_response(hyper::Response::default());
2310
2311 let _response = StateMachineBuilder::new_stub()
2312 .http(http)
2313 .metrics_reporter(&mut metrics_reporter)
2314 .oneshot(RequestParams::default())
2315 .await;
2316
2317 #[rustfmt::skip]
2320 assert_matches!(
2321 metrics_reporter.metrics.as_slice(),
2322 [
2323 Metrics::UpdateCheckResponseTime { response_time: _, successful: false },
2324 Metrics::UpdateCheckResponseTime { response_time: _, successful: false },
2325 Metrics::UpdateCheckResponseTime { response_time: _, successful: true },
2326 Metrics::RequestsPerCheck { count: 3, successful: true },
2327 Metrics::OmahaEventLost(Event {
2328 event_type: EventType::UpdateComplete,
2329 event_result: EventResult::Error,
2330 errorcode: Some(EventErrorCode::ParseResponse),
2331 previous_version: None,
2332 next_version: None,
2333 download_time_ms: None
2334 }),
2335 ]
2336 );
2337 });
2338 }
2339
2340 #[test]
2341 fn test_metrics_report_requests_per_check() {
2342 block_on(async {
2343 let mut metrics_reporter = MockMetricsReporter::new();
2344 let _response = StateMachineBuilder::new_stub()
2345 .metrics_reporter(&mut metrics_reporter)
2346 .oneshot(RequestParams::default())
2347 .await;
2348
2349 assert!(
2350 metrics_reporter
2351 .metrics
2352 .contains(&Metrics::RequestsPerCheck { count: 1, successful: true })
2353 );
2354 });
2355 }
2356
2357 #[test]
2358 fn test_metrics_report_requests_per_check_on_failure_followed_by_success() {
2359 block_on(async {
2360 let mut metrics_reporter = MockMetricsReporter::new();
2361 let mut http = MockHttpRequest::default();
2362
2363 for _ in 0..MAX_OMAHA_REQUEST_ATTEMPTS - 1 {
2364 http.add_error(http_request::mock_errors::make_transport_error());
2365 }
2366
2367 http.add_response(hyper::Response::default());
2368
2369 let _response = StateMachineBuilder::new_stub()
2370 .http(http)
2371 .metrics_reporter(&mut metrics_reporter)
2372 .oneshot(RequestParams::default())
2373 .await;
2374
2375 assert!(!metrics_reporter.metrics.is_empty());
2376 assert!(metrics_reporter.metrics.contains(&Metrics::RequestsPerCheck {
2377 count: MAX_OMAHA_REQUEST_ATTEMPTS,
2378 successful: true
2379 }));
2380 });
2381 }
2382
2383 #[test]
2384 fn test_metrics_report_requests_per_check_on_failure() {
2385 block_on(async {
2386 let mut metrics_reporter = MockMetricsReporter::new();
2387 let mut http = MockHttpRequest::default();
2388
2389 for _ in 0..MAX_OMAHA_REQUEST_ATTEMPTS {
2390 http.add_error(http_request::mock_errors::make_transport_error());
2391 }
2392
2393 http.add_response(hyper::Response::default());
2395
2396 let _response = StateMachineBuilder::new_stub()
2397 .http(http)
2398 .metrics_reporter(&mut metrics_reporter)
2399 .oneshot(RequestParams::default())
2400 .await;
2401
2402 assert!(!metrics_reporter.metrics.is_empty());
2403 assert!(metrics_reporter.metrics.contains(&Metrics::RequestsPerCheck {
2404 count: MAX_OMAHA_REQUEST_ATTEMPTS,
2405 successful: false
2406 }));
2407 });
2408 }
2409
2410 #[test]
2411 fn test_requests_per_check_backoff_with_mock_timer() {
2412 block_on(async {
2413 let mut timer = MockTimer::new();
2414 timer.expect_for_range(Duration::from_millis(500), Duration::from_millis(1500));
2415 timer.expect_for_range(Duration::from_millis(1500), Duration::from_millis(2500));
2416 let requested_waits = timer.get_requested_waits_view();
2417 let response = StateMachineBuilder::new_stub()
2418 .http(MockHttpRequest::empty())
2419 .timer(timer)
2420 .oneshot(RequestParams::default())
2421 .await;
2422
2423 let waits = requested_waits.borrow();
2424 assert_eq!(waits.len(), 2);
2425 assert_matches!(
2426 waits[0],
2427 RequestedWait::For(d) if d >= Duration::from_millis(500) && d <= Duration::from_millis(1500)
2428 );
2429 assert_matches!(
2430 waits[1],
2431 RequestedWait::For(d) if d >= Duration::from_millis(1500) && d <= Duration::from_millis(2500)
2432 );
2433
2434 assert_matches!(
2435 response,
2436 Err(UpdateCheckError::OmahaRequest(OmahaRequestError::HttpStatus(_)))
2437 );
2438 });
2439 }
2440
2441 #[test]
2442 fn test_metrics_report_update_check_failure_reason_omaha() {
2443 block_on(async {
2444 let mut metrics_reporter = MockMetricsReporter::new();
2445 let mut state_machine = StateMachineBuilder::new_stub()
2446 .metrics_reporter(&mut metrics_reporter)
2447 .build()
2448 .await;
2449
2450 state_machine.run_once().await;
2451
2452 assert!(
2453 metrics_reporter
2454 .metrics
2455 .contains(&Metrics::UpdateCheckFailureReason(UpdateCheckFailureReason::Omaha))
2456 );
2457 });
2458 }
2459
2460 #[test]
2461 fn test_metrics_report_update_check_failure_reason_network() {
2462 block_on(async {
2463 let mut metrics_reporter = MockMetricsReporter::new();
2464 let mut state_machine = StateMachineBuilder::new_stub()
2465 .http(MockHttpRequest::empty())
2466 .metrics_reporter(&mut metrics_reporter)
2467 .build()
2468 .await;
2469
2470 state_machine.run_once().await;
2471
2472 assert!(
2473 metrics_reporter.metrics.contains(&Metrics::UpdateCheckFailureReason(
2474 UpdateCheckFailureReason::Network
2475 ))
2476 );
2477 });
2478 }
2479
2480 #[test]
2481 fn test_persist_last_update_time() {
2482 block_on(async {
2483 let storage = Rc::new(Mutex::new(MemStorage::new()));
2484
2485 StateMachineBuilder::new_stub()
2486 .storage(Rc::clone(&storage))
2487 .oneshot_check()
2488 .await
2489 .map(|_| ())
2490 .collect::<()>()
2491 .await;
2492
2493 let storage = storage.lock().await;
2494 storage.get_int(LAST_UPDATE_TIME).await.unwrap();
2495 assert!(storage.committed());
2496 });
2497 }
2498
2499 #[test]
2500 fn test_persist_server_dictated_poll_interval() {
2501 block_on(async {
2502 let response = HttpResponse::builder()
2503 .header(X_RETRY_AFTER, 1234)
2504 .body(make_noupdate_httpresponse())
2505 .unwrap();
2506 let http = MockHttpRequest::new(response);
2507 let storage = Rc::new(Mutex::new(MemStorage::new()));
2508
2509 let mut state_machine = StateMachineBuilder::new_stub()
2510 .http(http)
2511 .storage(Rc::clone(&storage))
2512 .build()
2513 .await;
2514 state_machine.oneshot(RequestParams::default()).await.unwrap();
2515
2516 assert_eq!(
2517 state_machine.context.state.server_dictated_poll_interval,
2518 Some(Duration::from_secs(1234))
2519 );
2520
2521 let storage = storage.lock().await;
2522 assert_eq!(storage.get_int(SERVER_DICTATED_POLL_INTERVAL).await, Some(1234000000));
2523 assert!(storage.committed());
2524 });
2525 }
2526
2527 #[test]
2528 fn test_persist_server_dictated_poll_interval_http_error() {
2529 block_on(async {
2530 let response = HttpResponse::builder()
2531 .status(hyper::StatusCode::INTERNAL_SERVER_ERROR)
2532 .header(X_RETRY_AFTER, 1234)
2533 .body(vec![])
2534 .unwrap();
2535 let http = MockHttpRequest::new(response);
2536 let storage = Rc::new(Mutex::new(MemStorage::new()));
2537
2538 let mut state_machine = StateMachineBuilder::new_stub()
2539 .http(http)
2540 .storage(Rc::clone(&storage))
2541 .build()
2542 .await;
2543 assert_matches!(
2544 state_machine.oneshot(RequestParams::default()).await,
2545 Err(UpdateCheckError::OmahaRequest(OmahaRequestError::HttpStatus(_)))
2546 );
2547
2548 assert_eq!(
2549 state_machine.context.state.server_dictated_poll_interval,
2550 Some(Duration::from_secs(1234))
2551 );
2552
2553 let storage = storage.lock().await;
2554 assert_eq!(storage.get_int(SERVER_DICTATED_POLL_INTERVAL).await, Some(1234000000));
2555 assert!(storage.committed());
2556 });
2557 }
2558
2559 #[test]
2560 fn test_persist_server_dictated_poll_interval_max_duration() {
2561 block_on(async {
2562 let response = HttpResponse::builder()
2563 .status(hyper::StatusCode::INTERNAL_SERVER_ERROR)
2564 .header(X_RETRY_AFTER, 123456789)
2565 .body(vec![])
2566 .unwrap();
2567 let http = MockHttpRequest::new(response);
2568 let storage = Rc::new(Mutex::new(MemStorage::new()));
2569
2570 let mut state_machine = StateMachineBuilder::new_stub()
2571 .http(http)
2572 .storage(Rc::clone(&storage))
2573 .build()
2574 .await;
2575 assert_matches!(
2576 state_machine.oneshot(RequestParams::default()).await,
2577 Err(UpdateCheckError::OmahaRequest(OmahaRequestError::HttpStatus(_)))
2578 );
2579
2580 assert_eq!(
2581 state_machine.context.state.server_dictated_poll_interval,
2582 Some(Duration::from_secs(86400))
2583 );
2584
2585 let storage = storage.lock().await;
2586 assert_eq!(storage.get_int(SERVER_DICTATED_POLL_INTERVAL).await, Some(86400000000));
2587 assert!(storage.committed());
2588 });
2589 }
2590
2591 #[test]
2592 fn test_server_dictated_poll_interval_with_transport_error_no_retry() {
2593 block_on(async {
2594 let mut http = MockHttpRequest::empty();
2595 http.add_error(http_request::mock_errors::make_transport_error());
2596 let mut storage = MemStorage::new();
2597 let _ = storage.set_int(SERVER_DICTATED_POLL_INTERVAL, 1234000000).await;
2598 let _ = storage.commit().await;
2599 let storage = Rc::new(Mutex::new(storage));
2600
2601 let mut state_machine = StateMachineBuilder::new_stub()
2602 .http(http)
2603 .storage(Rc::clone(&storage))
2604 .build()
2605 .await;
2606 assert_matches!(
2610 state_machine.oneshot(RequestParams::default()).await,
2611 Err(UpdateCheckError::OmahaRequest(OmahaRequestError::HttpTransport(_)))
2612 );
2613
2614 assert_eq!(
2615 state_machine.context.state.server_dictated_poll_interval,
2616 Some(Duration::from_secs(1234))
2617 );
2618 });
2619 }
2620
2621 #[test]
2622 fn test_persist_app() {
2623 block_on(async {
2624 let storage = Rc::new(Mutex::new(MemStorage::new()));
2625 let app_set = make_test_app_set();
2626
2627 StateMachineBuilder::new_stub()
2628 .storage(Rc::clone(&storage))
2629 .app_set(app_set.clone())
2630 .oneshot_check()
2631 .await
2632 .map(|_| ())
2633 .collect::<()>()
2634 .await;
2635
2636 let storage = storage.lock().await;
2637 let apps = app_set.lock().await.get_apps();
2638 storage.get_string(&apps[0].id).await.unwrap();
2639 assert!(storage.committed());
2640 });
2641 }
2642
2643 #[test]
2644 fn test_load_last_update_time() {
2645 block_on(async {
2646 let mut storage = MemStorage::new();
2647 let mut mock_time = MockTimeSource::new_from_now();
2648 mock_time.truncate_submicrosecond_walltime();
2649 let last_update_time = mock_time.now_in_walltime() - Duration::from_secs(999);
2650 storage.set_time(LAST_UPDATE_TIME, last_update_time).await.unwrap();
2651
2652 let state_machine = StateMachineBuilder::new_stub()
2653 .policy_engine(StubPolicyEngine::new(&mock_time))
2654 .storage(Rc::new(Mutex::new(storage)))
2655 .build()
2656 .await;
2657
2658 assert_eq!(
2659 state_machine.context.schedule.last_update_time.unwrap(),
2660 PartialComplexTime::Wall(last_update_time)
2661 );
2662 });
2663 }
2664
2665 #[test]
2666 fn test_load_server_dictated_poll_interval() {
2667 block_on(async {
2668 let mut storage = MemStorage::new();
2669 storage.set_int(SERVER_DICTATED_POLL_INTERVAL, 56789).await.unwrap();
2670
2671 let state_machine =
2672 StateMachineBuilder::new_stub().storage(Rc::new(Mutex::new(storage))).build().await;
2673
2674 assert_eq!(
2675 Some(Duration::from_micros(56789)),
2676 state_machine.context.state.server_dictated_poll_interval
2677 );
2678 });
2679 }
2680
2681 #[test]
2682 fn test_load_app() {
2683 block_on(async {
2684 let app_set = VecAppSet::new(vec![
2685 App::builder()
2686 .id("{00000000-0000-0000-0000-000000000001}")
2687 .version([1, 2, 3, 4])
2688 .build(),
2689 ]);
2690 let mut storage = MemStorage::new();
2691 let persisted_app = PersistedApp {
2692 cohort: Cohort {
2693 id: Some("cohort_id".to_string()),
2694 hint: Some("test_channel".to_string()),
2695 name: None,
2696 },
2697 user_counting: UserCounting::ClientRegulatedByDate(Some(22222)),
2698 };
2699 let json = serde_json::to_string(&persisted_app).unwrap();
2700 let apps = app_set.get_apps();
2701 storage.set_string(&apps[0].id, &json).await.unwrap();
2702
2703 let app_set = Rc::new(Mutex::new(app_set));
2704
2705 let _state_machine = StateMachineBuilder::new_stub()
2706 .storage(Rc::new(Mutex::new(storage)))
2707 .app_set(Rc::clone(&app_set))
2708 .build()
2709 .await;
2710
2711 let apps = app_set.lock().await.get_apps();
2712 assert_eq!(persisted_app.cohort, apps[0].cohort);
2713 assert_eq!(UserCounting::ClientRegulatedByDate(Some(22222)), apps[0].user_counting);
2714 });
2715 }
2716
2717 #[test]
2718 fn test_report_check_interval_with_no_storage() {
2719 block_on(async {
2720 let mut mock_time = MockTimeSource::new_from_now();
2721 let mut state_machine = StateMachineBuilder::new_stub()
2722 .policy_engine(StubPolicyEngine::new(mock_time.clone()))
2723 .metrics_reporter(MockMetricsReporter::new())
2724 .build()
2725 .await;
2726
2727 state_machine.report_check_interval(InstallSource::ScheduledTask).await;
2728 assert!(state_machine.metrics_reporter.metrics.is_empty());
2730
2731 let interval = Duration::from_micros(999999);
2733 mock_time.advance(interval);
2734
2735 state_machine.report_check_interval(InstallSource::ScheduledTask).await;
2736
2737 assert_eq!(
2738 state_machine.metrics_reporter.metrics,
2739 vec![Metrics::UpdateCheckInterval {
2740 interval,
2741 clock: ClockType::Monotonic,
2742 install_source: InstallSource::ScheduledTask,
2743 }]
2744 );
2745 });
2746 }
2747
2748 #[test]
2749 fn test_report_check_interval_mono_transition() {
2750 block_on(async {
2751 let mut mock_time = MockTimeSource::new_from_now();
2752 let mut state_machine = StateMachineBuilder::new_stub()
2753 .policy_engine(StubPolicyEngine::new(mock_time.clone()))
2754 .metrics_reporter(MockMetricsReporter::new())
2755 .build()
2756 .await;
2757
2758 let initial_duration = Duration::from_secs(999);
2761 let initial_time = mock_time.now_in_walltime() - initial_duration;
2762 state_machine.context.schedule.last_update_check_time =
2763 Some(PartialComplexTime::Wall(initial_time));
2764 state_machine.report_check_interval(InstallSource::ScheduledTask).await;
2765
2766 let interval = Duration::from_micros(999999);
2768 mock_time.advance(interval);
2769 state_machine.report_check_interval(InstallSource::ScheduledTask).await;
2770
2771 mock_time.advance(interval);
2774 state_machine.report_check_interval(InstallSource::ScheduledTask).await;
2775 assert_eq!(
2776 state_machine.metrics_reporter.metrics,
2777 vec![
2778 Metrics::UpdateCheckInterval {
2779 interval: initial_duration,
2780 clock: ClockType::Wall,
2781 install_source: InstallSource::ScheduledTask,
2782 },
2783 Metrics::UpdateCheckInterval {
2784 interval,
2785 clock: ClockType::Monotonic,
2786 install_source: InstallSource::ScheduledTask,
2787 },
2788 Metrics::UpdateCheckInterval {
2789 interval,
2790 clock: ClockType::Monotonic,
2791 install_source: InstallSource::ScheduledTask,
2792 },
2793 ]
2794 );
2795 });
2796 }
2797
2798 #[derive(Debug)]
2799 pub struct TestInstaller {
2800 reboot_called: Rc<RefCell<bool>>,
2801 install_fails: usize,
2802 mock_time: MockTimeSource,
2803 }
2804 struct TestInstallerBuilder {
2805 install_fails: usize,
2806 mock_time: MockTimeSource,
2807 }
2808 impl TestInstaller {
2809 fn builder(mock_time: MockTimeSource) -> TestInstallerBuilder {
2810 TestInstallerBuilder { install_fails: 0, mock_time }
2811 }
2812 }
2813 impl TestInstallerBuilder {
2814 fn add_install_fail(mut self) -> Self {
2815 self.install_fails += 1;
2816 self
2817 }
2818 fn build(self) -> TestInstaller {
2819 TestInstaller {
2820 reboot_called: Rc::new(RefCell::new(false)),
2821 install_fails: self.install_fails,
2822 mock_time: self.mock_time,
2823 }
2824 }
2825 }
2826 const INSTALL_DURATION: Duration = Duration::from_micros(98765433);
2827
2828 impl Installer for TestInstaller {
2829 type InstallPlan = StubPlan;
2830 type Error = StubInstallErrors;
2831 type InstallResult = ();
2832
2833 fn perform_install<'a>(
2834 &'a mut self,
2835 _install_plan: &StubPlan,
2836 observer: Option<&'a dyn ProgressObserver>,
2837 ) -> LocalBoxFuture<'a, (Self::InstallResult, Vec<AppInstallResult<Self::Error>>)> {
2838 if self.install_fails > 0 {
2839 self.install_fails -= 1;
2840 future::ready(((), vec![AppInstallResult::Failed(StubInstallErrors::Failed)]))
2841 .boxed()
2842 } else {
2843 self.mock_time.advance(INSTALL_DURATION);
2844 async move {
2845 if let Some(observer) = observer {
2846 observer.receive_progress(None, 0.0, None, None).await;
2847 observer.receive_progress(None, 0.3, None, None).await;
2848 observer.receive_progress(None, 0.9, None, None).await;
2849 observer.receive_progress(None, 1.0, None, None).await;
2850 }
2851 ((), vec![AppInstallResult::Installed])
2852 }
2853 .boxed_local()
2854 }
2855 }
2856
2857 fn perform_reboot(&mut self) -> LocalBoxFuture<'_, Result<(), anyhow::Error>> {
2858 self.reboot_called.replace(true);
2859 future::ready(Ok(())).boxed_local()
2860 }
2861
2862 fn try_create_install_plan<'a>(
2863 &'a self,
2864 _request_params: &'a RequestParams,
2865 _request_metadata: Option<&'a RequestMetadata>,
2866 _response: &'a Response,
2867 _response_bytes: Vec<u8>,
2868 _ecdsa_signature: Option<Vec<u8>>,
2869 ) -> LocalBoxFuture<'a, Result<Self::InstallPlan, Self::Error>> {
2870 future::ready(Ok(StubPlan)).boxed_local()
2871 }
2872 }
2873
2874 #[test]
2875 fn test_report_successful_update_duration() {
2876 block_on(async {
2877 let http = MockHttpRequest::new(make_update_available_response());
2878 let storage = Rc::new(Mutex::new(MemStorage::new()));
2879
2880 let mut mock_time = MockTimeSource::new_from_now();
2881 mock_time.truncate_submicrosecond_walltime();
2882 let now = mock_time.now();
2883
2884 let update_completed_time = now + INSTALL_DURATION;
2885 let expected_update_duration = update_completed_time.wall_duration_since(now).unwrap();
2886
2887 let first_seen_time = now - Duration::from_micros(1000);
2888
2889 let expected_duration_since_first_seen =
2890 update_completed_time.wall_duration_since(first_seen_time).unwrap();
2891
2892 let mut state_machine = StateMachineBuilder::new_stub()
2893 .http(http)
2894 .installer(TestInstaller::builder(mock_time.clone()).build())
2895 .policy_engine(StubPolicyEngine::new(mock_time.clone()))
2896 .metrics_reporter(MockMetricsReporter::new())
2897 .storage(Rc::clone(&storage))
2898 .build()
2899 .await;
2900
2901 {
2902 let mut storage = storage.lock().await;
2903 storage.set_string(INSTALL_PLAN_ID, "").await.unwrap();
2904 storage.set_time(UPDATE_FIRST_SEEN_TIME, first_seen_time).await.unwrap();
2905 storage.commit().await.unwrap();
2906 }
2907
2908 state_machine.run_once().await;
2909
2910 #[rustfmt::skip]
2911 assert_matches!(
2912 state_machine.metrics_reporter.metrics.as_slice(),
2913 [
2914 Metrics::UpdateCheckResponseTime { response_time: _, successful: true },
2915 Metrics::RequestsPerCheck { count: 1, successful: true },
2916 Metrics::OmahaEventLost(Event { event_type: EventType::UpdateDownloadStarted, event_result: EventResult::Success, .. }),
2917 Metrics::SuccessfulUpdateDuration(install_duration),
2918 Metrics::OmahaEventLost(Event { event_type: EventType::UpdateDownloadFinished, event_result: EventResult::Success, .. }),
2919 Metrics::OmahaEventLost(Event { event_type: EventType::UpdateComplete, event_result: EventResult::Success, .. }),
2920 Metrics::SuccessfulUpdateFromFirstSeen(duration_since_first_seen),
2921 Metrics::AttemptsToSuccessfulCheck(1),
2922 Metrics::AttemptsToSuccessfulInstall { count: 1, successful: true },
2923 ]
2924 if
2925 *install_duration == expected_update_duration &&
2926 *duration_since_first_seen == expected_duration_since_first_seen
2927 );
2928 });
2929 }
2930
2931 #[test]
2932 fn test_report_failed_update_duration() {
2933 block_on(async {
2934 let http = MockHttpRequest::new(make_update_available_response());
2935 let mut state_machine = StateMachineBuilder::new_stub()
2936 .http(http)
2937 .installer(StubInstaller { should_fail: true })
2938 .metrics_reporter(MockMetricsReporter::new())
2939 .build()
2940 .await;
2941 state_machine.run_once().await;
2944
2945 assert!(
2946 state_machine
2947 .metrics_reporter
2948 .metrics
2949 .contains(&Metrics::FailedUpdateDuration(Duration::from_micros(0)))
2950 );
2951 });
2952 }
2953
2954 #[test]
2955 fn test_record_update_first_seen_time() {
2956 block_on(async {
2957 let storage = Rc::new(Mutex::new(MemStorage::new()));
2958 let mut state_machine =
2959 StateMachineBuilder::new_stub().storage(Rc::clone(&storage)).build().await;
2960
2961 let mut mock_time = MockTimeSource::new_from_now();
2962 mock_time.truncate_submicrosecond_walltime();
2963 let now = mock_time.now_in_walltime();
2964 assert_eq!(state_machine.record_update_first_seen_time("id", now).await, now);
2965 {
2966 let storage = storage.lock().await;
2967 assert_eq!(storage.get_string(INSTALL_PLAN_ID).await, Some("id".to_string()));
2968 assert_eq!(storage.get_time(UPDATE_FIRST_SEEN_TIME).await, Some(now));
2969 assert_eq!(storage.len(), 2);
2970 assert!(storage.committed());
2971 }
2972
2973 mock_time.advance(Duration::from_secs(1000));
2974 let now2 = mock_time.now_in_walltime();
2975 assert_eq!(state_machine.record_update_first_seen_time("id", now2).await, now);
2976 {
2977 let storage = storage.lock().await;
2978 assert_eq!(storage.get_string(INSTALL_PLAN_ID).await, Some("id".to_string()));
2979 assert_eq!(storage.get_time(UPDATE_FIRST_SEEN_TIME).await, Some(now));
2980 assert_eq!(storage.len(), 2);
2981 assert!(storage.committed());
2982 }
2983 assert_eq!(state_machine.record_update_first_seen_time("id2", now2).await, now2);
2984 {
2985 let storage = storage.lock().await;
2986 assert_eq!(storage.get_string(INSTALL_PLAN_ID).await, Some("id2".to_string()));
2987 assert_eq!(storage.get_time(UPDATE_FIRST_SEEN_TIME).await, Some(now2));
2988 assert_eq!(storage.len(), 2);
2989 assert!(storage.committed());
2990 }
2991 });
2992 }
2993
2994 #[test]
2995 fn test_report_attempts_to_successful_check() {
2996 block_on(async {
2997 let storage = Rc::new(Mutex::new(MemStorage::new()));
2998 let mut state_machine = StateMachineBuilder::new_stub()
2999 .installer(StubInstaller { should_fail: true })
3000 .metrics_reporter(MockMetricsReporter::new())
3001 .storage(Rc::clone(&storage))
3002 .build()
3003 .await;
3004
3005 state_machine.report_attempts_to_successful_check(true).await;
3006
3007 assert_eq!(state_machine.context.state.consecutive_failed_update_checks, 0);
3010 assert_eq!(
3011 state_machine.metrics_reporter.metrics,
3012 vec![Metrics::AttemptsToSuccessfulCheck(1)]
3013 );
3014
3015 state_machine.report_attempts_to_successful_check(false).await;
3016 assert_eq!(state_machine.context.state.consecutive_failed_update_checks, 1);
3017
3018 state_machine.report_attempts_to_successful_check(false).await;
3019 assert_eq!(state_machine.context.state.consecutive_failed_update_checks, 2);
3020
3021 state_machine.report_attempts_to_successful_check(true).await;
3024 assert_eq!(state_machine.context.state.consecutive_failed_update_checks, 0);
3025 assert_eq!(
3026 state_machine.metrics_reporter.metrics,
3027 vec![Metrics::AttemptsToSuccessfulCheck(1), Metrics::AttemptsToSuccessfulCheck(3)]
3028 );
3029 });
3030 }
3031
3032 #[test]
3033 fn test_ping_omaha_updates_consecutive_failed_update_checks_and_persists() {
3034 block_on(async {
3035 let mut http = MockHttpRequest::empty();
3036 http.add_error(http_request::mock_errors::make_transport_error());
3037 http.add_response(HttpResponse::new(vec![]));
3038 let response = json!({"response":{
3039 "server": "prod",
3040 "protocol": "3.0",
3041 "app": [{
3042 "appid": "{00000000-0000-0000-0000-000000000001}",
3043 "status": "ok",
3044 }],
3045 }});
3046 let response = serde_json::to_vec(&response).unwrap();
3047 http.add_response(HttpResponse::new(response));
3048
3049 let storage = Rc::new(Mutex::new(MemStorage::new()));
3050
3051 {
3053 let mut storage = storage.lock().await;
3054 let _ = storage.set_int(CONSECUTIVE_FAILED_UPDATE_CHECKS, 1).await;
3055 let _ = storage.commit().await;
3056 }
3057
3058 let mut state_machine = StateMachineBuilder::new_stub()
3059 .storage(Rc::clone(&storage))
3060 .http(http)
3061 .build()
3062 .await;
3063
3064 async_generator::generate(move |mut co| async move {
3065 state_machine.ping_omaha(&mut co).await;
3068 assert_eq!(state_machine.context.state.consecutive_failed_update_checks, 2);
3069 {
3070 let storage = storage.lock().await;
3071 assert_eq!(storage.get_int(CONSECUTIVE_FAILED_UPDATE_CHECKS).await, Some(2));
3072 }
3073
3074 state_machine.ping_omaha(&mut co).await;
3075 assert_eq!(state_machine.context.state.consecutive_failed_update_checks, 3);
3076 {
3077 let storage = storage.lock().await;
3078 assert_eq!(storage.get_int(CONSECUTIVE_FAILED_UPDATE_CHECKS).await, Some(3));
3079 }
3080
3081 state_machine.ping_omaha(&mut co).await;
3083 assert_eq!(state_machine.context.state.consecutive_failed_update_checks, 0);
3084 {
3085 let storage = storage.lock().await;
3086 assert_eq!(storage.get_int(CONSECUTIVE_FAILED_UPDATE_CHECKS).await, None);
3087 }
3088 })
3089 .into_complete()
3090 .await;
3091 });
3092 }
3093
3094 #[test]
3095 fn test_report_attempts_to_successful_install() {
3096 block_on(async {
3097 let http = MockHttpRequest::new(make_update_available_response());
3098 let storage = Rc::new(Mutex::new(MemStorage::new()));
3099
3100 let mock_time = MockTimeSource::new_from_now();
3101
3102 let mut state_machine = StateMachineBuilder::new_stub()
3103 .http(http)
3104 .installer(TestInstaller::builder(mock_time.clone()).build())
3105 .policy_engine(StubPolicyEngine::new(mock_time.clone()))
3106 .metrics_reporter(MockMetricsReporter::new())
3107 .storage(Rc::clone(&storage))
3108 .build()
3109 .await;
3110
3111 state_machine.run_once().await;
3112
3113 #[rustfmt::skip]
3116 assert_matches!(
3117 state_machine.metrics_reporter.metrics.as_slice(),
3118 [
3119 Metrics::UpdateCheckResponseTime { response_time: _, successful: true },
3120 Metrics::RequestsPerCheck { count: 1, successful: true },
3121 Metrics::OmahaEventLost(Event { event_type: EventType::UpdateDownloadStarted, event_result: EventResult::Success, .. }),
3122 Metrics::SuccessfulUpdateDuration(_),
3123 Metrics::OmahaEventLost(Event { event_type: EventType::UpdateDownloadFinished, event_result: EventResult::Success, .. }),
3124 Metrics::OmahaEventLost(Event { event_type: EventType::UpdateComplete, event_result: EventResult::Success, .. }),
3125 Metrics::SuccessfulUpdateFromFirstSeen(_),
3126 Metrics::AttemptsToSuccessfulCheck(1),
3127 Metrics::AttemptsToSuccessfulInstall { count: 1, successful: true },
3128 ]
3129 );
3130 });
3131 }
3132
3133 #[test]
3134 fn test_report_attempts_to_successful_install_fails_then_succeeds() {
3135 block_on(async {
3136 let mut http = MockHttpRequest::new(make_update_available_response());
3137 http.add_response(HttpResponse::new(vec![]));
3141 http.add_response(HttpResponse::new(vec![]));
3142
3143 http.add_response(make_update_available_response());
3145 http.add_response(HttpResponse::new(vec![]));
3148 http.add_response(HttpResponse::new(vec![]));
3149
3150 let storage = Rc::new(Mutex::new(MemStorage::new()));
3151 let mock_time = MockTimeSource::new_from_now();
3152
3153 let mut state_machine = StateMachineBuilder::new_stub()
3154 .http(http)
3155 .installer(TestInstaller::builder(mock_time.clone()).add_install_fail().build())
3156 .policy_engine(StubPolicyEngine::new(mock_time.clone()))
3157 .metrics_reporter(MockMetricsReporter::new())
3158 .storage(Rc::clone(&storage))
3159 .build()
3160 .await;
3161
3162 state_machine.run_once().await;
3163 state_machine.run_once().await;
3164
3165 #[rustfmt::skip]
3168 assert_matches!(
3169 state_machine.metrics_reporter.metrics.as_slice(),
3170 [
3171 Metrics::UpdateCheckResponseTime { response_time: _, successful: true },
3172 Metrics::RequestsPerCheck { count: 1, successful: true },
3173 Metrics::FailedUpdateDuration(_),
3174 Metrics::AttemptsToSuccessfulCheck(1),
3175 Metrics::AttemptsToSuccessfulInstall { count: 1, successful: false },
3176 Metrics::UpdateCheckInterval { .. },
3177 Metrics::UpdateCheckResponseTime { response_time: _, successful: true },
3178 Metrics::RequestsPerCheck { count: 1, successful: true },
3179 Metrics::SuccessfulUpdateDuration(_),
3180 Metrics::OmahaEventLost(Event { .. }),
3181 Metrics::SuccessfulUpdateFromFirstSeen(_),
3182 Metrics::AttemptsToSuccessfulCheck(1),
3183 Metrics::AttemptsToSuccessfulInstall { count: 2, successful: true }
3184 ]
3185 );
3186 });
3187 }
3188
3189 #[test]
3190 fn test_report_attempts_to_successful_install_does_not_report_for_no_update() {
3191 block_on(async {
3192 let response = json!({"response":{
3193 "server": "prod",
3194 "protocol": "3.0",
3195 "app": [{
3196 "appid": "{00000000-0000-0000-0000-000000000001}",
3197 "status": "ok",
3198 "updatecheck": {
3199 "status": "noupdate",
3200 "info": "no update for you"
3201 }
3202 }],
3203 }});
3204 let response = serde_json::to_vec(&response).unwrap();
3205 let http = MockHttpRequest::new(HttpResponse::new(response.clone()));
3206
3207 let storage = Rc::new(Mutex::new(MemStorage::new()));
3208 let mock_time = MockTimeSource::new_from_now();
3209
3210 let mut state_machine = StateMachineBuilder::new_stub()
3211 .http(http)
3212 .installer(TestInstaller::builder(mock_time.clone()).build())
3213 .policy_engine(StubPolicyEngine::new(mock_time.clone()))
3214 .metrics_reporter(MockMetricsReporter::new())
3215 .storage(Rc::clone(&storage))
3216 .build()
3217 .await;
3218
3219 state_machine.run_once().await;
3220
3221 #[rustfmt::skip]
3224 assert_matches!(
3225 state_machine.metrics_reporter.metrics.as_slice(),
3226 [
3227 Metrics::UpdateCheckResponseTime { response_time: _, successful: true },
3228 Metrics::RequestsPerCheck { count: 1, successful: true },
3229 Metrics::AttemptsToSuccessfulCheck(1),
3230 ]
3231 );
3232 });
3233 }
3234
3235 #[test]
3236 fn test_successful_update_triggers_reboot() {
3237 let mut pool = LocalPool::new();
3238 let spawner = pool.spawner();
3239
3240 let http = MockHttpRequest::new(make_update_available_response());
3241 let mock_time = MockTimeSource::new_from_now();
3242 let next_update_time = mock_time.now();
3243 let (timer, mut timers) = BlockingTimer::new();
3244
3245 let installer = TestInstaller::builder(mock_time.clone()).build();
3246 let reboot_called = Rc::clone(&installer.reboot_called);
3247 let (_ctl, state_machine) = pool.run_until(
3248 StateMachineBuilder::new_stub()
3249 .http(http)
3250 .installer(installer)
3251 .policy_engine(StubPolicyEngine::new(mock_time))
3252 .timer(timer)
3253 .start(),
3254 );
3255 let observer = TestObserver::default();
3256 spawner.spawn_local(observer.observe(state_machine)).unwrap();
3257
3258 let blocked_timer = pool.run_until(timers.next()).unwrap();
3259 assert_eq!(blocked_timer.requested_wait(), RequestedWait::Until(next_update_time.into()));
3260 blocked_timer.unblock();
3261 pool.run_until_stalled();
3262
3263 assert!(*reboot_called.borrow());
3264 }
3265
3266 #[test]
3267 fn test_skip_reboot_if_not_needed() {
3268 let mut pool = LocalPool::new();
3269 let spawner = pool.spawner();
3270
3271 let http = MockHttpRequest::new(make_update_available_response());
3272 let mock_time = MockTimeSource::new_from_now();
3273 let next_update_time = mock_time.now();
3274 let reboot_check_options_received = Rc::new(RefCell::new(vec![]));
3275 let policy_engine = MockPolicyEngine {
3276 reboot_check_options_received: Rc::clone(&reboot_check_options_received),
3277 check_timing: Some(CheckTiming::builder().time(next_update_time).build()),
3278 time_source: mock_time.clone(),
3279 reboot_needed: Rc::new(RefCell::new(false)),
3280 ..MockPolicyEngine::default()
3281 };
3282 let (timer, mut timers) = BlockingTimer::new();
3283
3284 let installer = TestInstaller::builder(mock_time).build();
3285 let reboot_called = Rc::clone(&installer.reboot_called);
3286 let (_ctl, state_machine) = pool.run_until(
3287 StateMachineBuilder::new_stub()
3288 .http(http)
3289 .installer(installer)
3290 .policy_engine(policy_engine)
3291 .timer(timer)
3292 .start(),
3293 );
3294 let observer = TestObserver::default();
3295 spawner.spawn_local(observer.observe(state_machine)).unwrap();
3296
3297 let blocked_timer = pool.run_until(timers.next()).unwrap();
3298 assert_eq!(blocked_timer.requested_wait(), RequestedWait::Until(next_update_time.into()));
3299 blocked_timer.unblock();
3300 pool.run_until_stalled();
3301
3302 assert_eq!(
3303 observer.take_states(),
3304 vec![
3305 State::CheckingForUpdates(InstallSource::ScheduledTask),
3306 State::InstallingUpdate,
3307 State::Idle
3308 ]
3309 );
3310
3311 assert_eq!(*reboot_check_options_received.borrow(), vec![]);
3312 assert!(!*reboot_called.borrow());
3313 }
3314
3315 #[test]
3316 fn test_failed_update_does_not_trigger_reboot() {
3317 let mut pool = LocalPool::new();
3318 let spawner = pool.spawner();
3319
3320 let http = MockHttpRequest::new(make_update_available_response());
3321 let mock_time = MockTimeSource::new_from_now();
3322 let next_update_time = mock_time.now();
3323 let (timer, mut timers) = BlockingTimer::new();
3324
3325 let installer = TestInstaller::builder(mock_time.clone()).add_install_fail().build();
3326 let reboot_called = Rc::clone(&installer.reboot_called);
3327 let (_ctl, state_machine) = pool.run_until(
3328 StateMachineBuilder::new_stub()
3329 .http(http)
3330 .installer(installer)
3331 .policy_engine(StubPolicyEngine::new(mock_time))
3332 .timer(timer)
3333 .start(),
3334 );
3335 let observer = TestObserver::default();
3336 spawner.spawn_local(observer.observe(state_machine)).unwrap();
3337
3338 let blocked_timer = pool.run_until(timers.next()).unwrap();
3339 assert_eq!(blocked_timer.requested_wait(), RequestedWait::Until(next_update_time.into()));
3340 blocked_timer.unblock();
3341 pool.run_until_stalled();
3342
3343 assert!(!*reboot_called.borrow());
3344 }
3345
3346 #[test]
3350 fn test_reboots_immediately_if_user_initiated_update_requests_occurs_during_install() {
3351 let mut pool = LocalPool::new();
3352 let spawner = pool.spawner();
3353
3354 let http = MockHttpRequest::new(make_update_available_response());
3355 let mock_time = MockTimeSource::new_from_now();
3356
3357 let (send_install, mut recv_install) = mpsc::channel(0);
3358 let (send_reboot, mut recv_reboot) = mpsc::channel(0);
3359 let reboot_check_options_received = Rc::new(RefCell::new(vec![]));
3360 let policy_engine = MockPolicyEngine {
3361 reboot_check_options_received: Rc::clone(&reboot_check_options_received),
3362 check_timing: Some(CheckTiming::builder().time(mock_time.now()).build()),
3363 ..MockPolicyEngine::default()
3364 };
3365
3366 let (mut ctl, state_machine) = pool.run_until(
3367 StateMachineBuilder::new_stub()
3368 .http(http)
3369 .installer(BlockingInstaller {
3370 on_install: send_install,
3371 on_reboot: Some(send_reboot),
3372 })
3373 .policy_engine(policy_engine)
3374 .start(),
3375 );
3376
3377 let observer = TestObserver::default();
3378 spawner.spawn_local(observer.observe(state_machine)).unwrap();
3379
3380 let unblock_install = pool.run_until(recv_install.next()).unwrap();
3381 pool.run_until_stalled();
3382 assert_eq!(
3383 observer.take_states(),
3384 vec![State::CheckingForUpdates(InstallSource::ScheduledTask), State::InstallingUpdate]
3385 );
3386
3387 pool.run_until(async {
3388 assert_eq!(
3389 ctl.start_update_check(CheckOptions { source: InstallSource::OnDemand }).await,
3390 Ok(StartUpdateCheckResponse::AlreadyRunning)
3391 );
3392 });
3393
3394 pool.run_until_stalled();
3395 assert_eq!(observer.take_states(), vec![]);
3396
3397 unblock_install.send(vec![AppInstallResult::Installed]).unwrap();
3398 pool.run_until_stalled();
3399 assert_eq!(observer.take_states(), vec![State::WaitingForReboot]);
3400
3401 let unblock_reboot = pool.run_until(recv_reboot.next()).unwrap();
3402 pool.run_until_stalled();
3403 unblock_reboot.send(Ok(())).unwrap();
3404
3405 assert_eq!(
3407 *reboot_check_options_received.borrow(),
3408 vec![CheckOptions { source: InstallSource::OnDemand }]
3409 );
3410 }
3411
3412 #[test]
3415 fn test_reboots_immediately_when_check_now_comes_in_during_wait() {
3416 let mut pool = LocalPool::new();
3417 let spawner = pool.spawner();
3418
3419 let mut http = MockHttpRequest::new(make_update_available_response());
3420 http.add_response(HttpResponse::new(vec![]));
3422 http.add_response(HttpResponse::new(vec![]));
3423 http.add_response(HttpResponse::new(vec![]));
3424 http.add_response(make_update_available_response());
3426 let mut mock_time = MockTimeSource::new_from_now();
3427 mock_time.truncate_submicrosecond_walltime();
3428 let next_update_time = mock_time.now() + Duration::from_secs(1000);
3429 let (timer, mut timers) = BlockingTimer::new();
3430 let reboot_allowed = Rc::new(RefCell::new(false));
3431 let reboot_check_options_received = Rc::new(RefCell::new(vec![]));
3432 let policy_engine = MockPolicyEngine {
3433 time_source: mock_time.clone(),
3434 reboot_allowed: Rc::clone(&reboot_allowed),
3435 check_timing: Some(CheckTiming::builder().time(next_update_time).build()),
3436 reboot_check_options_received: Rc::clone(&reboot_check_options_received),
3437 ..MockPolicyEngine::default()
3438 };
3439 let installer = TestInstaller::builder(mock_time.clone()).build();
3440 let reboot_called = Rc::clone(&installer.reboot_called);
3441 let storage_ref = Rc::new(Mutex::new(MemStorage::new()));
3442 let apps = make_test_app_set();
3443
3444 let (mut ctl, state_machine) = pool.run_until(
3445 StateMachineBuilder::new_stub()
3446 .app_set(apps)
3447 .http(http)
3448 .installer(installer)
3449 .policy_engine(policy_engine)
3450 .timer(timer)
3451 .storage(Rc::clone(&storage_ref))
3452 .start(),
3453 );
3454
3455 let observer = TestObserver::default();
3456 spawner.spawn_local(observer.observe(state_machine)).unwrap();
3457
3458 let blocked_timer = pool.run_until(timers.next()).unwrap();
3460 assert_eq!(blocked_timer.requested_wait(), RequestedWait::Until(next_update_time.into()));
3461 blocked_timer.unblock();
3462 pool.run_until_stalled();
3463
3464 let blocked_timer1 = pool.run_until(timers.next()).unwrap();
3467 let blocked_timer2 = pool.run_until(timers.next()).unwrap();
3468 let (wait_for_reboot_timer, _wait_for_next_ping_timer) =
3469 match blocked_timer1.requested_wait() {
3470 RequestedWait::For(_) => (blocked_timer1, blocked_timer2),
3471 RequestedWait::Until(_) => (blocked_timer2, blocked_timer1),
3472 };
3473 assert_eq!(
3475 wait_for_reboot_timer.requested_wait(),
3476 RequestedWait::For(CHECK_REBOOT_ALLOWED_INTERVAL)
3477 );
3478
3479 assert!(!*reboot_called.borrow());
3482 *reboot_allowed.borrow_mut() = true;
3483 pool.run_until(async {
3484 assert_eq!(
3485 ctl.start_update_check(CheckOptions { source: InstallSource::OnDemand }).await,
3486 Ok(StartUpdateCheckResponse::AlreadyRunning)
3487 );
3488 });
3489 pool.run_until_stalled();
3490 assert!(*reboot_called.borrow());
3491
3492 assert_eq!(
3496 *reboot_check_options_received.borrow(),
3497 vec![
3498 CheckOptions { source: InstallSource::ScheduledTask },
3499 CheckOptions { source: InstallSource::OnDemand },
3500 ]
3501 );
3502 }
3503
3504 #[test]
3508 fn test_wait_for_reboot() {
3509 let mut pool = LocalPool::new();
3510 let spawner = pool.spawner();
3511
3512 let mut http = MockHttpRequest::new(make_update_available_response());
3513 http.add_response(HttpResponse::new(vec![]));
3515 http.add_response(HttpResponse::new(vec![]));
3516 http.add_response(HttpResponse::new(vec![]));
3517 http.add_response(make_update_available_response());
3519 let ping_request_viewer = MockHttpRequest::from_request_cell(http.get_request_cell());
3520 let mut mock_time = MockTimeSource::new_from_now();
3521 mock_time.truncate_submicrosecond_walltime();
3522 let next_update_time = mock_time.now() + Duration::from_secs(1000);
3523 let (timer, mut timers) = BlockingTimer::new();
3524 let reboot_allowed = Rc::new(RefCell::new(false));
3525 let policy_engine = MockPolicyEngine {
3526 time_source: mock_time.clone(),
3527 reboot_allowed: Rc::clone(&reboot_allowed),
3528 check_timing: Some(CheckTiming::builder().time(next_update_time).build()),
3529 ..MockPolicyEngine::default()
3530 };
3531 let installer = TestInstaller::builder(mock_time.clone()).build();
3532 let reboot_called = Rc::clone(&installer.reboot_called);
3533 let storage_ref = Rc::new(Mutex::new(MemStorage::new()));
3534 let apps = make_test_app_set();
3535
3536 let (mut ctl, state_machine) = pool.run_until(
3537 StateMachineBuilder::new_stub()
3538 .app_set(apps.clone())
3539 .http(http)
3540 .installer(installer)
3541 .policy_engine(policy_engine)
3542 .timer(timer)
3543 .storage(Rc::clone(&storage_ref))
3544 .start(),
3545 );
3546
3547 let observer = TestObserver::default();
3548 spawner.spawn_local(observer.observe(state_machine)).unwrap();
3549
3550 let blocked_timer = pool.run_until(timers.next()).unwrap();
3552 assert_eq!(blocked_timer.requested_wait(), RequestedWait::Until(next_update_time.into()));
3553 blocked_timer.unblock();
3554 pool.run_until_stalled();
3555
3556 let blocked_timer1 = pool.run_until(timers.next()).unwrap();
3559 let blocked_timer2 = pool.run_until(timers.next()).unwrap();
3560 let (wait_for_reboot_timer, wait_for_next_ping_timer) =
3561 match blocked_timer1.requested_wait() {
3562 RequestedWait::For(_) => (blocked_timer1, blocked_timer2),
3563 RequestedWait::Until(_) => (blocked_timer2, blocked_timer1),
3564 };
3565 assert_eq!(
3567 wait_for_reboot_timer.requested_wait(),
3568 RequestedWait::For(CHECK_REBOOT_ALLOWED_INTERVAL)
3569 );
3570 assert_eq!(
3572 wait_for_next_ping_timer.requested_wait(),
3573 RequestedWait::Until(next_update_time.into())
3574 );
3575 mock_time.advance(Duration::from_secs(1000));
3577 wait_for_next_ping_timer.unblock();
3578 pool.run_until_stalled();
3579
3580 let config = crate::configuration::test_support::config_generator();
3582 let request_params = RequestParams::default();
3583
3584 let apps = pool.run_until(apps.lock()).get_apps();
3585 let mut expected_request_builder = RequestBuilder::new(&config, &request_params)
3586 .session_id(GUID::from_u128(5))
3590 .request_id(GUID::from_u128(6));
3591 for app in &apps {
3592 expected_request_builder = expected_request_builder.add_ping(app);
3593 }
3594 pool.run_until(assert_request(&ping_request_viewer, expected_request_builder));
3595
3596 pool.run_until(async {
3597 assert_eq!(
3598 ctl.start_update_check(CheckOptions::default()).await,
3599 Ok(StartUpdateCheckResponse::AlreadyRunning)
3600 );
3601 });
3602
3603 pool.run_until(async {
3605 let storage = storage_ref.lock().await;
3606 let context = update_check::Context::load(&*storage).await;
3607 assert_eq!(context.schedule.last_update_time, Some(mock_time.now_in_walltime().into()));
3608 });
3609
3610 let wait_for_next_ping_timer = pool.run_until(timers.next()).unwrap();
3612 assert_eq!(
3613 wait_for_next_ping_timer.requested_wait(),
3614 RequestedWait::Until(next_update_time.into())
3615 );
3616
3617 wait_for_reboot_timer.unblock();
3619 pool.run_until_stalled();
3620 assert!(!*reboot_called.borrow());
3621
3622 let wait_for_reboot_timer = pool.run_until(timers.next()).unwrap();
3624 assert_eq!(
3625 wait_for_reboot_timer.requested_wait(),
3626 RequestedWait::For(CHECK_REBOOT_ALLOWED_INTERVAL)
3627 );
3628
3629 wait_for_next_ping_timer.unblock();
3631 pool.run_until_stalled();
3632
3633 let mut expected_request_builder = RequestBuilder::new(&config, &request_params)
3635 .session_id(GUID::from_u128(7))
3636 .request_id(GUID::from_u128(8));
3637 for app in &apps {
3638 expected_request_builder = expected_request_builder.add_ping(app);
3639 }
3640 pool.run_until(assert_request(&ping_request_viewer, expected_request_builder));
3641
3642 assert!(!*reboot_called.borrow());
3643
3644 *reboot_called.borrow_mut() = true;
3646 wait_for_reboot_timer.unblock();
3647 pool.run_until_stalled();
3648 assert!(*reboot_called.borrow());
3649 }
3650
3651 #[derive(Debug)]
3652 struct BlockingInstaller {
3653 on_install: mpsc::Sender<oneshot::Sender<Vec<AppInstallResult<StubInstallErrors>>>>,
3654 on_reboot: Option<mpsc::Sender<oneshot::Sender<Result<(), anyhow::Error>>>>,
3655 }
3656
3657 impl Installer for BlockingInstaller {
3658 type InstallPlan = StubPlan;
3659 type Error = StubInstallErrors;
3660 type InstallResult = ();
3661
3662 fn perform_install(
3663 &mut self,
3664 _install_plan: &StubPlan,
3665 _observer: Option<&dyn ProgressObserver>,
3666 ) -> LocalBoxFuture<'_, (Self::InstallResult, Vec<AppInstallResult<Self::Error>>)> {
3667 let (send, recv) = oneshot::channel();
3668 let send_fut = self.on_install.send(send);
3669
3670 async move {
3671 send_fut.await.unwrap();
3672 ((), recv.await.unwrap())
3673 }
3674 .boxed_local()
3675 }
3676
3677 fn perform_reboot(&mut self) -> LocalBoxFuture<'_, Result<(), anyhow::Error>> {
3678 match &mut self.on_reboot {
3679 Some(on_reboot) => {
3680 let (send, recv) = oneshot::channel();
3681 let send_fut = on_reboot.send(send);
3682
3683 async move {
3684 send_fut.await.unwrap();
3685 recv.await.unwrap()
3686 }
3687 .boxed_local()
3688 }
3689 None => future::ready(Ok(())).boxed_local(),
3690 }
3691 }
3692
3693 fn try_create_install_plan<'a>(
3694 &'a self,
3695 _request_params: &'a RequestParams,
3696 _request_metadata: Option<&'a RequestMetadata>,
3697 _response: &'a Response,
3698 _response_bytes: Vec<u8>,
3699 _ecdsa_signature: Option<Vec<u8>>,
3700 ) -> LocalBoxFuture<'a, Result<Self::InstallPlan, Self::Error>> {
3701 future::ready(Ok(StubPlan)).boxed_local()
3702 }
3703 }
3704
3705 #[derive(Debug, Default)]
3706 struct TestObserver {
3707 states: Rc<RefCell<Vec<State>>>,
3708 }
3709
3710 impl TestObserver {
3711 fn observe<T: Stream<Item = StateMachineEvent>>(
3712 &self,
3713 s: T,
3714 ) -> impl Future<Output = ()> + use<T> {
3715 let states = Rc::clone(&self.states);
3716 async move {
3717 futures::pin_mut!(s);
3718 while let Some(event) = s.next().await {
3719 if let StateMachineEvent::StateChange(state) = event {
3720 states.borrow_mut().push(state);
3721 }
3722 }
3723 }
3724 }
3725
3726 fn observe_until_terminal<T: Stream<Item = StateMachineEvent>>(
3727 &self,
3728 s: T,
3729 ) -> impl Future<Output = ()> + use<T> {
3730 let states = Rc::clone(&self.states);
3731 async move {
3732 futures::pin_mut!(s);
3733 while let Some(event) = s.next().await {
3734 if let StateMachineEvent::StateChange(state) = event {
3735 states.borrow_mut().push(state);
3736 match state {
3737 State::Idle | State::WaitingForReboot => return,
3738 _ => {}
3739 }
3740 }
3741 }
3742 }
3743 }
3744
3745 fn take_states(&self) -> Vec<State> {
3746 std::mem::take(&mut *self.states.borrow_mut())
3747 }
3748 }
3749
3750 #[test]
3751 fn test_start_update_during_update_replies_with_in_progress() {
3752 let mut pool = LocalPool::new();
3753 let spawner = pool.spawner();
3754
3755 let http = MockHttpRequest::new(make_update_available_response());
3756 let (send_install, mut recv_install) = mpsc::channel(0);
3757 let (mut ctl, state_machine) = pool.run_until(
3758 StateMachineBuilder::new_stub()
3759 .http(http)
3760 .installer(BlockingInstaller { on_install: send_install, on_reboot: None })
3761 .start(),
3762 );
3763
3764 let observer = TestObserver::default();
3765 spawner.spawn_local(observer.observe_until_terminal(state_machine)).unwrap();
3766
3767 let unblock_install = pool.run_until(recv_install.next()).unwrap();
3768 pool.run_until_stalled();
3769 assert_eq!(
3770 observer.take_states(),
3771 vec![State::CheckingForUpdates(InstallSource::ScheduledTask), State::InstallingUpdate]
3772 );
3773
3774 pool.run_until(async {
3775 assert_eq!(
3776 ctl.start_update_check(CheckOptions::default()).await,
3777 Ok(StartUpdateCheckResponse::AlreadyRunning)
3778 );
3779 });
3780 pool.run_until_stalled();
3781 assert_eq!(observer.take_states(), vec![]);
3782
3783 unblock_install.send(vec![AppInstallResult::Installed]).unwrap();
3784 pool.run_until_stalled();
3785
3786 assert_eq!(observer.take_states(), vec![State::WaitingForReboot]);
3787 }
3788
3789 #[test]
3790 fn test_start_update_during_timer_starts_update() {
3791 let mut pool = LocalPool::new();
3792 let spawner = pool.spawner();
3793
3794 let mut mock_time = MockTimeSource::new_from_now();
3795 let next_update_time = mock_time.now() + Duration::from_secs(321);
3796
3797 let (timer, mut timers) = BlockingTimer::new();
3798 let policy_engine = MockPolicyEngine {
3799 check_timing: Some(CheckTiming::builder().time(next_update_time).build()),
3800 time_source: mock_time.clone(),
3801 ..MockPolicyEngine::default()
3802 };
3803 let (mut ctl, state_machine) = pool.run_until(
3804 StateMachineBuilder::new_stub().policy_engine(policy_engine).timer(timer).start(),
3805 );
3806
3807 let observer = TestObserver::default();
3808 spawner.spawn_local(observer.observe(state_machine)).unwrap();
3809
3810 let blocked_timer = pool.run_until(timers.next()).unwrap();
3811 assert_eq!(blocked_timer.requested_wait(), RequestedWait::Until(next_update_time.into()));
3812 mock_time.advance(Duration::from_secs(200));
3813 assert_eq!(observer.take_states(), vec![]);
3814
3815 pool.run_until_stalled();
3817 assert_eq!(observer.take_states(), vec![]);
3818
3819 blocked_timer.unblock();
3820 let blocked_timer = pool.run_until(timers.next()).unwrap();
3821 assert_eq!(blocked_timer.requested_wait(), RequestedWait::Until(next_update_time.into()));
3822 assert_eq!(
3823 observer.take_states(),
3824 vec![
3825 State::CheckingForUpdates(InstallSource::ScheduledTask),
3826 State::ErrorCheckingForUpdate,
3827 State::Idle
3828 ]
3829 );
3830
3831 pool.run_until(async {
3833 assert_eq!(
3834 ctl.start_update_check(CheckOptions::default()).await,
3835 Ok(StartUpdateCheckResponse::Started)
3836 );
3837 });
3838 pool.run_until_stalled();
3839 assert_eq!(
3840 observer.take_states(),
3841 vec![
3842 State::CheckingForUpdates(InstallSource::ScheduledTask),
3843 State::ErrorCheckingForUpdate,
3844 State::Idle
3845 ]
3846 );
3847 }
3848
3849 #[test]
3850 fn test_start_update_check_returns_throttled() {
3851 let mut pool = LocalPool::new();
3852 let spawner = pool.spawner();
3853
3854 let mut mock_time = MockTimeSource::new_from_now();
3855 let next_update_time = mock_time.now() + Duration::from_secs(321);
3856
3857 let (timer, mut timers) = BlockingTimer::new();
3858 let policy_engine = MockPolicyEngine {
3859 check_timing: Some(CheckTiming::builder().time(next_update_time).build()),
3860 time_source: mock_time.clone(),
3861 check_decision: CheckDecision::ThrottledByPolicy,
3862 ..MockPolicyEngine::default()
3863 };
3864 let (mut ctl, state_machine) = pool.run_until(
3865 StateMachineBuilder::new_stub().policy_engine(policy_engine).timer(timer).start(),
3866 );
3867
3868 let observer = TestObserver::default();
3869 spawner.spawn_local(observer.observe(state_machine)).unwrap();
3870
3871 let blocked_timer = pool.run_until(timers.next()).unwrap();
3872 assert_eq!(blocked_timer.requested_wait(), RequestedWait::Until(next_update_time.into()));
3873 mock_time.advance(Duration::from_secs(200));
3874 assert_eq!(observer.take_states(), vec![]);
3875
3876 pool.run_until(async {
3877 assert_eq!(
3878 ctl.start_update_check(CheckOptions::default()).await,
3879 Ok(StartUpdateCheckResponse::Throttled)
3880 );
3881 });
3882 pool.run_until_stalled();
3883 assert_eq!(observer.take_states(), vec![]);
3884 }
3885
3886 #[test]
3887 fn test_progress_observer() {
3888 block_on(async {
3889 let http = MockHttpRequest::new(make_update_available_response());
3890 let mock_time = MockTimeSource::new_from_now();
3891 let progresses = StateMachineBuilder::new_stub()
3892 .http(http)
3893 .installer(TestInstaller::builder(mock_time.clone()).build())
3894 .policy_engine(StubPolicyEngine::new(mock_time))
3895 .oneshot_check()
3896 .await
3897 .filter_map(|event| {
3898 future::ready(match event {
3899 StateMachineEvent::InstallProgressChange(InstallProgress { progress }) => {
3900 Some(progress)
3901 }
3902 _ => None,
3903 })
3904 })
3905 .collect::<Vec<f32>>()
3906 .await;
3907 assert_eq!(progresses, [0.0, 0.3, 0.9, 1.0]);
3908 });
3909 }
3910
3911 #[test]
3912 fn test_report_waited_for_reboot_duration_doesnt_panic_on_wrong_current_time() {
3916 block_on(async {
3917 let metrics_reporter = MockMetricsReporter::new();
3918
3919 let state_machine_start_monotonic = Instant::now();
3920 let update_finish_time = SystemTime::now();
3921
3922 let now_wall = update_finish_time + Duration::from_secs(1);
3926 let now_monotonic = state_machine_start_monotonic + Duration::from_secs(10);
3927
3928 let mut state_machine =
3929 StateMachineBuilder::new_stub().metrics_reporter(metrics_reporter).build().await;
3930
3931 state_machine
3935 .report_waited_for_reboot_duration(
3936 update_finish_time,
3937 state_machine_start_monotonic,
3938 ComplexTime { wall: now_wall, mono: now_monotonic },
3939 )
3940 .expect_err("should overflow and error out");
3941
3942 assert!(state_machine.metrics_reporter.metrics.is_empty());
3944 });
3945 }
3946
3947 #[test]
3948 fn test_report_waited_for_reboot_duration() {
3949 let mut pool = LocalPool::new();
3950 let spawner = pool.spawner();
3951
3952 let response = json!({"response": {
3953 "server": "prod",
3954 "protocol": "3.0",
3955 "app": [{
3956 "appid": "{00000000-0000-0000-0000-000000000001}",
3957 "status": "ok",
3958 "updatecheck": {
3959 "status": "ok",
3960 "manifest": {
3961 "version": "1.2.3.5",
3962 "actions": {
3963 "action": [],
3964 },
3965 "packages": {
3966 "package": [],
3967 },
3968 }
3969 }
3970 }],
3971 }});
3972 let response = serde_json::to_vec(&response).unwrap();
3973 let http = MockHttpRequest::new(HttpResponse::new(response));
3974 let mut mock_time = MockTimeSource::new_from_now();
3975 mock_time.truncate_submicrosecond_walltime();
3976 let storage = Rc::new(Mutex::new(MemStorage::new()));
3977
3978 assert_matches!(
3980 pool.run_until(
3981 StateMachineBuilder::new_stub()
3982 .http(http)
3983 .policy_engine(StubPolicyEngine::new(mock_time.clone()))
3984 .storage(Rc::clone(&storage))
3985 .oneshot(RequestParams::default())
3986 ),
3987 Ok(_)
3988 );
3989
3990 mock_time.advance(Duration::from_secs(999));
3991
3992 let config = Config {
3994 updater: Updater { name: "updater".to_string(), version: Version::from([0, 1]) },
3995 os: OS { version: "1.2.3.5".to_string(), ..OS::default() },
3996 service_url: "http://example.com/".to_string(),
3997 omaha_public_keys: None,
3998 };
3999 let metrics_reporter = Rc::new(RefCell::new(MockMetricsReporter::new()));
4000 let (_ctl, state_machine) = pool.run_until(
4001 StateMachineBuilder::new_stub()
4002 .config(config)
4003 .metrics_reporter(Rc::clone(&metrics_reporter))
4004 .policy_engine(StubPolicyEngine::new(mock_time.clone()))
4005 .storage(Rc::clone(&storage))
4006 .timer(MockTimer::new())
4007 .start(),
4008 );
4009
4010 let observer = TestObserver::default();
4012 spawner.spawn_local(observer.observe(state_machine)).unwrap();
4013 pool.run_until_stalled();
4014
4015 assert_eq!(
4016 metrics_reporter
4017 .borrow()
4018 .metrics
4019 .iter()
4020 .filter(|m| matches!(m, Metrics::WaitedForRebootDuration(_)))
4021 .collect::<Vec<_>>(),
4022 vec![&Metrics::WaitedForRebootDuration(Duration::from_secs(999))]
4023 );
4024
4025 pool.run_until(async {
4027 let storage = storage.lock().await;
4028 assert_eq!(storage.get_time(UPDATE_FINISH_TIME).await, None);
4029 assert_eq!(storage.get_string(TARGET_VERSION).await, None);
4030 assert!(storage.committed());
4031 })
4032 }
4033
4034 #[test]
4036 fn run_cup_but_decoration_error() {
4037 block_on(async {
4038 let http = MockHttpRequest::new(HttpResponse::new(make_noupdate_httpresponse()));
4039
4040 let stub_cup_handler = MockCupv2Handler::new().set_decoration_error(|| {
4041 Some(CupDecorationError::ParseError("".parse::<http::Uri>().unwrap_err()))
4042 });
4043
4044 assert_matches!(
4045 StateMachineBuilder::new_stub()
4046 .http(http)
4047 .cup_handler(Some(stub_cup_handler))
4048 .oneshot(RequestParams::default())
4049 .await,
4050 Err(UpdateCheckError::OmahaRequest(OmahaRequestError::CupDecoration(
4051 CupDecorationError::ParseError(_)
4052 )))
4053 );
4054
4055 info!("update check complete!");
4056 });
4057 }
4058
4059 #[test]
4060 fn run_cup_but_verification_error() {
4061 block_on(async {
4062 let http = MockHttpRequest::new(HttpResponse::new(make_noupdate_httpresponse()));
4063
4064 let stub_cup_handler = MockCupv2Handler::new()
4065 .set_verification_error(|| Some(CupVerificationError::EtagHeaderMissing));
4066
4067 assert_matches!(
4068 StateMachineBuilder::new_stub()
4069 .http(http)
4070 .cup_handler(Some(stub_cup_handler))
4071 .oneshot(RequestParams::default())
4072 .await,
4073 Err(UpdateCheckError::OmahaRequest(OmahaRequestError::CupValidation(
4074 CupVerificationError::EtagHeaderMissing
4075 )))
4076 );
4077
4078 info!("update check complete!");
4079 });
4080 }
4081
4082 #[test]
4083 fn run_cup_valid() {
4084 block_on(async {
4085 let http = MockHttpRequest::new(HttpResponse::new(make_noupdate_httpresponse()));
4086
4087 assert_matches!(
4088 StateMachineBuilder::new_stub()
4089 .http(http)
4090 .oneshot(RequestParams::default())
4092 .await,
4093 Ok(_)
4094 );
4095
4096 info!("update check complete!");
4097 });
4098 }
4099}