mirror of
https://github.com/fleetdm/fleet
synced 2026-05-22 16:39:01 +00:00
Added MIA status for hosts that haven't been updated for 30 days (#570)
This commit is contained in:
parent
21ec1ed1b1
commit
e6b1ed9ade
8 changed files with 159 additions and 72 deletions
|
|
@ -9,6 +9,19 @@ type TargetSearchResults struct {
|
|||
Labels []Label
|
||||
}
|
||||
|
||||
// TargetMetrics contains information about the state
|
||||
// of hosts that are tracked by the app
|
||||
type TargetMetrics struct {
|
||||
TotalHosts uint
|
||||
// OnlineHosts have updated within the last 30 minutes
|
||||
OnlineHosts uint
|
||||
// OfflineHosts are hosts that haven't updated in 30 minutes
|
||||
OfflineHosts uint
|
||||
// MissingInActionHosts are hosts that haven't had an update for more
|
||||
// than thirty days
|
||||
MissingInActionHosts uint
|
||||
}
|
||||
|
||||
type TargetService interface {
|
||||
// SearchTargets will accept a search query, a slice of IDs of hosts to omit,
|
||||
// and a slice of IDs of labels to omit, and it will return a set of targets
|
||||
|
|
@ -17,8 +30,10 @@ type TargetService interface {
|
|||
|
||||
// CountHostsInTargets returns the count of hosts in the selected
|
||||
// targets. The first return uint is the total number of hosts in the
|
||||
// targets. The second return uint is the total online hosts.
|
||||
CountHostsInTargets(ctx context.Context, hostIDs []uint, labelIDs []uint) (total uint, online uint, err error)
|
||||
// targets. The second return uint is the total online hosts. The third
|
||||
// returned uint is the total number of hosts that have been offline for more
|
||||
// than 30 days. (Missing in action)
|
||||
CountHostsInTargets(ctx context.Context, hostIDs []uint, labelIDs []uint) (*TargetMetrics, error)
|
||||
}
|
||||
|
||||
type TargetType int
|
||||
|
|
|
|||
|
|
@ -1,11 +1,28 @@
|
|||
package service
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/go-kit/kit/endpoint"
|
||||
"github.com/kolide/kolide-ose/server/kolide"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
const (
|
||||
// StatusOnline host is active
|
||||
StatusOnline string = "online"
|
||||
// StatusOffline no communication with host for OfflineDuration
|
||||
StatusOffline string = "offline"
|
||||
// StatusMIA no communition with host for MIADuration
|
||||
StatusMIA string = "mia"
|
||||
// OfflineDuration if a host hasn't been in communition for this
|
||||
// period it is considered offline
|
||||
OfflineDuration time.Duration = 30 * time.Minute
|
||||
// OfflineDuration if a host hasn't been in communition for this
|
||||
// period it is considered MIA
|
||||
MIADuration time.Duration = 30 * 24 * time.Hour
|
||||
)
|
||||
|
||||
type hostResponse struct {
|
||||
kolide.Host
|
||||
Status string `json:"status"`
|
||||
|
|
|
|||
|
|
@ -16,9 +16,11 @@ type getLabelRequest struct {
|
|||
|
||||
type labelResponse struct {
|
||||
kolide.Label
|
||||
DisplayText string `json:"display_text"`
|
||||
Count uint `json:"count"`
|
||||
Online uint `json:"online"`
|
||||
DisplayText string `json:"display_text"`
|
||||
Count uint `json:"count"`
|
||||
Online uint `json:"online"`
|
||||
Offline uint `json:"offline"`
|
||||
MissingInAction uint `json:"missing_in_action"`
|
||||
}
|
||||
|
||||
type getLabelResponse struct {
|
||||
|
|
@ -35,7 +37,7 @@ func makeGetLabelEndpoint(svc kolide.Service) endpoint.Endpoint {
|
|||
if err != nil {
|
||||
return getLabelResponse{Err: err}, nil
|
||||
}
|
||||
total, online, err := svc.CountHostsInTargets(ctx, nil, []uint{label.ID})
|
||||
metrics, err := svc.CountHostsInTargets(ctx, nil, []uint{label.ID})
|
||||
if err != nil {
|
||||
return getLabelResponse{Err: err}, nil
|
||||
}
|
||||
|
|
@ -43,8 +45,10 @@ func makeGetLabelEndpoint(svc kolide.Service) endpoint.Endpoint {
|
|||
Label: labelResponse{
|
||||
*label,
|
||||
label.Name,
|
||||
total,
|
||||
online,
|
||||
metrics.TotalHosts,
|
||||
metrics.OnlineHosts,
|
||||
metrics.OfflineHosts,
|
||||
metrics.MissingInActionHosts,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
|
@ -75,7 +79,7 @@ func makeListLabelsEndpoint(svc kolide.Service) endpoint.Endpoint {
|
|||
|
||||
resp := listLabelsResponse{}
|
||||
for _, label := range labels {
|
||||
total, online, err := svc.CountHostsInTargets(ctx, nil, []uint{label.ID})
|
||||
metrics, err := svc.CountHostsInTargets(ctx, nil, []uint{label.ID})
|
||||
if err != nil {
|
||||
return listLabelsResponse{Err: err}, nil
|
||||
}
|
||||
|
|
@ -83,8 +87,10 @@ func makeListLabelsEndpoint(svc kolide.Service) endpoint.Endpoint {
|
|||
labelResponse{
|
||||
*label,
|
||||
label.Name,
|
||||
total,
|
||||
online,
|
||||
metrics.TotalHosts,
|
||||
metrics.OnlineHosts,
|
||||
metrics.OfflineHosts,
|
||||
metrics.MissingInActionHosts,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
|
@ -114,7 +120,7 @@ func makeCreateLabelEndpoint(svc kolide.Service) endpoint.Endpoint {
|
|||
if err != nil {
|
||||
return createLabelResponse{Err: err}, nil
|
||||
}
|
||||
total, online, err := svc.CountHostsInTargets(ctx, nil, []uint{label.ID})
|
||||
metrics, err := svc.CountHostsInTargets(ctx, nil, []uint{label.ID})
|
||||
if err != nil {
|
||||
return createLabelResponse{Err: err}, nil
|
||||
}
|
||||
|
|
@ -122,8 +128,10 @@ func makeCreateLabelEndpoint(svc kolide.Service) endpoint.Endpoint {
|
|||
Label: labelResponse{
|
||||
*label,
|
||||
label.Name,
|
||||
total,
|
||||
online,
|
||||
metrics.TotalHosts,
|
||||
metrics.OnlineHosts,
|
||||
metrics.OfflineHosts,
|
||||
metrics.MissingInActionHosts,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,9 +25,10 @@ type hostSearchResult struct {
|
|||
|
||||
type labelSearchResult struct {
|
||||
kolide.Label
|
||||
DisplayText string `json:"display_text"`
|
||||
Count uint `json:"count"`
|
||||
Online uint `json:"online"`
|
||||
DisplayText string `json:"display_text"`
|
||||
Count uint `json:"count"`
|
||||
Online uint `json:"online"`
|
||||
MissingInAction uint `json:"missing_in_action"`
|
||||
}
|
||||
|
||||
type targetsData struct {
|
||||
|
|
@ -36,10 +37,11 @@ type targetsData struct {
|
|||
}
|
||||
|
||||
type searchTargetsResponse struct {
|
||||
Targets *targetsData `json:"targets,omitempty"`
|
||||
SelectedTargetsCount uint `json:"selected_targets_count"`
|
||||
SelectedTargetsOnline uint `json:"selected_targets_online"`
|
||||
Err error `json:"error,omitempty"`
|
||||
Targets *targetsData `json:"targets,omitempty"`
|
||||
SelectedTargetsCount uint `json:"selected_targets_count"`
|
||||
SelectedTargetsOnline uint `json:"selected_targets_online"`
|
||||
SelectedTargetsMissingInAction uint `json:"selected_targets_missing_in_action"`
|
||||
Err error `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
func (r searchTargetsResponse) error() error { return r.Err }
|
||||
|
|
@ -68,7 +70,7 @@ func makeSearchTargetsEndpoint(svc kolide.Service) endpoint.Endpoint {
|
|||
}
|
||||
|
||||
for _, label := range results.Labels {
|
||||
total, online, err := svc.CountHostsInTargets(ctx, nil, []uint{label.ID})
|
||||
metrics, err := svc.CountHostsInTargets(ctx, nil, []uint{label.ID})
|
||||
if err != nil {
|
||||
return searchTargetsResponse{Err: err}, nil
|
||||
}
|
||||
|
|
@ -76,21 +78,23 @@ func makeSearchTargetsEndpoint(svc kolide.Service) endpoint.Endpoint {
|
|||
labelSearchResult{
|
||||
label,
|
||||
label.Name,
|
||||
total,
|
||||
online,
|
||||
metrics.TotalHosts,
|
||||
metrics.OnlineHosts,
|
||||
metrics.MissingInActionHosts,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
total, online, err := svc.CountHostsInTargets(ctx, req.Selected.Hosts, req.Selected.Labels)
|
||||
metrics, err := svc.CountHostsInTargets(ctx, req.Selected.Hosts, req.Selected.Labels)
|
||||
if err != nil {
|
||||
return searchTargetsResponse{Err: err}, nil
|
||||
}
|
||||
|
||||
return searchTargetsResponse{
|
||||
Targets: targets,
|
||||
SelectedTargetsCount: total,
|
||||
SelectedTargetsOnline: online,
|
||||
Targets: targets,
|
||||
SelectedTargetsCount: metrics.TotalHosts,
|
||||
SelectedTargetsOnline: metrics.OnlineHosts,
|
||||
SelectedTargetsMissingInAction: metrics.MissingInActionHosts,
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -63,8 +63,10 @@ func (svc service) NewDistributedQueryCampaign(ctx context.Context, queryString
|
|||
}
|
||||
|
||||
type targetTotals struct {
|
||||
Total uint `json:"count"`
|
||||
Online uint `json:"online"`
|
||||
Total uint `json:"count"`
|
||||
Online uint `json:"online"`
|
||||
Offline uint `json:"offline"`
|
||||
MissingInAction uint `json:"missing_in_action"`
|
||||
}
|
||||
|
||||
func (svc service) StreamCampaignResults(ctx context.Context, conn *websocket.Conn, campaignID uint) {
|
||||
|
|
@ -126,16 +128,20 @@ func (svc service) StreamCampaignResults(ctx context.Context, conn *websocket.Co
|
|||
}
|
||||
}
|
||||
|
||||
var totals targetTotals
|
||||
totals.Total, totals.Online, err = svc.CountHostsInTargets(
|
||||
context.Background(), hostIDs, labelIDs,
|
||||
)
|
||||
metrics, err := svc.CountHostsInTargets(context.Background(), hostIDs, labelIDs)
|
||||
if err != nil {
|
||||
if err = conn.WriteJSONError("error retrieving target counts"); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
totals := targetTotals{
|
||||
Total: metrics.TotalHosts,
|
||||
Online: metrics.OnlineHosts,
|
||||
Offline: metrics.OfflineHosts,
|
||||
MissingInAction: metrics.MissingInActionHosts,
|
||||
}
|
||||
|
||||
if err = conn.WriteJSONMessage("totals", totals); err != nil {
|
||||
return
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,6 @@
|
|||
package service
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/kolide/kolide-ose/server/kolide"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
|
@ -16,11 +14,14 @@ func (svc service) GetHost(ctx context.Context, id uint) (*kolide.Host, error) {
|
|||
}
|
||||
|
||||
func (svc service) HostStatus(ctx context.Context, host kolide.Host) string {
|
||||
if host.UpdatedAt.Add(30 * time.Minute).Before(svc.clock.Now()) {
|
||||
return "offline"
|
||||
} else {
|
||||
return "online"
|
||||
if host.UpdatedAt.Add(OfflineDuration).Before(svc.clock.Now()) {
|
||||
if host.UpdatedAt.Add(MIADuration).Before(svc.clock.Now()) {
|
||||
return StatusMIA
|
||||
}
|
||||
return StatusOffline
|
||||
}
|
||||
|
||||
return StatusOnline
|
||||
}
|
||||
|
||||
func (svc service) DeleteHost(ctx context.Context, id uint) error {
|
||||
|
|
|
|||
|
|
@ -23,30 +23,39 @@ func (svc service) SearchTargets(ctx context.Context, query string, selectedHost
|
|||
return results, nil
|
||||
}
|
||||
|
||||
func (svc service) CountHostsInTargets(ctx context.Context, hostIDs []uint, labelIDs []uint) (total uint, online uint, err error) {
|
||||
func (svc service) CountHostsInTargets(ctx context.Context, hostIDs []uint, labelIDs []uint) (*kolide.TargetMetrics, error) {
|
||||
hosts, err := svc.ds.ListUniqueHostsInLabels(labelIDs)
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, id := range hostIDs {
|
||||
h, err := svc.ds.Host(id)
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
return nil, err
|
||||
}
|
||||
hosts = append(hosts, *h)
|
||||
}
|
||||
|
||||
hostLookup := map[uint]bool{}
|
||||
online = uint(0)
|
||||
|
||||
result := &kolide.TargetMetrics{}
|
||||
|
||||
for _, host := range hosts {
|
||||
if !hostLookup[host.ID] {
|
||||
hostLookup[host.ID] = true
|
||||
if svc.HostStatus(ctx, host) == "online" {
|
||||
online++
|
||||
switch svc.HostStatus(ctx, host) {
|
||||
case StatusOnline:
|
||||
result.OnlineHosts++
|
||||
case StatusOffline:
|
||||
result.OfflineHosts++
|
||||
case StatusMIA:
|
||||
result.MissingInActionHosts++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return uint(len(hostLookup)), online, nil
|
||||
result.TotalHosts = uint(len(hostLookup))
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -96,6 +96,15 @@ func TestCountHostsInTargets(t *testing.T) {
|
|||
require.Nil(t, err)
|
||||
require.Nil(t, ds.MarkHostSeen(h5, mockClock.Now()))
|
||||
|
||||
h6, err := ds.NewHost(&kolide.Host{
|
||||
HostName: "zzz.local",
|
||||
NodeKey: "6",
|
||||
UUID: "6",
|
||||
})
|
||||
require.Nil(t, err)
|
||||
const thirtyDaysAndAMinuteAgo = -1 * (30*24*60 + 1)
|
||||
require.Nil(t, ds.MarkHostSeen(h6, mockClock.Now().Add(thirtyDaysAndAMinuteAgo*time.Minute)))
|
||||
|
||||
l1, err := ds.NewLabel(&kolide.Label{
|
||||
Name: "label foo",
|
||||
Query: "query foo",
|
||||
|
|
@ -112,7 +121,7 @@ func TestCountHostsInTargets(t *testing.T) {
|
|||
require.NotZero(t, l2.ID)
|
||||
l2ID := fmt.Sprintf("%d", l2.ID)
|
||||
|
||||
for _, h := range []*kolide.Host{h1, h2, h3} {
|
||||
for _, h := range []*kolide.Host{h1, h2, h3, h6} {
|
||||
err = ds.RecordLabelQueryExecutions(h, map[string]bool{l1ID: true}, time.Now())
|
||||
assert.Nil(t, err)
|
||||
}
|
||||
|
|
@ -122,37 +131,55 @@ func TestCountHostsInTargets(t *testing.T) {
|
|||
assert.Nil(t, err)
|
||||
}
|
||||
|
||||
total, online, err := svc.CountHostsInTargets(ctx, nil, []uint{l1.ID, l2.ID})
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, uint(5), total)
|
||||
assert.Equal(t, uint(4), online)
|
||||
metrics, err := svc.CountHostsInTargets(ctx, nil, []uint{l1.ID, l2.ID})
|
||||
require.Nil(t, err)
|
||||
require.NotNil(t, metrics)
|
||||
assert.Equal(t, uint(6), metrics.TotalHosts)
|
||||
assert.Equal(t, uint(1), metrics.OfflineHosts)
|
||||
assert.Equal(t, uint(4), metrics.OnlineHosts)
|
||||
assert.Equal(t, uint(1), metrics.MissingInActionHosts)
|
||||
|
||||
total, online, err = svc.CountHostsInTargets(ctx, []uint{h1.ID, h2.ID}, []uint{l1.ID, l2.ID})
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, uint(5), total)
|
||||
assert.Equal(t, uint(4), online)
|
||||
metrics, err = svc.CountHostsInTargets(ctx, []uint{h1.ID, h2.ID}, []uint{l1.ID, l2.ID})
|
||||
require.Nil(t, err)
|
||||
require.NotNil(t, metrics)
|
||||
assert.Equal(t, uint(6), metrics.TotalHosts)
|
||||
assert.Equal(t, uint(1), metrics.OfflineHosts)
|
||||
assert.Equal(t, uint(4), metrics.OnlineHosts)
|
||||
assert.Equal(t, uint(1), metrics.MissingInActionHosts)
|
||||
|
||||
total, online, err = svc.CountHostsInTargets(ctx, []uint{h1.ID, h2.ID}, nil)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, uint(2), total)
|
||||
assert.Equal(t, uint(1), online)
|
||||
metrics, err = svc.CountHostsInTargets(ctx, []uint{h1.ID, h2.ID}, nil)
|
||||
require.Nil(t, err)
|
||||
require.NotNil(t, metrics)
|
||||
assert.Equal(t, uint(2), metrics.TotalHosts)
|
||||
assert.Equal(t, uint(1), metrics.OnlineHosts)
|
||||
assert.Equal(t, uint(1), metrics.OfflineHosts)
|
||||
assert.Equal(t, uint(0), metrics.MissingInActionHosts)
|
||||
|
||||
total, online, err = svc.CountHostsInTargets(ctx, []uint{h1.ID}, []uint{l2.ID})
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, uint(4), total)
|
||||
assert.Equal(t, uint(4), online)
|
||||
metrics, err = svc.CountHostsInTargets(ctx, []uint{h1.ID}, []uint{l2.ID})
|
||||
require.Nil(t, err)
|
||||
require.NotNil(t, metrics)
|
||||
assert.Equal(t, uint(4), metrics.TotalHosts)
|
||||
assert.Equal(t, uint(4), metrics.OnlineHosts)
|
||||
assert.Equal(t, uint(0), metrics.OfflineHosts)
|
||||
assert.Equal(t, uint(0), metrics.MissingInActionHosts)
|
||||
|
||||
total, online, err = svc.CountHostsInTargets(ctx, nil, nil)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, uint(0), total)
|
||||
assert.Equal(t, uint(0), online)
|
||||
metrics, err = svc.CountHostsInTargets(ctx, nil, nil)
|
||||
require.Nil(t, err)
|
||||
require.NotNil(t, metrics)
|
||||
assert.Equal(t, uint(0), metrics.TotalHosts)
|
||||
assert.Equal(t, uint(0), metrics.OnlineHosts)
|
||||
assert.Equal(t, uint(0), metrics.OfflineHosts)
|
||||
assert.Equal(t, uint(0), metrics.MissingInActionHosts)
|
||||
|
||||
// Advance clock so all hosts are offline
|
||||
mockClock.AddTime(1 * time.Hour)
|
||||
total, online, err = svc.CountHostsInTargets(ctx, nil, []uint{l1.ID, l2.ID})
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, uint(5), total)
|
||||
assert.Equal(t, uint(0), online)
|
||||
metrics, err = svc.CountHostsInTargets(ctx, nil, []uint{l1.ID, l2.ID})
|
||||
require.Nil(t, err)
|
||||
require.NotNil(t, metrics)
|
||||
assert.Equal(t, uint(6), metrics.TotalHosts)
|
||||
assert.Equal(t, uint(0), metrics.OnlineHosts)
|
||||
assert.Equal(t, uint(5), metrics.OfflineHosts)
|
||||
assert.Equal(t, uint(1), metrics.MissingInActionHosts)
|
||||
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue