package main // The after-upgrade check (novox/hq ADR 0508 §4), written to the run as its last step: the .pacnew and .pacsave // files the transaction left, the distribution's news since the node's previous upgrade, the units failed now // that were not failed in the record, and whether a restart is needed (the running kernel is no longer the one // its package installed). It passes when the transaction succeeded and no unit newly failed (and, with the // second part, every DKMS module is built); the rest is said and does not stop the round. The mesh restarts no // machine (ADR 0477 §5). import ( "sort" "strings" "time" ) // AfterCheck is the after-upgrade check of a run whose transaction ended, against the record taken before it // (nil when there is none: every unit failed now is then said as newly failed). func (u *Upgrades) AfterCheck(run Run, rec *Record) Check { c := Check{Pacnew: []string{}, Pacsave: []string{}, News: []NewsTitle{}, Mapped: []Mapped{}, NewlyFailed: []string{}, Unbuilt: []DKMSBuild{}} succeeded := transactionSucceeded(run) var started time.Time for _, s := range run.Steps { if s.Step == StepStarted { started = s.At } } if !started.IsZero() { if b := u.transactionBlock(run.Kind, started); b != nil { c.Pacnew = append(c.Pacnew, b.Pacnew...) c.Pacsave = append(c.Pacsave, b.Pacsave...) } } c.News, c.NewsError = u.newsSince(u.previousUpgrade(run)) healthy := true if failed, err := u.Machine.FailedUnits(); err != nil { healthy = false c.NewlyFailed = append(c.NewlyFailed, "(the failed units could not be read: "+err.Error()+")") } else { before := map[string]bool{} if rec != nil { for _, f := range rec.FailedUnits { before[f] = true } } for _, f := range failed { if !before[f] { c.NewlyFailed = append(c.NewlyFailed, f) } } } c.RunningKernel, c.InstalledKernel, c.RestartNeeded = u.restartNeeded() c.Passed = succeeded && healthy && len(c.NewlyFailed) == 0 u.afterCheckII(&c) return c } // previousUpgrade is when the node's upgrade before this run started: its previous run with a transaction. func (u *Upgrades) previousUpgrade(run Run) time.Time { runs, err := u.ownRuns() if err != nil { return time.Time{} } var last time.Time for _, r := range runs { if r.Run.Run >= run.Run || r.Kind != RunUpgrade { continue } for _, s := range r.Steps { if s.Step == StepStarted && s.At.After(last) { last = s.At } } } return last } // newsSince is the distribution's news published after a time, titles only; or why it could not be read. func (u *Upgrades) newsSince(since time.Time) ([]NewsTitle, string) { body, err := fetch(NewsFeed) if err != nil { return []NewsTitle{}, err.Error() } items, err := ParseNews(body, since) if err != nil { return []NewsTitle{}, "the feed could not be read: " + err.Error() } titles := []NewsTitle{} for _, it := range items { titles = append(titles, NewsTitle{Title: it.Title, Link: it.Link, Published: it.Published}) } return titles, "" } // kernelTag is a release's flavour, its letters alone: `7.2.8-arch1-2` is arch, `6.18.54-2-lts` is lts. func kernelTag(release string) string { return strings.Map(func(r rune) rune { if r >= 'a' && r <= 'z' { return r } return -1 }, release) } // installedKernels are the releases the installed kernel packages install, sorted. func (m *Machine) installedKernels() ([]string, error) { out, err := m.Out("pacman", "-Qq") if err != nil { return nil, err } var releases []string for _, name := range lines(out) { name = strings.TrimSpace(name) if name != "linux" && !strings.HasPrefix(name, "linux-") { continue } release, kernel, err := m.kernelRelease(name) if err != nil { return nil, err } if kernel { releases = append(releases, release) } } sort.Strings(releases) return releases, nil } // restartNeeded compares the running kernel with what its package installs now: when no installed kernel is the // running one, the one of its flavour is what a restart would boot. func (u *Upgrades) restartNeeded() (running, installed string, needed bool) { raw, err := u.Machine.ReadFile(OSRelease) if err != nil { return "(the running kernel could not be read: " + err.Error() + ")", "", false } running = strings.TrimSpace(string(raw)) kernels, err := u.Machine.installedKernels() if err != nil { return running, "(the installed kernels could not be read: " + err.Error() + ")", false } for _, k := range kernels { if k == running { return running, k, false } } for _, k := range kernels { if kernelTag(k) == kernelTag(running) { return running, k, true } } return running, "", true }