omaha_client/state_machine/
builder.rs

1// Copyright 2019 The Fuchsia Authors
2//
3// Licensed under a BSD-style license <LICENSE-BSD>, Apache License, Version 2.0
4// <LICENSE-APACHE or https://www.apache.org/licenses/LICENSE-2.0>, or the MIT
5// license <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your option.
6// This file may not be copied, modified, or distributed except according to
7// those terms.
8
9use crate::{
10    app_set::{AppSet, AppSetExt as _},
11    async_generator,
12    configuration::Config,
13    cup_ecdsa::Cupv2Handler,
14    http_request::HttpRequest,
15    installer::{Installer, Plan},
16    metrics::MetricsReporter,
17    policy::PolicyEngine,
18    request_builder::RequestParams,
19    state_machine::{update_check, ControlHandle, StateMachine, StateMachineEvent},
20    storage::Storage,
21    time::Timer,
22};
23use futures::{channel::mpsc, lock::Mutex, prelude::*};
24use std::rc::Rc;
25
26#[cfg(test)]
27use crate::{
28    app_set::VecAppSet,
29    common::App,
30    cup_ecdsa::test_support::MockCupv2Handler,
31    http_request::StubHttpRequest,
32    installer::stub::{StubInstaller, StubPlan},
33    metrics::StubMetricsReporter,
34    policy::StubPolicyEngine,
35    state_machine::{RebootAfterUpdate, UpdateCheckError},
36    storage::StubStorage,
37    time::{timers::StubTimer, MockTimeSource},
38};
39
40/// Helper type to build/start a [`StateMachine`].
41#[derive(Debug)]
42pub struct StateMachineBuilder<PE, HR, IN, TM, MR, ST, AS, CH>
43where
44    PE: PolicyEngine,
45    HR: HttpRequest,
46    IN: Installer,
47    TM: Timer,
48    MR: MetricsReporter,
49    ST: Storage,
50    AS: AppSet,
51    CH: Cupv2Handler,
52{
53    policy_engine: PE,
54    http: HR,
55    installer: IN,
56    timer: TM,
57    metrics_reporter: MR,
58    storage: Rc<Mutex<ST>>,
59    config: Config,
60    app_set: Rc<Mutex<AS>>,
61    cup_handler: Option<CH>,
62}
63
64impl<'a, PE, HR, IN, TM, MR, ST, AS, CH> StateMachineBuilder<PE, HR, IN, TM, MR, ST, AS, CH>
65where
66    PE: 'a + PolicyEngine,
67    HR: 'a + HttpRequest,
68    IN: 'a + Installer,
69    TM: 'a + Timer,
70    MR: 'a + MetricsReporter,
71    ST: 'a + Storage,
72    AS: 'a + AppSet,
73    CH: 'a + Cupv2Handler,
74{
75    /// Creates a new `StateMachineBuilder` using the given trait implementations.
76    #[allow(clippy::too_many_arguments)]
77    pub fn new(
78        policy_engine: PE,
79        http: HR,
80        installer: IN,
81        timer: TM,
82        metrics_reporter: MR,
83        storage: Rc<Mutex<ST>>,
84        config: Config,
85        app_set: Rc<Mutex<AS>>,
86        cup_handler: Option<CH>,
87    ) -> Self {
88        Self {
89            policy_engine,
90            http,
91            installer,
92            timer,
93            metrics_reporter,
94            storage,
95            config,
96            app_set,
97            cup_handler,
98        }
99    }
100}
101
102impl<'a, PE, HR, IN, TM, MR, ST, AS, CH> StateMachineBuilder<PE, HR, IN, TM, MR, ST, AS, CH>
103where
104    PE: 'a + PolicyEngine,
105    HR: 'a + HttpRequest,
106    IN: 'a + Installer,
107    TM: 'a + Timer,
108    MR: 'a + MetricsReporter,
109    ST: 'a + Storage,
110    AS: 'a + AppSet,
111    CH: 'a + Cupv2Handler,
112{
113    /// Configures the state machine to use the provided policy_engine implementation.
114    pub fn policy_engine<PE2: 'a + PolicyEngine>(
115        self,
116        policy_engine: PE2,
117    ) -> StateMachineBuilder<PE2, HR, IN, TM, MR, ST, AS, CH> {
118        StateMachineBuilder {
119            policy_engine,
120            http: self.http,
121            installer: self.installer,
122            timer: self.timer,
123            metrics_reporter: self.metrics_reporter,
124            storage: self.storage,
125            config: self.config,
126            app_set: self.app_set,
127            cup_handler: self.cup_handler,
128        }
129    }
130
131    /// Configures the state machine to use the provided http implementation.
132    pub fn http<HR2: 'a + HttpRequest>(
133        self,
134        http: HR2,
135    ) -> StateMachineBuilder<PE, HR2, IN, TM, MR, ST, AS, CH> {
136        StateMachineBuilder {
137            policy_engine: self.policy_engine,
138            http,
139            installer: self.installer,
140            timer: self.timer,
141            metrics_reporter: self.metrics_reporter,
142            storage: self.storage,
143            config: self.config,
144            app_set: self.app_set,
145            cup_handler: self.cup_handler,
146        }
147    }
148
149    /// Configures the state machine to use the provided installer implementation.
150    pub fn installer<IN2: 'a + Installer>(
151        self,
152        installer: IN2,
153    ) -> StateMachineBuilder<PE, HR, IN2, TM, MR, ST, AS, CH> {
154        StateMachineBuilder {
155            policy_engine: self.policy_engine,
156            http: self.http,
157            installer,
158            timer: self.timer,
159            metrics_reporter: self.metrics_reporter,
160            storage: self.storage,
161            config: self.config,
162            app_set: self.app_set,
163            cup_handler: self.cup_handler,
164        }
165    }
166
167    /// Configures the state machine to use the provided timer implementation.
168    pub fn timer<TM2: 'a + Timer>(
169        self,
170        timer: TM2,
171    ) -> StateMachineBuilder<PE, HR, IN, TM2, MR, ST, AS, CH> {
172        StateMachineBuilder {
173            policy_engine: self.policy_engine,
174            http: self.http,
175            installer: self.installer,
176            timer,
177            metrics_reporter: self.metrics_reporter,
178            storage: self.storage,
179            config: self.config,
180            app_set: self.app_set,
181            cup_handler: self.cup_handler,
182        }
183    }
184
185    /// Configures the state machine to use the provided metrics_reporter implementation.
186    pub fn metrics_reporter<MR2: 'a + MetricsReporter>(
187        self,
188        metrics_reporter: MR2,
189    ) -> StateMachineBuilder<PE, HR, IN, TM, MR2, ST, AS, CH> {
190        StateMachineBuilder {
191            policy_engine: self.policy_engine,
192            http: self.http,
193            installer: self.installer,
194            timer: self.timer,
195            metrics_reporter,
196            storage: self.storage,
197            config: self.config,
198            app_set: self.app_set,
199            cup_handler: self.cup_handler,
200        }
201    }
202
203    /// Configures the state machine to use the provided storage implementation.
204    pub fn storage<ST2: 'a + Storage>(
205        self,
206        storage: Rc<Mutex<ST2>>,
207    ) -> StateMachineBuilder<PE, HR, IN, TM, MR, ST2, AS, CH> {
208        StateMachineBuilder {
209            policy_engine: self.policy_engine,
210            http: self.http,
211            installer: self.installer,
212            timer: self.timer,
213            metrics_reporter: self.metrics_reporter,
214            storage,
215            config: self.config,
216            app_set: self.app_set,
217            cup_handler: self.cup_handler,
218        }
219    }
220
221    /// Configures the state machine to use the provided config.
222    pub fn config(mut self, config: Config) -> Self {
223        self.config = config;
224        self
225    }
226
227    /// Configures the state machine to use the provided app_set implementation.
228    pub fn app_set<AS2: 'a + AppSet>(
229        self,
230        app_set: Rc<Mutex<AS2>>,
231    ) -> StateMachineBuilder<PE, HR, IN, TM, MR, ST, AS2, CH> {
232        StateMachineBuilder {
233            policy_engine: self.policy_engine,
234            http: self.http,
235            installer: self.installer,
236            timer: self.timer,
237            metrics_reporter: self.metrics_reporter,
238            storage: self.storage,
239            config: self.config,
240            app_set,
241            cup_handler: self.cup_handler,
242        }
243    }
244
245    pub fn cup_handler<CH2: 'a + Cupv2Handler>(
246        self,
247        cup_handler: Option<CH2>,
248    ) -> StateMachineBuilder<PE, HR, IN, TM, MR, ST, AS, CH2> {
249        StateMachineBuilder {
250            policy_engine: self.policy_engine,
251            http: self.http,
252            installer: self.installer,
253            timer: self.timer,
254            metrics_reporter: self.metrics_reporter,
255            storage: self.storage,
256            config: self.config,
257            app_set: self.app_set,
258            cup_handler,
259        }
260    }
261}
262
263impl<'a, PE, HR, IN, TM, MR, ST, AS, CH, IR, PL> StateMachineBuilder<PE, HR, IN, TM, MR, ST, AS, CH>
264where
265    PE: 'a + PolicyEngine<InstallResult = IR, InstallPlan = PL>,
266    HR: 'a + HttpRequest,
267    IN: 'a + Installer<InstallResult = IR, InstallPlan = PL>,
268    TM: 'a + Timer,
269    MR: 'a + MetricsReporter,
270    ST: 'a + Storage,
271    AS: 'a + AppSet,
272    CH: 'a + Cupv2Handler,
273    IR: 'static + Send,
274    PL: 'a + Plan,
275{
276    pub async fn build(self) -> StateMachine<PE, HR, IN, TM, MR, ST, AS, CH> {
277        let StateMachineBuilder {
278            policy_engine,
279            http,
280            installer,
281            timer,
282            metrics_reporter,
283            storage,
284            config,
285            app_set,
286            cup_handler,
287        } = self;
288
289        let context = {
290            let storage = storage.lock().await;
291            let mut app_set = app_set.lock().await;
292            let ((), context) = futures::join!(
293                app_set.load(&*storage),
294                update_check::Context::load(&*storage)
295            );
296            tracing::info!("Omaha app set: {:?}", app_set.get_apps());
297            context
298        };
299
300        let time_source = policy_engine.time_source().clone();
301
302        StateMachine {
303            config,
304            policy_engine,
305            http,
306            installer,
307            timer,
308            time_source,
309            metrics_reporter,
310            storage_ref: storage,
311            context,
312            app_set,
313            cup_handler,
314        }
315    }
316
317    /// Start the StateMachine to do periodic update checks in the background or when requested
318    /// through the returned control handle.  The returned stream must be polled to make
319    /// forward progress.
320    // TODO: find a better name for this function.
321    pub async fn start(self) -> (ControlHandle, impl Stream<Item = StateMachineEvent> + 'a) {
322        let state_machine = self.build().await;
323
324        let (send, recv) = mpsc::channel(0);
325        (
326            ControlHandle(send),
327            async_generator::generate(move |co| state_machine.run(recv, co)).into_yielded(),
328        )
329    }
330
331    /// Run start_upate_check once, returning a stream of the states it produces.
332    pub async fn oneshot_check(self) -> impl Stream<Item = StateMachineEvent> + 'a {
333        let mut state_machine = self.build().await;
334        let request_params = RequestParams::default();
335
336        async_generator::generate(move |mut co| async move {
337            state_machine
338                .start_update_check(request_params, &mut co)
339                .await;
340        })
341        .into_yielded()
342    }
343
344    /// Run perform_update_check once, returning the update check result.
345    #[cfg(test)]
346    pub(super) async fn oneshot(
347        self,
348        request_params: RequestParams,
349    ) -> Result<(update_check::Response, RebootAfterUpdate<IR>), UpdateCheckError> {
350        self.build().await.oneshot(request_params).await
351    }
352}
353
354#[cfg(test)]
355impl
356    StateMachineBuilder<
357        StubPolicyEngine<StubPlan, MockTimeSource>,
358        StubHttpRequest,
359        StubInstaller,
360        StubTimer,
361        StubMetricsReporter,
362        StubStorage,
363        VecAppSet,
364        MockCupv2Handler,
365    >
366{
367    /// Create a new StateMachine with stub implementations and configuration.
368    pub fn new_stub() -> Self {
369        let config = crate::configuration::test_support::config_generator();
370
371        let app_set = VecAppSet::new(vec![App::builder()
372            .id("{00000000-0000-0000-0000-000000000001}")
373            .version([1, 2, 3, 4])
374            .cohort(crate::protocol::Cohort::new("stable-channel"))
375            .build()]);
376        let mock_time = MockTimeSource::new_from_now();
377
378        StateMachineBuilder::new(
379            StubPolicyEngine::new(mock_time),
380            StubHttpRequest,
381            StubInstaller::default(),
382            StubTimer,
383            StubMetricsReporter,
384            Rc::new(Mutex::new(StubStorage)),
385            config,
386            Rc::new(Mutex::new(app_set)),
387            Some(MockCupv2Handler::new()),
388        )
389    }
390}