| 1 | package hostops |
| 2 | |
| 3 | import ( |
| 4 | "context" |
| 5 | "errors" |
| 6 | "github.com/carlosframework/platform/internal/hostclaim" |
| 7 | "github.com/carlosframework/platform/internal/release" |
| 8 | "reflect" |
| 9 | "time" |
| 10 | ) |
| 11 | |
| 12 | // ReportAutomaticLosses materializes retained consequences even after operation |
| 13 | // or holder replacement. The signed record is durable before reporting is marked. |
| 14 | func (w *ControlWriter) ReportAutomaticLosses(ctx context.Context, signer release.Signer, host string) error { |
| 15 | if recoveryWriter(ctx) != w { |
| 16 | return ErrContract |
| 17 | } |
| 18 | if err := w.lossSigner(signer); err != nil { |
| 19 | return err |
| 20 | } |
| 21 | o, err := hostclaim.Read(ctx, w.raw, host) |
| 22 | if err != nil { |
| 23 | return err |
| 24 | } |
| 25 | if o.Absent { |
| 26 | return nil |
| 27 | } |
| 28 | for _, l := range o.Claim.EvidenceRefs { |
| 29 | if l.Reported { |
| 30 | continue |
| 31 | } |
| 32 | record, err := hostclaim.ReadLossRecord(ctx, w.raw, w.pub, l.ID) |
| 33 | if errors.Is(err, hostclaim.ErrLossRecordAbsent) { |
| 34 | at, e := time.Parse(time.RFC3339Nano, l.ObservedAt) |
| 35 | if e != nil { |
| 36 | return e |
| 37 | } |
| 38 | r := hostclaim.LossRecord{AlertsPending: true, V: 1, ID: l.ID, State: "prepared", Actor: "automatic", AcceptedAt: at, Host: host, HolderIncarnation: l.Incarnation, HolderStamp: l.Stamp, HolderSession: l.Session, ExpectedClaimGen: o.Claim.Gen, ExpectedClaimETag: o.ETag, OpID: l.Op, OpKind: l.Kind, OpPhase: l.Phase, Box: l.Box, Enrollment: l.Enrollment, FencedAt: at, Timers: hostclaim.TimerLoss{Knowledge: l.Timers}, Automatic: &l} |
| 39 | record, err = hostclaim.CreateLossRecord(ctx, w.raw, signer, r) |
| 40 | } |
| 41 | if err != nil { |
| 42 | return err |
| 43 | } |
| 44 | if !reflect.DeepEqual(record.Record.Automatic, &l) || record.Record.Host != host { |
| 45 | return hostclaim.ErrLossRecord |
| 46 | } |
| 47 | if record.Record.State == "prepared" { |
| 48 | next := record.Record |
| 49 | next.State = "committed" |
| 50 | next.ResultClaimGen = next.ExpectedClaimGen |
| 51 | record, err = hostclaim.ReplaceLossRecord(ctx, w.raw, signer, record, next) |
| 52 | if err != nil { |
| 53 | return err |
| 54 | } |
| 55 | } |
| 56 | if record.Record.State != "committed" { |
| 57 | return hostclaim.ErrLossRecord |
| 58 | } |
| 59 | o, err = hostclaim.Update(ctx, w.raw, o, func(c *hostclaim.Claim) error { |
| 60 | for i := range c.EvidenceRefs { |
| 61 | if c.EvidenceRefs[i].ID == l.ID { |
| 62 | if !reflect.DeepEqual(c.EvidenceRefs[i], l) { |
| 63 | return hostclaim.ErrLossRecord |
| 64 | } |
| 65 | c.EvidenceRefs[i].Reported = true |
| 66 | return nil |
| 67 | } |
| 68 | } |
| 69 | return hostclaim.ErrLossRecord |
| 70 | }) |
| 71 | if err != nil { |
| 72 | return err |
| 73 | } |
| 74 | } |
| 75 | return nil |
| 76 | } |
| 77 | |