218 lines
7.5 KiB
Go
218 lines
7.5 KiB
Go
package main
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/vmware/govmomi/vim25/types"
|
|
)
|
|
|
|
// aged builds a snapshot entry that was taken so many days ago.
|
|
func aged(name string, days int) snapEntry {
|
|
when := time.Now().Add(-time.Duration(days) * 24 * time.Hour)
|
|
return snapEntry{
|
|
ref: types.ManagedObjectReference{Type: "VirtualMachineSnapshot", Value: "snapshot-" + name},
|
|
name: name,
|
|
when: when,
|
|
created: when.Local().Format("02.01.2006 15:04"),
|
|
}
|
|
}
|
|
|
|
// A machine with nothing wrong with it says nothing. This is the one that
|
|
// matters: the whole point of the filter is that it is short.
|
|
func TestAHealthyMachineHasNoIssues(t *testing.T) {
|
|
r := testRow("web01", true, "10.0.0.5")
|
|
if got := r.issues(); len(got) > 0 {
|
|
t.Errorf("a healthy machine reported %v", got)
|
|
}
|
|
if r.hasIssues() {
|
|
t.Error("a healthy machine is in the issues list")
|
|
}
|
|
if r.issueColor() != colOff {
|
|
t.Error("a healthy machine's reason is coloured as though it had one")
|
|
}
|
|
}
|
|
|
|
// A machine that is switched off is not a fault, and the things that are only
|
|
// true of a running machine are not held against a stopped one.
|
|
func TestAStoppedMachineIsNotAnIssue(t *testing.T) {
|
|
r := testRow("web01", false, "10.0.0.5")
|
|
r.vm.Guest.ToolsRunningStatus = "guestToolsNotRunning"
|
|
r.vm.Guest.Disk = []types.GuestDiskInfo{{DiskPath: "/", Capacity: 100, FreeSpace: 1}}
|
|
|
|
if got := r.issues(); len(got) > 0 {
|
|
t.Errorf("a stopped machine reported %v", got)
|
|
}
|
|
}
|
|
|
|
func TestIssuesFound(t *testing.T) {
|
|
for _, c := range []struct {
|
|
what string
|
|
bend func(*vmRow)
|
|
want string
|
|
bad bool
|
|
}{
|
|
{"disconnected", func(r *vmRow) {
|
|
r.vm.Summary.Runtime.ConnectionState = types.VirtualMachineConnectionStateDisconnected
|
|
}, "disconnected", true},
|
|
{"orphaned", func(r *vmRow) {
|
|
r.vm.Summary.Runtime.ConnectionState = types.VirtualMachineConnectionStateOrphaned
|
|
}, "orphaned", true},
|
|
{"a question", func(r *vmRow) {
|
|
r.vm.Summary.Runtime.Question = &types.VirtualMachineQuestionInfo{Id: "1"}
|
|
}, "waiting for an answer", true},
|
|
{"consolidation", func(r *vmRow) {
|
|
r.vm.Summary.Runtime.ConsolidationNeeded = true
|
|
}, "consolidating", true},
|
|
{"a red status", func(r *vmRow) {
|
|
r.vm.Summary.OverallStatus = types.ManagedEntityStatusRed
|
|
}, "vCenter says red", true},
|
|
{"a yellow status", func(r *vmRow) {
|
|
r.vm.Summary.OverallStatus = types.ManagedEntityStatusYellow
|
|
}, "vCenter says yellow", false},
|
|
{"no tools", func(r *vmRow) {
|
|
r.vm.Guest.ToolsRunningStatus = "guestToolsNotRunning"
|
|
r.vm.Summary.Guest = &types.VirtualMachineGuestSummary{ToolsRunningStatus: "guestToolsNotRunning"}
|
|
}, "no VMware Tools", false},
|
|
{"a full filesystem", func(r *vmRow) {
|
|
r.vm.Guest.Disk = []types.GuestDiskInfo{{DiskPath: "/var", Capacity: 100 << 30, FreeSpace: 3 << 30}}
|
|
}, "/var 97 % full", true},
|
|
{"a nearly full filesystem", func(r *vmRow) {
|
|
r.vm.Guest.Disk = []types.GuestDiskInfo{{DiskPath: "/boot", Capacity: 100 << 30, FreeSpace: 8 << 30}}
|
|
}, "/boot 92 % full", false},
|
|
{"an old snapshot", func(r *vmRow) {
|
|
r.snaps = []snapEntry{aged("before-patch", 63)}
|
|
}, "before-patch is 63 days old", false},
|
|
} {
|
|
r := testRow("web01", true, "10.0.0.5")
|
|
c.bend(&r)
|
|
|
|
list := r.issueList()
|
|
found := false
|
|
for _, i := range list {
|
|
if strings.Contains(i.text, c.want) {
|
|
found = true
|
|
if i.bad != c.bad {
|
|
t.Errorf("%s: bad = %v, want %v (%q)", c.what, i.bad, c.bad, i.text)
|
|
}
|
|
}
|
|
}
|
|
if !found {
|
|
t.Errorf("%s: nothing said %q, only %v", c.what, c.want, r.issues())
|
|
}
|
|
if !r.hasIssues() {
|
|
t.Errorf("%s: the machine is not in the issues list", c.what)
|
|
}
|
|
}
|
|
}
|
|
|
|
// A filesystem that is merely fairly full is nobody's business.
|
|
func TestAFilesystemWithRoomIsNotReported(t *testing.T) {
|
|
r := testRow("web01", true, "10.0.0.5")
|
|
r.vm.Guest.Disk = []types.GuestDiskInfo{{DiskPath: "/", Capacity: 100 << 30, FreeSpace: 20 << 30}}
|
|
if got := r.issues(); len(got) > 0 {
|
|
t.Errorf("a filesystem at 80 %% reported %v", got)
|
|
}
|
|
}
|
|
|
|
// A snapshot is only old once it has stopped being somebody's afternoon. The
|
|
// table colours it yellow after a week; the list of things to answer for waits
|
|
// for a month, or it fills up with this morning's work.
|
|
func TestOnlyAMonthOldSnapshotIsAnIssue(t *testing.T) {
|
|
for _, c := range []struct {
|
|
days int
|
|
want bool
|
|
}{{2, false}, {snapStaleDays + 1, false}, {snapOldDays, true}, {90, true}} {
|
|
r := testRow("web01", true, "10.0.0.5")
|
|
r.snaps = []snapEntry{aged("s", c.days)}
|
|
if got := r.hasIssues(); got != c.want {
|
|
t.Errorf("a snapshot of %d days: reported = %v, want %v (%v)",
|
|
c.days, got, c.want, r.issues())
|
|
}
|
|
}
|
|
}
|
|
|
|
// vCenter's own alarms are passed on by the name somebody gave them, and an
|
|
// alarm that has been acknowledged has been dealt with by a person already.
|
|
func TestAlarms(t *testing.T) {
|
|
ref := types.ManagedObjectReference{Type: "Alarm", Value: "alarm-14"}
|
|
yes := true
|
|
|
|
r := testRow("web01", true, "10.0.0.5")
|
|
r.vm.TriggeredAlarmState = []types.AlarmState{
|
|
{Alarm: ref, OverallStatus: types.ManagedEntityStatusRed},
|
|
}
|
|
// Without the names, the reference is still said: it is little use, but it
|
|
// is not silence.
|
|
if got := strings.Join(r.issues(), " "); !strings.Contains(got, "alarm-14") {
|
|
t.Errorf("an alarm with no name resolved reported %q", got)
|
|
}
|
|
|
|
r.sess = &session{alarms: map[types.ManagedObjectReference]string{ref: "Host memory usage"}}
|
|
if got := strings.Join(r.issues(), " "); !strings.Contains(got, "Host memory usage") {
|
|
t.Errorf("the alarm's name was not used: %q", got)
|
|
}
|
|
|
|
r.vm.TriggeredAlarmState[0].Acknowledged = &yes
|
|
if got := r.issues(); len(got) > 0 {
|
|
t.Errorf("an acknowledged alarm still reported %v", got)
|
|
}
|
|
}
|
|
|
|
// The rolled-up status is not reported next to the alarm it is the rollup of:
|
|
// the same fact twice, once with a reason and once without.
|
|
func TestTheStatusIsNotReportedTwice(t *testing.T) {
|
|
r := testRow("web01", true, "10.0.0.5")
|
|
r.vm.Summary.OverallStatus = types.ManagedEntityStatusRed
|
|
r.vm.TriggeredAlarmState = []types.AlarmState{{
|
|
Alarm: types.ManagedObjectReference{Type: "Alarm", Value: "alarm-1"},
|
|
OverallStatus: types.ManagedEntityStatusRed,
|
|
}}
|
|
|
|
if got := r.issues(); len(got) != 1 {
|
|
t.Errorf("a red machine with one alarm reported %d things: %v", len(got), got)
|
|
}
|
|
if got := strings.Join(r.issues(), " "); strings.Contains(got, "says red") {
|
|
t.Errorf("the rollup was reported beside its own alarm: %q", got)
|
|
}
|
|
}
|
|
|
|
// Worst first, because the column they end up in is truncated from the right.
|
|
func TestTheWorstReasonComesFirst(t *testing.T) {
|
|
r := testRow("web01", true, "10.0.0.5")
|
|
r.vm.Guest.ToolsRunningStatus = "guestToolsNotRunning" // a word of warning
|
|
r.vm.Summary.Runtime.ConsolidationNeeded = true // broken
|
|
|
|
list := r.issueList()
|
|
if len(list) < 2 {
|
|
t.Fatalf("expected both reasons, got %v", r.issues())
|
|
}
|
|
if !list[0].bad {
|
|
t.Errorf("the reasons came out warning first: %v", r.issues())
|
|
}
|
|
if r.issueColor() != colFull {
|
|
t.Error("a machine with something broken is not painted as broken")
|
|
}
|
|
|
|
// Only a warning: yellow, not red.
|
|
r.vm.Summary.Runtime.ConsolidationNeeded = false
|
|
if r.issueColor() != colBusy {
|
|
t.Error("a machine with only a warning is painted as broken")
|
|
}
|
|
if cell := r.issueCell(); !strings.Contains(cell, "no VMware Tools") {
|
|
t.Errorf("the reason column says %q", cell)
|
|
}
|
|
}
|
|
|
|
func TestWithIssuesKeepsOnlyTheOnesToAnswerFor(t *testing.T) {
|
|
good := testRow("web01", true, "10.0.0.5")
|
|
bad := testRow("db01", true, "10.0.0.6")
|
|
bad.vm.Summary.Runtime.ConsolidationNeeded = true
|
|
|
|
got := withIssues([]vmRow{good, bad})
|
|
if len(got) != 1 || got[0].name != "db01" {
|
|
t.Errorf("the filter kept %d machines: %v", len(got), got)
|
|
}
|
|
}
|