152 lines
4.6 KiB
Go
152 lines
4.6 KiB
Go
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
|
|
}
|