From 0edfea5a46a54f1517afbe0a86e6ac6e44cdbc02 Mon Sep 17 00:00:00 2001 From: Gerrit Date: Wed, 10 Aug 2022 11:01:58 +0200 Subject: [PATCH] Adapt commands to new provisioning FSM (#144) --- cmd/machine.go | 62 +++++++++++--- cmd/tableprinters/common.go | 12 +-- cmd/tableprinters/machine.go | 157 ++++++++++++++++++----------------- cmd/tableprinters/network.go | 2 +- cmd/tableprinters/printer.go | 2 + go.mod | 4 +- go.sum | 8 +- pkg/api/emojis.go | 25 ++++++ pkg/api/issue.go | 20 +++-- 9 files changed, 182 insertions(+), 110 deletions(-) create mode 100644 pkg/api/emojis.go diff --git a/cmd/machine.go b/cmd/machine.go index e7b31ab4..52b596de 100644 --- a/cmd/machine.go +++ b/cmd/machine.go @@ -6,6 +6,7 @@ import ( "os" "os/exec" "strings" + "time" "fmt" @@ -19,6 +20,7 @@ import ( "github.com/metal-stack/metal-lib/pkg/genericcli" "github.com/metal-stack/metal-lib/pkg/pointer" "github.com/metal-stack/metalctl/cmd/sorters" + "github.com/metal-stack/metalctl/cmd/tableprinters" "github.com/metal-stack/metalctl/pkg/api" "github.com/spf13/cobra" "github.com/spf13/viper" @@ -293,6 +295,7 @@ In case the machine did not register properly a direct ipmi console access is av machineIpmiCmd := &cobra.Command{ Use: "ipmi []", Short: `display ipmi details of the machine, if no machine ID is given all ipmi addresses are returned.`, + Long: `display ipmi details of the machine, if no machine ID is given all ipmi addresses are returned.` + "\n" + api.EmojiHelpText(), RunE: func(cmd *cobra.Command, args []string) error { return w.machineIpmi(args) }, @@ -301,6 +304,7 @@ In case the machine did not register properly a direct ipmi console access is av machineIssuesCmd := &cobra.Command{ Use: "issues", Short: `display machines which are in a potential bad state`, + Long: `display machines which are in a potential bad state` + "\n" + api.EmojiHelpText(), RunE: func(cmd *cobra.Command, args []string) error { return w.machineIssues() }, @@ -351,6 +355,7 @@ In case the machine did not register properly a direct ipmi console access is av c := c must(cmds.listCmd.RegisterFlagCompletionFunc(c.flagName, c.f)) } + cmds.listCmd.Long = cmds.listCmd.Short + "\n" + api.EmojiHelpText() machineIpmiCmd.Flags().AddFlagSet(cmds.listCmd.Flags()) @@ -1080,7 +1085,25 @@ func (c *machineCmd) machineLogs(args []string) error { return err } - return newPrinterFromCLI().Print(pointer.SafeDeref(resp.Events).Log) + err = newPrinterFromCLI().Print(pointer.SafeDeref(resp.Events).Log) + if err != nil { + return err + } + + if pointer.SafeDeref(resp.Events).LastErrorEvent != nil { + timeSince := time.Since(time.Time(resp.Events.LastErrorEvent.Time)) + if timeSince > tableprinters.LastErrorEventRelevant { + return nil + } + + fmt.Println() + fmt.Printf("Recent last error (%s ago):\n", timeSince.String()) + fmt.Println() + + return newPrinterFromCLI().Print(resp.Events.LastErrorEvent) + } + + return nil } func (c *machineCmd) machineConsole(args []string) error { @@ -1204,6 +1227,11 @@ func (c *machineCmd) machineIssues() error { return err } + err = sorters.MachineIPMISort(resp.Payload) + if err != nil { + return err + } + var ( only = viper.GetStringSlice("only") omit = viper.GetStringSlice("omit") @@ -1240,15 +1268,22 @@ func (c *machineCmd) machineIssues() error { return } - mWithIssues, ok := res[*m.ID] - if !ok { - mWithIssues = api.MachineWithIssues{ + var mWithIssues *api.MachineWithIssues + for _, machine := range res { + machine := machine + if pointer.SafeDeref(m.ID) == pointer.SafeDeref(machine.Machine.ID) { + mWithIssues = &machine + break + } + } + if mWithIssues == nil { + mWithIssues = &api.MachineWithIssues{ Machine: *m, } + res = append(res, *mWithIssues) } mWithIssues.Issues = append(mWithIssues.Issues, issue) - res[*m.ID] = mWithIssues } ) @@ -1276,17 +1311,24 @@ func (c *machineCmd) machineIssues() error { addIssue(m, api.IssueLivelinessNotAvailable) } - if m.Allocation == nil && len(m.Events.Log) > 0 && *m.Events.Log[0].Event == "Phoned Home" { + if pointer.SafeDeref(pointer.SafeDeref(m.Events).FailedMachineReclaim) { addIssue(m, api.IssueFailedMachineReclaim) } - if m.Events.IncompleteProvisioningCycles != nil && - *m.Events.IncompleteProvisioningCycles != "" && - *m.Events.IncompleteProvisioningCycles != "0" { + if pointer.SafeDeref(pointer.SafeDeref(m.Events).CrashLoop) { if m.Events != nil && len(m.Events.Log) > 0 && *m.Events.Log[0].Event == "Waiting" { // Machine which are waiting are not considered to have issues } else { - addIssue(m, api.IssueIncompleteCycles) + addIssue(m, api.IssueCrashLoop) + } + } + + if pointer.SafeDeref(m.Events).LastErrorEvent != nil { + timeSince := time.Since(time.Time(m.Events.LastErrorEvent.Time)) + if timeSince < tableprinters.LastErrorEventRelevant { + issue := api.IssueLastEventError + issue.Description = fmt.Sprintf("%s (%s ago)", issue.Description, timeSince.String()) + addIssue(m, issue) } } diff --git a/cmd/tableprinters/common.go b/cmd/tableprinters/common.go index f2dd93c2..1f2f48d0 100644 --- a/cmd/tableprinters/common.go +++ b/cmd/tableprinters/common.go @@ -9,14 +9,10 @@ import ( ) const ( - bark = "🚧" - circle = "↻" - dot = "●" - exclamationMark = "❗" - lock = "🔒" - nbr = " " - question = "❓" - skull = "💀" + LastErrorEventRelevant = 7 * 24 * time.Hour + + dot = "●" + nbr = " " ) func depth(path string) uint { diff --git a/cmd/tableprinters/machine.go b/cmd/tableprinters/machine.go index 64491d53..48484909 100644 --- a/cmd/tableprinters/machine.go +++ b/cmd/tableprinters/machine.go @@ -17,9 +17,9 @@ func (t *TablePrinter) MachineTable(data []*models.V1MachineResponse, wide bool) rows [][]string ) - header := []string{"ID", "", "", "Last Event", "When", "Age", "Hostname", "Project", "Size", "Image", "Partition"} + header := []string{"ID", "", "Last Event", "When", "Age", "Hostname", "Project", "Size", "Image", "Partition"} if wide { - header = []string{"ID", "", "Last Event", "When", "Age", "Description", "Name", "Hostname", "Project", "IPs", "Size", "Image", "Partition", "Started", "Tags", "Lock/Reserve"} + header = []string{"ID", "Last Event", "When", "Age", "Description", "Name", "Hostname", "Project", "IPs", "Size", "Image", "Partition", "Started", "Tags", "Lock/Reserve"} } for _, machine := range data { @@ -29,19 +29,6 @@ func (t *TablePrinter) MachineTable(data []*models.V1MachineResponse, wide bool) machineID = blue(machineID) } - status := pointer.SafeDeref(machine.Liveliness) - statusEmoji := "" - switch status { - case "Alive": - statusEmoji = nbr - case "Dead": - statusEmoji = skull - case "Unknown": - statusEmoji = question - default: - statusEmoji = question - } - alloc := pointer.SafeDeref(machine.Allocation) sizeID := pointer.SafeDeref(pointer.SafeDeref(machine.Size).ID) partitionID := pointer.SafeDeref(pointer.SafeDeref(machine.Partition).ID) @@ -71,49 +58,89 @@ func (t *TablePrinter) MachineTable(data []*models.V1MachineResponse, wide bool) } reserved := "" - lockEmoji := "" if *machine.State.Value != "" { reserved = fmt.Sprintf("%s:%s", *machine.State.Value, *machine.State.Description) - if *machine.State.Value == "LOCKED" { - lockEmoji = lock - } - if *machine.State.Value == "RESERVED" { - lockEmoji = bark - } } + lastEvent := "" - lastEventEmoji := "" when := "" if len(machine.Events.Log) > 0 { since := time.Since(time.Time(machine.Events.LastEventTime)) when = humanizeDuration(since) lastEvent = *machine.Events.Log[0].Event - lastEventEmoji = lastEvent } - if machine.Events.IncompleteProvisioningCycles != nil { - if *machine.Events.IncompleteProvisioningCycles != "" && *machine.Events.IncompleteProvisioningCycles != "0" { - lastEvent += " (!)" - lastEventEmoji += nbr + circle - } - } + emojis, _ := getMachineStatusEmojis(machine.Liveliness, machine.Events, machine.State) if wide { - rows = append(rows, []string{machineID, status, lastEvent, when, age, desc, name, hostname, project, ips, sizeID, image, partitionID, started, tags, reserved}) + rows = append(rows, []string{machineID, lastEvent, when, age, desc, name, hostname, project, ips, sizeID, image, partitionID, started, tags, reserved}) } else { - rows = append(rows, []string{machineID, lockEmoji, statusEmoji, lastEventEmoji, when, age, truncatedHostname, project, sizeID, image, partitionID}) + rows = append(rows, []string{machineID, emojis, lastEvent, when, age, truncatedHostname, project, sizeID, image, partitionID}) } } return header, rows, nil } +func getMachineStatusEmojis(liveliness *string, events *models.V1MachineRecentProvisioningEvents, state *models.V1MachineState) (string, string) { + var ( + emojis []string + wide []string + ) + + switch l := pointer.SafeDeref(liveliness); l { + case "Alive": + // noop + case "Dead": + emojis = append(emojis, api.Skull) + wide = append(wide, l) + case "Unknown": + emojis = append(emojis, api.Question) + wide = append(wide, l) + default: + emojis = append(emojis, api.Question) + wide = append(wide, l) + } + + if state != nil { + switch pointer.SafeDeref(state.Value) { + case "": + // noop + case "LOCKED": + emojis = append(emojis, api.Lock) + wide = append(wide, "Locked") + case "RESERVED": + emojis = append(emojis, api.Bark) + wide = append(wide, "Reserved") + } + } + + if events != nil { + if pointer.SafeDeref(events.FailedMachineReclaim) { + emojis = append(emojis, api.Ambulance) + wide = append(wide, "FailedReclaim") + } + + if events.LastErrorEvent != nil && time.Since(time.Time(events.LastErrorEvent.Time)) < LastErrorEventRelevant { + emojis = append(emojis, api.Exclamation) + wide = append(wide, "LastEventErrors") + } + + if pointer.SafeDeref(events.CrashLoop) { + emojis = append(emojis, api.Loop) + wide = append(wide, "CrashLoop") + } + } + + return strings.Join(emojis, nbr), strings.Join(wide, ", ") +} + func (t *TablePrinter) MachineIPMITable(data []*models.V1MachineIPMIResponse, wide bool) ([]string, [][]string, error) { var ( rows [][]string ) - header := []string{"ID", "Status", "Power", "IP", "Mac", "Board Part Number", "Bios Version", "BMC Version", "Size", "Partition"} + header := []string{"ID", "", "Power", "IP", "Mac", "Board Part Number", "Bios Version", "BMC Version", "Size", "Partition"} if wide { header = []string{"ID", "Status", "Power", "IP", "Mac", "Board Part Number", "Chassis Serial", "Product Serial", "Bios Version", "BMC Version", "Size", "Partition"} } @@ -123,20 +150,6 @@ func (t *TablePrinter) MachineIPMITable(data []*models.V1MachineIPMIResponse, wi partition := pointer.SafeDeref(pointer.SafeDeref(machine.Partition).ID) size := pointer.SafeDeref(pointer.SafeDeref(machine.Size).ID) - statusEmoji := "" - if machine.Liveliness != nil { - switch *machine.Liveliness { - case "Alive": - statusEmoji = nbr - case "Dead": - statusEmoji = skull - case "Unknown": - statusEmoji = question - default: - statusEmoji = question - } - } - ipAddress := "" mac := "" bpn := "" @@ -168,10 +181,12 @@ func (t *TablePrinter) MachineIPMITable(data []*models.V1MachineIPMIResponse, wi biosVersion = pointer.SafeDeref(bios.Version) } + emojis, wideEmojis := getMachineStatusEmojis(machine.Liveliness, machine.Events, machine.State) + if wide { - rows = append(rows, []string{id, statusEmoji, powerText, ipAddress, mac, bpn, cs, ps, biosVersion, bmcVersion, size, partition}) + rows = append(rows, []string{id, wideEmojis, powerText, ipAddress, mac, bpn, cs, ps, biosVersion, bmcVersion, size, partition}) } else { - rows = append(rows, []string{id, statusEmoji, power, ipAddress, mac, bpn, biosVersion, bmcVersion, size, partition}) + rows = append(rows, []string{id, emojis, power, ipAddress, mac, bpn, biosVersion, bmcVersion, size, partition}) } } @@ -204,7 +219,15 @@ func (t *TablePrinter) MachineLogsTable(data []*models.V1MachineProvisioningEven ) for _, i := range data { - rows = append(rows, []string{time.Time(i.Time).String(), pointer.SafeDeref(i.Event), i.Message}) + msg := i.Message + if !wide { + split := strings.Split(msg, "\n") + if len(split) > 1 { + msg = split[0] + " " + genericcli.TruncateElipsis + } + msg = genericcli.TruncateEnd(msg, 120) + } + rows = append(rows, []string{time.Time(i.Time).Format(time.RFC1123), pointer.SafeDeref(i.Event), msg}) } t.t.GetTable().SetAutoWrapText(false) @@ -217,12 +240,12 @@ func (t *TablePrinter) MachineIssuesTable(data api.MachineIssues, wide bool) ([] rows [][]string ) - header := []string{"ID", "Power", "Allocated", "Lock", "Lock Reason", "Status", "Last Event", "When", "Issues"} + header := []string{"ID", "Power", "Allocated", "", "Lock Reason", "Last Event", "When", "Issues"} if wide { - header = []string{"ID", "Name", "Partition", "Project", "Power", "Status", "State", "Lock Reason", "Last Event", "When", "Issues"} + header = []string{"ID", "Name", "Partition", "Project", "Power", "State", "Lock Reason", "Last Event", "When", "Issues"} } - for id, machineWithIssues := range data { + for _, machineWithIssues := range data { machine := machineWithIssues.Machine widename := "" @@ -243,30 +266,10 @@ func (t *TablePrinter) MachineIssuesTable(data api.MachineIssues, wide bool) ([] allocated = "yes" } - status := pointer.SafeDeref(machine.Liveliness) - statusEmoji := "" - switch status { - case "Alive": - statusEmoji = nbr - case "Dead": - statusEmoji = skull - case "Unknown": - statusEmoji = question - default: - statusEmoji = question - } - - lockEmoji := "" lockText := "" lockDesc := "" lockDescWide := "" if machine.State != nil && machine.State.Value != nil && *machine.State.Value != "" { - if *machine.State.Value == "LOCKED" { - lockEmoji = lock - } - if *machine.State.Value == "RESERVED" { - lockEmoji = bark - } lockText = *machine.State.Value } if machine.State != nil && machine.State.Value != nil && *machine.State.Description != "" { @@ -278,12 +281,10 @@ func (t *TablePrinter) MachineIssuesTable(data api.MachineIssues, wide bool) ([] when := "" lastEvent := "" - lastEventEmoji := "" if len(machine.Events.Log) > 0 { since := time.Since(time.Time(machine.Events.LastEventTime)) when = humanizeDuration(since) lastEvent = *machine.Events.Log[0].Event - lastEventEmoji = lastEvent } var issues []string @@ -295,10 +296,12 @@ func (t *TablePrinter) MachineIssuesTable(data api.MachineIssues, wide bool) ([] issues = append(issues, text) } + emojis, _ := getMachineStatusEmojis(machine.Liveliness, machine.Events, machine.State) + if wide { - rows = append(rows, []string{id, widename, partition, project, powerText, status, lockText, lockDescWide, lastEvent, when, strings.Join(issues, "\n")}) + rows = append(rows, []string{pointer.SafeDeref(machine.ID), widename, partition, project, powerText, lockText, lockDescWide, lastEvent, when, strings.Join(issues, "\n")}) } else { - rows = append(rows, []string{id, power, allocated, lockEmoji, lockDesc, statusEmoji, lastEventEmoji, when, strings.Join(issues, "\n")}) + rows = append(rows, []string{pointer.SafeDeref(machine.ID), power, allocated, emojis, lockDesc, lastEvent, when, strings.Join(issues, "\n")}) } } diff --git a/cmd/tableprinters/network.go b/cmd/tableprinters/network.go index c9c97761..f21f6b96 100644 --- a/cmd/tableprinters/network.go +++ b/cmd/tableprinters/network.go @@ -81,7 +81,7 @@ func addNetwork(prefix string, n *models.V1NetworkResponse, wide bool) []string if *n.Usage.AvailablePrefixes > 0 { prefixUse := float64(*n.Usage.UsedPrefixes) / float64(*n.Usage.AvailablePrefixes) if prefixUse >= 0.9 { - shortPrefixUsage = exclamationMark + shortPrefixUsage = color.RedString(dot) } usage = fmt.Sprintf("%s\nPrefixes:%d/%d", usage, *n.Usage.UsedPrefixes, *n.Usage.AvailablePrefixes) } diff --git a/cmd/tableprinters/printer.go b/cmd/tableprinters/printer.go index 9c76146e..c868bbb4 100644 --- a/cmd/tableprinters/printer.go +++ b/cmd/tableprinters/printer.go @@ -61,6 +61,8 @@ func (t *TablePrinter) ToHeaderAndRows(data any, wide bool) ([]string, [][]strin return t.MachineIPMITable(pointer.WrapInSlice(d), wide) case []*models.V1MachineProvisioningEvent: return t.MachineLogsTable(d, wide) + case *models.V1MachineProvisioningEvent: + return t.MachineLogsTable(pointer.WrapInSlice(d), wide) case *models.V1FirmwaresResponse: return t.FirmwareTable(d, wide) case *models.V1FilesystemLayoutResponse: diff --git a/go.mod b/go.mod index 97be81d8..aa48df76 100644 --- a/go.mod +++ b/go.mod @@ -5,8 +5,8 @@ go 1.18 require ( github.com/dustin/go-humanize v1.0.0 github.com/fatih/color v1.13.0 - github.com/metal-stack/metal-go v0.18.5 - github.com/metal-stack/metal-lib v0.10.1-0.20220720131928-d4393229d6ae + github.com/metal-stack/metal-go v0.18.6-0.20220809101900-6c94eb580780 + github.com/metal-stack/metal-lib v0.10.1-0.20220809104615-84e6267b5546 github.com/metal-stack/updater v1.1.3 github.com/metal-stack/v v1.0.3 github.com/spf13/cobra v1.5.0 diff --git a/go.sum b/go.sum index 060ec9a9..f42149d3 100644 --- a/go.sum +++ b/go.sum @@ -301,10 +301,10 @@ github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4 github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/metal-stack/masterdata-api v0.8.12 h1:hfg+g0W1tzs/5mV9RG4+l6pAtH/SMaeEUQujx+50Z7U= github.com/metal-stack/masterdata-api v0.8.12/go.mod h1:le/cvZ9nvw9QQ/eVJSrO7rNdwMMuIrQxFalaIcv/GUg= -github.com/metal-stack/metal-go v0.18.5 h1:ySdF2S/bb+C1qdc2nmEgpRYJEDBN0OaE8DHrM+nthbs= -github.com/metal-stack/metal-go v0.18.5/go.mod h1:2JU77TT7PrN9PUwCBYreYEOeziCrYvwfzQ4DLFyKSMM= -github.com/metal-stack/metal-lib v0.10.1-0.20220720131928-d4393229d6ae h1:yf2lBz2Sj0a8dvR5dgFEClddxDTKKolxLAXq/kbpWtc= -github.com/metal-stack/metal-lib v0.10.1-0.20220720131928-d4393229d6ae/go.mod h1:nvLSxC4juyUpZ0enJHdIIxG0zk8BNWHO9vIHefRRdfE= +github.com/metal-stack/metal-go v0.18.6-0.20220809101900-6c94eb580780 h1:dkBbfp0rt9qvjBk/qCSNP+ooEBqXvCvWplRom/7QIrM= +github.com/metal-stack/metal-go v0.18.6-0.20220809101900-6c94eb580780/go.mod h1:2JU77TT7PrN9PUwCBYreYEOeziCrYvwfzQ4DLFyKSMM= +github.com/metal-stack/metal-lib v0.10.1-0.20220809104615-84e6267b5546 h1:XhOkTdG3cMtthHT7Sizzw4WaVmDyvvyjxCBGD8LOJWM= +github.com/metal-stack/metal-lib v0.10.1-0.20220809104615-84e6267b5546/go.mod h1:v+DrfAXaIjPQWglCIt+znHikBp+YBKESAwvBxPcI8qo= github.com/metal-stack/security v0.6.4 h1:nCr1Hf2a4qWRCBsNCifPA4Ic4sHfQlrGjxUvksWcRvM= github.com/metal-stack/security v0.6.4/go.mod h1:vP9TLOtIETfvqq6FvvYSHCKHrDXomuba2dSV3pS6BO0= github.com/metal-stack/updater v1.1.3 h1:6RyjitHZFmLo6Mr1sgD4bbFS3v4ZWKaMM+ZDDtOvdIE= diff --git a/pkg/api/emojis.go b/pkg/api/emojis.go new file mode 100644 index 00000000..d3481148 --- /dev/null +++ b/pkg/api/emojis.go @@ -0,0 +1,25 @@ +package api + +const ( + Ambulance = "🚑" + Exclamation = "❗" + Bark = "🚧" + Loop = "⭕" + Lock = "🔒" + Question = "❓" + Skull = "💀" +) + +func EmojiHelpText() string { + return ` +Meaning of the emojis: + +🚧 Machine is reserved. Reserved machines are not considered for random allocation until the reservation flag is removed. +🔒 Machine is locked. Locked machines can not be deleted until the lock is removed. +💀 Machine is dead. The metal-api does not receive any events from this machine. +❗ Machine has a last event error. The machine has recently encountered an error during the provisioning lifecycle. +❓ Machine is in unknown condition. The metal-api does not receive phoned home events anymore or has never booted successfully. +⭕ Machine is in a provisioning crash loop. Flag can be reset through an API-triggered reboot or when the machine reaches the phoned home state. +🚑 Machine reclaim has failed. The machine was deleted but it is not going back into the available machine pool. +` +} diff --git a/pkg/api/issue.go b/pkg/api/issue.go index 8cfe2a19..5a6a516f 100644 --- a/pkg/api/issue.go +++ b/pkg/api/issue.go @@ -13,7 +13,7 @@ type ( Issues Issues } // MachineIssues is map of a machine response to a list of machine issues - MachineIssues map[string]MachineWithIssues + MachineIssues []MachineWithIssues // Issue formulates an issue of a machine Issue struct { @@ -26,8 +26,6 @@ type ( ) var ( - circle = "↻" - IssueNoPartition = Issue{ ShortName: "no-partition", Description: "machine with no partition", @@ -52,10 +50,10 @@ var ( Description: "machine phones home but not allocated", RefURL: "https://docs.metal-stack.io/stable/installation/troubleshoot/#failed-machine-reclaim", } - IssueIncompleteCycles = Issue{ - ShortName: "incomplete-cycles", - Description: fmt.Sprintf("machine has an incomplete lifecycle (%s)", circle), - RefURL: "https://docs.metal-stack.io/stable/installation/troubleshoot/#incomplete-cycles", + IssueCrashLoop = Issue{ + ShortName: "crashloop", + Description: fmt.Sprintf("machine is in a provisioning crash loop (%s)", Loop), + RefURL: "https://docs.metal-stack.io/stable/installation/troubleshoot/#crashloop", } IssueASNUniqueness = Issue{ ShortName: "asn-not-unique", @@ -77,6 +75,11 @@ var ( Description: "BMC IP address is not distinct", RefURL: "https://docs.metal-stack.io/stable/installation/troubleshoot/#bmc-no-distinct-ip", } + IssueLastEventError = Issue{ + ShortName: "last-event-error", + Description: "the machine had an error during the provisioning lifecycle", + RefURL: "https://docs.metal-stack.io/stable/installation/troubleshoot/#last-event-error", + } AllIssues = Issues{ IssueNoPartition, @@ -84,10 +87,11 @@ var ( IssueLivelinessUnknown, IssueLivelinessNotAvailable, IssueFailedMachineReclaim, - IssueIncompleteCycles, + IssueCrashLoop, IssueASNUniqueness, IssueBMCWithoutMAC, IssueBMCWithoutIP, IssueNonDistinctBMCIP, + IssueLastEventError, } )