icinga_client/
types.rs

1use chrono::{DateTime, Local, TimeZone};
2use serde::{Deserialize, Serialize};
3use std::convert::TryFrom;
4
5use HostState::*;
6use ServiceState::*;
7
8#[derive(serde::Deserialize, serde::Serialize)]
9pub struct IcingaObjectResults<T> {
10    pub results: Vec<IcingaObjectResult<T>>,
11}
12
13#[derive(serde::Deserialize, serde::Serialize)]
14pub struct IcingaObjectResult<T> {
15    pub attrs: T,
16}
17
18#[derive(Debug, PartialEq, PartialOrd, Eq, Ord, Copy, Clone, Serialize, Deserialize)]
19#[serde(try_from = "f64", into = "f64")]
20pub enum HostState {
21    UP,
22    DOWN,
23}
24
25impl TryFrom<u8> for HostState {
26    type Error = String;
27    fn try_from(i: u8) -> Result<Self, Self::Error> {
28        match i {
29            0 => Ok(UP),
30            1 => Ok(DOWN),
31            _ => Err(format!("Not a valid state number: {}", i)),
32        }
33    }
34}
35
36impl TryFrom<f64> for HostState {
37    type Error = String;
38    fn try_from(f: f64) -> Result<Self, Self::Error> {
39        if f.fract() != 0_f64 || f < 0_f64 || f > 255_f64 {
40            return Err(format!("Not a valid host state number: {}", f));
41        }
42        HostState::try_from(f as u8)
43    }
44}
45
46impl From<HostState> for f64 {
47    fn from(s: HostState) -> Self {
48        u8::from(s) as f64
49    }
50}
51
52impl From<HostState> for u8 {
53    fn from(s: HostState) -> Self {
54        match s {
55            UP => 0,
56            DOWN => 1,
57        }
58    }
59}
60
61#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Copy, Clone, Serialize, Deserialize)]
62#[serde(try_from = "f64", into = "f64")]
63pub enum ServiceState {
64    OK,
65    WARNING,
66    CRITICAL,
67    UNKNOWN,
68}
69
70impl TryFrom<u8> for ServiceState {
71    type Error = String;
72    fn try_from(i: u8) -> Result<Self, Self::Error> {
73        match i {
74            0 => Ok(OK),
75            1 => Ok(WARNING),
76            2 => Ok(CRITICAL),
77            3 => Ok(UNKNOWN),
78            _ => Err(format!("Not a valid state number: {}", i)),
79        }
80    }
81}
82
83impl TryFrom<f64> for ServiceState {
84    type Error = String;
85    fn try_from(f: f64) -> Result<Self, Self::Error> {
86        if f.fract() != 0_f64 || f < 0_f64 || f > 255_f64 {
87            return Err(format!("Not a valid service state number: {}", f));
88        }
89        ServiceState::try_from(f as u8)
90    }
91}
92impl From<ServiceState> for f64 {
93    fn from(s: ServiceState) -> Self {
94        u8::from(s) as f64
95    }
96}
97
98impl From<ServiceState> for u8 {
99    fn from(s: ServiceState) -> Self {
100        match s {
101            OK => 0,
102            WARNING => 1,
103            CRITICAL => 2,
104            UNKNOWN => 3,
105        }
106    }
107}
108
109#[derive(Debug, PartialEq, PartialOrd, Copy, Clone, Serialize, Deserialize)]
110#[serde(transparent)]
111pub struct Timestamp {
112    icinga_timestamp: f64,
113}
114
115impl Timestamp {
116    pub fn new(icinga_timestamp: f64) -> Self {
117        Timestamp { icinga_timestamp }
118    }
119
120    pub fn zero() -> Self {
121        Timestamp {
122            icinga_timestamp: 0_f64,
123        }
124    }
125
126    /// Get a DateTime of the timestamp.
127    pub fn datetime<T: TimeZone>(&self, tz: &T) -> DateTime<T> {
128        let nanos = self.icinga_timestamp.fract() * 1_000_000_000_f64;
129        tz.timestamp(self.icinga_timestamp.trunc() as i64, nanos as u32)
130    }
131
132    pub fn localtime(&self) -> DateTime<Local> {
133        self.datetime(&Local)
134    }
135}
136
137pub trait CheckedObject {
138    const PATH: &'static str;
139    const OBJECT_TYPE_FOR_QUERY: &'static str;
140
141    fn name(&self) -> &str;
142    fn is_ok(&self) -> bool;
143    fn acknowledgement(&self) -> &Acknowledgement;
144    fn is_handled(&self) -> bool;
145
146    fn set_name(&mut self, name: String);
147    fn set_acknowledgement(&mut self, ack: Acknowledgement);
148    fn set_handled(&mut self, handled: bool);
149}
150
151#[derive(Debug, PartialEq, PartialOrd, Clone, Serialize, Deserialize)]
152pub struct Host {
153    pub name: String,
154    pub display_name: String,
155    pub address: String,
156    pub address6: String,
157    pub state: HostState,
158    pub last_state: HostState,
159    pub last_hard_state: HostState,
160    pub last_state_change: Timestamp,
161    pub last_state_up: Timestamp,
162    pub last_state_down: Timestamp,
163
164    pub next_check: Timestamp,
165    pub last_check_result: Option<LastCheckResult>,
166
167    #[serde(flatten)]
168    pub acknowledgement: Acknowledgement,
169
170    pub handled: bool,
171}
172
173impl Host {
174    ///
175    /// Return `true` if the Host is *handled*.
176    ///
177    /// A host is *handled* if it is either
178    ///
179    /// - acknowledged
180    /// - on downtime
181    /// - dependent on a handled service or host
182    ///
183    /// The returned value corresponds to the `handled` field in icinga's JSON representation for
184    /// hosts
185    pub fn is_handled(&self) -> bool {
186        self.handled
187    }
188}
189
190impl CheckedObject for Host {
191    const PATH: &'static str = "/v1/objects/hosts";
192    const OBJECT_TYPE_FOR_QUERY: &'static str = "host";
193
194    fn name(&self) -> &str {
195        &self.name
196    }
197
198    fn is_ok(&self) -> bool {
199        self.state == HostState::UP
200    }
201
202    fn acknowledgement(&self) -> &Acknowledgement {
203        &self.acknowledgement
204    }
205
206    fn is_handled(&self) -> bool {
207        self.handled
208    }
209
210    fn set_name(&mut self, name: String) {
211        self.name = name
212    }
213
214    fn set_acknowledgement(&mut self, ack: Acknowledgement) {
215        self.acknowledgement = ack
216    }
217
218    fn set_handled(&mut self, handled: bool) {
219        self.handled = handled
220    }
221}
222
223#[derive(Debug, PartialEq, PartialOrd, Eq, Ord, Clone, Serialize, Deserialize)]
224pub struct LastCheckResult {
225    pub output: String,
226}
227
228#[derive(Debug, PartialEq, PartialOrd, Clone, Serialize, Deserialize)]
229pub struct Service {
230    #[serde(rename = "__name")]
231    pub name: String,
232    pub display_name: String,
233    pub host_name: String,
234    pub state: ServiceState,
235    pub last_state: ServiceState,
236    pub last_hard_state: ServiceState,
237    pub last_state_change: Timestamp,
238    pub last_state_critical: Timestamp,
239    pub last_state_warning: Timestamp,
240    pub last_state_ok: Timestamp,
241    pub last_state_unknown: Timestamp,
242
243    pub next_check: Timestamp,
244    pub last_check_result: Option<LastCheckResult>,
245
246    #[serde(flatten)]
247    pub acknowledgement: Acknowledgement,
248
249    pub handled: bool,
250    pub last_reachable: bool,
251}
252
253impl Service {
254    /// Return `true` if the Service is *handled*.
255    ///
256    /// A service is *handled* if it is either
257    ///
258    /// - acknowledged
259    /// - on downtime
260    /// - dependent on a handled service or host
261    ///
262    /// The returned value corresponds to the `handled` field in icinga's JSON representation for
263    /// services
264    pub fn is_handled(&self) -> bool {
265        self.handled
266    }
267
268    pub fn host_is_reachable(&self) -> bool {
269        self.last_reachable
270    }
271}
272
273impl CheckedObject for Service {
274    const PATH: &'static str = "/v1/objects/services";
275    const OBJECT_TYPE_FOR_QUERY: &'static str = "service";
276
277    fn name(&self) -> &str {
278        &self.name
279    }
280
281    fn is_ok(&self) -> bool {
282        self.state == ServiceState::OK
283    }
284
285    fn acknowledgement(&self) -> &Acknowledgement {
286        &self.acknowledgement
287    }
288
289    fn is_handled(&self) -> bool {
290        self.handled
291    }
292
293    fn set_name(&mut self, name: String) {
294        self.name = name
295    }
296
297    fn set_acknowledgement(&mut self, ack: Acknowledgement) {
298        self.acknowledgement = ack
299    }
300
301    fn set_handled(&mut self, handled: bool) {
302        self.handled = handled
303    }
304}
305
306#[derive(Debug, PartialEq, PartialOrd, Clone, Serialize, Deserialize)]
307#[serde(from = "raw::Acknowledgement", into = "raw::Acknowledgement")]
308pub struct Acknowledgement {
309    pub state: AckState,
310    pub last_change: Option<Timestamp>,
311}
312
313#[derive(Debug, PartialEq, PartialOrd, Clone)]
314pub enum AckState {
315    None,
316    Acknowledged {
317        kind: AckKind,
318        expiry: Option<Timestamp>,
319    },
320}
321
322#[derive(Debug, PartialEq, PartialOrd, Clone)]
323pub enum AckKind {
324    Normal,
325    Sticky,
326}
327
328impl Acknowledgement {
329    /// Create a fresh, None-Acknowledgement
330    pub fn none() -> Self {
331        Acknowledgement {
332            state: AckState::None,
333            last_change: None,
334        }
335    }
336
337    pub fn is_acknowledged(&self) -> bool {
338        self.state != AckState::None
339    }
340}
341
342impl From<raw::Acknowledgement> for Acknowledgement {
343    fn from(raw_ack: raw::Acknowledgement) -> Self {
344        fn expiry(ts: Timestamp) -> Option<Timestamp> {
345            if ts.icinga_timestamp == 0_f64 {
346                None
347            } else {
348                Some(ts)
349            }
350        }
351        let state = match raw_ack.state {
352            raw::AckState::None => AckState::None,
353            raw::AckState::Normal => AckState::Acknowledged {
354                kind: AckKind::Normal,
355                expiry: expiry(raw_ack.expiry),
356            },
357            raw::AckState::Sticky => AckState::Acknowledged {
358                kind: AckKind::Sticky,
359                expiry: expiry(raw_ack.expiry),
360            },
361        };
362        Acknowledgement {
363            state,
364            last_change: raw_ack.last_change,
365        }
366    }
367}
368
369impl From<Acknowledgement> for raw::Acknowledgement {
370    fn from(ack: Acknowledgement) -> Self {
371        let (state, expiry) = match ack.state {
372            AckState::None => (raw::AckState::None, Timestamp::zero()),
373            AckState::Acknowledged {
374                kind: AckKind::Normal,
375                expiry,
376            } => (raw::AckState::Normal, expiry.unwrap_or(Timestamp::zero())),
377            AckState::Acknowledged {
378                kind: AckKind::Sticky,
379                expiry,
380            } => (raw::AckState::Sticky, expiry.unwrap_or(Timestamp::zero())),
381        };
382        let last_change = ack.last_change;
383        raw::Acknowledgement {
384            state,
385            expiry,
386            last_change,
387        }
388    }
389}
390
391pub mod raw {
392    //! Struct definitions for the `raw` variants of icinga-returned structures. These are also
393    //! *serializeable* but it return are slightly more inconvenient to work with.
394
395    use std::convert::TryFrom;
396
397    use super::Timestamp;
398    use serde::{Deserialize, Serialize};
399
400    #[derive(Debug, PartialEq, PartialOrd, Clone, Serialize, Deserialize)]
401    pub struct Acknowledgement {
402        #[serde(rename = "acknowledgement")]
403        pub state: AckState,
404        #[serde(rename = "acknowledgement_expiry")]
405        pub expiry: Timestamp,
406        #[serde(rename = "acknowledgement_last_change")]
407        pub last_change: Option<Timestamp>,
408    }
409
410    #[derive(Debug, PartialEq, PartialOrd, Eq, Ord, Clone, Serialize, Deserialize)]
411    #[serde(try_from = "f64", into = "f64")]
412    pub enum AckState {
413        None,
414        Normal,
415        Sticky,
416    }
417
418    impl TryFrom<f64> for AckState {
419        type Error = String;
420        fn try_from(f: f64) -> Result<Self, Self::Error> {
421            if f.fract() != 0_f64 || f < 0_f64 || f > 255_f64 {
422                return Err(format!("Not a valid host state number: {}", f));
423            }
424            AckState::try_from(f as u8)
425        }
426    }
427
428    impl From<AckState> for f64 {
429        fn from(s: AckState) -> Self {
430            match s {
431                AckState::None => 0_f64,
432                AckState::Normal => 1_f64,
433                AckState::Sticky => 2_f64,
434            }
435        }
436    }
437
438    impl TryFrom<u8> for AckState {
439        type Error = String;
440        fn try_from(i: u8) -> Result<Self, Self::Error> {
441            match i {
442                0 => Ok(AckState::None),
443                1 => Ok(AckState::Normal),
444                2 => Ok(AckState::Sticky),
445                _ => Err(format!("Invalid acknowledgement state number: {}", i)),
446            }
447        }
448    }
449}
450
451#[cfg(test)]
452mod test {
453
454    #[cfg(test)]
455    use pretty_assertions::assert_eq;
456
457    use super::*;
458    use chrono::Local;
459    use serde_json::Value;
460
461    static HOST_OBJ: &[u8] = include_bytes!("../test-input/example-host-object.json");
462    static SERVICE_OBJ: &[u8] = include_bytes!("../test-input/example-service-object.json");
463
464    #[test]
465    fn test_timestamp_localtime() {
466        let icinga_timestamp = 1584180320.814572_f64;
467        assert_eq!(
468            Timestamp::new(icinga_timestamp).localtime(),
469            Local.timestamp(1584180320, 814572095)
470        );
471
472        assert_eq!(
473            Timestamp::new(1583917754.192786_f64).localtime(),
474            Local.timestamp(1583917754, 192785978)
475        );
476
477        assert_eq!(
478            Timestamp::new(1583917754.192786111_f64).localtime(),
479            Local.timestamp(1583917754, 192786216)
480        );
481    }
482
483    #[test]
484    fn test_de_host_object() {
485        let r: Host = serde_json::from_slice(HOST_OBJ).unwrap();
486        assert_eq!(
487            r,
488            Host {
489                name: "example-host".to_string(),
490                display_name: "example-host".to_string(),
491                address: "example-host.de".to_string(),
492                address6: "".to_string(),
493                state: HostState::UP,
494                last_state: HostState::UP,
495                last_hard_state: HostState::UP,
496                last_state_change: Timestamp::new(1584154326.317929_f64),
497                last_state_up: Timestamp::new(1584180320.814572_f64),
498                last_state_down: Timestamp::new(1584154292.773004_f64),
499                next_check: Timestamp::new(1584180380.234708_f64),
500                last_check_result: Some(LastCheckResult {
501                    output: "PING OK - Packet loss = 0%, RTA = 23.24 ms".to_string()
502                }),
503
504                acknowledgement: Acknowledgement::none(),
505
506                handled: false,
507            }
508        )
509    }
510
511    static REDUCED_HOST_OBJ: &[u8] = r#"
512{
513       "acknowledgement": 0.0,
514       "acknowledgement_expiry": 0.0,
515       "acknowledgement_last_change": null,
516       "address": "example-host.de",
517       "address6": "",
518       "display_name": "example-host",
519       "handled": false,
520       "last_check_result": {
521         "output": "PING OK - Packet loss = 0%, RTA = 23.24 ms"
522       },
523       "last_hard_state": 0.0,
524       "last_state": 0.0,
525       "last_state_change": 1584154326.317929,
526       "last_state_down": 1584154292.773004,
527       "last_state_up": 1584180320.814572,
528       "name": "example-host",
529       "next_check": 1584180380.234708,
530       "state": 0.0
531     }
532"#
533    .as_bytes();
534
535    #[test]
536    fn test_se_host_object() {
537        let host: Host = Host {
538            name: "example-host".to_string(),
539            display_name: "example-host".to_string(),
540            address: "example-host.de".to_string(),
541            address6: "".to_string(),
542            state: HostState::UP,
543            last_state: HostState::UP,
544            last_hard_state: HostState::UP,
545            last_state_change: Timestamp::new(1584154326.317929_f64),
546            last_state_up: Timestamp::new(1584180320.814572_f64),
547            last_state_down: Timestamp::new(1584154292.773004_f64),
548            next_check: Timestamp::new(1584180380.234708_f64),
549            last_check_result: Some(LastCheckResult {
550                output: "PING OK - Packet loss = 0%, RTA = 23.24 ms".to_string(),
551            }),
552
553            acknowledgement: Acknowledgement::none(),
554
555            handled: false,
556        };
557        assert_eq!(
558            serde_json::to_value(host).unwrap(),
559            serde_json::from_slice::<Value>(REDUCED_HOST_OBJ).unwrap()
560        )
561    }
562
563    #[test]
564    fn test_de_service_object() {
565        let r: Service = serde_json::from_slice(SERVICE_OBJ).unwrap();
566        assert_eq!(
567            r,
568            Service {
569                name: "example-host!example-service".to_string(),
570                display_name: "check-example-service".to_string(),
571                host_name: "example-host".to_string(),
572                state: ServiceState::OK,
573                last_state: ServiceState::OK,
574                last_hard_state: ServiceState::OK,
575                last_state_change: Timestamp::new(1583917754.192786_f64),
576                last_state_critical: Timestamp::new(0_f64),
577                last_state_ok: Timestamp::new(1584184342.288635_f64),
578                last_state_warning: Timestamp::new(1583917693.623402_f64),
579                last_state_unknown: Timestamp::new(1583860703.96808_f64),
580                next_check: Timestamp::new(1584184401.578774_f64),
581                last_check_result: Some(LastCheckResult {
582                    output: "OK: nothing wrong".to_string()
583                }),
584                acknowledgement: Acknowledgement::none(),
585                handled: false,
586                last_reachable: true,
587            }
588        )
589    }
590
591    static REDUCED_SERVICE_OBJ: &[u8] = r#"
592{
593       "__name": "example_host!example_service",
594       "acknowledgement": 0.0,
595       "acknowledgement_expiry": 0.0,
596       "acknowledgement_last_change": null,
597       "display_name": "example_service",
598       "handled": false,
599       "last_check_result": {
600         "output": "PING OK - Packet loss = 0%, RTA = 23.24 ms"
601       },
602       "host_name": "example_host",
603       "last_hard_state": 0.0,
604        "last_state": 0.0,
605        "last_state_change": 1584154326.317929,
606        "last_state_critical": 1584154292.773004,
607        "last_state_warning": 1584154292.773004,
608        "last_state_ok": 1584180320.814572,
609        "last_state_unknown": 1584154292.773004,
610        "last_reachable": true,
611       "next_check": 1584180380.234708,
612       "state": 0.0
613     }
614"#
615    .as_bytes();
616
617    #[test]
618    fn test_se_service_object() {
619        let service: Service = Service {
620            name: "example_host!example_service".to_string(),
621            host_name: "example_host".to_string(),
622            display_name: "example_service".to_string(),
623            state: ServiceState::OK,
624            last_state: ServiceState::OK,
625            last_hard_state: ServiceState::OK,
626            last_state_change: Timestamp::new(1584154326.317929_f64),
627            last_state_ok: Timestamp::new(1584180320.814572_f64),
628            last_state_warning: Timestamp::new(1584154292.773004_f64),
629            last_state_critical: Timestamp::new(1584154292.773004_f64),
630            last_state_unknown: Timestamp::new(1584154292.773004_f64),
631            last_reachable: true,
632            next_check: Timestamp::new(1584180380.234708_f64),
633            last_check_result: Some(LastCheckResult {
634                output: "PING OK - Packet loss = 0%, RTA = 23.24 ms".to_string(),
635            }),
636
637            acknowledgement: Acknowledgement::none(),
638
639            handled: false,
640        };
641        assert_eq!(
642            serde_json::to_value(service).unwrap(),
643            serde_json::from_slice::<Value>(REDUCED_SERVICE_OBJ).unwrap()
644        )
645    }
646
647    static ACK_OBJ_STR: &str = r#"
648        {
649        "acknowledgement": 1.0,
650        "acknowledgement_expiry": 0.0,
651        "acknowledgement_last_change": 1614632568.699408
652        }
653        "#;
654    static ACK_OBJ: &[u8] = ACK_OBJ_STR.as_bytes();
655    #[test]
656    fn test_de_ack_raw() {
657        let a: raw::Acknowledgement = serde_json::from_slice(ACK_OBJ).unwrap();
658        assert_eq!(
659            a,
660            raw::Acknowledgement {
661                state: raw::AckState::Normal,
662                expiry: Timestamp::zero(),
663                last_change: Some(Timestamp::new(1614632568.699408f64))
664            }
665        );
666    }
667    #[test]
668    fn test_se_ack_raw() {
669        let a: raw::Acknowledgement = raw::Acknowledgement {
670            state: raw::AckState::Normal,
671            expiry: Timestamp::zero(),
672            last_change: Some(Timestamp::new(1614632568.699408f64)),
673        };
674        assert_eq!(
675            serde_json::to_value(&a).unwrap(),
676            serde_json::from_slice::<Value>(ACK_OBJ).unwrap()
677        );
678    }
679    #[test]
680    fn test_de_ack() {
681        let a: Acknowledgement = serde_json::from_slice(ACK_OBJ).unwrap();
682        assert_eq!(
683            a,
684            Acknowledgement {
685                last_change: Some(Timestamp::new(1614632568.699408f64)),
686                state: AckState::Acknowledged {
687                    kind: AckKind::Normal,
688                    expiry: None
689                },
690            }
691        );
692
693        assert!(a.is_acknowledged(), "Should be acknowledged");
694    }
695
696    #[test]
697    fn test_se_ack() {
698        let a: Acknowledgement = Acknowledgement {
699            last_change: Some(Timestamp::new(1614632568.699408f64)),
700            state: AckState::Acknowledged {
701                kind: AckKind::Normal,
702                expiry: None,
703            },
704        };
705        assert_eq!(
706            serde_json::to_value(a).unwrap(),
707            serde_json::from_slice::<Value>(ACK_OBJ).unwrap()
708        );
709    }
710
711    static ACK_EXP_OBJ: &[u8] = r#"
712        {
713        "acknowledgement": 1.0,
714        "acknowledgement_expiry": 1714632568.699408,
715        "acknowledgement_last_change": 1614632568.699408
716        }
717        "#
718    .as_bytes();
719
720    #[test]
721    fn test_de_ack_raw_exp() {
722        let expected_expiry = Timestamp::new(1714632568.699408f64);
723        let a: raw::Acknowledgement = serde_json::from_slice(ACK_EXP_OBJ).unwrap();
724        assert_eq!(
725            a,
726            raw::Acknowledgement {
727                state: raw::AckState::Normal,
728                expiry: expected_expiry,
729                last_change: Some(Timestamp::new(1614632568.699408f64))
730            }
731        );
732    }
733
734    #[test]
735    fn test_de_ack_exp() {
736        let expected_expiry = Timestamp::new(1714632568.699408f64);
737        let a: Acknowledgement = serde_json::from_slice(ACK_EXP_OBJ).unwrap();
738        assert_eq!(
739            a,
740            Acknowledgement {
741                last_change: Some(Timestamp::new(1614632568.699408f64)),
742                state: AckState::Acknowledged {
743                    kind: AckKind::Normal,
744                    expiry: Some(expected_expiry)
745                },
746            }
747        );
748
749        assert!(a.is_acknowledged(), "Should be acknowledged");
750    }
751
752    static NOT_ACK_OBJ: &[u8] = r#"
753        {
754        "acknowledgement": 0.0,
755        "acknowledgement_expiry": 0.0,
756        "acknowledgement_last_change": 1614632568.699408
757        }
758        "#
759    .as_bytes();
760
761    #[test]
762    fn test_de_not_ack_raw() {
763        let a: raw::Acknowledgement = serde_json::from_slice(NOT_ACK_OBJ).unwrap();
764        assert_eq!(
765            a,
766            raw::Acknowledgement {
767                state: raw::AckState::None,
768                expiry: Timestamp::zero(),
769                last_change: Some(Timestamp::new(1614632568.699408_f64))
770            }
771        );
772    }
773
774    #[test]
775    fn test_de_not_ack() {
776        let a: Acknowledgement = serde_json::from_slice(NOT_ACK_OBJ).unwrap();
777        assert_eq!(
778            a,
779            Acknowledgement {
780                state: AckState::None,
781                last_change: Some(Timestamp::new(1614632568.699408_f64))
782            }
783        );
784        assert!(!a.is_acknowledged(), "Should not be acknowledged")
785    }
786}