add new status to host summary endpoint (#1057)

new_count shows all hosts that have been added to kolide in the last
24 hours
This commit is contained in:
Victor Vrantchan 2017-01-20 08:57:47 -05:00 committed by GitHub
parent 5f6f9388cd
commit eac718e937
6 changed files with 76 additions and 20 deletions

View file

@ -501,11 +501,12 @@ func testDistributedQueriesForHost(t *testing.T, ds kolide.Datastore) {
func testGenerateHostStatusStatistics(t *testing.T, ds kolide.Datastore) { func testGenerateHostStatusStatistics(t *testing.T, ds kolide.Datastore) {
mockClock := clock.NewMockClock() mockClock := clock.NewMockClock()
online, offline, mia, err := ds.GenerateHostStatusStatistics(mockClock.Now()) online, offline, mia, new, err := ds.GenerateHostStatusStatistics(mockClock.Now())
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, uint(0), online) assert.Equal(t, uint(0), online)
assert.Equal(t, uint(0), offline) assert.Equal(t, uint(0), offline)
assert.Equal(t, uint(0), mia) assert.Equal(t, uint(0), mia)
assert.Equal(t, uint(0), new)
// Online // Online
_, err = ds.NewHost(&kolide.Host{ _, err = ds.NewHost(&kolide.Host{
@ -515,6 +516,9 @@ func testGenerateHostStatusStatistics(t *testing.T, ds kolide.Datastore) {
NodeKey: "1", NodeKey: "1",
DetailUpdateTime: mockClock.Now(), DetailUpdateTime: mockClock.Now(),
SeenTime: mockClock.Now(), SeenTime: mockClock.Now(),
UpdateCreateTimestamps: kolide.UpdateCreateTimestamps{
CreateTimestamp: kolide.CreateTimestamp{CreatedAt: mockClock.Now()},
},
}) })
assert.Nil(t, err) assert.Nil(t, err)
@ -526,6 +530,9 @@ func testGenerateHostStatusStatistics(t *testing.T, ds kolide.Datastore) {
NodeKey: "2", NodeKey: "2",
DetailUpdateTime: mockClock.Now().Add(-1 * time.Minute), DetailUpdateTime: mockClock.Now().Add(-1 * time.Minute),
SeenTime: mockClock.Now().Add(-1 * time.Minute), SeenTime: mockClock.Now().Add(-1 * time.Minute),
UpdateCreateTimestamps: kolide.UpdateCreateTimestamps{
CreateTimestamp: kolide.CreateTimestamp{CreatedAt: mockClock.Now()},
},
}) })
assert.Nil(t, err) assert.Nil(t, err)
@ -537,6 +544,9 @@ func testGenerateHostStatusStatistics(t *testing.T, ds kolide.Datastore) {
NodeKey: "3", NodeKey: "3",
DetailUpdateTime: mockClock.Now().Add(-1 * time.Hour), DetailUpdateTime: mockClock.Now().Add(-1 * time.Hour),
SeenTime: mockClock.Now().Add(-1 * time.Hour), SeenTime: mockClock.Now().Add(-1 * time.Hour),
UpdateCreateTimestamps: kolide.UpdateCreateTimestamps{
CreateTimestamp: kolide.CreateTimestamp{CreatedAt: mockClock.Now()},
},
}) })
assert.Nil(t, err) assert.Nil(t, err)
@ -548,14 +558,18 @@ func testGenerateHostStatusStatistics(t *testing.T, ds kolide.Datastore) {
NodeKey: "4", NodeKey: "4",
DetailUpdateTime: mockClock.Now().Add(-35 * (24 * time.Hour)), DetailUpdateTime: mockClock.Now().Add(-35 * (24 * time.Hour)),
SeenTime: mockClock.Now().Add(-35 * (24 * time.Hour)), SeenTime: mockClock.Now().Add(-35 * (24 * time.Hour)),
UpdateCreateTimestamps: kolide.UpdateCreateTimestamps{
CreateTimestamp: kolide.CreateTimestamp{CreatedAt: mockClock.Now()},
},
}) })
assert.Nil(t, err) assert.Nil(t, err)
online, offline, mia, err = ds.GenerateHostStatusStatistics(mockClock.Now()) online, offline, mia, new, err = ds.GenerateHostStatusStatistics(mockClock.Now())
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, uint(2), online) assert.Equal(t, uint(2), online)
assert.Equal(t, uint(1), offline) assert.Equal(t, uint(1), offline)
assert.Equal(t, uint(1), mia) assert.Equal(t, uint(1), mia)
assert.Equal(t, uint(4), new)
} }
func testMarkHostSeen(t *testing.T, ds kolide.Datastore) { func testMarkHostSeen(t *testing.T, ds kolide.Datastore) {

View file

@ -114,11 +114,15 @@ func (d *Datastore) ListHosts(opt kolide.ListOptions) ([]*kolide.Host, error) {
return hosts, nil return hosts, nil
} }
func (d *Datastore) GenerateHostStatusStatistics(now time.Time) (online, offline, mia uint, err error) { func (d *Datastore) GenerateHostStatusStatistics(now time.Time) (online, offline, mia, new uint, err error) {
d.mtx.Lock() d.mtx.Lock()
defer d.mtx.Unlock() defer d.mtx.Unlock()
for _, host := range d.hosts { for _, host := range d.hosts {
if host.IsNew(now) {
new++
}
status := host.Status(now) status := host.Status(now)
switch status { switch status {
case kolide.StatusMIA: case kolide.StatusMIA:
@ -130,7 +134,7 @@ func (d *Datastore) GenerateHostStatusStatistics(now time.Time) (online, offline
} }
} }
return online, offline, mia, nil return online, offline, mia, new, nil
} }
func (d *Datastore) EnrollHost(osQueryHostID string, nodeKeySize int) (*kolide.Host, error) { func (d *Datastore) EnrollHost(osQueryHostID string, nodeKeySize int) (*kolide.Host, error) {

View file

@ -283,7 +283,7 @@ func (d *Datastore) ListHosts(opt kolide.ListOptions) ([]*kolide.Host, error) {
return hosts, nil return hosts, nil
} }
func (d *Datastore) GenerateHostStatusStatistics(now time.Time) (online, offline, mia uint, e error) { func (d *Datastore) GenerateHostStatusStatistics(now time.Time) (online, offline, mia, new uint, e error) {
sqlStatement := ` sqlStatement := `
SELECT ( SELECT (
SELECT count(id) SELECT count(id)
@ -300,7 +300,12 @@ func (d *Datastore) GenerateHostStatusStatistics(now time.Time) (online, offline
SELECT count(id) SELECT count(id)
FROM hosts FROM hosts
WHERE DATE_ADD(seen_time, INTERVAL 30 MINUTE) > ? WHERE DATE_ADD(seen_time, INTERVAL 30 MINUTE) > ?
) AS online ) AS online,
(
SELECT count(id)
FROM hosts
WHERE DATE_ADD(created_at, INTERVAL 1 DAY) >= ?
) AS new
FROM hosts FROM hosts
LIMIT 1; LIMIT 1;
` `
@ -309,8 +314,9 @@ func (d *Datastore) GenerateHostStatusStatistics(now time.Time) (online, offline
MIA uint `db:"mia"` MIA uint `db:"mia"`
Offline uint `db:"offline"` Offline uint `db:"offline"`
Online uint `db:"online"` Online uint `db:"online"`
New uint `db:"new"`
}{} }{}
err := d.db.Get(&counts, sqlStatement, now, now, now, now) err := d.db.Get(&counts, sqlStatement, now, now, now, now, now)
if err != nil && err != sql.ErrNoRows { if err != nil && err != sql.ErrNoRows {
e = errors.Wrap(err, "generating host statistics") e = errors.Wrap(err, "generating host statistics")
return return
@ -319,7 +325,8 @@ func (d *Datastore) GenerateHostStatusStatistics(now time.Time) (online, offline
mia = counts.MIA mia = counts.MIA
offline = counts.Offline offline = counts.Offline
online = counts.Online online = counts.Online
return online, offline, mia, nil new = counts.New
return online, offline, mia, new, nil
} }
// Optimized network interface fetch for sets of hosts. Instead of looping // Optimized network interface fetch for sets of hosts. Instead of looping

View file

@ -9,18 +9,26 @@ import (
) )
const ( const (
// StatusOnline host is active // StatusOnline host is active.
StatusOnline string = "online" StatusOnline = "online"
// StatusOffline no communication with host for OfflineDuration
StatusOffline string = "offline" // StatusOffline no communication with host for OfflineDuration.
// StatusMIA no communition with host for MIADuration StatusOffline = "offline"
StatusMIA string = "mia"
// StatusMIA no communication with host for MIADuration.
StatusMIA = "mia"
// NewDuration if a host has been created within this time period it's
// considered new.
NewDuration = 24 * time.Hour
// OfflineDuration if a host hasn't been in communition for this // OfflineDuration if a host hasn't been in communition for this
// period it is considered offline // period it is considered offline.
OfflineDuration time.Duration = 30 * time.Minute OfflineDuration = 30 * time.Minute
// OfflineDuration if a host hasn't been in communition for this // OfflineDuration if a host hasn't been in communition for this
// period it is considered MIA // period it is considered MIA.
MIADuration time.Duration = 30 * 24 * time.Hour MIADuration = 30 * 24 * time.Hour
) )
type HostStore interface { type HostStore interface {
@ -32,7 +40,7 @@ type HostStore interface {
EnrollHost(osqueryHostId string, nodeKeySize int) (*Host, error) EnrollHost(osqueryHostId string, nodeKeySize int) (*Host, error)
AuthenticateHost(nodeKey string) (*Host, error) AuthenticateHost(nodeKey string) (*Host, error)
MarkHostSeen(host *Host, t time.Time) error MarkHostSeen(host *Host, t time.Time) error
GenerateHostStatusStatistics(now time.Time) (online, offline, mia uint, err error) GenerateHostStatusStatistics(now time.Time) (online, offline, mia, new uint, err error)
SearchHosts(query string, omit ...uint) ([]*Host, error) SearchHosts(query string, omit ...uint) ([]*Host, error)
// DistributedQueriesForHost retrieves the distributed queries that the // DistributedQueriesForHost retrieves the distributed queries that the
// given host should run. The result map is a mapping from campaign ID // given host should run. The result map is a mapping from campaign ID
@ -92,6 +100,7 @@ type HostSummary struct {
OnlineCount uint `json:"online_count"` OnlineCount uint `json:"online_count"`
OfflineCount uint `json:"offline_count"` OfflineCount uint `json:"offline_count"`
MIACount uint `json:"mia_count"` MIACount uint `json:"mia_count"`
NewCount uint `json:"new_count"`
} }
// ResetPrimaryNetwork will determine if the PrimaryNetworkInterfaceID // ResetPrimaryNetwork will determine if the PrimaryNetworkInterfaceID
@ -148,3 +157,12 @@ func (h *Host) Status(now time.Time) string {
return StatusOnline return StatusOnline
} }
} }
func (h *Host) IsNew(now time.Time) bool {
withDuration := h.CreatedAt.Add(NewDuration)
if withDuration.After(now) ||
withDuration.Equal(now) {
return true
}
return false
}

View file

@ -61,3 +61,15 @@ func TestHostStatus(t *testing.T) {
host.SeenTime = mockClock.Now().Add(-35 * (24 * time.Hour)) // 35 days host.SeenTime = mockClock.Now().Add(-35 * (24 * time.Hour)) // 35 days
assert.Equal(t, StatusMIA, host.Status(mockClock.Now())) assert.Equal(t, StatusMIA, host.Status(mockClock.Now()))
} }
func TestHostIsNew(t *testing.T) {
mockClock := clock.NewMockClock()
host := Host{}
host.CreatedAt = mockClock.Now().AddDate(0, 0, -1)
assert.True(t, host.IsNew(mockClock.Now()))
host.CreatedAt = mockClock.Now().AddDate(0, 0, -2)
assert.False(t, host.IsNew(mockClock.Now()))
}

View file

@ -14,7 +14,7 @@ func (svc service) GetHost(ctx context.Context, id uint) (*kolide.Host, error) {
} }
func (svc service) GetHostSummary(ctx context.Context) (*kolide.HostSummary, error) { func (svc service) GetHostSummary(ctx context.Context) (*kolide.HostSummary, error) {
online, offline, mia, err := svc.ds.GenerateHostStatusStatistics(svc.clock.Now()) online, offline, mia, new, err := svc.ds.GenerateHostStatusStatistics(svc.clock.Now())
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -22,6 +22,7 @@ func (svc service) GetHostSummary(ctx context.Context) (*kolide.HostSummary, err
OnlineCount: online, OnlineCount: online,
OfflineCount: offline, OfflineCount: offline,
MIACount: mia, MIACount: mia,
NewCount: new,
}, nil }, nil
} }